My version of a Tricorder, Rads, CO2, VOCs, pressure, temp, humidity, light, sound, and magnetic. Gives real feedback via onboard AI
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
AI_Field_Analyzer_Risk_Thresholds.xlsxsheet - 9.48 kB - 06/10/2025 at 13:19 |
|
|
AI Field Analyzer.xlsxInitial BOM, do not use as there will be changessheet - 10.74 kB - 06/09/2025 at 00:55 |
|
Had some older C++ code that I was able to get converted to CircuitPython. Ran into a few issues first it was not detecting pulses, then it dawned on me the reason back in the day I went with C++ is it had code interrupts, though CircuitPython does not. So I had to basically optimize the code to update the screen less, every 3 seconds and also read the +CO2 and VOX sensor.
Current output
CO₂: 533 ppm | TVOC: 0 ppb | CPM: 5 | uSv/h: 0.0942827 | ✅ Excellent air quality. | ✅ Air is clean. | ✅ Background radiation—no risk.Pulse detected! Total: 2 CO₂: 535 ppm | TVOC: 0 ppb | CPM: 5 | uSv/h: 0.0942827 | ✅ Excellent air quality. | ✅ Air is clean. | ✅ Background radiation—no risk.Pulse detected! Total: 3 CO₂: 551 ppm | TVOC: 0 ppb | CPM: 5 | uSv/h: 0.0942827 | ✅ Excellent air quality. | ✅ Air is clean. | ✅ Background radiation—no risk..
As I have built a few Geiger's in the past I ended up getting a calibrated Cesium-137 check disk. I use this to do a calibration of the sensor. (when I get this fully built I should rent a real Geiger Counter and compare the too) This Geiger is a solid state version on a tube and will detect pulsus right at first power on however it really is only good for gamma rays and it was mentioned it was best to sample over a 2 minute time before displaying results. You can still hold it up to a source/sample and if i add the piezo you should hear detections. It the sample is very high detections I would leave it alone/walk away, if it is a mild detection there is likely no harm to stay for the full two minute cycle to get a more scientific/actuate reading
The counter is also calculated to give you a proper "uSv/h" (microsieverts per hour). Prob will need to redo the alpha calibration as it seems my last version was missing detections. However this code runs the radiation sensor at realtime.
alpha = 53.032 # uSv to CPM conversion factor
cpm = pulse_count
uSv_h = cpm / alph
GitHub link with the newest code - https://github.com/thedocdoc/AI-Field-Analyzer/tree/main I also added a helpful little program that scan the I2C Bus
I let it collect data for a bit then graphed out the data to get this: Based on the graph it might be good to do a bit of averaging on the TVOC and CO2 values, just not enough to skew the results. I.E take 3 readings in rapid / 3 = Result to print to OLED
I printed the T1 case to hold in my hands and found it boxy and uncomfortable. So, I went back and redesigned the whole thing to actually feel good, thinner overall, with big fillets at the bottom for two-handed grip. It’s amazing what holding a prototype will teach you that CAD never will.
Other upgrades:
Front hole for the radiation sensor. (I mean lets be honest we really want to be as far away as possible even if it's 4 inches)
Small window under the display for the TSL2591 High Dynamic Range Digital Light Sensor. Placed so you don’t shadow it or block it with your fingers, and it’ll double as an ambient light sensor for auto-adjusting display contrast. \o/
Larger vent near the screen: Expanded by 3–4mm in each direction, with 1mm steel wire mesh on the inside for protection. Nothing says “field-ready” like mesh that can stop a grasshopper?
Active airflow: Added a 17x17mm, 3V fan to blow air in. This means sensors react faster, and in the worst-case scenario, it just might save your bacon. (this should allow for positive air flow and also cool off internal electronics, yea I'm looking at you Google Coral Dev Mini!)
Tighter clamshell fit... You can actually pick it up and shake it now, no rattles, crazy right?
Pro tip: If you do build this, don’t even think about bringing it through TSA. Especially not with Play-Doh inside. I’m not responsible for the “sir, please step over here” moment.
Redesign after T1 design.
I swapped the Adafruit KB2040 over for a Marble Pico I had around, it is a super charged Pico with a stemmaQT port 8mb of external flash onboard, and a handy SD card slot. Then became a big issue of aligning the right I2C ports. I jumped back and forth between micropython and cicuitpython as well and settled on circuitpython as it is more supported lib wise it seems... I have yet to get logging going but I did add a startup timer/countdown for the sensor and then a "static" display on the serial output. It works sort of, and really is a bad method of doing it, seems circuitpython does not really have the same functions for clearing the serial output.
I ended up with this "AI FIELD ANALYZER v1.0 | Time: 10:08:57 | CO₂: 408 ppm | TVOC: 0 ppb | ✅ Excellent air quality, no ventilation needed. | ✅ No significant VOCs detected—air is clean...ly."
Unsure on the 64gig SD card may just be too much for it, plus I need a fat32 format as well.
Code is getting a bit long so, this is likely the last time I post it here and will be making a Github for it.
import time
import board
import busio
import adafruit_sgp30
# **Initialize I²C and Sensor**
i2c = busio.I2C(board.GP5, board.GP4)
sgp30 = adafruit_sgp30.Adafruit_SGP30(i2c)
sgp30.iaq_init()
# **Function to Classify Risk Level**
def get_warning(eCO2, TVOC):
"""Generates air quality warnings based on CO₂ & VOC readings."""
warnings = []
# **CO₂ Risk Levels**
if eCO2 < 1000:
warnings.append("✅ Excellent air quality, no ventilation needed.")
elif 1000 <= eCO2 < 2000:
warnings.append("⚠️ CO₂ levels rising—consider ventilating soon.")
else:
warnings.append("🚨 CO₂ dangerously high! Immediate ventilation needed!")
# **VOC Risk Levels**
if TVOC < 500:
warnings.append("✅ No significant VOCs detected—air is clean.")
elif 500 <= TVOC < 2000:
warnings.append("⚠️ Chemical odors detected—monitor air closely.")
else:
warnings.append("🚨 High VOC levels! Ventilation or evacuation advised!")
return warnings
# **Startup Countdown (OLED-Friendly)**
def sensor_startup_timer(seconds):
"""Displays countdown and clears it after startup."""
for i in range(seconds, 0, -1):
print("\r" + " " * 50, end="") # Clears previous text
print(f"\r⌛ Sensor Ready in {i}s...", end="") # Overwrites same line
time.sleep(1)
print("\r✅ SGP30 Sensor Ready!", end="") # Displays ready status for 2 seconds
time.sleep(2)
print("\033[2J\033[H", end="") # **Fully clears screen before measurements start**
# **Function to Update Display (OLED-Like Static Output)**
def update_display(eCO2, TVOC):
"""Refreshes display while keeping output static."""
current_time = time.localtime()
formatted_time = f"{current_time[3]:02d}:{current_time[4]:02d}:{current_time[5]:02d}"
warnings = get_warning(eCO2, TVOC)
# **Clear previous output before writing new data**
print("\r" + " " * 120, end="") # Clears previous output fully
print(f"\rAI FIELD ANALYZER v1.0 | Time: {formatted_time} | CO₂: {eCO2} ppm | TVOC: {TVOC} ppb | {warnings[0]} | {warnings[1]}", end="")
# **Run Startup Timer**
sensor_startup_timer(22) # Adjust time as needed
# **Sensor Loop**
while True:
eCO2, TVOC = sgp30.iaq_measure()
update_display(eCO2, TVOC)
time.sleep(1)
So I was able to find one of the sensors I would like to have at the local Microcenter, The SGP30 Gas sensor. Took it home and found out it was a dud, after returning the new one started reporting right away. The following readings were a very "professional" test of me lighting a paper recipe near it lol...
This is just a prototype/get it working situation, I plan on getting all of them hooked up to a Pi Pico and working together/reporting. I then can expand the code to the display. Once all of that is goo I can look at the Sensor PCB. The switch PCB however should be able to be done sooner then later as it is relatively simple.
I had a thought that once I do have them going it may be good to record sample sets in various areas, like a forest, my lab, the kitchen and so for so I build up a data set needed to help train the AI.
eCO2: 406 ppm, TVOC: 0 ppb
✅ Air quality is excellent. No ventilation needed.
✅ No significant VOCs detected. Air is clean.
eCO2: 741 ppm, TVOC: 0 ppb
✅ Air quality is excellent. No ventilation needed.
✅ No significant VOCs detected. Air is clean.
eCO2: 2595 ppm, TVOC: 397 ppb
🚨 CO₂ is dangerously high! Leave within 5 min.
✅ No significant VOCs detected. Air is clean.
eCO2: 5655 ppm, TVOC: 1810 ppb
🚨 CO₂ is dangerously high! Leave within 5 min.
⚠️ Chemical odors detected—address within 15 min.
eCO2: 2683 ppm, TVOC: 1219 ppb
🚨 CO₂ is dangerously high! Leave within 5 min.
⚠️ Chemical odors detected—address within 15 min.
eCO2: 847 ppm, TVOC: 586 ppb
✅ Air quality is excellent. No ventilation needed.
⚠️ Chemical odors detected—address within 15 min.
Code...
import time
import board
import busio
import adafruit_sgp30
# Initialize I2C and sensors
i2c = busio.I2C(board.SCL, board.SDA)
sgp30 = adafruit_sgp30.Adafruit_SGP30(i2c)
# Function to classify risk level
def get_warning(eCO2, TVOC):
warning = ""
# CO₂ Alerts
if eCO2 < 1000:
warning += "✅ Air quality is excellent. No ventilation needed.\n"
elif 1000 <= eCO2 < 2000:
warning += "⚠️ CO₂ levels rising—consider ventilating within 10-15 min.\n"
else:
warning += "🚨 CO₂ is dangerously high! Leave within 5 min.\n"
# VOC Alerts
if TVOC < 500:
warning += "✅ No significant VOCs detected. Air is clean.\n"
elif 500 <= TVOC < 2000:
warning += "⚠️ Chemical odors detected—address within 15 min.\n"
else:
warning += "🚨 High VOC levels! Leave within 5 min.\n"
return warning
print("SGP30 Sensor Ready")
while True:
eCO2, TVOC = sgp30.iaq_measure()
print(f"eCO2: {eCO2} ppm, TVOC: {TVOC} ppb")
# Generate warning message
alert_message = get_warning(eCO2, TVOC)
print(alert_message)
time.sleep(1)
Here is a proper flowchart on the internals of the device, still a work in progress but getting there I may swap out the magnetometer for something that has more axis and range. Decided to go with a speaker too as it will be able to give more then beeps, may still need to add a piezo beeper for low level warnings so I don't have to wake up the AI board.
Just dropped some pseudo-code based on my initial risk threshold chart in Python. The idea here is that this can run on a PI Pico 1 or 2, serving as the direct interface for all the sensors. It’s built to evaluate environmental risks, with preset thresholds that determine when to warn the user of danger.
These warnings are recommendations, you can always choose to ignore them. However, if the AI spins up, a piezoelectric beeper will activate, and that means you should probably pay attention. At that point, the system has detected a high-risk event, like elevated CO₂ or radiation—stuff you don’t want to second-guess.
Right now, this is still rudimentary and missing some functions I’ll need to fill in. But you have to start somewhere.
I’ve also added the chart to the project's file base. Threshold values can be adjusted depending on how risk-averse you are.
from machine import I2C, Pin, UART
from ssd1306 import SSD1306_I2C
from time import sleep, ticks_ms
# --- Init Display & UART ---
i2c = I2C(0, scl=Pin(1), sda=Pin(0))
oled = SSD1306_I2C(128, 64, i2c)
uart = UART(0, baudrate=115200, tx=Pin(4), rx=Pin(5))
non_critical_interval = 60_000 # 1 minute
last_non_critical_check = 0
def warn_user(sensor, message):
oled.fill(0)
oled.text("⚠ {}".format(sensor), 0, 0)
oled.text(message, 0, 10)
oled.show()
def wake_ai(snapshot):
uart.write("SNAPSHOT:" + snapshot + "\n")
def handle_risk(level, sensor, med_msg, high_msg, snapshot):
if level == "LOW":
return
elif level == "MEDIUM":
warn_user(sensor, med_msg)
elif level == "HIGH":
warn_user(sensor, high_msg)
wake_ai(snapshot)
def eval_risk(value, thresholds):
low, med = thresholds
if value < low: return "LOW"
elif value <= med: return "MEDIUM"
return "HIGH"
# --- AI Message Handler ---
def process_ai():
if uart.any():
msg = uart.readline().decode().strip()
oled.fill(0)
if msg.startswith("CRITICAL:"):
oled.text("AI:", 0, 0)
oled.text(msg[9:], 0, 10)
elif msg.startswith("INFO:"):
oled.text("Info:", 0, 0)
oled.text(msg[5:], 0, 10)
oled.show()
# --- Sensor Read Functions (stubbed) ---
def read_all_critical():
return {
"CO2": 1600.0,
"VOC": 1.2,
"Pressure": 985.0,
"Radiation": 0.7
}
def read_all_non_critical():
return {
"Temp": 34.0,
"Humidity": 72.0,
"Light": 3100,
"Sound": 89,
"Mag": 350
}
# --- Loop ---
while True:
now = ticks_ms()
critical = read_all_critical()
snapshot = ",".join(f"{k}={v}" for k, v in critical.items())
handle_risk(eval_risk(critical["CO2"], (1000, 2000)), "CO2", "Ventilate", "High CO₂!", snapshot)
handle_risk(eval_risk(critical["VOC"], (0.5, 2.0)), "VOC", "Irritants", "Toxic VOCs", snapshot)
handle_risk(eval_risk(critical["Pressure"], (980, 1000)), "Pressure", "Storm risk", "Severe drop", snapshot)
handle_risk(eval_risk(critical["Radiation"], (0.5, 5.0)), "Radiation", "Elevated", "Evacuate!", snapshot)
if now - last_non_critical_check > non_critical_interval:
noncrit = read_all_non_critical()
snapshot_nc = ",".join(f"{k}={v}" for k, v in noncrit.items())
handle_risk(eval_risk(noncrit["Temp"], (15, 30)), "Temp", "Discomfort", "Heat/Hypo!", snapshot_nc)
handle_risk(eval_risk(noncrit["Humidity"], (30, 60)), "Humidity", "Uncomfortable", "Resp. risk", snapshot_nc)
handle_risk(eval_risk(noncrit["Light"], (500, 2000)), "Light", "Glare", "Overexposed", snapshot_nc)
handle_risk(eval_risk(noncrit["Sound"], (70, 85)), "Sound", "Noisy", "Hearing danger", snapshot_nc)
handle_risk(eval_risk(noncrit["Mag"], (100, 300)), "Mag Field", "Elevated", "Interference", snapshot_nc)
last_non_critical_check = now
process_ai()
sleep(5) # Sleep time between wake cycles
I've been chewing on the power problem for this thing. (It's going to be a hog). However, the goal of having AI onboard isn't to show off, it's to say, "Hey, something's not right you should take a look and a warning of immediate danger to the human/group," without you needing to Google whether 1200ppm CO₂ is bad (spoiler: it is after a bit).
But here's the catch: if the AI runs 24/7, the battery's toast in an hour or two.
So here's my solution:
A Three-Tier Power System...
Tier 1 – Basic Threshold Monitoring
Always on. No AI, just dumb math: “Is radiation high?” “Is CO₂ dangerous?” If yes, flag it. This barely sips power and helps you know when to GTFO.
Battery: 24+ hours.
Tier 2 – “Something’s Weird” Detection
This is the AI’s cue. When multiple sensors go funky (say, pressure drops and VOCs spike), the Coral spins up, analyzes the pattern, and tells you what’s likely going on in plain English. Then it shuts back down. More power draw here, but it only runs as needed.
Battery: 8–12 hours, depending on how much weirdness you run into.
Tier 3 – Full Analysis Mode
Everything wakes up: full AI, screen, every sensor, the whole nine yards. For serious events or user-triggered deep dives. This burns battery quick, but that’s expected.
Battery: 1–2 hours max.
Smarter Display Use: (this is huge)
Screen stays off unless:
Something’s actually wrong
You press a button
The AI has something important to say
Otherwise, a simple red heartbeat LED handles status.
Sensor Prioritization:
Not every sensor needs to run constantly:
Critical: Radiation, CO₂ – always monitored
Environmental: Temp, humidity – checked periodically (can power off with MOSFET)
Secondary: Sound, magnetism – only wake up when needed
Power Targets (aiming for these):
Normal field use: 24+ hours
Monitoring mode: 8-12 hours (screen off, AI rarely active)
Emergency mode: 1–2 hours (everything running full blast)
Main idea: Most of the time, nothing interesting is happening. The device should act like it—no one wants a gadget that dies early or screams about nothing.
Next up: finish the sensor PCB layout, Switch PCB, and figure out the Coral’s power switching. Still not a great programmer, but the hardware’s shaping up.
Next up is finishing the sensor PCB layout, Switch PCB and figuring out the Coral's power switching. I'm still no great programmer, but the hardware's shaping up.
I’ve found that if you have access to CAD, it’s always better to design as much of the final project in virtual first.
This has a lot of advantages:
Stuff just fits on the first try if you model it accurately
You can save money by knowing the exact components going into the build
You get to preview what it’ll look like, and make changes before committing
I’m printing the initial case (top and bottom) without the details like bosses and mounting points, just to get it in my hands and see if it’s comfortable to hold and easy to store.
Some of the initial design details:
Clamshell case for the top and bottom
Mounting bosses for assembly
Double-checked that the OLED screen’s PCB will fit in the space provided
I still need to add an external charging port (likely USB-C). I’ll see if this can handle data too—if not, I might add an SD card slot as an easy way to store the collected data.
Next, I’ll see if there’s enough room for the main components: the dev board, battery, Pocket Geiger, and other sensors. There will likely be at least two custom PCBs—one for the switches, and one for the bulk of the sensors. If everything fits well, I probably won’t try to make it smaller; I want it to feel substantial in the hand.
I’ve always liked the idea of the Star Trek "Tricorder" one device too rule them all!, that tells you what’s really happening around you. The problem is, everything out there now is either just a collection of sensors with pretty graphs, or a phone app pretending to do more than it can. (yay its bad...)
The Innovation?
Nobody’s made something that actually tells you, simply and directly, what you need to know to stay safe or spot something weird.
So, I set a few ground rules for my design from the start:
Rugged and field-ready: It has to survive drops, dirt, and actual outdoor use. No delicate displays.
All the "right" sensors: I want real environmental sensors, radiation, CO2, VOCS, temp, humidity, light, sound, magnetic fields.
One thing i realized is most people don’t know if “1200ppm CO₂” is bad or not. The device has to translate that into plain English warnings, with beeps!.
Onboard AI: A Google Coral Mini lets me run custom-trained models directly on the device, so it can flag dangers or anomalies without needing a phone or cloud connection. (I think this is important if in a cave or approaching a UFO?)
Open hardware: I’m putting all files and build details out there. If it works, anyone should be able to make or improve it. Please do! I'm a terrible programmer... though a pretty decent hardware engineer...
Big battery is a must: You should be able to use this all day, without worrying about charging. (no mental stress that is, it getting you though a day of rigorous science)
My first pass was just a rough 3D model—thick walls, plenty of space, making it handheld friendly. I made sure the display would be easy to read outside (so it is a monochrome OLED), buttons big enough for gloves, and everything sealed tight but easy to service.
The screen is protected buy the design as is the sensor "vent" and power switch.
Create a sensor PCB layout and hook up to the coral, then loading up the Coral Mini with the first version of the AI model. (this step may take some time) All files, code, and build steps will be posted here as I go. If you want to follow along or build your own, everything will be on the project page or GITHUB.
This is just the start. The goal isn’t to make a flashy gadget—just a tool I wish already existed. If you have ideas, or spot something I missed, let me know. I’ll take real feedback any day.
Create an account to leave a comment. Already have an account? Log In.
Become a member to follow this project and never miss any updates