Add configurable DS18B20 temperature probe support

This commit is contained in:
2026-06-04 14:25:34 -06:00
parent f46e72fc7f
commit 0afd78284e
6 changed files with 490 additions and 48 deletions
+55
View File
@@ -538,3 +538,58 @@ Runtime behavior:
AP always remains enabled. AP always remains enabled.
STA tries saved networks by priority. STA tries saved networks by priority.
If STA disconnects, saved networks are retried every 30 seconds. If STA disconnects, saved networks are retried every 30 seconds.
---
# Temperature Probe Setup API
## Scan Temperature Sensors
POST /temps/scan
Response:
{
"type": "temp_scan_response",
"ok": true,
"devices": [
{
"index": 1,
"address": "28:AA:BB:CC:DD:EE:FF:00"
}
]
}
## Assign Temperature Sensor
POST /temps/assign
Request:
{
"id": "temp_1",
"index": 1
}
Alternative request:
{
"slot": 1,
"index": 1
}
## Clear Temperature Sensor
POST /temps/clear
Request:
{
"id": "temp_1"
}
Alternative request:
{
"slot": 1
}
+38
View File
@@ -414,3 +414,41 @@ Attempt STA WiFi connection using saved networks by priority.
AP remains available at: AP remains available at:
192.168.4.1 192.168.4.1
---
# Temperature Probe Setup
## scan temps
Scans the DS18B20 1-Wire bus and prints detected sensor addresses.
Example:
scan temps
## assign temp <slot> <number>
Assigns a detected physical sensor to a configured temp slot.
Example:
assign temp 1 1
assign temp 2 2
save
## clear temp <slot>
Clears and disables a configured temp slot.
Example:
clear temp 1
Wiring reminder:
DS18B20 red -> 3.3V
DS18B20 black -> GND
DS18B20 yellow -> GPIO4
A single 4.7k resistor is required between 3.3V and GPIO4/data.
+34
View File
@@ -232,3 +232,37 @@ Preferred response:
{"type":"config_wifi","networks":[{"ssid":"Starlink","password":"password","priority":1},{"ssid":"Home WiFi","password":"password","priority":2}]} {"type":"config_wifi","networks":[{"ssid":"Starlink","password":"password","priority":1},{"ssid":"Home WiFi","password":"password","priority":2}]}
Lower priority numbers are tried first. Lower priority numbers are tried first.
---
# Temperature Probe Setup
## Scan Temperature Sensors
Pico sends:
{"type":"scan_temps"}
ESP32 responds:
{"type":"temp_scan_response","ok":true,"devices":[{"index":1,"address":"28:AA:BB:CC:DD:EE:FF:00"}]}
## Assign Temperature Sensor
Pico sends:
{"type":"assign_temp","id":"temp_1","index":1}
Alternative:
{"type":"assign_temp","slot":1,"index":1}
## Clear Temperature Sensor
Pico sends:
{"type":"clear_temp","id":"temp_1"}
Alternative:
{"type":"clear_temp","slot":1}
@@ -474,9 +474,9 @@ void buildStatusDocument(JsonDocument& doc) {
temp["id"] = appConfig.tempSensors[i].id; temp["id"] = appConfig.tempSensors[i].id;
temp["name"] = appConfig.tempSensors[i].name; temp["name"] = appConfig.tempSensors[i].name;
temp["enabled"] = appConfig.tempSensors[i].enabled; temp["enabled"] = appConfig.tempSensors[i].enabled;
temp["online"] = tempOnline[i]; temp["online"] = sensors.online[i];
if (tempOnline[i]) { if (sensors.online[i]) {
temp["temperature_f"] = tempValues[i]; temp["temperature_f"] = sensors.tempsF[i];
} else { } else {
temp["temperature_f"] = nullptr; temp["temperature_f"] = nullptr;
} }
@@ -912,6 +912,82 @@ void handleUartMessage(const String& line) {
return; return;
} }
if (strcmp(type, "scan_temps") == 0) {
scanTempSensors();
DynamicJsonDocument response(2048);
response["type"] = "temp_scan_response";
response["ok"] = true;
JsonArray devices = response.createNestedArray("devices");
int count = getTempScanResultCount();
for (int i = 0; i < count; i++) {
JsonObject device = devices.createNestedObject();
device["index"] = i + 1;
device["address"] = getTempScanResultAddress(i);
}
serializeJson(response, DashboardSerial);
DashboardSerial.println();
return;
}
if (strcmp(type, "assign_temp") == 0) {
String id = doc["id"] | "";
int slot = doc["slot"] | 0;
int index = doc["index"] | 0;
int configIndex = -1;
if (id.length() > 0) {
configIndex = findTempConfigIndexForUart(id);
} else if (slot >= 1 && slot <= MAX_TEMP_SENSORS) {
configIndex = slot - 1;
}
if (configIndex < 0) {
sendError(DashboardSerial, "unknown_temp_sensor");
return;
}
if (!assignTempSensorByIndex(configIndex, index - 1)) {
sendError(DashboardSerial, "invalid_temp_selection");
return;
}
saveConfig();
sendConfigResponse(DashboardSerial);
return;
}
if (strcmp(type, "clear_temp") == 0) {
String id = doc["id"] | "";
int slot = doc["slot"] | 0;
int configIndex = -1;
if (id.length() > 0) {
configIndex = findTempConfigIndexForUart(id);
} else if (slot >= 1 && slot <= MAX_TEMP_SENSORS) {
configIndex = slot - 1;
}
if (configIndex < 0) {
sendError(DashboardSerial, "unknown_temp_sensor");
return;
}
if (!clearTempSensor(configIndex)) {
sendError(DashboardSerial, "unknown_temp_sensor");
return;
}
saveConfig();
sendConfigResponse(DashboardSerial);
return;
}
if (strcmp(type, "save_config") == 0) { if (strcmp(type, "save_config") == 0) {
saveConfig(); saveConfig();
sendConfigResponse(DashboardSerial); sendConfigResponse(DashboardSerial);
@@ -1191,6 +1267,47 @@ void handleDebugSerial() {
} }
} }
if (command == "scan temps") {
scanTempSensors();
printSensorAddresses();
return;
}
if (command.startsWith("assign temp ")) {
String rest = command.substring(12);
int space = rest.indexOf(' ');
if (space < 0) {
Serial.println("Invalid command. Use: assign temp <slot> <number>");
return;
}
int slot = rest.substring(0, space).toInt();
int index = rest.substring(space + 1).toInt();
if (!assignTempSensorByIndex(slot - 1, index - 1)) {
Serial.println("Invalid temp slot or sensor number.");
return;
}
saveConfig();
Serial.println("OK temp sensor assigned");
return;
}
if (command.startsWith("clear temp ")) {
int slot = command.substring(11).toInt();
if (!clearTempSensor(slot - 1)) {
Serial.println("Invalid temp slot.");
return;
}
saveConfig();
Serial.println("OK temp sensor cleared");
return;
}
if (command.startsWith("tempname ")) { if (command.startsWith("tempname ")) {
int firstSpace = command.indexOf(' '); int firstSpace = command.indexOf(' ');
int secondSpace = command.indexOf(' ', firstSpace + 1); int secondSpace = command.indexOf(' ', firstSpace + 1);
@@ -1524,6 +1641,102 @@ void handleWifiClear() {
sendWifiConfigJson(); sendWifiConfigJson();
} }
void sendTempScanJson() {
DynamicJsonDocument doc(2048);
doc["type"] = "temp_scan_response";
doc["ok"] = true;
JsonArray devices = doc.createNestedArray("devices");
int count = getTempScanResultCount();
for (int i = 0; i < count; i++) {
JsonObject device = devices.createNestedObject();
device["index"] = i + 1;
device["address"] = getTempScanResultAddress(i);
}
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
}
void handleTempScan() {
scanTempSensors();
sendTempScanJson();
}
void handleTempAssign() {
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, server.arg("plain"));
if (error) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
return;
}
String id = doc["id"] | "";
int slot = doc["slot"] | 0;
int index = doc["index"] | 0;
int configIndex = -1;
if (id.length() > 0) {
configIndex = findTempConfigIndexForUart(id);
} else if (slot >= 1 && slot <= MAX_TEMP_SENSORS) {
configIndex = slot - 1;
}
if (configIndex < 0) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}");
return;
}
if (!assignTempSensorByIndex(configIndex, index - 1)) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_temp_selection\"}");
return;
}
saveConfig();
sendConfigJson();
}
void handleTempClear() {
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, server.arg("plain"));
if (error) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
return;
}
String id = doc["id"] | "";
int slot = doc["slot"] | 0;
int configIndex = -1;
if (id.length() > 0) {
configIndex = findTempConfigIndexForUart(id);
} else if (slot >= 1 && slot <= MAX_TEMP_SENSORS) {
configIndex = slot - 1;
}
if (configIndex < 0) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}");
return;
}
if (!clearTempSensor(configIndex)) {
server.send(400, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}");
return;
}
saveConfig();
sendConfigJson();
}
void handleSaveConfig() { void handleSaveConfig() {
saveConfig(); saveConfig();
sendConfigJson(); sendConfigJson();
+135 -36
View File
@@ -4,13 +4,15 @@
#include "config.h" #include "config.h"
#include "sensors.h" #include "sensors.h"
#include "app_config.h"
SensorData sensors; SensorData sensors;
OneWire oneWire(ONEWIRE_PIN); OneWire oneWire(ONEWIRE_PIN);
DallasTemperature ds18b20(&oneWire); DallasTemperature ds18b20(&oneWire);
DeviceAddress sensorAddresses[4]; DeviceAddress sensorAddresses[MAX_TEMP_SENSORS];
String sensorAddressStrings[MAX_TEMP_SENSORS];
int sensorCount = 0; int sensorCount = 0;
float cToF(float c) { float cToF(float c) {
@@ -21,67 +23,164 @@ bool validTempC(float tempC) {
return tempC != DEVICE_DISCONNECTED_C && tempC > -55 && tempC < 125; return tempC != DEVICE_DISCONNECTED_C && tempC > -55 && tempC < 125;
} }
void printAddress(DeviceAddress address) { String addressToString(DeviceAddress address) {
String output = "";
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
if (address[i] < 16) Serial.print("0"); if (address[i] < 16) output += "0";
Serial.print(address[i], HEX); output += String(address[i], HEX);
if (i < 7) Serial.print(":"); if (i < 7) output += ":";
} }
output.toUpperCase();
return output;
}
void printAddress(DeviceAddress address) {
Serial.print(addressToString(address));
}
bool addressMatches(DeviceAddress address, const String& expected) {
if (expected.length() == 0) {
return false;
}
String actual = addressToString(address);
String normalizedExpected = expected;
normalizedExpected.toUpperCase();
return actual == normalizedExpected;
}
void clearSensorRuntimeData() {
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
sensors.tempsF[i] = -127.0;
sensors.online[i] = false;
}
}
int scanTempSensors() {
ds18b20.begin();
sensorCount = ds18b20.getDeviceCount();
if (sensorCount > MAX_TEMP_SENSORS) {
sensorCount = MAX_TEMP_SENSORS;
}
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
sensorAddressStrings[i] = "";
}
for (int i = 0; i < sensorCount; i++) {
if (ds18b20.getAddress(sensorAddresses[i], i)) {
sensorAddressStrings[i] = addressToString(sensorAddresses[i]);
}
}
return sensorCount;
}
int getTempScanResultCount() {
return sensorCount;
}
String getTempScanResultAddress(int index) {
if (index < 0 || index >= sensorCount) {
return "";
}
return sensorAddressStrings[index];
} }
void printSensorAddresses() { void printSensorAddresses() {
Serial.println("DS18B20 Sensors Found:"); Serial.println("DS18B20 Sensors Found:");
for (int i = 0; i < sensorCount; i++) { for (int i = 0; i < sensorCount; i++) {
Serial.print("Sensor "); Serial.print(i + 1);
Serial.print(i); Serial.print(") ");
Serial.print(": "); Serial.println(sensorAddressStrings[i]);
printAddress(sensorAddresses[i]);
Serial.println();
} }
if (sensorCount == 0) { if (sensorCount == 0) {
Serial.println("No DS18B20 sensors found."); Serial.println("No DS18B20 sensors found.");
} else {
Serial.println("Use: assign temp <slot> <number>");
} }
} }
void initSensors() { void initSensors() {
ds18b20.begin(); clearSensorRuntimeData();
sensorCount = ds18b20.getDeviceCount(); scanTempSensors();
if (sensorCount > 4) {
sensorCount = 4;
}
for (int i = 0; i < sensorCount; i++) {
ds18b20.getAddress(sensorAddresses[i], i);
}
printSensorAddresses(); printSensorAddresses();
} }
void updateSensors() { void updateSensors() {
clearSensorRuntimeData();
ds18b20.requestTemperatures(); ds18b20.requestTemperatures();
float tempsF[4] = {-127, -127, -127, -127}; for (int configIndex = 0; configIndex < MAX_TEMP_SENSORS; configIndex++) {
bool online[4] = {false, false, false, false}; if (!appConfig.tempSensors[configIndex].enabled) {
continue;
}
for (int i = 0; i < sensorCount; i++) { String configuredAddress = appConfig.tempSensors[configIndex].address;
float tempC = ds18b20.getTempC(sensorAddresses[i]);
if (validTempC(tempC)) { if (configuredAddress.length() == 0) {
tempsF[i] = cToF(tempC); // Backward-compatible behavior:
online[i] = true; // if no address is assigned, use physical scan order.
if (configIndex < sensorCount) {
float tempC = ds18b20.getTempC(sensorAddresses[configIndex]);
if (validTempC(tempC)) {
sensors.tempsF[configIndex] = cToF(tempC);
sensors.online[configIndex] = true;
}
}
continue;
}
for (int physicalIndex = 0; physicalIndex < sensorCount; physicalIndex++) {
if (!addressMatches(sensorAddresses[physicalIndex], configuredAddress)) {
continue;
}
float tempC = ds18b20.getTempC(sensorAddresses[physicalIndex]);
if (validTempC(tempC)) {
sensors.tempsF[configIndex] = cToF(tempC);
sensors.online[configIndex] = true;
}
break;
} }
} }
}
sensors.temp1 = tempsF[0]; bool assignTempSensorByIndex(int configIndex, int scanIndex) {
sensors.temp2 = tempsF[1]; if (configIndex < 0 || configIndex >= MAX_TEMP_SENSORS) {
sensors.temp3 = tempsF[2]; return false;
sensors.temp4 = tempsF[3]; }
sensors.temp1Online = online[0]; if (scanIndex < 0 || scanIndex >= sensorCount) {
sensors.temp2Online = online[1]; return false;
sensors.temp3Online = online[2]; }
sensors.temp4Online = online[3];
appConfig.tempSensors[configIndex].address = sensorAddressStrings[scanIndex];
appConfig.tempSensors[configIndex].enabled = true;
return true;
}
bool clearTempSensor(int configIndex) {
if (configIndex < 0 || configIndex >= MAX_TEMP_SENSORS) {
return false;
}
appConfig.tempSensors[configIndex].address = "";
appConfig.tempSensors[configIndex].enabled = false;
return true;
} }
+12 -9
View File
@@ -1,19 +1,22 @@
#pragma once #pragma once
struct SensorData { #include <Arduino.h>
float temp1 = -127.0; #include "app_config.h"
float temp2 = -127.0;
float temp3 = -127.0;
float temp4 = -127.0;
bool temp1Online = false; struct SensorData {
bool temp2Online = false; float tempsF[MAX_TEMP_SENSORS];
bool temp3Online = false; bool online[MAX_TEMP_SENSORS];
bool temp4Online = false;
}; };
extern SensorData sensors; extern SensorData sensors;
void initSensors(); void initSensors();
void updateSensors(); void updateSensors();
int scanTempSensors();
int getTempScanResultCount();
String getTempScanResultAddress(int index);
void printSensorAddresses(); void printSensorAddresses();
bool assignTempSensorByIndex(int configIndex, int scanIndex);
bool clearTempSensor(int configIndex);