Compare commits

...

10 Commits

Author SHA1 Message Date
648331b854 Initial commit 2026-02-26 16:31:54 +03:00
liamcottle
678b1e3aa7 add support for heltec v4 2025-11-11 23:39:41 +13:00
liamcottle
02c8dcfc9d update model code variables 2025-01-19 10:15:53 +13:00
liamcottle
499efb89ba add support for lilygo t3s3 2.4ghz sx1280 2025-01-18 01:21:59 +13:00
liamcottle
73a9c02ada add ability to recondition display 2025-01-07 01:36:08 +13:00
liamcottle
880b4a1126 add ability to configure display rotation 2025-01-07 00:06:36 +13:00
liamcottle
f73fe58b14 improve bluetooth pairing instructions 2025-01-06 03:58:18 +13:00
liamcottle
fe9d0df1be don't log 2025-01-06 03:43:28 +13:00
liamcottle
d44338bc76 close existing rnode connections before opening another 2025-01-06 03:42:42 +13:00
liamcottle
74e02504b4 show bluetooth pin when available 2025-01-06 03:24:06 +13:00
2 changed files with 1099 additions and 673 deletions

1524
index.html

File diff suppressed because it is too large Load Diff

View File

@@ -79,6 +79,16 @@ class RNode {
ROM_UNLOCK_BYTE = 0xF8;
CMD_HASHES = 0x60;
CMD_FW_UPD = 0x61;
CMD_DISP_ROT = 0x67;
CMD_DISP_INT = 0x45;
CMD_DISP_BLNK = 0x64;
CMD_DISP_RCND = 0x68;
CMD_WIFI_MODE = 0x6A;
CMD_WIFI_SSID = 0x6B;
CMD_WIFI_PSK = 0x6C;
CMD_WIFI_CHN = 0x6E;
CMD_WIFI_IP = 0x84;
CMD_WIFI_NM = 0x85;
CMD_BT_CTRL = 0x46;
CMD_BT_PIN = 0x62;
@@ -144,14 +154,14 @@ class RNode {
try {
this.reader.releaseLock();
} catch(e) {
console.log("failed to release lock on serial port readable, ignoring...", e);
//console.log("failed to release lock on serial port readable, ignoring...", e);
}
// close serial port
try {
await this.serialPort.close();
} catch(e) {
console.log("failed to close serial port, ignoring...", e);
//console.log("failed to close serial port, ignoring...", e);
}
}
@@ -608,6 +618,185 @@ class RNode {
}
async enableWiFiMode(wifiMode) {
await this.sendKissCommand([
this.CMD_WIFI_MODE,
wifiMode, // 0x00 = OFF, 0x01 = AP, 0x02 = STATION
]);
}
async disableWiFiMode() {
await this.sendKissCommand([
this.CMD_WIFI_MODE,
0x00, // OFF
]);
}
async setWiFiChannel(wifiChannel) {
await this.sendKissCommand([
this.CMD_WIFI_CHN,
wifiChannel, // 1-14
]);
}
async setWiFiSSID(wifiSSID) {
const encoder = new TextEncoder();
const ssidBytes = encoder.encode(wifiSSID);
// Add null terminator
const data = new Uint8Array(ssidBytes.length + 1);
data.set(ssidBytes);
data[data.length - 1] = 0x00;
// KISS escape
const escaped = [];
for (const byte of data) {
if (byte === this.FEND) {
escaped.push(this.FESC, this.TFEND);
} else if (byte === this.FESC) {
escaped.push(this.FESC, this.TFESC);
} else {
escaped.push(byte);
}
}
// Send command
await this.sendKissCommand([
this.CMD_WIFI_SSID,
...escaped,
]);
}
async setWiFiPSK(wifiPSK) {
if (wifiPSK == null) {
// Clear PSK
await this.sendKissCommand([
this.CMD_WIFI_PSK,
0x00
]);
return;
}
const encoder = new TextEncoder();
const pskBytes = encoder.encode(wifiPSK);
// Enforce firmware length rules (832 characters)
if (pskBytes.length < 8 || pskBytes.length > 32) {
throw new Error("Invalid PSK length (must be 832 bytes)");
}
// Add null terminator
const data = new Uint8Array(pskBytes.length + 1);
data.set(pskBytes);
data[data.length - 1] = 0x00;
// KISS escape
const escaped = [];
for (const byte of data) {
if (byte === this.FEND) {
escaped.push(this.FESC, this.TFEND);
} else if (byte === this.FESC) {
escaped.push(this.FESC, this.TFESC);
} else {
escaped.push(byte);
}
}
// Send command
await this.sendKissCommand([
this.CMD_WIFI_PSK,
...escaped,
]);
}
async setWiFiIP(wifiIP) {
if (wifiIP == null) {
// Clear IP → 0.0.0.0
await this.sendKissCommand([
this.CMD_WIFI_IP,
0x00, 0x00, 0x00, 0x00
]);
return;
}
if (typeof wifiIP !== "string") {
throw new TypeError("Invalid IP address (not a string)");
}
const octets = wifiIP.split(".");
if (octets.length !== 4) {
throw new Error("Invalid IP address length");
}
const ipBytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
const octet = Number(octets[i]);
if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
throw new Error("Invalid IP octet value");
}
ipBytes[i] = octet;
}
// KISS escape (same as PSK logic)
const escaped = [];
for (const byte of ipBytes) {
if (byte === this.KISS_FEND) {
escaped.push(this.KISS_FESC, this.KISS_TFEND);
} else if (byte === this.KISS_FESC) {
escaped.push(this.KISS_FESC, this.KISS_TFESC);
} else {
escaped.push(byte);
}
}
// Send command exactly like PSK does
await this.sendKissCommand([
this.CMD_WIFI_IP,
...escaped,
]);
}
async setWiFiNM(wifiNM) {
if (wifiNM == null) {
// Clear netmask → 0.0.0.0
await this.sendKissCommand([
this.CMD_WIFI_NM,
0x00, 0x00, 0x00, 0x00
]);
return;
}
if (typeof wifiNM !== "string") {
throw new TypeError("Invalid netmask (not a string)");
}
const octets = wifiNM.split(".");
if (octets.length !== 4) {
throw new Error("Invalid netmask length");
}
const nmBytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
const octet = Number(octets[i]);
if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
throw new Error("Invalid netmask octet value");
}
nmBytes[i] = octet;
}
// Send using same KISS pipeline as PSK/IP
await this.sendKissCommand([
this.CMD_WIFI_NM,
...nmBytes
]);
}
async readDisplay() {
const response = await this.sendCommand(this.CMD_DISP_READ, [
@@ -750,6 +939,34 @@ class RNode {
return new ROM(rom);
}
async setDisplayRotation(rotation) {
await this.sendKissCommand([
this.CMD_DISP_ROT,
rotation & 0xFF,
]);
}
async setDisplayIntensity(intensity) {
await this.sendKissCommand([
this.CMD_DISP_INT,
intensity & 0xFF,
]);
}
async setDisplayTimeout(timeout) {
await this.sendKissCommand([
this.CMD_DISP_BLNK,
timeout & 0xFF,
]);
}
async startDisplayReconditioning() {
await this.sendKissCommand([
this.CMD_DISP_RCND,
0x01,
]);
}
}
class ROM {
@@ -778,6 +995,7 @@ class ROM {
static MODEL_A7 = 0xA7
static MODEL_A5 = 0xA5;
static MODEL_AA = 0xAA;
static MODEL_AC = 0xAC;
static PRODUCT_T32_10 = 0xB2
static MODEL_BA = 0xBA
@@ -801,6 +1019,9 @@ class ROM {
static MODEL_C5 = 0xC5
static MODEL_CA = 0xCA
static PRODUCT_H32_V4 = 0xC3
static MODEL_C8 = 0xC8
static PRODUCT_HELTEC_T114 = 0xC2
static MODEL_C6 = 0xC6
static MODEL_C7 = 0xC7
@@ -820,8 +1041,8 @@ class ROM {
static MODEL_D9 = 0xD9;
static PRODUCT_TECHO = 0x15;
static MODEL_T4 = 0x16;
static MODEL_T9 = 0x17;
static MODEL_16 = 0x16;
static MODEL_17 = 0x17;
static PRODUCT_HMBRW = 0xF0
static MODEL_FF = 0xFF
@@ -835,12 +1056,29 @@ class ROM {
static ADDR_CHKSUM = 0x0B
static ADDR_SIGNATURE = 0x1B
static ADDR_INFO_LOCK = 0x9B
static ADDR_CONF_SF = 0x9C
static ADDR_CONF_CR = 0x9D
static ADDR_CONF_TXP = 0x9E
static ADDR_CONF_BW = 0x9F
static ADDR_CONF_FREQ = 0xA3
static ADDR_CONF_OK = 0xA7
static ADDR_CONF_BT = 0xB0
static ADDR_CONF_DSET = 0xB1
static ADDR_CONF_DINT = 0xB2
static ADDR_CONF_DADR = 0xB3
static ADDR_CONF_DBLK = 0xB4
static ADDR_CONF_DROT = 0xB8
static ADDR_CONF_PSET = 0xB5
static ADDR_CONF_PINT = 0xB6
static ADDR_CONF_BSET = 0xB7
static ADDR_CONF_DIA = 0xB9
static ADDR_CONF_WIFI = 0xBA
static ADDR_CONF_WCHN = 0xBB
static ADDR_CONF_SSID = 0x00
static ADDR_CONF_PSK = 0x21
static ADDR_CONF_IP = 0x42
static ADDR_CONF_NM = 0x46
static INFO_LOCK_BYTE = 0x73
static CONF_OK_BYTE = 0x73