Compare commits

..
Author SHA1 Message Date
Ben Meadors 8f18534ebd Protobufs 2026-02-11 06:24:02 -06:00
Ben Meadors fd084abbd9 Add STC31 CO2 sensor support and related configurations 2026-02-11 06:23:38 -06:00
101 changed files with 954 additions and 7536 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
pip install --no-cache-dir -U \
platformio==6.1.16 \
+4 -4
View File
@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.501
- renovate@43.10.3
- checkov@3.2.500
- renovate@43.4.0
- prettier@3.8.1
- trufflehog@3.93.3
- trufflehog@3.93.0
- yamllint@1.38.0
- bandit@1.9.3
- trivy@0.69.1
- taplo@0.10.0
- ruff@0.15.1
- ruff@0.15.0
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.1.0
+2 -2
View File
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
@@ -53,7 +53,7 @@ USER root
RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
libx11-6 libinput10 libxkbcommon-x11-0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \
+2 -2
View File
@@ -11,7 +11,7 @@ RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
&& rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
@@ -42,7 +42,7 @@ USER root
RUN apk --no-cache add \
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
i2c-tools libuv libx11 libinput libxkbcommon \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \
@@ -87,9 +87,6 @@
</screenshots>
<releases>
<release version="2.7.20" date="2026-02-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20</url>
</release>
<release version="2.7.19" date="2026-01-22">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
</release>
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Minesemi ME25LS01",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "MeshLink",
+1 -2
View File
@@ -31,8 +31,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Minimesh Lite",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "MINEWSEMI_MS24SF1_SX1262",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "BQ nRF52840",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "ProMicro compatible nRF52840",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed T1000-E",
+1 -2
View File
@@ -25,8 +25,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino", "freertos"],
"name": "Seeed WIO WM1110",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed WIO WM1110",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed WIO WM1110",
-6
View File
@@ -1,9 +1,3 @@
meshtasticd (2.7.20.0) unstable; urgency=medium
* Version 2.7.20
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 11 Feb 2026 12:19:54 +0000
meshtasticd (2.7.19.0) unstable; urgency=medium
* Version 2.7.19
+1 -2
View File
@@ -26,8 +26,7 @@ Build-Depends: debhelper-compat (= 13),
libx11-dev,
libinput-dev,
libxkbcommon-x11-dev,
libsqlite3-dev,
libsdl2-dev
libsqlite3-dev
Standards-Version: 4.6.2
Homepage: https://github.com/meshtastic/firmware
Rules-Requires-Root: no
-1
View File
@@ -49,7 +49,6 @@ BuildRequires: pkgconfig(libulfius)
BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11)
BuildRequires: pkgconfig(sdl2)
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
-30
View File
@@ -1,30 +0,0 @@
# Dependencies
node_modules/
# Build output
dist/
# Vite cache
.vite/
# TypeScript cache
*.tsbuildinfo
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
# Local env files
.env
.env.local
.env.*.local
-149
View File
@@ -1,149 +0,0 @@
# Meshtastic OLED Emulator
A Vue.js component and TypeScript library for emulating SSD1306/SH1106 OLED displays used in Meshtastic devices. This allows firmware UI development and testing directly in the browser.
## Features
- **Same API as firmware**: Uses the same `OLEDDisplay` API as esp8266-oled-ssd1306 library
- **Multiple display sizes**: 128x64, 128x32, 64x48, 128x128
- **Realistic rendering**: Pixel glow, OLED color tints (blue, white, yellow/blue dual)
- **XBM image support**: Render firmware icons and images
- **Font rendering**: Bitmap font support with text alignment
- **Vue 3 component**: Easy integration with Vue applications
## Installation
```bash
npm install @meshtastic/oled-emulator
```
## Quick Start
```vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import {
OLEDDisplay,
OLEDEmulator,
ArialMT_Plain_10,
} from "@meshtastic/oled-emulator";
// Create display matching firmware
const display = ref(new OLEDDisplay("128x64"));
onMounted(() => {
display.value.setFont(ArialMT_Plain_10);
display.value.clear();
display.value.drawString(10, 10, "Hello Mesh!");
display.value.drawRect(5, 5, 118, 54);
display.value.fillCircle(64, 32, 15);
});
</script>
<template>
<OLEDEmulator
:display="display"
:pixel-scale="4"
preset="ssd1306-blue"
:enable-glow="true"
/>
</template>
```
## OLEDDisplay API
The `OLEDDisplay` class provides the same methods as the firmware's display library:
```typescript
const display = new OLEDDisplay("128x64");
// Basic drawing
display.clear();
display.setPixel(x, y);
display.drawLine(x0, y0, x1, y1);
display.drawRect(x, y, width, height);
display.fillRect(x, y, width, height);
display.drawCircle(x, y, radius);
display.fillCircle(x, y, radius);
// Text
display.setFont(ArialMT_Plain_10);
display.setTextAlignment("TEXT_ALIGN_CENTER");
display.drawString(x, y, "Hello");
display.drawStringMaxWidth(x, y, maxWidth, "Long text...");
const width = display.getStringWidth("text");
// Images (XBM format)
display.drawXbm(x, y, width, height, imageData);
// Colors
display.setColor("WHITE"); // or 'BLACK', 'INVERSE'
// Buffer access (for custom rendering)
const buffer = display.getBuffer(); // Uint8Array
```
## Display Presets
The emulator includes several realistic display presets:
| Preset | Description |
| --------------------- | ------------------------------- |
| `ssd1306-blue` | Classic blue OLED (default) |
| `ssd1306-white` | White OLED |
| `ssd1306-yellow-blue` | Dual-color (top 16 rows yellow) |
| `sh1106` | SH1106 style |
| `sh1107` | SH1107 128x128 |
| `ssd1306-64x48` | Small 64x48 display |
| `ssd1306-128x32` | Wide 128x32 display |
## Screen Renderers
Pre-built screen renderers matching firmware UI:
```typescript
import { ScreenRenderers } from "@meshtastic/oled-emulator";
// Boot screen
ScreenRenderers.drawBootScreen(display, "2.5.0");
// Node info screen
ScreenRenderers.drawNodeInfoScreen(display, {
shortName: "MESH",
longName: "My Node",
nodeId: "!abcd1234",
lastHeard: "2m ago",
snr: 12.5,
});
// Message screen
ScreenRenderers.drawMessageScreen(display, {
from: "Alice",
text: "Hello from the mesh!",
time: "14:32",
channel: "LongFast",
});
// And more: drawGPSScreen, drawNodeListScreen, drawSystemScreen, drawCompassScreen
```
## Development
```bash
# Install dependencies
npm install
# Run development server
npm run dev
# Build library
npm run build
```
## Future: Protocol Bridge
A future enhancement will allow connecting this emulator to a real Meshtastic firmware instance (running on native/portduino), enabling live framebuffer streaming over WebSocket for real-time display mirroring.
## License
GPL-3.0 - Same as Meshtastic firmware
-22
View File
@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Meshtastic OLED Emulator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #121212;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
-2211
View File
File diff suppressed because it is too large Load Diff
-54
View File
@@ -1,54 +0,0 @@
{
"name": "@meshtastic/oled-emulator",
"version": "0.1.0",
"description": "Vue.js OLED display emulator for Meshtastic firmware UI development",
"keywords": [
"meshtastic",
"oled",
"ssd1306",
"sh1106",
"display",
"emulator",
"vue",
"vue3"
],
"author": "Meshtastic",
"license": "GPL-3.0",
"repository": {
"type": "git",
"url": "https://github.com/meshtastic/firmware.git",
"directory": "oled-emulator"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./style.css": "./dist/style.css"
},
"files": [
"dist",
"src"
],
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"type-check": "vue-tsc --noEmit"
},
"peerDependencies": {
"vue": "^3.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vite-plugin-dts": "^3.7.0",
"vue": "^3.4.0",
"vue-tsc": "^2.0.0"
}
}
File diff suppressed because it is too large Load Diff
-82
View File
@@ -1,82 +0,0 @@
/**
* DisplayPresets.ts - OLED display presets for emulator
*/
import type { OLEDDISPLAY_GEOMETRY } from "./OLEDDisplay";
// Display type presets with realistic OLED colors
export interface DisplayPreset {
name: string;
geometry: OLEDDISPLAY_GEOMETRY;
pixelOn: { r: number; g: number; b: number };
pixelOff: { r: number; g: number; b: number };
glow: number;
contrast: number;
}
export const DISPLAY_PRESETS: Record<string, DisplayPreset> = {
// Classic blue OLED (SSD1306)
"ssd1306-blue": {
name: "SSD1306 Blue",
geometry: "128x64",
pixelOn: { r: 100, g: 180, b: 255 },
pixelOff: { r: 2, g: 3, b: 8 },
glow: 0.4,
contrast: 1.0,
},
// White OLED (SSD1306)
"ssd1306-white": {
name: "SSD1306 White",
geometry: "128x64",
pixelOn: { r: 255, g: 255, b: 245 },
pixelOff: { r: 5, g: 5, b: 5 },
glow: 0.3,
contrast: 1.0,
},
// Yellow-blue dual color OLED
"ssd1306-yellow-blue": {
name: "SSD1306 Yellow/Blue",
geometry: "128x64",
// Yellow section is top 16 rows, blue is rest
pixelOn: { r: 255, g: 220, b: 50 }, // Will be overridden per-pixel
pixelOff: { r: 3, g: 3, b: 5 },
glow: 0.35,
contrast: 1.0,
},
// SH1106 (slightly different characteristics)
sh1106: {
name: "SH1106",
geometry: "128x64",
pixelOn: { r: 120, g: 200, b: 255 },
pixelOff: { r: 2, g: 2, b: 6 },
glow: 0.45,
contrast: 0.95,
},
// SH1107 128x128
sh1107: {
name: "SH1107 128x128",
geometry: "128x128",
pixelOn: { r: 255, g: 255, b: 240 },
pixelOff: { r: 4, g: 4, b: 6 },
glow: 0.3,
contrast: 1.0,
},
// Small 64x48 OLED
"ssd1306-64x48": {
name: "SSD1306 64x48",
geometry: "64x48",
pixelOn: { r: 100, g: 180, b: 255 },
pixelOff: { r: 2, g: 3, b: 8 },
glow: 0.4,
contrast: 1.0,
},
// Mini 128x32
"ssd1306-128x32": {
name: "SSD1306 128x32",
geometry: "128x32",
pixelOn: { r: 100, g: 180, b: 255 },
pixelOff: { r: 2, g: 3, b: 8 },
glow: 0.4,
contrast: 1.0,
},
};
-557
View File
@@ -1,557 +0,0 @@
/**
* OLEDDisplay.ts - TypeScript port of esp8266-oled-ssd1306 OLEDDisplay API
*
* This provides the same drawing API as the firmware uses, allowing
* Meshtastic screen rendering code to be ported to the web.
*
* Buffer format: Vertical byte packing (8 vertical pixels per byte)
* Same as SSD1306/SH1106 native format.
*/
export type OLEDDISPLAY_GEOMETRY =
| "128x64"
| "128x32"
| "64x48"
| "64x32"
| "128x128"
| "96x16";
export type OLEDDISPLAY_COLOR = "WHITE" | "BLACK" | "INVERSE";
export type OLEDDISPLAY_TEXT_ALIGNMENT =
| "TEXT_ALIGN_LEFT"
| "TEXT_ALIGN_CENTER"
| "TEXT_ALIGN_RIGHT";
export interface OLEDFont {
height: number;
firstChar: number;
charCount: number;
widths: number[];
data: Uint8Array;
}
// Resolution info based on geometry
const GEOMETRY_SIZES: Record<
OLEDDISPLAY_GEOMETRY,
{ width: number; height: number }
> = {
"128x64": { width: 128, height: 64 },
"128x32": { width: 128, height: 32 },
"64x48": { width: 64, height: 48 },
"64x32": { width: 64, height: 32 },
"128x128": { width: 128, height: 128 },
"96x16": { width: 96, height: 16 },
};
export class OLEDDisplay {
private buffer: Uint8Array;
private _width: number;
private _height: number;
private color: OLEDDISPLAY_COLOR = "WHITE";
private textAlignment: OLEDDISPLAY_TEXT_ALIGNMENT = "TEXT_ALIGN_LEFT";
private font: OLEDFont | null = null;
constructor(geometry: OLEDDISPLAY_GEOMETRY = "128x64") {
const size = GEOMETRY_SIZES[geometry];
this._width = size.width;
this._height = size.height;
// Buffer size: width * (height / 8) - vertical byte packing
this.buffer = new Uint8Array((this._width * this._height) / 8);
}
get width(): number {
return this._width;
}
get height(): number {
return this._height;
}
getWidth(): number {
return this._width;
}
getHeight(): number {
return this._height;
}
/**
* Get the raw framebuffer (vertical byte packing format)
*/
getBuffer(): Uint8Array {
return this.buffer;
}
getBufferSize(): number {
return this.buffer.length;
}
/**
* Clear the display buffer
*/
clear(): void {
this.buffer.fill(0);
}
/**
* Set the drawing color
*/
setColor(color: OLEDDISPLAY_COLOR): void {
this.color = color;
}
/**
* Set text alignment for drawString
*/
setTextAlignment(align: OLEDDISPLAY_TEXT_ALIGNMENT): void {
this.textAlignment = align;
}
/**
* Set the current font
*/
setFont(font: OLEDFont): void {
this.font = font;
}
/**
* Set a single pixel
*/
setPixel(x: number, y: number): void {
if (x < 0 || x >= this._width || y < 0 || y >= this._height) return;
// Vertical byte packing: each byte represents 8 vertical pixels
const byteIndex = x + Math.floor(y / 8) * this._width;
const bitMask = 1 << (y & 7);
switch (this.color) {
case "WHITE":
this.buffer[byteIndex] |= bitMask;
break;
case "BLACK":
this.buffer[byteIndex] &= ~bitMask;
break;
case "INVERSE":
this.buffer[byteIndex] ^= bitMask;
break;
}
}
/**
* Clear a single pixel (set to black)
*/
clearPixel(x: number, y: number): void {
if (x < 0 || x >= this._width || y < 0 || y >= this._height) return;
const byteIndex = x + Math.floor(y / 8) * this._width;
const bitMask = 1 << (y & 7);
this.buffer[byteIndex] &= ~bitMask;
}
/**
* Get a pixel value (true = on, false = off)
*/
getPixel(x: number, y: number): boolean {
if (x < 0 || x >= this._width || y < 0 || y >= this._height) return false;
const byteIndex = x + Math.floor(y / 8) * this._width;
const bitMask = 1 << (y & 7);
return (this.buffer[byteIndex] & bitMask) !== 0;
}
/**
* Draw a line using Bresenham's algorithm
*/
drawLine(x0: number, y0: number, x1: number, y1: number): void {
const dx = Math.abs(x1 - x0);
const dy = Math.abs(y1 - y0);
const sx = x0 < x1 ? 1 : -1;
const sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
while (true) {
this.setPixel(x0, y0);
if (x0 === x1 && y0 === y1) break;
const e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
/**
* Draw horizontal line (optimized)
*/
drawHorizontalLine(x: number, y: number, length: number): void {
if (y < 0 || y >= this._height) return;
for (let i = 0; i < length; i++) {
this.setPixel(x + i, y);
}
}
/**
* Draw vertical line (optimized)
*/
drawVerticalLine(x: number, y: number, length: number): void {
if (x < 0 || x >= this._width) return;
for (let i = 0; i < length; i++) {
this.setPixel(x, y + i);
}
}
/**
* Draw a rectangle outline
*/
drawRect(x: number, y: number, width: number, height: number): void {
this.drawHorizontalLine(x, y, width);
this.drawHorizontalLine(x, y + height - 1, width);
this.drawVerticalLine(x, y, height);
this.drawVerticalLine(x + width - 1, y, height);
}
/**
* Draw a filled rectangle
*/
fillRect(x: number, y: number, width: number, height: number): void {
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
this.setPixel(x + i, y + j);
}
}
}
/**
* Draw a circle outline using midpoint algorithm
*/
drawCircle(x0: number, y0: number, radius: number): void {
let x = radius;
let y = 0;
let err = 0;
while (x >= y) {
this.setPixel(x0 + x, y0 + y);
this.setPixel(x0 + y, y0 + x);
this.setPixel(x0 - y, y0 + x);
this.setPixel(x0 - x, y0 + y);
this.setPixel(x0 - x, y0 - y);
this.setPixel(x0 - y, y0 - x);
this.setPixel(x0 + y, y0 - x);
this.setPixel(x0 + x, y0 - y);
y++;
if (err <= 0) {
err += 2 * y + 1;
}
if (err > 0) {
x--;
err -= 2 * x + 1;
}
}
}
/**
* Draw a filled circle
*/
fillCircle(x0: number, y0: number, radius: number): void {
let x = radius;
let y = 0;
let err = 0;
while (x >= y) {
this.drawHorizontalLine(x0 - x, y0 + y, 2 * x + 1);
this.drawHorizontalLine(x0 - y, y0 + x, 2 * y + 1);
this.drawHorizontalLine(x0 - x, y0 - y, 2 * x + 1);
this.drawHorizontalLine(x0 - y, y0 - x, 2 * y + 1);
y++;
if (err <= 0) {
err += 2 * y + 1;
}
if (err > 0) {
x--;
err -= 2 * x + 1;
}
}
}
/**
* Draw a quarter circle (quadrant 0-3)
*/
drawCircleQuads(x0: number, y0: number, radius: number, quads: number): void {
let x = radius;
let y = 0;
let err = 0;
while (x >= y) {
if (quads & 0x1) {
this.setPixel(x0 + x, y0 - y);
this.setPixel(x0 + y, y0 - x);
}
if (quads & 0x2) {
this.setPixel(x0 - y, y0 - x);
this.setPixel(x0 - x, y0 - y);
}
if (quads & 0x4) {
this.setPixel(x0 - x, y0 + y);
this.setPixel(x0 - y, y0 + x);
}
if (quads & 0x8) {
this.setPixel(x0 + y, y0 + x);
this.setPixel(x0 + x, y0 + y);
}
y++;
if (err <= 0) {
err += 2 * y + 1;
}
if (err > 0) {
x--;
err -= 2 * x + 1;
}
}
}
/**
* Draw an XBM image (like firmware's drawXbm)
* XBM format: LSB first, horizontal
*/
drawXbm(
x: number,
y: number,
width: number,
height: number,
xbm: Uint8Array | number[],
): void {
const widthBytes = Math.ceil(width / 8);
for (let j = 0; j < height; j++) {
for (let i = 0; i < width; i++) {
const byteIndex = j * widthBytes + Math.floor(i / 8);
const bitIndex = i % 8;
if (xbm[byteIndex] & (1 << bitIndex)) {
this.setPixel(x + i, y + j);
}
}
}
}
/**
* Draw a PROGMEM-style image (vertical byte format, like some firmware icons)
*/
drawFastImage(
x: number,
y: number,
width: number,
height: number,
image: Uint8Array | number[],
): void {
const heightInBytes = Math.ceil(height / 8);
for (let i = 0; i < width; i++) {
for (let j = 0; j < heightInBytes; j++) {
const imageByte = image[i + j * width];
for (let bit = 0; bit < 8; bit++) {
if (imageByte & (1 << bit)) {
this.setPixel(x + i, y + j * 8 + bit);
}
}
}
}
}
/**
* Get the width of a string with the current font
*/
getStringWidth(text: string): number {
if (!this.font) return 0;
let width = 0;
for (const char of text) {
const charCode = char.charCodeAt(0);
if (
charCode >= this.font.firstChar &&
charCode < this.font.firstChar + this.font.charCount
) {
width += this.font.widths[charCode - this.font.firstChar];
} else {
// Unknown char - use space width or default
width += this.font.widths[0] || 4;
}
}
return width;
}
/**
* Draw a string at the given position
*/
drawString(x: number, y: number, text: string): void {
if (!this.font) return;
// Adjust x based on alignment
let startX = x;
if (this.textAlignment === "TEXT_ALIGN_CENTER") {
startX = x - Math.floor(this.getStringWidth(text) / 2);
} else if (this.textAlignment === "TEXT_ALIGN_RIGHT") {
startX = x - this.getStringWidth(text);
}
let cursorX = startX;
for (const char of text) {
const charCode = char.charCodeAt(0);
if (
charCode >= this.font.firstChar &&
charCode < this.font.firstChar + this.font.charCount
) {
const charIndex = charCode - this.font.firstChar;
const charWidth = this.font.widths[charIndex];
// Find the offset into the font data
let offset = 0;
const bytesPerColumn = Math.ceil(this.font.height / 8);
for (let i = 0; i < charIndex; i++) {
offset += this.font.widths[i] * bytesPerColumn;
}
// Draw the character
this.drawFontChar(
cursorX,
y,
charWidth,
this.font.height,
this.font.data,
offset,
);
cursorX += charWidth;
} else {
// Unknown character - advance by space
cursorX += this.font.widths[0] || 4;
}
}
}
/**
* Draw a single font character
*/
private drawFontChar(
x: number,
y: number,
width: number,
height: number,
data: Uint8Array,
offset: number,
): void {
const bytesPerColumn = Math.ceil(height / 8);
for (let col = 0; col < width; col++) {
for (let byteRow = 0; byteRow < bytesPerColumn; byteRow++) {
const dataByte = data[offset + col * bytesPerColumn + byteRow];
for (let bit = 0; bit < 8; bit++) {
const pixelY = byteRow * 8 + bit;
if (pixelY < height && dataByte & (1 << bit)) {
this.setPixel(x + col, y + pixelY);
}
}
}
}
}
/**
* Draw a string that wraps within maxWidth
*/
drawStringMaxWidth(
x: number,
y: number,
maxWidth: number,
text: string,
): void {
if (!this.font) return;
const words = text.split(" ");
let line = "";
let lineY = y;
for (const word of words) {
const testLine = line + (line ? " " : "") + word;
const testWidth = this.getStringWidth(testLine);
if (testWidth > maxWidth && line) {
this.drawString(x, lineY, line);
line = word;
lineY += this.font.height;
} else {
line = testLine;
}
}
if (line) {
this.drawString(x, lineY, line);
}
}
/**
* Draw a progress bar
*/
drawProgressBar(
x: number,
y: number,
width: number,
height: number,
progress: number,
): void {
// Clamp progress 0-100
progress = Math.max(0, Math.min(100, progress));
// Draw outline
this.drawRect(x, y, width, height);
// Draw fill
const fillWidth = Math.floor(((width - 4) * progress) / 100);
this.fillRect(x + 2, y + 2, fillWidth, height - 4);
}
/**
* Display - no-op for emulator (would send buffer to hardware)
*/
display(): void {
// In real hardware this sends the buffer to the display
// For emulator, this is a no-op - we render from getBuffer()
}
/**
* Flip the display (useful for some mounting orientations)
*/
flipScreenVertically(): void {
// Rotate buffer 180 degrees
const newBuffer = new Uint8Array(this.buffer.length);
const heightBytes = this._height / 8;
for (let x = 0; x < this._width; x++) {
for (let yByte = 0; yByte < heightBytes; yByte++) {
let srcByte = this.buffer[x + yByte * this._width];
// Reverse the bits
let reversed = 0;
for (let i = 0; i < 8; i++) {
if (srcByte & (1 << i)) {
reversed |= 1 << (7 - i);
}
}
// Place in mirrored position
const newX = this._width - 1 - x;
const newYByte = heightBytes - 1 - yByte;
newBuffer[newX + newYByte * this._width] = reversed;
}
}
this.buffer = newBuffer;
}
}
// Color constants for compatibility with firmware code style
export const WHITE: OLEDDISPLAY_COLOR = "WHITE";
export const BLACK: OLEDDISPLAY_COLOR = "BLACK";
export const INVERSE: OLEDDISPLAY_COLOR = "INVERSE";
export const TEXT_ALIGN_LEFT: OLEDDISPLAY_TEXT_ALIGNMENT = "TEXT_ALIGN_LEFT";
export const TEXT_ALIGN_CENTER: OLEDDISPLAY_TEXT_ALIGNMENT =
"TEXT_ALIGN_CENTER";
export const TEXT_ALIGN_RIGHT: OLEDDISPLAY_TEXT_ALIGNMENT = "TEXT_ALIGN_RIGHT";
-327
View File
@@ -1,327 +0,0 @@
<script setup lang="ts">
/**
* OLEDEmulator.vue - Vue 3 component for OLED display emulation
*
* Renders an OLEDDisplay framebuffer with realistic OLED visual effects
* including pixel glow, color tinting, and various display sizes.
*/
import { ref, computed, watch, onMounted, onUnmounted, type PropType } from 'vue';
import { OLEDDisplay } from './OLEDDisplay';
import { DISPLAY_PRESETS, type DisplayPreset } from './DisplayPresets';
const props = defineProps({
/**
* The OLEDDisplay instance to render
*/
display: {
type: Object as PropType<OLEDDisplay>,
required: true,
},
/**
* Pixel scale factor (how many canvas pixels per OLED pixel)
*/
pixelScale: {
type: Number,
default: 4,
},
/**
* Display preset name or custom preset object
*/
preset: {
type: [String, Object] as PropType<string | DisplayPreset>,
default: 'ssd1306-blue',
},
/**
* Enable pixel glow effect (slight bloom around lit pixels)
*/
enableGlow: {
type: Boolean,
default: true,
},
/**
* Enable sub-pixel rendering for more realistic look
*/
enableSubPixel: {
type: Boolean,
default: false,
},
/**
* Show pixel grid lines
*/
showGrid: {
type: Boolean,
default: false,
},
/**
* Auto-refresh interval in ms (0 = manual refresh only)
*/
refreshInterval: {
type: Number,
default: 50,
},
/**
* Yellow/blue split mode for dual-color OLEDs (rows 0-15 yellow)
*/
dualColor: {
type: Boolean,
default: false,
},
});
const emit = defineEmits<{
(e: 'click', x: number, y: number): void;
(e: 'refresh'): void;
}>();
const canvas = ref<HTMLCanvasElement | null>(null);
const refreshTimer = ref<number | null>(null);
// Get the current display preset
const currentPreset = computed<DisplayPreset>(() => {
if (typeof props.preset === 'string') {
return DISPLAY_PRESETS[props.preset] || DISPLAY_PRESETS['ssd1306-blue'];
}
return props.preset;
});
// Canvas dimensions
const canvasWidth = computed(() => props.display.width * props.pixelScale);
const canvasHeight = computed(() => props.display.height * props.pixelScale);
/**
* Render the framebuffer to canvas with OLED effects
*/
function render(): void {
const cvs = canvas.value;
if (!cvs) return;
const ctx = cvs.getContext('2d');
if (!ctx) return;
const buffer = props.display.getBuffer();
const width = props.display.width;
const height = props.display.height;
const scale = props.pixelScale;
const preset = currentPreset.value;
// Create image data
const imageData = ctx.createImageData(canvasWidth.value, canvasHeight.value);
const data = imageData.data;
// Yellow color for dual-color mode (top 16 rows)
const yellowOn = { r: 255, g: 220, b: 50 };
const blueOn = { r: 100, g: 180, b: 255 };
// Render each OLED pixel
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Get pixel from framebuffer (vertical byte packing)
const byteIndex = x + Math.floor(y / 8) * width;
const bitMask = 1 << (y & 7);
const isOn = (buffer[byteIndex] & bitMask) !== 0;
// Determine pixel color
let pixelColor: { r: number; g: number; b: number };
if (isOn) {
if (props.dualColor && y < 16) {
pixelColor = yellowOn;
} else if (props.dualColor) {
pixelColor = blueOn;
} else {
pixelColor = preset.pixelOn;
}
} else {
pixelColor = preset.pixelOff;
}
// Apply contrast
pixelColor = {
r: Math.min(255, pixelColor.r * preset.contrast),
g: Math.min(255, pixelColor.g * preset.contrast),
b: Math.min(255, pixelColor.b * preset.contrast),
};
// Scale up pixel to canvas
for (let sy = 0; sy < scale; sy++) {
for (let sx = 0; sx < scale; sx++) {
const px = x * scale + sx;
const py = y * scale + sy;
const idx = (py * canvasWidth.value + px) * 4;
// Apply glow effect (brighter center, dimmer edges)
let intensity = 1.0;
if (props.enableGlow && isOn) {
const centerX = scale / 2;
const centerY = scale / 2;
const dist = Math.sqrt(
Math.pow(sx - centerX, 2) + Math.pow(sy - centerY, 2)
);
const maxDist = Math.sqrt(2) * (scale / 2);
intensity = 1.0 - (dist / maxDist) * preset.glow;
}
// Apply sub-pixel rendering (RGB stripes)
if (props.enableSubPixel && isOn) {
const subPixelPos = sx % 3;
if (subPixelPos === 0) {
data[idx] = pixelColor.r * intensity * 1.2;
data[idx + 1] = pixelColor.g * intensity * 0.8;
data[idx + 2] = pixelColor.b * intensity * 0.8;
} else if (subPixelPos === 1) {
data[idx] = pixelColor.r * intensity * 0.8;
data[idx + 1] = pixelColor.g * intensity * 1.2;
data[idx + 2] = pixelColor.b * intensity * 0.8;
} else {
data[idx] = pixelColor.r * intensity * 0.8;
data[idx + 1] = pixelColor.g * intensity * 0.8;
data[idx + 2] = pixelColor.b * intensity * 1.2;
}
} else {
data[idx] = pixelColor.r * intensity;
data[idx + 1] = pixelColor.g * intensity;
data[idx + 2] = pixelColor.b * intensity;
}
data[idx + 3] = 255; // Alpha
}
}
}
}
// Draw grid lines if enabled
if (props.showGrid) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Draw right and bottom edge of each pixel
// Right edge
const rx = (x + 1) * scale - 1;
for (let sy = 0; sy < scale; sy++) {
const py = y * scale + sy;
const idx = (py * canvasWidth.value + rx) * 4;
data[idx] = Math.min(255, data[idx] + 20);
data[idx + 1] = Math.min(255, data[idx + 1] + 20);
data[idx + 2] = Math.min(255, data[idx + 2] + 20);
}
// Bottom edge
const by = (y + 1) * scale - 1;
for (let sx = 0; sx < scale; sx++) {
const px = x * scale + sx;
const idx = (by * canvasWidth.value + px) * 4;
data[idx] = Math.min(255, data[idx] + 20);
data[idx + 1] = Math.min(255, data[idx + 1] + 20);
data[idx + 2] = Math.min(255, data[idx + 2] + 20);
}
}
}
}
ctx.putImageData(imageData, 0, 0);
emit('refresh');
}
/**
* Handle canvas click
*/
function handleClick(event: MouseEvent): void {
const cvs = canvas.value;
if (!cvs) return;
const rect = cvs.getBoundingClientRect();
const scaleX = cvs.width / rect.width;
const scaleY = cvs.height / rect.height;
const canvasX = (event.clientX - rect.left) * scaleX;
const canvasY = (event.clientY - rect.top) * scaleY;
const oledX = Math.floor(canvasX / props.pixelScale);
const oledY = Math.floor(canvasY / props.pixelScale);
emit('click', oledX, oledY);
}
/**
* Start auto-refresh timer
*/
function startRefreshTimer(): void {
stopRefreshTimer();
if (props.refreshInterval > 0) {
refreshTimer.value = window.setInterval(render, props.refreshInterval);
}
}
/**
* Stop auto-refresh timer
*/
function stopRefreshTimer(): void {
if (refreshTimer.value !== null) {
clearInterval(refreshTimer.value);
refreshTimer.value = null;
}
}
// Lifecycle
onMounted(() => {
render();
startRefreshTimer();
});
onUnmounted(() => {
stopRefreshTimer();
});
// Watch for changes
watch(() => props.refreshInterval, startRefreshTimer);
watch(() => props.display, render, { deep: true });
watch(() => props.pixelScale, render);
watch(() => props.preset, render);
watch(() => props.enableGlow, render);
watch(() => props.showGrid, render);
watch(() => props.dualColor, render);
// Expose render method for manual refresh
defineExpose({ render });
</script>
<template>
<div class="oled-emulator-wrapper">
<canvas
ref="canvas"
:width="canvasWidth"
:height="canvasHeight"
class="oled-canvas"
@click="handleClick"
/>
<div class="oled-bezel" v-if="$slots.bezel">
<slot name="bezel" />
</div>
</div>
</template>
<style scoped>
.oled-emulator-wrapper {
display: inline-block;
position: relative;
background: #1a1a1a;
padding: 8px;
border-radius: 6px;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.oled-canvas {
display: block;
border-radius: 2px;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
.oled-bezel {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
</style>
-467
View File
@@ -1,467 +0,0 @@
/**
* OLEDFonts.ts - Font data for OLED display emulator
*
* These are simplified bitmap fonts matching the style used in
* Meshtastic firmware (based on ArialMT from esp8266-oled-ssd1306).
*
* Font format: Vertical byte packing, column-major order
* Each character is drawn column by column, with each byte representing
* 8 vertical pixels.
*/
import type { OLEDFont } from "./OLEDDisplay";
/**
* ArialMT Plain 10 - Small font (height ~13 pixels)
* Covers ASCII 32-126 (space through ~)
*/
export const ArialMT_Plain_10: OLEDFont = {
height: 13,
firstChar: 32,
charCount: 95,
widths: [
3, // space
3, // !
4, // "
6, // #
6, // $
9, // %
7, // &
2, // '
4, // (
4, // )
5, // *
6, // +
3, // ,
4, // -
3, // .
4, // /
6, // 0
6, // 1
6, // 2
6, // 3
6, // 4
6, // 5
6, // 6
6, // 7
6, // 8
6, // 9
3, // :
3, // ;
6, // <
6, // =
6, // >
6, // ?
11, // @
8, // A
7, // B
7, // C
7, // D
6, // E
6, // F
8, // G
7, // H
3, // I
5, // J
7, // K
6, // L
9, // M
7, // N
8, // O
6, // P
8, // Q
7, // R
6, // S
6, // T
7, // U
7, // V
11, // W
7, // X
7, // Y
6, // Z
4, // [
4, // \
4, // ]
6, // ^
6, // _
4, // `
6, // a
6, // b
5, // c
6, // d
6, // e
4, // f
6, // g
6, // h
3, // i
3, // j
6, // k
3, // l
9, // m
6, // n
6, // o
6, // p
6, // q
4, // r
5, // s
4, // t
6, // u
6, // v
9, // w
6, // x
6, // y
5, // z
4, // {
3, // |
4, // }
6, // ~
],
data: new Uint8Array([
// Space (32)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ! (33)
0x00, 0x00, 0xbe, 0x00, 0x00, 0x00,
// " (34)
0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
// # (35)
0x00, 0x48, 0xf8, 0x4e, 0xf8, 0x4e, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
// $ (36)
0x00, 0x9c, 0x92, 0xff, 0x92, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// % (37)
0x0c, 0x12, 0x12, 0x8c, 0x60, 0x18, 0xc6, 0x20, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// & (38)
0x00, 0x60, 0x9c, 0x92, 0x6a, 0xa4, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// ' (39)
0x00, 0x0e, 0x00, 0x00,
// ( (40)
0x00, 0x7c, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
// ) (41)
0x00, 0x82, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00,
// * (42)
0x14, 0x08, 0x3e, 0x08, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
// + (43)
0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// , (44)
0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
// - (45)
0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
// . (46)
0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
// / (47)
0x00, 0xc0, 0x38, 0x06, 0x00, 0x00, 0x00, 0x00,
// 0 (48)
0x7c, 0x82, 0x82, 0x82, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 1 (49)
0x00, 0x04, 0x02, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 2 (50)
0x84, 0xc2, 0xa2, 0x92, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 3 (51)
0x44, 0x82, 0x92, 0x92, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 4 (52)
0x30, 0x28, 0x24, 0xfe, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 5 (53)
0x4e, 0x8a, 0x8a, 0x8a, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 6 (54)
0x78, 0x94, 0x92, 0x92, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 7 (55)
0x02, 0xc2, 0x32, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 8 (56)
0x6c, 0x92, 0x92, 0x92, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 9 (57)
0x0c, 0x92, 0x92, 0x52, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// : (58)
0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
// ; (59)
0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
// < (60)
0x10, 0x28, 0x28, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// = (61)
0x28, 0x28, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// > (62)
0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ? (63)
0x04, 0x02, 0xa2, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// @ (64)
0xf0, 0x08, 0xe4, 0x12, 0x12, 0x12, 0xe2, 0x14, 0x08, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// A (65)
0x00, 0xc0, 0x38, 0x26, 0x26, 0x38, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// B (66)
0xfe, 0x92, 0x92, 0x92, 0x92, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// C (67)
0x7c, 0x82, 0x82, 0x82, 0x82, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// D (68)
0xfe, 0x82, 0x82, 0x82, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// E (69)
0xfe, 0x92, 0x92, 0x92, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// F (70)
0xfe, 0x12, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// G (71)
0x7c, 0x82, 0x82, 0x92, 0x92, 0x74, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// H (72)
0xfe, 0x10, 0x10, 0x10, 0x10, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// I (73)
0x00, 0xfe, 0x00, 0x00, 0x00, 0x00,
// J (74)
0x40, 0x80, 0x80, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// K (75)
0xfe, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// L (76)
0xfe, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// M (77)
0xfe, 0x04, 0x18, 0x60, 0x60, 0x18, 0x04, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// N (78)
0xfe, 0x04, 0x08, 0x30, 0x40, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// O (79)
0x7c, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// P (80)
0xfe, 0x12, 0x12, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Q (81)
0x7c, 0x82, 0x82, 0x82, 0x42, 0xc2, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// R (82)
0xfe, 0x12, 0x12, 0x32, 0x4c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// S (83)
0x4c, 0x92, 0x92, 0x92, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// T (84)
0x02, 0x02, 0xfe, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// U (85)
0x7e, 0x80, 0x80, 0x80, 0x80, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// V (86)
0x06, 0x18, 0x60, 0x80, 0x60, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// W (87)
0x06, 0x38, 0xc0, 0x38, 0x06, 0x38, 0xc0, 0x38, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// X (88)
0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// Y (89)
0x02, 0x04, 0x08, 0xf0, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// Z (90)
0xc2, 0xa2, 0x92, 0x8a, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// [ (91)
0x00, 0xfe, 0x82, 0x82, 0x00, 0x00, 0x00, 0x00,
// \ (92)
0x06, 0x38, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
// ] (93)
0x82, 0x82, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
// ^ (94)
0x08, 0x04, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// _ (95)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ` (96)
0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
// a (97)
0x40, 0xa8, 0xa8, 0xa8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// b (98)
0xfe, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// c (99)
0x70, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// d (100)
0x70, 0x88, 0x88, 0x88, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// e (101)
0x70, 0xa8, 0xa8, 0xa8, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// f (102)
0x08, 0xfc, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
// g (103)
0x30, 0x48, 0x48, 0x48, 0xf8, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
// h (104)
0xfe, 0x08, 0x08, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// i (105)
0x00, 0xfa, 0x00, 0x00, 0x00, 0x00,
// j (106)
0x00, 0x00, 0xfa, 0x01, 0x00, 0x00,
// k (107)
0xfe, 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// l (108)
0x00, 0xfe, 0x00, 0x00, 0x00, 0x00,
// m (109)
0xf8, 0x08, 0x08, 0xf0, 0x08, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// n (110)
0xf8, 0x08, 0x08, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// o (111)
0x70, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// p (112)
0xf8, 0x48, 0x48, 0x48, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// q (113)
0x30, 0x48, 0x48, 0x48, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
// r (114)
0xf8, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
// s (115)
0x90, 0xa8, 0xa8, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// t (116)
0x08, 0x7e, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,
// u (117)
0x78, 0x80, 0x80, 0x80, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// v (118)
0x18, 0x60, 0x80, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// w (119)
0x78, 0x80, 0x60, 0x18, 0x60, 0x80, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// x (120)
0x88, 0x50, 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// y (121)
0x18, 0x60, 0x80, 0x60, 0x18, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
// z (122)
0xc8, 0xa8, 0x98, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// { (123)
0x10, 0x6c, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
// | (124)
0x00, 0xfe, 0x00, 0x00, 0x00, 0x00,
// } (125)
0x82, 0x6c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
// ~ (126)
0x10, 0x08, 0x10, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]),
};
/**
* ArialMT Plain 16 - Medium font (height ~19 pixels)
* Simplified subset covering 0-9, A-Z, a-z, common punctuation
*/
export const ArialMT_Plain_16: OLEDFont = {
height: 19,
firstChar: 32,
charCount: 95,
widths: [
4, // space
4, // !
5, // "
8, // #
8, // $
12, // %
10, // &
2, // '
5, // (
5, // )
6, // *
8, // +
4, // ,
5, // -
4, // .
4, // /
8, // 0
8, // 1
8, // 2
8, // 3
8, // 4
8, // 5
8, // 6
8, // 7
8, // 8
8, // 9
4, // :
4, // ;
8, // <
8, // =
8, // >
8, // ?
15, // @
10, // A
9, // B
10, // C
10, // D
9, // E
8, // F
11, // G
9, // H
4, // I
6, // J
9, // K
8, // L
11, // M
9, // N
11, // O
9, // P
11, // Q
10, // R
9, // S
8, // T
9, // U
10, // V
14, // W
10, // X
10, // Y
9, // Z
4, // [
4, // \
4, // ]
7, // ^
8, // _
5, // `
8, // a
8, // b
7, // c
8, // d
8, // e
4, // f
8, // g
8, // h
4, // i
4, // j
8, // k
4, // l
12, // m
8, // n
8, // o
8, // p
8, // q
5, // r
7, // s
4, // t
8, // u
8, // v
12, // w
8, // x
8, // y
7, // z
5, // {
4, // |
5, // }
8, // ~
],
// Font data would be similar pattern but larger - using placeholder
data: new Uint8Array(2500).fill(0xff), // Placeholder - would contain actual font bitmap data
};
/**
* Helper to get FONT_HEIGHT_SMALL equivalent
*/
export const FONT_HEIGHT_SMALL = 13;
export const FONT_HEIGHT_MEDIUM = 19;
export const FONT_HEIGHT_LARGE = 28;
/**
* Helper function to create a font from raw byte data (for importing firmware fonts)
*/
export function createFontFromBytes(
height: number,
firstChar: number,
widths: number[],
data: number[],
): OLEDFont {
return {
height,
firstChar,
charCount: widths.length,
widths,
data: new Uint8Array(data),
};
}
-255
View File
@@ -1,255 +0,0 @@
/**
* OLEDImages.ts - Icon and image data for OLED display emulator
*
* These are direct ports of the XBM/bitmap icons from the Meshtastic firmware.
* Format: XBM style (LSB first, horizontal scanning)
*/
// Satellite icon (8x8)
export const imgSatellite_width = 8;
export const imgSatellite_height = 8;
export const imgSatellite = new Uint8Array([
0b00000000, 0b00000000, 0b00000000, 0b00011000, 0b11011011, 0b11111111,
0b11011011, 0b00011000,
]);
// USB icon (10x8)
export const imgUSB = new Uint8Array([
0x00, 0xfc, 0xf0, 0xfc, 0x88, 0xff, 0x86, 0xfe, 0x85, 0xfe, 0x89, 0xff, 0xf1,
0xfc, 0x00, 0xfc,
]);
// Power icon (8x16)
export const imgPower = new Uint8Array([
0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1c, 0x22, 0x22, 0x41, 0x7f,
0x22, 0x22, 0x22,
]);
// User icon (8x8)
export const imgUser = new Uint8Array([
0x3c, 0x42, 0x99, 0xa5, 0xa5, 0x99, 0x42, 0x3c,
]);
// Position empty arrow (6x8)
export const imgPositionEmpty = new Uint8Array([
0x20, 0x30, 0x28, 0x24, 0x42, 0xff,
]);
// Position solid arrow (6x8)
export const imgPositionSolid = new Uint8Array([
0x20, 0x30, 0x38, 0x3c, 0x7e, 0xff,
]);
// Mail icon (10x7)
export const mail_width = 10;
export const mail_height = 7;
export const mail = new Uint8Array([
0b11111111,
0b00, // Top line
0b10000001,
0b00, // Edges
0b11000011,
0b00, // Diagonals start
0b10100101,
0b00, // Inner M part
0b10011001,
0b00, // Inner M part
0b10000001,
0b00, // Edges
0b11111111,
0b00, // Bottom line
]);
// Hop icon (9x10)
export const hop_width = 9;
export const hop_height = 10;
export const hop = new Uint8Array([
0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, 0x00, 0xc0,
0x01, 0x40, 0x01, 0xc0, 0x01, 0x40, 0x00,
]);
// Mail icon (8x8)
export const icon_mail = new Uint8Array([
0b11111111, // ████████ top border
0b10000001, // █ █ sides
0b11000011, // ██ ██ diagonal
0b10100101, // █ █ █ █ inner M
0b10011001, // █ ██ █ inner M
0b10000001, // █ █ sides
0b10000001, // █ █ sides
0b11111111, // ████████ bottom
]);
// Compass icon (8x8)
export const icon_compass = new Uint8Array([
0x3c, // Row 0: ..####..
0x52, // Row 1: .#..#.#.
0x91, // Row 2: #...#..#
0x91, // Row 3: #...#..#
0x91, // Row 4: #...#..#
0x81, // Row 5: #......#
0x42, // Row 6: .#....#.
0x3c, // Row 7: ..####..
]);
// Radio icon (8x8)
export const icon_radio = new Uint8Array([
0x0f, // Row 0: ####....
0x10, // Row 1: ....#...
0x27, // Row 2: ###..#..
0x48, // Row 3: ...#..#.
0x93, // Row 4: ##..#..#
0xa4, // Row 5: ..#..#.#
0xa8, // Row 6: ...#.#.#
0xa9, // Row 7: #..#.#.#
]);
// System/settings icon (8x8)
export const icon_system = new Uint8Array([
0x24, // Row 0: ..#..#..
0x3c, // Row 1: ..####..
0xc3, // Row 2: ##....##
0x5a, // Row 3: .#.##.#.
0x5a, // Row 4: .#.##.#.
0xc3, // Row 5: ##....##
0x3c, // Row 6: ..####..
0x24, // Row 7: ..#..#..
]);
// WiFi icon (8x8)
export const icon_wifi = new Uint8Array([
0b00000000, 0b00011000, 0b00111100, 0b01111110, 0b11011011, 0b00011000,
0b00011000, 0b00000000,
]);
// Nodes icon (8x8)
export const icon_nodes = new Uint8Array([
0xf9, // Row 0: #..#####
0x00, // Row 1
0xf9, // Row 2: #..#####
0x00, // Row 3
0xf9, // Row 4: #..#####
0x00, // Row 5
0xf9, // Row 6: #..#####
0x00, // Row 7
]);
// Battery vertical (7x11)
export const batteryBitmap_v = new Uint8Array([
0b00011100, 0b00111110, 0b01000001, 0b01000001, 0b00000000, 0b00000000,
0b00000000, 0b01000001, 0b01000001, 0b01000001, 0b00111110,
]);
// Battery side gaps (8x3)
export const batteryBitmap_sidegaps_v = new Uint8Array([
0b10000010, 0b10000010, 0b10000010,
]);
// Lightning bolt vertical (5x5)
export const lightning_bolt_v = new Uint8Array([
0b00000100, 0b00000110, 0b00011111, 0b00001100, 0b00000100,
]);
// Horizontal battery bottom (13x9)
export const batteryBitmap_h_bottom = new Uint8Array([
0b00011110, 0b00000000, 0b00000001, 0b00000000, 0b00000001, 0b00000000,
0b00000001, 0b00000000, 0b00000001, 0b00000000, 0b00000001, 0b00000000,
0b00000001, 0b00000000, 0b00000001, 0b00000000, 0b00000001, 0b00000000,
0b00000001, 0b00000000, 0b00000001, 0b00000000, 0b00000001, 0b00000000,
0b00011110, 0b00000000,
]);
// Horizontal battery top (13x9)
export const batteryBitmap_h_top = new Uint8Array([
0b00111100, 0b00000000, 0b01000000, 0b00000000, 0b01000000, 0b00000000,
0b01000000, 0b00000000, 0b01000000, 0b00000000, 0b11000000, 0b00000000,
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000,
0b01000000, 0b00000000, 0b01000000, 0b00000000, 0b01000000, 0b00000000,
0b00111100, 0b00000000,
]);
// Lightning bolt horizontal (13x9)
export const lightning_bolt_h = new Uint8Array([
0b00000000, 0b00000000, 0b00100000, 0b00000000, 0b00110000, 0b00000000,
0b00111000, 0b00000000, 0b00111100, 0b00000000, 0b00011110, 0b00000000,
0b11111111, 0b00000000, 0b01111000, 0b00000000, 0b00111100, 0b00000000,
0b00011100, 0b00000000, 0b00001100, 0b00000000, 0b00000100, 0b00000000,
0b00000000, 0b00000000,
]);
// Signal bars icon (various signal strengths)
export const signal_bars = {
width: 12,
height: 8,
// 0 bars
none: new Uint8Array([
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000,
]),
// 1 bar
low: new Uint8Array([
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000011, 0b00000000, 0b00000011, 0b00000000,
]),
// 2 bars
medium: new Uint8Array([
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00001100, 0b00000000, 0b00001100, 0b00000000,
0b00001111, 0b00000000, 0b00001111, 0b00000000,
]),
// 3 bars
high: new Uint8Array([
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00110000, 0b00000000,
0b00110000, 0b00000000, 0b00111100, 0b00000000, 0b00111100, 0b00000000,
0b00111111, 0b00000000, 0b00111111, 0b00000000,
]),
// 4 bars (full)
full: new Uint8Array([
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11110000, 0b00000000,
0b11110000, 0b00000000, 0b11111100, 0b00000000, 0b11111100, 0b00000000,
0b11111111, 0b00000000, 0b11111111, 0b00000000,
]),
};
// Meshtastic logo (simplified 32x32)
export const logo_width = 32;
export const logo_height = 32;
export const meshtastic_logo = new Uint8Array([
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00,
0x03, 0xc0, 0x00, 0x80, 0x01, 0x80, 0x01, 0xc0, 0x00, 0x00, 0x03, 0x60, 0x00,
0x00, 0x06, 0x30, 0x00, 0x00, 0x0c, 0x18, 0xf8, 0x1f, 0x18, 0x08, 0x04, 0x20,
0x10, 0x0c, 0x02, 0x40, 0x30, 0x04, 0x01, 0x80, 0x20, 0x06, 0x01, 0x80, 0x60,
0x02, 0xe1, 0x87, 0x40, 0x02, 0x11, 0x88, 0x40, 0x03, 0x09, 0x90, 0xc0, 0x03,
0x09, 0x90, 0xc0, 0x02, 0x11, 0x88, 0x40, 0x02, 0xe1, 0x87, 0x40, 0x06, 0x01,
0x80, 0x60, 0x04, 0x01, 0x80, 0x20, 0x0c, 0x02, 0x40, 0x30, 0x08, 0x04, 0x20,
0x10, 0x18, 0xf8, 0x1f, 0x18, 0x30, 0x00, 0x00, 0x0c, 0x60, 0x00, 0x00, 0x06,
0xc0, 0x00, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0x00, 0x03, 0xc0, 0x00, 0x00,
0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
// Bluetooth icon (8x11)
export const icon_bluetooth = new Uint8Array([
0b00000000, 0b10000100, 0b01001010, 0b00101010, 0b00010100, 0b11111111,
0b00010100, 0b00101010, 0b01001010, 0b10000100, 0b00000000,
]);
// GPS/Location pin icon (8x12)
export const icon_gps_pin = new Uint8Array([
0b00111100, 0b01111110, 0b11111111, 0b11100111, 0b11100111, 0b11111111,
0b01111110, 0b00111100, 0b00011000, 0b00011000, 0b00001000, 0b00000000,
]);
// Message/chat bubble icon (10x8)
export const icon_message = new Uint8Array([
0b11111111, 0b00, 0b10000001, 0b00, 0b10000001, 0b00, 0b10000001, 0b00,
0b10000001, 0b00, 0b11111111, 0b00, 0b00000110, 0b00, 0b00000010, 0b00,
]);
/**
* Helper to convert icon data to XBM format if needed
*/
export function toXbm(data: Uint8Array | number[]): Uint8Array {
return data instanceof Uint8Array ? data : new Uint8Array(data);
}
-627
View File
@@ -1,627 +0,0 @@
/**
* ScreenRenderers.ts - Sample screen rendering functions
*
* These demonstrate how to port Meshtastic firmware UI screens
* to the OLED emulator. The API matches the firmware's drawing code.
*/
import {
OLEDDisplay,
WHITE,
BLACK,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_RIGHT,
} from "./OLEDDisplay";
import { Font_6x8 } from "./SimpleFonts";
import * as images from "./OLEDImages";
// Font height for simple 6x8 font
const FONT_HEIGHT_SMALL = 8;
// Screen resolution detection (matches SharedUIDisplay.cpp)
export type ScreenResolution = "UltraLow" | "Low" | "High";
export function determineScreenResolution(
width: number,
height: number,
): ScreenResolution {
if (width <= 64 || height <= 48) {
return "UltraLow";
}
if (width > 128) {
return "High";
}
return "Low";
}
// Helper for consistent line positioning
export function getTextPositions(display: OLEDDisplay): number[] {
const fontHeight = FONT_HEIGHT_SMALL;
return [
0, // textZeroLine
fontHeight - 1, // textFirstLine
fontHeight - 1 + (fontHeight - 5), // textSecondLine
fontHeight - 1 + 2 * (fontHeight - 5), // textThirdLine
fontHeight - 1 + 3 * (fontHeight - 5), // textFourthLine
fontHeight - 1 + 4 * (fontHeight - 5), // textFifthLine
];
}
/**
* Draw a rounded highlight box (used for inverted headers)
*/
export function drawRoundedHighlight(
display: OLEDDisplay,
x: number,
y: number,
w: number,
h: number,
r: number,
): void {
// Draw the center and side rectangles
display.fillRect(x + r, y, w - 2 * r, h); // center bar
display.fillRect(x, y + r, r, h - 2 * r); // left edge
display.fillRect(x + w - r, y + r, r, h - 2 * r); // right edge
// Draw the rounded corners using filled circles
display.fillCircle(x + r + 1, y + r, r); // top-left
display.fillCircle(x + w - r - 1, y + r, r); // top-right
display.fillCircle(x + r + 1, y + h - r - 1, r); // bottom-left
display.fillCircle(x + w - r - 1, y + h - r - 1, r); // bottom-right
}
/**
* Draw the common header bar (battery, title, icons)
*/
export function drawCommonHeader(
display: OLEDDisplay,
x: number,
y: number,
titleStr: string,
options: {
inverted?: boolean;
batteryPercent?: number;
isCharging?: boolean;
hasUSB?: boolean;
hasUnreadMessage?: boolean;
} = {},
): void {
const {
inverted = true,
batteryPercent = 75,
isCharging = false,
hasUSB = false,
hasUnreadMessage = false,
} = options;
const HEADER_OFFSET_Y = 1;
y += HEADER_OFFSET_Y;
display.setFont(Font_6x8);
const highlightHeight = FONT_HEIGHT_SMALL - 1;
const screenW = display.getWidth();
if (inverted) {
// Draw inverted header background
display.setColor(WHITE);
drawRoundedHighlight(display, x, y, screenW, highlightHeight, 2);
display.setColor(BLACK);
} else {
// Draw line under header
display.setColor(WHITE);
display.drawLine(0, 14, screenW, 14);
}
// Draw title centered
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(screenW / 2, y, titleStr);
// Reset color for remaining elements
if (inverted) {
display.setColor(WHITE);
}
// Draw battery on left
let batteryX = 1;
const batteryY = HEADER_OFFSET_Y + 1;
if (hasUSB && !isCharging) {
// Draw USB icon
display.drawXbm(batteryX + 1, batteryY + 2, 10, 8, images.imgUSB);
batteryX += 11;
} else {
// Draw battery outline
display.drawXbm(batteryX, batteryY, 7, 11, images.batteryBitmap_v);
if (isCharging) {
// Draw lightning bolt
display.drawXbm(
batteryX + 1,
batteryY + 3,
5,
5,
images.lightning_bolt_v,
);
} else {
// Draw battery level
display.drawXbm(
batteryX - 1,
batteryY + 4,
8,
3,
images.batteryBitmap_sidegaps_v,
);
const fillHeight = Math.floor((8 * batteryPercent) / 100);
const fillY = batteryY - fillHeight + 10;
display.fillRect(batteryX + 1, fillY, 5, fillHeight);
}
batteryX += 9;
}
// Draw mail icon on right if unread message
if (hasUnreadMessage) {
const mailX = screenW - images.mail_width - 2;
display.drawXbm(
mailX,
y + 2,
images.mail_width,
images.mail_height,
images.mail,
);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setColor(WHITE);
}
/**
* Draw a boot/splash screen with logo
*/
export function drawBootScreen(
display: OLEDDisplay,
appVersion: string = "2.5.0",
): void {
display.clear();
display.setFont(Font_6x8);
display.setColor(WHITE);
const centerX = display.getWidth() / 2;
const centerY = display.getHeight() / 2;
// Draw logo centered
const logoX = centerX - images.logo_width / 2;
const logoY = centerY - images.logo_height / 2 - 8;
display.drawXbm(
logoX,
logoY,
images.logo_width,
images.logo_height,
images.meshtastic_logo,
);
// Draw version below
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(
centerX,
centerY + images.logo_height / 2,
`v${appVersion}`,
);
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
/**
* Draw a node info screen
*/
export function drawNodeInfoScreen(
display: OLEDDisplay,
nodeInfo: {
shortName: string;
longName: string;
nodeId: string;
batteryLevel?: number;
lastHeard?: string;
snr?: number;
hopsAway?: number;
},
): void {
display.clear();
// Draw header
drawCommonHeader(display, 0, 0, "Node Info", {
inverted: true,
batteryPercent: 85,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
const lines = getTextPositions(display);
const screenW = display.getWidth();
// Node name (bolded by drawing twice offset)
display.drawString(0, lines[1], nodeInfo.longName);
display.drawString(1, lines[1], nodeInfo.longName);
// Short name and ID
display.drawString(0, lines[2], `${nodeInfo.shortName}${nodeInfo.nodeId}`);
// Last heard
if (nodeInfo.lastHeard) {
display.drawString(0, lines[3], `Heard: ${nodeInfo.lastHeard}`);
}
// Signal info on right side
if (nodeInfo.snr !== undefined) {
const snrStr = `SNR: ${nodeInfo.snr}dB`;
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(screenW, lines[3], snrStr);
}
// Hops
if (nodeInfo.hopsAway !== undefined) {
const hopStr =
nodeInfo.hopsAway === 0 ? "Direct" : `${nodeInfo.hopsAway} hops`;
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, lines[4], hopStr);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
/**
* Draw a message screen
*/
export function drawMessageScreen(
display: OLEDDisplay,
message: {
from: string;
text: string;
time: string;
channel?: string;
},
): void {
display.clear();
// Draw header with channel name if provided
const headerTitle = message.channel || "Message";
drawCommonHeader(display, 0, 0, headerTitle, {
inverted: true,
batteryPercent: 72,
hasUnreadMessage: true,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
const lines = getTextPositions(display);
const screenW = display.getWidth();
// From line
display.drawString(0, lines[1], `From: ${message.from}`);
// Time on right
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(screenW, lines[1], message.time);
// Message text (with word wrap)
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawStringMaxWidth(0, lines[2], screenW, message.text);
}
/**
* Draw a GPS/location screen
*/
export function drawGPSScreen(
display: OLEDDisplay,
gpsInfo: {
latitude?: number;
longitude?: number;
altitude?: number;
satellites?: number;
hasLock: boolean;
speed?: number;
},
): void {
display.clear();
drawCommonHeader(display, 0, 0, "GPS", {
inverted: true,
batteryPercent: 90,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
const lines = getTextPositions(display);
// Draw satellite icon and count
display.drawXbm(
0,
lines[1],
images.imgSatellite_width,
images.imgSatellite_height,
images.imgSatellite,
);
if (!gpsInfo.hasLock) {
display.drawString(12, lines[1], "No Lock");
} else {
display.drawString(12, lines[1], `${gpsInfo.satellites || 0} sats`);
}
if (
gpsInfo.hasLock &&
gpsInfo.latitude !== undefined &&
gpsInfo.longitude !== undefined
) {
// Coordinates
display.drawString(0, lines[2], `Lat: ${gpsInfo.latitude.toFixed(6)}`);
display.drawString(0, lines[3], `Lon: ${gpsInfo.longitude.toFixed(6)}`);
// Altitude
if (gpsInfo.altitude !== undefined) {
display.drawString(0, lines[4], `Alt: ${gpsInfo.altitude.toFixed(0)}m`);
}
// Speed
if (gpsInfo.speed !== undefined) {
const screenW = display.getWidth();
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(screenW, lines[4], `${gpsInfo.speed.toFixed(1)} km/h`);
}
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
/**
* Draw a node list screen
*/
export function drawNodeListScreen(
display: OLEDDisplay,
nodes: Array<{
shortName: string;
longName: string;
lastHeard: string;
snr?: number;
}>,
selectedIndex: number = 0,
): void {
display.clear();
drawCommonHeader(display, 0, 0, `Nodes (${nodes.length})`, {
inverted: true,
batteryPercent: 80,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
const lines = getTextPositions(display);
const screenW = display.getWidth();
const maxVisibleNodes = 4;
const startIndex = Math.max(
0,
selectedIndex - Math.floor(maxVisibleNodes / 2),
);
for (let i = 0; i < maxVisibleNodes && startIndex + i < nodes.length; i++) {
const node = nodes[startIndex + i];
const lineY = lines[i + 1];
const isSelected = startIndex + i === selectedIndex;
// Highlight selected row
if (isSelected) {
display.fillRect(0, lineY - 1, screenW, FONT_HEIGHT_SMALL);
display.setColor(BLACK);
}
// Node short name
display.drawString(0, lineY, node.shortName);
// Last heard on right
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(screenW, lineY, node.lastHeard);
display.setTextAlignment(TEXT_ALIGN_LEFT);
if (isSelected) {
display.setColor(WHITE);
}
}
}
/**
* Draw a system info screen
*/
export function drawSystemScreen(
display: OLEDDisplay,
sysInfo: {
uptime: string;
channelUtil: number;
airUtil: number;
batteryVoltage?: number;
nodes: number;
freeMemory?: number;
},
): void {
display.clear();
drawCommonHeader(display, 0, 0, "System", {
inverted: true,
batteryPercent: 95,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
const lines = getTextPositions(display);
const screenW = display.getWidth();
// Uptime
display.drawString(0, lines[1], `Uptime: ${sysInfo.uptime}`);
// Channel utilization
display.drawString(0, lines[2], `ChUtil: ${sysInfo.channelUtil.toFixed(1)}%`);
// Air utilization
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(
screenW,
lines[2],
`AirTx: ${sysInfo.airUtil.toFixed(1)}%`,
);
display.setTextAlignment(TEXT_ALIGN_LEFT);
// Nodes
display.drawString(0, lines[3], `Nodes: ${sysInfo.nodes}`);
// Battery voltage
if (sysInfo.batteryVoltage !== undefined) {
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(
screenW,
lines[3],
`${sysInfo.batteryVoltage.toFixed(2)}V`,
);
}
// Free memory
if (sysInfo.freeMemory !== undefined) {
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(
0,
lines[4],
`Free: ${(sysInfo.freeMemory / 1024).toFixed(0)}KB`,
);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
/**
* Draw a compass screen with bearing arrow
*/
export function drawCompassScreen(
display: OLEDDisplay,
compassInfo: {
heading: number; // Device heading in degrees
bearing: number; // Bearing to target in degrees
distance?: number; // Distance to target in meters
targetName?: string;
},
): void {
display.clear();
drawCommonHeader(display, 0, 0, compassInfo.targetName || "Compass", {
inverted: true,
batteryPercent: 70,
});
display.setFont(Font_6x8);
display.setColor(WHITE);
const screenW = display.getWidth();
const screenH = display.getHeight();
// Compass center and radius
const compassRadius = Math.min(screenW, screenH - 20) / 2 - 4;
const compassX = screenW / 2;
const compassY = (screenH + 16) / 2;
// Draw compass circle
display.drawCircle(compassX, compassY, compassRadius);
// Draw cardinal directions
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(compassX, compassY - compassRadius - 10, "N");
// Calculate relative bearing (bearing - heading)
const relativeBearing =
((compassInfo.bearing - compassInfo.heading) * Math.PI) / 180;
// Draw bearing arrow
const arrowLength = compassRadius - 6;
const arrowX = compassX + Math.sin(relativeBearing) * arrowLength;
const arrowY = compassY - Math.cos(relativeBearing) * arrowLength;
display.drawLine(compassX, compassY, arrowX, arrowY);
// Draw arrowhead
const headAngle = 0.4;
const headLength = 8;
const angle1 = relativeBearing + Math.PI - headAngle;
const angle2 = relativeBearing + Math.PI + headAngle;
display.drawLine(
arrowX,
arrowY,
arrowX + Math.sin(angle1) * headLength,
arrowY - Math.cos(angle1) * headLength,
);
display.drawLine(
arrowX,
arrowY,
arrowX + Math.sin(angle2) * headLength,
arrowY - Math.cos(angle2) * headLength,
);
// Draw distance at bottom
if (compassInfo.distance !== undefined) {
let distStr: string;
if (compassInfo.distance >= 1000) {
distStr = `${(compassInfo.distance / 1000).toFixed(1)} km`;
} else {
distStr = `${Math.round(compassInfo.distance)} m`;
}
display.drawString(compassX, screenH - FONT_HEIGHT_SMALL, distStr);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
/**
* Draw a progress/loading screen
*/
export function drawProgressScreen(
display: OLEDDisplay,
title: string,
progress: number,
statusText?: string,
): void {
display.clear();
display.setFont(Font_6x8);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_CENTER);
const screenW = display.getWidth();
const screenH = display.getHeight();
const centerX = screenW / 2;
const centerY = screenH / 2;
// Title
display.drawString(centerX, centerY - 20, title);
// Progress bar
const barWidth = screenW - 20;
const barHeight = 10;
const barX = 10;
const barY = centerY - barHeight / 2;
display.drawProgressBar(barX, barY, barWidth, barHeight, progress);
// Progress percentage
display.drawString(centerX, barY + barHeight + 4, `${Math.round(progress)}%`);
// Status text
if (statusText) {
display.drawString(centerX, screenH - FONT_HEIGHT_SMALL - 2, statusText);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
}
-421
View File
@@ -1,421 +0,0 @@
/**
* SimpleFonts.ts - Simple working bitmap fonts for OLED emulator
*
* These are basic monospace bitmap fonts that are easy to render correctly.
* Format: Each character is stored in column-major order, 1 byte per column.
* For fonts taller than 8px, multiple bytes per column are used.
*/
import type { OLEDFont } from "./OLEDDisplay";
/**
* Simple 6x8 monospace font
* Each character is 6 columns wide, 8 rows tall
* 1 byte per column (8 vertical pixels)
* Includes ASCII 32-126
*/
export const Font_6x8: OLEDFont = {
height: 8,
firstChar: 32,
charCount: 95,
widths: new Array(95).fill(6), // Fixed width font
data: new Uint8Array([
// Space (32)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ! (33)
0x00, 0x00, 0x5f, 0x00, 0x00, 0x00,
// " (34)
0x00, 0x07, 0x00, 0x07, 0x00, 0x00,
// # (35)
0x14, 0x7f, 0x14, 0x7f, 0x14, 0x00,
// $ (36)
0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x00,
// % (37)
0x23, 0x13, 0x08, 0x64, 0x62, 0x00,
// & (38)
0x36, 0x49, 0x55, 0x22, 0x50, 0x00,
// ' (39)
0x00, 0x05, 0x03, 0x00, 0x00, 0x00,
// ( (40)
0x00, 0x1c, 0x22, 0x41, 0x00, 0x00,
// ) (41)
0x00, 0x41, 0x22, 0x1c, 0x00, 0x00,
// * (42)
0x08, 0x2a, 0x1c, 0x2a, 0x08, 0x00,
// + (43)
0x08, 0x08, 0x3e, 0x08, 0x08, 0x00,
// , (44)
0x00, 0x50, 0x30, 0x00, 0x00, 0x00,
// - (45)
0x08, 0x08, 0x08, 0x08, 0x08, 0x00,
// . (46)
0x00, 0x60, 0x60, 0x00, 0x00, 0x00,
// / (47)
0x20, 0x10, 0x08, 0x04, 0x02, 0x00,
// 0 (48)
0x3e, 0x51, 0x49, 0x45, 0x3e, 0x00,
// 1 (49)
0x00, 0x42, 0x7f, 0x40, 0x00, 0x00,
// 2 (50)
0x42, 0x61, 0x51, 0x49, 0x46, 0x00,
// 3 (51)
0x21, 0x41, 0x45, 0x4b, 0x31, 0x00,
// 4 (52)
0x18, 0x14, 0x12, 0x7f, 0x10, 0x00,
// 5 (53)
0x27, 0x45, 0x45, 0x45, 0x39, 0x00,
// 6 (54)
0x3c, 0x4a, 0x49, 0x49, 0x30, 0x00,
// 7 (55)
0x01, 0x71, 0x09, 0x05, 0x03, 0x00,
// 8 (56)
0x36, 0x49, 0x49, 0x49, 0x36, 0x00,
// 9 (57)
0x06, 0x49, 0x49, 0x29, 0x1e, 0x00,
// : (58)
0x00, 0x36, 0x36, 0x00, 0x00, 0x00,
// ; (59)
0x00, 0x56, 0x36, 0x00, 0x00, 0x00,
// < (60)
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,
// = (61)
0x14, 0x14, 0x14, 0x14, 0x14, 0x00,
// > (62)
0x41, 0x22, 0x14, 0x08, 0x00, 0x00,
// ? (63)
0x02, 0x01, 0x51, 0x09, 0x06, 0x00,
// @ (64)
0x32, 0x49, 0x79, 0x41, 0x3e, 0x00,
// A (65)
0x7e, 0x11, 0x11, 0x11, 0x7e, 0x00,
// B (66)
0x7f, 0x49, 0x49, 0x49, 0x36, 0x00,
// C (67)
0x3e, 0x41, 0x41, 0x41, 0x22, 0x00,
// D (68)
0x7f, 0x41, 0x41, 0x22, 0x1c, 0x00,
// E (69)
0x7f, 0x49, 0x49, 0x49, 0x41, 0x00,
// F (70)
0x7f, 0x09, 0x09, 0x01, 0x01, 0x00,
// G (71)
0x3e, 0x41, 0x41, 0x51, 0x32, 0x00,
// H (72)
0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00,
// I (73)
0x00, 0x41, 0x7f, 0x41, 0x00, 0x00,
// J (74)
0x20, 0x40, 0x41, 0x3f, 0x01, 0x00,
// K (75)
0x7f, 0x08, 0x14, 0x22, 0x41, 0x00,
// L (76)
0x7f, 0x40, 0x40, 0x40, 0x40, 0x00,
// M (77)
0x7f, 0x02, 0x04, 0x02, 0x7f, 0x00,
// N (78)
0x7f, 0x04, 0x08, 0x10, 0x7f, 0x00,
// O (79)
0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00,
// P (80)
0x7f, 0x09, 0x09, 0x09, 0x06, 0x00,
// Q (81)
0x3e, 0x41, 0x51, 0x21, 0x5e, 0x00,
// R (82)
0x7f, 0x09, 0x19, 0x29, 0x46, 0x00,
// S (83)
0x46, 0x49, 0x49, 0x49, 0x31, 0x00,
// T (84)
0x01, 0x01, 0x7f, 0x01, 0x01, 0x00,
// U (85)
0x3f, 0x40, 0x40, 0x40, 0x3f, 0x00,
// V (86)
0x1f, 0x20, 0x40, 0x20, 0x1f, 0x00,
// W (87)
0x7f, 0x20, 0x18, 0x20, 0x7f, 0x00,
// X (88)
0x63, 0x14, 0x08, 0x14, 0x63, 0x00,
// Y (89)
0x03, 0x04, 0x78, 0x04, 0x03, 0x00,
// Z (90)
0x61, 0x51, 0x49, 0x45, 0x43, 0x00,
// [ (91)
0x00, 0x00, 0x7f, 0x41, 0x41, 0x00,
// \ (92)
0x02, 0x04, 0x08, 0x10, 0x20, 0x00,
// ] (93)
0x41, 0x41, 0x7f, 0x00, 0x00, 0x00,
// ^ (94)
0x04, 0x02, 0x01, 0x02, 0x04, 0x00,
// _ (95)
0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
// ` (96)
0x00, 0x01, 0x02, 0x04, 0x00, 0x00,
// a (97)
0x20, 0x54, 0x54, 0x54, 0x78, 0x00,
// b (98)
0x7f, 0x48, 0x44, 0x44, 0x38, 0x00,
// c (99)
0x38, 0x44, 0x44, 0x44, 0x20, 0x00,
// d (100)
0x38, 0x44, 0x44, 0x48, 0x7f, 0x00,
// e (101)
0x38, 0x54, 0x54, 0x54, 0x18, 0x00,
// f (102)
0x08, 0x7e, 0x09, 0x01, 0x02, 0x00,
// g (103)
0x08, 0x14, 0x54, 0x54, 0x3c, 0x00,
// h (104)
0x7f, 0x08, 0x04, 0x04, 0x78, 0x00,
// i (105)
0x00, 0x44, 0x7d, 0x40, 0x00, 0x00,
// j (106)
0x20, 0x40, 0x44, 0x3d, 0x00, 0x00,
// k (107)
0x00, 0x7f, 0x10, 0x28, 0x44, 0x00,
// l (108)
0x00, 0x41, 0x7f, 0x40, 0x00, 0x00,
// m (109)
0x7c, 0x04, 0x18, 0x04, 0x78, 0x00,
// n (110)
0x7c, 0x08, 0x04, 0x04, 0x78, 0x00,
// o (111)
0x38, 0x44, 0x44, 0x44, 0x38, 0x00,
// p (112)
0x7c, 0x14, 0x14, 0x14, 0x08, 0x00,
// q (113)
0x08, 0x14, 0x14, 0x18, 0x7c, 0x00,
// r (114)
0x7c, 0x08, 0x04, 0x04, 0x08, 0x00,
// s (115)
0x48, 0x54, 0x54, 0x54, 0x20, 0x00,
// t (116)
0x04, 0x3f, 0x44, 0x40, 0x20, 0x00,
// u (117)
0x3c, 0x40, 0x40, 0x20, 0x7c, 0x00,
// v (118)
0x1c, 0x20, 0x40, 0x20, 0x1c, 0x00,
// w (119)
0x3c, 0x40, 0x30, 0x40, 0x3c, 0x00,
// x (120)
0x44, 0x28, 0x10, 0x28, 0x44, 0x00,
// y (121)
0x0c, 0x50, 0x50, 0x50, 0x3c, 0x00,
// z (122)
0x44, 0x64, 0x54, 0x4c, 0x44, 0x00,
// { (123)
0x00, 0x08, 0x36, 0x41, 0x00, 0x00,
// | (124)
0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
// } (125)
0x00, 0x41, 0x36, 0x08, 0x00, 0x00,
// ~ (126)
0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00,
]),
};
/**
* Simple 5x7 monospace font (more compact)
* Each character is 5 columns wide, 7 rows tall
* 1 byte per column
*/
export const Font_5x7: OLEDFont = {
height: 7,
firstChar: 32,
charCount: 95,
widths: new Array(95).fill(5),
data: new Uint8Array([
// Space (32)
0x00, 0x00, 0x00, 0x00, 0x00,
// ! (33)
0x00, 0x00, 0x2f, 0x00, 0x00,
// " (34)
0x00, 0x07, 0x00, 0x07, 0x00,
// # (35)
0x14, 0x3e, 0x14, 0x3e, 0x14,
// $ (36)
0x24, 0x2a, 0x7f, 0x2a, 0x12,
// % (37)
0x23, 0x13, 0x08, 0x64, 0x62,
// & (38)
0x36, 0x49, 0x55, 0x22, 0x50,
// ' (39)
0x00, 0x05, 0x03, 0x00, 0x00,
// ( (40)
0x00, 0x1c, 0x22, 0x41, 0x00,
// ) (41)
0x00, 0x41, 0x22, 0x1c, 0x00,
// * (42)
0x14, 0x08, 0x3e, 0x08, 0x14,
// + (43)
0x08, 0x08, 0x3e, 0x08, 0x08,
// , (44)
0x00, 0x50, 0x30, 0x00, 0x00,
// - (45)
0x08, 0x08, 0x08, 0x08, 0x08,
// . (46)
0x00, 0x30, 0x30, 0x00, 0x00,
// / (47)
0x20, 0x10, 0x08, 0x04, 0x02,
// 0 (48)
0x3e, 0x51, 0x49, 0x45, 0x3e,
// 1 (49)
0x00, 0x42, 0x7f, 0x40, 0x00,
// 2 (50)
0x42, 0x61, 0x51, 0x49, 0x46,
// 3 (51)
0x21, 0x41, 0x45, 0x4b, 0x31,
// 4 (52)
0x18, 0x14, 0x12, 0x7f, 0x10,
// 5 (53)
0x27, 0x45, 0x45, 0x45, 0x39,
// 6 (54)
0x3c, 0x4a, 0x49, 0x49, 0x30,
// 7 (55)
0x01, 0x71, 0x09, 0x05, 0x03,
// 8 (56)
0x36, 0x49, 0x49, 0x49, 0x36,
// 9 (57)
0x06, 0x49, 0x49, 0x29, 0x1e,
// : (58)
0x00, 0x36, 0x36, 0x00, 0x00,
// ; (59)
0x00, 0x56, 0x36, 0x00, 0x00,
// < (60)
0x08, 0x14, 0x22, 0x41, 0x00,
// = (61)
0x14, 0x14, 0x14, 0x14, 0x14,
// > (62)
0x00, 0x41, 0x22, 0x14, 0x08,
// ? (63)
0x02, 0x01, 0x51, 0x09, 0x06,
// @ (64)
0x32, 0x49, 0x79, 0x41, 0x3e,
// A (65)
0x7e, 0x11, 0x11, 0x11, 0x7e,
// B (66)
0x7f, 0x49, 0x49, 0x49, 0x36,
// C (67)
0x3e, 0x41, 0x41, 0x41, 0x22,
// D (68)
0x7f, 0x41, 0x41, 0x22, 0x1c,
// E (69)
0x7f, 0x49, 0x49, 0x49, 0x41,
// F (70)
0x7f, 0x09, 0x09, 0x09, 0x01,
// G (71)
0x3e, 0x41, 0x49, 0x49, 0x7a,
// H (72)
0x7f, 0x08, 0x08, 0x08, 0x7f,
// I (73)
0x00, 0x41, 0x7f, 0x41, 0x00,
// J (74)
0x20, 0x40, 0x41, 0x3f, 0x01,
// K (75)
0x7f, 0x08, 0x14, 0x22, 0x41,
// L (76)
0x7f, 0x40, 0x40, 0x40, 0x40,
// M (77)
0x7f, 0x02, 0x0c, 0x02, 0x7f,
// N (78)
0x7f, 0x04, 0x08, 0x10, 0x7f,
// O (79)
0x3e, 0x41, 0x41, 0x41, 0x3e,
// P (80)
0x7f, 0x09, 0x09, 0x09, 0x06,
// Q (81)
0x3e, 0x41, 0x51, 0x21, 0x5e,
// R (82)
0x7f, 0x09, 0x19, 0x29, 0x46,
// S (83)
0x46, 0x49, 0x49, 0x49, 0x31,
// T (84)
0x01, 0x01, 0x7f, 0x01, 0x01,
// U (85)
0x3f, 0x40, 0x40, 0x40, 0x3f,
// V (86)
0x1f, 0x20, 0x40, 0x20, 0x1f,
// W (87)
0x3f, 0x40, 0x38, 0x40, 0x3f,
// X (88)
0x63, 0x14, 0x08, 0x14, 0x63,
// Y (89)
0x07, 0x08, 0x70, 0x08, 0x07,
// Z (90)
0x61, 0x51, 0x49, 0x45, 0x43,
// [ (91)
0x00, 0x7f, 0x41, 0x41, 0x00,
// \ (92)
0x02, 0x04, 0x08, 0x10, 0x20,
// ] (93)
0x00, 0x41, 0x41, 0x7f, 0x00,
// ^ (94)
0x04, 0x02, 0x01, 0x02, 0x04,
// _ (95)
0x40, 0x40, 0x40, 0x40, 0x40,
// ` (96)
0x00, 0x01, 0x02, 0x04, 0x00,
// a (97)
0x20, 0x54, 0x54, 0x54, 0x78,
// b (98)
0x7f, 0x48, 0x44, 0x44, 0x38,
// c (99)
0x38, 0x44, 0x44, 0x44, 0x20,
// d (100)
0x38, 0x44, 0x44, 0x48, 0x7f,
// e (101)
0x38, 0x54, 0x54, 0x54, 0x18,
// f (102)
0x08, 0x7e, 0x09, 0x01, 0x02,
// g (103)
0x0c, 0x52, 0x52, 0x52, 0x3e,
// h (104)
0x7f, 0x08, 0x04, 0x04, 0x78,
// i (105)
0x00, 0x44, 0x7d, 0x40, 0x00,
// j (106)
0x20, 0x40, 0x44, 0x3d, 0x00,
// k (107)
0x7f, 0x10, 0x28, 0x44, 0x00,
// l (108)
0x00, 0x41, 0x7f, 0x40, 0x00,
// m (109)
0x7c, 0x04, 0x18, 0x04, 0x78,
// n (110)
0x7c, 0x08, 0x04, 0x04, 0x78,
// o (111)
0x38, 0x44, 0x44, 0x44, 0x38,
// p (112)
0x7c, 0x14, 0x14, 0x14, 0x08,
// q (113)
0x08, 0x14, 0x14, 0x18, 0x7c,
// r (114)
0x7c, 0x08, 0x04, 0x04, 0x08,
// s (115)
0x48, 0x54, 0x54, 0x54, 0x20,
// t (116)
0x04, 0x3f, 0x44, 0x40, 0x20,
// u (117)
0x3c, 0x40, 0x40, 0x20, 0x7c,
// v (118)
0x1c, 0x20, 0x40, 0x20, 0x1c,
// w (119)
0x3c, 0x40, 0x30, 0x40, 0x3c,
// x (120)
0x44, 0x28, 0x10, 0x28, 0x44,
// y (121)
0x0c, 0x50, 0x50, 0x50, 0x3c,
// z (122)
0x44, 0x64, 0x54, 0x4c, 0x44,
// { (123)
0x00, 0x08, 0x36, 0x41, 0x00,
// | (124)
0x00, 0x00, 0x7f, 0x00, 0x00,
// } (125)
0x00, 0x41, 0x36, 0x08, 0x00,
// ~ (126)
0x10, 0x08, 0x08, 0x10, 0x08,
]),
};
// Alias for compatibility
export const SimpleFont = Font_6x8;
-50
View File
@@ -1,50 +0,0 @@
/**
* Meshtastic OLED Emulator
*
* A Vue.js component and TypeScript library for emulating SSD1306/SH1106 OLED displays.
* Provides the same OLEDDisplay API as the Meshtastic firmware, enabling
* screen development and testing in the browser.
*/
// Core display class
export {
OLEDDisplay,
type OLEDDISPLAY_GEOMETRY,
type OLEDDISPLAY_COLOR,
type OLEDDISPLAY_TEXT_ALIGNMENT,
type OLEDFont,
} from "./OLEDDisplay";
export {
WHITE,
BLACK,
INVERSE,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_RIGHT,
} from "./OLEDDisplay";
// Fonts
export {
ArialMT_Plain_10,
ArialMT_Plain_16,
FONT_HEIGHT_SMALL,
FONT_HEIGHT_MEDIUM,
FONT_HEIGHT_LARGE,
createFontFromBytes,
} from "./OLEDFonts";
// Simple working fonts (recommended)
export { Font_6x8, Font_5x7, SimpleFont } from "./SimpleFonts";
// Images and icons
export * as OLEDImages from "./OLEDImages";
// Screen renderers (firmware UI ports)
export * as ScreenRenderers from "./ScreenRenderers";
// Vue component
export { default as OLEDEmulator } from "./OLEDEmulator.vue";
export { DISPLAY_PRESETS, type DisplayPreset } from "./DisplayPresets";
// Demo component
export { default as OLEDEmulatorDemo } from "./Demo.vue";
-4
View File
@@ -1,4 +0,0 @@
import { createApp } from "vue";
import Demo from "./Demo.vue";
createApp(Demo).mount("#app");
-28
View File
@@ -1,28 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Vue */
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}
-35
View File
@@ -1,35 +0,0 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import dts from "vite-plugin-dts";
import { resolve } from "path";
export default defineConfig({
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
],
build: {
lib: {
entry: resolve(__dirname, "src/index.ts"),
name: "MeshtasticOLEDEmulator",
formats: ["es", "cjs"],
fileName: (format) => `index.${format === "es" ? "mjs" : "js"}`,
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
cssCodeSplit: false,
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});
+7 -8
View File
@@ -56,7 +56,6 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-DMESHTASTIC_EXCLUDE_POWERMON=1
-DMESHTASTIC_EXCLUDE_STATUS=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
-DLED_BUILTIN=-1
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
@@ -146,7 +145,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.9
adafruit/Adafruit MPU6050@2.2.8
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
adafruit/Adafruit LIS3DH@1.3.0
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
@@ -212,11 +211,11 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.3
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
sensirion/Sensirion I2C SFA3x@1.0.0
# renovate: datasource=custom.pio depName=Sensirion I2C STC3x packageName=sensirion/library/Sensirion I2C STC3x
sensirion/Sensirion I2C STC3x@1.0.1
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
@@ -242,8 +241,8 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.3
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
sensirion/Sensirion I2C SFA3x@1.0.0
# renovate: datasource=custom.pio depName=Sensirion I2C STC3x packageName=sensirion/library/Sensirion I2C STC3x
sensirion/Sensirion I2C STC3x@1.0.1
+59 -66
View File
@@ -1,21 +1,19 @@
#ifndef AMBIENTLIGHTINGTHREAD_H
#define AMBIENTLIGHTINGTHREAD_H
#include "Observer.h"
#include "configuration.h"
#include "detect/ScanI2C.h"
#include "sleep.h"
#ifdef HAS_NCP5623
#include <NCP5623.h>
#include <graphics/RAKled.h>
NCP5623 rgb;
#endif
#ifdef HAS_LP5562
#include <graphics/NomadStarLED.h>
LP5562 rgbw;
#endif
#ifdef HAS_NEOPIXEL
#include <Adafruit_NeoPixel.h>
#include <graphics/NeoPixel.h>
Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
#endif
#ifdef UNPHONE
@@ -23,24 +21,10 @@
extern unPhone unphone;
#endif
namespace concurrency
{
class AmbientLightingThread : public concurrency::OSThread
{
friend class StatusLEDModule; // Let the LEDStatusModule trigger the ambient lighting for notifications and battery status.
friend class ExternalNotificationModule; // Let the ExternalNotificationModule trigger the ambient lighting for notifications.
private:
#ifdef HAS_NCP5623
NCP5623 rgb;
#endif
#ifdef HAS_LP5562
LP5562 rgbw;
#endif
#ifdef HAS_NEOPIXEL
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
#endif
public:
explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLighting")
{
@@ -52,15 +36,14 @@ class AmbientLightingThread : public concurrency::OSThread
moduleConfig.ambient_lighting.led_state = true;
#endif
#endif
#if AMBIENT_LIGHTING_TEST
// define to enable test
moduleConfig.ambient_lighting.led_state = true;
moduleConfig.ambient_lighting.current = 10;
// Uncomment to test module
// moduleConfig.ambient_lighting.led_state = true;
// moduleConfig.ambient_lighting.current = 10;
// Default to a color based on our node number
moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
#endif
// moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
// moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
// moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
_type = type;
if (_type == ScanI2C::DeviceType::NONE) {
@@ -70,6 +53,11 @@ class AmbientLightingThread : public concurrency::OSThread
}
#endif
#ifdef HAS_RGB_LED
if (!moduleConfig.ambient_lighting.led_state) {
LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF");
disable();
return;
}
LOG_DEBUG("AmbientLighting init");
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623) {
@@ -89,13 +77,7 @@ class AmbientLightingThread : public concurrency::OSThread
pixels.clear(); // Set all pixel colors to 'off'
pixels.setBrightness(moduleConfig.ambient_lighting.current);
#endif
if (!moduleConfig.ambient_lighting.led_state) {
LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF");
disable();
return;
}
setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
setLighting();
#endif
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
}
@@ -109,8 +91,7 @@ class AmbientLightingThread : public concurrency::OSThread
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
#endif
setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
setLighting();
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
}
@@ -167,53 +148,65 @@ class AmbientLightingThread : public concurrency::OSThread
return 0;
}
protected:
void setLighting(float current, uint8_t red, uint8_t green, uint8_t blue)
void setLighting()
{
#ifdef HAS_NCP5623
rgb.setCurrent(current);
rgb.setRed(red);
rgb.setGreen(green);
rgb.setBlue(blue);
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue);
rgb.setCurrent(moduleConfig.ambient_lighting.current);
rgb.setRed(moduleConfig.ambient_lighting.red);
rgb.setGreen(moduleConfig.ambient_lighting.green);
rgb.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef HAS_LP5562
rgbw.setCurrent(current);
rgbw.setRed(red);
rgbw.setGreen(green);
rgbw.setBlue(blue);
LOG_DEBUG("Init LP5562 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue);
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
rgbw.setRed(moduleConfig.ambient_lighting.red);
rgbw.setGreen(moduleConfig.ambient_lighting.green);
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef HAS_NEOPIXEL
pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue),
0, NEOPIXEL_COUNT);
// RadioMaster Bandit has addressable LED at the two buttons
// this allow us to set different lighting for them in variant.h file.
#ifdef RADIOMASTER_900_BANDIT
#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX)
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
#endif
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
#endif
#endif
pixels.show();
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%f, red=%d, green=%d, blue=%d",
// current, red, green, blue);
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
// moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
// moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - red);
analogWrite(RGBLED_GREEN, 255 - green);
analogWrite(RGBLED_BLUE, 255 - blue);
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", red, green, blue);
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, red);
analogWrite(RGBLED_GREEN, green);
analogWrite(RGBLED_BLUE, blue);
LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", red, green, blue);
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef UNPHONE
unphone.rgb(red, green, blue);
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", red, green, blue);
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
}
};
#endif // AMBIENTLIGHTINGTHREAD_H
} // namespace concurrency
+1 -1
View File
@@ -217,7 +217,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
#define SFA30_ADDR 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA003I_ADDR 0x12
@@ -235,6 +234,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MAX30102_ADDR 0x57
#define SCD4X_ADDR 0x62
#define CW2015_ADDR 0x62
#define STC31_ADDR 0x2c // RAK12008 uses 0x2C address
#define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
+2 -2
View File
@@ -43,8 +43,8 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30};
return firstOfOrNONE(4, types);
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X};
return firstOfOrNONE(2, types);
}
ScanI2C::FoundDevice ScanI2C::firstRGBLED() const
+2 -2
View File
@@ -89,9 +89,9 @@ class ScanI2C
DA217,
CHSC6X,
CST226SE,
CW2015,
SEN5X,
SFA30,
CW2015
STC31
} DeviceType;
// typedef uint8_t DeviceAddress;
+64 -15
View File
@@ -1,6 +1,4 @@
#include "ScanI2CTwoWire.h"
#include "configuration.h"
#include "detect/ScanI2C.h"
#if !MESHTASTIC_EXCLUDE_I2C
@@ -156,6 +154,42 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
return String(productName);
}
/// for STC31 detection - probe by reading product identifier
/// Uses two-step sequence: prepareProductIdentifier (0x367c) then readProductIdentifier (0xe102)
bool probeSTC31(TwoWire *i2cBus, uint8_t address)
{
// Step 1: Send prepare product identifier command (0x367c)
uint8_t prepCmd[] = {0x36, 0x7c};
i2cBus->beginTransmission(address);
i2cBus->write(prepCmd, 2);
if (i2cBus->endTransmission() != 0)
return false;
delay(1);
// Step 2: Send read product identifier command (0xe102)
uint8_t readCmd[] = {0xe1, 0x02};
i2cBus->beginTransmission(address);
i2cBus->write(readCmd, 2);
if (i2cBus->endTransmission() != 0)
return false;
delay(10);
// Read 6 bytes: product number (4 bytes with CRC) + more data
// Product number is 32-bit: STC31 = 0x08010301
if (i2cBus->requestFrom(address, (uint8_t)6) != 6)
return false;
uint8_t response[6] = {0};
for (int i = 0; i < 6 && i2cBus->available(); ++i) {
response[i] = i2cBus->read();
}
uint32_t productId =
((uint32_t)response[0] << 24) | ((uint32_t)response[1] << 16) | ((uint32_t)response[3] << 8) | response[4];
return productId == 0x08010301;
}
#define SCAN_SIMPLE_CASE(ADDR, T, ...) \
case ADDR: \
logFoundDevice(__VA_ARGS__); \
@@ -458,13 +492,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
case LPS22HB_ADDR_ALT:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD060), 48); // get device marking
if (registerValue != 0) {
type = SFA30;
logFoundDevice("SFA30", (uint8_t)addr.address);
break;
}
// TODO - What happens with these two?
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
@@ -541,13 +568,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);
// TSL25911_ADDR (0x29) is shared by TSL2591, TSL2561, and STC31
// Probe for STC31 first (Sensirion CO2 sensor)
LOG_DEBUG("Probing address 0x29 for STC31...");
if (probeSTC31(i2cBus, addr.address)) {
type = STC31;
LOG_DEBUG("STC31 probe successful!");
logFoundDevice("STC31", (uint8_t)addr.address);
} else {
type = TSL2561;
logFoundDevice("TSL2561", (uint8_t)addr.address);
LOG_DEBUG("STC31 probe failed, checking for TSL sensors...");
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);
} else {
type = TSL2561;
logFoundDevice("TSL2561", (uint8_t)addr.address);
}
}
break;
@@ -557,6 +594,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
case STC31_ADDR:
// RAK12008 uses address 0x2C for STC31
LOG_DEBUG("Probing address 0x2c for STC31...");
if (probeSTC31(i2cBus, addr.address)) {
type = STC31;
LOG_DEBUG("STC31 probe successful at 0x2c!");
logFoundDevice("STC31", (uint8_t)addr.address);
} else {
LOG_DEBUG("STC31 probe failed at 0x2c");
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
}
break;
case CST328_ADDR:
// Do we have the CST328 or the CST226SE
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
+4
View File
@@ -0,0 +1,4 @@
#ifdef HAS_NEOPIXEL
#include <Adafruit_NeoPixel.h>
extern Adafruit_NeoPixel pixels;
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifdef HAS_NCP5623
#include <NCP5623.h>
extern NCP5623 rgb;
#endif
+28 -22
View File
@@ -427,33 +427,39 @@ const int *getTextPositions(OLEDDisplay *display)
// *************************
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
{
if (!isAPIConnected(service->api_state))
return;
bool drawConnectionState = false;
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
drawConnectionState = true;
}
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
display->setColor(BLACK);
display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
(connection_icon_height * scale) + (2 * scale));
display->setColor(WHITE);
if (currentResolution == ScreenResolution::High) {
const int bytesPerRow = (connection_icon_width + 7) / 8;
int iconX = 0;
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
if (drawConnectionState) {
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
display->setColor(BLACK);
display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
(connection_icon_height * scale) + (2 * scale));
display->setColor(WHITE);
if (currentResolution == ScreenResolution::High) {
const int bytesPerRow = (connection_icon_width + 7) / 8;
int iconX = 0;
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
for (int yy = 0; yy < connection_icon_height; ++yy) {
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
for (int xx = 0; xx < connection_icon_width; ++xx) {
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
if (byteVal & bitMask) {
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
for (int yy = 0; yy < connection_icon_height; ++yy) {
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
for (int xx = 0; xx < connection_icon_width; ++xx) {
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
if (byteVal & bitMask) {
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
}
}
}
}
} else {
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
connection_icon);
} else {
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
connection_icon);
}
}
}
-14
View File
@@ -63,18 +63,4 @@ bool isAllowedPunctuation(char c);
std::string sanitizeString(const std::string &input);
static inline bool isAPIConnected(uint8_t state)
{
static constexpr bool connectedStates[] = {
/* STATE_NONE */ false,
/* STATE_BLE */ true,
/* STATE_WIFI */ true,
/* STATE_SERIAL */ true,
/* STATE_PACKET */ true,
/* STATE_HTTP */ true,
/* STATE_ETH */ true,
};
return state < sizeof(connectedStates) ? connectedStates[state] : false;
}
} // namespace graphics
+6 -6
View File
@@ -110,14 +110,14 @@ void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, ui
uint32_t secs = (uptimeMillis % 60000) / 1000;
if (days) {
snprintf(uptimeStr, maxLength, "%s%ud %uh", prefix, days, hours);
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
} else if (hours) {
snprintf(uptimeStr, maxLength, "%s%uh %um", prefix, hours, mins);
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
} else if (!includeSecs) {
snprintf(uptimeStr, maxLength, "%s%um", prefix, mins);
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
} else if (mins) {
snprintf(uptimeStr, maxLength, "%s%um %us", prefix, mins, secs);
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
} else {
snprintf(uptimeStr, maxLength, "%s%us", prefix, secs);
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
}
}
}
+1 -1
View File
@@ -663,7 +663,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
char uptimeStr[32] = "";
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
+33 -211
View File
@@ -2,7 +2,6 @@
#if HAS_SCREEN
#include "CompassRenderer.h"
#include "GPSStatus.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#include "UIRenderer.h"
@@ -314,7 +313,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// === Create the shortName and title string ===
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
char titlestr[32] = {0};
snprintf(titlestr, sizeof(titlestr), "*%s*", shortName);
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
// === Draw battery/time/mail header (common across screens) ===
graphics::drawCommonHeader(display, x, y, titlestr);
@@ -343,162 +342,34 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
}
// === 2. Signal and Hops (combined on one line, if available) ===
// If both are present: "Sig: 97% [2hops]"
// If only one: show only that one
char signalHopsStr[32] = "";
bool haveSignal = false;
int bars = 0;
int percentSignal = clamp((int)((node->snr + 10) * 5), 0, 100);
// Helper to get SNR limit based on modem preset
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
return -6.0f;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
return -5.5f;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
return -4.5f;
default:
return -6.0f;
}
};
// Calculate signal grade using modem preset and SNR only
float snrLimit = getSnrLimit(config.lora.modem_preset);
float snr = node->snr;
// Determine signal quality label and bars using SNR-only grading
const char *qualityLabel = nullptr;
if (snr > snrLimit + 10) {
qualityLabel = "Good";
bars = 4;
} else if (snr > snrLimit + 6) {
qualityLabel = "Good";
bars = 3;
} else if (snr > snrLimit + 2) {
qualityLabel = "Good";
bars = 2;
} else if (snr > snrLimit - 4) {
qualityLabel = "Fair";
bars = 1;
} else {
qualityLabel = "Bad";
bars = 1;
}
// Add extra spacing on the left if we have an API connection to account for the common footer icons
const char *leftSideSpacing =
graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " ";
// Always use "Sig" for the label
const char *signalLabel = " Sig";
// --- Build the Signal/Hops line ---
// Only show signal if we have valid SNR
if (snr > -100 && snr != 0) {
snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel);
// If SNR looks reasonable, show signal
if ((int)((node->snr + 10) * 5) >= 0 && node->snr > -100) {
snprintf(signalHopsStr, sizeof(signalHopsStr), "%s: %d%%", signalLabel, percentSignal);
haveSignal = true;
}
// If hops is valid (>0), show right after signal
if (node->hops_away > 0) {
size_t len = strlen(signalHopsStr);
// Decide between "1 Hop" and "N Hops"
if (haveSignal) {
snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]");
snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away,
(node->hops_away == 1 ? "Hop" : "Hops"));
} else {
snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]");
snprintf(signalHopsStr, sizeof(signalHopsStr), "[%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops"));
}
}
if (signalHopsStr[0]) {
int yPos = getTextPositions(display)[line++];
int curX = x;
// Split combined string into signal text and hop suffix
char sigPart[20] = "";
const char *hopPart = nullptr;
char *bracket = strchr(signalHopsStr, '[');
if (bracket) {
size_t n = (size_t)(bracket - signalHopsStr);
if (n >= sizeof(sigPart))
n = sizeof(sigPart) - 1;
memcpy(sigPart, signalHopsStr, n);
sigPart[n] = '\0';
// Trim trailing spaces
while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') {
sigPart[strlen(sigPart) - 1] = '\0';
}
hopPart = bracket; // "[n Hop(s)]"
} else {
strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1);
sigPart[sizeof(sigPart) - 1] = '\0';
}
// Draw signal quality text
display->drawString(curX, yPos, sigPart);
curX += display->getStringWidth(sigPart) + 4;
// Draw signal bars (skip on UltraLow, text only)
if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) {
const int kMaxBars = 4;
if (bars < 1)
bars = 1;
if (bars > kMaxBars)
bars = kMaxBars;
int barX = curX;
const bool hi = (currentResolution == ScreenResolution::High);
int barWidth = hi ? 2 : 1;
int barGap = hi ? 2 : 1;
int maxBarHeight = FONT_HEIGHT_SMALL - 7;
if (!hi)
maxBarHeight -= 1;
int barY = yPos + (FONT_HEIGHT_SMALL - maxBarHeight) / 2;
for (int bi = 0; bi < kMaxBars; bi++) {
int barHeight = maxBarHeight * (bi + 1) / kMaxBars;
if (barHeight < 2)
barHeight = 2;
int bx = barX + bi * (barWidth + barGap);
int by = barY + maxBarHeight - barHeight;
if (bi < bars) {
display->fillRect(bx, by, barWidth, barHeight);
} else {
int baseY = barY + maxBarHeight - 1;
display->drawHorizontalLine(bx, baseY, barWidth);
}
}
curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2;
}
// Draw hops AFTER the bars as: [ number + hop icon ]
if (hopPart && node->hops_away > 0) {
// open bracket
display->drawString(curX, yPos, "[");
curX += display->getStringWidth("[") + 1;
// hop count
char hopCount[6];
snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away);
display->drawString(curX, yPos, hopCount);
curX += display->getStringWidth(hopCount) + 2;
// hop icon
const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2;
display->drawXbm(curX, iconY, hop_width, hop_height, hop);
curX += hop_width + 1;
// closing bracket
display->drawString(curX, yPos, "]");
}
if (signalHopsStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], signalHopsStr);
}
// === 3. Heard (last seen, skip if node never seen) ===
@@ -506,8 +377,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
uint32_t seconds = sinceLastSeen(node);
if (seconds != 0 && seconds != UINT32_MAX) {
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
// Format as "Heard:Xm ago", "Heard:Xh ago", or "Heard:Xd ago"
snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard:?" : "%sHeard:%d%c ago"), leftSideSpacing,
// Format as "Heard: Xm ago", "Heard: Xh ago", or "Heard: Xd ago"
snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard: ?" : " Heard: %d%c ago"),
(days ? days
: hours ? hours
: minutes),
@@ -515,18 +386,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
: hours ? 'h'
: 'm'));
}
if (seenStr[0]) {
if (seenStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
char upPrefix[12]; // enough for leftSideSpacing + "Up:"
snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing);
getUptimeStr(node->device_metrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr));
getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
}
if (uptimeStr[0]) {
if (uptimeStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
}
@@ -553,16 +422,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
if (miles < 0.1) {
int feet = (int)(miles * 5280);
if (feet > 0 && feet < 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet);
snprintf(distStr, sizeof(distStr), " Distance: %dft", feet);
haveDistance = true;
} else if (feet >= 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:¼mi", leftSideSpacing);
snprintf(distStr, sizeof(distStr), " Distance: ¼mi");
haveDistance = true;
}
} else {
int roundedMiles = (int)(miles + 0.5);
if (roundedMiles > 0 && roundedMiles < 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, roundedMiles);
snprintf(distStr, sizeof(distStr), " Distance: %dmi", roundedMiles);
haveDistance = true;
}
}
@@ -570,74 +439,26 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
if (distanceKm < 1.0) {
int meters = (int)(distanceKm * 1000);
if (meters > 0 && meters < 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters);
snprintf(distStr, sizeof(distStr), " Distance: %dm", meters);
haveDistance = true;
} else if (meters >= 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:1km", leftSideSpacing);
snprintf(distStr, sizeof(distStr), " Distance: 1km");
haveDistance = true;
}
} else {
int km = (int)(distanceKm + 0.5);
if (km > 0 && km < 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:%dkm", leftSideSpacing, km);
snprintf(distStr, sizeof(distStr), " Distance: %dkm", km);
haveDistance = true;
}
}
}
}
if (haveDistance && distStr[0]) {
// Only display if we actually have a value!
if (haveDistance && distStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], distStr);
}
// === 6. Battery after Distance line, otherwise next available line ===
char batLine[32] = "";
bool haveBatLine = false;
if (node->has_device_metrics) {
bool hasPct = node->device_metrics.has_battery_level;
bool hasVolt = node->device_metrics.has_voltage && node->device_metrics.voltage > 0.001f;
int pct = 0;
float volt = 0.0f;
if (hasPct) {
pct = (int)node->device_metrics.battery_level;
}
if (hasVolt) {
volt = node->device_metrics.voltage;
}
if (hasPct && pct > 0 && pct <= 100) {
// Normal battery percentage
if (hasVolt) {
snprintf(batLine, sizeof(batLine), "%sBat:%d%% (%.2fV)", leftSideSpacing, pct, volt);
} else {
snprintf(batLine, sizeof(batLine), "%sBat:%d%%", leftSideSpacing, pct);
}
haveBatLine = true;
} else if (hasPct && pct > 100) {
// Plugged in
if (hasVolt) {
snprintf(batLine, sizeof(batLine), "%sPlugged In (%.2fV)", leftSideSpacing, volt);
} else {
snprintf(batLine, sizeof(batLine), "%sPlugged In", leftSideSpacing);
}
haveBatLine = true;
} else if (!hasPct && hasVolt) {
// Voltage only
snprintf(batLine, sizeof(batLine), "%sBat:%.2fV", leftSideSpacing, volt);
haveBatLine = true;
}
}
const int maxTextLines = (currentResolution == ScreenResolution::High) ? 6 : 5;
// Only draw battery if it fits within the allowed lines
if (haveBatLine && line <= maxTextLines) {
display->drawString(x, getTextPositions(display)[line++], batLine);
}
// --- Compass Rendering: landscape (wide) screens use the original side-aligned logic ---
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
bool showCompass = false;
@@ -772,7 +593,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
char uptimeStr[32] = "";
if (currentResolution != ScreenResolution::UltraLow) {
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
}
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
@@ -1163,6 +984,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
config.display.heading_bold = false;
const char *displayLine = ""; // Initialize to empty string by default
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
if (config.position.fixed_position) {
@@ -1207,10 +1029,10 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
char uptimeStr[32];
#if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), false);
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
#else
// Non E-Ink: include seconds where useful
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true);
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
#endif
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
-6
View File
@@ -83,12 +83,6 @@ static const unsigned char mail[] PROGMEM = {
0b11111111, 0b00 // Bottom line
};
// Hop icon (9x10)
#define hop_width 9
#define hop_height 10
const uint8_t hop[] PROGMEM = {0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x38, 0x00, 0x28, 0x00,
0x38, 0x00, 0xC0, 0x01, 0x40, 0x01, 0xC0, 0x01, 0x40, 0x00};
// 📬 Mail / Message
const uint8_t icon_mail[] PROGMEM = {
0b11111111, // ████████ top border
+5 -7
View File
@@ -29,6 +29,7 @@
#include <Wire.h>
#endif
#include "detect/einkScan.h"
#include "graphics/RAKled.h"
#include "graphics/Screen.h"
#include "main.h"
#include "mesh/generated/meshtastic/config.pb.h"
@@ -246,7 +247,7 @@ const char *getDeviceName()
uint32_t timeLastPowered = 0;
static OSThread *powerFSMthread;
OSThread *ambientLightingThread;
static OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
#ifdef ARCH_PORTDUINO
@@ -348,10 +349,9 @@ void setup()
#endif
concurrency::hasBeenSetup = true;
#if HAS_SCREEN
meshtastic_Config_DisplayConfig_OledType screen_model =
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
#endif
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
#ifdef USE_SEGGER
@@ -568,7 +568,6 @@ void setup()
}
#endif
#if HAS_SCREEN
auto screenInfo = i2cScanner->firstScreen();
screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE;
@@ -586,7 +585,6 @@ void setup()
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
}
}
#endif
#define UPDATE_FROM_SCANNER(FIND_FN)
#if defined(USE_VIRTUAL_KEYBOARD)
@@ -681,6 +679,8 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::STC31, meshtastic_TelemetrySensorType_STC31);
#endif
#ifdef HAS_SDCARD
@@ -724,7 +724,6 @@ void setup()
else
playStartMelody();
#if HAS_SCREEN
// fixed screen override?
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
screen_model = config.display.oled;
@@ -737,7 +736,6 @@ void setup()
#if defined(USE_SH1107_128_64)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
#endif
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL)
+2 -2
View File
@@ -1415,7 +1415,7 @@ void NodeDB::loadFromDisk()
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
}
if (portduino_config.enable_UDP) {
config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST;
config.network.enabled_protocols = true;
}
#endif
@@ -1772,7 +1772,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
info->has_device_metrics = false;
info->has_position = false;
info->user.public_key.size = 0;
memset(info->user.public_key.bytes, 0, sizeof(info->user.public_key.bytes));
info->user.public_key.bytes[0] = 0;
} else {
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
+2 -2
View File
@@ -943,9 +943,9 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
}
} else if (!devicestate.owner.is_licensed) {
// we have an array of PA gain values. Find the highest power setting that works.
for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) {
for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
power -= tx_gain[radio_dbm];
+2 -5
View File
@@ -453,11 +453,8 @@ void RadioLibInterface::handleReceiveInterrupt()
}
#endif
if (state != RADIOLIB_ERR_NONE) {
// Log PacketHeader similar to RadioInterface::printPacket so we can try to match RX errors to other packets in the logs.
LOG_ERROR("Ignore received packet due to error=%d (maybe id=0x%08x fr=0x%08x to=0x%08x flags=0x%02x rxSNR=%g rxRSSI=%i "
"nextHop=0x%x relay=0x%x)",
state, radioBuffer.header.id, radioBuffer.header.from, radioBuffer.header.to, radioBuffer.header.flags,
iface->getSNR(), lround(iface->getRSSI()), radioBuffer.header.next_hop, radioBuffer.header.relay_node);
LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state,
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++;
airTime->logAirtime(RX_ALL_LOG, rxMsec);
@@ -36,9 +36,6 @@ PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO)
PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO)
PB_BIND(meshtastic_SCD30_config, meshtastic_SCD30_config, AUTO)
+7 -58
View File
@@ -79,9 +79,7 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
/* TODO: REPLACE */
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12,
/* TODO: REPLACE */
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13,
/* Traffic management module config */
meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13
} meshtastic_AdminMessage_ModuleConfigType;
typedef enum _meshtastic_AdminMessage_BackupLocation {
@@ -206,27 +204,6 @@ typedef struct _meshtastic_SEN5X_config {
bool set_one_shot_mode;
} meshtastic_SEN5X_config;
typedef struct _meshtastic_SCD30_config {
/* Set Automatic self-calibration enabled */
bool has_set_asc;
bool set_asc;
/* Recalibration target CO2 concentration in ppm (FRC or ASC) */
bool has_set_target_co2_conc;
uint32_t set_target_co2_conc;
/* Reference temperature in degC */
bool has_set_temperature;
float set_temperature;
/* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */
bool has_set_altitude;
uint32_t set_altitude;
/* Power mode for sensor (true for low power, false for normal) */
bool has_set_measurement_interval;
uint32_t set_measurement_interval;
/* Perform a factory reset of the sensor */
bool has_soft_reset;
bool soft_reset;
} meshtastic_SCD30_config;
typedef struct _meshtastic_SensorConfig {
/* SCD4X CO2 Sensor configuration */
bool has_scd4x_config;
@@ -234,9 +211,6 @@ typedef struct _meshtastic_SensorConfig {
/* SEN5X PM Sensor configuration */
bool has_sen5x_config;
meshtastic_SEN5X_config sen5x_config;
/* SCD30 CO2 Sensor configuration */
bool has_scd30_config;
meshtastic_SCD30_config scd30_config;
} meshtastic_SensorConfig;
typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t;
@@ -395,8 +369,8 @@ extern "C" {
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
#define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1))
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1))
#define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH
#define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD
@@ -424,7 +398,6 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
@@ -433,10 +406,9 @@ extern "C" {
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default, false, meshtastic_SCD30_config_init_default}
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default}
#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_SEN5X_config_init_default {false, 0, false, 0}
#define meshtastic_SCD30_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
@@ -444,10 +416,9 @@ extern "C" {
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero, false, meshtastic_SCD30_config_init_zero}
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero}
#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0}
#define meshtastic_SCD30_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_AdminMessage_InputEvent_event_code_tag 1
@@ -478,15 +449,8 @@ extern "C" {
#define meshtastic_SCD4X_config_set_power_mode_tag 7
#define meshtastic_SEN5X_config_set_temperature_tag 1
#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2
#define meshtastic_SCD30_config_set_asc_tag 1
#define meshtastic_SCD30_config_set_target_co2_conc_tag 2
#define meshtastic_SCD30_config_set_temperature_tag 3
#define meshtastic_SCD30_config_set_altitude_tag 4
#define meshtastic_SCD30_config_set_measurement_interval_tag 5
#define meshtastic_SCD30_config_soft_reset_tag 6
#define meshtastic_SensorConfig_scd4x_config_tag 1
#define meshtastic_SensorConfig_sen5x_config_tag 2
#define meshtastic_SensorConfig_scd30_config_tag 3
#define meshtastic_AdminMessage_get_channel_request_tag 1
#define meshtastic_AdminMessage_get_channel_response_tag 2
#define meshtastic_AdminMessage_get_owner_request_tag 3
@@ -676,13 +640,11 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
#define meshtastic_SensorConfig_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, scd30_config, 3)
X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2)
#define meshtastic_SensorConfig_CALLBACK NULL
#define meshtastic_SensorConfig_DEFAULT NULL
#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config
#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config
#define meshtastic_SensorConfig_scd30_config_MSGTYPE meshtastic_SCD30_config
#define meshtastic_SCD4X_config_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \
@@ -701,16 +663,6 @@ X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2)
#define meshtastic_SEN5X_config_CALLBACK NULL
#define meshtastic_SEN5X_config_DEFAULT NULL
#define meshtastic_SCD30_config_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \
X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \
X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \
X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \
X(a, STATIC, OPTIONAL, UINT32, set_measurement_interval, 5) \
X(a, STATIC, OPTIONAL, BOOL, soft_reset, 6)
#define meshtastic_SCD30_config_CALLBACK NULL
#define meshtastic_SCD30_config_DEFAULT NULL
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
@@ -721,7 +673,6 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
extern const pb_msgdesc_t meshtastic_SensorConfig_msg;
extern const pb_msgdesc_t meshtastic_SCD4X_config_msg;
extern const pb_msgdesc_t meshtastic_SEN5X_config_msg;
extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
@@ -734,7 +685,6 @@ extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg
#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg
#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg
#define meshtastic_SCD30_config_fields &meshtastic_SCD30_config_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
@@ -744,10 +694,9 @@ extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SCD30_config_size 27
#define meshtastic_SCD4X_config_size 29
#define meshtastic_SEN5X_config_size 7
#define meshtastic_SensorConfig_size 69
#define meshtastic_SensorConfig_size 40
#define meshtastic_SharedContact_size 127
#ifdef __cplusplus
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2419
#define meshtastic_BackupPreferences_size 2364
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196
+4 -10
View File
@@ -90,9 +90,6 @@ typedef struct _meshtastic_LocalModuleConfig {
/* StatusMessage Config */
bool has_statusmessage;
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
/* The part of the config that is specific to the Traffic Management module */
bool has_traffic_management;
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
} meshtastic_LocalModuleConfig;
@@ -102,9 +99,9 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default}
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default}
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default}
#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero}
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero}
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_LocalConfig_device_tag 1
@@ -131,7 +128,6 @@ extern "C" {
#define meshtastic_LocalModuleConfig_detection_sensor_tag 13
#define meshtastic_LocalModuleConfig_paxcounter_tag 14
#define meshtastic_LocalModuleConfig_statusmessage_tag 15
#define meshtastic_LocalModuleConfig_traffic_management_tag 16
/* Struct field encoding specification for nanopb */
#define meshtastic_LocalConfig_FIELDLIST(X, a) \
@@ -170,8 +166,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \
X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \
X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \
X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16)
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15)
#define meshtastic_LocalModuleConfig_CALLBACK NULL
#define meshtastic_LocalModuleConfig_DEFAULT NULL
#define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
@@ -188,7 +183,6 @@ X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16)
#define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
#define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
extern const pb_msgdesc_t meshtastic_LocalConfig_msg;
extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
@@ -200,7 +194,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
#define meshtastic_LocalConfig_size 751
#define meshtastic_LocalModuleConfig_size 813
#define meshtastic_LocalModuleConfig_size 758
#ifdef __cplusplus
} /* extern "C" */
@@ -30,9 +30,6 @@ PB_BIND(meshtastic_ModuleConfig_AudioConfig, meshtastic_ModuleConfig_AudioConfig
PB_BIND(meshtastic_ModuleConfig_PaxcounterConfig, meshtastic_ModuleConfig_PaxcounterConfig, AUTO)
PB_BIND(meshtastic_ModuleConfig_TrafficManagementConfig, meshtastic_ModuleConfig_TrafficManagementConfig, AUTO)
PB_BIND(meshtastic_ModuleConfig_SerialConfig, meshtastic_ModuleConfig_SerialConfig, AUTO)
@@ -228,39 +228,6 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig {
int32_t ble_threshold;
} meshtastic_ModuleConfig_PaxcounterConfig;
/* Config for the Traffic Management module.
Provides packet inspection and traffic shaping to help reduce channel utilization */
typedef struct _meshtastic_ModuleConfig_TrafficManagementConfig {
/* Master enable for traffic management module */
bool enabled;
/* Enable position deduplication to drop redundant position broadcasts */
bool position_dedup_enabled;
/* Number of bits of precision for position deduplication (0-32) */
uint32_t position_precision_bits;
/* Minimum interval in seconds between position updates from the same node */
uint32_t position_min_interval_secs;
/* Enable direct response to NodeInfo requests from local cache */
bool nodeinfo_direct_response;
/* Minimum hop distance from requestor before responding to NodeInfo requests */
uint32_t nodeinfo_direct_response_max_hops;
/* Enable per-node rate limiting to throttle chatty nodes */
bool rate_limit_enabled;
/* Time window in seconds for rate limiting calculations */
uint32_t rate_limit_window_secs;
/* Maximum packets allowed per node within the rate limit window */
uint32_t rate_limit_max_packets;
/* Enable dropping of unknown/undecryptable packets per rate_limit_window_secs */
bool drop_unknown_enabled;
/* Number of unknown packets before dropping from a node */
uint32_t unknown_packet_threshold;
/* Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected) */
bool exhaust_hop_telemetry;
/* Set hop_limit to 0 for relayed position broadcasts (own packets unaffected) */
bool exhaust_hop_position;
/* Preserve hop_limit for router-to-router traffic */
bool router_preserve_hops;
} meshtastic_ModuleConfig_TrafficManagementConfig;
/* Serial Config */
typedef struct _meshtastic_ModuleConfig_SerialConfig {
/* Preferences for the SerialModule */
@@ -501,8 +468,6 @@ typedef struct _meshtastic_ModuleConfig {
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
/* TODO: REPLACE */
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
/* Traffic management module config for mesh network optimization */
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
} payload_variant;
} meshtastic_ModuleConfig;
@@ -546,7 +511,6 @@ extern "C" {
#define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud
#define meshtastic_ModuleConfig_SerialConfig_baud_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Baud
#define meshtastic_ModuleConfig_SerialConfig_mode_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Mode
@@ -572,7 +536,6 @@ extern "C" {
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
@@ -590,7 +553,6 @@ extern "C" {
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
@@ -638,20 +600,6 @@ extern "C" {
#define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2
#define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3
#define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4
#define meshtastic_ModuleConfig_TrafficManagementConfig_enabled_tag 1
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_dedup_enabled_tag 2
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_precision_bits_tag 3
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_min_interval_secs_tag 4
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_tag 5
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_max_hops_tag 6
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_enabled_tag 7
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_window_secs_tag 8
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_max_packets_tag 9
#define meshtastic_ModuleConfig_TrafficManagementConfig_drop_unknown_enabled_tag 10
#define meshtastic_ModuleConfig_TrafficManagementConfig_unknown_packet_threshold_tag 11
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_telemetry_tag 12
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_position_tag 13
#define meshtastic_ModuleConfig_TrafficManagementConfig_router_preserve_hops_tag 14
#define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1
#define meshtastic_ModuleConfig_SerialConfig_echo_tag 2
#define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3
@@ -737,7 +685,6 @@ extern "C" {
#define meshtastic_ModuleConfig_detection_sensor_tag 12
#define meshtastic_ModuleConfig_paxcounter_tag 13
#define meshtastic_ModuleConfig_statusmessage_tag 14
#define meshtastic_ModuleConfig_traffic_management_tag 15
/* Struct field encoding specification for nanopb */
#define meshtastic_ModuleConfig_FIELDLIST(X, a) \
@@ -754,8 +701,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_varian
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14)
#define meshtastic_ModuleConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
@@ -772,7 +718,6 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_v
#define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
#define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
#define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
@@ -843,24 +788,6 @@ X(a, STATIC, SINGULAR, INT32, ble_threshold, 4)
#define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_TrafficManagementConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, BOOL, position_dedup_enabled, 2) \
X(a, STATIC, SINGULAR, UINT32, position_precision_bits, 3) \
X(a, STATIC, SINGULAR, UINT32, position_min_interval_secs, 4) \
X(a, STATIC, SINGULAR, BOOL, nodeinfo_direct_response, 5) \
X(a, STATIC, SINGULAR, UINT32, nodeinfo_direct_response_max_hops, 6) \
X(a, STATIC, SINGULAR, BOOL, rate_limit_enabled, 7) \
X(a, STATIC, SINGULAR, UINT32, rate_limit_window_secs, 8) \
X(a, STATIC, SINGULAR, UINT32, rate_limit_max_packets, 9) \
X(a, STATIC, SINGULAR, BOOL, drop_unknown_enabled, 10) \
X(a, STATIC, SINGULAR, UINT32, unknown_packet_threshold, 11) \
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_telemetry, 12) \
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_position, 13) \
X(a, STATIC, SINGULAR, BOOL, router_preserve_hops, 14)
#define meshtastic_ModuleConfig_TrafficManagementConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_TrafficManagementConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_SerialConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, BOOL, echo, 2) \
@@ -973,7 +900,6 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_AudioConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_PaxcounterConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_TrafficManagementConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_SerialConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_ExternalNotificationConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_StoreForwardConfig_msg;
@@ -993,7 +919,6 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg
#define meshtastic_ModuleConfig_AudioConfig_fields &meshtastic_ModuleConfig_AudioConfig_msg
#define meshtastic_ModuleConfig_PaxcounterConfig_fields &meshtastic_ModuleConfig_PaxcounterConfig_msg
#define meshtastic_ModuleConfig_TrafficManagementConfig_fields &meshtastic_ModuleConfig_TrafficManagementConfig_msg
#define meshtastic_ModuleConfig_SerialConfig_fields &meshtastic_ModuleConfig_SerialConfig_msg
#define meshtastic_ModuleConfig_ExternalNotificationConfig_fields &meshtastic_ModuleConfig_ExternalNotificationConfig_msg
#define meshtastic_ModuleConfig_StoreForwardConfig_fields &meshtastic_ModuleConfig_StoreForwardConfig_msg
@@ -1021,7 +946,6 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52
#define meshtastic_ModuleConfig_size 227
#define meshtastic_RemoteHardwarePin_size 21
@@ -21,9 +21,6 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO)
PB_BIND(meshtastic_LocalStats, meshtastic_LocalStats, AUTO)
PB_BIND(meshtastic_TrafficManagementStats, meshtastic_TrafficManagementStats, AUTO)
PB_BIND(meshtastic_HealthMetrics, meshtastic_HealthMetrics, AUTO)
+4 -53
View File
@@ -109,9 +109,7 @@ typedef enum _meshtastic_TelemetrySensorType {
/* STH21 Temperature and R. Humidity sensor */
meshtastic_TelemetrySensorType_SHT21 = 47,
/* Sensirion STC31 CO2 sensor */
meshtastic_TelemetrySensorType_STC31 = 48,
/* SCD30 CO2, humidity, temperature sensor */
meshtastic_TelemetrySensorType_SCD30 = 49
meshtastic_TelemetrySensorType_STC31 = 48
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -373,24 +371,6 @@ typedef struct _meshtastic_LocalStats {
int32_t noise_floor;
} meshtastic_LocalStats;
/* Traffic management statistics for mesh network optimization */
typedef struct _meshtastic_TrafficManagementStats {
/* Total number of packets inspected by traffic management */
uint32_t packets_inspected;
/* Number of position packets dropped due to deduplication */
uint32_t position_dedup_drops;
/* Number of NodeInfo requests answered from cache */
uint32_t nodeinfo_cache_hits;
/* Number of packets dropped due to rate limiting */
uint32_t rate_limit_drops;
/* Number of unknown/undecryptable packets dropped */
uint32_t unknown_packet_drops;
/* Number of packets with hop_limit exhausted for local-only broadcast */
uint32_t hop_exhausted_packets;
/* Number of times router hop preservation was applied */
uint32_t router_hops_preserved;
} meshtastic_TrafficManagementStats;
/* Health telemetry metrics */
typedef struct _meshtastic_HealthMetrics {
/* Heart rate (beats per minute) */
@@ -450,8 +430,6 @@ typedef struct _meshtastic_Telemetry {
meshtastic_HealthMetrics health_metrics;
/* Linux host metrics */
meshtastic_HostMetrics host_metrics;
/* Traffic management statistics */
meshtastic_TrafficManagementStats traffic_management_stats;
} variant;
} meshtastic_Telemetry;
@@ -489,9 +467,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SCD30
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SCD30+1))
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_STC31
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_STC31+1))
@@ -510,7 +487,6 @@ extern "C" {
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_TrafficManagementStats_init_default {0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -521,7 +497,6 @@ extern "C" {
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_TrafficManagementStats_init_zero {0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -612,13 +587,6 @@ extern "C" {
#define meshtastic_LocalStats_heap_free_bytes_tag 13
#define meshtastic_LocalStats_num_tx_dropped_tag 14
#define meshtastic_LocalStats_noise_floor_tag 15
#define meshtastic_TrafficManagementStats_packets_inspected_tag 1
#define meshtastic_TrafficManagementStats_position_dedup_drops_tag 2
#define meshtastic_TrafficManagementStats_nodeinfo_cache_hits_tag 3
#define meshtastic_TrafficManagementStats_rate_limit_drops_tag 4
#define meshtastic_TrafficManagementStats_unknown_packet_drops_tag 5
#define meshtastic_TrafficManagementStats_hop_exhausted_packets_tag 6
#define meshtastic_TrafficManagementStats_router_hops_preserved_tag 7
#define meshtastic_HealthMetrics_heart_bpm_tag 1
#define meshtastic_HealthMetrics_spO2_tag 2
#define meshtastic_HealthMetrics_temperature_tag 3
@@ -639,7 +607,6 @@ extern "C" {
#define meshtastic_Telemetry_local_stats_tag 6
#define meshtastic_Telemetry_health_metrics_tag 7
#define meshtastic_Telemetry_host_metrics_tag 8
#define meshtastic_Telemetry_traffic_management_stats_tag 9
#define meshtastic_Nau7802Config_zeroOffset_tag 1
#define meshtastic_Nau7802Config_calibrationFactor_tag 2
#define meshtastic_SEN5XState_last_cleaning_time_tag 1
@@ -753,17 +720,6 @@ X(a, STATIC, SINGULAR, INT32, noise_floor, 15)
#define meshtastic_LocalStats_CALLBACK NULL
#define meshtastic_LocalStats_DEFAULT NULL
#define meshtastic_TrafficManagementStats_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, packets_inspected, 1) \
X(a, STATIC, SINGULAR, UINT32, position_dedup_drops, 2) \
X(a, STATIC, SINGULAR, UINT32, nodeinfo_cache_hits, 3) \
X(a, STATIC, SINGULAR, UINT32, rate_limit_drops, 4) \
X(a, STATIC, SINGULAR, UINT32, unknown_packet_drops, 5) \
X(a, STATIC, SINGULAR, UINT32, hop_exhausted_packets, 6) \
X(a, STATIC, SINGULAR, UINT32, router_hops_preserved, 7)
#define meshtastic_TrafficManagementStats_CALLBACK NULL
#define meshtastic_TrafficManagementStats_DEFAULT NULL
#define meshtastic_HealthMetrics_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, UINT32, heart_bpm, 1) \
X(a, STATIC, OPTIONAL, UINT32, spO2, 2) \
@@ -792,8 +748,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_qual
X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) \
X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) \
X(a, STATIC, ONEOF, MESSAGE, (variant,health_metrics,variant.health_metrics), 7) \
X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8) \
X(a, STATIC, ONEOF, MESSAGE, (variant,traffic_management_stats,variant.traffic_management_stats), 9)
X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8)
#define meshtastic_Telemetry_CALLBACK NULL
#define meshtastic_Telemetry_DEFAULT NULL
#define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics
@@ -803,7 +758,6 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,traffic_management_stats,variant.tra
#define meshtastic_Telemetry_variant_local_stats_MSGTYPE meshtastic_LocalStats
#define meshtastic_Telemetry_variant_health_metrics_MSGTYPE meshtastic_HealthMetrics
#define meshtastic_Telemetry_variant_host_metrics_MSGTYPE meshtastic_HostMetrics
#define meshtastic_Telemetry_variant_traffic_management_stats_MSGTYPE meshtastic_TrafficManagementStats
#define meshtastic_Nau7802Config_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \
@@ -826,7 +780,6 @@ extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
extern const pb_msgdesc_t meshtastic_PowerMetrics_msg;
extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg;
extern const pb_msgdesc_t meshtastic_LocalStats_msg;
extern const pb_msgdesc_t meshtastic_TrafficManagementStats_msg;
extern const pb_msgdesc_t meshtastic_HealthMetrics_msg;
extern const pb_msgdesc_t meshtastic_HostMetrics_msg;
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
@@ -839,7 +792,6 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
#define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg
#define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg
#define meshtastic_LocalStats_fields &meshtastic_LocalStats_msg
#define meshtastic_TrafficManagementStats_fields &meshtastic_TrafficManagementStats_msg
#define meshtastic_HealthMetrics_fields &meshtastic_HealthMetrics_msg
#define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
@@ -858,7 +810,6 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
#define meshtastic_PowerMetrics_size 81
#define meshtastic_SEN5XState_size 27
#define meshtastic_Telemetry_size 272
#define meshtastic_TrafficManagementStats_size 42
#ifdef __cplusplus
} /* extern "C" */
+1 -1
View File
@@ -391,7 +391,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
node->has_device_metrics = false;
node->has_position = false;
node->user.public_key.size = 0;
memset(node->user.public_key.bytes, 0, sizeof(node->user.public_key.bytes));
node->user.public_key.bytes[0] = 0;
saveChanges(SEGMENT_NODEDATABASE, false);
}
break;
+96 -2
View File
@@ -24,8 +24,24 @@
#include "mesh/generated/meshtastic/rtttl.pb.h"
#include <Arduino.h>
#ifdef HAS_NCP5623
#include <graphics/RAKled.h>
#endif
#ifdef HAS_LP5562
#include <graphics/NomadStarLED.h>
#endif
#ifdef HAS_NEOPIXEL
#include <graphics/NeoPixel.h>
#endif
#ifdef UNPHONE
#include "unPhone.h"
extern unPhone unphone;
#endif
#if defined(HAS_RGB_LED)
#include "AmbientLightingThread.h"
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
@@ -107,6 +123,32 @@ int32_t ExternalNotificationModule::runOnce()
green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7
blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7
white = (colorState & 12) ? brightnessValues[brightnessIndex] : 0;
#ifdef HAS_NCP5623
if (rgb_found.type == ScanI2C::NCP5623) {
rgb.setColor(red, green, blue);
}
#endif
#ifdef HAS_LP5562
if (rgb_found.type == ScanI2C::LP5562) {
rgbw.setColor(red, green, blue, white);
}
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
analogWrite(RGBLED_GREEN, 255 - green);
analogWrite(RGBLED_BLUE, 255 - blue);
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, red);
analogWrite(RGBLED_GREEN, green);
analogWrite(RGBLED_BLUE, blue);
#endif
#ifdef HAS_NEOPIXEL
pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
pixels.show();
#endif
#ifdef UNPHONE
unphone.rgb(red, green, blue);
#endif
if (ascending) { // fade in
brightnessIndex++;
if (brightnessIndex == (sizeof(brightnessValues) - 1)) {
@@ -213,9 +255,34 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
blue = 0;
white = 0;
}
ambientLightingThread->setLighting(moduleConfig.ambient_lighting.current, red, green, blue);
#endif
#ifdef HAS_NCP5623
if (rgb_found.type == ScanI2C::NCP5623) {
rgb.setColor(red, green, blue);
}
#endif
#ifdef HAS_LP5562
if (rgb_found.type == ScanI2C::LP5562) {
rgbw.setColor(red, green, blue, white);
}
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
analogWrite(RGBLED_GREEN, 255 - green);
analogWrite(RGBLED_BLUE, 255 - blue);
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, red);
analogWrite(RGBLED_GREEN, green);
analogWrite(RGBLED_BLUE, blue);
#endif
#ifdef HAS_NEOPIXEL
pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
pixels.show();
#endif
#ifdef UNPHONE
unphone.rgb(red, green, blue);
#endif
#ifdef HAS_DRV2605
if (on) {
drv.go();
@@ -340,6 +407,33 @@ ExternalNotificationModule::ExternalNotificationModule()
LOG_INFO("Use Pin %i in PWM mode", config.device.buzzer_gpio);
}
}
#ifdef HAS_NCP5623
if (rgb_found.type == ScanI2C::NCP5623) {
rgb.begin();
rgb.setCurrent(10);
}
#endif
#ifdef HAS_LP5562
if (rgb_found.type == ScanI2C::LP5562) {
rgbw.begin();
rgbw.setCurrent(20);
}
#endif
#ifdef RGBLED_RED
pinMode(RGBLED_RED, OUTPUT); // set up the RGB led pins
pinMode(RGBLED_GREEN, OUTPUT);
pinMode(RGBLED_BLUE, OUTPUT);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed
analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off
analogWrite(RGBLED_BLUE, 255);
#endif
#ifdef HAS_NEOPIXEL
pixels.begin(); // Initialise the pixel(s)
pixels.clear(); // Set all pixel colors to 'off'
pixels.setBrightness(moduleConfig.ambient_lighting.current);
#endif
} else {
LOG_INFO("External Notification Module Disabled");
disable();
-5
View File
@@ -5,11 +5,6 @@
#include "configuration.h"
#include "input/InputBroker.h"
#ifdef HAS_RGB_LED
#include "AmbientLightingThread.h"
extern AmbientLightingThread *ambientLightingThread;
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#include <NonBlockingRtttl.h>
#else
+1 -1
View File
@@ -148,7 +148,7 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
// Strip the public key if the user is licensed
if (u.is_licensed && u.public_key.size > 0) {
memset(u.public_key.bytes, 0, sizeof(u.public_key.bytes));
u.public_key.bytes[0] = 0;
u.public_key.size = 0;
}
-10
View File
@@ -164,16 +164,6 @@ int32_t StatusLEDModule::runOnce()
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
#ifdef RGB_LED_POWER
if (!config.device.led_heartbeat_disabled) {
if (CHARGE_LED_state == LED_STATE_ON) {
ambientLightingThread->setLighting(10, 255, 0, 0);
} else {
ambientLightingThread->setLighting(0, 0, 0, 0);
}
}
#endif
#ifdef Battery_LED_1
digitalWrite(Battery_LED_1, chargeIndicatorLED1);
#endif
-4
View File
@@ -59,7 +59,3 @@ class StatusLEDModule : private concurrency::OSThread
};
extern StatusLEDModule *statusLEDModule;
#ifdef RGB_LED_POWER
#include "AmbientLightingThread.h"
extern AmbientLightingThread *ambientLightingThread;
#endif
+35 -43
View File
@@ -1,4 +1,3 @@
#include "DebugConfiguration.h"
#include "configuration.h"
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
@@ -26,12 +25,14 @@
#if __has_include(<SensirionI2cScd4x.h>)
#include "Sensor/SCD4XSensor.h"
#endif
#if __has_include(<SensirionI2cSfa3x.h>)
#include "Sensor/SFA30Sensor.h"
#if __has_include(<SensirionI2cStc3x.h>)
#include "Sensor/STC31Sensor.h"
#endif
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
LOG_DEBUG("AirQualityTelemetryModule::i2cScanFinished called, air_quality_enabled=%d",
moduleConfig.telemetry.air_quality_enabled);
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
return;
}
@@ -54,8 +55,8 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<SensirionI2cScd4x.h>)
addSensor<SCD4XSensor>(i2cScanner, ScanI2C::DeviceType::SCD4X);
#endif
#if __has_include(<SensirionI2cSfa3x.h>)
addSensor<SFA30Sensor>(i2cScanner, ScanI2C::DeviceType::SFA30);
#if __has_include(<SensirionI2cStc3x.h>)
addSensor<STC31Sensor>(i2cScanner, ScanI2C::DeviceType::STC31);
#endif
}
@@ -78,8 +79,7 @@ int32_t AirQualityTelemetryModule::runOnce()
}
if (firstTime) {
// This is the first time the OSThread library has called this function, so
// do some setup
// This is the first time the OSThread library has called this function, so do some setup
firstTime = false;
if (moduleConfig.telemetry.air_quality_enabled) {
@@ -100,6 +100,10 @@ int32_t AirQualityTelemetryModule::runOnce()
return disable();
}
bool isSensorRole = config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
// SENSOR role bypasses airtime restrictions - telemetry is its primary purpose
bool airTimeAllows = isSensorRole || (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil());
// Wake up the sensors that need it
LOG_INFO("Waking up sensors...");
for (TelemetrySensor *sensor : sensors) {
@@ -110,8 +114,7 @@ int32_t AirQualityTelemetryModule::runOnce()
Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
airTimeAllows) {
if (!sensor->isActive()) {
LOG_DEBUG("Waking up: %s", sensor->sensorName);
return sensor->wakeUp();
@@ -125,18 +128,19 @@ int32_t AirQualityTelemetryModule::runOnce()
}
}
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
bool timeToSend = (lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(
lastSentToMesh,
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes));
if (timeToSend && airTimeAllows) {
sendTelemetry();
lastSentToMesh = millis();
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
(isSensorRole || service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
// SENSOR role always sends; others only send while queue is empty (phone assumed connected)
sendTelemetry(NODENUM_BROADCAST, true);
lastSentToPhone = millis();
}
@@ -145,9 +149,9 @@ int32_t AirQualityTelemetryModule::runOnce()
LOG_DEBUG("Sending sensors to sleep");
for (TelemetrySensor *sensor : sensors) {
if (sensor->isActive() && sensor->canSleep()) {
if (sensor->wakeUpTimeMs() <
(int32_t)Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes)) {
if (sensor->wakeUpTimeMs() < Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs,
numOnlineNodes)) {
LOG_DEBUG("Disabling %s until next period", sensor->sensorName);
sensor->sleep();
} else {
@@ -229,8 +233,6 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
if (m.has_co2)
entries.push_back("CO2: " + String(m.co2) + "ppm");
if (m.has_form_formaldehyde)
entries.push_back("HCHO: " + String(m.form_formaldehyde) + "ppb");
// === Show first available metric on top-right of first line ===
if (!entries.empty()) {
@@ -266,19 +268,17 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
if (t->variant.air_quality_metrics.has_pm10_standard)
LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, "
"pm100_standard=%i",
sender, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
t->variant.air_quality_metrics.pm100_standard);
LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender,
t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
t->variant.air_quality_metrics.pm100_standard);
if (t->variant.air_quality_metrics.has_co2)
LOG_INFO("CO2=%i, CO2_T=%.2f, CO2_H=%.2f", t->variant.air_quality_metrics.co2,
t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity);
// TODO - Decide what to do with these
// LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
// t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental,
// t->variant.air_quality_metrics.pm100_environmental);
if (t->variant.air_quality_metrics.has_form_formaldehyde)
LOG_INFO("HCHO=%.2f, HCHO_T=%.2f, HCHO_H=%.2f", t->variant.air_quality_metrics.form_formaldehyde,
t->variant.air_quality_metrics.form_temperature, t->variant.air_quality_metrics.form_humidity);
LOG_INFO(" | CO2=%i, CO2_T=%f, CO2_H=%f", t->variant.air_quality_metrics.co2,
t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity);
#endif
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
@@ -366,18 +366,10 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
m.variant.air_quality_metrics.has_co2_humidity;
if (hasAnyCO2) {
LOG_INFO("Send: co2=%i, co2_t=%.2f, co2_rh=%.2f", m.variant.air_quality_metrics.co2,
LOG_INFO("Send: co2=%i, co2_t=%f, co2_rh=%f", m.variant.air_quality_metrics.co2,
m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity);
}
bool hasAnyHCHO = m.variant.air_quality_metrics.has_form_formaldehyde ||
m.variant.air_quality_metrics.has_form_temperature || m.variant.air_quality_metrics.has_form_humidity;
if (hasAnyHCHO) {
LOG_INFO("Send: hcho=%.2f, hcho_t=%.2f, hcho_rh=%.2f", m.variant.air_quality_metrics.form_formaldehyde,
m.variant.air_quality_metrics.form_temperature, m.variant.air_quality_metrics.form_humidity);
}
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
@@ -446,4 +438,4 @@ AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(
return result;
}
#endif
#endif
+1 -2
View File
@@ -64,9 +64,8 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
bool firstTime = true;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
// uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
};
#endif
#endif
@@ -26,6 +26,7 @@
// Sensors
#include "Sensor/CGRadSensSensor.h"
#include "Sensor/RCWL9620Sensor.h"
#include "Sensor/TelemetrySensor.h"
#include "Sensor/nullSensor.h"
namespace graphics
@@ -571,6 +572,13 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
valid = valid || get_metrics;
hasSensor = true;
#endif
// Update shared humidity for cross-sensor compensation (e.g., STC31 CO2 sensor)
if (m->variant.environment_metrics.has_relative_humidity) {
lastEnvironmentHumidity = m->variant.environment_metrics.relative_humidity;
hasValidHumidity = true;
}
return valid && hasSensor;
}
@@ -138,10 +138,6 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.air_quality_metrics.has_particles_100um = true;
measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26);
LOG_DEBUG("Got %s readings: pM1p0_standard=%u, pM2p5_standard=%u, pM10p0_standard=%u", sensorName,
measurement->variant.air_quality_metrics.pm10_standard, measurement->variant.air_quality_metrics.pm25_standard,
measurement->variant.air_quality_metrics.pm100_standard);
return true;
}
+29 -62
View File
@@ -125,7 +125,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement)
reClockI2C(currentClock, _bus, false);
#endif
LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum%.2f", sensorName, co2, temperature, humidity);
LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity);
if (error != SCD4X_NO_ERROR) {
LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error);
if (co2 == 0) {
@@ -333,6 +333,12 @@ bool SCD4XSensor::setASC(bool ascEnabled)
return false;
}
if (ascActive) {
LOG_INFO("%s: ASC is enabled", sensorName);
} else {
LOG_INFO("%s: ASC is disabled", sensorName);
}
return true;
}
@@ -352,7 +358,7 @@ bool SCD4XSensor::setASC(bool ascEnabled)
bool SCD4XSensor::setASCBaseline(uint32_t targetCO2)
{
// TODO - Remove?
// Available in library, but not described in datasheet.
// Available in library, but not described in datasheet.
uint16_t error;
LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2);
@@ -534,7 +540,6 @@ bool SCD4XSensor::setAltitude(uint32_t altitude)
if (!stopMeasurement()) {
return false;
}
LOG_INFO("%s: setting altitude at %um", sensorName, altitude);
error = scd4x.setSensorAltitude(altitude);
@@ -543,15 +548,11 @@ bool SCD4XSensor::setAltitude(uint32_t altitude)
return false;
}
// NOTE: this gives an error if issued. Sensirion's library
// doesn't indicate it's needed.
// error = scd4x.persistSettings();
// if (error != SCD4X_NO_ERROR) {
// LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error);
// return false;
// }
LOG_INFO("%s: altitude set", sensorName);
error = scd4x.persistSettings();
if (error != SCD4X_NO_ERROR) {
LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error);
return false;
}
return true;
}
@@ -574,8 +575,6 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure)
{
uint16_t error;
LOG_INFO("%s: setting ambient pressure at %u Pa", sensorName, ambientPressure);
error = scd4x.setAmbientPressure(ambientPressure);
if (error != SCD4X_NO_ERROR) {
@@ -590,8 +589,6 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure)
return false;
}
LOG_INFO("%s: ambient pressure set set", sensorName);
return true;
}
@@ -827,28 +824,15 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa
if (request->sensor_config.scd4x_config.has_factory_reset) {
LOG_DEBUG("%s: Requested factory reset", sensorName);
if (!this->factoryReset()) {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->factoryReset();
} else {
if (request->sensor_config.scd4x_config.has_set_asc) {
getASC(ascActive);
bool currentASC = ascActive;
this->setASC(request->sensor_config.scd4x_config.set_asc);
if (request->sensor_config.scd4x_config.set_asc == false) {
LOG_DEBUG("%s: Request for FRC", sensorName);
if (request->sensor_config.scd4x_config.has_set_target_co2_conc) {
if (this->setASC(request->sensor_config.scd4x_config.set_asc)) {
if (!this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc)) {
result = AdminMessageHandleResult::NOT_HANDLED;
// Set it back to ASC if failed
setASC(currentASC);
break;
};
} else {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc);
} else {
// FRC requested but no target CO2 provided
LOG_ERROR("%s: target CO2 not provided", sensorName);
@@ -857,17 +841,12 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa
}
} else {
LOG_DEBUG("%s: Request for ASC", sensorName);
if (this->setASC(request->sensor_config.scd4x_config.set_asc)) {
if (request->sensor_config.scd4x_config.has_set_target_co2_conc) {
LOG_DEBUG("%s: Request has target CO2", sensorName);
this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc);
// NOTE - in this situation, if we set ASC, but baseline set fails, we stay on ASC
} else {
LOG_DEBUG("%s: Request doesn't have target CO2", sensorName);
}
if (request->sensor_config.scd4x_config.has_set_target_co2_conc) {
LOG_DEBUG("%s: Request has target CO2", sensorName);
// TODO - Remove? see setASCBaseline function
this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc);
} else {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
LOG_DEBUG("%s: Request doesn't have target CO2", sensorName);
}
}
}
@@ -876,36 +855,27 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa
// NOTE: this requires to have a sensor working on stable environment
// And to make it between readings
if (request->sensor_config.scd4x_config.has_set_temperature) {
if (!this->setTemperature(request->sensor_config.scd4x_config.set_temperature)) {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->setTemperature(request->sensor_config.scd4x_config.set_temperature);
}
// Check for altitude or pressure offset
if (request->sensor_config.scd4x_config.has_set_altitude) {
if (!this->setAltitude(request->sensor_config.scd4x_config.set_altitude)) {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->setAltitude(request->sensor_config.scd4x_config.set_altitude);
} else if (request->sensor_config.scd4x_config.has_set_ambient_pressure) {
if (!this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure)) {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure);
}
// Check for low power mode
// NOTE: to switch from one mode to another do:
// setPowerMode -> startMeasurement
if (request->sensor_config.scd4x_config.has_set_power_mode) {
if (!this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode)) {
result = AdminMessageHandleResult::NOT_HANDLED;
break;
}
this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode);
}
}
// Start measurement mode
this->startMeasurement();
result = AdminMessageHandleResult::HANDLED;
break;
@@ -913,9 +883,6 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa
result = AdminMessageHandleResult::NOT_HANDLED;
}
// Start measurement mode
this->startMeasurement();
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
+13 -24
View File
@@ -572,7 +572,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
// Check if it is time to do a cleaning
uint32_t now;
int32_t passed = 0;
int32_t passed;
now = getValidTime(RTCQuality::RTCQualityDevice);
// If time is not RTCQualityNone, it will return non-zero
@@ -665,16 +665,16 @@ bool SEN5XSensor::readValues()
sen5xmeasurement.vocIndex = !isnan(int_vocIndex) ? int_vocIndex / 10.0f : FLT_MAX;
sen5xmeasurement.noxIndex = !isnan(int_noxIndex) ? int_noxIndex / 10.0f : FLT_MAX;
LOG_DEBUG("Got %s readings: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sensorName, sen5xmeasurement.pM1p0,
sen5xmeasurement.pM2p5, sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
LOG_DEBUG("Got: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5,
sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
if (model != SEN50) {
LOG_DEBUG("Got %s readings: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sensorName, sen5xmeasurement.humidity,
sen5xmeasurement.temperature, sen5xmeasurement.vocIndex);
LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sen5xmeasurement.humidity, sen5xmeasurement.temperature,
sen5xmeasurement.vocIndex);
}
if (model == SEN55) {
LOG_DEBUG("Got %s readings: noxIndex=%.2f", sensorName, sen5xmeasurement.noxIndex);
LOG_DEBUG("Got: noxIndex=%.2f", sen5xmeasurement.noxIndex);
}
return true;
@@ -727,9 +727,9 @@ bool SEN5XSensor::readPNValues(bool cumulative)
sen5xmeasurement.pN1p0 -= sen5xmeasurement.pN0p5;
}
LOG_DEBUG("Got %s readings: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sensorName,
sen5xmeasurement.pN0p5, sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0,
sen5xmeasurement.pN10p0, sen5xmeasurement.tSize);
LOG_DEBUG("Got: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sen5xmeasurement.pN0p5,
sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, sen5xmeasurement.pN10p0,
sen5xmeasurement.tSize);
return true;
}
@@ -919,11 +919,6 @@ bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement)
void SEN5XSensor::setMode(bool setOneShot)
{
oneShotMode = setOneShot;
if (oneShotMode) {
LOG_INFO("%s setting mode to one shot mode", sensorName);
} else {
LOG_INFO("%s setting mode to continuous mode", sensorName);
}
}
AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
@@ -939,22 +934,16 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa
break;
}
// Check for one-shot/continuous mode request
if (request->sensor_config.sen5x_config.has_set_one_shot_mode) {
this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode);
}
// TODO - Add admin command to set temperature offset?
// TODO - Add admin command to set temperature offset
// Check for temperature offset
// if (request->sensor_config.sen5x_config.has_set_temperature) {
// this->setTemperature(request->sensor_config.sen5x_config.set_temperature);
// }
// TODO - Add admin command to trigger fan cleaning?
// Check for one-shot/continuous mode request
// if (request->sensor_config.sen5x_config.has_fan_cleaning && request->sensor_config.sen5x_config.fan_cleaning) {
// this->startCleaning();
// }
if (request->sensor_config.sen5x_config.has_set_one_shot_mode) {
this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode);
}
result = AdminMessageHandleResult::HANDLED;
break;
@@ -1,198 +0,0 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cSfa3x.h>)
#include "../detect/reClockI2C.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "SFA30Sensor.h"
SFA30Sensor::SFA30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SFA30, "SFA30"){};
bool SFA30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
_bus = bus;
_address = dev->address.address;
#ifdef SFA30_I2C_CLOCK_SPEED
#ifdef CAN_RECLOCK_I2C
uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
#elif !HAS_SCREEN
reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true);
#else
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
return false;
#endif /* CAN_RECLOCK_I2C */
#endif /* SFA30_I2C_CLOCK_SPEED */
sfa30.begin(*_bus, _address);
delay(20);
if (this->isError(sfa30.deviceReset())) {
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
return false;
}
state = State::IDLE;
if (this->isError(sfa30.startContinuousMeasurement())) {
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
return false;
}
LOG_INFO("%s starting measurement", sensorName);
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
status = 1;
state = State::ACTIVE;
measureStarted = getTime();
LOG_INFO("%s Enabled", sensorName);
initI2CSensor();
return true;
};
bool SFA30Sensor::isError(uint16_t response)
{
if (response == SFA30_NO_ERROR)
return false;
// TODO - Check error to char conversion
LOG_ERROR("%s: %u", sensorName, response);
return true;
}
void SFA30Sensor::sleep()
{
#ifdef SFA30_I2C_CLOCK_SPEED
#ifdef CAN_RECLOCK_I2C
uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
#elif !HAS_SCREEN
reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true);
#else
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
return;
#endif /* CAN_RECLOCK_I2C */
#endif /* SFA30_I2C_CLOCK_SPEED */
// Note - not recommended for this sensor on a periodic basis
if (this->isError(sfa30.stopMeasurement())) {
LOG_ERROR("%s: can't stop measurement", sensorName);
};
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
LOG_INFO("%s: stop measurement", sensorName);
state = State::IDLE;
measureStarted = 0;
}
uint32_t SFA30Sensor::wakeUp()
{
#ifdef SFA30_I2C_CLOCK_SPEED
#ifdef CAN_RECLOCK_I2C
uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
#elif !HAS_SCREEN
reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true);
#else
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
return false;
#endif /* CAN_RECLOCK_I2C */
#endif /* SFA30_I2C_CLOCK_SPEED */
LOG_INFO("Waking up %s", sensorName);
if (this->isError(sfa30.startContinuousMeasurement())) {
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
return 0;
}
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
state = State::ACTIVE;
measureStarted = getTime();
return SFA30_WARMUP_MS;
}
int32_t SFA30Sensor::wakeUpTimeMs()
{
return SFA30_WARMUP_MS;
}
bool SFA30Sensor::canSleep()
{
// Sleep is disabled in this sensor because readings are not tested with periodic sleep
// with such low power consumption, prefered to keep it active
return false;
}
bool SFA30Sensor::isActive()
{
return state == State::ACTIVE;
}
int32_t SFA30Sensor::pendingForReadyMs()
{
uint32_t now;
now = getTime();
uint32_t sinceHchoMeasureStarted = (now - measureStarted) * 1000;
LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceHchoMeasureStarted);
if (sinceHchoMeasureStarted < SFA30_WARMUP_MS) {
LOG_INFO("%s: not enough time passed since starting measurement", sensorName);
return SFA30_WARMUP_MS - sinceHchoMeasureStarted;
}
return 0;
}
bool SFA30Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
float hcho = 0.0;
float humidity = 0.0;
float temperature = 0.0;
#ifdef SFA30_I2C_CLOCK_SPEED
#ifdef CAN_RECLOCK_I2C
uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
#elif !HAS_SCREEN
reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true);
#else
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
return false;
#endif /* CAN_RECLOCK_I2C */
#endif /* SFA30_I2C_CLOCK_SPEED */
if (this->isError(sfa30.readMeasuredValues(hcho, humidity, temperature))) {
LOG_WARN("%s: No values", sensorName);
return false;
}
#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus, false);
#endif
measurement->variant.air_quality_metrics.has_form_temperature = true;
measurement->variant.air_quality_metrics.has_form_humidity = true;
measurement->variant.air_quality_metrics.has_form_formaldehyde = true;
measurement->variant.air_quality_metrics.form_temperature = temperature;
measurement->variant.air_quality_metrics.form_humidity = humidity;
measurement->variant.air_quality_metrics.form_formaldehyde = hcho;
LOG_DEBUG("Got %s readings: hcho=%.2f, hcho_temp=%.2f, hcho_hum=%.2f", sensorName, hcho, temperature, humidity);
return true;
}
#endif
@@ -1,39 +0,0 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cSfa3x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "RTC.h"
#include "TelemetrySensor.h"
#include <SensirionI2cSfa3x.h>
#define SFA30_I2C_CLOCK_SPEED 100000
#define SFA30_WARMUP_MS 10000
#define SFA30_NO_ERROR 0
class SFA30Sensor : public TelemetrySensor
{
private:
enum class State { IDLE, ACTIVE };
State state = State::IDLE;
uint32_t measureStarted = 0;
SensirionI2cSfa3x sfa30;
TwoWire *_bus{};
uint8_t _address{};
bool isError(uint16_t response);
public:
SFA30Sensor();
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool isActive() override;
virtual void sleep() override;
virtual uint32_t wakeUp() override;
virtual bool canSleep() override;
virtual int32_t wakeUpTimeMs() override;
virtual int32_t pendingForReadyMs() override;
};
#endif
@@ -0,0 +1,137 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cStc3x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "STC31Sensor.h"
#define STC31_NO_ERROR 0
// Binary gas configurations from STC31 datasheet
// See: setBinaryGas() - section 3.3.2 in the datasheet
#define STC31_BINARY_GAS_CO2_N2_100 0x0000 // CO2 in N2, 0-100 vol%
#define STC31_BINARY_GAS_CO2_AIR_100 0x0001 // CO2 in air, 0-100 vol%
#define STC31_BINARY_GAS_CO2_N2_25 0x0002 // CO2 in N2, 0-25 vol%
#define STC31_BINARY_GAS_CO2_AIR_25 0x0003 // CO2 in air, 0-25 vol%
STC31Sensor::STC31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_STC31, "STC31") {}
bool STC31Sensor::configureBinaryGas()
{
// STC31 requires binary gas mode to be set before measurements
// It resets to default mode (no gas selected) after power cycle or reset
LOG_DEBUG("%s: Configuring binary gas mode (CO2 in air, 0-25%%)", sensorName);
int16_t error = stc3x.setBinaryGas(STC31_BINARY_GAS_CO2_AIR_25);
if (error != STC31_NO_ERROR) {
LOG_ERROR("%s: Failed to set binary gas mode, error: %d", sensorName, error);
return false;
}
LOG_DEBUG("%s: Binary gas mode configured successfully", sensorName);
binaryGasConfigured = true;
return true;
}
bool STC31Sensor::setHumidityCompensation()
{
// Use actual humidity from environment sensor if available, otherwise default to 50%
float humidity = hasValidHumidity ? lastEnvironmentHumidity : 50.0f;
LOG_DEBUG("%s: Setting humidity compensation to %.1f%%", sensorName, humidity);
int16_t error = stc3x.setRelativeHumidity(humidity);
if (error != STC31_NO_ERROR) {
LOG_WARN("%s: Failed to set humidity compensation (%.1f%%), error: %d", sensorName, humidity, error);
return false;
}
return true;
}
bool STC31Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s at address 0x%02x", sensorName, dev->address.address);
_bus = bus;
_address = dev->address.address;
stc3x.begin(*_bus, _address);
// Allow sensor to fully stabilize after power-on
delay(100);
// Run self-test to verify sensor is working
STC3xTestResultT selfTestResult;
int16_t testError = stc3x.selfTest(selfTestResult);
if (testError != STC31_NO_ERROR) {
LOG_WARN("%s: Self-test command failed with error: %d", sensorName, testError);
} else if (selfTestResult.value != 0) {
LOG_WARN("%s: Self-test reported error: 0x%04x", sensorName, selfTestResult.value);
} else {
LOG_DEBUG("%s: Self-test passed", sensorName);
}
// Wait after self-test before configuring
delay(50);
if (!configureBinaryGas())
return false;
setHumidityCompensation(); // Non-fatal if this fails
status = 1;
initI2CSensor();
return true;
}
bool STC31Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
float gasConcentration = 0;
float temperature = 0;
// Binary gas mode is configured once during initDevice() and persists
// Humidity compensation can be updated for each measurement
setHumidityCompensation(); // Non-fatal if this fails
// Small delay for humidity compensation to take effect
delay(10);
// Retry measurement up to 3 times with increasing delays
int16_t error = STC31_NO_ERROR;
for (int attempt = 0; attempt < 3; attempt++) {
error = stc3x.measureGasConcentration(gasConcentration, temperature);
if (error == STC31_NO_ERROR)
break;
LOG_WARN("%s: Measurement attempt %d failed (error %d), retrying...", sensorName, attempt + 1, error);
// If we get NotEnoughDataError (0x00F in error), the sensor may have lost its config
// Try re-configuring binary gas mode
if ((error & 0x00F) == 0x00F && attempt == 1) {
LOG_WARN("%s: Sensor may have lost configuration, re-configuring binary gas mode", sensorName);
configureBinaryGas();
delay(100);
} else {
delay(100 * (attempt + 1));
}
}
if (error != STC31_NO_ERROR) {
LOG_ERROR("%s: Error reading measurement after retries: %d", sensorName, error);
// Mark that we need to re-configure on next attempt
binaryGasConfigured = false;
return false;
}
// Convert percentage to ppm: 1% = 10000 ppm
uint32_t co2_ppm = (uint32_t)(gasConcentration * 10000.0f);
LOG_DEBUG("%s readings: %.2f%% CO2 (=%u ppm), %.2f degC", sensorName, gasConcentration, co2_ppm, temperature);
measurement->variant.air_quality_metrics.has_co2 = true;
measurement->variant.air_quality_metrics.co2 = co2_ppm;
measurement->variant.air_quality_metrics.has_co2_temperature = true;
measurement->variant.air_quality_metrics.co2_temperature = temperature;
return true;
}
#endif
@@ -0,0 +1,26 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cStc3x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SensirionI2cStc3x.h>
class STC31Sensor : public TelemetrySensor
{
private:
SensirionI2cStc3x stc3x;
TwoWire *_bus{};
uint8_t _address{};
bool binaryGasConfigured = false;
bool configureBinaryGas();
bool setHumidityCompensation();
public:
STC31Sensor();
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
@@ -7,4 +7,9 @@
#include "TelemetrySensor.h"
#include "main.h"
// Shared humidity value for cross-sensor compensation
// Default to 50% if no humidity sensor is available
float lastEnvironmentHumidity = 50.0f;
bool hasValidHumidity = false;
#endif
@@ -16,6 +16,11 @@ class TwoWire;
#define DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
// Shared humidity value for cross-sensor compensation (e.g., STC31 CO2 sensor)
// Updated by environment sensors that measure humidity
extern float lastEnvironmentHumidity;
extern bool hasValidHumidity;
class TelemetrySensor
{
protected:
@@ -158,15 +158,6 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity);
}
if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
msgPayload["form_formaldehyde"] = new JSONValue(decoded->variant.air_quality_metrics.form_formaldehyde);
}
if (decoded->variant.air_quality_metrics.has_form_temperature) {
msgPayload["form_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.form_temperature);
}
if (decoded->variant.air_quality_metrics.has_form_humidity) {
msgPayload["form_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.form_humidity);
}
} else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
if (decoded->variant.power_metrics.has_ch1_voltage) {
msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage);
@@ -129,15 +129,6 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity;
}
if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
jsonObj["payload"]["form_formaldehyde"] = decoded->variant.air_quality_metrics.form_formaldehyde;
}
if (decoded->variant.air_quality_metrics.has_form_temperature) {
jsonObj["payload"]["form_temperature"] = decoded->variant.air_quality_metrics.form_temperature;
}
if (decoded->variant.air_quality_metrics.has_form_humidity) {
jsonObj["payload"]["form_humidity"] = decoded->variant.air_quality_metrics.form_humidity;
}
} else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
if (decoded->variant.power_metrics.has_ch1_voltage) {
jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage;
@@ -16,4 +16,4 @@ upload_speed = 460800
lib_deps =
${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
@@ -7,4 +7,4 @@ build_flags =
-I variants/esp32c3/heltec_hru_3601
lib_deps = ${esp32c3_base.lib_deps}
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
@@ -24,7 +24,7 @@ build_unflags =
lib_deps =
${esp32c6_base.lib_deps}
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@2.3.7
build_flags =
@@ -13,7 +13,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
zinggjm/GxEPD2@1.6.7
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=1
@@ -11,7 +11,7 @@ upload_speed = 921600
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=1
@@ -25,4 +25,4 @@ lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
zinggjm/GxEPD2@1.6.7
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
@@ -11,4 +11,4 @@ build_flags =
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
+5 -2
View File
@@ -124,8 +124,11 @@ build_flags =
-D SCREEN_TOUCH_RST=TOUCH_RST_PIN
lib_deps = ${heltec_v4_base.lib_deps}
${device-ui_base.lib_deps}
; ${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
; TODO revert to official device-ui (when merged)
# renovate: datasource=git-refs depName=Quency-D_device-ui packageName=https://github.com/Quency-D/device-ui gitBranch=heltec-v4-tft
https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip
-2
View File
@@ -76,8 +76,6 @@
#define BATTERY_SENSE_SAMPLES 30
#define ADC_MULTIPLIER 2.9333
#define OCV_ARRAY 7950, 7850, 7750, 7580, 7440, 7310, 7150, 7005, 6860, 6685, 6000
// NTC temperature sensor
#define NTC_PIN 14
+1 -1
View File
@@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps}
# TODO renovate
https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
adafruit/Adafruit NeoPixel@1.15.4
adafruit/Adafruit NeoPixel@1.15.3
[env:unphone-tft]
board_level = extra
@@ -1,21 +1,43 @@
# XIAO nRF52840 + XIAO Wio SX1262
For a mere doubling in price you too can swap out the XIAO ESP32S3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life!
For a mere doubling in price you too can swap out the XIAO ESP32C3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life!
I'm not really sure why else you would want to as the ESP32S3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32S3 to use for something else!
I'm not really sure why else you would want to as the ESP32C3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32C3 to use for something else!
If you're still reading you are clearly gonna do it anyway, so...mount the Wio SX1262 either on top or underneath depending on your preference. The `variant.h` will work with either configuration though it does map the Wio SX1262's button to nRF52840 Pin `D5` as it can still be used as a user button and it's nice to be able to gracefully shutdown a node by holding it down for 5 seconds.
If you do decide to wire up the button, orient it so looking straight-down at the Wio SX1262 the radio chip is at the bottom, button in the middle and the hole is at the top - the **left** side of the button should be soldered to `GND` (e.g. the 2nd pin down the top on the **right** row of pins) and the **right** side of the button should be soldered to `D5` (e.g. the 2nd pin up from the button on the **left** row of pins.). This mirrors the original wiring and wiring it in reverse could end up connecting GND to voltage and that's no beuno.
Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, and I2C has been mapped to NFC1 (SDA, D30) and NFC2 (SCL, D31)
Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, The same pins could be repurposed for `i2c` if you would like to have that instead of serial, in `variant.h` you would just need to change:
The same pins could be reordered if you would like to have a different arrangement, in `variant.h` you would just need to change the relevant lines:
```cpp
#define GPS_TX_PIN D6 // This is data from the MCU
#define GPS_RX_PIN D7 // This is data from the GNSS module
#define PIN_WIRE_SDA D6
#define PIN_WIRE_SCL D7
```c++
// RX and TX pins
#define PIN_SERIAL1_RX (6)
#define PIN_SERIAL1_TX (7)
```
to
```c++
// RX and TX pins
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
```
and
```c++
#define PIN_WIRE_SDA (-1)
#define PIN_WIRE_SCL (-1)
// #define PIN_WIRE_SDA (6)
// #define PIN_WIRE_SCL (7)
```
to
```c++
#define PIN_WIRE_SDA (6)
#define PIN_WIRE_SCL (7)
```
If you wanted both serial and i2c you could even go so far as to use the pads for the PDM mic which is missing on the non-sense board (`P1.00` / `P0.16`)... or move up to the nRF52840 Plus which has even more pins available but hasn't been checked/confirmed if it follows the same pin mapping as the non-plus.
@@ -1,17 +1,13 @@
; Seeed Xiao BLE but using the B2B from ESP32S3 variant
[env:seeed_xiao_nrf52840_btb]
extends = env:seeed_xiao_nrf52840_kit
; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY
[env:seeed-xiao-nrf52840-wio-sx1262]
board = xiao_ble_sense
extends = nrf52840_base
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Ivariants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262
-D PRIVATE_HW
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DPRIVATE_HW ; Define private hardware
-DSEEED_XIAO_NRF_WIO_BTB ; Define Seeed XIAO nRF Wio B2B
-USEEED_XIAO_NRF52840_KIT ; Remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit>
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
@@ -0,0 +1,62 @@
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// D0 .. D13
2, // D0 is P0.02 (A0)
3, // D1 is P0.03 (A1)
28, // D2 is P0.28 (A2)
29, // D3 is P0.29 (A3)
4, // D4 is P0.04 (A4,SDA)
5, // D5 is P0.05 (A5,SCL)
43, // D6 is P1.11 (TX)
44, // D7 is P1.12 (RX)
45, // D8 is P1.13 (SCK)
46, // D9 is P1.14 (MISO)
47, // D10 is P1.15 (MOSI)
// LEDs
26, // D11 is P0.26 (LED RED)
6, // D12 is P0.06 (LED BLUE)
30, // D13 is P0.30 (LED GREEN)
14, // D14 is P0.14 (READ_BAT)
// LSM6DS3TR
40, // D15 is P1.08 (6D_PWR)
27, // D16 is P0.27 (6D_I2C_SCL)
7, // D17 is P0.07 (6D_I2C_SDA)
11, // D18 is P0.11 (6D_INT1)
// MIC
42, // 17,//42, // D19 is P1.10 (MIC_PWR)
32, // 26,//32, // D20 is P1.00 (PDM_CLK)
16, // 25,//16, // D21 is P0.16 (PDM_DATA)
// BQ25100
13, // D22 is P0.13 (HICHG)
17, // D23 is P0.17 (~CHG)
//
21, // D24 is P0.21 (QSPI_SCK)
25, // D25 is P0.25 (QSPI_CSN)
20, // D26 is P0.20 (QSPI_SIO_0 DI)
24, // D27 is P0.24 (QSPI_SIO_1 DO)
22, // D28 is P0.22 (QSPI_SIO_2 WP)
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
// NFC
9, // D30 is P0.09 (NFC1)
10, // D31 is P0.10 (NFC2)
// VBAT
31, // D32 is P0.10 (VBAT)
};
void initVariant()
{
// Set BQ25101 ISET to 100mA instead of 50mA
pinMode(HICHG, OUTPUT);
digitalWrite(HICHG, LOW);
}
@@ -0,0 +1,183 @@
// basically xiao_ble with pins remapped for:
// Seeed XIAO nRF52840 : https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html
// Seeed Wio SX1626 : https://www.seeedstudio.com/Wio-SX1262-with-XIAO-ESP32S3-p-5982.html
#ifndef _SEEED_XIAO_NRF52840_SENSE_H_
#define _SEEED_XIAO_NRF52840_SENSE_H_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#define PINS_COUNT (33)
#define NUM_DIGITAL_PINS (33)
#define NUM_ANALOG_INPUTS (8) // A6 is used for battery, A7 is analog reference
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
// ----
#define LED_RED 11
#define LED_BLUE 12
#define LED_GREEN 13
#define PIN_LED1 LED_GREEN
#define PIN_LED2 LED_BLUE
#define PIN_LED3 LED_RED
#define LED_STATE_ON 1 // State when LED is lit
// XIAO Wio-SX1262 Shield User button
#define PIN_BUTTON1 5
#define BUTTON_NEED_PULLUP
// Digital Pins
// ------------
#define D0 (0ul)
#define D1 (1ul)
#define D2 (2ul)
#define D3 (3ul)
#define D4 (4ul)
#define D5 (5ul)
#define D6 (6ul)
#define D7 (7ul)
#define D8 (8ul)
#define D9 (9ul)
#define D10 (10ul)
// Analog Pins
// -----------
#define PIN_A0 (0)
#define PIN_A1 (1)
#define PIN_A2 (2)
#define PIN_A3 (3)
#define PIN_A4 (4)
#define PIN_A5 (5)
#define PIN_VBAT (32)
#define VBAT_ENABLE (14)
static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
#define ADC_RESOLUTION 12
// Other Pins
// ----------
#define PIN_NFC1 (30)
#define PIN_NFC2 (31)
// RX and TX pins
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
// complains if not defined
#define PIN_SERIAL2_RX (-1)
#define PIN_SERIAL2_TX (-1)
// 4 is used as RF_SW and 5 for USR button so...
#define PIN_WIRE_SDA (6)
#define PIN_WIRE_SCL (7)
static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;
// SPI SX1262
// ----------
#define SPI_SX1262
#ifdef SPI_SX1262
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (9)
#define PIN_SPI_MOSI (10)
#define PIN_SPI_SCK (8)
static const uint8_t SS = D3;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
// supported modules list
#define USE_SX1262
// common pinouts for SX126X modules
#define SX126X_CS D3
#define SX126X_DIO1 D0
#define SX126X_BUSY D1
#define SX126X_RESET D2
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_RXEN 38
#define SX126X_TXEN RADIOLIB_NC
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#endif
// Wire Interfaces
// -------------------
#define WIRE_INTERFACES_COUNT 1 // 2
// Sense version has IMU and PDM Mic
// #define XIAO_SENSE
#ifndef XIAO_SENSE
// 6 DoF IMU
#define PIN_LSM6DS3TR_C_POWER (15)
#define PIN_LSM6DS3TR_C_INT1 (18)
// PDM Interfaces
// ---------------
#define PIN_PDM_PWR (19)
#define PIN_PDM_CLK (20)
#define PIN_PDM_DIN (21)
#endif
// QSPI Pins
// ---------
#define PIN_QSPI_SCK (24)
#define PIN_QSPI_CS (25)
#define PIN_QSPI_IO0 (26)
#define PIN_QSPI_IO1 (27)
#define PIN_QSPI_IO2 (28)
#define PIN_QSPI_IO3 (29)
// On-board QSPI Flash
// -------------------
#define EXTERNAL_FLASH_DEVICES P25Q16H
#define EXTERNAL_FLASH_USE_QSPI
// Battery
// -------
// P0_14 = 14 Reads battery voltage from divider on signal board.
// PIN_VBAT is reading voltage divider on XIAO and is program pin 32 / or P0.31
#define ADC_CTRL VBAT_ENABLE
#define ADC_CTRL_ENABLED LOW
#define BATTERY_SENSE_RESOLUTION_BITS 10
#define CHARGE_LED 23 // P0_17 = 17 D23 YELLOW CHARGE LED
#define HICHG 22 // P0_13 = 13 D22 Charge-select pin for Lipo for 100 mA instead of default 50mA charge
// The battery sense is hooked to pin A0 (5)
#define BATTERY_PIN PIN_VBAT // PIN_A0
// ratio of voltage divider = 3.0 (R17=1M, R18=510k)
#define ADC_MULTIPLIER 3 // 3.0 + a bit for being optimistic
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
@@ -6,8 +6,7 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
-D PRIVATE_HW
-DEBYTE_E22
-DEBYTE_E22_900M30S
-USEEED_XIAO_NRF52840_KIT ; remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m33s]
@@ -17,5 +16,4 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
-D PRIVATE_HW
-DEBYTE_E22
-DEBYTE_E22_900M33S
-USEEED_XIAO_NRF52840_KIT ; remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define
build_unflags = -DGPS_L76K
+3 -49
View File
@@ -2,55 +2,9 @@
[env:xiao_ble]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
-D PRIVATE_HW
-DXIAO_BLE_LEGACY_PINOUT
-DEBYTE_E22
-USEEED_XIAO_NRF52840_KIT ; remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893
[env:xiao_ble_30db]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DPRIVATE_HW ; Define private hardware
-DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout
-DEBYTE_E22_900M30S ; Set 30db module
-USEEED_XIAO_NRF52840_KIT ; Remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893
[env:xiao_ble_33db]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DPRIVATE_HW ; Define private hardware
-DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout
-DEBYTE_E22_900M33S ; Set 33db module
-USEEED_XIAO_NRF52840_KIT ; Remove default HWID
-USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
-DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
@@ -13,22 +13,21 @@ extends = nrf52840_base
board = xiao_ble_sense
board_level = pr
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/seeed_xiao_nrf52840_kit
-I src/platform/nrf52/softdevice
-I src/platform/nrf52/softdevice/nrf52
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DSEEED_XIAO_NRF52840_KIT
-DSEEED_XIAO_NRF_KIT_DEFAULT
-DGPS_L76K
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
; Seeed Xiao BLE but with GPS moved to NFC pins, and therefore i2c active
; Seeed Xiao BLE but with GPS undefined, and therefore i2c active
[env:seeed_xiao_nrf52840_kit_i2c]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
-DSEEED_XIAO_NRF52840_KIT
-DSEEED_XIAO_NRF_KIT_I2C ; Define I2C variant
-USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define
build_unflags = -DGPS_L76K
@@ -17,37 +17,6 @@
extern "C" {
#endif // __cplusplus
/*
Xiao pin assignments
| Pin | Default | I2C | BTB | BLE-L | | Pin | Default | I2C | BTB | BLE-L |
| ----- | -------- | ---- | ---- | ----- | --- | ----- | ------- | ---- | ---- | ----- |
| | | | | | | | | | | |
| D0 | G_STBY | UBTN | DIO1 | CS | | 5v | | | | |
| D1 | DIO1 | DIO1 | Busy | DIO1 | | GND | | | | |
| D2 | NRST | NRST | NRST | Busy | | 3v3 | | | | |
| D3 | Busy | Busy | CS | NRST | | D10 | MOSI | MOSI | MOSI | MOSI |
| D4 | CS | CS | RXEN | SDA | | D9 | MISO | MISO | MISO | MISO |
| D5 | RXEN | RXEN | | SCL | | D8 | SCK | SCK | SCK | SCK |
| D6 | G_TX | SDA | G_TX | | | D7 | G_RX | SCL | G_RX | RXEN |
| | | | | | | | | | | |
| | End | | | | | | | | | |
| NFC1/ | SDA | G_TX | SDA | G_TX | | NFC2/ | SCL | G_RX | SCL | G_RX |
| D30 | | | | | | D31 | | | | |
| | | | | | | | | | | |
| | Internal | | | | | | | | | |
| D16 | SCL1 | SCL1 | SCL1 | SCL1 | | | | | | |
| D17 | SDA1 | SDA1 | SDA1 | SDA1 | | | | | | |
The default column shows the pin assignments for the Wio-SX1262 for XIAO
(standalone SKU 113010003 or nRF52840 kit SKU 102010710).
The I2C column shows an alternative pin assignment using I2C on D6/D7 in place of the GNSS.
The BTB column shows the pin assignment for the Wio-SX1262 -30-pin board-to-board connector version from the ESP32S3 kit.
The BLE-L column shows the pin assignment for the original DIY xiao_ble, and which is retained for legacy users.
Note that the in addition to the difference between the default and the I2C pinouts in placing the pins on NFC or
D6/D7, the user button is activated on D0. The button conflicts with the official GNSS module, so caution is advised.
*/
#define PINS_COUNT (33)
#define NUM_DIGITAL_PINS (33)
#define NUM_ANALOG_INPUTS (8)
@@ -122,15 +91,15 @@ static const uint8_t A5 = PIN_A5;
*/
#define USE_SX1262
#if defined(XIAO_BLE_LEGACY_PINOUT)
#ifdef XIAO_BLE_LEGACY_PINOUT
// Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22
#define SX126X_CS D0
#define SX126X_DIO1 D1
#define SX126X_BUSY D2
#define SX126X_RESET D3
#define SX126X_RXEN D7
#else
#if defined(SEEED_XIAO_NRF_WIO_BTB)
#elif defined(SEEED_XIAO_WIO_BTB)
// Wio-SX1262 for XIAO with 30-pin board-to-board connector
// https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Schematic_Diagram_Wio-SX1262_for_XIAO.pdf
#define SX126X_CS D3
@@ -140,15 +109,13 @@ static const uint8_t A5 = PIN_A5;
#define SX126X_RXEN D4
#else
// Wio-SX1262 for XIAO (standalone SKU 113010003 or nRF52840 kit SKU 102010710)
// Same for both default and I2C pinouts
// https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Wio-SX1262%20for%20XIAO%20V1.0_SCH.pdf
#define SX126X_CS D4
#define SX126X_DIO1 D1
#define SX126X_BUSY D3
#define SX126X_RESET D2
#define SX126X_RXEN D5
#endif // defined(SEEED_XIAO_NRF_WIO_BTB)
#endif // defined(XIAO_BLE_LEGACY_PINOUT)
#endif
// Common pinouts for all SX126x pinouts above
#define SX126X_TXEN RADIOLIB_NC
@@ -174,26 +141,18 @@ static const uint8_t SCK = PIN_SPI_SCK;
* GPS
*/
// GPS L76K
// Default GPS L76K
#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB)
#define GPS_L76K
#ifdef GPS_L76K
#define GPS_TX_PIN D6 // This is data from the MCU
#define GPS_RX_PIN D7 // This is data from the GNSS module
#if defined(SEEED_XIAO_NRF_KIT_DEFAULT)
#define PIN_GPS_STANDBY D0 // this is where the conflicting pinouts come from
#endif
// I2C and BLE-Legacy put them on the NFC pins
#else
#define GPS_TX_PIN (30)
#define GPS_RX_PIN (31)
#endif
#define HAS_GPS 1
#define GPS_BAUDRATE 9600
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_TX GPS_TX_PIN
#define PIN_SERIAL1_RX GPS_RX_PIN
#define PIN_GPS_STANDBY D0
#else
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
#endif
/*
* Battery
@@ -212,58 +171,37 @@ static const uint8_t SCK = PIN_SPI_SCK;
* Wire Interfaces
* Keep this section after potentially conflicting pin definitions
*/
#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much
#define WIRE_INTERFACES_COUNT 1 // changed to 1 for now, as LSM6DS3TR has issues.
#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much
#define WIRE_INTERFACES_COUNT 1
#if defined(XIAO_BLE_LEGACY_PINOUT)
// Used for I2C by DIY xiao_ble variant
#define PIN_WIRE_SDA D4
#define PIN_WIRE_SCL D5
#else
// Put the I2C pins on the NFC pins by default
#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB)
#define PIN_WIRE_SDA 30
#define PIN_WIRE_SCL 31
#else
// If not on legacy or defauly, we're wanting I2C on the back pins
#elif !defined(GPS_L76K)
// If D6 and D7 are free, I2C is probably the most versatile assignment
#define PIN_WIRE_SDA D6
#define PIN_WIRE_SCL D7
#endif // defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB)
#endif // defined(XIAO_BLE_LEGACY_PINOUT)
#else
// Internal LSM6DS3TR on XIAO nRF52840 Series
#define PIN_WIRE_SDA (17)
#define PIN_WIRE_SCL (16)
#endif
// // Internal LSM6DS3TR on XIAO nRF52840 Series - put it on wire1
// // Note: disabled for now, as there are some issues with the LSM.
// #define PIN_WIRE1_SDA (17)
// #define PIN_WIRE1_SCL (16)
static const uint8_t SDA = PIN_WIRE_SDA; // Not sure if this is needed
static const uint8_t SCL = PIN_WIRE_SCL; // Not sure if this is needed
// // QSPI Pins
// // ---------
// #define PIN_QSPI_SCK (24)
// #define PIN_QSPI_CS (25)
// #define PIN_QSPI_IO0 (26)
// #define PIN_QSPI_IO1 (27)
// #define PIN_QSPI_IO2 (28)
// #define PIN_QSPI_IO3 (29)
// // On-board QSPI Flash
// // -------------------
// #define EXTERNAL_FLASH_DEVICES P25Q16H
// #define EXTERNAL_FLASH_USE_QSPI
static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;
/*
* Buttons
* Keep this section after potentially conflicting pin definitions
* because D0 has multiple possible conflicts with various XIAO modules:
* - PIN_GPS_STANDBY on the L76K GNSS Module
* - DIO1 on the Wio-SX1262 - 30-pin board-to-board connector version
* - SX1262X CS on XIAO BLE legacy pinout
*/
#if defined(SEEED_XIAO_NRF_KIT_I2C)
#define BUTTON_PIN D0
#endif
#if defined(SEEED_XIAO_NRF_WIO_BTB)
#define BUTTON_PIN D5
#if !defined(GPS_L76K) && !defined(SEEED_XIAO_WIO_BTB) && !defined(XIAO_BLE_LEGACY_PINOUT)
#define BUTTON_PIN D0
#endif
#ifdef __cplusplus

Some files were not shown because too many files have changed in this diff Show More