Add DS18B20 payload sketch

This commit is contained in:
Christian Johannesen 2026-10-24 21:44:02 +02:00
parent 43dcd37ea7
commit 3960b4db6e
7 changed files with 267 additions and 7 deletions

View file

@ -1,5 +1,5 @@
HOST_PORT=8152
PUBLIC_DOMAIN=http://localhost
PUBLIC_DOMAIN=https://payloads.p20.dev
API_BEARER_TOKEN=replace-with-a-long-random-token
WIFI_SSID=replace-with-your-ssid
WIFI_PASSWORD=replace-with-your-password

View file

@ -156,6 +156,8 @@ Det samlede komponentindeks findes i [`components/`](components/README.md).
– browserstyret løbetekst via Wi-Fi
- [`wifi_matrix_status`](sketches/arduino-uno-r4-wifi/wifi_matrix_status) – forbinder
til et hotspot og viser forbindelse samt signalstyrke på LED-matrixen
- [`ds18b20_payload`](sketches/arduino-uno-r4-wifi/ds18b20_payload) – måler en
KS0023 DS18B20 og sender temperaturen til payload-serveren hvert 10. sekund
- [`lcd1602_i2c_demo`](sketches/arduino-uno-r4-wifi/lcd1602_i2c_demo) – effekt-demo
til et eksternt 16×2 HD44780-display med PCF8574 I²C-backpack

View file

@ -16,7 +16,11 @@
`DAT` forbindes til en digital pin. Modulet indeholder den nødvendige
pull-up-komponent; flere DS18B20-enheder kan dele samme 1-Wire-bus. Typiske
biblioteker er `OneWire` og `DallasTemperature`. Ingen sketches endnu.
biblioteker er `OneWire` og `DallasTemperature`.
## Sketches
- Arduino UNO R4 WiFi: [`ds18b20_payload`](../../sketches/arduino-uno-r4-wifi/ds18b20_payload) – måler hvert 10. sekund og sender temperaturen til payload-serveren.
## Kilde

View file

