Files
RNode_Flasher/js/rnode.js
2026-04-11 11:53:06 +03:00

1238 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class Utils {
/**
* Waits for the provided milliseconds, and then resolves.
* @param millis
* @returns {Promise<void>}
*/
static async sleepMillis(millis) {
await new Promise((resolve) => {
setTimeout(resolve, millis);
});
}
static bytesToHex(bytes) {
for(var hex = [], i = 0; i < bytes.length; i++){
var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
hex.push((current >>> 4).toString(16));
hex.push((current & 0xF).toString(16));
}
return hex.join("");
}
static md5(data) {
var bytes = [];
const hash = CryptoJS.MD5(CryptoJS.enc.Hex.parse(this.bytesToHex(data)));
for(var i = 0; i < hash.sigBytes; i++){
bytes.push((hash.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return bytes;
}
static packUInt32BE(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, value, false);
return new Uint8Array(buffer);
}
static unpackUInt32BE(byteArray) {
const buffer = new Uint8Array(byteArray).buffer;
const view = new DataView(buffer);
return view.getUint32(0, false);
}
}
class RNode {
KISS_FEND = 0xC0;
KISS_FESC = 0xDB;
KISS_TFEND = 0xDC;
KISS_TFESC = 0xDD;
CMD_FREQUENCY = 0x01;
CMD_BANDWIDTH = 0x02;
CMD_TXPOWER = 0x03;
CMD_SF = 0x04;
CMD_CR = 0x05;
CMD_RADIO_STATE = 0x06;
CMD_STAT_RX = 0x21;
CMD_STAT_TX = 0x22
CMD_STAT_RSSI = 0x23;
CMD_STAT_SNR = 0x24;
CMD_BOARD = 0x47;
CMD_PLATFORM = 0x48;
CMD_MCU = 0x49;
CMD_RESET = 0x55;
CMD_RESET_BYTE = 0xF8;
CMD_DEV_HASH = 0x56;
CMD_FW_VERSION = 0x50;
CMD_ROM_READ = 0x51;
CMD_ROM_WRITE = 0x52;
CMD_CONF_SAVE = 0x53;
CMD_CONF_DELETE = 0x54;
CMD_FW_HASH = 0x58;
CMD_UNLOCK_ROM = 0x59;
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;
CMD_DISP_READ = 0x66;
CMD_DETECT = 0x08;
DETECT_REQ = 0x73;
DETECT_RESP = 0x46;
RADIO_STATE_OFF = 0x00;
RADIO_STATE_ON = 0x01;
RADIO_STATE_ASK = 0xFF;
CMD_ERROR = 0x90
ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90;
PLATFORM_ESP32 = 0x80;
PLATFORM_NRF52 = 0x70;
MCU_1284P = 0x91;
MCU_2560 = 0x92;
MCU_ESP32 = 0x81;
MCU_NRF52 = 0x71;
BOARD_RNODE = 0x31;
BOARD_HMBRW = 0x32;
BOARD_TBEAM = 0x33;
BOARD_HUZZAH32 = 0x34;
BOARD_GENERIC_ESP32 = 0x35;
BOARD_LORA32_V2_0 = 0x36;
BOARD_LORA32_V2_1 = 0x37;
BOARD_RAK4631 = 0x51;
BOARD_XIAO_S3 = 0x3E;
HASH_TYPE_TARGET_FIRMWARE = 0x01;
HASH_TYPE_FIRMWARE = 0x02;
constructor(serialPort) {
this.serialPort = serialPort;
this.reader = serialPort.readable.getReader();
this.writable = serialPort.writable;
this.callbacks = {};
this.readLoop();
}
static async fromSerialPort(serialPort) {
// open port
await serialPort.open({
baudRate: 115200,
});
return new RNode(serialPort);
}
async close() {
// release reader lock
try {
this.reader.releaseLock();
} catch(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);
}
}
async write(bytes) {
const writer = this.writable.getWriter();
try {
await writer.write(new Uint8Array(bytes));
} finally {
writer.releaseLock();
}
}
async readLoop() {
try {
let buffer = [];
let inFrame = false;
while(true){
// read kiss frames until reader indicates it's done
const { value, done } = await this.reader.read();
if(done){
break;
}
// read kiss frames
for(const byte of value){
if(byte === this.KISS_FEND){
if(inFrame){
// End of frame
const decodedFrame = this.decodeKissFrame(buffer);
if(decodedFrame){
this.onCommandReceived(decodedFrame);
} else {
console.warn("Invalid frame ignored.");
}
buffer = [];
}
inFrame = !inFrame;
} else if(inFrame) {
buffer.push(byte);
}
}
}
} catch(error) {
// ignore error if reader was released
if(error instanceof TypeError){
return;
}
console.error('Error reading from serial port: ', error);
} finally {
this.reader.releaseLock();
}
}
onCommandReceived(data) {
try {
// get received command and bytes from data
const [ command, ...bytes ] = data;
console.log("onCommandReceived", "0x" + command.toString(16), bytes);
// find callback for received command
const callback = this.callbacks[command];
if(!callback){
return;
}
// fire callback
callback(bytes);
// forget callback
delete this.callbacks[command];
} catch(e) {
console.log("failed to handle received command", data, e);
}
}
decodeKissFrame(frame) {
const data = [];
let escaping = false;
for(const byte of frame){
if(escaping){
if(byte === this.KISS_TFEND){
data.push(this.KISS_FEND);
} else if(byte === this.KISS_TFESC) {
data.push(this.KISS_FESC);
} else {
return null; // Invalid escape sequence
}
escaping = false;
} else if(byte === this.KISS_FESC) {
escaping = true;
} else {
data.push(byte);
}
}
// return null if incomplete escape at end
return escaping ? null : data;
}
createKissFrame(data) {
let frame = [this.KISS_FEND];
for(let byte of data){
if(byte === this.KISS_FEND){
frame.push(this.KISS_FESC, this.KISS_TFEND);
} else if(byte === this.KISS_FESC){
frame.push(this.KISS_FESC, this.KISS_TFESC);
} else {
frame.push(byte);
}
}
frame.push(this.KISS_FEND);
return new Uint8Array(frame);
}
async sendKissCommand(data) {
await this.write(this.createKissFrame(data));
}
// sends a command to the rnode, and resolves the promise with the result
async sendCommand(command, data) {
return new Promise(async (resolve, reject) => {
try {
// listen for response
this.callbacks[command] = (response) => {
resolve(response);
};
// send command
await this.sendKissCommand([
command,
...data,
]);
} catch(e) {
reject(e);
}
});
}
async reset() {
await this.sendKissCommand([
this.CMD_RESET,
this.CMD_RESET_BYTE,
]);
}
async detect() {
return new Promise(async (resolve) => {
try {
// timeout after provided millis
const timeout = setTimeout(() => {
resolve(false);
}, 2000);
// detect rnode
const response = await this.sendCommand(this.CMD_DETECT, [
this.DETECT_REQ,
]);
// we no longer want to timeout
clearTimeout(timeout);
// device is an rnode if response is as expected
const [ responseByte ] = response;
const isRnode = responseByte === this.DETECT_RESP;
resolve(isRnode);
} catch(e) {
resolve(false);
}
});
}
async getFirmwareVersion() {
const response = await this.sendCommand(this.CMD_FW_VERSION, [
0x00,
]);
// read response from device
var [ majorVersion, minorVersion ] = response;
if(minorVersion.length === 1){
minorVersion = "0" + minorVersion;
}
// 1.23
return majorVersion + "." + minorVersion;
}
async getPlatform() {
const response = await this.sendCommand(this.CMD_PLATFORM, [
0x00,
]);
// read response from device
const [ platformByte ] = response;
return platformByte;
}
async getMcu() {
const response = await this.sendCommand(this.CMD_MCU, [
0x00,
]);
// read response from device
const [ mcuByte ] = response;
return mcuByte;
}
async getBoard() {
const response = await this.sendCommand(this.CMD_BOARD, [
0x00,
]);
// read response from device
const [ boardByte ] = response;
return boardByte;
}
async getDeviceHash() {
const response = await this.sendCommand(this.CMD_DEV_HASH, [
0x01, // anything != 0x00
]);
// read response from device
const [ ...deviceHash ] = response;
return deviceHash;
}
async getTargetFirmwareHash() {
const response = await this.sendCommand(this.CMD_HASHES, [
this.HASH_TYPE_TARGET_FIRMWARE,
]);
// read response from device
const [ hashType, ...targetFirmwareHash ] = response;
return targetFirmwareHash;
}
async getFirmwareHash() {
const response = await this.sendCommand(this.CMD_HASHES, [
this.HASH_TYPE_FIRMWARE,
]);
// read response from device
const [ hashType, ...firmwareHash ] = response;
return firmwareHash;
}
async getRom() {
const response = await this.sendCommand(this.CMD_ROM_READ, [
0x00,
]);
// read response from device
const [ ...eepromBytes ] = response;
return eepromBytes;
}
async getFrequency() {
const response = await this.sendCommand(this.CMD_FREQUENCY, [
// request frequency by sending zero as 4 bytes
0x00,
0x00,
0x00,
0x00,
]);
// read response from device
const [ ...frequencyBytes ] = response;
// convert 4 bytes to 32bit integer representing frequency in hertz
const frequencyInHz = frequencyBytes[0] << 24 | frequencyBytes[1] << 16 | frequencyBytes[2] << 8 | frequencyBytes[3];
return frequencyInHz;
}
async getBandwidth() {
const response = await this.sendCommand(this.CMD_BANDWIDTH, [
// request bandwidth by sending zero as 4 bytes
0x00,
0x00,
0x00,
0x00,
]);
// read response from device
const [ ...bandwidthBytes ] = response;
// convert 4 bytes to 32bit integer representing bandwidth in hertz
const bandwidthInHz = bandwidthBytes[0] << 24 | bandwidthBytes[1] << 16 | bandwidthBytes[2] << 8 | bandwidthBytes[3];
return bandwidthInHz;
}
async getTxPower() {
const response = await this.sendCommand(this.CMD_TXPOWER, [
0xFF, // request tx power
]);
// read response from device
const [ txPower ] = response;
return txPower;
}
async getSpreadingFactor() {
const response = await this.sendCommand(this.CMD_SF, [
0xFF, // request spreading factor
]);
// read response from device
const [ spreadingFactor ] = response;
return spreadingFactor;
}
async getCodingRate() {
const response = await this.sendCommand(this.CMD_CR, [
0xFF, // request coding rate
]);
// read response from device
const [ codingRate ] = response;
return codingRate;
}
async getRadioState() {
const response = await this.sendCommand(this.CMD_RADIO_STATE, [
0xFF, // request radio state
]);
// read response from device
const [ radioState ] = response;
return radioState;
}
async getRxStat() {
const response = await this.sendCommand(this.CMD_STAT_RX, [
0x00,
]);
// read response from device
const [ ...statBytes ] = response;
// convert 4 bytes to 32bit integer
const stat = statBytes[0] << 24 | statBytes[1] << 16 | statBytes[2] << 8 | statBytes[3];
return stat;
}
async getTxStat() {
const response = await this.sendCommand(this.CMD_STAT_TX, [
0x00,
]);
// read response from device
const [ ...statBytes ] = response;
// convert 4 bytes to 32bit integer
const stat = statBytes[0] << 24 | statBytes[1] << 16 | statBytes[2] << 8 | statBytes[3];
return stat;
}
async getRssiStat() {
const response = await this.sendCommand(this.CMD_STAT_RSSI, [
0x00,
]);
// read response from device
const [ rssi ] = response;
return rssi;
}
async disableBluetooth() {
await this.sendKissCommand([
this.CMD_BT_CTRL,
0x00, // stop
]);
}
async enableBluetooth() {
await this.sendKissCommand([
this.CMD_BT_CTRL,
0x01, // start
]);
}
async startBluetoothPairing(pinCallback) {
// listen for bluetooth pin
// pin will be available once the user has initiated pairing from an Android device
this.callbacks[this.CMD_BT_PIN] = (response) => {
// read response from device
const [ ...pinBytes ] = response;
// convert 4 bytes to 32bit integer
const pin = pinBytes[0] << 24 | pinBytes[1] << 16 | pinBytes[2] << 8 | pinBytes[3];
// tell user what the bluetooth pin is
console.log("Bluetooth Pairing Pin: " + pin);
pinCallback(pin);
};
// enable pairing
await this.sendKissCommand([
this.CMD_BT_CTRL,
0x02, // enable pairing
]);
}
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 null → clear IP to 0.0.0.0
if (wifiIP == null) {
await this.sendKissCommand([
this.CMD_WIFI_IP,
0x00, 0x00, 0x00, 0x00
]);
return;
}
// Ensure it's a string
if (typeof wifiIP !== "string") {
throw new TypeError("Invalid IP address (not a string)");
}
// Split into octets
const parts = wifiIP.trim().split(".");
if (parts.length !== 4) {
throw new Error("Invalid IP address format");
}
// Convert to byte array
const ipBytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
const value = Number(parts[i]);
if (!Number.isInteger(value) || value < 0 || value > 255) {
throw new Error(`Invalid IP octet: ${parts[i]}`);
}
ipBytes[i] = value;
}
// Optional debug check
console.log("Setting WiFi IP bytes:", [...ipBytes]);
// Send command (no manual escaping)
await this.sendKissCommand([
this.CMD_WIFI_IP,
...ipBytes
]);
}
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, [
0x01,
]);
// read response from device
const [ ...displayBuffer ] = response;
return displayBuffer;
}
async setFrequency(frequencyInHz) {
const c1 = frequencyInHz >> 24;
const c2 = frequencyInHz >> 16 & 0xFF;
const c3 = frequencyInHz >> 8 & 0xFF;
const c4 = frequencyInHz & 0xFF;
await this.sendKissCommand([
this.CMD_FREQUENCY,
c1,
c2,
c3,
c4,
]);
}
async setBandwidth(bandwidthInHz) {
const c1 = bandwidthInHz >> 24;
const c2 = bandwidthInHz >> 16 & 0xFF;
const c3 = bandwidthInHz >> 8 & 0xFF;
const c4 = bandwidthInHz & 0xFF;
await this.sendKissCommand([
this.CMD_BANDWIDTH,
c1,
c2,
c3,
c4,
]);
}
async setTxPower(db) {
await this.sendKissCommand([
this.CMD_TXPOWER,
db,
]);
}
async setSpreadingFactor(spreadingFactor) {
await this.sendKissCommand([
this.CMD_SF,
spreadingFactor,
]);
}
async setCodingRate(codingRate) {
await this.sendKissCommand([
this.CMD_CR,
codingRate,
]);
}
async setRadioStateOn() {
await this.sendKissCommand([
this.CMD_RADIO_STATE,
this.RADIO_STATE_ON,
]);
}
async setRadioStateOff() {
await this.sendKissCommand([
this.CMD_RADIO_STATE,
this.RADIO_STATE_OFF,
]);
}
// setTNCMode
async saveConfig() {
await this.sendKissCommand([
this.CMD_CONF_SAVE,
0x00,
]);
}
// setNormalMode
async deleteConfig() {
await this.sendKissCommand([
this.CMD_CONF_DELETE,
0x00,
]);
}
async indicateFirmwareUpdate() {
await this.sendKissCommand([
this.CMD_FW_UPD,
0x01,
]);
}
async setFirmwareHash(hash) {
await this.sendKissCommand([
this.CMD_FW_HASH,
...hash,
]);
}
async writeRom(address, value) {
// write to rom
await this.sendKissCommand([
this.CMD_ROM_WRITE,
address,
value,
]);
// wait a bit to allow device to write to rom
await Utils.sleepMillis(85);
}
async wipeRom() {
await this.sendKissCommand([
this.CMD_UNLOCK_ROM,
this.ROM_UNLOCK_BYTE,
]);
// wiping can take up to 30 seconds
await Utils.sleepMillis(30000);
}
async getRomAsObject() {
const rom = await this.getRom();
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 {
static PLATFORM_AVR = 0x90
static PLATFORM_ESP32 = 0x80
static PLATFORM_NRF52 = 0x70
static MCU_1284P = 0x91
static MCU_2560 = 0x92
static MCU_ESP32 = 0x81
static MCU_NRF52 = 0x71
static PRODUCT_RAK4631 = 0x10
static MODEL_11 = 0x11
static MODEL_12 = 0x12
static PRODUCT_RNODE = 0x03
static MODEL_A1 = 0xA1
static MODEL_A6 = 0xA6
static MODEL_A4 = 0xA4
static MODEL_A9 = 0xA9
static MODEL_A3 = 0xA3
static MODEL_A8 = 0xA8
static MODEL_A2 = 0xA2
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
static MODEL_BB = 0xBB
static PRODUCT_T32_20 = 0xB0
static MODEL_B3 = 0xB3
static MODEL_B8 = 0xB8
static PRODUCT_T32_21 = 0xB1
static MODEL_B4 = 0xB4
static MODEL_B9 = 0xB9
static MODEL_B4_TCXO = 0x04 // The TCXO model codes are only used here to select the
static MODEL_B9_TCXO = 0x09 // correct firmware, actual model codes in firmware is still 0xB4 and 0xB9.
static PRODUCT_H32_V2 = 0xC0
static MODEL_C4 = 0xC4
static MODEL_C9 = 0xC9
static PRODUCT_H32_V3 = 0xC1
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
static PRODUCT_TBEAM = 0xE0
static MODEL_E4 = 0xE4
static MODEL_E9 = 0xE9
static MODEL_E3 = 0xE3
static MODEL_E8 = 0xE8
static PRODUCT_TBEAM_S_V1 = 0xEA;
static MODEL_DB = 0xDB
static MODEL_DC = 0xDC
static PRODUCT_TDECK = 0xD0;
static MODEL_D4 = 0xD4;
static MODEL_D9 = 0xD9;
static PRODUCT_TECHO = 0x15;
static MODEL_16 = 0x16;
static MODEL_17 = 0x17;
static PRODUCT_XIAO_S3 = 0xEB;
static MODEL_DE = 0xDE;
static MODEL_DD = 0xDD;
static PRODUCT_HMBRW = 0xF0
static MODEL_FF = 0xFF
static MODEL_FE = 0xFE
static ADDR_PRODUCT = 0x00
static ADDR_MODEL = 0x01
static ADDR_HW_REV = 0x02
static ADDR_SERIAL = 0x03
static ADDR_MADE = 0x07
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
static BOARD_RNODE = 0x31
static BOARD_HMBRW = 0x32
static BOARD_TBEAM = 0x33
static BOARD_HUZZAH32 = 0x34
static BOARD_GENERIC_ESP32 = 0x35
static BOARD_LORA32_V2_0 = 0x36
static BOARD_LORA32_V2_1 = 0x37
static BOARD_RAK4631 = 0x51
static MANUAL_FLASH_MODELS = [ROM.MODEL_A1, ROM.MODEL_A6]
constructor(eeprom) {
this.eeprom = eeprom;
}
getProduct() {
return this.eeprom[ROM.ADDR_PRODUCT];
}
getModel() {
return this.eeprom[ROM.ADDR_MODEL];
}
getHardwareRevision() {
return this.eeprom[ROM.ADDR_HW_REV];
}
getSerialNumber() {
return [
this.eeprom[ROM.ADDR_SERIAL],
this.eeprom[ROM.ADDR_SERIAL + 1],
this.eeprom[ROM.ADDR_SERIAL + 2],
this.eeprom[ROM.ADDR_SERIAL + 3],
];
}
getMade() {
return [
this.eeprom[ROM.ADDR_MADE],
this.eeprom[ROM.ADDR_MADE + 1],
this.eeprom[ROM.ADDR_MADE + 2],
this.eeprom[ROM.ADDR_MADE + 3],
];
}
getChecksum() {
const checksum = [];
for(var i = 0; i < 16; i++){
checksum.push(this.eeprom[ROM.ADDR_CHKSUM + i]);
}
return checksum;
}
getSignature() {
const signature = [];
for(var i = 0; i < 128; i++){
signature.push(this.eeprom[ROM.ADDR_SIGNATURE + i]);
}
return signature;
}
getCalculatedChecksum() {
return Utils.md5([
this.getProduct(),
this.getModel(),
this.getHardwareRevision(),
...this.getSerialNumber(),
...this.getMade(),
]);
}
getConfiguredSpreadingFactor() {
return this.eeprom[ROM.ADDR_CONF_SF];
}
getConfiguredCodingRate() {
return this.eeprom[ROM.ADDR_CONF_CR];
}
getConfiguredTxPower() {
return this.eeprom[ROM.ADDR_CONF_TXP];
}
getConfiguredFrequency() {
return this.eeprom[ROM.ADDR_CONF_FREQ] << 24
| this.eeprom[ROM.ADDR_CONF_FREQ + 1] << 16
| this.eeprom[ROM.ADDR_CONF_FREQ + 2] << 8
| this.eeprom[ROM.ADDR_CONF_FREQ + 3];
}
getConfiguredBandwidth() {
return this.eeprom[ROM.ADDR_CONF_BW] << 24
| this.eeprom[ROM.ADDR_CONF_BW + 1] << 16
| this.eeprom[ROM.ADDR_CONF_BW + 2] << 8
| this.eeprom[ROM.ADDR_CONF_BW + 3];
}
isInfoLocked() {
return this.eeprom[ROM.ADDR_INFO_LOCK] === ROM.INFO_LOCK_BYTE;
}
isConfigured() {
return this.eeprom[ROM.ADDR_CONF_OK] === ROM.CONF_OK_BYTE;
}
parse() {
// ensure info lock byte is set
if(!this.isInfoLocked()){
return null;
}
// convert to hex
const checksumHex = Utils.bytesToHex(this.getChecksum());
const calculatedChecksumHex = Utils.bytesToHex(this.getCalculatedChecksum());
const signatureHex = Utils.bytesToHex(this.getSignature());
// add details
var details = {
is_provisioned: true,
is_configured: this.isConfigured(),
product: this.getProduct(),
model: this.getModel(),
hardware_revision: this.getHardwareRevision(),
serial_number: Utils.unpackUInt32BE(this.getSerialNumber()),
made: Utils.unpackUInt32BE(this.getMade()),
checksum: checksumHex,
calculated_checksum: calculatedChecksumHex,
signature: signatureHex,
}
// if configured, add configuration to details
if(details.is_configured){
details = {
...details,
configured_spreading_factor: this.getConfiguredSpreadingFactor(),
configured_coding_rate: this.getConfiguredCodingRate(),
configured_tx_power: this.getConfiguredTxPower(),
configured_frequency: this.getConfiguredFrequency(),
configured_bandwidth: this.getConfiguredBandwidth(),
};
}
// if checksum in eeprom does not match checksum calculated from info, it is not provisioned
if(details.checksum !== details.calculated_checksum){
details.is_provisioned = false;
}
return details;
}
}