Add configurable DS18B20 temperature probe support
This commit is contained in:
+55
@@ -538,3 +538,58 @@ Runtime behavior:
|
||||
AP always remains enabled.
|
||||
STA tries saved networks by priority.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -414,3 +414,41 @@ Attempt STA WiFi connection using saved networks by priority.
|
||||
AP remains available at:
|
||||
|
||||
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.
|
||||
|
||||
@@ -232,3 +232,37 @@ Preferred response:
|
||||
{"type":"config_wifi","networks":[{"ssid":"Starlink","password":"password","priority":1},{"ssid":"Home WiFi","password":"password","priority":2}]}
|
||||
|
||||
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["name"] = appConfig.tempSensors[i].name;
|
||||
temp["enabled"] = appConfig.tempSensors[i].enabled;
|
||||
temp["online"] = tempOnline[i];
|
||||
if (tempOnline[i]) {
|
||||
temp["temperature_f"] = tempValues[i];
|
||||
temp["online"] = sensors.online[i];
|
||||
if (sensors.online[i]) {
|
||||
temp["temperature_f"] = sensors.tempsF[i];
|
||||
} else {
|
||||
temp["temperature_f"] = nullptr;
|
||||
}
|
||||
@@ -912,6 +912,82 @@ void handleUartMessage(const String& line) {
|
||||
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) {
|
||||
saveConfig();
|
||||
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 ")) {
|
||||
int firstSpace = command.indexOf(' ');
|
||||
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
||||
@@ -1524,6 +1641,102 @@ void handleWifiClear() {
|
||||
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() {
|
||||
saveConfig();
|
||||
sendConfigJson();
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "sensors.h"
|
||||
#include "app_config.h"
|
||||
|
||||
SensorData sensors;
|
||||
|
||||
OneWire oneWire(ONEWIRE_PIN);
|
||||
DallasTemperature ds18b20(&oneWire);
|
||||
|
||||
DeviceAddress sensorAddresses[4];
|
||||
DeviceAddress sensorAddresses[MAX_TEMP_SENSORS];
|
||||
String sensorAddressStrings[MAX_TEMP_SENSORS];
|
||||
int sensorCount = 0;
|
||||
|
||||
float cToF(float c) {
|
||||
@@ -21,67 +23,164 @@ bool validTempC(float tempC) {
|
||||
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++) {
|
||||
if (address[i] < 16) Serial.print("0");
|
||||
Serial.print(address[i], HEX);
|
||||
if (i < 7) Serial.print(":");
|
||||
if (address[i] < 16) output += "0";
|
||||
output += String(address[i], HEX);
|
||||
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() {
|
||||
Serial.println("DS18B20 Sensors Found:");
|
||||
|
||||
for (int i = 0; i < sensorCount; i++) {
|
||||
Serial.print("Sensor ");
|
||||
Serial.print(i);
|
||||
Serial.print(": ");
|
||||
printAddress(sensorAddresses[i]);
|
||||
Serial.println();
|
||||
Serial.print(i + 1);
|
||||
Serial.print(") ");
|
||||
Serial.println(sensorAddressStrings[i]);
|
||||
}
|
||||
|
||||
if (sensorCount == 0) {
|
||||
Serial.println("No DS18B20 sensors found.");
|
||||
} else {
|
||||
Serial.println("Use: assign temp <slot> <number>");
|
||||
}
|
||||
}
|
||||
|
||||
void initSensors() {
|
||||
ds18b20.begin();
|
||||
sensorCount = ds18b20.getDeviceCount();
|
||||
|
||||
if (sensorCount > 4) {
|
||||
sensorCount = 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sensorCount; i++) {
|
||||
ds18b20.getAddress(sensorAddresses[i], i);
|
||||
}
|
||||
|
||||
clearSensorRuntimeData();
|
||||
scanTempSensors();
|
||||
printSensorAddresses();
|
||||
}
|
||||
|
||||
void updateSensors() {
|
||||
clearSensorRuntimeData();
|
||||
|
||||
ds18b20.requestTemperatures();
|
||||
|
||||
float tempsF[4] = {-127, -127, -127, -127};
|
||||
bool online[4] = {false, false, false, false};
|
||||
for (int configIndex = 0; configIndex < MAX_TEMP_SENSORS; configIndex++) {
|
||||
if (!appConfig.tempSensors[configIndex].enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sensorCount; i++) {
|
||||
float tempC = ds18b20.getTempC(sensorAddresses[i]);
|
||||
String configuredAddress = appConfig.tempSensors[configIndex].address;
|
||||
|
||||
if (configuredAddress.length() == 0) {
|
||||
// Backward-compatible behavior:
|
||||
// if no address is assigned, use physical scan order.
|
||||
if (configIndex < sensorCount) {
|
||||
float tempC = ds18b20.getTempC(sensorAddresses[configIndex]);
|
||||
|
||||
if (validTempC(tempC)) {
|
||||
tempsF[i] = cToF(tempC);
|
||||
online[i] = true;
|
||||
sensors.tempsF[configIndex] = cToF(tempC);
|
||||
sensors.online[configIndex] = true;
|
||||
}
|
||||
}
|
||||
|
||||
sensors.temp1 = tempsF[0];
|
||||
sensors.temp2 = tempsF[1];
|
||||
sensors.temp3 = tempsF[2];
|
||||
sensors.temp4 = tempsF[3];
|
||||
|
||||
sensors.temp1Online = online[0];
|
||||
sensors.temp2Online = online[1];
|
||||
sensors.temp3Online = online[2];
|
||||
sensors.temp4Online = online[3];
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool assignTempSensorByIndex(int configIndex, int scanIndex) {
|
||||
if (configIndex < 0 || configIndex >= MAX_TEMP_SENSORS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scanIndex < 0 || scanIndex >= sensorCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
struct SensorData {
|
||||
float temp1 = -127.0;
|
||||
float temp2 = -127.0;
|
||||
float temp3 = -127.0;
|
||||
float temp4 = -127.0;
|
||||
#include <Arduino.h>
|
||||
#include "app_config.h"
|
||||
|
||||
bool temp1Online = false;
|
||||
bool temp2Online = false;
|
||||
bool temp3Online = false;
|
||||
bool temp4Online = false;
|
||||
struct SensorData {
|
||||
float tempsF[MAX_TEMP_SENSORS];
|
||||
bool online[MAX_TEMP_SENSORS];
|
||||
};
|
||||
|
||||
extern SensorData sensors;
|
||||
|
||||
void initSensors();
|
||||
void updateSensors();
|
||||
|
||||
int scanTempSensors();
|
||||
int getTempScanResultCount();
|
||||
String getTempScanResultAddress(int index);
|
||||
void printSensorAddresses();
|
||||
|
||||
bool assignTempSensorByIndex(int configIndex, int scanIndex);
|
||||
bool clearTempSensor(int configIndex);
|
||||
|
||||
Reference in New Issue
Block a user