@ -12,6 +12,13 @@ const secretsPath = path.join(
"wifi_matrix_status",
"arduino_secrets.h",
);
const payloadSecretsPath = path.join(
repositoryRoot,
"sketches",
"arduino-uno-r4-wifi",
"ds18b20_payload",
"arduino_secrets.h",
);
const secrets = await fs.readFile(secretsPath, "utf8");
const readDefine = (name) => {
@ -20,14 +27,30 @@ const readDefine = (name) => {
return JSON.parse(`"${match[1]}"`);
};
const ssid = readDefine("SECRET_SSID");
const password = readDefine("SECRET_PASS");
const existingEnv = await fs.readFile(path.join(repositoryRoot, ".env"), "utf8").catch(() => "");
const existingToken = existingEnv.match(/^API_BEARER_TOKEN=(.+)$/m)?.[1]?.trim();
const apiToken = existingToken || crypto.randomBytes(32).toString("base64url");
const env = [
"HOST_PORT=8152",
"PUBLIC_DOMAIN=http://localhost",
`API_BEARER_TOKEN=${crypto.randomBytes(32).toString("base64url")}`,
`WIFI_SSID=${JSON.stringify(readDefine("SECRET_SSID"))}`,
`WIFI_PASSWORD=${JSON.stringify(readDefine("SECRET_PASS"))}`,
"PUBLIC_DOMAIN=https://payloads.p20.dev",
`API_BEARER_TOKEN=${apiToken}`,
`WIFI_SSID=${JSON.stringify(ssid)}`,
`WIFI_PASSWORD=${JSON.stringify(password)}`,
"",
].join("\n");
const payloadSecrets = [
"#pragma once",
"",
"// Generated by npm --prefix server run configure. Do not commit.",
`#define SECRET_SSID ${JSON.stringify(ssid)}`,
`#define SECRET_PASS ${JSON.stringify(password)}`,
`#define SECRET_API_TOKEN ${JSON.stringify(apiToken)}`,
"",
].join("\n");
await fs.writeFile(path.join(repositoryRoot, ".env"), env, { mode: 0o600 });
console.log("Created .env with HOST_PORT=8152, PUBLIC_DOMAIN=http://localhost and local secrets.");
await fs.writeFile(payloadSecretsPath, payloadSecrets, { mode: 0o600 });
console.log("Created .env and the DS18B20 payload sketch's local secret file.");

View file

@ -0,0 +1,49 @@
# DS18B20 til payload-server
Sketchen måler temperaturen med et Keyestudio KS0023 DS18B20-modul og sender
en JSON-payload til `https://payloads.p20.dev/api/payloads` hvert 10. sekund.
Den indbyggede 12×8 LED-matrix viser samtidig:
- et flueben, når Wi-Fi er forbundet, eller et kryds uden forbindelse;
- en stribe på 10 LED'er, som tæller ned med én LED pr. sekund.
## Tilslutning
| KS0023 | Arduino UNO R4 WiFi |
| --- | --- |
| `DAT` | D2 |
| `VCC` | 5V |
| `GND` | GND |
KS0023-modulet har pull-up-modstanden til 1-Wire-datalinjen monteret på boardet.
## Secrets
Kopiér `arduino_secrets.example.h` til `arduino_secrets.h`. Brug samme
`SECRET_SSID` og `SECRET_PASS` som i `wifi_matrix_status`, og indsæt værdien af
`API_BEARER_TOKEN` fra repoets lokale `.env` som `SECRET_API_TOKEN`.
Den lokale secret-fil er allerede ignoreret af Git.
## Biblioteker og kompilering
Installér `OneWire` og `DallasTemperature` i Arduino IDE's Library Manager.
`WiFiS3` og `Arduino_LED_Matrix` følger med UNO R4-boardpakken.
```powershell
arduino-cli lib install OneWire DallasTemperature
arduino-cli compile --fqbn arduino:renesas_uno:unor4wifi sketches/arduino-uno-r4-wifi/ds18b20_payload
arduino-cli upload -p COM5 --fqbn arduino:renesas_uno:unor4wifi sketches/arduino-uno-r4-wifi/ds18b20_payload
```
API-payloaden har formen:
```json
{
"topic": "sensors/temperature",
"payload": {
"device": "uno-r4-wifi-ds18b20",
"temperatureC": 22.50
}
}
```

View file

@ -0,0 +1,7 @@
#pragma once
// Copy this file to arduino_secrets.h and fill in the local values.
// arduino_secrets.h is ignored by Git and must never be committed.
#define SECRET_SSID "your-network-name"
#define SECRET_PASS "your-network-password"
#define SECRET_API_TOKEN "your-payload-api-bearer-token"

View file

@ -0,0 +1,175 @@
#include <Arduino_LED_Matrix.h>
#include <DallasTemperature.h>
#include <OneWire.h>
#include <WiFiS3.h>
#include "arduino_secrets.h"
constexpr uint8_t ROWS = 8;
constexpr uint8_t COLUMNS = 12;
constexpr uint8_t ONE_WIRE_PIN = 2;
constexpr unsigned long PAYLOAD_INTERVAL_MS = 10000;
constexpr unsigned long WIFI_RETRY_MS = 3000;
constexpr unsigned long RESPONSE_TIMEOUT_MS = 5000;
constexpr char API_HOST[] = "payloads.p20.dev";
constexpr uint16_t API_PORT = 443;
constexpr char API_PATH[] = "/api/payloads";
ArduinoLEDMatrix matrix;
OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature temperatureSensors(&oneWire);
uint8_t frame[ROWS][COLUMNS] = {};
unsigned long lastPayloadAt = 0;
unsigned long lastWiFiAttemptAt = 0;
int lastDisplayedSeconds = -1;
bool lastDisplayedConnected = false;
void clearFrame() {
memset(frame, 0, sizeof(frame));
}
void setPixel(int x, int y, bool on = true) {
if (x >= 0 && x < COLUMNS && y >= 0 && y < ROWS) {
frame[y][x] = on ? 1 : 0;
}
}
void showCountdown(uint8_t secondsRemaining, bool connected) {
clearFrame();
// Wi-Fi status: a check mark when connected, an X otherwise.
if (connected) {
setPixel(0, 2);
setPixel(1, 3);
setPixel(2, 2);
setPixel(3, 1);
} else {
setPixel(0, 0);
setPixel(1, 1);
setPixel(2, 2);
setPixel(2, 0);
setPixel(0, 2);
}
// Ten LEDs count down from 10 to 1 before the next payload.
const uint8_t lit = min(secondsRemaining, static_cast<uint8_t>(10));
for (uint8_t column = 0; column < lit; ++column) {
setPixel(column + 1, 6);
}
matrix.renderBitmap(frame, ROWS, COLUMNS);
}
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
if (millis() - lastWiFiAttemptAt < WIFI_RETRY_MS) return;
lastWiFiAttemptAt = millis();
Serial.print(F("Forbinder til Wi-Fi: "));
Serial.println(SECRET_SSID);
WiFi.begin(SECRET_SSID, SECRET_PASS);
}
bool postTemperature(float celsius) {
WiFiSSLClient client;
if (!client.connect(API_HOST, API_PORT)) {
Serial.println(F("Kunne ikke oprette HTTPS-forbindelse til payload-serveren."));
return false;
}
String body = F("{\"topic\":\"sensors/temperature\",\"payload\":{");
body += F("\"device\":\"uno-r4-wifi-ds18b20\",\"temperatureC\":");
body += String(celsius, 2);
body += F("}}");
client.print(F("POST "));
client.print(API_PATH);
client.println(F(" HTTP/1.1"));
client.print(F("Host: "));
client.println(API_HOST);
client.print(F("Authorization: Bearer "));
client.println(SECRET_API_TOKEN);
client.println(F("Content-Type: application/json"));
client.println(F("X-Device-ID: uno-r4-wifi-ds18b20"));
client.println(F("X-Client-Name: ds18b20-payload-sketch"));
client.print(F("Content-Length: "));
client.println(body.length());
client.println(F("Connection: close"));
client.println();
client.print(body);
const unsigned long startedAt = millis();
while (!client.available() && client.connected() &&
millis() - startedAt < RESPONSE_TIMEOUT_MS) {
delay(10);
}
if (!client.available()) {
Serial.println(F("Payload-serveren svarede ikke inden timeout."));
client.stop();
return false;
}
const String statusLine = client.readStringUntil('\n');
const bool accepted = statusLine.indexOf(" 201 ") >= 0;
Serial.print(F("Serverens svar: "));
Serial.println(statusLine);
client.stop();
return accepted;
}
void sendPayload() {
temperatureSensors.requestTemperatures();
const float celsius = temperatureSensors.getTempCByIndex(0);
if (celsius == DEVICE_DISCONNECTED_C) {
Serial.println(F("DS18B20 blev ikke fundet paa 1-Wire-bussen."));
return;
}
Serial.print(F("Temperatur: "));
Serial.print(celsius, 2);
Serial.println(F(" C"));
Serial.println(postTemperature(celsius) ? F("Payload sendt.") : F("Payload fejlede."));
}
void setup() {
Serial.begin(115200);
matrix.begin();
temperatureSensors.begin();
temperatureSensors.setResolution(10);
showCountdown(10, false);
lastWiFiAttemptAt = millis() - WIFI_RETRY_MS;
connectWiFi();
lastPayloadAt = millis();
}
void loop() {
connectWiFi();
const unsigned long elapsed = millis() - lastPayloadAt;
const uint8_t secondsRemaining = elapsed >= PAYLOAD_INTERVAL_MS
? 0
: static_cast<uint8_t>((PAYLOAD_INTERVAL_MS - elapsed + 999) / 1000);
const bool connected = WiFi.status() == WL_CONNECTED;
if (secondsRemaining != lastDisplayedSeconds || connected != lastDisplayedConnected) {
lastDisplayedSeconds = secondsRemaining;
lastDisplayedConnected = connected;
showCountdown(secondsRemaining, connected);
}
if (elapsed >= PAYLOAD_INTERVAL_MS) {
if (WiFi.status() == WL_CONNECTED) {
sendPayload();
} else {
Serial.println(F("Payload sprunget over: Wi-Fi er ikke forbundet."));
}
lastPayloadAt = millis();
lastDisplayedSeconds = -1;
}
delay(20);
}