Skip to content

Commit 7cd33eb

Browse files
authored
Merge pull request #43 from RTGS-Lab/feature/sdi12-analog-mux
added support for analog mux sensor
2 parents e5f90c3 + 7691684 commit 7cd33eb

8 files changed

Lines changed: 325 additions & 14 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "SDI12AnalogMux",
3+
"keywords": "sdi12, temperature, rtd, pt100, sensor, analog, multiplexer",
4+
"description": "Driver for Pico-based SDI-12 PT100 analog multiplexer temperature sensor with 7 RTD channels plus internal temperature monitoring.",
5+
"version": "1.0.0",
6+
"frameworks": "arduino",
7+
"dependencies": {
8+
"Driver_-_Sensor": "*",
9+
"Driver_-_Talon-SDI12": "*"
10+
}
11+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
//© 2025 Regents of the University of Minnesota. All rights reserved.
2+
3+
#include <SDI12AnalogMux.h>
4+
5+
SDI12AnalogMux::SDI12AnalogMux(SDI12Talon& talon_, uint8_t talonPort_, uint8_t sensorPort_, uint8_t version): talon(talon_)
6+
{
7+
//Only update values if they are in range, otherwise stick with default values
8+
if(talonPort_ > 0) talonPort = talonPort_ - 1;
9+
else talonPort = 255; //Reset to null default if not in range
10+
if(sensorPort_ > 0) sensorPort = sensorPort_ - 1;
11+
else sensorPort = 255; //Reset to null default if not in range
12+
sensorInterface = BusType::SDI12;
13+
}
14+
15+
String SDI12AnalogMux::begin(time_t time, bool &criticalFault, bool &fault)
16+
{
17+
return ""; //No special initialization required
18+
}
19+
20+
String SDI12AnalogMux::selfDiagnostic(uint8_t diagnosticLevel, time_t time)
21+
{
22+
if(getSensorPort() == 0) throwError(FIND_FAIL); //If no port found, report failure
23+
else if(isPresent() == false) throwError(DETECT_FAIL); //If sensor port is good, but fail to detect sensor, throw error
24+
String output = "\"SDI12AnalogMux\":{";
25+
if(diagnosticLevel == 0) {
26+
//TBD
27+
}
28+
29+
if(diagnosticLevel <= 1) {
30+
//TBD
31+
}
32+
33+
if(diagnosticLevel <= 2) {
34+
//TBD
35+
}
36+
37+
if(diagnosticLevel <= 3) {
38+
//TBD
39+
}
40+
41+
if(diagnosticLevel <= 4) {
42+
//TBD
43+
}
44+
45+
if(diagnosticLevel <= 5) {
46+
if(getSensorPort() != 0 && isPresent() == true) { //Test as normal
47+
String adr = talon.sendCommand("?!");
48+
int adrVal = adr.toInt();
49+
output = output + "\"Adr\":";
50+
if(adr.equals("") || (!adr.equals("0") && adrVal == 0)) output = output + "null"; //If no return, report null
51+
else output = output + adr; //Otherwise report the read value
52+
output = output + ",";
53+
}
54+
else output = output + "\"Adr\":null,"; //Else append null string
55+
56+
}
57+
return output + "\"Pos\":[" + getTalonPortString() + "," + getSensorPortString() + "]}"; //Write position in logical form - Return completed closed output
58+
}
59+
60+
String SDI12AnalogMux::getMetadata()
61+
{
62+
uint8_t adr = (talon.sendCommand("?!")).toInt(); //Get address of local device
63+
String id = talon.command("I", adr);
64+
Serial.println(id); //DEBUG!
65+
String sdi12Version;
66+
String mfg;
67+
String model;
68+
String senseVersion;
69+
String sn;
70+
if((id.substring(0, 1)).toInt() != adr) { //If address returned is not the same as the address read, throw error
71+
Serial.println("ADDRESS MISMATCH!"); //DEBUG!
72+
//Throw error!
73+
sdi12Version = "null";
74+
mfg = "null";
75+
model = "null";
76+
senseVersion = "null";
77+
sn = "null";
78+
}
79+
else { //Standard across SDI-12 devices
80+
sdi12Version = (id.substring(1,3)).trim(); //Grab SDI-12 version code
81+
mfg = (id.substring(3, 11)).trim(); //Grab manufacturer
82+
model = (id.substring(11,17)).trim(); //Grab sensor model name
83+
senseVersion = (id.substring(17,20)).trim(); //Grab version number
84+
sn = (id.substring(20,33)).trim(); //Grab the serial number
85+
}
86+
String metadata = "\"SDI12AnalogMux\":{";
87+
metadata = metadata + "\"Hardware\":\"" + senseVersion + "\","; //Report sensor version pulled from SDI-12 system
88+
metadata = metadata + "\"Firmware\":\"" + FIRMWARE_VERSION + "\","; //Static firmware version
89+
metadata = metadata + "\"SDI12_Ver\":\"" + sdi12Version.substring(0,1) + "." + sdi12Version.substring(1,2) + "\",";
90+
metadata = metadata + "\"ADR\":" + String(adr) + ",";
91+
metadata = metadata + "\"Mfg\":\"" + mfg + "\",";
92+
metadata = metadata + "\"Model\":\"" + model + "\",";
93+
metadata = metadata + "\"SN\":\"" + sn + "\",";
94+
metadata = metadata + "\"Pos\":[" + getTalonPortString() + "," + getSensorPortString() + "]"; //Concatenate position
95+
metadata = metadata + "}"; //CLOSE
96+
return metadata;
97+
}
98+
99+
String SDI12AnalogMux::getData(time_t time)
100+
{
101+
String output = "\"SDI12AnalogMux\":{"; //OPEN JSON BLOB
102+
bool readDone = false;
103+
delay(2500); //Give sensor time to initialize (ADC + RTD config + SDI-12 setup)
104+
105+
// Arrays to store temperature readings for all 9 channels
106+
float temperatures[9] = {-9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999}; //Initialize with error values
107+
bool channelSuccess[9] = {false, false, false, false, false, false, false, false, false};
108+
109+
if(getSensorPort() != 0) { //Check for valid port
110+
int adr = -1;
111+
112+
// First, get the sensor address (only need to do this once)
113+
for(int retry = 0; retry < talon.retryCount; retry++) {
114+
if(!isPresent()) continue; //If presence check fails, try again
115+
adr = talon.getAddress();
116+
if(adr >= 0) break; //Address found successfully
117+
}
118+
119+
if(adr >= 0) {
120+
// Read all 9 temperature channels (M1-M9)
121+
for(int channel = 1; channel <= 9; channel++) {
122+
for(int retry = 0; retry < talon.retryCount; retry++) {
123+
// Send measurement command (aM1! through aM9!)
124+
int waitTime = talon.startMeasurmentIndex(channel, adr);
125+
if(waitTime < 0) {
126+
continue; //If wait time invalid, try again
127+
}
128+
129+
// Wait for measurement to complete (sensor returns 0 seconds, but add small buffer)
130+
delay(waitTime*1000 + 100); //Wait requested time plus 100ms buffer
131+
132+
// Retrieve data with D0 command
133+
String data = talon.command("D0", adr);
134+
135+
// Parse the temperature value
136+
if(parseTemperature(data, temperatures[channel-1])) {
137+
channelSuccess[channel-1] = true;
138+
readDone = true;
139+
break; //Successfully read this channel, move to next
140+
}
141+
}
142+
}
143+
}
144+
145+
if(readDone == false) throwError(talon.SDI12_READ_FAIL); //Throw error if no channels read successfully
146+
}
147+
else throwError(FIND_FAIL);
148+
149+
// Build JSON output with all 9 channels
150+
output = output + appendData(temperatures[0], "RTD1_Temp", 2, true);
151+
output = output + appendData(temperatures[1], "RTD2_Temp", 2, true);
152+
output = output + appendData(temperatures[2], "RTD3_Temp", 2, true);
153+
output = output + appendData(temperatures[3], "RTD4_Temp", 2, true);
154+
output = output + appendData(temperatures[4], "RTD5_Temp", 2, true);
155+
output = output + appendData(temperatures[5], "RTD6_Temp", 2, true);
156+
output = output + appendData(temperatures[6], "RTD7_Temp", 2, true);
157+
output = output + appendData(temperatures[7], "Pico_Temp", 2, true);
158+
output = output + appendData(temperatures[8], "ADC_Temp", 2, false); //Last entry, no trailing comma
159+
160+
output = output + ",\"Pos\":[" + getTalonPortString() + "," + getSensorPortString() + "]"; //Concatenate position
161+
output = output + "}"; //CLOSE JSON BLOB
162+
Serial.println(output); //DEBUG!
163+
return output;
164+
}
165+
166+
bool SDI12AnalogMux::isPresent()
167+
{
168+
uint8_t adr = (talon.sendCommand("?!")).toInt();
169+
170+
String id = talon.command("I", adr);
171+
id.remove(0, 1); //Trim address character from start
172+
Serial.print("SDI12 Address: "); //DEBUG!
173+
Serial.print(adr);
174+
Serial.print(",");
175+
Serial.println(id);
176+
// Check for either "GEMS" (vendor) or "GORGON" (model) in identification string
177+
if(id.indexOf("GEMS") > 0 || id.indexOf("GORGON") > 0) return true;
178+
else return false;
179+
}
180+
181+
String SDI12AnalogMux::appendData(float data, String label, uint8_t precision, bool appendComma)
182+
{
183+
String val = "";
184+
if(data == -9999 || data == 9999999) val = "\"" + label + "\":null"; //Append null if value is error indicator
185+
else val = "\"" + label + "\":" + String(data, precision); //Otherwise, append as normal using fixed specified precision
186+
187+
if(appendComma) return val + ",";
188+
else return val;
189+
}
190+
191+
bool SDI12AnalogMux::parseTemperature(String input, float &temperature)
192+
{
193+
// Expected format: "a±xx.xx<CR><LF>" where a is address
194+
// Example: "0+25.43\r\n"
195+
196+
if(input.length() < 3) return false; //String too short to be valid
197+
198+
// Remove address character from start
199+
input.remove(0, 1);
200+
input.trim(); //Remove any trailing whitespace/CR/LF
201+
202+
// Check if string starts with + or - sign
203+
if(input.charAt(0) != '+' && input.charAt(0) != '-') return false;
204+
205+
// Parse the float value
206+
temperature = input.toFloat();
207+
208+
// Basic sanity check: PT100 sensors typically measure -200°C to +850°C
209+
// But for this application, reasonable range is likely -50 to +150°C
210+
if(temperature < -200.0 || temperature > 1000.0) {
211+
temperature = -9999; //Set to error value
212+
return false;
213+
}
214+
215+
return true;
216+
}
217+
218+
String SDI12AnalogMux::getErrors()
219+
{
220+
String output = "\"SDI12AnalogMux\":{"; // OPEN JSON BLOB
221+
output = output + "\"CODES\":["; //Open codes pair
222+
223+
for(int i = 0; i < min(MAX_NUM_ERRORS, numErrors); i++) { //Iterate over used element of array without exceeding bounds
224+
output = output + "\"0x" + String(errors[i], HEX) + "\","; //Add each error code
225+
errors[i] = 0; //Clear errors as they are read
226+
}
227+
if(output.substring(output.length() - 1).equals(",")) {
228+
output = output.substring(0, output.length() - 1); //Trim trailing ','
229+
}
230+
output = output + "],"; //close codes pair
231+
output = output + "\"OW\":"; //Open state pair
232+
if(numErrors > MAX_NUM_ERRORS) output = output + "1,"; //If overwritten, indicate the overwrite is true
233+
else output = output + "0,"; //Otherwise set it as clear
234+
output = output + "\"NUM\":" + String(numErrors) + ","; //Append number of errors
235+
output = output + "\"Pos\":[" + getTalonPortString() + "," + getSensorPortString() + "]"; //Concatenate position
236+
output = output + "}"; //CLOSE JSON BLOB
237+
numErrors = 0; //Clear error count
238+
return output;
239+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//© 2025 Regents of the University of Minnesota. All rights reserved.
2+
3+
#ifndef SDI12ANALOGMUX_h
4+
#define SDI12ANALOGMUX_h
5+
6+
#include <Sensor.h>
7+
#include <SDI12Talon.h>
8+
9+
class SDI12AnalogMux: public Sensor
10+
{
11+
constexpr static int DEFAULT_PORT = 2; ///<Use port 2 by default
12+
constexpr static int DEFAULT_SENSOR_PORT = 0; ///<Use port 0 by default
13+
constexpr static int DEFAULT_VERSION = 0x00; ///<Use hardware version unknown by default
14+
const String FIRMWARE_VERSION = "1.0.0";
15+
16+
public:
17+
SDI12AnalogMux(SDI12Talon& talon_, uint8_t talonPort_ = DEFAULT_PORT, uint8_t sensorPort_ = DEFAULT_SENSOR_PORT, uint8_t version = DEFAULT_VERSION);
18+
String begin(time_t time, bool &criticalFault, bool &fault);
19+
String getData(time_t time);
20+
String selfDiagnostic(uint8_t diagnosticLevel = 4, time_t time = 0);
21+
String getMetadata();
22+
String getErrors();
23+
bool isPresent();
24+
25+
private:
26+
SDI12Talon& talon;
27+
String appendData(float data, String label, uint8_t precision = 2, bool appendComma = true);
28+
bool parseTemperature(String input, float &temperature);
29+
30+
bool initDone = false;
31+
uint8_t version = 0;
32+
};
33+
34+
#endif

src/FlightControl.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ void initializeSensorSystem();
4747
#include <Sensor.h>
4848
#include <Talon.h>
4949
#include <BaroVue10.h>
50+
#include <SDI12AnalogMux.h>
5051
#include <Kestrel.h>
5152
#include <KestrelFileHandler.h>
5253
#include <Haar.h>
@@ -1272,12 +1273,14 @@ int detectTalons(String dummyStr)
12721273
// logger.enableI2C_Global(true);
12731274
// logger.enableI2C_OB(false);
12741275
// talons[i]->begin(Time.now(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //DEBUG!
1275-
talons[i]->begin(logger.getTime(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime!
1276-
// talons[i]->begin(0, dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime!
1276+
talons[i]->begin(logger.getTime(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime!
1277+
// talons[i]->begin(0, dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime!
1278+
Serial.println(">>> FlightControl: Talon begin() returned"); //DEBUG!
12771279
//Serial.println("TALON BEGIN DONE"); //DEBUG!
12781280
//Serial.flush(); //DEBUG!
1279-
//delay(10000); //DEBUG!
1280-
logger.enableData(talons[i]->getTalonPort(), false); //Turn data back off to prevent conflict
1281+
//delay(10000); //DEBUG!
1282+
logger.enableData(talons[i]->getTalonPort(), false); //Turn data back off to prevent conflict
1283+
Serial.println(">>> FlightControl: Disabled data, exiting detectTalons loop"); //DEBUG!
12811284
//Serial.println("ENABLE DATA DONE"); //DEBUG!
12821285
// Serial.flush(); //DEBUG!
12831286
//delay(10000); //DEBUG!
@@ -1289,6 +1292,7 @@ int detectTalons(String dummyStr)
12891292

12901293
int detectSensors(String dummyStr)
12911294
{
1295+
Serial.println(">>> FlightControl: detectSensors() START - Power should still be ON from detectTalons"); //DEBUG!
12921296
/////////////// SENSOR AUTO DETECTION //////////////////////
12931297
for(int t = 0; t < talons.size(); t++) { //Iterate over each Talon
12941298
// Serial.println(talons[t]->talonInterface); //DEBUG!

src/configuration/ConfigurationManager.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@
5757
config += "\"numApogeeSolar\":" + std::to_string(m_numApogeeSolar) + ",";
5858
config += "\"numCO2\":" + std::to_string(m_numCO2) + ",";
5959
config += "\"numO2\":" + std::to_string(m_numO2) + ",";
60-
config += "\"numPressure\":" + std::to_string(m_numPressure);
60+
config += "\"numPressure\":" + std::to_string(m_numPressure) + ",";
61+
config += "\"numAnalogMux\":" + std::to_string(m_numAnalogMux);
6162
config += "}}}";
62-
63+
6364
return config;
6465
}
6566

@@ -83,6 +84,7 @@
8384
tempUid |= m_numCO2 << 12;
8485
tempUid |= m_numO2 << 8;
8586
tempUid |= m_numPressure << 4;
87+
tempUid |= m_numAnalogMux;
8688
m_SensorConfigUid = tempUid;
8789
return m_SensorConfigUid;
8890
}
@@ -129,7 +131,8 @@
129131
m_numCO2 = extractJsonIntField(sensorsJson, "numCO2", 0);
130132
m_numO2 = extractJsonIntField(sensorsJson, "numO2", 0);
131133
m_numPressure = extractJsonIntField(sensorsJson, "numPressure", 0);
132-
134+
m_numAnalogMux = extractJsonIntField(sensorsJson, "numAnalogMux", 0);
135+
133136
updateSensorConfigurationUid();
134137
}
135138
}
@@ -243,6 +246,10 @@ std::unique_ptr<BaroVue10> ConfigurationManager::createPressureSensor(SDI12Talon
243246
return std::make_unique<BaroVue10>(talon, 0, 0x00); // Default ports and version
244247
}
245248

249+
std::unique_ptr<SDI12AnalogMux> ConfigurationManager::createAnalogMuxSensor(SDI12Talon& talon) {
250+
return std::make_unique<SDI12AnalogMux>(talon, 0, 0, 0x00); // Default ports and version
251+
}
252+
246253
// EEPROM backup functionality
247254
bool ConfigurationManager::saveConfigToEEPROM() {
248255
// Write system and sensor UIDs (they encode all the config values)
@@ -285,7 +292,8 @@ bool ConfigurationManager::loadConfigFromEEPROM() {
285292
m_numCO2 = (sensorUid >> 12) & 0xF;
286293
m_numO2 = (sensorUid >> 8) & 0xF;
287294
m_numPressure = (sensorUid >> 4) & 0xF;
288-
295+
m_numAnalogMux = sensorUid & 0xF;
296+
289297
// Store the UIDs
290298
m_SystemConfigUid = systemUid;
291299
m_SensorConfigUid = sensorUid;

0 commit comments

Comments
 (0)