Add temp sensor.
This commit is contained in:
20
README.md
20
README.md
@@ -116,7 +116,7 @@ The firmware in `src/main.cpp` implements the boilerplate as a compact Arduino E
|
|||||||
- Configurable API access control where each route can be `PUBLIC` or require one role.
|
- Configurable API access control where each route can be `PUBLIC` or require one role.
|
||||||
- Public boilerplate APIs: ping, add, and LED brightness.
|
- Public boilerplate APIs: ping, add, and LED brightness.
|
||||||
- LED brightness is persisted in non-volatile memory, applied on boot, and loaded into the Admin UI slider.
|
- LED brightness is persisted in non-volatile memory, applied on boot, and loaded into the Admin UI slider.
|
||||||
- DS18B20 temperature sensor readings are exposed through the live Admin UI card and `/api/uptime` compatibility endpoint.
|
- DS18B20 temperature sensor readings are exposed in Celsius and Fahrenheit through the live Admin UI card and `/api/temperature` endpoint.
|
||||||
- User management with the standard roles `Sysadmin`, `UserAdmin`, `WebUIConnect`, and `Debugger`.
|
- User management with the standard roles `Sysadmin`, `UserAdmin`, `WebUIConnect`, and `Debugger`.
|
||||||
- Custom role management. System roles are protected and cannot be deleted.
|
- Custom role management. System roles are protected and cannot be deleted.
|
||||||
- Active/inactive user accounts with role checkboxes in the Admin UI.
|
- Active/inactive user accounts with role checkboxes in the Admin UI.
|
||||||
@@ -204,7 +204,7 @@ The defaults can be changed at compile time:
|
|||||||
|
|
||||||
### DS18B20 temperature sensor
|
### DS18B20 temperature sensor
|
||||||
|
|
||||||
The live Admin UI card reads a DS18B20 one-wire temperature sensor. The default data pin is GPIO3, which is exposed as `D1` on the Seeed Studio XIAO ESP32C3.
|
The live Admin UI card reads a DS18B20 one-wire temperature sensor and displays both Celsius and Fahrenheit. The default data pin is GPIO3, which is exposed as `D1` on the Seeed Studio XIAO ESP32C3.
|
||||||
|
|
||||||
Connect the 3-wire sensor as follows:
|
Connect the 3-wire sensor as follows:
|
||||||
|
|
||||||
@@ -222,6 +222,20 @@ Keep the sensor powered from `3V3`, not `5V`, so the data line stays safe for th
|
|||||||
#define DS18B20_PIN 3
|
#define DS18B20_PIN 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Read the current temperature with:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/temperature
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
The response includes `temperatureC`, `temperatureF`, `sensorConnected`, and `pin`. The live dashboard subscribes to:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/temperature/events
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
Login:
|
Login:
|
||||||
@@ -326,6 +340,8 @@ Protected APIs and their default roles:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `POST /api/logout` | `WebUIConnect` |
|
| `POST /api/logout` | `WebUIConnect` |
|
||||||
| `GET /api/me` | `WebUIConnect` |
|
| `GET /api/me` | `WebUIConnect` |
|
||||||
|
| `GET /api/temperature` | `WebUIConnect` |
|
||||||
|
| `GET /api/temperature/events` | `WebUIConnect` |
|
||||||
| `GET /api/apis` | `Sysadmin` |
|
| `GET /api/apis` | `Sysadmin` |
|
||||||
| `POST /api/apis` | `Sysadmin` |
|
| `POST /api/apis` | `Sysadmin` |
|
||||||
| `GET /api/users` | `UserAdmin` |
|
| `GET /api/users` | `UserAdmin` |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<link rel="stylesheet" href="/css/app.css">
|
<link rel="stylesheet" href="/css/app.css">
|
||||||
<script defer src="/js/app.js"></script>
|
<script defer src="/js/app.js"></script>
|
||||||
<script type="module" src="/components/status-card.js"></script>
|
<script type="module" src="/components/status-card.js"></script>
|
||||||
<script type="module" src="/components/uptime-card.js"></script>
|
<script type="module" src="/components/temperature-card.js"></script>
|
||||||
<script type="module" src="/components/led-card.js"></script>
|
<script type="module" src="/components/led-card.js"></script>
|
||||||
<script type="module" src="/components/add-card.js"></script>
|
<script type="module" src="/components/add-card.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="dashboard" class="panel active"><div class="grid">
|
<div id="dashboard" class="panel active"><div class="grid">
|
||||||
<status-card></status-card>
|
<status-card></status-card>
|
||||||
<uptime-card></uptime-card>
|
<temperature-card></temperature-card>
|
||||||
<led-card></led-card>
|
<led-card></led-card>
|
||||||
<add-card></add-card>
|
<add-card></add-card>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class UptimeCard extends HTMLElement {
|
class TemperatureCard extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.source = null;
|
this.source = null;
|
||||||
@@ -7,12 +7,13 @@ class UptimeCard extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.innerHTML = `
|
this.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
:host{display:block}section{height:100%;box-sizing:border-box}.liveValue{font-size:34px;font-weight:700;line-height:1.15;margin-top:10px}.state{display:inline-flex;align-items:center;gap:6px;margin-top:6px}.dot{width:9px;height:9px;border-radius:50%;background:#b3261e}.dot.on{background:#188038}
|
:host{display:block}section{height:100%;box-sizing:border-box}.liveValue{font-size:34px;font-weight:700;line-height:1.15;margin-top:10px}.secondaryValue{font-size:18px;font-weight:600;margin-top:2px}.state{display:inline-flex;align-items:center;gap:6px;margin-top:6px}.dot{width:9px;height:9px;border-radius:50%;background:#b3261e}.dot.on{background:#188038}
|
||||||
</style>
|
</style>
|
||||||
<section>
|
<section>
|
||||||
<h3>Live temperature</h3>
|
<h3>Live temperature</h3>
|
||||||
<div class="muted">Server-Sent Events</div>
|
<div class="muted">Server-Sent Events</div>
|
||||||
<div class="liveValue"><span id="temperature">--</span>°C</div>
|
<div class="liveValue"><span id="temperatureC">--</span>°C</div>
|
||||||
|
<div class="secondaryValue"><span id="temperatureF">--</span>°F</div>
|
||||||
<div class="muted state"><span id="dot" class="dot"></span><span id="state">Disconnected</span></div>
|
<div class="muted state"><span id="dot" class="dot"></span><span id="state">Disconnected</span></div>
|
||||||
</section>`;
|
</section>`;
|
||||||
window.addEventListener('app:login', event => this.start(event.detail.token));
|
window.addEventListener('app:login', event => this.start(event.detail.token));
|
||||||
@@ -36,13 +37,14 @@ class UptimeCard extends HTMLElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState('Connecting', false);
|
this.setState('Connecting', false);
|
||||||
this.source = new EventSource('/api/uptime/events?token=' + encodeURIComponent(token));
|
this.source = new EventSource('/api/temperature/events?token=' + encodeURIComponent(token));
|
||||||
this.source.addEventListener('open', () => this.setState('Connected', true));
|
this.source.addEventListener('open', () => this.setState('Connected', true));
|
||||||
this.source.addEventListener('temperature', event => {
|
this.source.addEventListener('temperature', event => {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(event.data);
|
const json = JSON.parse(event.data);
|
||||||
const connected = !!json.sensorConnected && json.temperatureC !== null;
|
const connected = !!json.sensorConnected && json.temperatureC !== null && json.temperatureF !== null;
|
||||||
this.querySelector('#temperature').textContent = connected ? Number(json.temperatureC).toFixed(2) : '--';
|
this.querySelector('#temperatureC').textContent = connected ? Number(json.temperatureC).toFixed(2) : '--';
|
||||||
|
this.querySelector('#temperatureF').textContent = connected ? Number(json.temperatureF).toFixed(2) : '--';
|
||||||
this.setState(connected ? 'Connected' : 'Sensor unavailable', connected);
|
this.setState(connected ? 'Connected' : 'Sensor unavailable', connected);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState('Invalid event', false);
|
this.setState('Invalid event', false);
|
||||||
@@ -57,4 +59,4 @@ class UptimeCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('uptime-card', UptimeCard);
|
customElements.define('temperature-card', TemperatureCard);
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
void handlePing();
|
void handlePing();
|
||||||
void handleAdd();
|
void handleAdd();
|
||||||
void handleLed();
|
void handleLed();
|
||||||
void handleUptime();
|
void handleTemperature();
|
||||||
void handleUptimeEvents();
|
void handleTemperatureEvents();
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ ApiDef customApiDefs[] = {
|
|||||||
{"/api/ping", "GET", "", true, handlePing, nullptr},
|
{"/api/ping", "GET", "", true, handlePing, nullptr},
|
||||||
{"/api/add", "POST", "", true, handleAdd, nullptr},
|
{"/api/add", "POST", "", true, handleAdd, nullptr},
|
||||||
{"/api/led", "POST", "", true, handleLed, nullptr},
|
{"/api/led", "POST", "", true, handleLed, nullptr},
|
||||||
{"/api/uptime", "GET", "WebUIConnect", false, handleUptime, nullptr},
|
{"/api/temperature", "GET", "WebUIConnect", false, handleTemperature, nullptr},
|
||||||
{"/api/uptime/events", "GET", "WebUIConnect", false, handleUptimeEvents, nullptr},
|
{"/api/temperature/events", "GET", "WebUIConnect", false, handleTemperatureEvents, nullptr},
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t CUSTOM_API_DEF_COUNT = sizeof(customApiDefs) / sizeof(customApiDefs[0]);
|
const size_t CUSTOM_API_DEF_COUNT = sizeof(customApiDefs) / sizeof(customApiDefs[0]);
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ void handleLed() {
|
|||||||
sendJson(200, jsonOk("\"brightness\":" + String(ledBrightness)));
|
sendJson(200, jsonOk("\"brightness\":" + String(ledBrightness)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUptime() {
|
void handleTemperature() {
|
||||||
if (!authorize()) return;
|
if (!authorize()) return;
|
||||||
sendJson(200, jsonOk(temperatureJsonFields()));
|
sendJson(200, jsonOk(temperatureJsonFields()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUptimeEvents() {
|
void handleTemperatureEvents() {
|
||||||
if (!authorize()) return;
|
if (!authorize()) return;
|
||||||
String payload = "retry: 1000\n";
|
String payload = "retry: 1000\n";
|
||||||
payload += "event: temperature\n";
|
payload += "event: temperature\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user