Initial commit: RNodeTHV4 boundary mode firmware for Heltec V4
Bridges LoRa mesh and TCP/WiFi backbone networks using microReticulum. Based on microReticulum_Firmware with boundary mode additions: - BoundaryMode.h: State management and EEPROM persistence - BoundaryConfig.h: WiFi captive portal for configuration - TcpInterface.h: TCP backbone interface with HDLC framing - Display.h: Custom OLED layout with network status indicators - Transport/Identity library patches for embedded memory constraints
11
.gitignore
vendored
Executable file
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
*.hex
|
||||
*.pyc
|
||||
TODO
|
||||
Release/*.hex
|
||||
Release/*.zip
|
||||
Release/*.json
|
||||
Console/build
|
||||
build/*
|
||||
.pio/*
|
||||
.vscode/*
|
||||
171
BLESerial.cpp
Executable file
@@ -0,0 +1,171 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Boards.h"
|
||||
|
||||
#if PLATFORM != PLATFORM_NRF52
|
||||
#if HAS_BLE
|
||||
|
||||
#include "BLESerial.h"
|
||||
|
||||
uint32_t bt_passkey_callback();
|
||||
void bt_passkey_notify_callback(uint32_t passkey);
|
||||
bool bt_security_request_callback();
|
||||
void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result);
|
||||
bool bt_confirm_pin_callback(uint32_t pin);
|
||||
void bt_connect_callback(BLEServer *server);
|
||||
void bt_disconnect_callback(BLEServer *server);
|
||||
bool bt_client_authenticated();
|
||||
|
||||
uint32_t BLESerial::onPassKeyRequest() { return bt_passkey_callback(); }
|
||||
void BLESerial::onPassKeyNotify(uint32_t passkey) { bt_passkey_notify_callback(passkey); }
|
||||
bool BLESerial::onSecurityRequest() { return bt_security_request_callback(); }
|
||||
void BLESerial::onAuthenticationComplete(esp_ble_auth_cmpl_t auth_result) { bt_authentication_complete_callback(auth_result); }
|
||||
void BLESerial::onConnect(BLEServer *server) { bt_connect_callback(server); }
|
||||
void BLESerial::onDisconnect(BLEServer *server) { bt_disconnect_callback(server); ble_server->startAdvertising(); }
|
||||
bool BLESerial::onConfirmPIN(uint32_t pin) { return bt_confirm_pin_callback(pin); };
|
||||
bool BLESerial::connected() { return ble_server->getConnectedCount() > 0; }
|
||||
|
||||
int BLESerial::read() {
|
||||
int result = this->rx_buffer.pop();
|
||||
if (result == '\n') { this->numAvailableLines--; }
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t BLESerial::readBytes(uint8_t *buffer, size_t bufferSize) {
|
||||
int i = 0;
|
||||
while (i < bufferSize && available()) { buffer[i] = (uint8_t)this->rx_buffer.pop(); i++; }
|
||||
return i;
|
||||
}
|
||||
|
||||
int BLESerial::peek() {
|
||||
if (this->rx_buffer.getLength() == 0) return -1;
|
||||
return this->rx_buffer.get(0);
|
||||
}
|
||||
|
||||
int BLESerial::available() { return this->rx_buffer.getLength(); }
|
||||
|
||||
size_t BLESerial::print(const char *str) {
|
||||
if (ble_server->getConnectedCount() <= 0) return 0;
|
||||
size_t written = 0; for (size_t i = 0; str[i] != '\0'; i++) { written += this->write(str[i]); }
|
||||
flush();
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
size_t BLESerial::write(const uint8_t *buffer, size_t bufferSize) {
|
||||
if (ble_server->getConnectedCount() <= 0) { return 0; } else {
|
||||
size_t written = 0; for (int i = 0; i < bufferSize; i++) { written += this->write(buffer[i]); }
|
||||
flush();
|
||||
|
||||
return written;
|
||||
}
|
||||
}
|
||||
|
||||
size_t BLESerial::write(uint8_t byte) {
|
||||
if (bt_client_authenticated()) {
|
||||
if (ble_server->getConnectedCount() <= 0) { return 0; } else {
|
||||
this->transmitBuffer[this->transmitBufferLength] = byte;
|
||||
this->transmitBufferLength++;
|
||||
if (this->transmitBufferLength == maxTransferSize) { flush(); }
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BLESerial::flush() {
|
||||
if (this->transmitBufferLength > 0) {
|
||||
TxCharacteristic->setValue(this->transmitBuffer, this->transmitBufferLength);
|
||||
this->transmitBufferLength = 0;
|
||||
this->lastFlushTime = millis();
|
||||
TxCharacteristic->notify(true);
|
||||
}
|
||||
}
|
||||
|
||||
void BLESerial::disconnect() {
|
||||
if (ble_server->getConnectedCount() > 0) {
|
||||
uint16_t conn_id = ble_server->getConnId();
|
||||
// Serial.printf("Have connected: %d\n", conn_id);
|
||||
ble_server->disconnect(conn_id);
|
||||
// Serial.println("Disconnected");
|
||||
} else {
|
||||
// Serial.println("No connected");
|
||||
}
|
||||
}
|
||||
|
||||
void BLESerial::begin(const char *name) {
|
||||
ConnectedDeviceCount = 0;
|
||||
BLEDevice::init(name);
|
||||
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN ,ESP_PWR_LVL_P9);
|
||||
|
||||
ble_server = BLEDevice::createServer();
|
||||
ble_server->setCallbacks(this);
|
||||
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM);
|
||||
BLEDevice::setSecurityCallbacks(this);
|
||||
|
||||
SetupSerialService();
|
||||
this->startAdvertising();
|
||||
}
|
||||
|
||||
void BLESerial::startAdvertising() {
|
||||
ble_adv = BLEDevice::getAdvertising();
|
||||
ble_adv->addServiceUUID(BLE_SERIAL_SERVICE_UUID);
|
||||
ble_adv->setMinPreferred(0x20);
|
||||
ble_adv->setMaxPreferred(0x40);
|
||||
ble_adv->setScanResponse(true);
|
||||
ble_adv->start();
|
||||
}
|
||||
|
||||
void BLESerial::stopAdvertising() {
|
||||
ble_adv = BLEDevice::getAdvertising();
|
||||
ble_adv->stop();
|
||||
}
|
||||
|
||||
void BLESerial::end() { BLEDevice::deinit(); }
|
||||
|
||||
void BLESerial::onWrite(BLECharacteristic *characteristic) {
|
||||
if (characteristic->getUUID().toString() == BLE_RX_UUID) {
|
||||
auto value = characteristic->getValue();
|
||||
for (int i = 0; i < value.length(); i++) { rx_buffer.push(value[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
void BLESerial::SetupSerialService() {
|
||||
SerialService = ble_server->createService(BLE_SERIAL_SERVICE_UUID);
|
||||
|
||||
RxCharacteristic = SerialService->createCharacteristic(BLE_RX_UUID, BLECharacteristic::PROPERTY_WRITE);
|
||||
RxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
|
||||
RxCharacteristic->addDescriptor(new BLE2902());
|
||||
RxCharacteristic->setWriteProperty(true);
|
||||
RxCharacteristic->setCallbacks(this);
|
||||
|
||||
TxCharacteristic = SerialService->createCharacteristic(BLE_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
|
||||
TxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
|
||||
TxCharacteristic->addDescriptor(new BLE2902());
|
||||
TxCharacteristic->setNotifyProperty(true);
|
||||
TxCharacteristic->setReadProperty(true);
|
||||
|
||||
SerialService->start();
|
||||
}
|
||||
|
||||
BLESerial::BLESerial() { }
|
||||
|
||||
#endif
|
||||
#endif
|
||||
136
BLESerial.h
Executable file
@@ -0,0 +1,136 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Boards.h"
|
||||
|
||||
#if PLATFORM != PLATFORM_NRF52
|
||||
#if HAS_BLE
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLE2902.h>
|
||||
|
||||
template <size_t n>
|
||||
class BLEFIFO {
|
||||
private:
|
||||
uint8_t buffer[n];
|
||||
int head = 0;
|
||||
int tail = 0;
|
||||
|
||||
public:
|
||||
void push(uint8_t value) {
|
||||
buffer[head] = value;
|
||||
head = (head + 1) % n;
|
||||
if (head == tail) { tail = (tail + 1) % n; }
|
||||
}
|
||||
|
||||
int pop() {
|
||||
if (head == tail) {
|
||||
return -1;
|
||||
} else {
|
||||
uint8_t value = buffer[tail];
|
||||
tail = (tail + 1) % n;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() { head = 0; tail = 0; }
|
||||
|
||||
int get(size_t index) {
|
||||
if (index >= this->getLength()) {
|
||||
return -1;
|
||||
} else {
|
||||
return buffer[(tail + index) % n];
|
||||
}
|
||||
}
|
||||
|
||||
size_t getLength() {
|
||||
if (head >= tail) {
|
||||
return head - tail;
|
||||
} else {
|
||||
return n - tail + head;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define RX_BUFFER_SIZE 6144
|
||||
#define BLE_BUFFER_SIZE 512 // Must fit in max GATT attribute length
|
||||
#define MIN_MTU 50
|
||||
|
||||
class BLESerial : public BLECharacteristicCallbacks, public BLEServerCallbacks, public BLESecurityCallbacks, public Stream {
|
||||
public:
|
||||
BLESerial();
|
||||
|
||||
void begin(const char *name);
|
||||
void end();
|
||||
void disconnect();
|
||||
void startAdvertising();
|
||||
void stopAdvertising();
|
||||
void onWrite(BLECharacteristic *characteristic);
|
||||
int available();
|
||||
int peek();
|
||||
int read();
|
||||
size_t readBytes(uint8_t *buffer, size_t bufferSize);
|
||||
size_t write(uint8_t byte);
|
||||
size_t write(const uint8_t *buffer, size_t bufferSize);
|
||||
size_t print(const char *value);
|
||||
void flush();
|
||||
void onConnect(BLEServer *server);
|
||||
void onDisconnect(BLEServer *server);
|
||||
|
||||
uint32_t onPassKeyRequest();
|
||||
void onPassKeyNotify(uint32_t passkey);
|
||||
bool onSecurityRequest();
|
||||
void onAuthenticationComplete(esp_ble_auth_cmpl_t);
|
||||
bool onConfirmPIN(uint32_t pin);
|
||||
|
||||
bool connected();
|
||||
|
||||
BLEServer *ble_server;
|
||||
BLEAdvertising *ble_adv;
|
||||
BLEService *SerialService;
|
||||
BLECharacteristic *TxCharacteristic;
|
||||
BLECharacteristic *RxCharacteristic;
|
||||
size_t transmitBufferLength;
|
||||
unsigned long long lastFlushTime;
|
||||
|
||||
private:
|
||||
BLESerial(BLESerial const &other) = delete;
|
||||
void operator=(BLESerial const &other) = delete;
|
||||
|
||||
BLEFIFO<RX_BUFFER_SIZE> rx_buffer;
|
||||
size_t numAvailableLines;
|
||||
uint8_t transmitBuffer[BLE_BUFFER_SIZE];
|
||||
|
||||
int ConnectedDeviceCount;
|
||||
void SetupSerialService();
|
||||
|
||||
uint16_t peerMTU;
|
||||
uint16_t maxTransferSize = BLE_BUFFER_SIZE;
|
||||
|
||||
bool checkMTU();
|
||||
|
||||
const char *BLE_SERIAL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
|
||||
const char *BLE_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
|
||||
const char *BLE_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
|
||||
|
||||
bool started = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
615
Bluetooth.h
Executable file
@@ -0,0 +1,615 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
#endif
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
#if HAS_BLUETOOTH == true
|
||||
#include "BluetoothSerial.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
BluetoothSerial SerialBT;
|
||||
#elif HAS_BLE == true
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "BLESerial.h"
|
||||
BLESerial SerialBT;
|
||||
#endif
|
||||
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
#include <bluefruit.h>
|
||||
#include <math.h>
|
||||
#define BLE_RX_BUF 6144
|
||||
BLEUart SerialBT(BLE_RX_BUF);
|
||||
BLEDis bledis;
|
||||
BLEBas blebas;
|
||||
bool SerialBT_init = false;
|
||||
#endif
|
||||
|
||||
#define BT_PAIRING_TIMEOUT 35000
|
||||
#define BLE_FLUSH_TIMEOUT 20
|
||||
uint32_t bt_pairing_started = 0;
|
||||
|
||||
#define BT_DEV_ADDR_LEN 6
|
||||
#define BT_DEV_HASH_LEN 16
|
||||
uint8_t dev_bt_mac[BT_DEV_ADDR_LEN];
|
||||
char bt_da[BT_DEV_ADDR_LEN];
|
||||
char bt_dh[BT_DEV_HASH_LEN];
|
||||
char bt_devname[11];
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
#if HAS_BLUETOOTH == true
|
||||
|
||||
void bt_confirm_pairing(uint32_t numVal) {
|
||||
bt_ssp_pin = numVal;
|
||||
kiss_indicate_btpin();
|
||||
if (bt_allow_pairing) {
|
||||
SerialBT.confirmReply(true);
|
||||
} else {
|
||||
SerialBT.confirmReply(false);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_stop() {
|
||||
display_unblank();
|
||||
if (bt_state != BT_STATE_OFF) {
|
||||
SerialBT.end();
|
||||
bt_allow_pairing = false;
|
||||
bt_state = BT_STATE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_start() {
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) {
|
||||
SerialBT.begin(bt_devname);
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_enable_pairing() {
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) bt_start();
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
}
|
||||
|
||||
void bt_disable_pairing() {
|
||||
display_unblank();
|
||||
bt_allow_pairing = false;
|
||||
bt_ssp_pin = 0;
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
|
||||
void bt_pairing_complete(boolean success) {
|
||||
display_unblank();
|
||||
if (success) {
|
||||
bt_disable_pairing();
|
||||
} else {
|
||||
bt_ssp_pin = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_connection_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
|
||||
display_unblank();
|
||||
if(event == ESP_SPP_SRV_OPEN_EVT) {
|
||||
bt_state = BT_STATE_CONNECTED;
|
||||
cable_state = CABLE_STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
if(event == ESP_SPP_CLOSE_EVT ){
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_setup_hw() {
|
||||
if (!bt_ready) {
|
||||
if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) {
|
||||
bt_enabled = true;
|
||||
} else {
|
||||
bt_enabled = false;
|
||||
}
|
||||
if (btStart()) {
|
||||
if (esp_bluedroid_init() == ESP_OK) {
|
||||
if (esp_bluedroid_enable() == ESP_OK) {
|
||||
const uint8_t* bda_ptr = esp_bt_dev_get_address();
|
||||
char *data = (char*)malloc(BT_DEV_ADDR_LEN+1);
|
||||
for (int i = 0; i < BT_DEV_ADDR_LEN; i++) {
|
||||
data[i] = bda_ptr[i];
|
||||
}
|
||||
data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE));
|
||||
unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN);
|
||||
memcpy(bt_dh, hash, BT_DEV_HASH_LEN);
|
||||
sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]);
|
||||
free(data);
|
||||
|
||||
SerialBT.enableSSP();
|
||||
SerialBT.onConfirmRequest(bt_confirm_pairing);
|
||||
SerialBT.onAuthComplete(bt_pairing_complete);
|
||||
SerialBT.register_callback(bt_connection_callback);
|
||||
|
||||
bt_ready = true;
|
||||
return true;
|
||||
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
}
|
||||
|
||||
bool bt_init() {
|
||||
bt_state = BT_STATE_OFF;
|
||||
if (bt_setup_hw()) {
|
||||
if (bt_enabled && !console_active) bt_start();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void update_bt() {
|
||||
if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) {
|
||||
bt_disable_pairing();
|
||||
}
|
||||
}
|
||||
|
||||
#elif HAS_BLE == true
|
||||
bool bt_setup_hw(); void bt_security_setup();
|
||||
BLESecurity *ble_security = new BLESecurity();
|
||||
bool ble_authenticated = false;
|
||||
uint32_t pairing_pin = 0;
|
||||
|
||||
void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flush(); } }
|
||||
|
||||
void bt_start() {
|
||||
// Serial.println("BT start");
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) {
|
||||
bt_state = BT_STATE_ON;
|
||||
SerialBT.begin(bt_devname);
|
||||
SerialBT.setTimeout(10);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_stop() {
|
||||
// Serial.println("BT stop");
|
||||
display_unblank();
|
||||
if (bt_state != BT_STATE_OFF) {
|
||||
bt_allow_pairing = false;
|
||||
bt_state = BT_STATE_OFF;
|
||||
SerialBT.end();
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_init() {
|
||||
// Serial.println("BT init");
|
||||
bt_state = BT_STATE_OFF;
|
||||
if (bt_setup_hw()) {
|
||||
if (bt_enabled && !console_active) bt_start();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_debond_all() {
|
||||
// Serial.println("Debonding all");
|
||||
int dev_num = esp_ble_get_bond_device_num();
|
||||
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
|
||||
esp_ble_get_bond_device_list(&dev_num, dev_list);
|
||||
for (int i = 0; i < dev_num; i++) { esp_ble_remove_bond_device(dev_list[i].bd_addr); }
|
||||
free(dev_list);
|
||||
}
|
||||
|
||||
void bt_enable_pairing() {
|
||||
// Serial.println("BT enable pairing");
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) bt_start();
|
||||
|
||||
bt_security_setup();
|
||||
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
bt_ssp_pin = pairing_pin;
|
||||
}
|
||||
|
||||
void bt_disable_pairing() {
|
||||
// Serial.println("BT disable pairing");
|
||||
display_unblank();
|
||||
bt_allow_pairing = false;
|
||||
bt_ssp_pin = 0;
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
|
||||
void bt_passkey_notify_callback(uint32_t passkey) {
|
||||
// Serial.printf("Got passkey notification: %d\n", passkey);
|
||||
if (bt_allow_pairing) {
|
||||
bt_ssp_pin = passkey;
|
||||
bt_pairing_started = millis();
|
||||
kiss_indicate_btpin();
|
||||
} else {
|
||||
// Serial.println("Pairing not allowed, re-init");
|
||||
SerialBT.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_confirm_pin_callback(uint32_t pin) {
|
||||
// Serial.printf("Confirm PIN callback: %d\n", pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
void bt_update_passkey() {
|
||||
// Serial.println("Updating passkey");
|
||||
pairing_pin = random(899999)+100000;
|
||||
bt_ssp_pin = pairing_pin;
|
||||
}
|
||||
|
||||
uint32_t bt_passkey_callback() {
|
||||
// Serial.println("API passkey request");
|
||||
if (pairing_pin == 0) { bt_update_passkey(); }
|
||||
return pairing_pin;
|
||||
}
|
||||
|
||||
bool bt_client_authenticated() {
|
||||
return ble_authenticated;
|
||||
}
|
||||
|
||||
bool bt_security_request_callback() {
|
||||
if (bt_allow_pairing) {
|
||||
// Serial.println("Accepting security request");
|
||||
return true;
|
||||
} else {
|
||||
// Serial.println("Rejecting security request");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result) {
|
||||
if (auth_result.success == true) {
|
||||
// Serial.println("Authentication success");
|
||||
ble_authenticated = true;
|
||||
if (bt_state == BT_STATE_PAIRING) {
|
||||
// Serial.println("Pairing complete, disconnecting");
|
||||
delay(2000); SerialBT.disconnect();
|
||||
} else { bt_state = BT_STATE_CONNECTED; }
|
||||
} else {
|
||||
// Serial.println("Authentication fail");
|
||||
ble_authenticated = false;
|
||||
bt_state = BT_STATE_ON;
|
||||
bt_update_passkey();
|
||||
bt_security_setup();
|
||||
}
|
||||
bt_allow_pairing = false;
|
||||
bt_ssp_pin = 0;
|
||||
}
|
||||
|
||||
void bt_connect_callback(BLEServer *server) {
|
||||
uint16_t conn_id = server->getConnId();
|
||||
// Serial.printf("Connected: %d\n", conn_id);
|
||||
display_unblank();
|
||||
ble_authenticated = false;
|
||||
if (bt_state != BT_STATE_PAIRING) { bt_state = BT_STATE_CONNECTED; }
|
||||
cable_state = CABLE_STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
void bt_disconnect_callback(BLEServer *server) {
|
||||
uint16_t conn_id = server->getConnId();
|
||||
// Serial.printf("Disconnected: %d\n", conn_id);
|
||||
display_unblank();
|
||||
ble_authenticated = false;
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
|
||||
bool bt_setup_hw() {
|
||||
// Serial.println("BT setup hw");
|
||||
if (!bt_ready) {
|
||||
if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) {
|
||||
bt_enabled = true;
|
||||
} else {
|
||||
bt_enabled = false;
|
||||
}
|
||||
if (btStart()) {
|
||||
if (esp_bluedroid_init() == ESP_OK) {
|
||||
if (esp_bluedroid_enable() == ESP_OK) {
|
||||
const uint8_t* bda_ptr = esp_bt_dev_get_address();
|
||||
char *data = (char*)malloc(BT_DEV_ADDR_LEN+1);
|
||||
for (int i = 0; i < BT_DEV_ADDR_LEN; i++) {
|
||||
data[i] = bda_ptr[i];
|
||||
}
|
||||
data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE));
|
||||
unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN);
|
||||
memcpy(bt_dh, hash, BT_DEV_HASH_LEN);
|
||||
sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]);
|
||||
free(data);
|
||||
|
||||
bt_security_setup();
|
||||
|
||||
bt_ready = true;
|
||||
return true;
|
||||
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
}
|
||||
|
||||
void bt_security_setup() {
|
||||
// Serial.println("Executing BT security setup");
|
||||
if (pairing_pin == 0) { bt_update_passkey(); }
|
||||
uint32_t passkey = pairing_pin;
|
||||
// Serial.printf("Passkey is %d\n", passkey);
|
||||
|
||||
uint8_t key_size = 16;
|
||||
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
|
||||
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE;
|
||||
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
|
||||
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT;
|
||||
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
void update_bt() {
|
||||
if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) {
|
||||
bt_disable_pairing();
|
||||
}
|
||||
if (bt_state == BT_STATE_CONNECTED && millis()-SerialBT.lastFlushTime >= BLE_FLUSH_TIMEOUT) {
|
||||
if (SerialBT.transmitBufferLength > 0) {
|
||||
bt_flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
uint32_t pairing_pin = 0;
|
||||
|
||||
uint8_t eeprom_read(uint32_t mapped_addr);
|
||||
|
||||
void bt_stop() {
|
||||
// Serial.println("BT Stop");
|
||||
if (bt_state != BT_STATE_OFF) {
|
||||
bt_allow_pairing = false;
|
||||
bt_state = BT_STATE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flushTXD(); } }
|
||||
|
||||
void bt_disable_pairing() {
|
||||
// Serial.println("BT Disable pairing");
|
||||
bt_allow_pairing = false;
|
||||
pairing_pin = 0;
|
||||
bt_ssp_pin = 0;
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
|
||||
void bt_pairing_complete(uint16_t conn_handle, uint8_t auth_status) {
|
||||
// Serial.println("BT pairing complete");
|
||||
BLEConnection* connection = Bluefruit.Connection(conn_handle);
|
||||
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
|
||||
ble_gap_conn_sec_mode_t security = connection->getSecureMode();
|
||||
// Serial.println("Bonding success");
|
||||
|
||||
// On the NRF52 it is not possible with the Arduino library to reject
|
||||
// requests from devices with no IO capabilities, which would allow
|
||||
// bypassing pin entry through pairing using the "just works" mode.
|
||||
// Therefore, we must check the security level of the connection after
|
||||
// pairing to ensure "just works" has not been used. If it has, we need
|
||||
// to disconnect, unpair and delete any bonding information immediately.
|
||||
// Settings on the SerialBT service should prevent unauthorised access to
|
||||
// the serial port anyway, but this is still wise to do regardless.
|
||||
//
|
||||
// Note: It may be nice to have this done in the BLESecurity class in the
|
||||
// future, but as it stands right now I'd have to fork the BSP to do
|
||||
// that, which I don't fancy doing. Impact on security is likely minimal.
|
||||
// Requires investigation.
|
||||
|
||||
if (security.sm == 1 && security.lv >= 3) {
|
||||
// Serial.println("Auth level success");
|
||||
bt_state = BT_STATE_CONNECTED;
|
||||
cable_state = CABLE_STATE_DISCONNECTED;
|
||||
connection->disconnect();
|
||||
bt_disable_pairing();
|
||||
} else {
|
||||
// Serial.println("Auth level failure, debonding");
|
||||
if (connection->bonded()) { connection->removeBondKey(); }
|
||||
connection->disconnect();
|
||||
bt_disable_pairing();
|
||||
}
|
||||
} else {
|
||||
// Serial.println("Bonding failure");
|
||||
connection->disconnect();
|
||||
bt_disable_pairing();
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) {
|
||||
// Serial.println("Passkey callback");
|
||||
if (bt_allow_pairing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void bt_connect_callback(uint16_t conn_handle) {
|
||||
// Serial.println("Connect callback");
|
||||
bt_state = BT_STATE_CONNECTED;
|
||||
cable_state = CABLE_STATE_DISCONNECTED;
|
||||
|
||||
BLEConnection* conn = Bluefruit.Connection(conn_handle);
|
||||
conn->requestPHY(BLE_GAP_PHY_2MBPS);
|
||||
conn->requestMtuExchange(512+3);
|
||||
conn->requestDataLengthUpdate();
|
||||
}
|
||||
|
||||
void bt_disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
// Serial.println("Disconnect callback");
|
||||
if (reason != BLE_GAP_SEC_STATUS_SUCCESS) {
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_update_passkey() {
|
||||
// Serial.println("Update passkey");
|
||||
pairing_pin = random(899999)+100000;
|
||||
bt_ssp_pin = pairing_pin;
|
||||
}
|
||||
|
||||
uint32_t bt_get_passkey() {
|
||||
// Serial.println("API passkey request");
|
||||
if (pairing_pin == 0) { bt_update_passkey(); }
|
||||
return pairing_pin;
|
||||
}
|
||||
|
||||
bool bt_setup_hw() {
|
||||
// Serial.println("Setup HW");
|
||||
if (!bt_ready) {
|
||||
#if HAS_EEPROM
|
||||
if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) {
|
||||
#else
|
||||
if (eeprom_read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) {
|
||||
#endif
|
||||
bt_enabled = true;
|
||||
} else {
|
||||
bt_enabled = false;
|
||||
}
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.autoConnLed(false);
|
||||
if (Bluefruit.begin()) {
|
||||
uint32_t pin = bt_get_passkey();
|
||||
char pin_char[6];
|
||||
sprintf(pin_char,"%lu", pin);
|
||||
|
||||
Bluefruit.setTxPower(8); // Check bluefruit.h for supported values
|
||||
Bluefruit.Security.setIOCaps(true, false, false); // display, yes; yes / no, no; keyboard, no
|
||||
// This device is indeed capable of yes / no through the pairing mode
|
||||
// being set, but I have chosen to set it thus to force the input of the
|
||||
// pin on the device initiating the pairing.
|
||||
|
||||
Bluefruit.Security.setMITM(true);
|
||||
Bluefruit.Security.setPairPasskeyCallback(bt_passkey_callback);
|
||||
Bluefruit.Security.setSecuredCallback(bt_connect_callback);
|
||||
Bluefruit.Security.setPIN(pin_char);
|
||||
Bluefruit.Periph.setDisconnectCallback(bt_disconnect_callback);
|
||||
Bluefruit.Security.setPairCompleteCallback(bt_pairing_complete);
|
||||
Bluefruit.Periph.setConnInterval(6, 12); // 7.5 - 15 ms
|
||||
|
||||
const ble_gap_addr_t gap_addr = Bluefruit.getAddr();
|
||||
char *data = (char*)malloc(BT_DEV_ADDR_LEN+1);
|
||||
for (int i = 0; i < BT_DEV_ADDR_LEN; i++) {
|
||||
data[i] = gap_addr.addr[i];
|
||||
}
|
||||
#if HAS_EEPROM
|
||||
data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE));
|
||||
#else
|
||||
data[BT_DEV_ADDR_LEN] = eeprom_read(eeprom_addr(ADDR_SIGNATURE));
|
||||
#endif
|
||||
unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN);
|
||||
memcpy(bt_dh, hash, BT_DEV_HASH_LEN);
|
||||
sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]);
|
||||
free(data);
|
||||
|
||||
bt_ready = true;
|
||||
return true;
|
||||
|
||||
} else { return false; }
|
||||
} else { return false; }
|
||||
}
|
||||
|
||||
void bt_start() {
|
||||
// Serial.println("BT Start");
|
||||
if (bt_state == BT_STATE_OFF) {
|
||||
Bluefruit.setName(bt_devname);
|
||||
bledis.setManufacturer(BLE_MANUFACTURER);
|
||||
bledis.setModel(BLE_MODEL);
|
||||
// start device information service
|
||||
bledis.begin();
|
||||
blebas.begin();
|
||||
|
||||
// Guard to ensure SerialBT service is not duplicated through BT being power cycled
|
||||
if (!SerialBT_init) {
|
||||
SerialBT.bufferTXD(true); // enable buffering
|
||||
|
||||
SerialBT.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // enable encryption for BLE serial
|
||||
SerialBT.begin();
|
||||
SerialBT_init = true;
|
||||
}
|
||||
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
|
||||
// Include bleuart 128-bit uuid
|
||||
Bluefruit.Advertising.addService(SerialBT);
|
||||
|
||||
// There is no room for Name in Advertising packet
|
||||
// Use Scan response for Name
|
||||
Bluefruit.ScanResponse.addName();
|
||||
|
||||
Bluefruit.Advertising.start(0);
|
||||
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_init() {
|
||||
// Serial.println("BT init");
|
||||
bt_state = BT_STATE_OFF;
|
||||
if (bt_setup_hw()) {
|
||||
if (bt_enabled && !console_active) bt_start();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_enable_pairing() {
|
||||
// Serial.println("BT enable pairing");
|
||||
if (bt_state == BT_STATE_OFF) bt_start();
|
||||
|
||||
uint32_t pin = bt_get_passkey();
|
||||
char pin_char[6];
|
||||
sprintf(pin_char,"%lu", pin);
|
||||
Bluefruit.Security.setPIN(pin_char);
|
||||
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
kiss_indicate_btpin();
|
||||
}
|
||||
|
||||
void bt_debond_all() { }
|
||||
|
||||
void update_bt() {
|
||||
if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) {
|
||||
bt_disable_pairing();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
938
Boards.h
Executable file
@@ -0,0 +1,938 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Modem.h"
|
||||
|
||||
#ifndef BOARDS_H
|
||||
#define BOARDS_H
|
||||
|
||||
#define PLATFORM_AVR 0x90
|
||||
#define PLATFORM_ESP32 0x80
|
||||
#define PLATFORM_NRF52 0x70
|
||||
|
||||
#define MCU_1284P 0x91
|
||||
#define MCU_2560 0x92
|
||||
#define MCU_ESP32 0x81
|
||||
#define MCU_NRF52 0x71
|
||||
|
||||
// Products, boards and models ////
|
||||
#define PRODUCT_RNODE 0x03 // RNode devices
|
||||
#define BOARD_RNODE 0x31 // Original v1.0 RNode
|
||||
#define MODEL_A4 0xA4 // RNode v1.0, 433 MHz
|
||||
#define MODEL_A9 0xA9 // RNode v1.0, 868 MHz
|
||||
|
||||
#define BOARD_RNODE_NG_20 0x40 // RNode hardware revision v2.0
|
||||
#define MODEL_A3 0xA3 // RNode v2.0, 433 MHz
|
||||
#define MODEL_A8 0xA8 // RNode v2.0, 868 MHz
|
||||
|
||||
#define BOARD_RNODE_NG_21 0x41 // RNode hardware revision v2.1
|
||||
#define MODEL_A2 0xA2 // RNode v2.1, 433 MHz
|
||||
#define MODEL_A7 0xA7 // RNode v2.1, 868 MHz
|
||||
|
||||
#define BOARD_T3S3 0x42 // T3S3 devices
|
||||
#define MODEL_A1 0xA1 // T3S3, 433 MHz with SX1268
|
||||
#define MODEL_A5 0xA5 // T3S3, 433 MHz with SX1278
|
||||
#define MODEL_A6 0xA6 // T3S3, 868 MHz with SX1262
|
||||
#define MODEL_AA 0xAA // T3S3, 868 MHz with SX1276
|
||||
#define MODEL_AC 0xAC // T3S3, 2.4 GHz with SX1280 and PA
|
||||
|
||||
#define PRODUCT_TBEAM 0xE0 // T-Beam devices
|
||||
#define BOARD_TBEAM 0x33
|
||||
#define MODEL_E4 0xE4 // T-Beam SX1278, 433 Mhz
|
||||
#define MODEL_E9 0xE9 // T-Beam SX1276, 868 Mhz
|
||||
#define MODEL_E3 0xE3 // T-Beam SX1268, 433 Mhz
|
||||
#define MODEL_E8 0xE8 // T-Beam SX1262, 868 Mhz
|
||||
|
||||
#define PRODUCT_TDECK_V1 0xD0
|
||||
#define BOARD_TDECK 0x3B
|
||||
#define MODEL_D4 0xD4 // LilyGO T-Deck, 433 MHz
|
||||
#define MODEL_D9 0xD9 // LilyGO T-Deck, 868 MHz
|
||||
|
||||
#define PRODUCT_TBEAM_S_V1 0xEA
|
||||
#define BOARD_TBEAM_S_V1 0x3D
|
||||
#define MODEL_DB 0xDB // LilyGO T-Beam Supreme, 433 MHz
|
||||
#define MODEL_DC 0xDC // LilyGO T-Beam Supreme, 868 MHz
|
||||
|
||||
#define PRODUCT_XIAO_S3 0xEB
|
||||
#define BOARD_XIAO_S3 0x3E
|
||||
#define MODEL_DE 0xDE // Xiao ESP32S3 with Wio-SX1262 module, 433 MHz
|
||||
#define MODEL_DD 0xDD // Xiao ESP32S3 with Wio-SX1262 module, 868 MHz
|
||||
|
||||
#define PRODUCT_T32_10 0xB2
|
||||
#define BOARD_LORA32_V1_0 0x39
|
||||
#define MODEL_BA 0xBA // LilyGO T3 v1.0, 433 MHz
|
||||
#define MODEL_BB 0xBB // LilyGO T3 v1.0, 868 MHz
|
||||
|
||||
#define PRODUCT_T32_20 0xB0
|
||||
#define BOARD_LORA32_V2_0 0x36
|
||||
#define MODEL_B3 0xB3 // LilyGO T3 v2.0, 433 MHz
|
||||
#define MODEL_B8 0xB8 // LilyGO T3 v2.0, 868 MHz
|
||||
|
||||
#define PRODUCT_T32_21 0xB1
|
||||
#define BOARD_LORA32_V2_1 0x37
|
||||
#define MODEL_B4 0xB4 // LilyGO T3 v2.1, 433 MHz
|
||||
#define MODEL_B9 0xB9 // LilyGO T3 v2.1, 868 MHz
|
||||
|
||||
#define PRODUCT_H32_V2 0xC0 // Board code 0x38
|
||||
#define BOARD_HELTEC32_V2 0x38
|
||||
#define MODEL_C4 0xC4 // Heltec Lora32 v2, 433 MHz
|
||||
#define MODEL_C9 0xC9 // Heltec Lora32 v2, 868 MHz
|
||||
|
||||
#define PRODUCT_H32_V3 0xC1
|
||||
#define BOARD_HELTEC32_V3 0x3A
|
||||
#define MODEL_C5 0xC5 // Heltec Lora32 v3, 433 MHz
|
||||
#define MODEL_CA 0xCA // Heltec Lora32 v3, 868 MHz
|
||||
|
||||
#define PRODUCT_H32_V4 0xC3
|
||||
#define BOARD_HELTEC32_V4 0x3F
|
||||
#define MODEL_C8 0xC8 // Heltec Lora32 v3, 850-950 MHz, 28dBm
|
||||
|
||||
#define PRODUCT_HELTEC_T114 0xC2 // Heltec Mesh Node T114
|
||||
#define BOARD_HELTEC_T114 0x3C
|
||||
#define MODEL_C6 0xC6 // Heltec Mesh Node T114, 470-510 MHz
|
||||
#define MODEL_C7 0xC7 // Heltec Mesh Node T114, 863-928 MHz
|
||||
|
||||
#define PRODUCT_TECHO 0x15 // LilyGO T-Echo devices
|
||||
#define BOARD_TECHO 0x44
|
||||
#define MODEL_16 0x16 // T-Echo 433 MHz
|
||||
#define MODEL_17 0x17 // T-Echo 868/915 MHz
|
||||
|
||||
#define PRODUCT_RAK4631 0x10
|
||||
#define BOARD_RAK4631 0x51
|
||||
#define MODEL_11 0x11 // RAK4631, 433 Mhz
|
||||
#define MODEL_12 0x12 // RAK4631, 868 Mhz
|
||||
|
||||
#define PRODUCT_HMBRW 0xF0
|
||||
#define BOARD_HMBRW 0x32
|
||||
#define BOARD_HUZZAH32 0x34
|
||||
#define BOARD_GENERIC_ESP32 0x35
|
||||
#define BOARD_GENERIC_NRF52 0x50
|
||||
#define MODEL_FE 0xFE // Homebrew board, max 17dBm output power
|
||||
#define MODEL_FF 0xFF // Homebrew board, max 14dBm output power
|
||||
|
||||
#if defined(__AVR_ATmega1284P__)
|
||||
#define PLATFORM PLATFORM_AVR
|
||||
#define MCU_VARIANT MCU_1284P
|
||||
#elif defined(__AVR_ATmega2560__)
|
||||
#define PLATFORM PLATFORM_AVR
|
||||
#define MCU_VARIANT MCU_2560
|
||||
#elif defined(ESP32)
|
||||
#define PLATFORM PLATFORM_ESP32
|
||||
#define MCU_VARIANT MCU_ESP32
|
||||
#elif defined(NRF52840_XXAA)
|
||||
#include <variant.h>
|
||||
#define PLATFORM PLATFORM_NRF52
|
||||
#define MCU_VARIANT MCU_NRF52
|
||||
#else
|
||||
#error "The firmware cannot be compiled for the selected MCU variant"
|
||||
#endif
|
||||
|
||||
#ifndef MODEM
|
||||
#if BOARD_MODEL == BOARD_RAK4631
|
||||
#define MODEM SX1262
|
||||
#elif BOARD_MODEL == BOARD_GENERIC_NRF52
|
||||
#define MODEM SX1262
|
||||
#else
|
||||
#define MODEM SX1276
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define HAS_DISPLAY false
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE false
|
||||
#define HAS_WIFI false
|
||||
#define HAS_TCXO false
|
||||
#define HAS_PMU false
|
||||
#define HAS_NP false
|
||||
#define HAS_EEPROM false
|
||||
#define HAS_INPUT false
|
||||
#define HAS_SLEEP false
|
||||
#define HAS_LORA_PA false
|
||||
#define HAS_LORA_LNA false
|
||||
#define PIN_DISP_SLEEP -1
|
||||
#define VALIDATE_FIRMWARE true
|
||||
|
||||
#if defined(ENABLE_TCXO)
|
||||
#define HAS_TCXO true
|
||||
#endif
|
||||
|
||||
#if MCU_VARIANT == MCU_1284P
|
||||
const int pin_cs = 4;
|
||||
const int pin_reset = 3;
|
||||
const int pin_dio = 2;
|
||||
const int pin_led_rx = 12;
|
||||
const int pin_led_tx = 13;
|
||||
|
||||
#define BOARD_MODEL BOARD_RNODE
|
||||
#define HAS_EEPROM true
|
||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||
#define EEPROM_SIZE 4096
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
|
||||
#elif MCU_VARIANT == MCU_2560
|
||||
const int pin_cs = 5;
|
||||
const int pin_reset = 4;
|
||||
const int pin_dio = 2;
|
||||
const int pin_led_rx = 12;
|
||||
const int pin_led_tx = 13;
|
||||
|
||||
#define BOARD_MODEL BOARD_HMBRW
|
||||
#define HAS_EEPROM true
|
||||
#define CONFIG_UART_BUFFER_SIZE 768
|
||||
#define CONFIG_QUEUE_SIZE 5120
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 24
|
||||
#define EEPROM_SIZE 4096
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
|
||||
#elif MCU_VARIANT == MCU_ESP32
|
||||
|
||||
// Board models for ESP32 based builds are
|
||||
// defined by the build target in the makefile.
|
||||
// If you are not using make to compile this
|
||||
// firmware, you can manually define model here.
|
||||
//
|
||||
// #define BOARD_MODEL BOARD_GENERIC_ESP32
|
||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||
|
||||
#define EEPROM_SIZE 1024
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
#define CONFIG_OFFSET 0
|
||||
|
||||
#define GPS_BAUD_RATE 9600
|
||||
#define PIN_GPS_TX 12
|
||||
#define PIN_GPS_RX 34
|
||||
|
||||
#if BOARD_MODEL == BOARD_GENERIC_ESP32
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 4;
|
||||
const int pin_reset = 33;
|
||||
const int pin_dio = 39;
|
||||
const int pin_led_rx = 14;
|
||||
const int pin_led_tx = 32;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TBEAM
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_PMU true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
#define I2C_SDA 21
|
||||
#define I2C_SCL 22
|
||||
#define PMU_IRQ 35
|
||||
|
||||
#define HAS_INPUT true
|
||||
const int pin_btn_usr1 = 38;
|
||||
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 23;
|
||||
const int pin_led_rx = 2;
|
||||
const int pin_led_tx = 4;
|
||||
|
||||
#if MODEM == SX1262
|
||||
#define HAS_TCXO true
|
||||
#define HAS_BUSY true
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define OCP_TUNED 0x18
|
||||
const int pin_busy = 32;
|
||||
const int pin_dio = 33;
|
||||
const int pin_tcxo_enable = -1;
|
||||
#else
|
||||
const int pin_dio = 26;
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_HUZZAH32
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 4;
|
||||
const int pin_reset = 33;
|
||||
const int pin_dio = 39;
|
||||
const int pin_led_rx = 14;
|
||||
const int pin_led_tx = 32;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_LORA32_V1_0
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 14;
|
||||
const int pin_dio = 26;
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 25;
|
||||
const int pin_led_tx = 2;
|
||||
#else
|
||||
const int pin_led_rx = 2;
|
||||
const int pin_led_tx = 2;
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_LORA32_V2_0
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 12;
|
||||
const int pin_dio = 26;
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 2;
|
||||
const int pin_led_tx = 0;
|
||||
#else
|
||||
const int pin_led_rx = 22;
|
||||
const int pin_led_tx = 22;
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_LORA32_V2_1
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_PMU true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 23;
|
||||
const int pin_dio = 26;
|
||||
#if HAS_TCXO == true
|
||||
const int pin_tcxo_enable = 33;
|
||||
#endif
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 15;
|
||||
const int pin_led_tx = 4;
|
||||
#else
|
||||
const int pin_led_rx = 25;
|
||||
const int pin_led_tx = 25;
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V2
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define PIN_WAKEUP GPIO_NUM_0
|
||||
#define WAKEUP_LEVEL 0
|
||||
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 14;
|
||||
const int pin_dio = 26;
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 36;
|
||||
const int pin_led_tx = 37;
|
||||
#else
|
||||
const int pin_led_rx = 25;
|
||||
const int pin_led_tx = 25;
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
#define IS_ESP32S3 true
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define PIN_WAKEUP GPIO_NUM_0
|
||||
#define WAKEUP_LEVEL 0
|
||||
#define OCP_TUNED 0x18
|
||||
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 13;
|
||||
const int pin_led_tx = 14;
|
||||
#else
|
||||
const int pin_led_rx = 35;
|
||||
const int pin_led_tx = 35;
|
||||
#endif
|
||||
|
||||
#define MODEM SX1262
|
||||
#define HAS_TCXO true
|
||||
const int pin_tcxo_enable = -1;
|
||||
#define HAS_BUSY true
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
|
||||
// Following pins are for the SX1262
|
||||
const int pin_cs = 8;
|
||||
const int pin_busy = 13;
|
||||
const int pin_dio = 14;
|
||||
const int pin_reset = 12;
|
||||
const int pin_mosi = 10;
|
||||
const int pin_miso = 11;
|
||||
const int pin_sclk = 9;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
#define IS_ESP32S3 true
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH false
|
||||
#ifdef BOUNDARY_MODE
|
||||
#define HAS_BLE false
|
||||
#else
|
||||
#define HAS_BLE true
|
||||
#endif
|
||||
#define HAS_WIFI true
|
||||
#define HAS_PMU true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define HAS_LORA_PA true
|
||||
#define HAS_LORA_LNA true
|
||||
#define PIN_WAKEUP GPIO_NUM_0
|
||||
#define WAKEUP_LEVEL 0
|
||||
#define OCP_TUNED 0x18
|
||||
#define Vext GPIO_NUM_36
|
||||
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 13;
|
||||
const int pin_led_tx = 14;
|
||||
#else
|
||||
const int pin_led_rx = 35;
|
||||
const int pin_led_tx = 35;
|
||||
#endif
|
||||
|
||||
#define MODEM SX1262
|
||||
#define HAS_TCXO true
|
||||
const int pin_tcxo_enable = -1;
|
||||
#define HAS_BUSY true
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define LNA_GD_THRSHLD (-109)
|
||||
#define LNA_GD_LIMIT (-89)
|
||||
|
||||
#define LORA_LNA_GAIN 17
|
||||
#define LORA_LNA_GVT 12
|
||||
#define LORA_PA_GC1109 true
|
||||
#define LORA_PA_PWR_EN 7
|
||||
#define LORA_PA_CSD 2
|
||||
#define LORA_PA_CPS 46
|
||||
|
||||
#define PA_MAX_OUTPUT 28
|
||||
#define PA_GAIN_POINTS 22
|
||||
#define PA_GAIN_VALUES 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
|
||||
|
||||
const int pin_cs = 8;
|
||||
const int pin_busy = 13;
|
||||
const int pin_dio = 14;
|
||||
const int pin_reset = 12;
|
||||
const int pin_mosi = 10;
|
||||
const int pin_miso = 11;
|
||||
const int pin_sclk = 9;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_RNODE_NG_20
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_NP true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 12;
|
||||
const int pin_dio = 26;
|
||||
const int pin_np = 4;
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 2;
|
||||
const int pin_led_tx = 0;
|
||||
#else
|
||||
const int pin_led_rx = 22;
|
||||
const int pin_led_tx = 22;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_RNODE_NG_21
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP true
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
const int pin_cs = 18;
|
||||
const int pin_reset = 23;
|
||||
const int pin_dio = 26;
|
||||
const int pin_np = 12;
|
||||
const int pin_dac = 25;
|
||||
const int pin_adc = 34;
|
||||
// CBA already defined by framework
|
||||
//const int SD_MISO = 2;
|
||||
// CBA already defined by framework
|
||||
//const int SD_MOSI = 15;
|
||||
const int SD_CLK = 14;
|
||||
// CBA already defined by framework
|
||||
//const int SD_CS = 13;
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 12;
|
||||
const int pin_led_tx = 4;
|
||||
#else
|
||||
const int pin_led_rx = 25;
|
||||
const int pin_led_tx = 25;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_T3S3
|
||||
#define IS_ESP32S3 true
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define PIN_WAKEUP GPIO_NUM_0
|
||||
#define WAKEUP_LEVEL 0
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
const int pin_cs = 7;
|
||||
const int pin_reset = 8;
|
||||
const int pin_sclk = 5;
|
||||
const int pin_mosi = 6;
|
||||
const int pin_miso = 3;
|
||||
|
||||
#if MODEM == SX1262
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
const int pin_busy = 34;
|
||||
const int pin_dio = 33;
|
||||
const int pin_tcxo_enable = -1;
|
||||
#elif MODEM == SX1280
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define DIO2_AS_RF_SWITCH false
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
#define HAS_PA true
|
||||
const int pa_max_input = 3;
|
||||
|
||||
#define HAS_RF_SWITCH_RX_TX true
|
||||
const int pin_rxen = 21;
|
||||
const int pin_txen = 10;
|
||||
|
||||
const int pin_busy = 36;
|
||||
const int pin_dio = 9;
|
||||
const int pin_tcxo_enable = -1;
|
||||
#else
|
||||
const int pin_dio = 9;
|
||||
#endif
|
||||
|
||||
const int pin_np = 38;
|
||||
const int pin_dac = 25;
|
||||
const int pin_adc = 1;
|
||||
|
||||
const int SD_MISO = 2;
|
||||
const int SD_MOSI = 11;
|
||||
const int SD_CLK = 14;
|
||||
const int SD_CS = 13;
|
||||
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 37;
|
||||
const int pin_led_tx = 37;
|
||||
#else
|
||||
const int pin_led_rx = 37;
|
||||
const int pin_led_tx = 37;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TDECK
|
||||
#define IS_ESP32S3 true
|
||||
#define MODEM SX1262
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
|
||||
#define HAS_DISPLAY false
|
||||
#define HAS_CONSOLE false
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define PIN_WAKEUP GPIO_NUM_0
|
||||
#define WAKEUP_LEVEL 0
|
||||
|
||||
const int pin_poweron = 10;
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
const int pin_cs = 9;
|
||||
const int pin_reset = 17;
|
||||
const int pin_sclk = 40;
|
||||
const int pin_mosi = 41;
|
||||
const int pin_miso = 38;
|
||||
const int pin_tcxo_enable = -1;
|
||||
const int pin_dio = 45;
|
||||
const int pin_busy = 13;
|
||||
|
||||
const int SD_MISO = 38;
|
||||
const int SD_MOSI = 41;
|
||||
const int SD_CLK = 40;
|
||||
const int SD_CS = 39;
|
||||
|
||||
const int DISPLAY_DC = 11;
|
||||
const int DISPLAY_CS = 12;
|
||||
const int DISPLAY_MISO = 38;
|
||||
const int DISPLAY_MOSI = 41;
|
||||
const int DISPLAY_CLK = 40;
|
||||
const int DISPLAY_BL_PIN = 42;
|
||||
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#else
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#define IS_ESP32S3 true
|
||||
#define MODEM SX1262
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
#define OCP_TUNED 0x18
|
||||
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP false
|
||||
|
||||
#define PMU_IRQ 40
|
||||
#define I2C_SCL 41
|
||||
#define I2C_SDA 42
|
||||
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
const int pin_cs = 10;
|
||||
const int pin_reset = 5;
|
||||
const int pin_sclk = 12;
|
||||
const int pin_mosi = 11;
|
||||
const int pin_miso = 13;
|
||||
const int pin_tcxo_enable = -1;
|
||||
const int pin_dio = 1;
|
||||
const int pin_busy = 4;
|
||||
|
||||
const int SD_MISO = 37;
|
||||
const int SD_MOSI = 35;
|
||||
const int SD_CLK = 36;
|
||||
const int SD_CS = 47;
|
||||
|
||||
const int IMU_CS = 34;
|
||||
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#else
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_XIAO_S3
|
||||
#define IS_ESP32S3 true
|
||||
#define MODEM SX1262
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
|
||||
#define HAS_DISPLAY false
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define PIN_WAKEUP GPIO_NUM_21
|
||||
#define WAKEUP_LEVEL 0
|
||||
|
||||
const int pin_btn_usr1 = 21;
|
||||
const int pin_cs = 41;
|
||||
const int pin_reset = 42;
|
||||
const int pin_sclk = 7;
|
||||
const int pin_mosi = 9;
|
||||
const int pin_miso = 8;
|
||||
const int pin_tcxo_enable = -1;
|
||||
const int pin_dio = 39;
|
||||
const int pin_busy = 40;
|
||||
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 48;
|
||||
const int pin_led_tx = 48;
|
||||
#else
|
||||
const int pin_led_rx = 48;
|
||||
const int pin_led_tx = 48;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error An unsupported ESP32 board was selected. Cannot compile RNode firmware.
|
||||
#endif
|
||||
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
#if BOARD_MODEL == BOARD_RAK4631
|
||||
#define HAS_EEPROM false
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_CONSOLE false
|
||||
#define HAS_PMU false
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_TCXO true
|
||||
#define HAS_RF_SWITCH_RX_TX true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_INPUT true
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||
#define EEPROM_SIZE 296
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
#define BLE_MANUFACTURER "RAK Wireless"
|
||||
#define BLE_MODEL "RAK4640"
|
||||
|
||||
const int pin_btn_usr1 = 9;
|
||||
|
||||
// Following pins are for the sx1262
|
||||
const int pin_rxen = 37;
|
||||
const int pin_txen = -1;
|
||||
const int pin_reset = 38;
|
||||
const int pin_cs = 42;
|
||||
const int pin_sclk = 43;
|
||||
const int pin_mosi = 44;
|
||||
const int pin_miso = 45;
|
||||
const int pin_busy = 46;
|
||||
const int pin_dio = 47;
|
||||
const int pin_led_rx = LED_BLUE;
|
||||
const int pin_led_tx = LED_GREEN;
|
||||
const int pin_tcxo_enable = -1;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TECHO
|
||||
#define _PINNUM(port, pin) ((port) * 32 + (pin))
|
||||
#define MODEM SX1262
|
||||
#define HAS_EEPROM false
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_CONSOLE false
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_TCXO true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define BLE_MANUFACTURER "LilyGO"
|
||||
#define BLE_MODEL "T-Echo"
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define EEPROM_SIZE 296
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
|
||||
#define CONFIG_UART_BUFFER_SIZE 32768
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BACKLIGHT true
|
||||
#define DISPLAY_SCALE 1
|
||||
|
||||
#define LED_ON LOW
|
||||
#define LED_OFF HIGH
|
||||
#define PIN_LED_GREEN _PINNUM(1, 1)
|
||||
#define PIN_LED_RED _PINNUM(1, 3)
|
||||
#define PIN_LED_BLUE _PINNUM(0, 14)
|
||||
#define PIN_VEXT_EN _PINNUM(0, 12)
|
||||
|
||||
const int pin_disp_cs = 30;
|
||||
const int pin_disp_dc = 28;
|
||||
const int pin_disp_reset = 2;
|
||||
const int pin_disp_busy = 3;
|
||||
const int pin_disp_en = -1;
|
||||
const int pin_disp_sck = 31;
|
||||
const int pin_disp_mosi = 29;
|
||||
const int pin_disp_miso = -1;
|
||||
const int pin_backlight = 43;
|
||||
|
||||
const int pin_btn_usr1 = _PINNUM(1, 10);
|
||||
const int pin_btn_touch = _PINNUM(0, 11);
|
||||
|
||||
const int pin_reset = 25;
|
||||
const int pin_cs = 24;
|
||||
const int pin_sclk = 19;
|
||||
const int pin_mosi = 22;
|
||||
const int pin_miso = 23;
|
||||
const int pin_busy = 17;
|
||||
const int pin_dio = 20;
|
||||
const int pin_tcxo_enable = 21;
|
||||
const int pin_led_rx = PIN_LED_BLUE;
|
||||
const int pin_led_tx = PIN_LED_RED;
|
||||
|
||||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
#define MODEM SX1262
|
||||
#define HAS_EEPROM false
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_CONSOLE false
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP true
|
||||
#define HAS_SD false
|
||||
#define HAS_TCXO true
|
||||
#define HAS_BUSY true
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP true
|
||||
#define DIO2_AS_RF_SWITCH true
|
||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||
#define CONFIG_QUEUE_SIZE 6144
|
||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||
#define EEPROM_SIZE 296
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
#define BLE_MANUFACTURER "Heltec"
|
||||
#define BLE_MODEL "T114"
|
||||
|
||||
#define PIN_T114_ADC_EN 6
|
||||
#define PIN_VEXT_EN 21
|
||||
|
||||
// LED
|
||||
#define LED_T114_GREEN 3
|
||||
#define PIN_T114_LED 14
|
||||
#define NP_M 1
|
||||
const int pin_np = PIN_T114_LED;
|
||||
|
||||
// SPI
|
||||
#define PIN_T114_MOSI 22
|
||||
#define PIN_T114_MISO 23
|
||||
#define PIN_T114_SCK 19
|
||||
#define PIN_T114_SS 24
|
||||
|
||||
// SX1262
|
||||
#define PIN_T114_RST 25
|
||||
#define PIN_T114_DIO1 20
|
||||
#define PIN_T114_BUSY 17
|
||||
|
||||
// TFT
|
||||
#define DISPLAY_SCALE 2
|
||||
#define PIN_T114_TFT_MOSI 9
|
||||
#define PIN_T114_TFT_MISO 11 // not connected
|
||||
#define PIN_T114_TFT_SCK 8
|
||||
#define PIN_T114_TFT_SS 11
|
||||
#define PIN_T114_TFT_DC 12
|
||||
#define PIN_T114_TFT_RST 2
|
||||
#define PIN_T114_TFT_EN 3
|
||||
#define PIN_T114_TFT_BLGT 15
|
||||
|
||||
// pins for buttons on Heltec T114
|
||||
const int pin_btn_usr1 = 42;
|
||||
|
||||
// pins for sx1262 on Heltec T114
|
||||
const int pin_reset = PIN_T114_RST;
|
||||
const int pin_cs = PIN_T114_SS;
|
||||
const int pin_sclk = PIN_T114_SCK;
|
||||
const int pin_mosi = PIN_T114_MOSI;
|
||||
const int pin_miso = PIN_T114_MISO;
|
||||
const int pin_busy = PIN_T114_BUSY;
|
||||
const int pin_dio = PIN_T114_DIO1;
|
||||
const int pin_led_rx = 35;
|
||||
const int pin_led_tx = 35;
|
||||
const int pin_tcxo_enable = -1;
|
||||
|
||||
// pins for ST7789 display on Heltec T114
|
||||
const int DISPLAY_DC = PIN_T114_TFT_DC;
|
||||
const int DISPLAY_CS = PIN_T114_TFT_SS;
|
||||
const int DISPLAY_MISO = PIN_T114_TFT_MISO;
|
||||
const int DISPLAY_MOSI = PIN_T114_TFT_MOSI;
|
||||
const int DISPLAY_CLK = PIN_T114_TFT_SCK;
|
||||
const int DISPLAY_BL_PIN = PIN_T114_TFT_BLGT;
|
||||
const int DISPLAY_RST = PIN_T114_TFT_RST;
|
||||
|
||||
#else
|
||||
#error An unsupported nRF board was selected. Cannot compile RNode firmware.
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef DISPLAY_SCALE
|
||||
#define DISPLAY_SCALE 1
|
||||
#endif
|
||||
|
||||
#ifndef HAS_RF_SWITCH_RX_TX
|
||||
const int pin_rxen = -1;
|
||||
const int pin_txen = -1;
|
||||
#endif
|
||||
|
||||
#ifndef HAS_BUSY
|
||||
const int pin_busy = -1;
|
||||
#endif
|
||||
|
||||
#ifndef LED_ON
|
||||
#define LED_ON HIGH
|
||||
#endif
|
||||
|
||||
#ifndef LED_OFF
|
||||
#define LED_OFF LOW
|
||||
#endif
|
||||
|
||||
#ifndef DIO2_AS_RF_SWITCH
|
||||
#define DIO2_AS_RF_SWITCH false
|
||||
#endif
|
||||
|
||||
// Default OCP value if not specified
|
||||
// in board configuration
|
||||
#ifndef OCP_TUNED
|
||||
#define OCP_TUNED 0x18
|
||||
#endif
|
||||
|
||||
#ifndef NP_M
|
||||
#define NP_M 0.15
|
||||
#endif
|
||||
|
||||
#endif
|
||||
501
BoundaryConfig.h
Normal file
@@ -0,0 +1,501 @@
|
||||
// Copyright (C) 2026, Boundary Mode Extension
|
||||
// Based on microReticulum_Firmware by Mark Qvist
|
||||
//
|
||||
// BoundaryConfig.h — Captive-portal web configuration for Boundary Mode.
|
||||
// When triggered (first boot with no config, or button hold >5s),
|
||||
// the device starts a WiFi AP with a web form for all settings:
|
||||
// WiFi STA credentials, TCP backbone params, LoRa radio params,
|
||||
// and optional AP-mode TCP server.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
#ifndef BOUNDARY_CONFIG_H
|
||||
#define BOUNDARY_CONFIG_H
|
||||
|
||||
#ifdef BOUNDARY_MODE
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
|
||||
// ─── Config Portal State ─────────────────────────────────────────────────────
|
||||
static bool config_portal_active = false;
|
||||
static WebServer* config_server = nullptr;
|
||||
static DNSServer* config_dns = nullptr;
|
||||
|
||||
static const char CONFIG_AP_SSID[] = "RNode-Boundary-Setup";
|
||||
static const uint16_t DNS_PORT = 53;
|
||||
static const uint16_t HTTP_PORT = 80;
|
||||
|
||||
// Forward declarations
|
||||
void config_portal_start();
|
||||
void config_portal_stop();
|
||||
void config_portal_loop();
|
||||
bool config_portal_is_active();
|
||||
bool boundary_needs_config();
|
||||
|
||||
// ─── Common bandwidth values (Hz) ───────────────────────────────────────────
|
||||
// These match Reticulum standard channel plans
|
||||
struct BwOption { uint32_t hz; const char* label; };
|
||||
static const BwOption BW_OPTIONS[] = {
|
||||
{ 7800, "7.8 kHz" },
|
||||
{ 10400, "10.4 kHz" },
|
||||
{ 15600, "15.6 kHz" },
|
||||
{ 20800, "20.8 kHz" },
|
||||
{ 31250, "31.25 kHz" },
|
||||
{ 41700, "41.7 kHz" },
|
||||
{ 62500, "62.5 kHz" },
|
||||
{125000, "125 kHz" },
|
||||
{250000, "250 kHz" },
|
||||
{500000, "500 kHz" },
|
||||
};
|
||||
static const int BW_OPTIONS_COUNT = sizeof(BW_OPTIONS) / sizeof(BW_OPTIONS[0]);
|
||||
|
||||
// ─── HTML Page Generation ────────────────────────────────────────────────────
|
||||
|
||||
static void config_send_html() {
|
||||
// Read current values from EEPROM/globals for pre-population
|
||||
char cur_ssid[33] = "";
|
||||
char cur_psk[33] = "";
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
cur_ssid[i] = EEPROM.read(config_addr(ADDR_CONF_SSID + i));
|
||||
if (cur_ssid[i] == (char)0xFF) cur_ssid[i] = '\0';
|
||||
}
|
||||
cur_ssid[32] = '\0';
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
cur_psk[i] = EEPROM.read(config_addr(ADDR_CONF_PSK + i));
|
||||
if (cur_psk[i] == (char)0xFF) cur_psk[i] = '\0';
|
||||
}
|
||||
cur_psk[32] = '\0';
|
||||
|
||||
// Current LoRa values (from globals, which were loaded from EEPROM)
|
||||
uint32_t cur_freq = lora_freq;
|
||||
uint32_t cur_bw = lora_bw;
|
||||
int cur_sf = lora_sf;
|
||||
int cur_cr = lora_cr;
|
||||
int cur_txp = lora_txp;
|
||||
if (cur_txp == 0xFF) cur_txp = 28; // Default TX power
|
||||
|
||||
// Default frequency if not set
|
||||
if (cur_freq == 0) cur_freq = 914875000; // 914.875 MHz default
|
||||
if (cur_bw == 0) cur_bw = 125000; // 125 kHz default
|
||||
if (cur_sf == 0) cur_sf = 10; // SF10 default
|
||||
if (cur_cr < 5 || cur_cr > 8) cur_cr = 5; // CR 4/5 default
|
||||
|
||||
// Build the HTML page
|
||||
String html = F(
|
||||
"<!DOCTYPE html><html><head>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>RNode Boundary Setup</title>"
|
||||
"<style>"
|
||||
"body{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;margin:0;padding:16px;}"
|
||||
"h1{color:#e94560;font-size:1.4em;margin:0 0 8px;}"
|
||||
"h2{color:#0f3460;background:#e0e0e0;padding:6px 10px;margin:18px -10px 10px;font-size:1em;border-radius:4px;}"
|
||||
"form{max-width:480px;margin:0 auto;}"
|
||||
"label{display:block;margin:8px 0 2px;font-size:0.9em;color:#aaa;}"
|
||||
"input,select{width:100%;padding:8px;margin:2px 0 6px;box-sizing:border-box;"
|
||||
"background:#16213e;border:1px solid #0f3460;color:#e0e0e0;border-radius:4px;font-size:0.95em;}"
|
||||
"input:focus,select:focus{border-color:#e94560;outline:none;}"
|
||||
".row{display:flex;gap:10px;}.row>div{flex:1;}"
|
||||
".note{font-size:0.8em;color:#666;margin:2px 0 8px;}"
|
||||
"button{width:100%;padding:12px;margin:20px 0;background:#e94560;color:#fff;"
|
||||
"border:none;border-radius:4px;font-size:1.1em;cursor:pointer;}"
|
||||
"button:hover{background:#c73e54;}"
|
||||
".ok{background:#16213e;padding:20px;border-radius:8px;text-align:center;}"
|
||||
".ok h1{color:#0f0;}"
|
||||
"</style></head><body>"
|
||||
"<h1>📡 RNode Boundary Node</h1>"
|
||||
"<form method='POST' action='/save'>"
|
||||
);
|
||||
|
||||
// ── WiFi STA Section ──
|
||||
html += F(
|
||||
"<h2>📶 WiFi Network</h2>"
|
||||
"<label>WiFi</label>"
|
||||
"<select name='wifi_en'>"
|
||||
);
|
||||
html += F("<option value='1'");
|
||||
if (boundary_state.wifi_enabled) html += F(" selected");
|
||||
html += F(">Enabled</option>");
|
||||
html += F("<option value='0'");
|
||||
if (!boundary_state.wifi_enabled) html += F(" selected");
|
||||
html += F(">Disabled (LoRa-only repeater)</option>");
|
||||
html += F("</select>");
|
||||
|
||||
html += F(
|
||||
"<label>SSID</label>"
|
||||
"<input name='ssid' maxlength='32' placeholder='Your WiFi network' value='"
|
||||
);
|
||||
html += String(cur_ssid);
|
||||
html += F(
|
||||
"'>"
|
||||
"<label>Password</label>"
|
||||
"<input name='psk' type='password' maxlength='32' placeholder='WiFi password' value='"
|
||||
);
|
||||
html += String(cur_psk);
|
||||
html += F("'>");
|
||||
|
||||
// ── TCP Backbone Section ──
|
||||
html += F(
|
||||
"<h2>🌐 TCP Backbone</h2>"
|
||||
"<label>Mode</label>"
|
||||
"<select name='tcp_mode'>"
|
||||
);
|
||||
html += F("<option value='0'");
|
||||
if (boundary_state.tcp_mode == 0) html += F(" selected");
|
||||
html += F(">Disabled</option>");
|
||||
html += F("<option value='1'");
|
||||
if (boundary_state.tcp_mode == 1) html += F(" selected");
|
||||
html += F(">Client (connect to backbone)</option>");
|
||||
html += F("</select>");
|
||||
|
||||
html += F("<label>Backbone Host</label>");
|
||||
html += F("<input name='bb_host' maxlength='63' placeholder='e.g. 192.168.1.100' value='");
|
||||
html += String(boundary_state.backbone_host);
|
||||
html += F("'>");
|
||||
|
||||
html += F("<label>Backbone Port</label>");
|
||||
html += F("<input name='bb_port' type='number' min='1' max='65535' value='");
|
||||
html += String(boundary_state.backbone_port);
|
||||
html += F("'>");
|
||||
|
||||
// ── Local TCP Server Section ──
|
||||
html += F(
|
||||
"<h2>📡 Local TCP Server (optional)</h2>"
|
||||
"<p class='note'>Run a TCP server on the same WiFi network so local devices can connect. "
|
||||
"Uses Access Point mode (does not forward announces).</p>"
|
||||
"<label>Local TCP Server</label>"
|
||||
"<select name='ap_tcp_en'>"
|
||||
);
|
||||
html += F("<option value='0'");
|
||||
if (!boundary_state.ap_tcp_enabled) html += F(" selected");
|
||||
html += F(">Disabled</option>");
|
||||
html += F("<option value='1'");
|
||||
if (boundary_state.ap_tcp_enabled) html += F(" selected");
|
||||
html += F(">Enabled</option>");
|
||||
html += F("</select>");
|
||||
|
||||
html += F("<label>TCP Port</label>");
|
||||
html += F("<input name='ap_tcp_port' type='number' min='1' max='65535' value='");
|
||||
html += String(boundary_state.ap_tcp_port);
|
||||
html += F("'>");
|
||||
|
||||
// ── LoRa Radio Section ──
|
||||
html += F(
|
||||
"<h2>📻 LoRa Radio</h2>"
|
||||
);
|
||||
|
||||
// Frequency — show in MHz for human-friendliness
|
||||
char freq_str[16];
|
||||
dtostrf((double)cur_freq / 1000000.0, 1, 3, freq_str);
|
||||
html += F("<label>Frequency (MHz)</label>");
|
||||
html += F("<input name='freq' type='text' placeholder='914.875' value='");
|
||||
html += String(freq_str);
|
||||
html += F("'>");
|
||||
html += F("<p class='note'>e.g. 914.875, 868.000, 433.000</p>");
|
||||
|
||||
// Bandwidth — dropdown
|
||||
html += F("<label>Bandwidth</label><select name='bw'>");
|
||||
for (int i = 0; i < BW_OPTIONS_COUNT; i++) {
|
||||
html += F("<option value='");
|
||||
html += String(BW_OPTIONS[i].hz);
|
||||
html += "'";
|
||||
if (BW_OPTIONS[i].hz == cur_bw) html += F(" selected");
|
||||
html += ">";
|
||||
html += BW_OPTIONS[i].label;
|
||||
html += F("</option>");
|
||||
}
|
||||
html += F("</select>");
|
||||
|
||||
// Spreading Factor — dropdown 6-12
|
||||
html += F("<label>Spreading Factor</label><select name='sf'>");
|
||||
for (int sf = 6; sf <= 12; sf++) {
|
||||
html += F("<option value='");
|
||||
html += String(sf);
|
||||
html += "'";
|
||||
if (sf == cur_sf) html += F(" selected");
|
||||
html += ">SF";
|
||||
html += String(sf);
|
||||
html += F("</option>");
|
||||
}
|
||||
html += F("</select>");
|
||||
|
||||
// Coding Rate — dropdown 5-8 (maps to 4/5 through 4/8)
|
||||
html += F("<label>Coding Rate</label><select name='cr'>");
|
||||
for (int cr = 5; cr <= 8; cr++) {
|
||||
html += F("<option value='");
|
||||
html += String(cr);
|
||||
html += "'";
|
||||
if (cr == cur_cr) html += F(" selected");
|
||||
html += ">4/";
|
||||
html += String(cr);
|
||||
html += F("</option>");
|
||||
}
|
||||
html += F("</select>");
|
||||
|
||||
// TX Power
|
||||
html += F("<label>TX Power (dBm)</label>");
|
||||
html += F("<input name='txp' type='number' min='2' max='");
|
||||
#ifdef PA_MAX_OUTPUT
|
||||
html += String(PA_MAX_OUTPUT);
|
||||
#else
|
||||
html += "22";
|
||||
#endif
|
||||
html += F("' value='");
|
||||
html += String(cur_txp);
|
||||
html += F("'>");
|
||||
|
||||
#ifdef PA_MAX_OUTPUT
|
||||
html += F("<p class='note'>Max output for this board: ");
|
||||
html += String(PA_MAX_OUTPUT);
|
||||
html += F(" dBm (with PA)</p>");
|
||||
#endif
|
||||
|
||||
// ── Submit ──
|
||||
html += F(
|
||||
"<button type='submit'>Save & Reboot</button>"
|
||||
"</form></body></html>"
|
||||
);
|
||||
|
||||
config_server->send(200, "text/html", html);
|
||||
}
|
||||
|
||||
// ─── Handle POST /save ──────────────────────────────────────────────────────
|
||||
|
||||
static void config_handle_save() {
|
||||
// ── WiFi STA credentials ──
|
||||
String ssid = config_server->arg("ssid");
|
||||
String psk = config_server->arg("psk");
|
||||
|
||||
// Write SSID to config EEPROM area
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint8_t c = (i < (int)ssid.length()) ? ssid[i] : 0x00;
|
||||
EEPROM.write(config_addr(ADDR_CONF_SSID + i), c);
|
||||
}
|
||||
EEPROM.write(config_addr(ADDR_CONF_SSID + 32), 0x00);
|
||||
|
||||
// Write PSK
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint8_t c = (i < (int)psk.length()) ? psk[i] : 0x00;
|
||||
EEPROM.write(config_addr(ADDR_CONF_PSK + i), c);
|
||||
}
|
||||
EEPROM.write(config_addr(ADDR_CONF_PSK + 32), 0x00);
|
||||
|
||||
// Set WiFi mode to STA
|
||||
EEPROM.write(eeprom_addr(ADDR_CONF_WIFI), WR_WIFI_STA);
|
||||
|
||||
// ── WiFi enable setting ──
|
||||
boundary_state.wifi_enabled = (config_server->arg("wifi_en").toInt() == 1);
|
||||
|
||||
// ── TCP backbone settings ──
|
||||
boundary_state.tcp_mode = (uint8_t)config_server->arg("tcp_mode").toInt(); // 0=disabled, 1=client
|
||||
if (boundary_state.tcp_mode > 1) boundary_state.tcp_mode = 0;
|
||||
boundary_state.tcp_port = (uint16_t)config_server->arg("tcp_port").toInt();
|
||||
if (boundary_state.tcp_port == 0) boundary_state.tcp_port = 4242;
|
||||
|
||||
String bb_host = config_server->arg("bb_host");
|
||||
memset(boundary_state.backbone_host, 0, sizeof(boundary_state.backbone_host));
|
||||
strncpy(boundary_state.backbone_host, bb_host.c_str(), sizeof(boundary_state.backbone_host) - 1);
|
||||
|
||||
boundary_state.backbone_port = (uint16_t)config_server->arg("bb_port").toInt();
|
||||
if (boundary_state.backbone_port == 0) boundary_state.backbone_port = 4242;
|
||||
|
||||
// ── Local TCP server settings ──
|
||||
boundary_state.ap_tcp_enabled = (config_server->arg("ap_tcp_en").toInt() == 1);
|
||||
boundary_state.ap_tcp_port = (uint16_t)config_server->arg("ap_tcp_port").toInt();
|
||||
if (boundary_state.ap_tcp_port == 0) boundary_state.ap_tcp_port = 4242;
|
||||
|
||||
// Save boundary config to EEPROM
|
||||
boundary_save_config();
|
||||
|
||||
// ── LoRa radio settings ──
|
||||
String freq_str = config_server->arg("freq");
|
||||
double freq_mhz = freq_str.toDouble();
|
||||
if (freq_mhz > 0) {
|
||||
lora_freq = (uint32_t)(freq_mhz * 1000000.0);
|
||||
}
|
||||
|
||||
String bw_str = config_server->arg("bw");
|
||||
uint32_t bw_val = (uint32_t)bw_str.toInt();
|
||||
if (bw_val > 0) lora_bw = bw_val;
|
||||
|
||||
int sf_val = config_server->arg("sf").toInt();
|
||||
if (sf_val >= 6 && sf_val <= 12) lora_sf = sf_val;
|
||||
|
||||
int cr_val = config_server->arg("cr").toInt();
|
||||
if (cr_val >= 5 && cr_val <= 8) lora_cr = cr_val;
|
||||
|
||||
int txp_val = config_server->arg("txp").toInt();
|
||||
if (txp_val >= 2 && txp_val <= 30) lora_txp = txp_val;
|
||||
|
||||
// Save LoRa config to EEPROM (reuse existing eeprom_conf functions)
|
||||
// Write directly since hw_ready may not be set yet
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_SF), lora_sf);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_CR), lora_cr);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_TXP), lora_txp);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_BW) + 0, lora_bw >> 24);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_BW) + 1, lora_bw >> 16);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_BW) + 2, lora_bw >> 8);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_BW) + 3, lora_bw);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_FREQ) + 0, lora_freq >> 24);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_FREQ) + 1, lora_freq >> 16);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_FREQ) + 2, lora_freq >> 8);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_FREQ) + 3, lora_freq);
|
||||
eeprom_update(eeprom_addr(ADDR_CONF_OK), CONF_OK_BYTE);
|
||||
|
||||
EEPROM.commit();
|
||||
|
||||
// ── Send confirmation page ──
|
||||
String ok = F(
|
||||
"<!DOCTYPE html><html><head>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>Saved</title>"
|
||||
"<style>"
|
||||
"body{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:40px;"
|
||||
"display:flex;align-items:center;justify-content:center;min-height:80vh;}"
|
||||
".ok{background:#16213e;padding:30px;border-radius:12px;text-align:center;max-width:400px;}"
|
||||
"h1{color:#4caf50;margin-bottom:16px;}"
|
||||
"p{color:#aaa;}"
|
||||
"</style></head><body>"
|
||||
"<div class='ok'>"
|
||||
"<h1>✅ Configuration Saved</h1>"
|
||||
"<p>Device will reboot in 3 seconds and connect to your WiFi network.</p>"
|
||||
"<p style='color:#666;font-size:0.85em;'>If the device cannot connect, hold the button for 5+ seconds to re-enter setup.</p>"
|
||||
"</div></body></html>"
|
||||
);
|
||||
config_server->send(200, "text/html", ok);
|
||||
|
||||
// Give the response time to send
|
||||
delay(3000);
|
||||
|
||||
// Reboot
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// ─── Captive Portal redirect ─────────────────────────────────────────────────
|
||||
static void config_handle_redirect() {
|
||||
config_server->sendHeader("Location", "http://10.0.0.1/", true);
|
||||
config_server->send(302, "text/plain", "Redirecting to setup...");
|
||||
}
|
||||
|
||||
// ─── Check if config is needed ───────────────────────────────────────────────
|
||||
bool boundary_needs_config() {
|
||||
// Check if WiFi SSID is configured
|
||||
char ssid[33];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
ssid[i] = EEPROM.read(config_addr(ADDR_CONF_SSID + i));
|
||||
if (ssid[i] == (char)0xFF) ssid[i] = '\0';
|
||||
}
|
||||
ssid[32] = '\0';
|
||||
|
||||
// Also check boundary mode enable flag
|
||||
uint8_t bmode = EEPROM.read(config_addr(ADDR_CONF_BMODE));
|
||||
|
||||
// Need config if no SSID set and boundary not yet configured
|
||||
if (ssid[0] == '\0' && bmode != BOUNDARY_ENABLE_BYTE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Start Config Portal ─────────────────────────────────────────────────────
|
||||
void config_portal_start() {
|
||||
if (config_portal_active) return;
|
||||
|
||||
Serial.println("[Config] Starting configuration portal...");
|
||||
|
||||
// Stop any existing WiFi
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
delay(100);
|
||||
|
||||
// Start AP
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(CONFIG_AP_SSID, NULL); // Open AP for easy setup
|
||||
delay(150);
|
||||
|
||||
IPAddress ap_addr(10, 0, 0, 1);
|
||||
IPAddress ap_mask(255, 255, 255, 0);
|
||||
WiFi.softAPConfig(ap_addr, ap_addr, ap_mask);
|
||||
|
||||
Serial.print("[Config] AP started: ");
|
||||
Serial.println(CONFIG_AP_SSID);
|
||||
Serial.print("[Config] IP: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
// Start DNS server for captive portal (redirect all domains to us)
|
||||
config_dns = new DNSServer();
|
||||
config_dns->start(DNS_PORT, "*", ap_addr);
|
||||
|
||||
// Start web server
|
||||
config_server = new WebServer(HTTP_PORT);
|
||||
config_server->on("/", HTTP_GET, config_send_html);
|
||||
config_server->on("/save", HTTP_POST, config_handle_save);
|
||||
config_server->onNotFound(config_handle_redirect); // Captive portal catch-all
|
||||
config_server->begin();
|
||||
|
||||
config_portal_active = true;
|
||||
|
||||
Serial.println("[Config] Portal ready — connect to WiFi: " + String(CONFIG_AP_SSID));
|
||||
|
||||
#if HAS_DISPLAY
|
||||
if (disp_ready) {
|
||||
// Show config mode on display
|
||||
stat_area.fillScreen(SSD1306_BLACK);
|
||||
stat_area.setCursor(0, 0);
|
||||
stat_area.println("CONFIG MODE");
|
||||
stat_area.println("");
|
||||
stat_area.println("Connect to:");
|
||||
stat_area.println(CONFIG_AP_SSID);
|
||||
stat_area.println("");
|
||||
stat_area.println("Open browser");
|
||||
stat_area.println("http://10.0.0.1");
|
||||
display.clearDisplay();
|
||||
display.drawBitmap(0, 0, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK);
|
||||
display.display();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─── Stop Config Portal ──────────────────────────────────────────────────────
|
||||
void config_portal_stop() {
|
||||
if (!config_portal_active) return;
|
||||
|
||||
Serial.println("[Config] Stopping configuration portal");
|
||||
|
||||
if (config_server) {
|
||||
config_server->stop();
|
||||
delete config_server;
|
||||
config_server = nullptr;
|
||||
}
|
||||
if (config_dns) {
|
||||
config_dns->stop();
|
||||
delete config_dns;
|
||||
config_dns = nullptr;
|
||||
}
|
||||
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
config_portal_active = false;
|
||||
}
|
||||
|
||||
// ─── Portal Loop — call from main loop() ─────────────────────────────────────
|
||||
void config_portal_loop() {
|
||||
if (!config_portal_active) return;
|
||||
if (config_dns) config_dns->processNextRequest();
|
||||
if (config_server) config_server->handleClient();
|
||||
}
|
||||
|
||||
// ─── Is portal active? ──────────────────────────────────────────────────────
|
||||
bool config_portal_is_active() {
|
||||
return config_portal_active;
|
||||
}
|
||||
|
||||
#endif // BOUNDARY_MODE
|
||||
#endif // BOUNDARY_CONFIG_H
|
||||
225
BoundaryMode.h
Normal file
@@ -0,0 +1,225 @@
|
||||
// Copyright (C) 2026, Boundary Mode Extension
|
||||
// Based on microReticulum_Firmware by Mark Qvist
|
||||
//
|
||||
// BoundaryMode.h — Configuration and runtime state for the Boundary Mode
|
||||
// firmware variant. This header defines the WiFi backbone connection
|
||||
// parameters and boundary-specific operational settings.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
#ifndef BOUNDARY_MODE_H
|
||||
#define BOUNDARY_MODE_H
|
||||
|
||||
#ifdef BOUNDARY_MODE
|
||||
|
||||
// ─── Boundary Mode Configuration ────────────────────────────────────────────
|
||||
//
|
||||
// The boundary node operates with TWO RNS interfaces:
|
||||
//
|
||||
// 1. LoRaInterface (MODE_GATEWAY) — radio side, handles LoRa mesh
|
||||
// 2. TcpInterface (MODE_BOUNDARY) — WiFi side, connects to TCP backbone
|
||||
//
|
||||
// RNS Transport is ALWAYS enabled in boundary mode.
|
||||
// Packets received on either interface are routed through Transport
|
||||
// to the other interface based on path table lookups and announce rules.
|
||||
|
||||
// ─── WiFi Backbone Connection ────────────────────────────────────────────────
|
||||
// These can be overridden via build flags or EEPROM at runtime.
|
||||
|
||||
// Default backbone server to connect to (client mode)
|
||||
// Set to empty string "" if operating in server mode
|
||||
#ifndef BOUNDARY_BACKBONE_HOST
|
||||
#define BOUNDARY_BACKBONE_HOST ""
|
||||
#endif
|
||||
|
||||
#ifndef BOUNDARY_BACKBONE_PORT
|
||||
#define BOUNDARY_BACKBONE_PORT 4242
|
||||
#endif
|
||||
|
||||
// TCP interface mode: 0 = disabled, 1 = client (connect out)
|
||||
#ifndef BOUNDARY_TCP_MODE
|
||||
#define BOUNDARY_TCP_MODE 1
|
||||
#endif
|
||||
|
||||
// TCP server listen port (when in server mode)
|
||||
#ifndef BOUNDARY_TCP_PORT
|
||||
#define BOUNDARY_TCP_PORT 4242
|
||||
#endif
|
||||
|
||||
// ─── EEPROM Extension Addresses ──────────────────────────────────────────────
|
||||
// We use the CONFIG area (config_addr) for additional boundary mode settings.
|
||||
// These are after the existing WiFi SSID/PSK/IP/NM fields.
|
||||
// Existing layout:
|
||||
// 0x00-0x20: SSID (33 bytes)
|
||||
// 0x21-0x41: PSK (33 bytes)
|
||||
// 0x42-0x45: IP (4 bytes)
|
||||
// 0x46-0x49: NM (4 bytes)
|
||||
// Our additions (config_addr space, 0x4A onwards):
|
||||
#define ADDR_CONF_BMODE 0x4A // Boundary mode enabled flag (1 byte, 0x73 = enabled)
|
||||
#define ADDR_CONF_BTCP_MODE 0x4B // TCP mode: 0=server, 1=client (1 byte)
|
||||
#define ADDR_CONF_BTCP_PORT 0x4C // TCP port (2 bytes, big-endian)
|
||||
#define ADDR_CONF_BHOST 0x4E // Backbone host (64 bytes, null-terminated)
|
||||
#define ADDR_CONF_BHPORT 0x8E // Backbone target port (2 bytes, big-endian)
|
||||
#define ADDR_CONF_AP_TCP_EN 0x90 // AP TCP server enable (1 byte, 0x73 = enabled)
|
||||
#define ADDR_CONF_AP_TCP_PORT 0x91 // AP TCP server port (2 bytes, big-endian)
|
||||
#define ADDR_CONF_AP_SSID 0x93 // AP SSID (33 bytes, null-terminated)
|
||||
#define ADDR_CONF_AP_PSK 0xB4 // AP PSK (33 bytes, null-terminated)
|
||||
#define ADDR_CONF_WIFI_EN 0xD5 // WiFi enable flag (1 byte, 0x73 = enabled)
|
||||
// Total: 0xD6 (214 bytes used of 256 CONFIG area)
|
||||
|
||||
#define BOUNDARY_ENABLE_BYTE 0x73
|
||||
|
||||
// ─── Boundary Mode Runtime State ─────────────────────────────────────────────
|
||||
#ifndef BOUNDARY_STATE_DEFINED
|
||||
#define BOUNDARY_STATE_DEFINED
|
||||
struct BoundaryState {
|
||||
bool enabled;
|
||||
bool wifi_enabled; // false = LoRa-only repeater (no WiFi)
|
||||
uint8_t tcp_mode; // 0=disabled, 1=client
|
||||
uint16_t tcp_port; // Local port (client outbound)
|
||||
char backbone_host[64];
|
||||
uint16_t backbone_port; // Target port for client mode
|
||||
|
||||
// AP TCP server settings
|
||||
bool ap_tcp_enabled; // Whether to run a WiFi AP with TCP server
|
||||
uint16_t ap_tcp_port; // Port for the AP TCP server
|
||||
char ap_ssid[33]; // AP SSID
|
||||
char ap_psk[33]; // AP PSK (empty = open)
|
||||
|
||||
// Runtime state
|
||||
bool wifi_connected;
|
||||
bool tcp_connected; // Backbone (WAN) connected
|
||||
bool ap_tcp_connected; // Local TCP server (LAN) has client
|
||||
bool ap_active;
|
||||
uint32_t packets_bridged_lora_to_tcp;
|
||||
uint32_t packets_bridged_tcp_to_lora;
|
||||
uint32_t last_bridge_activity;
|
||||
};
|
||||
#endif // BOUNDARY_STATE_DEFINED
|
||||
|
||||
// Global boundary state instance (defined in RNode_Firmware.ino)
|
||||
extern BoundaryState boundary_state;
|
||||
|
||||
// ─── Boundary Mode EEPROM Load/Save ─────────────────────────────────────────
|
||||
|
||||
inline void boundary_load_config() {
|
||||
// Check if boundary mode is configured
|
||||
uint8_t bmode = EEPROM.read(config_addr(ADDR_CONF_BMODE));
|
||||
boundary_state.enabled = (bmode == BOUNDARY_ENABLE_BYTE);
|
||||
|
||||
if (!boundary_state.enabled) {
|
||||
// Use compile-time defaults
|
||||
boundary_state.wifi_enabled = true;
|
||||
boundary_state.tcp_mode = BOUNDARY_TCP_MODE;
|
||||
boundary_state.tcp_port = BOUNDARY_TCP_PORT;
|
||||
strncpy(boundary_state.backbone_host, BOUNDARY_BACKBONE_HOST,
|
||||
sizeof(boundary_state.backbone_host) - 1);
|
||||
boundary_state.backbone_host[sizeof(boundary_state.backbone_host) - 1] = '\0';
|
||||
boundary_state.backbone_port = BOUNDARY_BACKBONE_PORT;
|
||||
boundary_state.ap_tcp_enabled = false;
|
||||
boundary_state.ap_tcp_port = 4242;
|
||||
boundary_state.ap_ssid[0] = '\0';
|
||||
boundary_state.ap_psk[0] = '\0';
|
||||
// Mark as enabled since we're compiled with BOUNDARY_MODE
|
||||
boundary_state.enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load wifi enable flag (default to enabled if unprogrammed 0xFF)
|
||||
uint8_t wifi_en_byte = EEPROM.read(config_addr(ADDR_CONF_WIFI_EN));
|
||||
boundary_state.wifi_enabled = (wifi_en_byte == BOUNDARY_ENABLE_BYTE || wifi_en_byte == 0xFF);
|
||||
|
||||
// Load from EEPROM
|
||||
boundary_state.tcp_mode = EEPROM.read(config_addr(ADDR_CONF_BTCP_MODE));
|
||||
if (boundary_state.tcp_mode > 1) boundary_state.tcp_mode = 0; // 0=disabled, 1=client
|
||||
|
||||
boundary_state.tcp_port =
|
||||
((uint16_t)EEPROM.read(config_addr(ADDR_CONF_BTCP_PORT)) << 8) |
|
||||
(uint16_t)EEPROM.read(config_addr(ADDR_CONF_BTCP_PORT + 1));
|
||||
if (boundary_state.tcp_port == 0 || boundary_state.tcp_port == 0xFFFF) {
|
||||
boundary_state.tcp_port = BOUNDARY_TCP_PORT;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 63; i++) {
|
||||
boundary_state.backbone_host[i] = EEPROM.read(config_addr(ADDR_CONF_BHOST + i));
|
||||
if (boundary_state.backbone_host[i] == 0xFF) {
|
||||
boundary_state.backbone_host[i] = '\0';
|
||||
}
|
||||
}
|
||||
boundary_state.backbone_host[63] = '\0';
|
||||
|
||||
boundary_state.backbone_port =
|
||||
((uint16_t)EEPROM.read(config_addr(ADDR_CONF_BHPORT)) << 8) |
|
||||
(uint16_t)EEPROM.read(config_addr(ADDR_CONF_BHPORT + 1));
|
||||
if (boundary_state.backbone_port == 0 || boundary_state.backbone_port == 0xFFFF) {
|
||||
boundary_state.backbone_port = BOUNDARY_BACKBONE_PORT;
|
||||
}
|
||||
|
||||
// Load AP TCP server settings
|
||||
boundary_state.ap_tcp_enabled =
|
||||
(EEPROM.read(config_addr(ADDR_CONF_AP_TCP_EN)) == BOUNDARY_ENABLE_BYTE);
|
||||
|
||||
boundary_state.ap_tcp_port =
|
||||
((uint16_t)EEPROM.read(config_addr(ADDR_CONF_AP_TCP_PORT)) << 8) |
|
||||
(uint16_t)EEPROM.read(config_addr(ADDR_CONF_AP_TCP_PORT + 1));
|
||||
if (boundary_state.ap_tcp_port == 0 || boundary_state.ap_tcp_port == 0xFFFF) {
|
||||
boundary_state.ap_tcp_port = 4242;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
boundary_state.ap_ssid[i] = EEPROM.read(config_addr(ADDR_CONF_AP_SSID + i));
|
||||
if (boundary_state.ap_ssid[i] == (char)0xFF) boundary_state.ap_ssid[i] = '\0';
|
||||
}
|
||||
boundary_state.ap_ssid[32] = '\0';
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
boundary_state.ap_psk[i] = EEPROM.read(config_addr(ADDR_CONF_AP_PSK + i));
|
||||
if (boundary_state.ap_psk[i] == (char)0xFF) boundary_state.ap_psk[i] = '\0';
|
||||
}
|
||||
boundary_state.ap_psk[32] = '\0';
|
||||
|
||||
// Reset runtime state
|
||||
boundary_state.packets_bridged_lora_to_tcp = 0;
|
||||
boundary_state.packets_bridged_tcp_to_lora = 0;
|
||||
boundary_state.last_bridge_activity = 0;
|
||||
boundary_state.wifi_connected = false;
|
||||
boundary_state.tcp_connected = false;
|
||||
boundary_state.ap_active = false;
|
||||
}
|
||||
|
||||
inline void boundary_save_config() {
|
||||
EEPROM.write(config_addr(ADDR_CONF_BMODE), BOUNDARY_ENABLE_BYTE);
|
||||
EEPROM.write(config_addr(ADDR_CONF_WIFI_EN),
|
||||
boundary_state.wifi_enabled ? BOUNDARY_ENABLE_BYTE : 0x00);
|
||||
EEPROM.write(config_addr(ADDR_CONF_BTCP_MODE), boundary_state.tcp_mode);
|
||||
EEPROM.write(config_addr(ADDR_CONF_BTCP_PORT), (boundary_state.tcp_port >> 8) & 0xFF);
|
||||
EEPROM.write(config_addr(ADDR_CONF_BTCP_PORT + 1), boundary_state.tcp_port & 0xFF);
|
||||
for (int i = 0; i < 63; i++) {
|
||||
EEPROM.write(config_addr(ADDR_CONF_BHOST + i), boundary_state.backbone_host[i]);
|
||||
}
|
||||
EEPROM.write(config_addr(ADDR_CONF_BHOST + 63), 0x00);
|
||||
EEPROM.write(config_addr(ADDR_CONF_BHPORT), (boundary_state.backbone_port >> 8) & 0xFF);
|
||||
EEPROM.write(config_addr(ADDR_CONF_BHPORT + 1), boundary_state.backbone_port & 0xFF);
|
||||
|
||||
// AP TCP server settings
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_TCP_EN),
|
||||
boundary_state.ap_tcp_enabled ? BOUNDARY_ENABLE_BYTE : 0x00);
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_TCP_PORT), (boundary_state.ap_tcp_port >> 8) & 0xFF);
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_TCP_PORT + 1), boundary_state.ap_tcp_port & 0xFF);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_SSID + i), boundary_state.ap_ssid[i]);
|
||||
}
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_SSID + 32), 0x00);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_PSK + i), boundary_state.ap_psk[i]);
|
||||
}
|
||||
EEPROM.write(config_addr(ADDR_CONF_AP_PSK + 32), 0x00);
|
||||
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
#endif // BOUNDARY_MODE
|
||||
#endif // BOUNDARY_MODE_H
|
||||
BIN
Builds/Handheld RNode/Case_Battery_Door.stl
Executable file
BIN
Builds/Handheld RNode/Case_Bottom_Large_Battery.stl
Executable file
BIN
Builds/Handheld RNode/Case_Bottom_No_Battery.stl
Executable file
BIN
Builds/Handheld RNode/Case_Bottom_Small_Battery.stl
Executable file
BIN
Builds/Handheld RNode/Case_Top.stl
Executable file
BIN
Builds/Handheld RNode/Handheld_RNode_Recipe.pdf
Executable file
BIN
Builds/Handheld RNode/LED_Guide.stl
Executable file
BIN
Builds/Handheld RNode/LED_Window.stl
Executable file
BIN
Builds/Handheld RNode/Power_Switch.stl
Executable file
242
Config.h
Executable file
@@ -0,0 +1,242 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "ROM.h"
|
||||
#include "Boards.h"
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define MAJ_VERS 0x01
|
||||
#define MIN_VERS 0x55
|
||||
|
||||
#define MODE_HOST 0x11
|
||||
#define MODE_TNC 0x12
|
||||
|
||||
#define CABLE_STATE_DISCONNECTED 0x00
|
||||
#define CABLE_STATE_CONNECTED 0x01
|
||||
uint8_t cable_state = CABLE_STATE_DISCONNECTED;
|
||||
|
||||
#define BT_STATE_NA 0xff
|
||||
#define BT_STATE_OFF 0x00
|
||||
#define BT_STATE_ON 0x01
|
||||
#define BT_STATE_PAIRING 0x02
|
||||
#define BT_STATE_CONNECTED 0x03
|
||||
uint8_t bt_state = BT_STATE_NA;
|
||||
uint32_t bt_ssp_pin = 0;
|
||||
bool bt_ready = false;
|
||||
bool bt_enabled = false;
|
||||
bool bt_allow_pairing = false;
|
||||
|
||||
#define WR_CHANNEL_DEFAULT 1
|
||||
#define WR_WIFI_OFF 0x00
|
||||
#define WR_WIFI_STA 0x01
|
||||
#define WR_WIFI_AP 0x02
|
||||
#define WR_STATE_NA 0xff
|
||||
#define WR_STATE_OFF 0x00
|
||||
#define WR_STATE_ON 0x01
|
||||
#define WR_STATE_CONNECTED 0x02
|
||||
uint8_t wr_state = WR_STATE_OFF;
|
||||
uint8_t wr_channel = WR_CHANNEL_DEFAULT;
|
||||
|
||||
#define M_FRQ_S 27388122
|
||||
#define M_FRQ_R 27388061
|
||||
bool console_active = false;
|
||||
bool modem_installed = false;
|
||||
|
||||
#define MTU 508
|
||||
#define SINGLE_MTU 255
|
||||
#define HEADER_L 1
|
||||
#define MIN_L 1
|
||||
#define CMD_L 64
|
||||
|
||||
bool mw_radio_online = false;
|
||||
|
||||
#define eeprom_addr(a) (a+EEPROM_OFFSET)
|
||||
#define config_addr(a) (a+CONFIG_OFFSET)
|
||||
|
||||
#if (MODEM == SX1262 || MODEM == SX1280) && defined(NRF52840_XXAA)
|
||||
SPIClass spiModem(NRF_SPIM2, pin_miso, pin_sclk, pin_mosi);
|
||||
#endif
|
||||
|
||||
// MCU independent configuration parameters
|
||||
const long serial_baudrate = 115200;
|
||||
|
||||
// SX1276 RSSI offset to get dBm value from
|
||||
// packet RSSI register
|
||||
const int rssi_offset = 157;
|
||||
|
||||
// Default LoRa settings
|
||||
#define PHY_HEADER_LORA_SYMBOLS 20
|
||||
#define PHY_CRC_LORA_BITS 16
|
||||
#define LORA_PREAMBLE_SYMBOLS_MIN 18
|
||||
#define LORA_PREAMBLE_TARGET_MS 24
|
||||
#define LORA_PREAMBLE_FAST_DELTA 18
|
||||
#define LORA_FAST_THRESHOLD_BPS 30E3
|
||||
#define LORA_LIMIT_THRESHOLD_BPS 60E3
|
||||
#define LORA_GUARD_THRESHOLD_BPS 14E3
|
||||
#define LORA_FAST_GUARD_MS 48
|
||||
long lora_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN;
|
||||
long lora_preamble_time_ms = 0;
|
||||
long lora_header_time_ms = 0;
|
||||
float lora_symbol_time_ms = 0.0;
|
||||
float lora_symbol_rate = 0.0;
|
||||
float lora_us_per_byte = 0.0;
|
||||
bool lora_low_datarate = false;
|
||||
bool lora_limit_rate = false;
|
||||
bool lora_guard_rate = false;
|
||||
|
||||
// CSMA Parameters
|
||||
#define CSMA_SIFS_MS 0
|
||||
#define CSMA_POST_TX_YIELD_SLOTS 3
|
||||
#define CSMA_SLOT_MAX_MS 100
|
||||
#define CSMA_SLOT_MIN_MS 24
|
||||
#define CSMA_SLOT_MIN_FAST_DELTA 18
|
||||
#define CSMA_SLOT_SYMBOLS 12
|
||||
#define CSMA_CW_BANDS 4
|
||||
#define CSMA_CW_MIN 0
|
||||
#define CSMA_CW_PER_BAND_WINDOWS 15
|
||||
#define CSMA_BAND_1_MAX_AIRTIME 7
|
||||
#define CSMA_BAND_N_MIN_AIRTIME 85
|
||||
#define CSMA_INFR_THRESHOLD_DB 11
|
||||
#define CSMA_RFENV_RECAL_MS 2500
|
||||
#define CSMA_RFENV_RECAL_LIMIT_DB -83
|
||||
bool interference_detected = false;
|
||||
bool avoid_interference = true;
|
||||
int csma_slot_ms = CSMA_SLOT_MIN_MS;
|
||||
unsigned long difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms;
|
||||
unsigned long difs_wait_start = -1;
|
||||
unsigned long cw_wait_start = -1;
|
||||
unsigned long cw_wait_target = -1;
|
||||
unsigned long cw_wait_passed = 0;
|
||||
int csma_cw = -1;
|
||||
uint8_t cw_band = 1;
|
||||
uint8_t cw_min = 0;
|
||||
uint8_t cw_max = CSMA_CW_PER_BAND_WINDOWS;
|
||||
|
||||
// LoRa settings
|
||||
int lora_sf = 0;
|
||||
int lora_cr = 5;
|
||||
int lora_txp = 0xFF;
|
||||
uint32_t lora_bw = 0;
|
||||
uint32_t lora_freq = 0;
|
||||
uint32_t lora_bitrate = 0;
|
||||
|
||||
// Operational variables
|
||||
bool radio_locked = true;
|
||||
bool radio_online = false;
|
||||
bool community_fw = true;
|
||||
bool hw_ready = false;
|
||||
bool radio_error = false;
|
||||
bool disp_ready = false;
|
||||
bool pmu_ready = false;
|
||||
bool promisc = false;
|
||||
bool implicit = false;
|
||||
bool memory_low = false;
|
||||
uint8_t implicit_l = 0;
|
||||
|
||||
uint8_t op_mode = MODE_HOST;
|
||||
uint8_t model = 0x00;
|
||||
uint8_t hwrev = 0x00;
|
||||
|
||||
#define NOISE_FLOOR_SAMPLES 128
|
||||
int noise_floor = -292;
|
||||
int current_rssi = -292;
|
||||
int last_rssi = -292;
|
||||
uint8_t last_rssi_raw = 0x00;
|
||||
uint8_t last_snr_raw = 0x80;
|
||||
uint8_t seq = 0xFF;
|
||||
uint16_t read_len = 0;
|
||||
uint16_t host_write_len = 0;
|
||||
|
||||
// Incoming packet buffer
|
||||
uint8_t pbuf[MTU];
|
||||
|
||||
// KISS command buffer
|
||||
uint8_t cmdbuf[CMD_L];
|
||||
|
||||
// LoRa transmit buffer
|
||||
uint8_t tbuf[MTU];
|
||||
|
||||
uint32_t stat_rx = 0;
|
||||
uint32_t stat_tx = 0;
|
||||
|
||||
#define STATUS_INTERVAL_MS 3
|
||||
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
|
||||
#define DCD_SAMPLES 2500
|
||||
#define UTIL_UPDATE_INTERVAL_MS 1000
|
||||
#define UTIL_UPDATE_INTERVAL (UTIL_UPDATE_INTERVAL_MS/STATUS_INTERVAL_MS)
|
||||
#define AIRTIME_LONGTERM 3600
|
||||
#define AIRTIME_LONGTERM_MS (AIRTIME_LONGTERM*1000)
|
||||
#define AIRTIME_BINLEN_MS (STATUS_INTERVAL_MS*DCD_SAMPLES)
|
||||
#define AIRTIME_BINS ((AIRTIME_LONGTERM*1000)/AIRTIME_BINLEN_MS)
|
||||
bool util_samples[DCD_SAMPLES];
|
||||
uint16_t airtime_bins[AIRTIME_BINS];
|
||||
float longterm_bins[AIRTIME_BINS];
|
||||
int dcd_sample = 0;
|
||||
float local_channel_util = 0.0;
|
||||
float total_channel_util = 0.0;
|
||||
float longterm_channel_util = 0.0;
|
||||
float airtime = 0.0;
|
||||
float longterm_airtime = 0.0;
|
||||
#define current_airtime_bin(void) (millis()%AIRTIME_LONGTERM_MS)/AIRTIME_BINLEN_MS
|
||||
#endif
|
||||
float st_airtime_limit = 0.0;
|
||||
float lt_airtime_limit = 0.0;
|
||||
bool airtime_lock = false;
|
||||
|
||||
bool stat_signal_detected = false;
|
||||
bool stat_signal_synced = false;
|
||||
bool stat_rx_ongoing = false;
|
||||
bool dcd = false;
|
||||
bool dcd_led = false;
|
||||
bool dcd_waiting = false;
|
||||
long dcd_wait_until = 0;
|
||||
uint16_t dcd_count = 0;
|
||||
uint16_t dcd_threshold = 2;
|
||||
|
||||
uint32_t status_interval_ms = STATUS_INTERVAL_MS;
|
||||
uint32_t last_status_update = 0;
|
||||
uint32_t last_dcd = 0;
|
||||
|
||||
// Power management
|
||||
#define BATTERY_STATE_UNKNOWN 0x00
|
||||
#define BATTERY_STATE_DISCHARGING 0x01
|
||||
#define BATTERY_STATE_CHARGING 0x02
|
||||
#define BATTERY_STATE_CHARGED 0x03
|
||||
bool battery_installed = false;
|
||||
bool battery_indeterminate = false;
|
||||
bool external_power = false;
|
||||
bool battery_ready = false;
|
||||
float battery_voltage = 0.0;
|
||||
float battery_percent = 0.0;
|
||||
uint8_t battery_state = 0x00;
|
||||
uint8_t display_intensity = 0xFF;
|
||||
uint8_t display_addr = 0xFF;
|
||||
volatile bool display_updating = false;
|
||||
bool display_blanking_enabled = false;
|
||||
bool display_diagnostics = true;
|
||||
bool device_init_done = false;
|
||||
bool eeprom_ok = false;
|
||||
bool firmware_update_mode = false;
|
||||
bool serial_in_frame = false;
|
||||
|
||||
// Boot flags
|
||||
#define START_FROM_BOOTLOADER 0x01
|
||||
#define START_FROM_POWERON 0x02
|
||||
#define START_FROM_BROWNOUT 0x03
|
||||
#define START_FROM_JTAG 0x04
|
||||
|
||||
#endif
|
||||
203
Console.h
Executable file
@@ -0,0 +1,203 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <FS.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
#include "SD.h"
|
||||
#include "SPI.h"
|
||||
|
||||
#if HAS_SD
|
||||
SPIClass *spi = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#include "esp32c3/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#include "esp32s3/rom/rtc.h"
|
||||
#else
|
||||
#error Target CONFIG_IDF_TARGET is not supported
|
||||
#endif
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
void console_dbg(String msg) {
|
||||
Serial.print("[Webserver] ");
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
bool exists(String path){
|
||||
bool yes = false;
|
||||
File file = SPIFFS.open(path, "r");
|
||||
if(!file.isDirectory()){
|
||||
yes = true;
|
||||
}
|
||||
file.close();
|
||||
return yes;
|
||||
}
|
||||
|
||||
String console_get_content_type(String filename) {
|
||||
if (server.hasArg("download")) {
|
||||
return "application/octet-stream";
|
||||
} else if (filename.endsWith(".htm")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".html")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (filename.endsWith(".js")) {
|
||||
return "application/javascript";
|
||||
} else if (filename.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (filename.endsWith(".gif")) {
|
||||
return "image/gif";
|
||||
} else if (filename.endsWith(".jpg")) {
|
||||
return "image/jpeg";
|
||||
} else if (filename.endsWith(".ico")) {
|
||||
return "image/x-icon";
|
||||
} else if (filename.endsWith(".xml")) {
|
||||
return "text/xml";
|
||||
} else if (filename.endsWith(".pdf")) {
|
||||
return "application/x-pdf";
|
||||
} else if (filename.endsWith(".zip")) {
|
||||
return "application/x-zip";
|
||||
} else if (filename.endsWith(".gz")) {
|
||||
return "application/x-gzip";
|
||||
} else if (filename.endsWith(".whl")) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool console_serve_file(String path) {
|
||||
console_dbg("Request for: "+path);
|
||||
if (path.endsWith("/")) {
|
||||
path += "index.html";
|
||||
}
|
||||
|
||||
if (path == "/r/manual/index.html") {
|
||||
path = "/m.html";
|
||||
}
|
||||
if (path == "/r/manual/Reticulum Manual.pdf") {
|
||||
path = "/h.html";
|
||||
}
|
||||
|
||||
|
||||
String content_type = console_get_content_type(path);
|
||||
String pathWithGz = path + ".gz";
|
||||
if (exists(pathWithGz) || exists(path)) {
|
||||
if (exists(pathWithGz)) {
|
||||
path += ".gz";
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(path, "r");
|
||||
console_dbg("Serving file to client");
|
||||
server.streamFile(file, content_type);
|
||||
file.close();
|
||||
|
||||
console_dbg("File serving done\n");
|
||||
return true;
|
||||
} else {
|
||||
int spos = pathWithGz.lastIndexOf('/');
|
||||
if (spos > 0) {
|
||||
String remap_path = "/d";
|
||||
remap_path.concat(pathWithGz.substring(spos));
|
||||
Serial.println(remap_path);
|
||||
|
||||
if (exists(remap_path)) {
|
||||
File file = SPIFFS.open(remap_path, "r");
|
||||
console_dbg("Serving remapped file to client");
|
||||
server.streamFile(file, content_type);
|
||||
console_dbg("Closing file");
|
||||
file.close();
|
||||
|
||||
console_dbg("File serving done\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console_dbg("Error: Could not open file for serving\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
void console_register_pages() {
|
||||
server.onNotFound([]() {
|
||||
if (!console_serve_file(server.uri())) {
|
||||
server.send(404, "text/plain", "Not Found");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void console_start() {
|
||||
Serial.println("");
|
||||
console_dbg("Starting Access Point...");
|
||||
WiFi.softAP(bt_devname);
|
||||
delay(150);
|
||||
IPAddress ip(10, 0, 0, 1);
|
||||
IPAddress nm(255, 255, 255, 0);
|
||||
WiFi.softAPConfig(ip, ip, nm);
|
||||
|
||||
if(!SPIFFS.begin(true)){
|
||||
console_dbg("Error: Could not mount SPIFFS");
|
||||
return;
|
||||
} else {
|
||||
console_dbg("SPIFFS Ready");
|
||||
}
|
||||
|
||||
#if HAS_SD
|
||||
spi = new SPIClass(HSPI);
|
||||
spi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS);
|
||||
if(!SD.begin(SD_CS, *spi)){
|
||||
console_dbg("No SD card inserted");
|
||||
} else {
|
||||
uint8_t cardType = SD.cardType();
|
||||
if(cardType == CARD_NONE){
|
||||
console_dbg("No SD card type");
|
||||
} else {
|
||||
console_dbg("SD Card Type: ");
|
||||
if(cardType == CARD_MMC){
|
||||
console_dbg("MMC");
|
||||
} else if(cardType == CARD_SD){
|
||||
console_dbg("SDSC");
|
||||
} else if(cardType == CARD_SDHC){
|
||||
console_dbg("SDHC");
|
||||
} else {
|
||||
console_dbg("UNKNOWN");
|
||||
}
|
||||
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
||||
Serial.printf("SD Card Size: %lluMB\n", cardSize);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
console_register_pages();
|
||||
server.begin();
|
||||
led_indicate_console();
|
||||
}
|
||||
|
||||
void console_loop(){
|
||||
server.handleClient();
|
||||
// Internally, this yields the thread and allows
|
||||
// other tasks to run.
|
||||
delay(2);
|
||||
}
|
||||
47
Console/Makefile
Executable file
@@ -0,0 +1,47 @@
|
||||
PATH_RETICULUM_WEBSITE=../../sites/reticulum.network
|
||||
PATH_PACKAGES=../../dist_archive
|
||||
|
||||
clean:
|
||||
@echo Cleaning...
|
||||
@-rm -rf ./build
|
||||
|
||||
dirs:
|
||||
@mkdir -p ./build
|
||||
@mkdir -p ./build/3d
|
||||
@mkdir -p ./build/pkg
|
||||
@mkdir -p ./build/css
|
||||
@mkdir -p ./build/gfx
|
||||
@mkdir -p ./build/images
|
||||
|
||||
pages:
|
||||
python ./build.py
|
||||
|
||||
pages-debug:
|
||||
python ./build.py --no-gz --no-remap
|
||||
|
||||
sourcepack:
|
||||
@echo Packing firmware sources...
|
||||
zip --junk-paths -r build/pkg/rnode_firmware.zip ../arduino-cli.yaml ../BLESerial.cpp ../BLESerial.h ../Bluetooth.h ../Boards.h ../Config.h ../Console.h ../Device.h ../Display.h ../Framing.h ../Graphics.h ../LICENSE ../Makefile ../MD5.cpp ../MD5.h ../partition_hashes ../Power.h ../README.md ../release_hashes.py ../RNode_Firmware.ino ../ROM.h ../sx126x.cpp ../sx126x.h ../sx127x.cpp ../sx127x.h ../sx128x.cpp ../sx128x.h ../Utilities.h ../esp32_btbufs.py
|
||||
|
||||
data:
|
||||
@echo Including assets...
|
||||
@cp assets/css/* build/css/
|
||||
@cp assets/gfx/* build/gfx/
|
||||
@cp assets/images/* build/images/
|
||||
@cp assets/stl/* build/3d/
|
||||
#@cp assets/pkg/* build/pkg/
|
||||
# @cp assets/scripts/* build/scripts/
|
||||
# @cp -r ../../Reticulum/docs/manual/* build/reticulum_manual/
|
||||
# @cp -r ../../Reticulum/docs/Reticulum\ Manual.pdf build/reticulum_manual/
|
||||
|
||||
external:
|
||||
make -C $(PATH_RETICULUM_WEBSITE) clean website
|
||||
-rm -r $(PATH_PACKAGES)/reticulum.network
|
||||
cp -r $(PATH_RETICULUM_WEBSITE)/build $(PATH_PACKAGES)/reticulum.network
|
||||
|
||||
site: clean external dirs data sourcepack pages
|
||||
|
||||
local: clean external dirs data sourcepack pages-debug
|
||||
|
||||
serve:
|
||||
python -m http.server 7777 --bind 127.0.0.1 --directory ./build
|
||||
1009
Console/assets/css/water.css
Executable file
BIN
Console/assets/css/yond.woff2
Executable file
BIN
Console/assets/gfx/cs.webp
Executable file
|
After Width: | Height: | Size: 806 B |
BIN
Console/assets/gfx/icon.png
Executable file
|
After Width: | Height: | Size: 955 B |
BIN
Console/assets/gfx/nn.webp
Executable file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Console/assets/gfx/ph.png
Executable file
|
After Width: | Height: | Size: 747 B |
BIN
Console/assets/gfx/rnode_iso.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
Console/assets/gfx/sideband.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
Console/assets/images/3_conv.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
Console/assets/images/an1.webp
Executable file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Console/assets/images/bg1ds1.webp
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
Console/assets/images/bg1ds2.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
Console/assets/images/bg_h_1.webp
Executable file
|
After Width: | Height: | Size: 33 KiB |
BIN
Console/assets/images/bg_h_2.webp
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
Console/assets/images/g1p.webp
Executable file
|
After Width: | Height: | Size: 23 KiB |
BIN
Console/assets/images/g2p.webp
Executable file
|
After Width: | Height: | Size: 32 KiB |
BIN
Console/assets/images/g3p.webp
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
Console/assets/images/g4p.webp
Executable file
|
After Width: | Height: | Size: 24 KiB |
BIN
Console/assets/images/lora_rnodes.webp
Executable file
|
After Width: | Height: | Size: 35 KiB |
BIN
Console/assets/images/nn_an.webp
Executable file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Console/assets/images/nn_conv.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
Console/assets/images/nn_init.webp
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Console/assets/stl/Handheld_RNode_Parts.7z
Executable file
373
Console/build.py
Executable file
@@ -0,0 +1,373 @@
|
||||
import markdown
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
packages = {
|
||||
"rns": "rns-1.0.3-py3-none-any.whl",
|
||||
"nomadnet": "nomadnet-0.9.1-py3-none-any.whl",
|
||||
"lxmf": "lxmf-0.9.3-py3-none-any.whl",
|
||||
"rnsh": "rnsh-0.1.5-py3-none-any.whl",
|
||||
}
|
||||
|
||||
DEFAULT_TITLE = "RNode Bootstrap Console"
|
||||
SOURCES_PATH="./source"
|
||||
BUILD_PATH="./build"
|
||||
PACKAGES_PATH = "../../dist_archive"
|
||||
RNS_SOURCE_PATH = "../../Reticulum"
|
||||
INPUT_ENCODING="utf-8"
|
||||
OUTPUT_ENCODING="utf-8"
|
||||
|
||||
LXMF_ADDRESS = "8dd57a738226809646089335a6b03695"
|
||||
|
||||
document_start = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="{ASSET_PATH}css/water.css?v=4">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{ASSET_PATH}gfx/icon.png">
|
||||
<meta charset="utf-8"/>
|
||||
<title>{PAGE_TITLE}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<div id="load_overlay" style="background-color:#2a2a2f; position:absolute; top:0px; left:0px; width:100%; height:100%; z-index:2000;"></div>
|
||||
<span class="logo">RNode Console</span>
|
||||
{MENU}<hr>"""
|
||||
|
||||
document_end = """</body></html>"""
|
||||
|
||||
menu_md = """<center markdown=\"1\"><span class="menu">[Start]({CONTENT_PATH}index.html) | [Replicate]({CONTENT_PATH}replicate.html) | [Software]({CONTENT_PATH}software.html) | [Learn]({CONTENT_PATH}learn.html) | [Help](help.html) | [Contribute]({CONTENT_PATH}contribute.html)</span></center>"""
|
||||
|
||||
manual_redirect = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url=/m/index.html">
|
||||
</head>
|
||||
</html>
|
||||
"""
|
||||
help_redirect = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url=/help.html">
|
||||
</head>
|
||||
</html>
|
||||
"""
|
||||
|
||||
url_maps = [
|
||||
# { "path": "", "target": "/.md"},
|
||||
]
|
||||
|
||||
def scan_pages(base_path):
|
||||
files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
|
||||
page_sources = []
|
||||
|
||||
for file in files:
|
||||
if file.endswith(".md"):
|
||||
page_sources.append(base_path+"/"+file)
|
||||
|
||||
for directory in directories:
|
||||
page_sources.extend(scan_pages(base_path+"/"+directory))
|
||||
|
||||
return page_sources
|
||||
|
||||
def get_prop(md, prop):
|
||||
try:
|
||||
pt = "["+prop+"]: <> ("
|
||||
pp = md.find(pt)
|
||||
if pp != -1:
|
||||
ps = pp+len(pt)
|
||||
pe = md.find(")", ps)
|
||||
return md[ps:pe]
|
||||
else:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print("Error while extracting topic property: "+str(e))
|
||||
return None
|
||||
|
||||
def list_topic(topic):
|
||||
base_path = SOURCES_PATH+"/"+topic
|
||||
files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "." and file != "index.md"]
|
||||
|
||||
topic_entries = []
|
||||
for file in files:
|
||||
if file.endswith(".md"):
|
||||
fp = base_path+"/"+file
|
||||
f = open(fp, "rb")
|
||||
link_path = fp.replace(SOURCES_PATH, ".").replace(".md", ".html")
|
||||
|
||||
md = f.read().decode(INPUT_ENCODING)
|
||||
topic_entries.append({
|
||||
"title": get_prop(md, "title"),
|
||||
"image": get_prop(md, "image"),
|
||||
"date": get_prop(md, "date"),
|
||||
"excerpt": get_prop(md, "excerpt"),
|
||||
"md": md,
|
||||
"file": link_path
|
||||
})
|
||||
|
||||
topic_entries.sort(key=lambda e: e["date"], reverse=True)
|
||||
return topic_entries
|
||||
|
||||
def render_topic(topic_entries):
|
||||
md = ""
|
||||
for topic in topic_entries:
|
||||
md += "<a class=\"topic_link\" href=\""+str(topic["file"])+"\">"
|
||||
md += "<span class=\"topic\">"
|
||||
md += "<img class=\"topic_image\" src=\""+str(topic["image"])+"\"/>"
|
||||
md += "<span class=\"topic_title\">"+str(topic["title"])+"</span>"
|
||||
#md += "<span class=\"topic_date\">"+str(topic["date"])+"</span>"
|
||||
md += "<span class=\"topic_excerpt\">"+str(topic["excerpt"])+"</span>"
|
||||
md += "</span>"
|
||||
md += "</a>"
|
||||
|
||||
|
||||
return md
|
||||
|
||||
def generate_html(f, root_path):
|
||||
md = f.read().decode(INPUT_ENCODING)
|
||||
|
||||
page_title = get_prop(md, "title")
|
||||
if page_title == None:
|
||||
page_title = DEFAULT_TITLE
|
||||
else:
|
||||
page_title += " | "+DEFAULT_TITLE
|
||||
|
||||
tt = "{TOPIC:"
|
||||
tp = md.find(tt)
|
||||
if tp != -1:
|
||||
ts = tp+len(tt)
|
||||
te = md.find("}", ts)
|
||||
topic = md[ts:te]
|
||||
|
||||
rt = tt+topic+"}"
|
||||
tl = render_topic(list_topic(topic))
|
||||
print("Found topic: "+str(topic)+", rt "+str(rt))
|
||||
md = md.replace(rt, tl)
|
||||
|
||||
menu_html = markdown.markdown(menu_md.replace("{CONTENT_PATH}", root_path), extensions=["md_in_html", "markdown.extensions.fenced_code", "sane_lists"]).replace("<p></p>", "")
|
||||
page_html = markdown.markdown(md, extensions=["md_in_html", "markdown.extensions.fenced_code"]).replace("{ASSET_PATH}", root_path)
|
||||
page_html = page_html.replace("{LXMF_ADDRESS}", LXMF_ADDRESS)
|
||||
for pkg_name in packages:
|
||||
page_html = page_html.replace("{PKG_"+pkg_name+"}", "pkg/"+pkg_name+".zip")
|
||||
page_html = page_html.replace("{PKG_BASE_"+pkg_name+"}", pkg_name+".zip")
|
||||
page_html = page_html.replace("{PKG_NAME_"+pkg_name+"}", packages[pkg_name])
|
||||
|
||||
page_date = get_prop(md, "date")
|
||||
if page_date != None:
|
||||
page_html = page_html.replace("{DATE}", page_date)
|
||||
|
||||
return document_start.replace("{ASSET_PATH}", root_path).replace("{MENU}", menu_html).replace("{PAGE_TITLE}", page_title) + page_html + document_end
|
||||
|
||||
source_files = scan_pages(SOURCES_PATH)
|
||||
|
||||
mf = open(BUILD_PATH+"/m.html", "w")
|
||||
mf.write(manual_redirect)
|
||||
mf.close()
|
||||
mf = open(BUILD_PATH+"/h.html", "w")
|
||||
mf.write(help_redirect)
|
||||
mf.close()
|
||||
|
||||
def optimise_manual(path):
|
||||
pm = 180
|
||||
scale_imgs = [
|
||||
("_images/board_rnodev2.png", pm),
|
||||
("_images/board_rnode.png", pm),
|
||||
("_images/board_heltec32v20.png", pm),
|
||||
("_images/board_heltec32v30.png", pm),
|
||||
("_images/board_heltec32v4.png", pm),
|
||||
("_images/board_t3v21.png", pm),
|
||||
("_images/board_t3v20.png", pm),
|
||||
("_images/board_t3v10.png", pm),
|
||||
("_images/board_t3s3.png", pm),
|
||||
("_images/board_tbeam.png", pm),
|
||||
("_images/board_tdeck.png", pm),
|
||||
("_images/board_rak4631.png", pm),
|
||||
("_images/board_tbeam_supreme.png", pm),
|
||||
("_images/sideband_devices.webp", pm),
|
||||
("_images/nomadnet_3.png", pm),
|
||||
("_images/meshchat_1.webp", pm),
|
||||
("_images/radio_is5ac.png", pm),
|
||||
("_images/radio_rblhg5.png", pm),
|
||||
("_static/rns_logo_512.png", 256),
|
||||
("../images/bg_h_1.webp", pm),
|
||||
]
|
||||
|
||||
import subprocess
|
||||
import shlex
|
||||
for i,s in scale_imgs:
|
||||
fp = path+"/"+i
|
||||
input_file = fp
|
||||
output_file = input_file
|
||||
resize = "convert "+input_file+" -quality 25 -resize "+str(s)+" "+output_file
|
||||
print(resize)
|
||||
subprocess.call(shlex.split(resize), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
# if output_file != input_file and os.path.isfile(input_file): os.unlink(input_file)
|
||||
|
||||
remove_files = [
|
||||
"objects.inv",
|
||||
"Reticulum Manual.pdf",
|
||||
"Reticulum Manual.epub",
|
||||
"_static/styles/furo.css.map",
|
||||
"_static/scripts/furo.js.map",
|
||||
"_static/jquery-3.6.0.js",
|
||||
"_static/jquery.js",
|
||||
"static/underscore-1.13.1.js",
|
||||
"_static/_sphinx_javascript_frameworks_compat.js",
|
||||
"_static/scripts/furo.js.LICENSE.txt",
|
||||
"_static/styles/furo-extensions.css.map",
|
||||
"_images/board_rak4631.png",
|
||||
"_images/board_rnodev2.png",
|
||||
"_images/board_t114.png",
|
||||
"_images/board_t3s3.png",
|
||||
"_images/board_t3v10.png",
|
||||
"_images/board_t3v20.png",
|
||||
"_images/board_t3v21.png",
|
||||
"_images/board_tbeam.png",
|
||||
"_images/board_tdeck.png",
|
||||
"_images/board_techo.png",
|
||||
"_images/board_tbeam_supreme.png",
|
||||
"_images/board_opencomxl.png",
|
||||
"_images/board_heltec32v20.png",
|
||||
"_images/board_heltec32v30.png",
|
||||
# "_static/pygments.css",
|
||||
# "_static/language_data.js",
|
||||
# "_static/searchtools.js",
|
||||
# "searchindex.js",
|
||||
]
|
||||
for file in remove_files:
|
||||
fp = path+"/"+file
|
||||
print("Removing file: "+str(fp))
|
||||
try:
|
||||
os.unlink(fp)
|
||||
except Exception as e:
|
||||
print("An error occurred while attempting to unlink "+str(fp)+": "+str(e))
|
||||
|
||||
remove_dirs = [
|
||||
"_sources",
|
||||
]
|
||||
for d in remove_dirs:
|
||||
fp = path+"/"+d
|
||||
print("Removing dir: "+str(fp))
|
||||
shutil.rmtree(fp)
|
||||
|
||||
shutil.move(path, BUILD_PATH+"/m")
|
||||
|
||||
def fetch_reticulum_site():
|
||||
r_site_path = BUILD_PATH+"/r"
|
||||
if not os.path.isdir(r_site_path):
|
||||
shutil.copytree(PACKAGES_PATH+"/reticulum.network", r_site_path)
|
||||
if os.path.isdir(r_site_path+"/manual"):
|
||||
optimise_manual(r_site_path+"/manual")
|
||||
remove_files = [
|
||||
"gfx/reticulum_logo_512.png",
|
||||
]
|
||||
for file in remove_files:
|
||||
fp = r_site_path+"/"+file
|
||||
print("Removing file: "+str(fp))
|
||||
os.unlink(fp)
|
||||
replace_paths()
|
||||
|
||||
def replace_paths():
|
||||
repls = [
|
||||
("gfx/reticulum_logo_512.png", "/m/_static/rns_logo_512.png")
|
||||
]
|
||||
for root, dirs, files in os.walk(BUILD_PATH):
|
||||
for file in files:
|
||||
fpath = root+"/"+file
|
||||
if fpath.endswith(".html"):
|
||||
print("Performing replacements in "+fpath+"")
|
||||
f = open(fpath, "rb")
|
||||
html = f.read().decode("utf-8")
|
||||
f.close()
|
||||
for s,r in repls:
|
||||
html = html.replace(s,r)
|
||||
f = open(fpath, "wb")
|
||||
f.write(html.encode("utf-8"))
|
||||
f.close()
|
||||
|
||||
# if not os.path.isdir(BUILD_PATH+"/d"):
|
||||
# os.makedirs(BUILD_PATH+"/d")
|
||||
# shutil.move(fpath, BUILD_PATH+"/d/")
|
||||
|
||||
|
||||
def remap_names():
|
||||
for root, dirs, files in os.walk(BUILD_PATH):
|
||||
for file in files:
|
||||
fpath = root+"/"+file
|
||||
spath = fpath.replace(BUILD_PATH, "")
|
||||
if len(spath) > 31:
|
||||
print("Path "+spath+" is too long, remapping...")
|
||||
if not os.path.isdir(BUILD_PATH+"/d"):
|
||||
os.makedirs(BUILD_PATH+"/d")
|
||||
shutil.move(fpath, BUILD_PATH+"/d/")
|
||||
|
||||
|
||||
|
||||
def gz_all():
|
||||
import gzip
|
||||
for root, dirs, files in os.walk(BUILD_PATH):
|
||||
for file in files:
|
||||
fpath = root+"/"+file
|
||||
print("Gzipping "+fpath+"...")
|
||||
f = open(fpath, "rb")
|
||||
g = gzip.open(fpath+".gz", "wb")
|
||||
g.writelines(f)
|
||||
g.close()
|
||||
f.close()
|
||||
os.unlink(fpath)
|
||||
|
||||
from zipfile import ZipFile
|
||||
for pkg_name in packages:
|
||||
pkg_file = packages[pkg_name]
|
||||
pkg_full_path = PACKAGES_PATH+"/"+pkg_file
|
||||
if os.path.isfile(pkg_full_path):
|
||||
print("Including "+pkg_file)
|
||||
z = ZipFile(BUILD_PATH+"/pkg/"+pkg_name+".zip", "w")
|
||||
z.write(pkg_full_path, pkg_full_path[len(PACKAGES_PATH+"/"):])
|
||||
z.close()
|
||||
# shutil.copy(pkg_full_path, BUILD_PATH+"/"+pkg_name)
|
||||
|
||||
else:
|
||||
print("Could not find "+pkg_full_path)
|
||||
exit(1)
|
||||
|
||||
for um in url_maps:
|
||||
with open(SOURCES_PATH+"/"+um["target"], "rb") as f:
|
||||
of = BUILD_PATH+um["target"].replace(SOURCES_PATH, "").replace(".md", ".html")
|
||||
root_path = "../"
|
||||
html = generate_html(f, root_path)
|
||||
|
||||
print("Map path : "+str(um["path"]))
|
||||
print("Map target : "+str(um["target"]))
|
||||
print("Mapped root path: "+str(root_path))
|
||||
|
||||
if not os.path.isdir(BUILD_PATH+"/"+um["path"]):
|
||||
os.makedirs(BUILD_PATH+"/"+um["path"], exist_ok=True)
|
||||
|
||||
with open(BUILD_PATH+"/"+um["path"]+"/index.html", "wb") as wf:
|
||||
wf.write(html.encode(OUTPUT_ENCODING))
|
||||
|
||||
for mdf in source_files:
|
||||
with open(mdf, "rb") as f:
|
||||
of = BUILD_PATH+mdf.replace(SOURCES_PATH, "").replace(".md", ".html")
|
||||
root_path = "../"*(len(of.replace(BUILD_PATH+"/", "").split("/"))-1)
|
||||
html = generate_html(f, root_path)
|
||||
|
||||
if not os.path.isdir(os.path.dirname(of)):
|
||||
os.makedirs(os.path.dirname(of), exist_ok=True)
|
||||
|
||||
with open(of, "wb") as wf:
|
||||
wf.write(html.encode(OUTPUT_ENCODING))
|
||||
|
||||
fetch_reticulum_site()
|
||||
if not "--no-gz" in sys.argv:
|
||||
gz_all()
|
||||
|
||||
if not "--no-remap" in sys.argv:
|
||||
remap_names()
|
||||
8
Console/source/builds/ap.md
Executable file
@@ -0,0 +1,8 @@
|
||||
[date]: <> (2023-01-12)
|
||||
[title]: <> (Outdoor RNode)
|
||||
[image]: <> (gfx/cs.webp)
|
||||
[excerpt]: <> (An outdoor-mountable RNode suitable for Access Point or network extension operation. Also supports high-capacity batteries and solar charging.)
|
||||
## Outdoor AP RNode
|
||||
This RNode comes with a weather-proof case and convenient cable management options, suitable for outdoor mounting and operation. It is possible to mount this RNode directly to masts and antennas, and it supports high-capacity batteries and solar charging.
|
||||
|
||||
This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it!
|
||||
168
Console/source/builds/handheld.md
Executable file
@@ -0,0 +1,168 @@
|
||||
[date]: <> (2023-01-14)
|
||||
[title]: <> (Handheld RNode)
|
||||
[image]: <> (gfx/rnode_iso.webp)
|
||||
[excerpt]: <> (This RNode is suitable for mobile and handheld operation, and offers both wireless and wired connectivity to host devices. A good all-round unit. It is also suitable for permanent installation indoors.)
|
||||
## Handheld RNode Recipe
|
||||
*Version 2.1*
|
||||
|
||||
This build recipe will help you create an RNode that is suitable for mobile and handheld operation, and offers both wireless and wired connectivity to host devices. It is also useful for permanent installation indoors, or even outdoors, as long as it is protected from water ingress and direct sunlight.
|
||||
|
||||
Depending on the board you use, it will offer a workable frequency range between **420 and 520 MHz**, or **820 and 1020 MHz**, and a maximum TX power of **17 dBm** (50 mW).
|
||||
|
||||
<img alt="Completed Handheld RNode" src="{ASSET_PATH}images/bg_h_1.webp" style="width: 100%;"/>
|
||||
<center>*A completed Handheld RNode*</center>
|
||||
|
||||
### Table of Contents
|
||||
|
||||
1. [Preparation](#prep)
|
||||
2. [Supported Boards](#devboard)
|
||||
3. [Materials](#materials)
|
||||
4. [Print Parts](#parts)
|
||||
5. [Install Tools](#tools)
|
||||
6. [Firmware Setup](#firmware)
|
||||
7. [Assembly](#assembly)
|
||||
|
||||
|
||||
### <a name="prep"></a>Step 1: Preparation
|
||||
|
||||
When you have completed this recipe, you will end up with a fully-featured RNode device, similar to the one pictured above. To make it as easy as possible to complete this guide, make sure to read it all in its entirity *before* starting. I also recommend you familiarise yourself with the required materials, and the software tools needed for the setup.
|
||||
|
||||
To complete this build recipe, you will need access to the following items:
|
||||
|
||||
- A computer with a functional operating system, such as Linux, BSD or macOS
|
||||
- One of the [supported development boards](#devboard) for this recipe
|
||||
- A suitable USB cable for connecting the development board to your computer
|
||||
- A 3D printer and the necessary amount of material for printing the [device parts](#parts)
|
||||
- 6 pieces of M2x6mm screws to assemble the case
|
||||
- A suitable antenna
|
||||
- An optional NeoPixel RGB LED
|
||||
- An optional [battery](#battery)
|
||||
- This build can use any single-cell (3.7v) lithium battery with a 1.25mm JST connector, provided it will fit in the case. Please see [this section](#battery) for details on battery sizes.
|
||||
|
||||
### <a name="devboard"></a>Step 2: Supported Development Boards
|
||||
|
||||
This RNode design is using a **LilyGO LoRa32 v2.1** board, in either the **433 MHz**, **868 MHz**, **915 MHz** or **923 MHz** variants. It seems that the 868, 915 and 923 MHz variants are in fact completely identical, and all offer a frequency range between 820 and 1020 MHz. The 433MHz variants offer a frequency range between 420 and 520 MHz.
|
||||
|
||||
These boards are also sold under many different "brand" names other than LilyGO, but using the images below, you should be able to identify the correct ones.
|
||||
|
||||
It is easiest to obtain the version of the board with an **u.FL** (sometimes also labeled *IPX* or *IPEX*) antenna connector, instead of the **SMA** connector. This version comes with an SMA to u.FL pigtail, which is installed into the 3D-printed case. If it is not possible to obtain this version, you can use the one with an **SMA** connector, either as is, or by removing the **SMA** connector, and using the on-board **u.FL** connector instead.
|
||||
|
||||
If you do not wish to use the 3D-printable case included in this guide, it does not matter which version you get. There is **no functional difference** between the boards with **SMA** and **u.FL** connectors.
|
||||
|
||||
<img alt="Compatible board" src="{ASSET_PATH}images/bg_h_2.webp" style="width: 100%;"/>
|
||||
<center>*The correct board version for this RNode build recipe*</center>
|
||||
|
||||
If you want to use the case provided for this build guide, and you have the version with an *SMA* connector, you will have to desolder the **SMA** connector, and activate the *u.FL* connector instead (it's already installed on all the boards, just not activated on the **SMA** connector versions).
|
||||
|
||||
To activate the **u.FL** connector, you will just have to "rotate" the small resistor next to the antenna connectors by 90 degrees, so it "points" at the connector you wish to use.
|
||||
|
||||
Please note that the "resistor" is actually just a zero-ohm jumper. If you don't feel like fiddling around with small components, you can simply remove it, and bridge the relevant gap with a blob of solder.
|
||||
|
||||
Refer to the following two pictures to locate the resistor that needs moving:
|
||||
|
||||
<img alt="Before desoldering" src="{ASSET_PATH}images/bg1ds1.webp" style="display: inline-block;width: 48.7%; margin-right:1%;"/>
|
||||
<img alt="After desoldering" src="{ASSET_PATH}images/bg1ds2.webp" style="display: inline-block;width: 48.7%; margin-left: 1%;"/>
|
||||
<center>*Before and after removing the SMA connector and moving the resistor*</center>
|
||||
|
||||
You will also need to dismount the OLED display from the small acrylic riser on the board, and unscrew and discard the riser. Be careful not to damage the display or ribbon cable while doing this. The OLED display will be mounted directly into a matching slot in the 3D-printed case.
|
||||
|
||||
As before, if you do not want to use the 3D printed case supplied here, it's probably much easier to keep the display on the board, and you can simply skip this step.
|
||||
|
||||
### <a name="materials"></a>Step 3: Obtain Materials
|
||||
|
||||
In addition to the board, you will need a few other components to build this RNode.
|
||||
|
||||
- A suitable **antenna**. Most boards purchased online include a passable antenna, but you may want to upgrade it to a better one.
|
||||
- 6 pieces of **M2x6mm screws** for assembling the case. Can be bought in most hardware stores or from online vendors.
|
||||
- An optional **NeoPixel RGB LED** for displaying status, and TX/RX activity. If you do not want to add this, it can simply be omitted.
|
||||
- The easiest way is to use the PCB-mounted NeoPixel "mini-buttons" manufactured by [adafruit.com](https://www.adafruit.com/product/1612). These fit exactly into the slot in the mounting position in the 3D-printed case, and are easy to connect cables to.
|
||||
- An optional **lithium-polymer battery**.
|
||||
- This RNode supports **3.7v**, **single-cell** LiPo batteries with a **1.25mm JST connector**
|
||||
- The standard case can fit up to a 700mAh LP602248 battery
|
||||
- Maximum battery dimensions for this case is 50mm x 25mm x 6mm
|
||||
- There is a larger bottom casing available that fits 1100mAh batteries
|
||||
- Maximum battery dimensions for this case is 50mm x 25mm x 12mm
|
||||
|
||||
### <a name="parts"></a>Step 4: 3D Print Parts
|
||||
|
||||
To complete the build of this RNode, you will need to 3D-print the parts for the casing. Download, extract and slice the STL files from the [parts package]({ASSET_PATH}3d/Handheld_RNode_Parts.7z) in your preferred software.
|
||||
|
||||
- Two of the parts are LED light-guides, and should be printed in a semi-translucent material:
|
||||
- The `LED_Window.stl` file is a light-guide for the NeoPixel LED, mounted in the circular cutout at the top of the device.
|
||||
- The `LED_Guide.stl` file is a light-guide for the power and charging LEDs, mounted in the rectangular grove at the bottom of the device.
|
||||
- The rest of the parts can be printed in any material, but for durability and heat-resistance, PETG is recommended.
|
||||
- The `Power_Switch.stl` file is a small power-switch slider, mounted in the matching grove on the bottom-left of the device.
|
||||
- The `Case_Top.stl` file is the top shell of the case. It holds the OLED display and NeoPixel RGB LED, and mounts to the bottom shell of the case with 6 M2 screws. The screw holes in both the top and bottom shells of the case are dimensioned to be self-threading when screws are inserted for the first time. Do not over-tighten.
|
||||
- The `Case_Bottom_Small_Battery.stl` file is the default bottom shell of the case. It holds batteries up to approximately 700mAh.
|
||||
- The `Case_Bottom_Large_Battery.stl` file is an alternative bottom shell for the case. It holds batteries up to approximately 1100mAh.
|
||||
- The `Case_Bottom_No_Battery.stl` file is an alternative bottom shell for the case. It does not have space for a battery, but results in a very compact device.
|
||||
- The `Case_Battery_Door.stl` file is the door for the battery compartment of the device. It snap-fits tightly into place in the bottom shell, and features a small slot for opening with a flathead screwdriver or similar.
|
||||
|
||||
All files are dimensioned to fit together perfectly without any scaling on a well-tuned 3D-printer.
|
||||
|
||||
The recommended layer height for all files is 0.15mm for FDM printers.
|
||||
|
||||
### <a name="tools"></a>Step 5: Install Tools
|
||||
|
||||
To install and configure the RNode Firmware on the device, you will need to install the `rnodeconf` program on your computer. This is included in the `rns` package, that can be [installed directly from this RNode]({ASSET_PATH}s_rns.html). Please carry out the installation instructions on [this page]({ASSET_PATH}s_rns.html), and continue to the next step when the `rnodeconf` program is installed.
|
||||
|
||||
|
||||
### <a name="firmware"></a>Step 6: Firmware Setup
|
||||
|
||||
Once the `rnodeconf` program is installed, we will use it to install the RNode Firmware on your device, and do the initial provisioning of configuration parameters. This process can be completed automatically, by using the auto-installer. Run the `rnodeconf` auto-installer with the following command:
|
||||
|
||||
```
|
||||
rnodeconf --autoinstall
|
||||
```
|
||||
|
||||
1. The program will ask you to connect your device to an USB-port on your computer. Do so, and hit enter.
|
||||
2. Select the serial port the device is connected as.
|
||||
3. You will now be asked what device this is, select the option **A Specific Kind of RNode**.
|
||||
4. The installer will ask you what model your device is. Select the **Handheld RNode v2.x** option that matches the frequency band of your device.
|
||||
5. The installer will display a summary of your choices. If you are satisfied, confirm your selection.
|
||||
6. The installer will now automatically install and configure the firmware and prepare the device for use.
|
||||
|
||||
> **Please Note!** If you are connected to the Internet while installing, the autoinstaller will automatically download any needed firmware files to a local cache before installing.
|
||||
|
||||
> If you do not have an active Internet connection while installing, you can extract and use the firmware from this device instead. This will **only** work if you are building the same type of RNode as the device you are extracting from, as the firmware has to match the targeted board and hardware configuration.
|
||||
|
||||
If you need to extract the firmware from an existing RNode, run the following command:
|
||||
|
||||
```
|
||||
rnodeconf --extract
|
||||
```
|
||||
|
||||
If `rnodeconf` finds a working RNode, it will extract and save the firmware from the device for later use. You can then run the auto-installer with the `--use-extracted` option to use the locally extracted file:
|
||||
|
||||
```
|
||||
rnodeconf --autoinstall --use-extracted
|
||||
```
|
||||
|
||||
This also works for updating the firmware on existing RNodes, so you can extract a newer firmware from one RNode, and deploy it onto other RNodes using the same method. Just use the `--update` option instead of `--autoinstall`.
|
||||
|
||||
### <a name="assembly"></a>Step 7: Assembly
|
||||
|
||||
With the firmware installed and configured, and the case parts printed, it's time to put it all together.
|
||||
|
||||
1. Insert the **SMA to u.FL** pigtail adatper into the matching **slot** in the top part of the bottom shell. Make sure it lines up with the internal hex-nut cut-out in the bottom shell, as the hex nut of the adapter will get pulled into this cut-out, and thereby self-lock, when an antenna is connected. You can optionally mount a locking nut on the exterior thread of the SMA connector when the case has been completely assembled.
|
||||
2. Thread the cable of the **SMA to u.FL** pigtail adapter into the matching grove, and run it out of the bottom opening.
|
||||
3. Mount the **power-switch slider** into the matching slot, in the bottom-left part of the bottom shell.
|
||||
4. With the SMA connector and power switch mounted, slide the **board** into the bottom shell, such that the **power switch** of the **board** mates with the slot in the already installed power-switch slider. Click the **board** into place in the bottom shell.
|
||||
5. Optionally mount the **NeoPixel LED**:
|
||||
- Measure out cables that matches lenghts between the NeoPixel mounting slot, and the corresponding pins on the board.
|
||||
- Solder the **V+**, **GND** and **DATA** cables to the NeoPixel.
|
||||
- Solder the **V+** cable to the **3.3v** pin on the board.
|
||||
- Solder the **GND** cable to the **GND** pin on the board.
|
||||
- Solder the **DATA** cable to **IO Pin 12** on the board.
|
||||
- Mount the **NeoPixel** in the circular slot in the top part of the top shell.
|
||||
6. Carefully mount the OLED display in the rectangular slot in the middle part of the top shell.
|
||||
7. While ensuring that all internal cables stay within their routing groves, place the **top shell** on top of the **bottom shell**, making sure that the screw-mounting holes line up.
|
||||
8. Mount the 6 **M2x6mm screws** into the mounting holes, until the two shells of the case are tightly and securely connected.
|
||||
9. Flip over the device.
|
||||
10. Connect the male **u.FL** connector to the female **u.FL** socket on the **board**.
|
||||
11. Optionally, connect the male JST connector of the **battery** to the female JST connector on the **board**.
|
||||
12. Fit the **battery door** into place.
|
||||
|
||||
Congratulations, Your Handheld RNode is now complete!
|
||||
|
||||
Flip the power switch, and start using it!
|
||||
8
Console/source/builds/micropylon.md
Executable file
@@ -0,0 +1,8 @@
|
||||
[date]: <> (2023-01-09)
|
||||
[title]: <> (Reticulum MicroPylon)
|
||||
[image]: <> (gfx/cs.webp)
|
||||
[excerpt]: <> (A powerful, solar-powered multi-transceiver RNode-based radio system for autonomous and self-configuring Reticulum network deployments.)
|
||||
## Reticulum MicroPylon
|
||||
This radio system is a powerful and flexible multi-transceiver radio system, designed for rapidly deploying autonomous and self-configuring Reticulum networks over wide areas.
|
||||
|
||||
This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it!
|
||||
6
Console/source/builds/wallmount.md
Executable file
@@ -0,0 +1,6 @@
|
||||
[date]: <> (2023-01-10)
|
||||
[title]: <> (Wall-Mount RNode)
|
||||
[image]: <> (gfx/cs.webp)
|
||||
[excerpt]: <> (A sleek, wall-mountable RNode, suitable for permanent installation and operation indoors, or in a semi-protected environment outdoors.)
|
||||
## Wall-Mount RNode
|
||||
This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it!
|
||||
20
Console/source/contact.md
Executable file
@@ -0,0 +1,20 @@
|
||||
[title]: <> (Contact)
|
||||
# Contact Me
|
||||
|
||||
**Hello!** I am the creator of the RNode ecosystem.
|
||||
|
||||
If you have any general questions or comments about any of the projects I maintain, I encourage you to post it in one of the following places:
|
||||
|
||||
- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub
|
||||
- The [Reticulum Matrix Channel](#reticulum:matrix.org) at `#reticulum:matrix.org`
|
||||
- The [Reticulum subreddit](https://reddit.com/r/reticulum)
|
||||
|
||||
To get in touch with me personally, you can use one of the following methods, in order of preference:
|
||||
|
||||
- LXMF at `8dd57a738226809646089335a6b03695`
|
||||
- Matrix using `@unsignedmark:matrix.org`
|
||||
- Email by using the address mark at unsigned dot io
|
||||
|
||||
Please use the public forums and channels for support and help requests. I receive a lot of messages, and while I try to answer everyone (eventually), this is not always possible.
|
||||
|
||||
<center>`3502`</center>
|
||||
39
Console/source/contribute.md
Executable file
@@ -0,0 +1,39 @@
|
||||
[title]: <> (Donate)
|
||||
## Keep Communications Free and Open
|
||||
Please take part in keeping the continued development, maintenance and distribution of the RNode ecosystem possible by donating via one of the following channels:
|
||||
|
||||
- Monero<br/>
|
||||
```
|
||||
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
|
||||
```
|
||||
<br/><br/>
|
||||
- Ethereum<br/>
|
||||
```
|
||||
0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73
|
||||
```
|
||||
<br/><br/>
|
||||
- Bitcoin<br/>
|
||||
```
|
||||
35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH
|
||||
```
|
||||
<br/><br/>
|
||||
- Ko-Fi<br/>
|
||||
<a href="https://ko-fi.com/markqvist">`https://ko-fi.com/markqvist`</a>
|
||||
|
||||
## Spread Knowledge and Awareness
|
||||
Another great way to contribute, is to spread awareness about the RNode project. Here's some ideas:
|
||||
|
||||
- Introduce the concepts of Free & Open Communications Systems to your community
|
||||
- Teach others to build and use RNodes, and how to set up resilient and private communications systems
|
||||
- Learn about using Reticulum to set up resilient communications networks, and teach these skills to people in your area that need them
|
||||
|
||||
## Contribute Code & Material
|
||||
If you like to write, build and design, there are plenty of oppertunities to take part in the community around RNode, and the wider Reticulum community as well.
|
||||
|
||||
There's always plenty of work to do, from writing code, tutorials and guides, to designing parts, devices and integrations, and translating material to other languages.
|
||||
|
||||
You can find us the following places:
|
||||
|
||||
- The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org`
|
||||
- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub
|
||||
- The [Reticulum subreddit](https://reddit.com/r/reticulum)
|
||||
123
Console/source/guides/install_firmware.md
Executable file
@@ -0,0 +1,123 @@
|
||||
[date]: <> (2023-01-12)
|
||||
[title]: <> (Installing RNode Firmware on Supported Devices)
|
||||
[image]: <> (images/g2p.webp)
|
||||
[excerpt]: <> (If you have a T-Beam or LoRa32 device handy, it is very easy to get it set up for all the things that the RNode firmware allows you to do.)
|
||||
<div class="article_date">{DATE}</div>
|
||||
# Installing RNode Firmware on Supported Devices
|
||||
|
||||
Do you have one of the devices available that the RNode Firmware supports? In that case, it is very easy to turn it into a working RNode by using the `rnodeconf` autoinstaller.
|
||||
|
||||
With the firmware installed, you can use your newly created RNode as:
|
||||
|
||||
- A [LoRa interface for Reticulum]({ASSET_PATH}m/interfaces.html#rnode-lora-interface)
|
||||
- A LoRa packet sniffer with [LoRaMon](https://unsigned.io/loramon/)
|
||||
- A Linux network interface using the [tncattach program]({ASSET_PATH}pkg/tncattach.zip)
|
||||
- A LoRa-based TNC for almost any amateur radio packet application
|
||||
|
||||
So let's get started! You will need either a **LilyGO T-Beam v1.1**, a **LilyGO LoRa32 v2.0**, a **LilyGO LoRa32 v2.1** or a **Heltec LoRa32 v2** device. More supported devices are added regularly, so it might be useful to check the latest [list of supported devices]({ASSET_PATH}supported.html) as well.
|
||||
|
||||
It is currently recommended to use one of the following devices: A **LilyGO LoRa32 v2.1** (also known as **TTGO T3 v1.6.1**) or a **LilyGO T-Beam v1.1**.
|
||||
|
||||

|
||||
<center>*Some of the device types compatible with this installation guide*</center>
|
||||
|
||||
## Device Variations
|
||||
|
||||
Some devices come with transceiver chips that are currently unsupported by the RNode Firmware. Currently devices with an **SX1276** or **SX1278** chip are supported. Support for **SX1262**, **SX1268** and **SX1280** is being added. Please support the development with [donations]({ASSET_PATH}donate.html), if you would like to see these chips supported.
|
||||
|
||||
> **Beware!** Some devices, like the T-Beam, use SiLabs USB chips. These may need [additional drivers](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) to work well on macOS and Windows. Linux usually has up-to-date drivers pre-installed. The SiLabs driver may also experience conflicts with earlier, pre-installed versions of the driver, causing a *resource busy* error, which can be fixed by [removing the old driver](https://community.platformio.org/t/mac-usb-port-detected-but-won-t-upload/20663/2).
|
||||
|
||||
## Preparations
|
||||
|
||||
To get started, you will need to install at least version 2.1.0 of the [RNode Configuration Utility]({ASSET_PATH}m/using.html#the-rnodeconf-utility).
|
||||
|
||||
The `rnodeconf` program is included in the `rns` package. Please read [these instructions]({ASSET_PATH}s_rns.html) for more information on how to install it from this repository, or from the Internet. If installation goes well, you can now move on to the next step.
|
||||
|
||||
## Install The Firmware
|
||||
|
||||
We are now ready to start installing the firmware. To install the RNode Firmware on your devices, run the RNode autoinstaller using this command:
|
||||
|
||||
```txt
|
||||
rnodeconf --autoinstall
|
||||
```
|
||||
|
||||
The installer will now ask you to insert the device you want to set up, scan for connected serial ports, and ask you a number of questions regarding the device. When it has the information it needs, it will install the correct firmware and configure the necessary parameters in the device EEPROM for it to function properly.
|
||||
|
||||
If the install goes well, you will be greated with a success message telling you that your device is now ready.
|
||||
|
||||
> **Please Note!** If you are connected to the Internet while installing, the autoinstaller will automatically download any needed firmware files to a local cache before installing.
|
||||
|
||||
> If you do not have an active Internet connection while installing, you can extract and use the firmware from this device instead. This will **only** work if you are building the same type of RNode as the device you are extracting from, as the firmware has to match the targeted board and hardware configuration.
|
||||
|
||||
If you need to extract the firmware from an existing RNode, run the following command:
|
||||
|
||||
```
|
||||
rnodeconf --extract
|
||||
```
|
||||
|
||||
If `rnodeconf` finds a working RNode, it will extract and save the firmware from the device for later use. You can then run the auto-installer with the `--use-extracted` option to use the locally extracted file:
|
||||
|
||||
```
|
||||
rnodeconf --autoinstall --use-extracted
|
||||
```
|
||||
|
||||
This also works for updating the firmware on existing RNodes, so you can extract a newer firmware from one RNode, and deploy it onto other RNodes using the same method. Just use the `--update` option instead of `--autoinstall`.
|
||||
|
||||
## Verify Installation
|
||||
To confirm everything is OK, you can query the device info with:
|
||||
|
||||
```txt
|
||||
rnodeconf --info /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Remember to replace `/dev/ttyUSB0` with the actual port the installer used in the previous step. You should now see `rnodeconf` connect to your device and show something like this:
|
||||
|
||||
```txt
|
||||
[20:11:22] Opening serial port /dev/ttyUSB0...
|
||||
[20:11:25] Device connected
|
||||
[20:11:25] Current firmware version: 1.26
|
||||
[20:11:25] Reading EEPROM...
|
||||
[20:11:25] EEPROM checksum correct
|
||||
[20:11:25] Device signature validated
|
||||
[20:11:25]
|
||||
[20:11:25] Device info:
|
||||
[20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36)
|
||||
[20:11:25] Device signature : Validated - Local signature
|
||||
[20:11:25] Firmware version : 1.26
|
||||
[20:11:25] Hardware revision : 1
|
||||
[20:11:25] Serial number : 00:00:00:02
|
||||
[20:11:25] Frequency range : 850.0 MHz - 950.0 MHz
|
||||
[20:11:25] Max TX power : 17 dBm
|
||||
[20:11:25] Manufactured : 2022-01-27 20:10:32
|
||||
[20:11:25] Device mode : Normal (host-controlled)
|
||||
```
|
||||
|
||||
On the hardware side, you should see the status LED flashing briefly approximately every 2 seconds. If all of the above checks out, congratulations! Your RNode is now ready to use. If your device has a display, it should also come alive and show you various information related to the device state.
|
||||
|
||||
If you want to use it with [Reticulum]({ASSET_PATH}s_rns.html), [Nomad Network]({ASSET_PATH}s_nn.html), [LoRaMon](https://unsigned.io/loramon), or other such applications, leave it in the default `Normal (host-controlled)` mode.
|
||||
|
||||
If you want to use it with legacy amateur radio applications that work with KISS TNCs, you should [set it up in TNC mode]({ASSET_PATH}guides/tnc_mode.html).
|
||||
|
||||
## External RGB LED
|
||||
If you are using a **LilyGO LoRa32 v2.1** device, you can connect an external **NeoPixel RGB LED** for device status using the following setup:
|
||||
|
||||
- Connect the NeoPixel **V+** pin to the **3.3v** pin on the board.
|
||||
- Connect the NeoPixel **GND** pin to the **GND** pin on the board.
|
||||
- Connect the NeoPixel **DATA** pin to **IO Pin 12** on the board.
|
||||
|
||||
For the firmware to activate the NeoPixel LED, you must also make specific choices in the autoinstaller guide:
|
||||
|
||||
- When asked what type of device you have, select **A specific kind of RNode**.
|
||||
- When asked what model the device is, select the **Handheld v2.x RNode** that matches the frequency of your board.
|
||||
|
||||
## External Display & LEDs
|
||||
If you are using a **LilyGO T-Beam** device, you can connect an external **SSD1306 OLED** display using the following setup:
|
||||
|
||||
- The **SSD1306**-based display must be set to use **I2C** and address `0x3D`
|
||||
- Connect display **GND** to T-Beam **GND**
|
||||
- Connect display **Vin** to suitable power-supplying pin on the T-Beam
|
||||
- Connect display **RST** to T-Beam **Pin 13**
|
||||
- Connect display **I2C CLK** to T-Beam **SCL** / **Pin 22**
|
||||
- Connect display **I2C DATA** to T-Beam **SDA** / **Pin 21**
|
||||
|
||||
On **T-Beam** devices, you can also connect external RX/TX LEDs to **Pin 2** and **Pin 4**.
|
||||
237
Console/source/guides/loracomms.md
Executable file
@@ -0,0 +1,237 @@
|
||||
[date]: <> (2023-01-14)
|
||||
[title]: <> (Private, Secure and Uncensorable Messaging Over a LoRa Mesh)
|
||||
[image]: <> (images/g1p.webp)
|
||||
[excerpt]: <> (Or: How to set up a completely private, independent and encrypted communication system in half an hour, using stuff you can buy for under $100.)
|
||||
<div class="article_date">{DATE}</div>
|
||||
# Private, Secure and Uncensorable Messaging Over a LoRa Mesh
|
||||
*Or: How to set up a completely private, independent and encrypted communication system in half an hour, using stuff you can buy for under $100.*
|
||||
|
||||

|
||||
|
||||
In this post, we will explore how two people, Alice and Bob, can set up a LoRa mesh communication system for their use that has the following characteristics:
|
||||
|
||||
- Allows both real-time and asynchronous text message communication between Alice and Bob.
|
||||
- Works *completely* indpendently of any infrastructure outside the control of Alice and Bob. Even if the Internet, cellular networks and the power grid fails, Alice and Bob must still be able to communicate.
|
||||
- Is completely private and outside the reach of automated surveillance, and does not reveal any identifying information about Alice or Bob, nor any contents of or information about their conversations.
|
||||
|
||||
In later parts of this series, we will expand the system to provide these oppertunities to an entire community, and add other mediums like Packet Radio, but for now we will focus on learning the basics by just establishing a Free Communications System between Alice and Bob.
|
||||
|
||||
To accomplish this, we will be building a small and simple system based on freely available and Open Source software. To realise our system we will need the following components:
|
||||
|
||||
- A networking system that can function reliably and efficiently even without any functional Internet infrastructure available. This will be provided by [Reticulum]({ASSET_PATH}r/index.html).
|
||||
- Software that Alice and Bob can interact with on their computers and mobile devices to actually communicate with each other. This will be provided by the programs [Nomad Network]({ASSET_PATH}s_nn.html) and [Sideband]({ASSET_PATH}s_sideband.html).
|
||||
- Radio hardware that Reticulum can use to cover the 7 kilometer distance between Bobs apartment and Alices house. This will be provided by installing the [RNode Firmware]({ASSET_PATH}guides/install_firmware.html) on a couple of small LoRa radio modules that can be purchased cheaply off Amazon or similar online vendors.
|
||||
|
||||
As you might have already guessed, the "magic glue" that acutally makes this entire system possible is [Reticulum]({ASSET_PATH}r/index.html).
|
||||
|
||||
Reticulum is a complete networking stack that was designed to handle challenging situations and requirements like this. Reticulum is an incredibly flexible networking platform, that can use almost anything as a carrier for digital information transfer, and it can automatically form secure mesh networks with very minimal resources, infrastructure and setup.
|
||||
|
||||
Please do keep in mind though, that at the time of writing this, Reticulum is still in beta. There might be bugs and security issues that have not yet been discovered. You can keep up with such things, and get updates on the general development and releases, over on the [Reticulum GitHub page](https://github.com/markqvist/reticulum).
|
||||
|
||||
The user-facing software that Alice and Bob will be installing already includes Reticulum, so there is no complicated installation and configuration setups, and getting everything up and running will be quite simple. The requirements are also very minimal, and everything can run on hardware they already have available, be that an old computer, a Raspberry Pi, or an Android phone.
|
||||
|
||||
Let's get started.
|
||||
|
||||
# LoRa Radio Setup
|
||||
The first step is to get the LoRa radios prepared and installed. I have written in more length and details about these subjects in other posts on this site ([Installing RNode Firmware on Supported Devices]({ASSET_PATH}guides/install_firmware.html) and [How To Make Your Own RNodes]({ASSET_PATH}guides/make_rnodes.html), so this article will just quickly guide you through the basics required to get up and running. For much more information, read the above articles.
|
||||
|
||||
First of all, Alice and Bob need to get a compatible piece of radio hardware to use. Had they been living closer to each other, they might have just been able to use WiFi, but they need to cover a distance of more than 7 kilometers, so they decide to go with a couple of LoRa radios.
|
||||
|
||||
They take a look at the RNode Firmware [Supported Devices List]({ASSET_PATH}supported.html), and decide to go with a couple of LilyGO T-Beam devices. They could have also used others, and they don't need to choose the same device, as long as they are within the same frequency range, all compatible devices work with Reticulum and can communicate with each other, as soon as the RNode Firmware has been installed on them.
|
||||
|
||||

|
||||
|
||||
Once the devices arrive, it is time to get the firmware installed. For this they will need a computer running some sort of Linux. Alice has a computer with Ubuntu installed, so they decide to use that. Since Python3 came installed as standard with the OS, Alice can go ahead and install the RNode configuration program by simply opening a terminal and typing:
|
||||
|
||||
```
|
||||
pip install rnodeconf
|
||||
```
|
||||
|
||||
The above command installs the program they need to flash the LoRa radios with the right firmware. If for some reason Python3 had not already been installed on Alices computer, she would have had to install it first with the command `sudo apt install python python-pip`.
|
||||
|
||||
Now that the firmware installer is ready, it is time to actually get the firmware on to the devices. Alice launches the installer with the following command:
|
||||
|
||||
```
|
||||
rnodeconf --autoinstall
|
||||
```
|
||||
|
||||
After this she is greated with an interactive guide that asks a few questions about the device type, grabs the latest firmware files, and installs them onto the device. After repeating with the second device, that is all there is to it, and the LoRa radios are now ready for use with Reticulum.
|
||||
|
||||
# Installation at Alices House
|
||||
To get a better signal, Alice mounts her LoRa radio in the attic of her house. She then runs a USB cable from the mounting location to the computer she wants to use for messaging, and plugs the cable into the computer. The LoRa radio is now directly connected to her computer via USB, and receives power from it when the computer is on.
|
||||
|
||||
At her computer (running Ubuntu Linux), she installs the Nomad Network program by entering the following command in a terminal:
|
||||
|
||||
```
|
||||
pip install nomadnet
|
||||
```
|
||||
|
||||
After a few seconds, Nomad Network and Reticulum is installed and ready to use. She can now run the Nomad Network client by entering the following command:
|
||||
|
||||
```
|
||||
nomadnet
|
||||
```
|
||||
|
||||
All required directories and configuration files will now be created, and the client will start up. After a few seconds, Alice will be greeted with a screen like this:
|
||||
|
||||

|
||||
|
||||
Confirming that everything is installed and working, it is time to add the LoRa radio as an interface that Reticulum can use. To do this, she opens up the Reticulum configuration file (located at `˜/.reticulum/config`) in a text editor.
|
||||
|
||||
By referring to the [RNode LoRa Interface]({ASSET_PATH}m/interfaces.html#rnode-lora-interface) section of the [Reticulum Manual]({ASSET_PATH}m), she can just copy-and-paste in a new configuration section for the interface, and edit the radio parameters to her requirements. She ends up with a configuration file that looks like this in it's entirity:
|
||||
|
||||
```
|
||||
[reticulum]
|
||||
enable_transport = False
|
||||
share_instance = Yes
|
||||
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
panic_on_interface_error = No
|
||||
|
||||
[logging]
|
||||
loglevel = 4
|
||||
|
||||
[interfaces]
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
interface_enabled = True
|
||||
port = /dev/ttyUSB0
|
||||
frequency = 867200000
|
||||
bandwidth = 125000
|
||||
txpower = 7
|
||||
spreadingfactor = 8
|
||||
codingrate = 5
|
||||
```
|
||||
|
||||
*Please note that the assignment and use of radio frequency spectrum is completely outside the scope of this exploratory post. Laws and regulations about spectrum use vary greatly around the world, and you will have to do your own research for what frequencies and modes you can use in your location, and what licenses, if any, are required for any given use case.*
|
||||
|
||||
Alice can now start the Nomad Network client again, and this time around it will initialise and use the LoRa radio installed in her attic. Having completed Alices part of the setup, lets move on to Bobs apartment.
|
||||
|
||||
# Installation at Bobs Apartment
|
||||
Bob likes his messaging to happen on a handy device like a phone, so he decides to go with the [Sideband]({ASSET_PATH}s_sideband.html) app instead of Nomad Network. He goes to the [download page](https://github.com/markqvist/Sideband/releases/latest) and installs the APK on his Android phone. He now needs a way to connect to the LoRa radio already running at Alices house to establish communication.
|
||||
|
||||
Since he doesn't want to walk around with the LoRa radio constantly dangling by a USB cable from his phone, he decides to set up a Reticulum gateway in his apartment using a Raspberry Pi he had lying around. The RNode LoRa radio will connect via USB to the Raspberry Pi, and the Raspberry Pi will be connected to the WiFi network in his apartment.
|
||||
|
||||
This way, any device on his WiFi network (including his Android phone) will be able to route information through the LoRa radio as well. Reticulum takes care of everything automatically, and there is no need to configure addresses, subnet, routing rules or anything.
|
||||
|
||||
Both his WiFi router and the Rasperry Pi is powered by a small battery system, so even if the power goes out, the system will be able to stay on for several days on the battery, and indefinitely if he props up a solar panel on his balcony.
|
||||
|
||||
Bob installs a fresh copy of Raspberry Pi OS on the small computer, and in the terminal issues the following command to install Reticulum:
|
||||
|
||||
```
|
||||
pip install rns
|
||||
```
|
||||
|
||||
In this case, Bob will not be running any user-facing software on the Raspberry Pi itself, so instead he starts Reticulum as a service, by running the `rnsd` program, to check that everything installed correctly:
|
||||
|
||||
```
|
||||
rnsd
|
||||
```
|
||||
|
||||
After a moment, the following output is shown from the `rnsd` program, signalling that everything is working properly, but that a new, default configuration file has just been created:
|
||||
|
||||
```
|
||||
[2022-03-26 17:14:05] [Notice] Could not load config file, creating default configuration file...
|
||||
[2022-03-26 17:14:05] [Notice] Default config file created. Make any necessary changes in /home/bob/.reticulum/config and restart Reticulum if needed.
|
||||
[2022-03-26 17:14:09] [Notice] Started rnsd version 0.3.3
|
||||
```
|
||||
|
||||
Bob terminates the `rnsd` program, and then connects the LoRa radio to the Raspberry Pi with a USB cable. Since he doesn't have any particular access to the roof or attic of the building, he just sticky-tapes the LoRa radio to a window facing in the general direction of Alices house.
|
||||
|
||||
He then proceeds to add the same interface configuration to his Reticulum configuration file as Alice did, so that the radio parameters of their respective LoRa radios match each other.
|
||||
|
||||
To allow other devices on his network to route through his new Reticulum gateway, he also adds the line `enable_transport = yes` to his Reticulum config file, so the file in it's entirity looks like this:
|
||||
|
||||
```
|
||||
[reticulum]
|
||||
enable_transport = Yes
|
||||
share_instance = Yes
|
||||
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
panic_on_interface_error = No
|
||||
|
||||
[logging]
|
||||
loglevel = 4
|
||||
|
||||
[interfaces]
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
interface_enabled = True
|
||||
port = /dev/ttyUSB0
|
||||
frequency = 867200000
|
||||
bandwidth = 125000
|
||||
txpower = 7
|
||||
spreadingfactor = 8
|
||||
codingrate = 5
|
||||
```
|
||||
|
||||
After starting the program again, this time using `rnsd -vvv` to get more verbose output, he can now see that the LoRa radio is correctly configured and used by Reticulum:
|
||||
|
||||
```
|
||||
[2022-03-26 18:17:43] [Debug] Bringing up system interfaces...
|
||||
[2022-03-26 18:17:43] [Verbose] AutoInterface[Default Interface] discovering peers for 1.8 seconds...
|
||||
[2022-03-26 18:17:45] [Notice] Opening serial port /dev/ttyUSB0...
|
||||
[2022-03-26 18:17:47] [Notice] Serial port /dev/ttyUSB0 is now open
|
||||
[2022-03-26 18:17:47] [Verbose] Configuring RNode interface...
|
||||
[2022-03-26 18:17:47] [Verbose] Wating for radio configuration validation for RNodeInterface[RNode LoRa Interface]...
|
||||
[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting frequency is 867.2 MHz
|
||||
[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting bandwidth is 125 KHz
|
||||
[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting TX power is 7 dBm
|
||||
[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting spreading factor is 8
|
||||
[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting coding rate is 5
|
||||
[2022-03-26 18:17:47] [Verbose] RNodeInterface[RNode LoRa Interface] On-air bitrate is now 3.1 kbps
|
||||
[2022-03-26 18:17:47] [Notice] RNodeInterface[RNode LoRa Interface] is configured and powered up
|
||||
[2022-03-26 18:17:48] [Debug] System interfaces are ready
|
||||
[2022-03-26 18:17:48] [Verbose] Configuration loaded from /home/bob/.reticulum/config
|
||||
[2022-03-26 18:17:50] [Verbose] Loaded 0 path table entries from storage
|
||||
[2022-03-26 18:17:50] [Verbose] Loaded 0 tunnel table entries from storage
|
||||
[2022-03-26 18:17:50] [Verbose] Transport instance <a5dc367015b30f2d7b59> started
|
||||
[2022-03-26 18:17:50] [Notice] Started rnsd version 0.3.3
|
||||
```
|
||||
|
||||
Everything is ready, and when Bob launches the Sideband appplication on his phone, Alice and him will now be able to communicate securely and independently of any other infrastructure.
|
||||
|
||||
# Communication
|
||||
Both the [Nomad Network]({ASSET_PATH}s_nn.html) program and the [Sideband]({ASSET_PATH}s_sidband.html) application use a cryptographic message delivery system named [LXMF]({ASSET_PATH}s_lxmf.html), that in turn uses Reticulum for encryption and privacy guarantees. Both Nomad Network and Sideband are *LXMF clients*.
|
||||
|
||||
Much like many different e-mail clients exist, so can many different LXMF clients, and they can all communicate with each other, which is why Alice and Bob can message each other even though they prefer to use very different kinds of user-facing software.
|
||||
|
||||
An LXMF addresses consist of 32 hexadecimal characters, and are usually encapsulated in single angle quotation marks like this: `<9824f6367015b30f2d7b8a24bc6205d7>`.
|
||||
|
||||
Nobody controls the allocation of addresses, and since the address space is so huge, and governed by cryptographic principles, you can create as many or as few adresses as you need.
|
||||
|
||||
Since you can just create them with freely avaible software, and without any sort of permission from anyone, they are never linked to any personally identifiable information either. They are completely and truly anonymous from the beginning, and you control how much or how little of your identity you associate with them.
|
||||
|
||||
For an LXMF address to be reachable for direct-delivery instant messaging on a Reticulum network, it must announce it's public keys on the network. Both Sideband and Nomad Network allows you to send an announce on the network, and both programs can be configured to do so automatically when they start. If you only want to use the system for "email-style" communication (via LXMF propagation nodes), you don't *need* to send any announces on the network, but to learn how it all works, it is a good idea to just set the programs to automatically announce at start up.
|
||||
|
||||
To make sure his public cryptographic key is known by the network, Bob taps the **Announce** button in the Sideband app:
|
||||
|
||||
<center><p><img src="{ASSET_PATH}images/an1.webp"/></p></center>
|
||||
|
||||
After a few seconds, Bobs announce shows up in the **Announce Stream** section of the Nomad Network program on Alices computer:
|
||||
|
||||
<center><p><img src="{ASSET_PATH}images/nn_an.webp"/></p></center>
|
||||
|
||||
Using the received announce, Alice starts a conversation with Bob. Either one of them could also have started the conversation by manually typing in the others LXMF address in their program, but in many cases it can be convenient to use the announces. Now that everything is ready, they exchange a few messages to test the system. On Bobs Android phone, this looks like this:
|
||||
|
||||
<center><p><img style="max-width: 100%; width: 300px;" src="{ASSET_PATH}images/3_conv.webp"/></p></center>
|
||||
|
||||
And on Alices computer running Nomad Network, it looks like this:
|
||||
|
||||
<center><p><img src="{ASSET_PATH}images/nn_conv.webp"/></p></center>
|
||||
|
||||
Although pretty useful, what we have explored here does not even begin to scratch the surface of what is possible with Reticulum and associated software. I hope you will find yourself inspired to explore and read deeper into the documentation and available software.
|
||||
|
||||
To learn more, take a look at the [Learn]({ASSET_PATH}learn.html) section.
|
||||
111
Console/source/guides/make_rnodes.md
Executable file
@@ -0,0 +1,111 @@
|
||||
[date]: <> (2023-01-10)
|
||||
[title]: <> (How To Make Your Own RNodes)
|
||||
[image]: <> (images/g3p.webp)
|
||||
[excerpt]: <> (This article will outline the general process, and provide the information you need, for building your own RNode from a few basic modules. The RNode will be functionally identical to a commercially purchased board.)
|
||||
# How To Make Your Own RNodes
|
||||
|
||||
This article will outline the general process, and provide the information you need, for building your own RNode from a few basic modules. The RNode will be functionally identical to a purchased device.
|
||||
|
||||
Once you have learned the put together a custom RNode with your own choice of components, you can use these skills to create your own RNode designs from scratch, using either a custom-designed PCB, or simply by mounting your choice of modules in a enclosure or case.
|
||||
|
||||
If you haven't already, you migh also want to check out how to [install the RNode firmware directly on pre-made LoRa development boards]({ASSET_PATH}guides/install_firmware.html).
|
||||
|
||||

|
||||
<center>*A homemade RNode, based on an ESP32 board and a transceiver module, ready for use*</center>
|
||||
|
||||
Since there is not *one right way* to cut this pie, this article will probably not give the *exact* steps for the combination of components you choose, but will instead attempt to provide you with the information you need to build RNodes from a wide variety of microcontroller boards and LoRa modules. Generally speaking, you will need three things to construct a working RNode:
|
||||
|
||||
- A supported microcontroller board
|
||||
- A supported transceiver module
|
||||
- A way to mount and connect the two
|
||||
|
||||
## Preparing the Hardware
|
||||
|
||||
Currently, the RNode firmware supports a variety of different microcontrollers, and more are being added regurlarly. That means that there is a *lot* of boards to choose from. You can probably use most boards that are based on either the **ATmega1284P**, **ATmega2560** or **ESP32** microcontrollers. Regarding microcontroller boards there is a few key points to take note of:
|
||||
|
||||
- You will need to connect the transceiver module over the SPI bus. This means that the board should have SPI pins for exposed for you to connect to. UART-only modules will **not** work.
|
||||
- Logic voltage levels must match the transceiver module you are using, or you will have to add a voltage level converter in between the two devices, that is fast enough for the clock of the SPI bus (usually 8 or 10MHz). I recommend using a microcontroller and transceiver module with matching logic levels. Most will be 3.3 volts.
|
||||
- Apart from the SPI pins for *clock*, *chip select*, *MOSI* and *MISO*, you will also need an output pin for a *reset* line to the transceiver module, and one **interrupt-capable** input pin for the interrupt signal from the transceiver module. Almost all boards should have plenty of IO available for this, but you might as well make sure before ordering anything.
|
||||
- You need to choose a board that can provide enough power on it's internal regulators to power the transceiver module while it is transmitting. This can draw quite a bit of power, and some boards only have very small 3.3v regulators, which will not cut it while driving the transmitter at full tilt.
|
||||
|
||||
Regarding the LoRa transceiver module, there is going to be an almost overwhelming amount of options to choose from. To narrow it down, here are the essential characteristics to look for:
|
||||
|
||||
- The RNode firmware needs a module based on the **Semtech SX1276**, **Semtech SX1278**, **SX1262**, **SX1268** and **SX1280** LoRa transceiver ICs. These come in several different variants, for all frequency bands from about 150 MHz to 2500 MHz.
|
||||
- The module *must* expose the direct SPI bus to the transceiver chip. UART based modules that add their own communications layer will not work.
|
||||
- The module must also expose the *reset* line of the chip, and provide the **DIO0** (or other relevant) interrupt signal *from* the chip.
|
||||
- As mentioned above, the module must be logic-level compatible with the microcontroller you are using, unless you want to add a level-shifter. Resistor divider arrays will most likely not work here, due to the bus speeds required.
|
||||
|
||||
Keeping those things in mind, you should be able to select a suitable combination of microcontroller board and transceiver module.
|
||||
|
||||
## Assembling the RNode
|
||||
|
||||
Ok, having gone through the endless combinations and selected a board and a module, you are actually almost done. Connecting the devices together is pretty simple, and should only take a few minutes. I recommend that you place both devices in a solderless breadboard initially, to make sure everything is working as expected. Once you have a working setup, you can make it more durable and permanent by soldering it to a prototyping board, and connecting permanent lines between the devices.
|
||||
|
||||
In the photo above I used an Adafruit Feather ESP32 board and a ModTronix inAir4 module. That will result in an RNode suitable for the 420 MHz to 520 MHz range. To complete the device I did the following:
|
||||
|
||||
1. Connect the GND pin of the microcontroller board to the GND rail of the breadboard.
|
||||
2. Connect the GND pin of the transceiver module to the GND rail of the breadboard.
|
||||
3. Connect the 3.3 volt output line of the microcontroller board to the V_IN pin of the transceiver module.
|
||||
4. Connect the *chip select* pin of the microcontroller board to the *chip select* pin of the transceiver module.
|
||||
5. Connect the *SPI clock* pin of the microcontroller board to the *SPI clock* pin of the transceiver module.
|
||||
6. Connect the *MOSI* pin of microcontroller board to the *MOSI* pin of the transceiver module.
|
||||
7. Connect the *MISO* pin of the microcontroller board to the *MISO* pin of the transceiver module.
|
||||
8. Connect the *transceiver reset* pin of the microcontroller board to the *reset* pin of the transceiver module.
|
||||
9. Connect the *DIO0* pin of the transceiver module to the *DIO0 interrupt pin* of the microcontroller board.
|
||||
10. You can optionally connect transmit and receiver LEDs to the corresponding pins of the microcontroller board.
|
||||
|
||||
The pin layouts of your transceiver module and microcontroller board will vary, but you can look up the correct pin assignments for your processor type and board layout in the [Config.h](https://github.com/markqvist/RNode_Firmware/blob/master/Config.h) file of the [RNode Firmware](https://unsigned.io/rnode_firmware).
|
||||
|
||||
### Loading the Firmware
|
||||
Once the hardware is assembled, you are ready to load the firmware onto the board and configure the configuration parameters in the boards EEPROM. Luckily, this process is completely automated by the [RNode Configuration Utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnodeconf-utility). To prepare for loading the firmware, make sure that `python` and `pip` is installed on your system, then install the `rns` package (which includes the `rnodeconf` program) by issuing the command:
|
||||
|
||||
|
||||
```txt
|
||||
pip install rns
|
||||
```
|
||||
|
||||
If installation goes well, you can now move on to the next step.
|
||||
|
||||
> *Take Care*: A LoRa transceiver module **must** be connected to the board for the firmware to start and accept commands. If the firmware does not verify that the correct transceiver is available on the SPI bus, execution is stopped, and the board will not accept commands. If you find the board unresponsive after installing the firmware, or EEPROM configuration fails, double-check your transceiver module wiring!
|
||||
|
||||
Having double-checked that everything is connected correctly, it is time to power up the board and install the firmware. Run the `rnodeconf` autoinstaller by executing the command:
|
||||
|
||||
```txt
|
||||
rnodeconf --autoinstall
|
||||
```
|
||||
|
||||
The installer will now ask you to insert the device you want to set up, scan for connected serial ports, and ask you a number of questions regarding the device. When it has the information it needs, it will install the correct firmware and configure the necessary parameters in the device EEPROM for it to function properly.
|
||||
|
||||
If the install goes well, you will be greated with a success message telling you that your device is now ready. To confirm everything is OK, you can query the device info with:
|
||||
|
||||
```txt
|
||||
rnodeconf --info /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Remember to replace `/dev/ttyUSB0` with the actual port the installer used in the previous step. You should now see `rnodeconf` connect to your device and show something like this:
|
||||
|
||||
```txt
|
||||
[2022-01-27 20:11:22] Opening serial port /dev/ttyUSB0...
|
||||
[2022-01-27 20:11:25] Device connected
|
||||
[2022-01-27 20:11:25] Current firmware version: 1.26
|
||||
[2022-01-27 20:11:25] Reading EEPROM...
|
||||
[2022-01-27 20:11:25] EEPROM checksum correct
|
||||
[2022-01-27 20:11:25] Device signature validated
|
||||
[2022-01-27 20:11:25]
|
||||
[2022-01-27 20:11:25] Device info:
|
||||
[2022-01-27 20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36)
|
||||
[2022-01-27 20:11:25] Device signature : Validated - Local signature
|
||||
[2022-01-27 20:11:25] Firmware version : 1.26
|
||||
[2022-01-27 20:11:25] Hardware revision : 1
|
||||
[2022-01-27 20:11:25] Serial number : 00:00:00:02
|
||||
[2022-01-27 20:11:25] Frequency range : 850.0 MHz - 950.0 MHz
|
||||
[2022-01-27 20:11:25] Max TX power : 17 dBm
|
||||
[2022-01-27 20:11:25] Manufactured : 2022-01-27 20:10:32
|
||||
[2022-01-27 20:11:25] Device mode : Normal (host-controlled)
|
||||
```
|
||||
|
||||
On the hardware side, you should see the status LED flashing briefly approximately every 2 seconds. If all of the above checks out, congratulations! Your RNode is now ready to use.
|
||||
|
||||
If you want to use it with [Reticulum]({ASSET_PATH}s_rns.html), [Nomad Network]({ASSET_PATH}s_nn.html), [LoRaMon](https://unsigned.io/loramon), or other such applications, leave it in the default `Normal (host-controlled)` mode.
|
||||
|
||||
If you want to use it with legacy amateur radio applications that work with KISS TNCs, you should [set it up in TNC mode]({ASSET_PATH}guides/tnc_mode.html).
|
||||
22
Console/source/guides/tnc_mode.md
Executable file
@@ -0,0 +1,22 @@
|
||||
[date]: <> (2023-01-07)
|
||||
[title]: <> (Using an RNode With Amateur Radio Software)
|
||||
[image]: <> (images/g4p.webp)
|
||||
[excerpt]: <> (If you want to use an RNode with amateur radio applications, like APRS or a packet radio BBS, you will need to put the device into TNC Mode. In this mode, an RNode will behave exactly like a KISS-compatible TNC, which will make it usable with any amateur radio software.)
|
||||
<div class="article_date">{DATE}</div>
|
||||
# Using an RNode With Amateur Radio Software
|
||||
|
||||
If you want to use an RNode with amateur radio applications, like APRS or a packet radio BBS, you will need to put the device into *TNC Mode*. In this mode, an RNode will behave exactly like a KISS-compatible TNC, which will make it usable with any amateur radio software that can talk to a KISS TNC over a serial port.
|
||||
|
||||
You can use the [RNode Configuration Utility]({ASSET_PATH}m/using.html#the-rnodeconf-utility) to change settings on your device, including putting it into TNC mode.
|
||||
|
||||
The `rnodeconf` program is included in the `rns` package. Please read [these instructions]({ASSET_PATH}s_rns.html) for more information on how to install it from this repository, or from the Internet.
|
||||
|
||||
With the `rnodeconf` program installed, you can put your RNode into TNC mode simply by entering the command:
|
||||
|
||||
```
|
||||
rnodeconf -T /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Remember to replace `/dev/ttyUSB0` with the actual port your RNode is connected to. The program will now ask you for the channel configuration parameters, like frequency, bandwidth, transmission power and so on. It is also possible to specify all the parameters at once on the command line, see the `rnodeconf --help` for information on how to do this.
|
||||
|
||||
That's all there is to it! Your RNode is now configured in TNC mode, and ready for use with amateur radio applications.
|
||||
14
Console/source/help.md
Executable file
@@ -0,0 +1,14 @@
|
||||
[title]: <> (Get Help)
|
||||
## Get Help
|
||||
If you are having trouble, or if something is not working, this RNode contains a number of useful resources.
|
||||
|
||||
- Read [Questions & Answers](qa.html) section
|
||||
- Read the [Reticulum Manual](m/index.html) stored on this RNode
|
||||
- Browse a copy of the [Reticulum Website](r/index.html) stored on this RNode
|
||||
|
||||
## Community & Support
|
||||
If things still aren't working as expected here are some great places to ask for help:
|
||||
|
||||
- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub
|
||||
- The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org`
|
||||
- The [Reticulum subreddit](https://reddit.com/r/reticulum)
|
||||
28
Console/source/index.md
Executable file
@@ -0,0 +1,28 @@
|
||||
## Hello!
|
||||
<table style="margin-bottom: 1.5em;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align:middle;padding-left: 0;">
|
||||
You have connected to the <b>RNode Bootstrap Console</b>.<br/>
|
||||
<br/>
|
||||
The tools and information contained in this RNode will allow you to replicate the RNode design, build more RNodes and grow your communications ecosystems.<br/>
|
||||
<br/>
|
||||
This repository also contains tools, software and information necessary to bootstrap networks and communications systems based on RNodes and Reticulum.
|
||||
</td>
|
||||
<td width="33%" style="vertical-align:middle;padding-right: 0;">
|
||||
<img src="{ASSET_PATH}gfx/rnode_iso.webp" width="100%"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<center>
|
||||
<h3>What would you like to do?</h3>
|
||||
<div style="width:66%">You can browse this repository freely, or jump straight into a task-oriented workflow by selecting one of the starting points below.</div>
|
||||
<a href="./replicate.html"><button type="button" id="task-replicate">Create RNodes</button></a>
|
||||
<a href="./software.html"><button type="button" id="task-rns">Install Software</button></a>
|
||||
<a href="./learn.html"><button type="button" id="task-rns">Learn More</button></a>
|
||||
<a href="./m/networks.html"><button type="button" id="task-rns">Build A Network</button></a>
|
||||
<a href="./help.html"><button type="button" id="task-rns">Get Help</button></a>
|
||||
<a href="https://unsigned.io/shop"><button type="button" id="task-rns">Buy an RNode</button></a>
|
||||
<a href="./contribute.html"><button type="button" id="task-rns">Contribute</button></a>
|
||||
</center>
|
||||
14
Console/source/learn.md
Executable file
@@ -0,0 +1,14 @@
|
||||
[title]: <> (Learn More)
|
||||
## Learn More
|
||||
This RNode contains a selection of tutorials and guides on setting up communications, creating RNodes, building networks and using Reticulum. You can learn more by:
|
||||
|
||||
- Reading the [What is an RNode?](rnode.html) page
|
||||
- Checking the [Questions & Answers](qa.html) section
|
||||
- Reading the [Reticulum Manual](m/index.html) stored on this RNode
|
||||
- Browsing a copy of the [Reticulum Website]({ASSET_PATH}r/index.html) stored on this RNode
|
||||
- Visiting the [unsigned.io](https://unsigned.io/) website
|
||||
- You can also find **unsigned.io** on Nomad Network, at `ec58b0e430cd9628907383954feea068`
|
||||
|
||||
## Guides
|
||||
|
||||
{TOPIC:guides}
|
||||
20
Console/source/qa.md
Executable file
@@ -0,0 +1,20 @@
|
||||
[title]: <> (Questions & Answers)
|
||||
## Questions & Answers
|
||||
This section contains a list of common questions, and associated answers.
|
||||
|
||||
- **What are the system requirements for running Reticulum?**
|
||||
Practically any system that can run Python3 can also run Reticulum. Any computer made since the early 2000's should work, provided it has a reasonably up-to-date operating system installed. Even low-power embedded devices with 256 megabytes of RAM will run Reticulum.
|
||||
- **Does Reticulum work without the Internet?**
|
||||
Yes. Reticulum *is* itself both a networking, and an inter-net protocol. A key difference between Reticulum and IPv4/v6, however, is that Reticulum does not require any central coordination or authority to work. As soon as two devices running Reticulum can talk to each other, they form a network. That network can dynamically grow to planetary-scale nets, split up, re-connect and heal in any number of ways, while still continuing to function. As long as there is *some sort of physical way* for two or more devices to communicate, Reticulum will allow them to form a secure and reliable network.
|
||||
- **Who owns and controls the addresses I use on a Reticulum network?**
|
||||
You do. Every address is in complete ownership and control of the person that created it.
|
||||
- **If nobody centrally controls the addresses, will my address still be globally reachable?**
|
||||
Yes. Reticulum ensures end-to-end connectivity. All addresses are globally and directly reachable. Reticulum has no concept of "private address spaces" and NAT, as you might be suffering from with IPv4.
|
||||
- **Is communication over Reticulum encrypted?**
|
||||
Yes. All traffic is end-to-end encrypted. Reticulum *is fundamentally unable to route unencrypted traffic*. Links established over Reticulum networks offer forward secrecy, by using ephemeral encryption keys.
|
||||
- **Could you build a global Internet with Reticulum instead of IP?**
|
||||
Yes. In theory this is completely possible, but it will take a lot of refinement, development, hardware support and adoption to transition the global base-layer for communication to Reticulum. Please [help us]({ASSET_PATH}contribute.html) towards this goal!
|
||||
- **Is Reticulum as fast and optimised as my favorite TCP/IP stack?**
|
||||
Currently not, but we are working towards being much faster than IP. The primary focus of Reticulum has been to build an understandable and well-documented *reference implementation*, that works exceptionally well over medium-bandwidth to extremely low-bandwidth forms of communication. This focus is very valuable, since it allows people to build secure communications networks that span vast areas, with very simple hardware, and very little cost.
|
||||
- **Who created all of this?**
|
||||
The Reticulum protocol, and the RNode system was created by [Mark Qvist]({ASSET_PATH}contact.html), of [unsigned.io](https://unsigned.io).
|
||||
5
Console/source/recipes.md
Executable file
@@ -0,0 +1,5 @@
|
||||
[title]: <> (RNode Recipes)
|
||||
## RNode Build Recipes
|
||||
This section contains a library of build recipes for various types of RNodes. All the recipes contain necessary plans, instructions and 3D-printable files for completing the build.
|
||||
|
||||
{TOPIC:builds}
|
||||
27
Console/source/replicate.md
Executable file
@@ -0,0 +1,27 @@
|
||||
[title]: <> (Replicate)
|
||||
## Create RNodes
|
||||
This section contains the tools and guides necessary to create more RNodes. Creating any number of RNodes is **completely free and unrestricted** for all personal, non-commercial and humanitarian purposes. If doing so provides value to you or your community, you are encouraged to [contribute](./contribute.html) whatever you find to be reasonable.
|
||||
|
||||
If you want to create RNodes for sale or commercial purposes, please read the [selling RNodes]({ASSET_PATH}sell_rnodes.html) section for more details.
|
||||
|
||||
### Firmware Source Code
|
||||
If you would like to inspect or compile the RNode Firmware source code yourself, you can download a copy of the [RNode Firmware source-code]({ASSET_PATH}pkg/rnode_firmware.zip) stored in this RNode.
|
||||
|
||||
### Getting Started
|
||||
To create your own RNodes, there are generally three distinct paths you can take:
|
||||
|
||||
- The first, and easiest option, is to [create a basic RNode]({ASSET_PATH}guides/install_firmware.html) from one of the supported development boards. This option allows you to simply acquire a board from any online or local vendor that sells them, and then use the `rnodeconf` program to automatically turn it into an RNode. Such an RNode will be functionally equivalent to the other options, but might lack some niceties.
|
||||
- The second option is to use one of the [RNode Build Recipes]({ASSET_PATH}recipes.html) included here. These recipes contain all the resources needed to build a specific type of RNode, such as a handheld device, or an outdoor-mountable, solar-powered access point.
|
||||
- The third option is to [create your own RNode design]({ASSET_PATH}guides/make_rnodes.html) from scratch. This offers unlimited flexibility, but is a bit more involved.
|
||||
|
||||
If you already have some experience with 3D-printing and electronics projects, the recommended path is to pick a [build recipe]({ASSET_PATH}recipes.html) for the RNode type you want. That way, you will get a neat and portable unit that's ready for real-world use.
|
||||
|
||||
If you are just getting started, it might be nice to get a working "proof-of-concept" with minimal effort first, though. In such a case, [creating a basic RNode]({ASSET_PATH}guides/install_firmware.html) is a good starting point.
|
||||
<br/><br/>
|
||||
<center>
|
||||
<h3>Choose a path to get started</h3>
|
||||
<br/>
|
||||
<a href="{ASSET_PATH}guides/install_firmware.html"><button type="button" id="task-rns">Basic Build</button></a>
|
||||
<a href="{ASSET_PATH}recipes.html"><button type="button" id="task-rns">Build Recipes</button></a>
|
||||
<a href="{ASSET_PATH}guides/make_rnodes.html"><button type="button" id="task-rns">New Design</button></a>
|
||||
</center>
|
||||
11
Console/source/rnode.md
Executable file
@@ -0,0 +1,11 @@
|
||||
[title]: <> (What is an RNode?)
|
||||
## What is an RNode?
|
||||
An RNode is an open, free and unrestricted digital radio transceiver. It enables anyone to send and receive any kind of data over both short and very long distances. RNodes can be used with many different kinds of programs and systems, but they are especially well suited for use with Reticulum.
|
||||
|
||||
RNode is not a product, and not any one specific device in particular. It is a system that is easy to replicate across space and time, that produces highly functional communications tools, which respects user autonomy and empowers individuals and communities to protect their sovereignty and privacy.
|
||||
|
||||
The RNode system is primarily software, which *transforms* available hardware devices into functional, physical RNodes, which can then be used to solve a wide range of communications tasks. Such RNodes can be modified and build to suit the specific time, locale and environment they need to exist in.
|
||||
|
||||
If you notice the presence of a circularity in the naming of the system as a whole, and the physical devices, it is no coincidence. Every RNode contains the seeds necessary to reproduce the system, and create more RNodes, and even to bootstrap entire communications networks, completely independently of existing infrastructure, or the lack thereof.
|
||||
|
||||
The production of one particular RNode device is not an end, but the potential starting point of a new branch of devices on the tree of the RNode system as a whole. This tree fits into the larger biome of Free & Open Communications Systems, which I hope that you - by using communications tools like RNode - will help grow and prosper.
|
||||
23
Console/source/s_lxmf.md
Executable file
@@ -0,0 +1,23 @@
|
||||
[title]: <> (LXMF)
|
||||
## LXMF
|
||||
LXMF is a simple and flexible messaging format and delivery protocol that allows a wide variety of implementations, while using as little bandwidth as possible. It is built on top of [Reticulum](https://reticulum.network) and offers zero-conf message routing, end-to-end encryption and Forward Secrecy, and can be transported over any kind of medium that Reticulum supports.
|
||||
|
||||
LXMF is efficient enough that it can deliver messages over extremely low-bandwidth systems such as packet radio or LoRa. Encrypted LXMF messages can also be encoded as QR-codes or text-based URIs, allowing completely analog *paper message* transport.
|
||||
|
||||
Installing this LXMF library allows other programs on your system, like Nomad Network, to use the LXMF messaging system. It also includes the `lxmd` program that you can use to run LXMF propagation nodes on your network.
|
||||
|
||||
**Local Installation**
|
||||
|
||||
If you do not have access to the Internet, or would prefer to install LXMF directly from this RNode, you can use the following instructions.
|
||||
|
||||
- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first
|
||||
- Download the [{PKG_BASE_lxmf}]({ASSET_PATH}{PKG_lxmf}) package from this RNode and unzip it
|
||||
- Install it with the command `pip install ./{PKG_NAME_lxmf}`
|
||||
- Verify the installed Reticulum version by running `lxmd --version`
|
||||
|
||||
**Online Installation**
|
||||
|
||||
If you are connected to the Internet, you can try to install the latest version of LXMF via the `pip` package manager.
|
||||
|
||||
- Install Nomad Network by running the command `pip install lxmf`
|
||||
- Verify the installed Reticulum version by running `lxmd --version`
|
||||
27
Console/source/s_nn.md
Executable file
@@ -0,0 +1,27 @@
|
||||
[title]: <> (Nomad Network)
|
||||
## Nomad Network
|
||||
Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy.
|
||||
|
||||
Nomad Network Allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them. No signups, no agreements, no handover of any data, no permissions and gatekeepers.
|
||||
|
||||

|
||||
|
||||
Nomad Network is build on [LXMF](lxmf.html) and [Reticulum]({ASSET_PATH}r/), which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to fiber optics.
|
||||
|
||||
Nomad Network does not need any connections to the public internet to work. In fact, it doesn't even need an IP or Ethernet network. You can use it entirely over packet radio, LoRa or even serial lines. But if you wish, you can bridge islanded networks over the Internet or private ethernet networks, or you can build networks running completely over the Internet. The choice is yours.
|
||||
|
||||
### Local Installation
|
||||
|
||||
If you do not have access to the Internet, or would prefer to install Nomad Network directly from this RNode, you can use the following instructions.
|
||||
|
||||
- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) and [LXMF](./s_lxmf.html) packages first
|
||||
- Download the [{PKG_BASE_nomadnet}]({ASSET_PATH}{PKG_nomadnet}) package from this RNode and unzip it
|
||||
- Install it with the command `pip install ./{PKG_NAME_nomadnet}`
|
||||
- Verify the installed Nomad Network version by running `nomadnet --version`
|
||||
|
||||
### Online Installation
|
||||
|
||||
If you are connected to the Internet, you can try to install the latest version of Nomad Network via the `pip` package manager.
|
||||
|
||||
- Install Nomad Network by running the command `pip install nomadnet`
|
||||
- Verify the installed Nomad Network version by running `nomadnet --version`
|
||||
33
Console/source/s_rns.md
Executable file
@@ -0,0 +1,33 @@
|
||||
[title]: <> (Reticulum)
|
||||
## Reticulum
|
||||
The cryptographic networking stack for building resilient networks anywhere. The vision of Reticulum is to allow anyone to operate their own sovereign communication networks, and to make it cheap and easy to cover vast areas with a myriad of independent, interconnectable and autonomous networks. Reticulum is Unstoppable Networks for The People.
|
||||
|
||||
<p align="center"><img width="30%" src="{ASSET_PATH}m/_static/rns_logo_512.png"></p>
|
||||
|
||||
This packages requires you have `python` and `pip` installed on your computer. This should come as standard on most operating systems released since 2020.
|
||||
|
||||
### Local Installation
|
||||
If you do not have access to the Internet, or would prefer to install Reticulum directly from this RNode, you can use the following instructions.
|
||||
|
||||
- Download the [{PKG_BASE_rns}]({ASSET_PATH}{PKG_rns}) package from this RNode and unzip it
|
||||
- Install it with the command `pip install ./{PKG_NAME_rns}`
|
||||
- Verify the installed Reticulum version by running `rnstatus --version`
|
||||
|
||||
### Online Installation
|
||||
If you are connected to the Internet, you can try to install the latest version of Reticulum via the `pip` package manager.
|
||||
|
||||
- Install Reticulum by running the command `pip install rns`
|
||||
- Verify the installed Reticulum version by running `rnstatus --version`
|
||||
|
||||
### Dependencies
|
||||
If the installation has problems resolving dependencies, first try installing the `python-cryptography`, `python-netifaces` and `python-pyserial` packages from your systems package manager.
|
||||
|
||||
If this fails, or is simply not possible in your situation, you can make the installation of Reticulum ignore the resolution of dependencies using the command:
|
||||
|
||||
`pip install --no-dependencies ./{PKG_NAME_rns}`
|
||||
|
||||
This will allow you to install Reticulum on systems, or in circumstances, where one or more dependencies cannot be resolved. This will most likely mean that some functionality will not be available, which may be a worthwhile tradeoff in some situations.
|
||||
|
||||
If you use this method of installation, it is essential to read the [Pure-Python Reticulum]({ASSET_PATH}m/gettingstartedfast.html#pure-python-reticulum) section of the Reticulum Manual, and to understand the potential security implications of this installation method.
|
||||
|
||||
For more detailed information, please read the entire [Getting Started section of the Reticulum Manual]({ASSET_PATH}m/gettingstartedfast.html).
|
||||
20
Console/source/s_rnsh.md
Executable file
@@ -0,0 +1,20 @@
|
||||
[title]: <> (Shell Over Reticulum)
|
||||
## Shell Over Reticulum
|
||||
|
||||
The `rnsh` program lets you establish fully interactive remote shell sessions over Reticulum. It also allows you to pipe any program to or from a remote system, and is similar to how the ``ssh`` program works.
|
||||
|
||||
### Local Installation
|
||||
|
||||
If you do not have access to the Internet, or would prefer to install `rnsh` directly from this RNode, you can use the following instructions.
|
||||
|
||||
- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first
|
||||
- Download the [{PKG_BASE_rnsh}]({ASSET_PATH}{PKG_rnsh}) package from this RNode and unzip it
|
||||
- Install it with the command `pip install ./{PKG_NAME_rnsh}`
|
||||
- Verify the installed `rnsh` version by running `rnsh --version`
|
||||
|
||||
### Online Installation
|
||||
|
||||
If you are connected to the Internet, you can try to install the latest version of `rnsh` via the `pip` package manager.
|
||||
|
||||
- Install `rnsh` by running the command `pip install rnsh`
|
||||
- Verify the installed `rnsh` version by running `rnsh --version`
|
||||
12
Console/source/s_sideband.md
Executable file
@@ -0,0 +1,12 @@
|
||||
[title]: <> (Sideband)
|
||||
## Sideband
|
||||
Sideband is an LXMF client for Android, Linux, Windows and macOS. It has built-in support for communicating over RNodes, and many other mediums, such as Packet Radio, WiFi, I2P, or anything else Reticulum supports.
|
||||
|
||||
Sideband also supports voice calls, file transfers, and exchanging messages through encrypted QR-codes on paper, or through messages embedded directly in lxm:// links.
|
||||
|
||||

|
||||
|
||||
The installation files for the Sideband program is too large to be included on this RNode, but downloads for Linux, Android and macOS can be obtained from following sources:
|
||||
|
||||
- The [Sideband page](https://unsigned.io/sideband/) on [unsigned.io](https://unsigned.io/)
|
||||
- The [GitHub release page for Sideband](https://github.com/markqvist/Sideband/releases/latest)
|
||||
9
Console/source/sell_rnodes.md
Executable file
@@ -0,0 +1,9 @@
|
||||
[title]: <> (Sell RNodes)
|
||||
## Build & Sell RNodes
|
||||
Creating any number of RNodes is completely free and unrestricted for all personal, non-commercial and humanitarian purposes. Feel free to use all the resources provided here, and on the [unsigned.io](https://unsigned.io/) website. If doing so provides value to you or your community, you are encouraged to [contribute]({ASSET_PATH}contribute.html) whatever you find to be reasonable.
|
||||
|
||||
The RNode Ecosystem is free and non-proprietary, and actively seeks to distribute it's ownership and control. If you want to build RNodes for commercial purposes, including selling them, you must do so adhering to the Open Source licenses that the various parts of the RNode project is released under, and under your own responsibility.
|
||||
|
||||
The RNode Firmware is released under GPLv3, and basing commercial works on it means (among other things), that you must also make your derivatives open source and available under the same terms.
|
||||
|
||||
In practice, this means that you can use the firmware commercially, but you should understand your obligation to provide all future users of the system the same rights that you have been provided by the GPLv3.
|
||||
23
Console/source/software.md
Executable file
@@ -0,0 +1,23 @@
|
||||
[title]: <> (Software)
|
||||
## Software
|
||||
This RNode contains a repository of downloadable software and utilities, that are useful for bootstrapping communications networks, and for replicating RNodes.
|
||||
|
||||
**Please Note!** Whenever you install software onto your computer, there is a risk that someone modified this software to include malicious code. Be extra careful installing anything from this RNode, if you did not get it from a source you trust, or if there is a risk it was modified in transit.
|
||||
|
||||
If possible, you can check that the `SHA-256` hashes of any downloaded files correspond to the list of release hashes published on the [Reticulum Release page](https://github.com/markqvist/Reticulum/releases).
|
||||
|
||||
**You Have The Source!** Due to the size limitations of shipping all this software within an RNode, we don't include separate source-code archives for the below programs, but *all the source code is included within the Python .whl files*!
|
||||
|
||||
You can simply unzip any of them with any program that understands `zip` files, and you will find the source code inside the unzipped directory (for some zip programs, you may need to change the file ending to `.zip`).
|
||||
|
||||
You can also download the copy of the [RNode Firmware source-code]({ASSET_PATH}pkg/rnode_firmware.zip) that is stored in this RNode.
|
||||
<br/><br/>
|
||||
<center>
|
||||
<h3>Choose a software package to get started</h3>
|
||||
<br/>
|
||||
<a href="./s_rns.html"><button type="button" id="task-rns">Reticulum</button></a>
|
||||
<a href="./s_lxmf.html"><button type="button" id="task-rns">LXMF</button></a>
|
||||
<a href="./s_nn.html"><button type="button" id="task-rns">Nomad Network</button></a>
|
||||
<a href="./s_rnsh.html"><button type="button" id="task-rns">RN Shell</button></a>
|
||||
<a href="./s_sideband.html"><button type="button" id="task-rns">Sideband</button></a>
|
||||
</center>
|
||||
17
Console/source/supported.md
Executable file
@@ -0,0 +1,17 @@
|
||||
[title]: <> (Supported Hardware)
|
||||
## Supported Boards & Devices
|
||||
The RNode Firmware supports the following boards:
|
||||
|
||||
- Handheld v2.x RNodes from [unsigned.io](https://unsigned.io/shop/product/handheld-rnode)
|
||||
- Original v1.x RNodes from [unsigned.io](https://unsigned.io/shop/product/rnode)
|
||||
- LilyGO T-Beam v1.1 devices
|
||||
- LilyGO LoRa32 v2.0 devices
|
||||
- LilyGO LoRa32 v2.1 devices
|
||||
- Heltec LoRa32 v2 devices
|
||||
- Homebrew RNodes based on ATmega1284p boards
|
||||
- Homebrew RNodes based on ATmega2560 boards
|
||||
- Homebrew RNodes based on Adafruit Feather ESP32 boards
|
||||
- Homebrew RNodes based on generic ESP32 boards
|
||||
|
||||
## Supported Transceiver Modules
|
||||
The RNode Firmware supports all transceiver modules based on **Semtech SX1276** or **Semtech SX1278** chips, that have an **SPI interface** and expose the **DIO_0** interrupt pin from the chip.
|
||||
267
Device.h
Executable file
@@ -0,0 +1,267 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <Ed25519.h>
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
#include "mbedtls/md.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_flash_partitions.h"
|
||||
#include "esp_partition.h"
|
||||
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
#include "Adafruit_nRFCrypto.h"
|
||||
|
||||
// size of chunk to retrieve from flash sector
|
||||
#define CHUNK_SIZE 128
|
||||
|
||||
#define END_SECTION_SIZE 256
|
||||
|
||||
#if defined(NRF52840_XXAA)
|
||||
// https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map
|
||||
// each section follows along from one another, in this order
|
||||
// this is always at the start of the memory map
|
||||
#define APPLICATION_START 0x26000
|
||||
|
||||
#define USER_DATA_START 0xED000
|
||||
|
||||
#define IMG_SIZE_START 0xFF008
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// Forward declaration from Utilities.h
|
||||
void eeprom_update(int mapped_addr, uint8_t byte);
|
||||
uint8_t eeprom_read(uint32_t addr);
|
||||
void hard_reset(void);
|
||||
|
||||
#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52
|
||||
void eeprom_flush();
|
||||
#endif
|
||||
|
||||
const uint8_t dev_keys [] PROGMEM = {
|
||||
0x0f, 0x15, 0x86, 0x74, 0xa0, 0x7d, 0xf2, 0xde, 0x32, 0x11, 0x29, 0xc1, 0x0d, 0xda, 0xcc, 0xc3,
|
||||
0xe1, 0x9b, 0xac, 0xf2, 0x27, 0x06, 0xee, 0x89, 0x1f, 0x7a, 0xfc, 0xc3, 0x6a, 0xf5, 0x38, 0x08
|
||||
};
|
||||
|
||||
#define DEV_SIG_LEN 64
|
||||
uint8_t dev_sig[DEV_SIG_LEN];
|
||||
|
||||
#define DEV_KEY_LEN 32
|
||||
uint8_t dev_k_prv[DEV_KEY_LEN];
|
||||
uint8_t dev_k_pub[DEV_KEY_LEN];
|
||||
|
||||
#define DEV_HASH_LEN 32
|
||||
uint8_t dev_hash[DEV_HASH_LEN];
|
||||
uint8_t dev_partition_table_hash[DEV_HASH_LEN];
|
||||
uint8_t dev_bootloader_hash[DEV_HASH_LEN];
|
||||
uint8_t dev_firmware_hash[DEV_HASH_LEN];
|
||||
uint8_t dev_firmware_hash_target[DEV_HASH_LEN];
|
||||
|
||||
#define EEPROM_SIG_LEN 128
|
||||
uint8_t dev_eeprom_signature[EEPROM_SIG_LEN];
|
||||
|
||||
bool dev_signature_validated = false;
|
||||
bool fw_signature_validated = true;
|
||||
|
||||
#define DEV_SIG_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN
|
||||
#define dev_sig_addr(a) (a+DEV_SIG_OFFSET)
|
||||
|
||||
#define DEV_FWHASH_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN-DEV_HASH_LEN
|
||||
#define dev_fwhash_addr(a) (a+DEV_FWHASH_OFFSET)
|
||||
|
||||
bool device_signatures_ok() {
|
||||
return dev_signature_validated && fw_signature_validated;
|
||||
}
|
||||
|
||||
void device_validate_signature() {
|
||||
int n_keys = sizeof(dev_keys)/DEV_KEY_LEN;
|
||||
bool valid_signature_found = false;
|
||||
for (int i = 0; i < n_keys; i++) {
|
||||
memcpy(dev_k_pub, dev_keys+DEV_KEY_LEN*i, DEV_KEY_LEN);
|
||||
if (Ed25519::verify(dev_sig, dev_k_pub, dev_hash, DEV_HASH_LEN)) {
|
||||
valid_signature_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_signature_found) {
|
||||
dev_signature_validated = true;
|
||||
} else {
|
||||
dev_signature_validated = false;
|
||||
}
|
||||
}
|
||||
|
||||
void device_save_signature() {
|
||||
device_validate_signature();
|
||||
if (dev_signature_validated) {
|
||||
for (uint8_t i = 0; i < DEV_SIG_LEN; i++) {
|
||||
eeprom_update(dev_sig_addr(i), dev_sig[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void device_load_signature() {
|
||||
for (uint8_t i = 0; i < DEV_SIG_LEN; i++) {
|
||||
#if HAS_EEPROM
|
||||
dev_sig[i] = EEPROM.read(dev_sig_addr(i));
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
dev_sig[i] = eeprom_read(dev_sig_addr(i));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void device_load_firmware_hash() {
|
||||
for (uint8_t i = 0; i < DEV_HASH_LEN; i++) {
|
||||
#if HAS_EEPROM
|
||||
dev_firmware_hash_target[i] = EEPROM.read(dev_fwhash_addr(i));
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
dev_firmware_hash_target[i] = eeprom_read(dev_fwhash_addr(i));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void device_save_firmware_hash() {
|
||||
for (uint8_t i = 0; i < DEV_HASH_LEN; i++) {
|
||||
eeprom_update(dev_fwhash_addr(i), dev_firmware_hash_target[i]);
|
||||
}
|
||||
#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52
|
||||
eeprom_flush();
|
||||
#endif
|
||||
if (!fw_signature_validated) hard_reset();
|
||||
}
|
||||
|
||||
#if MCU_VARIANT == MCU_NRF52
|
||||
uint32_t retrieve_application_size() {
|
||||
uint8_t bytes[4];
|
||||
memcpy(bytes, (const void*)IMG_SIZE_START, 4);
|
||||
uint32_t fw_len = bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
|
||||
return fw_len;
|
||||
}
|
||||
|
||||
void calculate_region_hash(unsigned long long start, unsigned long long end, uint8_t* return_hash) {
|
||||
// this function calculates the hash digest of a region of memory,
|
||||
// currently it is only designed to work for the application region
|
||||
uint8_t chunk[CHUNK_SIZE] = {0};
|
||||
|
||||
// to store potential last chunk of program
|
||||
uint8_t chunk_next[CHUNK_SIZE] = {0};
|
||||
nRFCrypto_Hash hash;
|
||||
|
||||
hash.begin(CRYS_HASH_SHA256_mode);
|
||||
|
||||
uint8_t size;
|
||||
|
||||
while (start < end ) {
|
||||
const void* src = (const void*)start;
|
||||
if (start + CHUNK_SIZE >= end) {
|
||||
size = end - start;
|
||||
}
|
||||
else {
|
||||
size = CHUNK_SIZE;
|
||||
}
|
||||
|
||||
memcpy(chunk, src, CHUNK_SIZE);
|
||||
|
||||
hash.update(chunk, size);
|
||||
|
||||
start += CHUNK_SIZE;
|
||||
}
|
||||
hash.end(return_hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
void device_validate_partitions() {
|
||||
device_load_firmware_hash();
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
esp_partition_t partition;
|
||||
partition.address = ESP_PARTITION_TABLE_OFFSET;
|
||||
partition.size = ESP_PARTITION_TABLE_MAX_LEN;
|
||||
partition.type = ESP_PARTITION_TYPE_DATA;
|
||||
esp_partition_get_sha256(&partition, dev_partition_table_hash);
|
||||
partition.address = ESP_BOOTLOADER_OFFSET;
|
||||
partition.size = ESP_PARTITION_TABLE_OFFSET;
|
||||
partition.type = ESP_PARTITION_TYPE_APP;
|
||||
esp_partition_get_sha256(&partition, dev_bootloader_hash);
|
||||
esp_partition_get_sha256(esp_ota_get_running_partition(), dev_firmware_hash);
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
// todo, add bootloader, partition table, or softdevice?
|
||||
calculate_region_hash(APPLICATION_START, APPLICATION_START+retrieve_application_size(), dev_firmware_hash);
|
||||
#endif
|
||||
#if VALIDATE_FIRMWARE
|
||||
for (uint8_t i = 0; i < DEV_HASH_LEN; i++) {
|
||||
if (dev_firmware_hash_target[i] != dev_firmware_hash[i]) {
|
||||
fw_signature_validated = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool device_firmware_ok() {
|
||||
return fw_signature_validated;
|
||||
}
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
|
||||
bool device_init() {
|
||||
if (bt_ready) {
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
for (uint8_t i=0; i<EEPROM_SIG_LEN; i++){dev_eeprom_signature[i]=EEPROM.read(eeprom_addr(ADDR_SIGNATURE+i));}
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
|
||||
mbedtls_md_starts(&ctx);
|
||||
#if HAS_BLUETOOTH == true || HAS_BLE == true
|
||||
mbedtls_md_update(&ctx, dev_bt_mac, BT_DEV_ADDR_LEN);
|
||||
#else
|
||||
// TODO: Get from BLE stack instead
|
||||
// mbedtls_md_update(&ctx, dev_bt_mac, BT_DEV_ADDR_LEN);
|
||||
#endif
|
||||
mbedtls_md_update(&ctx, dev_eeprom_signature, EEPROM_SIG_LEN);
|
||||
mbedtls_md_finish(&ctx, dev_hash);
|
||||
mbedtls_md_free(&ctx);
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
for (uint8_t i=0; i<EEPROM_SIG_LEN; i++){dev_eeprom_signature[i]=eeprom_read(eeprom_addr(ADDR_SIGNATURE+i));}
|
||||
nRFCrypto.begin();
|
||||
|
||||
nRFCrypto_Hash hash;
|
||||
|
||||
hash.begin(CRYS_HASH_SHA256_mode);
|
||||
|
||||
#if HAS_BLUETOOTH == true || HAS_BLE == true
|
||||
hash.update(dev_bt_mac, BT_DEV_ADDR_LEN);
|
||||
#else
|
||||
// TODO: Get from BLE stack instead
|
||||
// hash.update(dev_bt_mac, BT_DEV_ADDR_LEN);
|
||||
#endif
|
||||
hash.update(dev_eeprom_signature, EEPROM_SIG_LEN);
|
||||
|
||||
hash.end(dev_hash);
|
||||
#endif
|
||||
device_load_signature();
|
||||
device_validate_signature();
|
||||
|
||||
device_validate_partitions();
|
||||
|
||||
#if MCU_VARIANT == MCU_NRF52
|
||||
nRFCrypto.end();
|
||||
#endif
|
||||
device_init_done = true;
|
||||
return device_init_done && fw_signature_validated;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
1
Documentation/README.md
Executable file
@@ -0,0 +1 @@
|
||||
# RNode Documentation
|
||||
BIN
Documentation/RNode_v1_Manual.pdf
Executable file
BIN
Documentation/images/126dcfe92fb7.webp
Executable file
|
After Width: | Height: | Size: 36 KiB |
BIN
Documentation/images/devboards_1.webp
Executable file
|
After Width: | Height: | Size: 182 KiB |
BIN
Documentation/images/rnv21_bgp.webp
Executable file
|
After Width: | Height: | Size: 176 KiB |
BIN
Documentation/rnfw_1.jpg
Executable file
|
After Width: | Height: | Size: 112 KiB |
35
FileStream.h
Executable file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_RNS
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "FileSystemType.h"
|
||||
|
||||
class FileStream : public RNS::FileStreamImpl {
|
||||
|
||||
private:
|
||||
std::unique_ptr<File> _file;
|
||||
bool _closed = false;
|
||||
|
||||
public:
|
||||
FileStream(File* file) : RNS::FileStreamImpl(), _file(file) {}
|
||||
virtual ~FileStream() { if (!_closed) close(); }
|
||||
|
||||
public:
|
||||
inline virtual const char* name() { return _file->name(); }
|
||||
inline virtual size_t size() { return _file->size(); }
|
||||
inline virtual void close() { _closed = true; _file->close(); }
|
||||
|
||||
// Print overrides
|
||||
inline virtual size_t write(uint8_t byte) { return _file->write(byte); }
|
||||
inline virtual size_t write(const uint8_t *buffer, size_t size) { return _file->write(buffer, size); }
|
||||
|
||||
// Stream overrides
|
||||
inline virtual int available() { return _file->available(); }
|
||||
inline virtual int read() { return _file->read(); }
|
||||
inline virtual int peek() { return _file->peek(); }
|
||||
inline virtual void flush() { _file->flush(); }
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
478
FileSystem.cpp
Executable file
@@ -0,0 +1,478 @@
|
||||
#include "FileSystem.h"
|
||||
#include "FileStream.h"
|
||||
#include "FileSystemType.h"
|
||||
|
||||
#ifdef HAS_RNS
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS
|
||||
|
||||
inline int _countLfsBlock(void *p, lfs_block_t block) {
|
||||
lfs_size_t *size = (lfs_size_t*) p;
|
||||
*size += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_ssize_t usedBlocks() {
|
||||
lfs_size_t size = 0;
|
||||
lfs_traverse(FS._getFS(), _countLfsBlock, &size);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t usedBytes() {
|
||||
const lfs_config* config = FS._getFS()->cfg;
|
||||
const size_t usedBlockCount = usedBlocks();
|
||||
return config->block_size * usedBlockCount;
|
||||
}
|
||||
|
||||
size_t totalBytes() {
|
||||
const lfs_config* config = FS._getFS()->cfg;
|
||||
return config->block_size * config->block_count;
|
||||
}
|
||||
|
||||
#elif FS_TYPE == FS_TYPE_FLASHFS
|
||||
|
||||
Adafruit_FlashTransport_SPI g_flashTransport(SS, SPI);
|
||||
|
||||
//Flash definition structure for GD25Q16C Flash (RAK15001)
|
||||
Cached_SPIFlash g_flash(&g_flashTransport);
|
||||
SPIFlash_Device_t g_RAK15001 {
|
||||
.total_size = (1UL << 21),
|
||||
.start_up_time_us = 5000,
|
||||
.manufacturer_id = 0xc8,
|
||||
.memory_type = 0x40,
|
||||
.capacity = 0x15,
|
||||
.max_clock_speed_mhz = 15,
|
||||
.quad_enable_bit_mask = 0x00,
|
||||
.has_sector_protection = false,
|
||||
.supports_fast_read = true,
|
||||
.supports_qspi = false,
|
||||
.supports_qspi_writes = false,
|
||||
.write_status_register_split = false,
|
||||
.single_status_byte = true,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
bool FileSystem::init() {
|
||||
TRACE("Initializing filesystem...");
|
||||
try {
|
||||
#if FS_TYPE == FS_TYPE_SPIFFS
|
||||
// Initialize SPIFFS
|
||||
INFO("SPIFFS mounting filesystem");
|
||||
if (!SPIFFS.begin(true, "")) {
|
||||
ERROR("SPIFFS filesystem mount failed");
|
||||
return false;
|
||||
}
|
||||
INFO("SPIFFS filesystem is ready");
|
||||
#elif FS_TYPE == FS_TYPE_LITTLEFS
|
||||
// Initialize LittleFS
|
||||
INFO("LittleFS mounting filesystem");
|
||||
if (!LittleFS.begin(true, "")) {
|
||||
ERROR("LittleFS filesystem mount failed");
|
||||
return false;
|
||||
}
|
||||
DEBUG("LittleFS filesystem is ready");
|
||||
#elif FS_TYPE == FS_TYPE_INTERNALFS
|
||||
// Initialize InternalFileSystem
|
||||
INFO("InternalFS mounting filesystem");
|
||||
if (!InternalFS.begin()) {
|
||||
ERROR("InternalFS filesystem mount failed");
|
||||
return false;
|
||||
}
|
||||
INFO("InternalFS filesystem is ready");
|
||||
#elif FS_TYPE == FS_TYPE_FLASHFS
|
||||
// Initialize FlashFileSystem
|
||||
INFO("FlashFS mounting filesystem");
|
||||
if (!g_flash.begin(&g_RAK15001)) {
|
||||
ERROR("FlashFS failed to initialize");
|
||||
return false;
|
||||
}
|
||||
if (!FlashFS.begin(&g_flash)) {
|
||||
ERROR("FlashFS filesystem mount failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// Ensure filesystem is writable and reformat if not
|
||||
RNS::Bytes test("test");
|
||||
if (write_file("/test", test) < 4) {
|
||||
HEAD("Failed to write test file, filesystem is being reformatted...", RNS::LOG_CRITICAL);
|
||||
//FS.format();
|
||||
reformat();
|
||||
}
|
||||
else {
|
||||
remove_file("/test");
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
//ERROR("FileSystem init Exception: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
TRACE("Finished initializing");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::format() {
|
||||
INFO("Formatting filesystem...");
|
||||
try {
|
||||
if (!FS.format()) {
|
||||
ERROR("Format failed!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("FileSystem reformat Exception: " + std::string(e.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileSystem::reformat() {
|
||||
INFO("Reformatting filesystem...");
|
||||
try {
|
||||
RNS::Bytes eeprom;
|
||||
read_file("/eeprom", eeprom);
|
||||
RNS::Bytes transport_identity;
|
||||
read_file("/transport_identity", transport_identity);
|
||||
//RNS::Bytes time_offset;
|
||||
//read_file("/time_offset", time_offset);
|
||||
if (!FS.format()) {
|
||||
ERROR("Format failed!");
|
||||
return false;
|
||||
}
|
||||
if (eeprom) {
|
||||
write_file("/eeprom", eeprom);
|
||||
}
|
||||
if (transport_identity) {
|
||||
write_file("/transport_identity", transport_identity);
|
||||
}
|
||||
//if (time_offset) {
|
||||
// write_file("/time_offset", time_offset);
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("FileSystem reformat Exception: " + std::string(e.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
void FileSystem::listDir(const char* dir, const char* prefix /*= ""*/) {
|
||||
Serial.print(prefix);
|
||||
std::string full_dir(dir);
|
||||
if (full_dir.compare("/") != 0) {
|
||||
full_dir += "/";
|
||||
}
|
||||
Serial.println(full_dir.c_str());
|
||||
std::string pre(prefix);
|
||||
pre.append(" ");
|
||||
try {
|
||||
File root = FS.open(dir);
|
||||
if (!root) {
|
||||
Serial.print(pre.c_str());
|
||||
Serial.println("(failed to open directory)");
|
||||
return;
|
||||
}
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
char* name = (char*)file.name();
|
||||
std::string recurse_dir(full_dir);
|
||||
if (file.isDirectory()) {
|
||||
recurse_dir += name;
|
||||
listDir(recurse_dir.c_str(), pre.c_str());
|
||||
}
|
||||
else {
|
||||
Serial.print(pre.c_str());
|
||||
//Serial.print("FILE: ");
|
||||
Serial.print(name);
|
||||
Serial.print(" (");
|
||||
Serial.print(file.size());
|
||||
Serial.println(" bytes)");
|
||||
}
|
||||
file.close();
|
||||
file = root.openNextFile();
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Serial.print("listDir Exception: ");
|
||||
Serial.println(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystem::dumpDir(const char* dir) {
|
||||
Serial.print("DIR: ");
|
||||
std::string full_dir(dir);
|
||||
if (full_dir.compare("/") != 0) {
|
||||
full_dir += "/";
|
||||
}
|
||||
Serial.println(full_dir.c_str());
|
||||
try {
|
||||
File root = FS.open(dir);
|
||||
if (!root) {
|
||||
Serial.println("(failed to open directory)");
|
||||
return;
|
||||
}
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
char* name = (char*)file.name();
|
||||
if (file.isDirectory()) {
|
||||
std::string recurse_dir(full_dir);
|
||||
recurse_dir += name;
|
||||
dumpDir(recurse_dir.c_str());
|
||||
}
|
||||
else {
|
||||
Serial.print("\nFILE: ");
|
||||
Serial.print(name);
|
||||
Serial.print(" (");
|
||||
Serial.print(file.size());
|
||||
Serial.println(" bytes)");
|
||||
char data[4096];
|
||||
size_t size = file.size();
|
||||
size_t read = file.readBytes(data, (size < sizeof(data)) ? size : sizeof(data));
|
||||
Serial.write(data, read);
|
||||
Serial.println("");
|
||||
}
|
||||
file.close();
|
||||
file = root.openNextFile();
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Serial.print("dumpDir Exception: ");
|
||||
Serial.println(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*virtua*/ bool FileSystem::file_exists(const char* file_path) {
|
||||
TRACEF("file_exists: checking for existence of file %s", file_path);
|
||||
/*
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
File file(FS);
|
||||
if (file.open(file_path, FILE_O_READ)) {
|
||||
#else
|
||||
File file = FS.open(file_path, FILE_READ);
|
||||
if (file) {
|
||||
#endif
|
||||
bool is_directory = file.isDirectory();
|
||||
file.close();
|
||||
return !is_directory;
|
||||
}
|
||||
return false;
|
||||
*/
|
||||
return FS.exists(file_path);
|
||||
}
|
||||
|
||||
/*virtua*/ size_t FileSystem::read_file(const char* file_path, RNS::Bytes& data) {
|
||||
TRACEF("read_file: reading from file %s", file_path);
|
||||
size_t read = 0;
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
File file(FS);
|
||||
if (file.open(file_path, FILE_O_READ)) {
|
||||
#else
|
||||
File file = FS.open(file_path, FILE_READ);
|
||||
if (file) {
|
||||
#endif
|
||||
size_t size = file.size();
|
||||
read = file.readBytes((char*)data.writable(size), size);
|
||||
TRACEF("read_file: read %u bytes from file %s", read, file_path);
|
||||
if (read != size) {
|
||||
ERRORF("read_file: failed to read file %s", file_path);
|
||||
data.resize(read);
|
||||
}
|
||||
//TRACE("read_file: closing input file");
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
ERRORF("read_file: failed to open input file %s", file_path);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
/*virtua*/ size_t FileSystem::write_file(const char* file_path, const RNS::Bytes& data) {
|
||||
TRACEF("write_file: writing to file %s", file_path);
|
||||
// CBA TODO Replace remove with working truncation
|
||||
if (FS.exists(file_path)) {
|
||||
FS.remove(file_path);
|
||||
}
|
||||
size_t wrote = 0;
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
File file(FS);
|
||||
if (file.open(file_path, FILE_O_WRITE)) {
|
||||
#else
|
||||
File file = FS.open(file_path, FILE_WRITE);
|
||||
if (file) {
|
||||
#endif
|
||||
// Seek to beginning to overwrite
|
||||
//file.seek(0);
|
||||
//file.truncate(0);
|
||||
wrote = file.write(data.data(), data.size());
|
||||
TRACEF("write_file: wrote %u bytes to file %s", wrote, file_path);
|
||||
if (wrote < data.size()) {
|
||||
WARNINGF("write_file: not all data was written to file %s", file_path);
|
||||
}
|
||||
//TRACE("write_file: closing output file");
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
ERRORF("write_file: failed to open output file %s", file_path);
|
||||
}
|
||||
return wrote;
|
||||
}
|
||||
|
||||
/*virtual*/ RNS::FileStream FileSystem::open_file(const char* file_path, RNS::FileStream::MODE file_mode) {
|
||||
TRACEF("open_file: opening file %s", file_path);
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
int mode;
|
||||
if (file_mode == RNS::FileStream::MODE_READ) {
|
||||
mode = FILE_O_READ;
|
||||
}
|
||||
else if (file_mode == RNS::FileStream::MODE_WRITE) {
|
||||
mode = FILE_O_WRITE;
|
||||
// CBA TODO Replace remove with working truncation
|
||||
if (FS.exists(file_path)) {
|
||||
FS.remove(file_path);
|
||||
}
|
||||
}
|
||||
else if (file_mode == RNS::FileStream::MODE_APPEND) {
|
||||
// CBA This is the default write mode for nrf52 littlefs
|
||||
mode = FILE_O_WRITE;
|
||||
}
|
||||
else {
|
||||
ERRORF("open_file: unsupported mode %d", file_mode);
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
File* file = new File(FS);
|
||||
if (!file->open(file_path, mode)) {
|
||||
ERRORF("open_file: failed to open output file %s", file_path);
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
// Seek to beginning to overwrite (this is failing on nrf52)
|
||||
//if (file_mode == RNS::FileStream::MODE_WRITE) {
|
||||
// file->seek(0);
|
||||
// file->truncate(0);
|
||||
//}
|
||||
TRACEF("open_file: successfully opened file %s", file_path);
|
||||
return RNS::FileStream(new FileStream(file));
|
||||
#else
|
||||
const char* mode;
|
||||
if (file_mode == RNS::FileStream::MODE_READ) {
|
||||
mode = FILE_READ;
|
||||
}
|
||||
else if (file_mode == RNS::FileStream::MODE_WRITE) {
|
||||
mode = FILE_WRITE;
|
||||
}
|
||||
else if (file_mode == RNS::FileStream::MODE_APPEND) {
|
||||
mode = FILE_APPEND;
|
||||
}
|
||||
else {
|
||||
ERRORF("open_file: unsupported mode %d", file_mode);
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
TRACEF("open_file: opening file %s in mode %s", file_path, mode);
|
||||
// CBA Using copy constructor to obtain File*
|
||||
File* file = new File(FS.open(file_path, mode));
|
||||
if (file == nullptr || !(*file)) {
|
||||
ERRORF("open_file: failed to open output file %s", file_path);
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
TRACEF("open_file: successfully opened file %s", file_path);
|
||||
return RNS::FileStream(new FileStream(file));
|
||||
#endif
|
||||
}
|
||||
|
||||
/*virtua*/ bool FileSystem::remove_file(const char* file_path) {
|
||||
TRACEF("remove_file: removing file %s", file_path);
|
||||
return FS.remove(file_path);
|
||||
}
|
||||
|
||||
/*virtua*/ bool FileSystem::rename_file(const char* from_file_path, const char* to_file_path) {
|
||||
TRACEF("rename_file: renaming file %s to %s", from_file_path, to_file_path);
|
||||
return FS.rename(from_file_path, to_file_path);
|
||||
}
|
||||
|
||||
/*virtua*/ bool FileSystem::directory_exists(const char* directory_path) {
|
||||
TRACEF("directory_exists: checking for existence of directory %s", directory_path);
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
File file(FS);
|
||||
if (file.open(directory_path, FILE_O_READ)) {
|
||||
#else
|
||||
File file = FS.open(directory_path, FILE_READ);
|
||||
if (file) {
|
||||
#endif
|
||||
bool is_directory = file.isDirectory();
|
||||
file.close();
|
||||
return is_directory;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*virtua*/ bool FileSystem::create_directory(const char* directory_path) {
|
||||
TRACEF("create_directory: creating directory %s", directory_path);
|
||||
if (!FS.mkdir(directory_path)) {
|
||||
ERROR("create_directory: failed to create directory " + std::string(directory_path));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*virtua*/ bool FileSystem::remove_directory(const char* directory_path) {
|
||||
TRACEF("remove_directory: removing directory %s", directory_path);
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
||||
if (!FS.rmdir_r(directory_path)) {
|
||||
#else
|
||||
if (!FS.rmdir(directory_path)) {
|
||||
#endif
|
||||
ERROR("remove_directory: failed to remove directory " + std::string(directory_path));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*virtua*/ std::list<std::string> FileSystem::list_directory(const char* directory_path) {
|
||||
TRACEF("list_directory: listing directory %s", directory_path);
|
||||
std::list<std::string> files;
|
||||
File root = FS.open(directory_path);
|
||||
if (!root) {
|
||||
ERROR("list_directory: failed to open directory " + std::string(directory_path));
|
||||
return files;
|
||||
}
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
if (!file.isDirectory()) {
|
||||
char* name = (char*)file.name();
|
||||
files.push_back(name);
|
||||
}
|
||||
// CBA Following close required to avoid leaking memory
|
||||
file.close();
|
||||
file = root.openNextFile();
|
||||
}
|
||||
root.close();
|
||||
TRACE("list_directory: returning directory listing");
|
||||
return files;
|
||||
}
|
||||
|
||||
/*virtual*/ size_t FileSystem::storage_size() {
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS
|
||||
return totalBytes();
|
||||
#else
|
||||
return FS.totalBytes();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*virtual*/ size_t FileSystem::storage_available() {
|
||||
#if FS_TYPE == FS_TYPE_INTERNALFS
|
||||
return (totalBytes() - usedBytes());
|
||||
#else
|
||||
return (FS.totalBytes() - FS.usedBytes());
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
41
FileSystem.h
Executable file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_RNS
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <FileStream.h>
|
||||
#include <Bytes.h>
|
||||
#include <Type.h>
|
||||
|
||||
#include <Stream.h>
|
||||
|
||||
class FileSystem : public RNS::FileSystemImpl {
|
||||
|
||||
public:
|
||||
FileSystem() {}
|
||||
|
||||
bool init();
|
||||
bool format();
|
||||
bool reformat();
|
||||
|
||||
// CBA Debug
|
||||
static void listDir(const char* dir, const char* prefix = "");
|
||||
static void dumpDir(const char* dir);
|
||||
|
||||
public:
|
||||
virtual bool file_exists(const char* file_path);
|
||||
virtual size_t read_file(const char* file_path, RNS::Bytes& data);
|
||||
virtual size_t write_file(const char* file_path, const RNS::Bytes& data);
|
||||
virtual RNS::FileStream open_file(const char* file_path, RNS::FileStream::MODE file_mode);
|
||||
virtual bool remove_file(const char* file_path);
|
||||
virtual bool rename_file(const char* from_file_path, const char* to_file_path);
|
||||
virtual bool directory_exists(const char* directory_path);
|
||||
virtual bool create_directory(const char* directory_path);
|
||||
virtual bool remove_directory(const char* directory_path);
|
||||
virtual std::list<std::string> list_directory(const char* directory_path);
|
||||
virtual size_t storage_size();
|
||||
virtual size_t storage_available();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
51
FileSystemType.h
Executable file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_RNS
|
||||
|
||||
// CBA This header file was required to break-out defined and includes here that could
|
||||
// not be include in FileSystem.h due to conlficts with SPIFFS in Console.h
|
||||
|
||||
#include "Boards.h"
|
||||
|
||||
#define FS_TYPE_SPIFFS 0
|
||||
#define FS_TYPE_LITTLEFS 1
|
||||
#define FS_TYPE_INTERNALFS 2
|
||||
#define FS_TYPE_FLASHFS 3
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
#if defined(USE_FLASHFS)
|
||||
#define FS_TYPE FS_TYPE_FLASHFS
|
||||
#else
|
||||
//#define FS_TYPE FS_TYPE_SPIFFS
|
||||
#define FS_TYPE FS_TYPE_LITTLEFS
|
||||
#endif
|
||||
#elif MCU_VARIANT == MCU_NRF52
|
||||
#if defined(USE_FLASHFS)
|
||||
#define FS_TYPE FS_TYPE_FLASHFS
|
||||
#else
|
||||
#define FS_TYPE FS_TYPE_INTERNALFS
|
||||
#endif
|
||||
#else
|
||||
#define FS_TYPE FS_TYPE_SPIFFS
|
||||
#endif
|
||||
|
||||
#if FS_TYPE == FS_TYPE_SPIFFS
|
||||
#include <SPIFFS.h>
|
||||
#define FS SPIFFS
|
||||
#elif FS_TYPE == FS_TYPE_LITTLEFS
|
||||
#include <LittleFS.h>
|
||||
#define FS LittleFS
|
||||
#elif FS_TYPE == FS_TYPE_INTERNALFS
|
||||
#include <InternalFileSystem.h>
|
||||
#define FS InternalFS
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#elif FS_TYPE == FS_TYPE_FLASHFS
|
||||
#include <Cached_SPIFlash.h>
|
||||
#include <FlashFileSystem.h>
|
||||
#define FS FlashFS
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#else
|
||||
#error "FileSystem type not specified"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
131
Fonts/Org_01.h
Executable file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
#include <Adafruit_GFX.h>
|
||||
|
||||
// Org_v01 by Orgdot (www.orgdot.com/aliasfonts). A tiny,
|
||||
// stylized font with all characters within a 6 pixel height.
|
||||
|
||||
const uint8_t Org_01Bitmaps[] PROGMEM = {
|
||||
0xE8, 0xA0, 0x57, 0xD5, 0xF5, 0x00, 0xFD, 0x3E, 0x5F, 0x80, 0x88, 0x88,
|
||||
0x88, 0x80, 0xF4, 0xBF, 0x2E, 0x80, 0x80, 0x6A, 0x40, 0x95, 0x80, 0xAA,
|
||||
0x80, 0x5D, 0x00, 0xC0, 0xF0, 0x80, 0x08, 0x88, 0x88, 0x00, 0xFC, 0x63,
|
||||
0x1F, 0x80, 0xF8, 0xF8, 0x7F, 0x0F, 0x80, 0xF8, 0x7E, 0x1F, 0x80, 0x8C,
|
||||
0x7E, 0x10, 0x80, 0xFC, 0x3E, 0x1F, 0x80, 0xFC, 0x3F, 0x1F, 0x80, 0xF8,
|
||||
0x42, 0x10, 0x80, 0xFC, 0x7F, 0x1F, 0x80, 0xFC, 0x7E, 0x1F, 0x80, 0x90,
|
||||
0xB0, 0x2A, 0x22, 0xF0, 0xF0, 0x88, 0xA8, 0xF8, 0x4E, 0x02, 0x00, 0xFD,
|
||||
0x6F, 0x0F, 0x80, 0xFC, 0x7F, 0x18, 0x80, 0xF4, 0x7D, 0x1F, 0x00, 0xFC,
|
||||
0x21, 0x0F, 0x80, 0xF4, 0x63, 0x1F, 0x00, 0xFC, 0x3F, 0x0F, 0x80, 0xFC,
|
||||
0x3F, 0x08, 0x00, 0xFC, 0x2F, 0x1F, 0x80, 0x8C, 0x7F, 0x18, 0x80, 0xF9,
|
||||
0x08, 0x4F, 0x80, 0x78, 0x85, 0x2F, 0x80, 0x8D, 0xB1, 0x68, 0x80, 0x84,
|
||||
0x21, 0x0F, 0x80, 0xFD, 0x6B, 0x5A, 0x80, 0xFC, 0x63, 0x18, 0x80, 0xFC,
|
||||
0x63, 0x1F, 0x80, 0xFC, 0x7F, 0x08, 0x00, 0xFC, 0x63, 0x3F, 0x80, 0xFC,
|
||||
0x7F, 0x29, 0x00, 0xFC, 0x3E, 0x1F, 0x80, 0xF9, 0x08, 0x42, 0x00, 0x8C,
|
||||
0x63, 0x1F, 0x80, 0x8C, 0x62, 0xA2, 0x00, 0xAD, 0x6B, 0x5F, 0x80, 0x8A,
|
||||
0x88, 0xA8, 0x80, 0x8C, 0x54, 0x42, 0x00, 0xF8, 0x7F, 0x0F, 0x80, 0xEA,
|
||||
0xC0, 0x82, 0x08, 0x20, 0x80, 0xD5, 0xC0, 0x54, 0xF8, 0x80, 0xF1, 0xFF,
|
||||
0x8F, 0x99, 0xF0, 0xF8, 0x8F, 0x1F, 0x99, 0xF0, 0xFF, 0x8F, 0x6B, 0xA4,
|
||||
0xF9, 0x9F, 0x10, 0x8F, 0x99, 0x90, 0xF0, 0x55, 0xC0, 0x8A, 0xF9, 0x90,
|
||||
0xF8, 0xFD, 0x63, 0x10, 0xF9, 0x99, 0xF9, 0x9F, 0xF9, 0x9F, 0x80, 0xF9,
|
||||
0x9F, 0x20, 0xF8, 0x88, 0x47, 0x1F, 0x27, 0xC8, 0x42, 0x00, 0x99, 0x9F,
|
||||
0x99, 0x97, 0x8C, 0x6B, 0xF0, 0x96, 0x69, 0x99, 0x9F, 0x10, 0x2E, 0x8F,
|
||||
0x2B, 0x22, 0xF8, 0x89, 0xA8, 0x0F, 0xE0};
|
||||
|
||||
const GFXglyph Org_01Glyphs[] PROGMEM = {{0, 0, 0, 6, 0, 1}, // 0x20 ' '
|
||||
{0, 1, 5, 2, 0, -4}, // 0x21 '!'
|
||||
{1, 3, 1, 4, 0, -4}, // 0x22 '"'
|
||||
{2, 5, 5, 6, 0, -4}, // 0x23 '#'
|
||||
{6, 5, 5, 6, 0, -4}, // 0x24 '$'
|
||||
{10, 5, 5, 6, 0, -4}, // 0x25 '%'
|
||||
{14, 5, 5, 6, 0, -4}, // 0x26 '&'
|
||||
{18, 1, 1, 2, 0, -4}, // 0x27 '''
|
||||
{19, 2, 5, 3, 0, -4}, // 0x28 '('
|
||||
{21, 2, 5, 3, 0, -4}, // 0x29 ')'
|
||||
{23, 3, 3, 4, 0, -3}, // 0x2A '*'
|
||||
{25, 3, 3, 4, 0, -3}, // 0x2B '+'
|
||||
{27, 1, 2, 2, 0, 0}, // 0x2C ','
|
||||
{28, 4, 1, 5, 0, -2}, // 0x2D '-'
|
||||
{29, 1, 1, 2, 0, 0}, // 0x2E '.'
|
||||
{30, 5, 5, 6, 0, -4}, // 0x2F '/'
|
||||
{34, 5, 5, 6, 0, -4}, // 0x30 '0'
|
||||
{38, 1, 5, 2, 0, -4}, // 0x31 '1'
|
||||
{39, 5, 5, 6, 0, -4}, // 0x32 '2'
|
||||
{43, 5, 5, 6, 0, -4}, // 0x33 '3'
|
||||
{47, 5, 5, 6, 0, -4}, // 0x34 '4'
|
||||
{51, 5, 5, 6, 0, -4}, // 0x35 '5'
|
||||
{55, 5, 5, 6, 0, -4}, // 0x36 '6'
|
||||
{59, 5, 5, 6, 0, -4}, // 0x37 '7'
|
||||
{63, 5, 5, 6, 0, -4}, // 0x38 '8'
|
||||
{67, 5, 5, 6, 0, -4}, // 0x39 '9'
|
||||
{71, 1, 4, 2, 0, -3}, // 0x3A ':'
|
||||
{72, 1, 4, 2, 0, -3}, // 0x3B ';'
|
||||
{73, 3, 5, 4, 0, -4}, // 0x3C '<'
|
||||
{75, 4, 3, 5, 0, -3}, // 0x3D '='
|
||||
{77, 3, 5, 4, 0, -4}, // 0x3E '>'
|
||||
{79, 5, 5, 6, 0, -4}, // 0x3F '?'
|
||||
{83, 5, 5, 6, 0, -4}, // 0x40 '@'
|
||||
{87, 5, 5, 6, 0, -4}, // 0x41 'A'
|
||||
{91, 5, 5, 6, 0, -4}, // 0x42 'B'
|
||||
{95, 5, 5, 6, 0, -4}, // 0x43 'C'
|
||||
{99, 5, 5, 6, 0, -4}, // 0x44 'D'
|
||||
{103, 5, 5, 6, 0, -4}, // 0x45 'E'
|
||||
{107, 5, 5, 6, 0, -4}, // 0x46 'F'
|
||||
{111, 5, 5, 6, 0, -4}, // 0x47 'G'
|
||||
{115, 5, 5, 6, 0, -4}, // 0x48 'H'
|
||||
{119, 5, 5, 6, 0, -4}, // 0x49 'I'
|
||||
{123, 5, 5, 6, 0, -4}, // 0x4A 'J'
|
||||
{127, 5, 5, 6, 0, -4}, // 0x4B 'K'
|
||||
{131, 5, 5, 6, 0, -4}, // 0x4C 'L'
|
||||
{135, 5, 5, 6, 0, -4}, // 0x4D 'M'
|
||||
{139, 5, 5, 6, 0, -4}, // 0x4E 'N'
|
||||
{143, 5, 5, 6, 0, -4}, // 0x4F 'O'
|
||||
{147, 5, 5, 6, 0, -4}, // 0x50 'P'
|
||||
{151, 5, 5, 6, 0, -4}, // 0x51 'Q'
|
||||
{155, 5, 5, 6, 0, -4}, // 0x52 'R'
|
||||
{159, 5, 5, 6, 0, -4}, // 0x53 'S'
|
||||
{163, 5, 5, 6, 0, -4}, // 0x54 'T'
|
||||
{167, 5, 5, 6, 0, -4}, // 0x55 'U'
|
||||
{171, 5, 5, 6, 0, -4}, // 0x56 'V'
|
||||
{175, 5, 5, 6, 0, -4}, // 0x57 'W'
|
||||
{179, 5, 5, 6, 0, -4}, // 0x58 'X'
|
||||
{183, 5, 5, 6, 0, -4}, // 0x59 'Y'
|
||||
{187, 5, 5, 6, 0, -4}, // 0x5A 'Z'
|
||||
{191, 2, 5, 3, 0, -4}, // 0x5B '['
|
||||
{193, 5, 5, 6, 0, -4}, // 0x5C '\'
|
||||
{197, 2, 5, 3, 0, -4}, // 0x5D ']'
|
||||
{199, 3, 2, 4, 0, -4}, // 0x5E '^'
|
||||
{200, 5, 1, 6, 0, 1}, // 0x5F '_'
|
||||
{201, 1, 1, 2, 0, -4}, // 0x60 '`'
|
||||
{202, 4, 4, 5, 0, -3}, // 0x61 'a'
|
||||
{204, 4, 5, 5, 0, -4}, // 0x62 'b'
|
||||
{207, 4, 4, 5, 0, -3}, // 0x63 'c'
|
||||
{209, 4, 5, 5, 0, -4}, // 0x64 'd'
|
||||
{212, 4, 4, 5, 0, -3}, // 0x65 'e'
|
||||
{214, 3, 5, 4, 0, -4}, // 0x66 'f'
|
||||
{216, 4, 5, 5, 0, -3}, // 0x67 'g'
|
||||
{219, 4, 5, 5, 0, -4}, // 0x68 'h'
|
||||
{222, 1, 4, 2, 0, -3}, // 0x69 'i'
|
||||
{223, 2, 5, 3, 0, -3}, // 0x6A 'j'
|
||||
{225, 4, 5, 5, 0, -4}, // 0x6B 'k'
|
||||
{228, 1, 5, 2, 0, -4}, // 0x6C 'l'
|
||||
{229, 5, 4, 6, 0, -3}, // 0x6D 'm'
|
||||
{232, 4, 4, 5, 0, -3}, // 0x6E 'n'
|
||||
{234, 4, 4, 5, 0, -3}, // 0x6F 'o'
|
||||
{236, 4, 5, 5, 0, -3}, // 0x70 'p'
|
||||
{239, 4, 5, 5, 0, -3}, // 0x71 'q'
|
||||
{242, 4, 4, 5, 0, -3}, // 0x72 'r'
|
||||
{244, 4, 4, 5, 0, -3}, // 0x73 's'
|
||||
{246, 5, 5, 6, 0, -4}, // 0x74 't'
|
||||
{250, 4, 4, 5, 0, -3}, // 0x75 'u'
|
||||
{252, 4, 4, 5, 0, -3}, // 0x76 'v'
|
||||
{254, 5, 4, 6, 0, -3}, // 0x77 'w'
|
||||
{257, 4, 4, 5, 0, -3}, // 0x78 'x'
|
||||
{259, 4, 5, 5, 0, -3}, // 0x79 'y'
|
||||
{262, 4, 4, 5, 0, -3}, // 0x7A 'z'
|
||||
{264, 3, 5, 4, 0, -4}, // 0x7B '{'
|
||||
{266, 1, 5, 2, 0, -4}, // 0x7C '|'
|
||||
{267, 3, 5, 4, 0, -4}, // 0x7D '}'
|
||||
{269, 5, 3, 6, 0, -3}}; // 0x7E '~'
|
||||
|
||||
const GFXfont Org_01 PROGMEM = {(uint8_t *)Org_01Bitmaps,
|
||||
(GFXglyph *)Org_01Glyphs, 0x20, 0x7E, 7};
|
||||
|
||||
// Approx. 943 bytes
|
||||
123
Fonts/PicoPixel.h
Executable file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
#include <Adafruit_GFX.h>
|
||||
|
||||
// Picopixel by Sebastian Weber. A tiny font
|
||||
// with all characters within a 6 pixel height.
|
||||
|
||||
const uint8_t PicopixelBitmaps[] PROGMEM = {
|
||||
0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A,
|
||||
0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60,
|
||||
0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C,
|
||||
0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94,
|
||||
0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA,
|
||||
0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4,
|
||||
0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58,
|
||||
0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7,
|
||||
0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55,
|
||||
0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5,
|
||||
0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE,
|
||||
0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92,
|
||||
0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75,
|
||||
0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54,
|
||||
0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A};
|
||||
|
||||
const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' '
|
||||
{0, 1, 5, 2, 0, -4}, // 0x21 '!'
|
||||
{1, 3, 2, 4, 0, -4}, // 0x22 '"'
|
||||
{2, 5, 5, 6, 0, -4}, // 0x23 '#'
|
||||
{6, 3, 6, 4, 0, -4}, // 0x24 '$'
|
||||
{9, 3, 5, 4, 0, -4}, // 0x25 '%'
|
||||
{11, 4, 5, 5, 0, -4}, // 0x26 '&'
|
||||
{14, 1, 2, 2, 0, -4}, // 0x27 '''
|
||||
{15, 2, 5, 3, 0, -4}, // 0x28 '('
|
||||
{17, 2, 5, 3, 0, -4}, // 0x29 ')'
|
||||
{19, 3, 3, 4, 0, -3}, // 0x2A '*'
|
||||
{21, 3, 3, 4, 0, -3}, // 0x2B '+'
|
||||
{23, 2, 2, 3, 0, 0}, // 0x2C ','
|
||||
{24, 3, 1, 4, 0, -2}, // 0x2D '-'
|
||||
{25, 1, 1, 2, 0, 0}, // 0x2E '.'
|
||||
{26, 3, 5, 4, 0, -4}, // 0x2F '/'
|
||||
{28, 3, 5, 4, 0, -4}, // 0x30 '0'
|
||||
{30, 2, 5, 3, 0, -4}, // 0x31 '1'
|
||||
{32, 3, 5, 4, 0, -4}, // 0x32 '2'
|
||||
{34, 3, 5, 4, 0, -4}, // 0x33 '3'
|
||||
{36, 3, 5, 4, 0, -4}, // 0x34 '4'
|
||||
{38, 3, 5, 4, 0, -4}, // 0x35 '5'
|
||||
{40, 3, 5, 4, 0, -4}, // 0x36 '6'
|
||||
{42, 3, 5, 4, 0, -4}, // 0x37 '7'
|
||||
{44, 3, 5, 4, 0, -4}, // 0x38 '8'
|
||||
{46, 3, 5, 4, 0, -4}, // 0x39 '9'
|
||||
{48, 1, 3, 2, 0, -3}, // 0x3A ':'
|
||||
{49, 2, 4, 3, 0, -3}, // 0x3B ';'
|
||||
{50, 2, 3, 3, 0, -3}, // 0x3C '<'
|
||||
{51, 3, 3, 4, 0, -3}, // 0x3D '='
|
||||
{53, 2, 3, 3, 0, -3}, // 0x3E '>'
|
||||
{54, 3, 5, 4, 0, -4}, // 0x3F '?'
|
||||
{56, 3, 5, 4, 0, -4}, // 0x40 '@'
|
||||
{58, 3, 5, 4, 0, -4}, // 0x41 'A'
|
||||
{60, 3, 5, 4, 0, -4}, // 0x42 'B'
|
||||
{62, 3, 5, 4, 0, -4}, // 0x43 'C'
|
||||
{64, 3, 5, 4, 0, -4}, // 0x44 'D'
|
||||
{66, 3, 5, 4, 0, -4}, // 0x45 'E'
|
||||
{68, 3, 5, 4, 0, -4}, // 0x46 'F'
|
||||
{70, 3, 5, 4, 0, -4}, // 0x47 'G'
|
||||
{72, 3, 5, 4, 0, -4}, // 0x48 'H'
|
||||
{74, 1, 5, 2, 0, -4}, // 0x49 'I'
|
||||
{75, 3, 5, 4, 0, -4}, // 0x4A 'J'
|
||||
{77, 3, 5, 4, 0, -4}, // 0x4B 'K'
|
||||
{79, 3, 5, 4, 0, -4}, // 0x4C 'L'
|
||||
{81, 5, 5, 6, 0, -4}, // 0x4D 'M'
|
||||
{85, 4, 5, 5, 0, -4}, // 0x4E 'N'
|
||||
{88, 3, 5, 4, 0, -4}, // 0x4F 'O'
|
||||
{90, 3, 5, 4, 0, -4}, // 0x50 'P'
|
||||
{92, 3, 6, 4, 0, -4}, // 0x51 'Q'
|
||||
{95, 3, 5, 4, 0, -4}, // 0x52 'R'
|
||||
{97, 3, 5, 4, 0, -4}, // 0x53 'S'
|
||||
{99, 3, 5, 4, 0, -4}, // 0x54 'T'
|
||||
{101, 3, 5, 4, 0, -4}, // 0x55 'U'
|
||||
{103, 3, 5, 4, 0, -4}, // 0x56 'V'
|
||||
{105, 5, 5, 6, 0, -4}, // 0x57 'W'
|
||||
{109, 3, 5, 4, 0, -4}, // 0x58 'X'
|
||||
{111, 3, 5, 4, 0, -4}, // 0x59 'Y'
|
||||
{113, 3, 5, 4, 0, -4}, // 0x5A 'Z'
|
||||
{115, 2, 5, 3, 0, -4}, // 0x5B '['
|
||||
{117, 3, 5, 4, 0, -4}, // 0x5C '\'
|
||||
{119, 2, 5, 3, 0, -4}, // 0x5D ']'
|
||||
{121, 3, 2, 4, 0, -4}, // 0x5E '^'
|
||||
{122, 4, 1, 4, 0, 1}, // 0x5F '_'
|
||||
{123, 2, 2, 3, 0, -4}, // 0x60 '`'
|
||||
{124, 3, 4, 4, 0, -3}, // 0x61 'a'
|
||||
{126, 3, 5, 4, 0, -4}, // 0x62 'b'
|
||||
{128, 3, 3, 4, 0, -2}, // 0x63 'c'
|
||||
{130, 3, 5, 4, 0, -4}, // 0x64 'd'
|
||||
{132, 3, 4, 4, 0, -3}, // 0x65 'e'
|
||||
{134, 2, 5, 3, 0, -4}, // 0x66 'f'
|
||||
{136, 3, 5, 4, 0, -3}, // 0x67 'g'
|
||||
{138, 3, 5, 4, 0, -4}, // 0x68 'h'
|
||||
{140, 1, 5, 2, 0, -4}, // 0x69 'i'
|
||||
{141, 2, 6, 3, 0, -4}, // 0x6A 'j'
|
||||
{143, 3, 5, 4, 0, -4}, // 0x6B 'k'
|
||||
{145, 2, 5, 3, 0, -4}, // 0x6C 'l'
|
||||
{147, 5, 3, 6, 0, -2}, // 0x6D 'm'
|
||||
{149, 3, 3, 4, 0, -2}, // 0x6E 'n'
|
||||
{151, 3, 3, 4, 0, -2}, // 0x6F 'o'
|
||||
{153, 3, 4, 4, 0, -2}, // 0x70 'p'
|
||||
{155, 3, 4, 4, 0, -2}, // 0x71 'q'
|
||||
{157, 2, 3, 3, 0, -2}, // 0x72 'r'
|
||||
{158, 3, 4, 4, 0, -3}, // 0x73 's'
|
||||
{160, 2, 5, 3, 0, -4}, // 0x74 't'
|
||||
{162, 3, 3, 4, 0, -2}, // 0x75 'u'
|
||||
{164, 3, 3, 4, 0, -2}, // 0x76 'v'
|
||||
{166, 5, 3, 6, 0, -2}, // 0x77 'w'
|
||||
{168, 3, 3, 4, 0, -2}, // 0x78 'x'
|
||||
{170, 3, 4, 4, 0, -2}, // 0x79 'y'
|
||||
{172, 3, 4, 4, 0, -3}, // 0x7A 'z'
|
||||
{174, 3, 5, 4, 0, -4}, // 0x7B '{'
|
||||
{176, 1, 6, 2, 0, -4}, // 0x7C '|'
|
||||
{177, 3, 5, 4, 0, -4}, // 0x7D '}'
|
||||
{179, 4, 2, 5, 0, -3}}; // 0x7E '~'
|
||||
|
||||
const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps,
|
||||
(GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7};
|
||||
|
||||
// Approx. 852 bytes
|
||||
124
Framing.h
Executable file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef FRAMING_H
|
||||
#define FRAMING_H
|
||||
|
||||
#define FEND 0xC0
|
||||
#define FESC 0xDB
|
||||
#define TFEND 0xDC
|
||||
#define TFESC 0xDD
|
||||
|
||||
#define CMD_UNKNOWN 0xFE
|
||||
#define CMD_DATA 0x00
|
||||
#define CMD_FREQUENCY 0x01
|
||||
#define CMD_BANDWIDTH 0x02
|
||||
#define CMD_TXPOWER 0x03
|
||||
#define CMD_SF 0x04
|
||||
#define CMD_CR 0x05
|
||||
#define CMD_RADIO_STATE 0x06
|
||||
#define CMD_RADIO_LOCK 0x07
|
||||
#define CMD_DETECT 0x08
|
||||
#define CMD_IMPLICIT 0x09
|
||||
#define CMD_LEAVE 0x0A
|
||||
#define CMD_ST_ALOCK 0x0B
|
||||
#define CMD_LT_ALOCK 0x0C
|
||||
#define CMD_PROMISC 0x0E
|
||||
#define CMD_READY 0x0F
|
||||
|
||||
#define CMD_STAT_RX 0x21
|
||||
#define CMD_STAT_TX 0x22
|
||||
#define CMD_STAT_RSSI 0x23
|
||||
#define CMD_STAT_SNR 0x24
|
||||
#define CMD_STAT_CHTM 0x25
|
||||
#define CMD_STAT_PHYPRM 0x26
|
||||
#define CMD_STAT_BAT 0x27
|
||||
#define CMD_STAT_CSMA 0x28
|
||||
#define CMD_STAT_TEMP 0x29
|
||||
#define CMD_BLINK 0x30
|
||||
#define CMD_RANDOM 0x40
|
||||
|
||||
#define CMD_FB_EXT 0x41
|
||||
#define CMD_FB_READ 0x42
|
||||
#define CMD_FB_WRITE 0x43
|
||||
#define CMD_FB_READL 0x44
|
||||
#define CMD_DISP_READ 0x66
|
||||
#define CMD_DISP_INT 0x45
|
||||
#define CMD_DISP_ADDR 0x63
|
||||
#define CMD_DISP_BLNK 0x64
|
||||
#define CMD_DISP_ROT 0x67
|
||||
#define CMD_DISP_RCND 0x68
|
||||
#define CMD_NP_INT 0x65
|
||||
#define CMD_BT_CTRL 0x46
|
||||
#define CMD_BT_UNPAIR 0x70
|
||||
#define CMD_BT_PIN 0x62
|
||||
#define CMD_DIS_IA 0x69
|
||||
#define CMD_WIFI_MODE 0x6A
|
||||
#define CMD_WIFI_SSID 0x6B
|
||||
#define CMD_WIFI_PSK 0x6C
|
||||
#define CMD_WIFI_CHN 0x6E
|
||||
#define CMD_WIFI_IP 0x84
|
||||
#define CMD_WIFI_NM 0x85
|
||||
|
||||
#define CMD_BOARD 0x47
|
||||
#define CMD_PLATFORM 0x48
|
||||
#define CMD_MCU 0x49
|
||||
#define CMD_FW_VERSION 0x50
|
||||
#define CMD_CFG_READ 0x6D
|
||||
#define CMD_ROM_READ 0x51
|
||||
#define CMD_ROM_WRITE 0x52
|
||||
#define CMD_CONF_SAVE 0x53
|
||||
#define CMD_CONF_DELETE 0x54
|
||||
#define CMD_DEV_HASH 0x56
|
||||
#define CMD_DEV_SIG 0x57
|
||||
#define CMD_FW_HASH 0x58
|
||||
#define CMD_HASHES 0x60
|
||||
#define CMD_FW_UPD 0x61
|
||||
#define CMD_UNLOCK_ROM 0x59
|
||||
#define ROM_UNLOCK_BYTE 0xF8
|
||||
#define CMD_RESET 0x55
|
||||
#define CMD_RESET_BYTE 0xF8
|
||||
|
||||
#define CMD_LOG 0x80
|
||||
#define CMD_TIME 0x81
|
||||
#define CMD_MUX_CHAIN 0x82
|
||||
#define CMD_MUX_DSCVR 0x83
|
||||
|
||||
#define DETECT_REQ 0x73
|
||||
#define DETECT_RESP 0x46
|
||||
|
||||
#define RADIO_STATE_OFF 0x00
|
||||
#define RADIO_STATE_ON 0x01
|
||||
|
||||
#define NIBBLE_SEQ 0xF0
|
||||
#define NIBBLE_FLAGS 0x0F
|
||||
#define FLAG_SPLIT 0x01
|
||||
#define SEQ_UNSET 0xFF
|
||||
|
||||
#define CMD_ERROR 0x90
|
||||
#define ERROR_INITRADIO 0x01
|
||||
#define ERROR_TXFAILED 0x02
|
||||
#define ERROR_EEPROM_LOCKED 0x03
|
||||
#define ERROR_QUEUE_FULL 0x04
|
||||
#define ERROR_MEMORY_LOW 0x05
|
||||
#define ERROR_MODEM_TIMEOUT 0x06
|
||||
|
||||
// Serial framing variables
|
||||
size_t frame_len;
|
||||
bool IN_FRAME = false;
|
||||
bool ESCAPE = false;
|
||||
uint8_t command = CMD_UNKNOWN;
|
||||
|
||||
#endif
|
||||
445
Graphics.h
Executable file
@@ -0,0 +1,445 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const unsigned char bm_cable [] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x1c, 0x00, 0x38, 0x07, 0xfc, 0x08, 0x38, 0x10, 0x1c, 0x10, 0x00, 0x08, 0x00,
|
||||
0x07, 0xc0, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x07, 0xc0, 0x08, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x80, 0x04, 0x43, 0x08, 0x46,
|
||||
0xf1, 0x8f, 0x02, 0x16, 0x02, 0x23, 0x01, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_rf [] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xc4,
|
||||
0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x4e, 0xc4,
|
||||
0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x4e,
|
||||
0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x71, 0x4e,
|
||||
0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_wifi [] PROGMEM = {
|
||||
0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x04, 0x60,
|
||||
0x0a, 0x50, 0x0e, 0x60, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x04, 0x60,
|
||||
0x0a, 0x50, 0x0e, 0x60, 0x0a, 0x40, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x74,
|
||||
0x54, 0x40, 0x55, 0x64, 0x29, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x45, 0x74,
|
||||
0x54, 0x40, 0x55, 0x64, 0x29, 0x44, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_bt [] PROGMEM = {
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x01, 0x40,
|
||||
0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40,
|
||||
0x01, 0x80, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40,
|
||||
0x29, 0x94, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x11, 0x48,
|
||||
0x29, 0x94, 0x11, 0x48, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_boot [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff, 0xff,
|
||||
0xff, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff, 0xff,
|
||||
0xff, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x0c, 0x38, 0xe1, 0xc3, 0x33, 0x38, 0x7f, 0xfe, 0x7e, 0x72, 0x64, 0xe7, 0x31, 0x33, 0xff,
|
||||
0xff, 0x1e, 0x70, 0x61, 0xe7, 0x30, 0x32, 0x7f, 0xff, 0xce, 0x72, 0x61, 0xe7, 0x32, 0x32, 0x7f,
|
||||
0xfe, 0x1e, 0x72, 0x64, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_fw_update [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfc, 0x98, 0x70, 0xf1, 0xc3, 0x33, 0x38, 0x7f, 0xfc, 0x99, 0x32, 0x64, 0xe7, 0x31, 0x33, 0xff,
|
||||
0xfc, 0x98, 0x72, 0x60, 0xe7, 0x30, 0x32, 0x7f, 0xfc, 0x99, 0xf2, 0x64, 0xe7, 0x32, 0x32, 0x7f,
|
||||
0xfe, 0x39, 0xf0, 0xe4, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xf8, 0x66, 0x1c, 0xe6, 0x73, 0x8e, 0x1c, 0x3f, 0xf9, 0xe6, 0x4c, 0x46, 0x53, 0x26, 0x4c, 0xff,
|
||||
0xf8, 0x66, 0x1c, 0x06, 0x53, 0x06, 0x1c, 0x3f, 0xf9, 0xe6, 0x1c, 0xa6, 0x03, 0x26, 0x1c, 0xff,
|
||||
0xf9, 0xe6, 0x4c, 0xe7, 0x27, 0x26, 0x4c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_console_active [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x8e, 0x67, 0x0e, 0x39, 0xe1, 0xff,
|
||||
0xff, 0x93, 0x26, 0x26, 0x7c, 0x99, 0xe7, 0xff, 0xff, 0x9f, 0x26, 0x07, 0x1c, 0x99, 0xe1, 0xff,
|
||||
0xff, 0x93, 0x26, 0x47, 0xcc, 0x99, 0xe7, 0xff, 0xff, 0xc7, 0x8e, 0x66, 0x1e, 0x38, 0x61, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3c, 0x70, 0xcc, 0xcc, 0x3f, 0xff,
|
||||
0xff, 0xfc, 0x99, 0x39, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xfc, 0x19, 0xf9, 0xce, 0x1c, 0x3f, 0xff,
|
||||
0xff, 0xfc, 0x99, 0x39, 0xce, 0x1c, 0xff, 0xff, 0xff, 0xfc, 0x9c, 0x79, 0xcf, 0x3c, 0x3f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_updating [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x7f, 0xf5, 0xff, 0x75, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x57, 0xd5, 0x7f, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x57, 0x55, 0x75, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff,
|
||||
0xff, 0xff, 0x60, 0x00, 0x00, 0x03, 0x7f, 0xff, 0xff, 0xff, 0x30, 0x00, 0x00, 0x07, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x1c, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x56, 0x00, 0x00, 0x35, 0x7f, 0xff, 0xff, 0xff, 0x57, 0x00, 0x00, 0x74, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0xc0, 0x01, 0xd1, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x60, 0x03, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x70, 0x07, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x5c, 0x1d, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x56, 0x35, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x57, 0xe5, 0x44, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff,
|
||||
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_version [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x99, 0x86, 0x1e, 0x19, 0xc7, 0x33, 0xff, 0xff, 0x99, 0x9e, 0x4c, 0xf9, 0x93, 0x13, 0xff,
|
||||
0xff, 0xc3, 0x86, 0x1e, 0x39, 0x93, 0x03, 0xff, 0xff, 0xc3, 0x9e, 0x1f, 0x99, 0x93, 0x23, 0xff,
|
||||
0xff, 0xe7, 0x86, 0x4c, 0x39, 0xc7, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_fw_corrupt [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x30, 0xe7, 0x33, 0x9c, 0x70, 0xe1, 0xff,
|
||||
0xcf, 0x32, 0x62, 0x32, 0x99, 0x32, 0x67, 0xff, 0xc3, 0x30, 0xe0, 0x32, 0x98, 0x30, 0xe1, 0xff,
|
||||
0xcf, 0x30, 0xe5, 0x30, 0x19, 0x30, 0xe7, 0xff, 0xcf, 0x32, 0x67, 0x39, 0x39, 0x32, 0x61, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xc7, 0x0e, 0x1c, 0x98, 0x70, 0xfc, 0xff,
|
||||
0xc9, 0x93, 0x26, 0x4c, 0x99, 0x39, 0xfb, 0x7f, 0xcf, 0x93, 0x0e, 0x1c, 0x98, 0x79, 0xfb, 0x7f,
|
||||
0xc9, 0x93, 0x0e, 0x1c, 0x99, 0xf9, 0xf7, 0xbf, 0xe3, 0xc7, 0x26, 0x4e, 0x39, 0xf9, 0xf4, 0xbf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static unsigned char bm_def[] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff,
|
||||
0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff,
|
||||
0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00,
|
||||
0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00,
|
||||
0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00,
|
||||
0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00,
|
||||
0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54,
|
||||
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x62, 0x24, 0x49, 0x22, 0x4e, 0x44,
|
||||
0x00, 0x24, 0x93, 0x66, 0xc9, 0x32, 0x44, 0x28, 0x00, 0x20, 0x92, 0xa5, 0x49, 0x2a, 0x44, 0x10,
|
||||
0x00, 0x24, 0x92, 0x24, 0x49, 0x26, 0x44, 0x10, 0x00, 0x18, 0x62, 0x24, 0x46, 0x22, 0x44, 0x10,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1c, 0x9c, 0x44, 0x88, 0xc7, 0x1c, 0x00, 0x00, 0x10, 0x92, 0x6c, 0xa9, 0x24, 0x90,
|
||||
0x00, 0x00, 0x1c, 0x9c, 0x54, 0xa9, 0xe7, 0x1c, 0x00, 0x00, 0x10, 0x94, 0x44, 0xa9, 0x25, 0x10,
|
||||
0x00, 0x00, 0x10, 0x92, 0x44, 0x51, 0x24, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_def_lc [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff,
|
||||
0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff,
|
||||
0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00,
|
||||
0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00,
|
||||
0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00,
|
||||
0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00,
|
||||
0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54,
|
||||
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x39, 0x10, 0x61, 0x88, 0x91, 0x1c,
|
||||
0x02, 0x49, 0x21, 0x90, 0x92, 0x4d, 0x9b, 0x20, 0x02, 0x4e, 0x39, 0x50, 0x82, 0x4a, 0x95, 0x18,
|
||||
0x02, 0x48, 0x21, 0x30, 0x92, 0x48, 0x91, 0x04, 0x01, 0x88, 0x39, 0x10, 0x61, 0x88, 0x91, 0x38,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xc8, 0x8e, 0x73, 0x91, 0x1c, 0x00, 0x00, 0x02, 0x05, 0x10, 0x22, 0x1b, 0x20,
|
||||
0x00, 0x00, 0x01, 0x82, 0x0c, 0x23, 0x95, 0x18, 0x00, 0x00, 0x00, 0x42, 0x02, 0x22, 0x11, 0x04,
|
||||
0x00, 0x00, 0x03, 0x82, 0x1c, 0x23, 0x91, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_frame [] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc,
|
||||
0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x03, 0xc0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04,
|
||||
0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04,
|
||||
0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc,
|
||||
0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x1c,
|
||||
0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x18,
|
||||
0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x38,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xaa, 0x8a, 0xaa, 0x80
|
||||
};
|
||||
|
||||
const unsigned char bm_console [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff,
|
||||
0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff,
|
||||
0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xfc, 0x7f, 0x99, 0xe6, 0x61, 0x99, 0xfe, 0x3f,
|
||||
0xf8, 0xe7, 0x99, 0x26, 0x67, 0x99, 0xe7, 0x1f, 0xf9, 0xc7, 0x99, 0x26, 0x61, 0x99, 0xe3, 0x9f,
|
||||
0xf1, 0x8f, 0x98, 0x06, 0x67, 0x99, 0xf1, 0x8f, 0xf3, 0x9f, 0x9c, 0xce, 0x67, 0x99, 0xf9, 0xcf,
|
||||
0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf, 0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf,
|
||||
0xf3, 0x9f, 0x9f, 0xe3, 0x83, 0xf9, 0xf9, 0xcf, 0xf1, 0x8f, 0x9f, 0xc9, 0x93, 0xf9, 0xf1, 0x8f,
|
||||
0xf9, 0xc7, 0x9f, 0xc1, 0x83, 0xf9, 0xe3, 0x9f, 0xf8, 0xe7, 0x9f, 0xc9, 0x9f, 0xf9, 0xe7, 0x1f,
|
||||
0xfc, 0x7f, 0x9f, 0xc9, 0x9f, 0xf9, 0xfe, 0x3f, 0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f,
|
||||
0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff,
|
||||
0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff,
|
||||
0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f,
|
||||
0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x78, 0x1e, 0x07, 0x81, 0xe0, 0x78, 0x1f,
|
||||
0xef, 0xbb, 0xee, 0xfb, 0xbe, 0xef, 0xbb, 0xef, 0xe8, 0xda, 0xb6, 0x9d, 0xb3, 0x6d, 0xda, 0x37,
|
||||
0xef, 0xda, 0xf6, 0xb5, 0xad, 0x6c, 0xdb, 0xf7, 0xe8, 0x5a, 0x36, 0x95, 0xad, 0x6c, 0xda, 0x97,
|
||||
0xef, 0xdb, 0xf6, 0x85, 0xb3, 0x6c, 0xda, 0x97, 0xea, 0x5a, 0x36, 0xb5, 0xb3, 0x6c, 0xdb, 0xf7,
|
||||
0xef, 0xda, 0xf6, 0xa5, 0xad, 0x6f, 0xda, 0x57, 0xe8, 0x5a, 0xb6, 0x85, 0xad, 0x6c, 0xda, 0x57,
|
||||
0xef, 0xdb, 0xf6, 0xfd, 0xbf, 0x6f, 0xdb, 0xf7, 0xe0, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x07,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0x42, 0x7c, 0x60, 0xf0, 0x78, 0x3c, 0x7f, 0xfe, 0x4a, 0x7c, 0x64, 0xf2, 0x79, 0x3c, 0x7f,
|
||||
0xfe, 0x43, 0xfe, 0x64, 0xf2, 0x79, 0x3e, 0x7f, 0xfe, 0x4e, 0x7e, 0x64, 0x92, 0x49, 0x26, 0x7f,
|
||||
0xfe, 0x4e, 0x7e, 0x60, 0x90, 0x48, 0x26, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
|
||||
const unsigned char bm_checks [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0x0c, 0x99, 0xc7, 0x0f,
|
||||
0xe6, 0x00, 0x7f, 0x93, 0x3c, 0x99, 0x93, 0x3f, 0xe6, 0x00, 0x7f, 0x93, 0x0e, 0x39, 0x9f, 0x0f,
|
||||
0xff, 0xff, 0xff, 0x93, 0x3e, 0x39, 0x93, 0x3f, 0xff, 0xff, 0xff, 0x87, 0x0f, 0x79, 0xc7, 0x0f,
|
||||
0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x87,
|
||||
0xe6, 0x00, 0x7c, 0x99, 0x33, 0xc9, 0x87, 0x3f, 0xe6, 0x00, 0x7c, 0xf8, 0x30, 0xcf, 0x8f, 0x8f,
|
||||
0xff, 0xff, 0xfc, 0x99, 0x33, 0xc9, 0x87, 0xe7, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x0f,
|
||||
0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x9c, 0x3c, 0x78, 0x70, 0xc3, 0x0f,
|
||||
0xe6, 0x0d, 0x3c, 0x99, 0x33, 0xe7, 0xcf, 0x27, 0xe6, 0x04, 0x7c, 0x38, 0x38, 0xf1, 0xc3, 0x27,
|
||||
0xff, 0xfe, 0xfc, 0xf9, 0x3e, 0x7c, 0xcf, 0x27, 0xff, 0xff, 0xfc, 0xf9, 0x30, 0xe1, 0xc3, 0x0f,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_hwfail [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0xe3, 0x87, 0x0e, 0x73, 0x8e, 0x1c, 0x3f,
|
||||
0xe4, 0xc9, 0x93, 0x26, 0x53, 0x26, 0x4c, 0xff, 0xe0, 0xc1, 0x87, 0x26, 0x53, 0x06, 0x1c, 0x3f,
|
||||
0xe4, 0xc9, 0x87, 0x26, 0x03, 0x26, 0x1c, 0xff, 0xe4, 0xc9, 0x93, 0x0f, 0x27, 0x26, 0x4c, 0x3f,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xe1, 0xc7, 0x33, 0xc9, 0x87, 0x0f, 0xf9, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x93, 0x3f, 0xf6, 0xff,
|
||||
0xe1, 0x83, 0x33, 0xc9, 0x87, 0x0f, 0xf6, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x87, 0x3f, 0xef, 0x7f,
|
||||
0xe7, 0x93, 0x30, 0xe3, 0x93, 0x0f, 0xe9, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_conf_missing [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x33, 0x87, 0x0c, 0xcc, 0xe1, 0xff, 0xff,
|
||||
0xe2, 0x33, 0x3e, 0x7c, 0xc4, 0xcf, 0xff, 0xff, 0xe0, 0x33, 0x8f, 0x1c, 0xc0, 0xc9, 0xff, 0xff,
|
||||
0xe5, 0x33, 0xe7, 0xcc, 0xc8, 0xc9, 0xff, 0xff, 0xe7, 0x33, 0x0e, 0x1c, 0xcc, 0xe3, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf1, 0xe3, 0x99, 0x86, 0x70, 0xff, 0xf6, 0xff,
|
||||
0xe4, 0xc9, 0x89, 0x9e, 0x67, 0xff, 0xf6, 0xff, 0xe7, 0xc9, 0x81, 0x86, 0x64, 0xff, 0xef, 0x7f,
|
||||
0xe4, 0xc9, 0x91, 0x9e, 0x64, 0xff, 0xe9, 0x7f, 0xf1, 0xe3, 0x99, 0x9e, 0x71, 0xff, 0xd9, 0xbf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_no_radio [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc7, 0x0e, 0x71, 0xfc, 0xce, 0x38, 0x7f,
|
||||
0xc9, 0x93, 0x26, 0x64, 0xfc, 0x4c, 0x9c, 0xff, 0xc3, 0x83, 0x26, 0x64, 0xfc, 0x0c, 0x9c, 0xff,
|
||||
0xc3, 0x93, 0x26, 0x64, 0xfc, 0x8c, 0x9c, 0xff, 0xc9, 0x93, 0x0e, 0x71, 0xfc, 0xce, 0x3c, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x8e, 0x4c, 0xcc, 0x3f, 0xff, 0xfc, 0xff,
|
||||
0xcf, 0x26, 0x4c, 0x4c, 0x9f, 0xff, 0xfb, 0x7f, 0xc3, 0x26, 0x4c, 0x0c, 0x9f, 0xff, 0xfb, 0x7f,
|
||||
0xcf, 0x26, 0x4c, 0x8c, 0x9f, 0xff, 0xf7, 0xbf, 0xcf, 0x8f, 0x1c, 0xcc, 0x3f, 0xff, 0xf4, 0xbf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_hwok [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xf2, 0x71, 0xc3, 0x87, 0x39, 0xc7, 0x0e, 0x1f, 0xf2, 0x64, 0xc9, 0x93, 0x29, 0x93, 0x26, 0x7f,
|
||||
0xf0, 0x60, 0xc3, 0x93, 0x29, 0x83, 0x0e, 0x1f, 0xf2, 0x64, 0xc3, 0x93, 0x01, 0x93, 0x0e, 0x7f,
|
||||
0xf2, 0x64, 0xc9, 0x87, 0x93, 0x93, 0x26, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x30, 0xff, 0x8e, 0x4f, 0xff,
|
||||
0xff, 0xf3, 0x13, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x03, 0x39, 0xff, 0x26, 0x3f, 0xff,
|
||||
0xff, 0xf3, 0x23, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x33, 0x39, 0xff, 0x8e, 0x4f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xc3, 0x0e, 0x67, 0xf8, 0x70, 0xe3, 0x87, 0x33, 0xe7, 0x27, 0x0f, 0xf9, 0x33, 0xc9, 0x93, 0x87,
|
||||
0xe7, 0x0f, 0x9f, 0xf8, 0x70, 0xc1, 0x93, 0xcf, 0xe7, 0x0f, 0x0f, 0xf8, 0x73, 0xc9, 0x93, 0xcf,
|
||||
0xe7, 0x26, 0x67, 0xf9, 0x30, 0xc9, 0x87, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_nfr [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff,
|
||||
0xff, 0x9f, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0x6f, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff,
|
||||
0xff, 0x6f, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xfe, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x9b, 0xe6, 0x71, 0xc3, 0xe1, 0xc7, 0x0f,
|
||||
0xfd, 0x9b, 0xe2, 0x64, 0xe7, 0xe7, 0x93, 0x27, 0xfb, 0x9d, 0xe0, 0x64, 0xe7, 0xe1, 0x93, 0x0f,
|
||||
0xfb, 0x9d, 0xe4, 0x64, 0xe7, 0xe7, 0x93, 0x0f, 0xf7, 0xfe, 0xe6, 0x71, 0xe7, 0xe7, 0xc7, 0x27,
|
||||
0xf7, 0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x9f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xef, 0xff, 0x7f, 0xf8, 0x71, 0xcf, 0x0f, 0xff, 0xf0, 0x00, 0xff, 0xf3, 0xe4, 0xcf, 0x3f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0xe0, 0xcf, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x64, 0xcf, 0x3f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf0, 0xe4, 0xc3, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_online [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x1c, 0x66, 0x61, 0x8c, 0x24, 0x90, 0x87,
|
||||
0xe6, 0x49, 0x22, 0x4f, 0x24, 0xe4, 0x93, 0x93, 0xe6, 0x18, 0x20, 0x63, 0x3c, 0x26, 0x30, 0x87,
|
||||
0xe6, 0x19, 0x24, 0x79, 0x24, 0xe6, 0x33, 0x87, 0xe6, 0x49, 0x26, 0x43, 0x8c, 0x27, 0x70, 0x93,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe6, 0x73, 0xe7, 0x33, 0x87, 0xff,
|
||||
0xff, 0xe4, 0xe2, 0x73, 0xe7, 0x13, 0x9f, 0xff, 0xff, 0xe4, 0xe0, 0x73, 0xe7, 0x03, 0x87, 0xff,
|
||||
0xff, 0xe4, 0xe4, 0x73, 0xe7, 0x23, 0x9f, 0xff, 0xff, 0xf1, 0xe6, 0x70, 0xe7, 0x33, 0x87, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_pairing [] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xf0, 0xe3, 0x98, 0x73, 0x33, 0x87, 0xff, 0xff, 0xf2, 0xc9, 0x99, 0x33, 0x13, 0x3f, 0xff,
|
||||
0xff, 0xf0, 0xc1, 0x98, 0x73, 0x03, 0x27, 0xff, 0xff, 0xf3, 0xc9, 0x98, 0x73, 0x23, 0x27, 0xff,
|
||||
0xff, 0xf3, 0xc9, 0x99, 0x33, 0x33, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
const unsigned char bm_n_uh [] PROGMEM = {
|
||||
0x07, 0x27, 0x27, 0x27, 0x07, 0x8f, 0x8f, 0xcf, 0xcf, 0xcf, 0x07, 0xe7, 0x07, 0x3f, 0x07, 0x07,
|
||||
0xe7, 0xc7, 0xe7, 0x07, 0x27, 0x27, 0x07, 0xe7, 0xe7, 0x07, 0x3f, 0x07, 0xe7, 0x07, 0x07, 0x3f,
|
||||
0x07, 0x27, 0x07, 0x07, 0xc7, 0xcf, 0x9f, 0x1f, 0x07, 0x27, 0x07, 0x27, 0x07, 0x07, 0x27, 0x07,
|
||||
0xe7, 0xe7
|
||||
};
|
||||
|
||||
const unsigned char bm_plug [] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x7f, 0x80, 0x55, 0xfc, 0x00, 0xaa, 0xfc, 0x00, 0x00,
|
||||
0x7f, 0x80, 0x00, 0x1c, 0x00
|
||||
};
|
||||
|
||||
const unsigned char bm_hg_low [] PROGMEM = {
|
||||
0xf8, 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0xf8, 0xf8
|
||||
};
|
||||
|
||||
const unsigned char bm_hg_high [] PROGMEM = {
|
||||
0xf8, 0x88, 0xf8, 0x70, 0x20, 0x70, 0xf8, 0xf8, 0xf8
|
||||
};
|
||||
95
Input.h
Executable file
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
#define PIN_BUTTON pin_btn_usr1
|
||||
|
||||
#define PRESSED LOW
|
||||
#define RELEASED HIGH
|
||||
|
||||
#define EVENT_ALL 0x00
|
||||
#define EVENT_CLICKS 0x01
|
||||
#define EVENT_BUTTON_DOWN 0x11
|
||||
#define EVENT_BUTTON_UP 0x12
|
||||
#define EVENT_BUTTON_CLICK 0x13
|
||||
#define EVENT_BUTTON_DOUBLE_CLICK 0x14
|
||||
#define EVENT_BUTTON_TRIPLE_CLICK 0x15
|
||||
|
||||
int button_events = EVENT_CLICKS;
|
||||
int button_state = RELEASED;
|
||||
int debounce_state = button_state;
|
||||
unsigned long button_debounce_last = 0;
|
||||
unsigned long button_debounce_delay = 25;
|
||||
unsigned long button_down_last = 0;
|
||||
unsigned long button_up_last = 0;
|
||||
|
||||
// Forward declaration
|
||||
void button_event(uint8_t event, unsigned long duration);
|
||||
|
||||
void input_init() {
|
||||
pinMode(PIN_BUTTON, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
void input_get_all_events() {
|
||||
button_events = EVENT_ALL;
|
||||
}
|
||||
|
||||
void input_get_click_events() {
|
||||
button_events = EVENT_CLICKS;
|
||||
}
|
||||
|
||||
void input_read() {
|
||||
int button_reading = digitalRead(PIN_BUTTON);
|
||||
if (button_reading != debounce_state) {
|
||||
button_debounce_last = millis();
|
||||
debounce_state = button_reading;
|
||||
}
|
||||
|
||||
if ((millis() - button_debounce_last) > button_debounce_delay) {
|
||||
if (button_reading != button_state) {
|
||||
// State changed
|
||||
int previous_state = button_state;
|
||||
button_state = button_reading;
|
||||
|
||||
if (button_events == EVENT_ALL) {
|
||||
if (button_state == PRESSED) {
|
||||
button_event(EVENT_BUTTON_DOWN, 0);
|
||||
} else if (button_state == RELEASED) {
|
||||
button_event(EVENT_BUTTON_UP, 0);
|
||||
}
|
||||
} else if (button_events == EVENT_CLICKS) {
|
||||
if (previous_state == PRESSED && button_state == RELEASED) {
|
||||
button_up_last = millis();
|
||||
button_event(EVENT_BUTTON_CLICK, button_up_last-button_down_last);
|
||||
} else if (previous_state == RELEASED && button_state == PRESSED) {
|
||||
button_down_last = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool button_pressed() {
|
||||
if (button_state == PRESSED) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
674
LICENSE
Executable file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
301
MD5.cpp
Executable file
@@ -0,0 +1,301 @@
|
||||
#include "MD5.h"
|
||||
|
||||
MD5::MD5()
|
||||
{
|
||||
//nothing
|
||||
return;
|
||||
}
|
||||
|
||||
char* MD5::make_digest(const unsigned char *digest, int len) /* {{{ */
|
||||
{
|
||||
char * md5str = (char*) malloc(sizeof(char)*(len*2+1));
|
||||
static const char hexits[17] = "0123456789abcdef";
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
md5str[i * 2] = hexits[digest[i] >> 4];
|
||||
md5str[(i * 2) + 1] = hexits[digest[i] & 0x0F];
|
||||
}
|
||||
md5str[len * 2] = '\0';
|
||||
return md5str;
|
||||
}
|
||||
|
||||
/*
|
||||
* The basic MD5 functions.
|
||||
*
|
||||
* E and G are optimized compared to their RFC 1321 definitions for
|
||||
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
|
||||
* implementation.
|
||||
* E() has been used instead of F() because F() is already defined in the Arduino core
|
||||
*/
|
||||
#define E(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
||||
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
|
||||
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
||||
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
|
||||
|
||||
/*
|
||||
* The MD5 transformation for all four rounds.
|
||||
*/
|
||||
#define STEP(f, a, b, c, d, x, t, s) \
|
||||
(a) += f((b), (c), (d)) + (x) + (t); \
|
||||
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
|
||||
(a) += (b);
|
||||
|
||||
/*
|
||||
* SET reads 4 input bytes in little-endian byte order and stores them
|
||||
* in a properly aligned word in host byte order.
|
||||
*
|
||||
* The check for little-endian architectures that tolerate unaligned
|
||||
* memory accesses is just an optimization. Nothing will break if it
|
||||
* doesn't work.
|
||||
*/
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
|
||||
# define SET(n) \
|
||||
(*(MD5_u32plus *)&ptr[(n) * 4])
|
||||
# define GET(n) \
|
||||
SET(n)
|
||||
#else
|
||||
# define SET(n) \
|
||||
(ctx->block[(n)] = \
|
||||
(MD5_u32plus)ptr[(n) * 4] | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
|
||||
# define GET(n) \
|
||||
(ctx->block[(n)])
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This processes one or more 64-byte data blocks, but does NOT update
|
||||
* the bit counters. There are no alignment requirements.
|
||||
*/
|
||||
const void *MD5::body(void *ctxBuf, const void *data, size_t size)
|
||||
{
|
||||
MD5_CTX *ctx = (MD5_CTX*)ctxBuf;
|
||||
const unsigned char *ptr;
|
||||
MD5_u32plus a, b, c, d;
|
||||
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
|
||||
|
||||
ptr = (unsigned char*)data;
|
||||
|
||||
a = ctx->a;
|
||||
b = ctx->b;
|
||||
c = ctx->c;
|
||||
d = ctx->d;
|
||||
|
||||
do {
|
||||
saved_a = a;
|
||||
saved_b = b;
|
||||
saved_c = c;
|
||||
saved_d = d;
|
||||
|
||||
/* Round 1
|
||||
* E() has been used instead of F() because F() is already defined in the Arduino core
|
||||
*/
|
||||
STEP(E, a, b, c, d, SET(0), 0xd76aa478, 7)
|
||||
STEP(E, d, a, b, c, SET(1), 0xe8c7b756, 12)
|
||||
STEP(E, c, d, a, b, SET(2), 0x242070db, 17)
|
||||
STEP(E, b, c, d, a, SET(3), 0xc1bdceee, 22)
|
||||
STEP(E, a, b, c, d, SET(4), 0xf57c0faf, 7)
|
||||
STEP(E, d, a, b, c, SET(5), 0x4787c62a, 12)
|
||||
STEP(E, c, d, a, b, SET(6), 0xa8304613, 17)
|
||||
STEP(E, b, c, d, a, SET(7), 0xfd469501, 22)
|
||||
STEP(E, a, b, c, d, SET(8), 0x698098d8, 7)
|
||||
STEP(E, d, a, b, c, SET(9), 0x8b44f7af, 12)
|
||||
STEP(E, c, d, a, b, SET(10), 0xffff5bb1, 17)
|
||||
STEP(E, b, c, d, a, SET(11), 0x895cd7be, 22)
|
||||
STEP(E, a, b, c, d, SET(12), 0x6b901122, 7)
|
||||
STEP(E, d, a, b, c, SET(13), 0xfd987193, 12)
|
||||
STEP(E, c, d, a, b, SET(14), 0xa679438e, 17)
|
||||
STEP(E, b, c, d, a, SET(15), 0x49b40821, 22)
|
||||
|
||||
/* Round 2 */
|
||||
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
|
||||
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
|
||||
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
|
||||
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
|
||||
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
|
||||
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
|
||||
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
|
||||
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
|
||||
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
|
||||
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
|
||||
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
|
||||
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
|
||||
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
|
||||
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
|
||||
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
|
||||
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
|
||||
|
||||
/* Round 3 */
|
||||
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
|
||||
STEP(H, d, a, b, c, GET(8), 0x8771f681, 11)
|
||||
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
|
||||
STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)
|
||||
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
|
||||
STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11)
|
||||
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
|
||||
STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)
|
||||
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
|
||||
STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11)
|
||||
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
|
||||
STEP(H, b, c, d, a, GET(6), 0x04881d05, 23)
|
||||
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
|
||||
STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)
|
||||
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
|
||||
STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23)
|
||||
|
||||
/* Round 4 */
|
||||
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
|
||||
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
|
||||
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
|
||||
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
|
||||
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
|
||||
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
|
||||
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
|
||||
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
|
||||
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
|
||||
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
|
||||
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
|
||||
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
|
||||
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
|
||||
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
|
||||
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
|
||||
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
|
||||
|
||||
a += saved_a;
|
||||
b += saved_b;
|
||||
c += saved_c;
|
||||
d += saved_d;
|
||||
|
||||
ptr += 64;
|
||||
} while (size -= 64);
|
||||
|
||||
ctx->a = a;
|
||||
ctx->b = b;
|
||||
ctx->c = c;
|
||||
ctx->d = d;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MD5::MD5Init(void *ctxBuf)
|
||||
{
|
||||
MD5_CTX *ctx = (MD5_CTX*)ctxBuf;
|
||||
ctx->a = 0x67452301;
|
||||
ctx->b = 0xefcdab89;
|
||||
ctx->c = 0x98badcfe;
|
||||
ctx->d = 0x10325476;
|
||||
|
||||
ctx->lo = 0;
|
||||
ctx->hi = 0;
|
||||
|
||||
memset(ctx->block, 0, sizeof(ctx->block));
|
||||
memset(ctx->buffer, 0, sizeof(ctx->buffer));
|
||||
}
|
||||
|
||||
void MD5::MD5Update(void *ctxBuf, const void *data, size_t size)
|
||||
{
|
||||
MD5_CTX *ctx = (MD5_CTX*)ctxBuf;
|
||||
MD5_u32plus saved_lo;
|
||||
MD5_u32plus used, free;
|
||||
|
||||
saved_lo = ctx->lo;
|
||||
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) {
|
||||
ctx->hi++;
|
||||
}
|
||||
ctx->hi += size >> 29;
|
||||
|
||||
used = saved_lo & 0x3f;
|
||||
|
||||
if (used) {
|
||||
free = 64 - used;
|
||||
|
||||
if (size < free) {
|
||||
memcpy(&ctx->buffer[used], data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&ctx->buffer[used], data, free);
|
||||
data = (unsigned char *)data + free;
|
||||
size -= free;
|
||||
body(ctx, ctx->buffer, 64);
|
||||
}
|
||||
|
||||
if (size >= 64) {
|
||||
data = body(ctx, data, size & ~(size_t)0x3f);
|
||||
size &= 0x3f;
|
||||
}
|
||||
|
||||
memcpy(ctx->buffer, data, size);
|
||||
}
|
||||
|
||||
void MD5::MD5Final(unsigned char *result, void *ctxBuf)
|
||||
{
|
||||
MD5_CTX *ctx = (MD5_CTX*)ctxBuf;
|
||||
MD5_u32plus used, free;
|
||||
|
||||
used = ctx->lo & 0x3f;
|
||||
|
||||
ctx->buffer[used++] = 0x80;
|
||||
|
||||
free = 64 - used;
|
||||
|
||||
if (free < 8) {
|
||||
memset(&ctx->buffer[used], 0, free);
|
||||
body(ctx, ctx->buffer, 64);
|
||||
used = 0;
|
||||
free = 64;
|
||||
}
|
||||
|
||||
memset(&ctx->buffer[used], 0, free - 8);
|
||||
|
||||
ctx->lo <<= 3;
|
||||
ctx->buffer[56] = ctx->lo;
|
||||
ctx->buffer[57] = ctx->lo >> 8;
|
||||
ctx->buffer[58] = ctx->lo >> 16;
|
||||
ctx->buffer[59] = ctx->lo >> 24;
|
||||
ctx->buffer[60] = ctx->hi;
|
||||
ctx->buffer[61] = ctx->hi >> 8;
|
||||
ctx->buffer[62] = ctx->hi >> 16;
|
||||
ctx->buffer[63] = ctx->hi >> 24;
|
||||
|
||||
body(ctx, ctx->buffer, 64);
|
||||
|
||||
result[0] = ctx->a;
|
||||
result[1] = ctx->a >> 8;
|
||||
result[2] = ctx->a >> 16;
|
||||
result[3] = ctx->a >> 24;
|
||||
result[4] = ctx->b;
|
||||
result[5] = ctx->b >> 8;
|
||||
result[6] = ctx->b >> 16;
|
||||
result[7] = ctx->b >> 24;
|
||||
result[8] = ctx->c;
|
||||
result[9] = ctx->c >> 8;
|
||||
result[10] = ctx->c >> 16;
|
||||
result[11] = ctx->c >> 24;
|
||||
result[12] = ctx->d;
|
||||
result[13] = ctx->d >> 8;
|
||||
result[14] = ctx->d >> 16;
|
||||
result[15] = ctx->d >> 24;
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
unsigned char* MD5::make_hash(char *arg)
|
||||
{
|
||||
MD5_CTX context;
|
||||
unsigned char * hash = (unsigned char *) malloc(16);
|
||||
MD5Init(&context);
|
||||
MD5Update(&context, arg, strlen(arg));
|
||||
MD5Final(hash, &context);
|
||||
return hash;
|
||||
}
|
||||
unsigned char* MD5::make_hash(char *arg,size_t size)
|
||||
{
|
||||
MD5_CTX context;
|
||||
unsigned char * hash = (unsigned char *) malloc(16);
|
||||
MD5Init(&context);
|
||||
MD5Update(&context, arg, size);
|
||||
MD5Final(hash, &context);
|
||||
return hash;
|
||||
}
|
||||
52
MD5.h
Executable file
@@ -0,0 +1,52 @@
|
||||
#ifndef MD5_h
|
||||
#define MD5_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
/*
|
||||
* This is an OpenSSL-compatible implementation of the RSA Data Security,
|
||||
* Inc. MD5 Message-Digest Algorithm (RFC 1321).
|
||||
*
|
||||
* Written by Solar Designer <solar at openwall.com> in 2001, and placed
|
||||
* in the public domain. There's absolutely no warranty.
|
||||
*
|
||||
* This differs from Colin Plumb's older public domain implementation in
|
||||
* that no 32-bit integer data type is required, there's no compile-time
|
||||
* endianness configuration, and the function prototypes match OpenSSL's.
|
||||
* The primary goals are portability and ease of use.
|
||||
*
|
||||
* This implementation is meant to be fast, but not as fast as possible.
|
||||
* Some known optimizations are not included to reduce source code size
|
||||
* and avoid compile-time configuration.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Updated by Scott MacVicar for arduino
|
||||
* <scott@macvicar.net>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef unsigned long MD5_u32plus;
|
||||
|
||||
typedef struct {
|
||||
MD5_u32plus lo, hi;
|
||||
MD5_u32plus a, b, c, d;
|
||||
unsigned char buffer[64];
|
||||
MD5_u32plus block[16];
|
||||
} MD5_CTX;
|
||||
|
||||
class MD5
|
||||
{
|
||||
public:
|
||||
MD5();
|
||||
static unsigned char* make_hash(char *arg);
|
||||
static unsigned char* make_hash(char *arg,size_t size);
|
||||
static char* make_digest(const unsigned char *digest, int len);
|
||||
static const void *body(void *ctxBuf, const void *data, size_t size);
|
||||
static void MD5Init(void *ctxBuf);
|
||||
static void MD5Final(unsigned char *result, void *ctxBuf);
|
||||
static void MD5Update(void *ctxBuf, const void *data, size_t size);
|
||||
};
|
||||
|
||||
#endif
|
||||
518
Makefile
Executable file
@@ -0,0 +1,518 @@
|
||||
# Copyright (C) 2024, Mark Qvist
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Version 2.0.17 of the Arduino ESP core is based on ESP-IDF v4.4.7
|
||||
ARDUINO_ESP_CORE_VER = 2.0.17
|
||||
|
||||
# Version 3.2.0 of the Arduino ESP core is based on ESP-IDF v5.4.1
|
||||
# ARDUINO_ESP_CORE_VER = 3.2.0
|
||||
|
||||
all: release
|
||||
|
||||
clean:
|
||||
-rm -r ./build
|
||||
-rm ./Release/rnode_firmware*
|
||||
|
||||
prep: prep-avr prep-esp32 prep-samd
|
||||
|
||||
prep-avr:
|
||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
||||
arduino-cli core install arduino:avr --config-file arduino-cli.yaml
|
||||
arduino-cli core install unsignedio:avr --config-file arduino-cli.yaml
|
||||
|
||||
prep-esp32:
|
||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
||||
arduino-cli core install esp32:esp32@$(ARDUINO_ESP_CORE_VER) --config-file arduino-cli.yaml
|
||||
arduino-cli lib install "Adafruit SSD1306"
|
||||
arduino-cli lib install "Adafruit SH110X"
|
||||
arduino-cli lib install "Adafruit ST7735 and ST7789 Library"
|
||||
arduino-cli lib install "Adafruit NeoPixel"
|
||||
arduino-cli lib install "XPowersLib"
|
||||
arduino-cli lib install "Crypto"
|
||||
|
||||
prep-samd:
|
||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
||||
arduino-cli core install adafruit:samd --config-file arduino-cli.yaml
|
||||
|
||||
prep-nrf:
|
||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
||||
arduino-cli core install rakwireless:nrf52 --config-file arduino-cli.yaml
|
||||
arduino-cli core install Heltec_nRF52:Heltec_nRF52 --config-file arduino-cli.yaml
|
||||
arduino-cli core install adafruit:nrf52 --config-file arduino-cli.yaml
|
||||
arduino-cli lib install "GxEPD2"
|
||||
arduino-cli config set library.enable_unsafe_install true
|
||||
arduino-cli lib install --git-url https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95
|
||||
pip install adafruit-nrfutil --upgrade
|
||||
|
||||
console-site:
|
||||
make -C Console clean site
|
||||
|
||||
spiffs: console-site spiffs-image
|
||||
|
||||
spiffs-image:
|
||||
python Release/esptool/spiffsgen.py 1966080 ./Console/build Release/console_image.bin
|
||||
|
||||
upload-spiffs:
|
||||
@echo Deploying SPIFFS image...
|
||||
python ./Release/esptool/esptool.py --chip esp32s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
check_bt_buffers:
|
||||
@./esp32_btbufs.py ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/libraries/BluetoothSerial/src/BluetoothSerial.cpp
|
||||
|
||||
firmware:
|
||||
arduino-cli compile --log --fqbn unsignedio:avr:rnode
|
||||
|
||||
firmware-mega2560:
|
||||
arduino-cli compile --log --fqbn arduino:avr:mega
|
||||
|
||||
firmware-tbeam: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\""
|
||||
|
||||
firmware-tbeam_sx126x: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\" \"-DMODEM=0x03\""
|
||||
|
||||
firmware-t3s3:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x03\""
|
||||
|
||||
firmware-t3s3_sx127x:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x01\""
|
||||
|
||||
firmware-t3s3_sx1280_pa:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x04\""
|
||||
|
||||
firmware-tdeck:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3B\""
|
||||
|
||||
firmware-tbeam_supreme:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=-DBOARD_MODEL=0x3D"
|
||||
|
||||
firmware-lora32_v10: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\""
|
||||
|
||||
firmware-lora32_v10_extled: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\" \"-DEXTERNAL_LEDS=true\""
|
||||
|
||||
firmware-lora32_v20: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\" \"-DEXTERNAL_LEDS=true\""
|
||||
|
||||
firmware-lora32_v21: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\""
|
||||
|
||||
firmware-lora32_v21_extled: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DEXTERNAL_LEDS=true\""
|
||||
|
||||
firmware-lora32_v21_tcxo: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DENABLE_TCXO=true\""
|
||||
|
||||
firmware-heltec32_v2: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\""
|
||||
|
||||
firmware-heltec32_v2_extled: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\" \"-DEXTERNAL_LEDS=true\""
|
||||
|
||||
firmware-heltec32_v3:
|
||||
arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V3 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3A\""
|
||||
|
||||
firmware-heltec32_v4:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3F\""
|
||||
|
||||
firmware-rnode_ng_20: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\""
|
||||
|
||||
firmware-rnode_ng_21: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x41\""
|
||||
|
||||
firmware-featheresp32: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:featheresp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x34\""
|
||||
|
||||
firmware-genericesp32: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\""
|
||||
|
||||
firmware-rak4631:
|
||||
arduino-cli compile --log --fqbn rakwireless:nrf52:WisCoreRAK4631Board -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x51\""
|
||||
|
||||
firmware-heltec_t114:
|
||||
arduino-cli compile --log --fqbn Heltec_nRF52:Heltec_nRF52:HT-n5262 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3C\""
|
||||
|
||||
firmware-techo:
|
||||
arduino-cli compile --log --fqbn adafruit:nrf52:pca10056 -e --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x44\""
|
||||
|
||||
firmware-xiao_s3:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:XIAO_ESP32S3" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3E\""
|
||||
|
||||
upload:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn unsignedio:avr:rnode
|
||||
|
||||
upload-mega2560:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:mega
|
||||
|
||||
upload-tbeam:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:t-beam
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.t-beam/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-tbeam_sx1262:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:t-beam
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.t-beam/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-lora32_v10:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-lora32_v20:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-lora32_v21:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:ttgo-lora32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-heltec32_v2:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:heltec_wifi_lora_32_V2
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB1 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-heltec32_v3:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:heltec_wifi_lora_32_V3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32-s3 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-heltec32_v4:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32-s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-tdeck:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32-s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-tbeam_supreme:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32-s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-rnode_ng_20:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-rnode_ng_21:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:ttgo-lora32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-t3s3:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-featheresp32:
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:featheresp32
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.featheresp32/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
upload-rak4631:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn rakwireless:nrf52:WisCoreRAK4631Board
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0)
|
||||
|
||||
upload-heltec_t114:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn Heltec_nRF52:Heltec_nRF52:HT-n5262
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0)
|
||||
|
||||
upload-techo:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn adafruit:nrf52:pca10056
|
||||
@sleep 6
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0)
|
||||
|
||||
upload-xiao_s3:
|
||||
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:XIAO_ESP32S3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bin)
|
||||
@sleep 3
|
||||
python ./Release/esptool/esptool.py --chip esp32s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
|
||||
|
||||
release: release-all
|
||||
|
||||
release-all: console-site spiffs-image release-tbeam release-tbeam_sx1262 release-lora32_v10 release-lora32_v20 release-lora32_v21 release-lora32_v10_extled release-lora32_v20_extled release-lora32_v21_extled release-lora32_v21_tcxo release-featheresp32 release-genericesp32 release-heltec32_v2 release-heltec32_v3 release-heltec32_v4 release-heltec32_v2_extled release-heltec_t114 release-techo release-rnode_ng_20 release-rnode_ng_21 release-t3s3 release-t3s3_sx127x release-t3s3_sx1280_pa release-tdeck release-tbeam_supreme release-rak4631 release-xiao_s3 release-hashes
|
||||
|
||||
release-hashes:
|
||||
python ./release_hashes.py > ./Release/release.json
|
||||
|
||||
release-rnode:
|
||||
arduino-cli compile --fqbn unsignedio:avr:rnode -e
|
||||
cp build/unsignedio.avr.rnode/RNode_Firmware.ino.hex Release/rnode_firmware.hex
|
||||
rm -r build
|
||||
|
||||
release-tbeam: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam.boot_app0
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bin build/rnode_firmware_tbeam.bin
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam.bootloader
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_tbeam.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam.boot_app0 build/rnode_firmware_tbeam.bin build/rnode_firmware_tbeam.bootloader build/rnode_firmware_tbeam.partitions
|
||||
rm -r build
|
||||
|
||||
release-tbeam_sx1262: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\" \"-DMODEM=0x03\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam_sx1262.boot_app0
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bin build/rnode_firmware_tbeam_sx1262.bin
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam_sx1262.bootloader
|
||||
cp build/esp32.esp32.t-beam/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam_sx1262.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_tbeam_sx1262.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam_sx1262.boot_app0 build/rnode_firmware_tbeam_sx1262.bin build/rnode_firmware_tbeam_sx1262.bootloader build/rnode_firmware_tbeam_sx1262.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v10: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v10.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v10.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v10.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v10.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v10.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v10.boot_app0 build/rnode_firmware_lora32v10.bin build/rnode_firmware_lora32v10.bootloader build/rnode_firmware_lora32v10.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v20: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v20.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v20.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v20.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v20.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v20.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v20.boot_app0 build/rnode_firmware_lora32v20.bin build/rnode_firmware_lora32v20.bootloader build/rnode_firmware_lora32v20.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v21: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v21.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21.boot_app0 build/rnode_firmware_lora32v21.bin build/rnode_firmware_lora32v21.bootloader build/rnode_firmware_lora32v21.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v10_extled: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\" \"-DEXTERNAL_LEDS=true\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v10.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v10.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v10.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v10.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v10.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v10.boot_app0 build/rnode_firmware_lora32v10.bin build/rnode_firmware_lora32v10.bootloader build/rnode_firmware_lora32v10.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v20_extled: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\" \"-DEXTERNAL_LEDS=true\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v20.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v20.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v20.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v20.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v20_extled.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v20.boot_app0 build/rnode_firmware_lora32v20.bin build/rnode_firmware_lora32v20.bootloader build/rnode_firmware_lora32v20.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v21_extled: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DEXTERNAL_LEDS=true\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v21_extled.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21.boot_app0 build/rnode_firmware_lora32v21.bin build/rnode_firmware_lora32v21.bootloader build/rnode_firmware_lora32v21.partitions
|
||||
rm -r build
|
||||
|
||||
release-lora32_v21_tcxo: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DENABLE_TCXO=true\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21_tcxo.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21_tcxo.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21_tcxo.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21_tcxo.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_lora32v21_tcxo.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21_tcxo.boot_app0 build/rnode_firmware_lora32v21_tcxo.bin build/rnode_firmware_lora32v21_tcxo.bootloader build/rnode_firmware_lora32v21_tcxo.partitions
|
||||
rm -r build
|
||||
|
||||
release-heltec32_v2: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v2.boot_app0
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v2.bin
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v2.bootloader
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v2.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_heltec32v2.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v2.boot_app0 build/rnode_firmware_heltec32v2.bin build/rnode_firmware_heltec32v2.bootloader build/rnode_firmware_heltec32v2.partitions
|
||||
rm -r build
|
||||
|
||||
release-heltec32_v3: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V3 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3A\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v3.boot_app0
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v3.bin
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v3.bootloader
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v3.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_heltec32v3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v3.boot_app0 build/rnode_firmware_heltec32v3.bin build/rnode_firmware_heltec32v3.bootloader build/rnode_firmware_heltec32v3.partitions
|
||||
rm -r build
|
||||
|
||||
release-heltec32_v4: check_bt_buffers
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3F\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v4pa.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v4pa.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v4pa.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v4pa.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_heltec32v4pa.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v4pa.boot_app0 build/rnode_firmware_heltec32v4pa.bin build/rnode_firmware_heltec32v4pa.bootloader build/rnode_firmware_heltec32v4pa.partitions
|
||||
rm -r build
|
||||
|
||||
release-heltec32_v2_extled: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\" \"-DEXTERNAL_LEDS=true\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v2.boot_app0
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v2.bin
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v2.bootloader
|
||||
cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v2.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_heltec32v2.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v2.boot_app0 build/rnode_firmware_heltec32v2.bin build/rnode_firmware_heltec32v2.bootloader build/rnode_firmware_heltec32v2.partitions
|
||||
rm -r build
|
||||
|
||||
release-rnode_ng_20: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_ng20.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_ng20.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_ng20.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_ng20.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_ng20.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_ng20.boot_app0 build/rnode_firmware_ng20.bin build/rnode_firmware_ng20.bootloader build/rnode_firmware_ng20.partitions
|
||||
rm -r build
|
||||
|
||||
release-rnode_ng_21: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x41\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_ng21.boot_app0
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_ng21.bin
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_ng21.bootloader
|
||||
cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_ng21.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_ng21.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_ng21.boot_app0 build/rnode_firmware_ng21.bin build/rnode_firmware_ng21.bootloader build/rnode_firmware_ng21.partitions
|
||||
rm -r build
|
||||
|
||||
release-t3s3:
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x03\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_t3s3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3.boot_app0 build/rnode_firmware_t3s3.bin build/rnode_firmware_t3s3.bootloader build/rnode_firmware_t3s3.partitions
|
||||
rm -r build
|
||||
|
||||
release-t3s3_sx1280_pa:
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x04\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3_sx1280_pa.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3_sx1280_pa.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3_sx1280_pa.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3_sx1280_pa.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_t3s3_sx1280_pa.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3_sx1280_pa.boot_app0 build/rnode_firmware_t3s3_sx1280_pa.bin build/rnode_firmware_t3s3_sx1280_pa.bootloader build/rnode_firmware_t3s3_sx1280_pa.partitions
|
||||
rm -r build
|
||||
|
||||
release-t3s3_sx127x:
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x42\" \"-DMODEM=0x01\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3_sx127x.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3_sx127x.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3_sx127x.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3_sx127x.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_t3s3_sx127x.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3_sx127x.boot_app0 build/rnode_firmware_t3s3_sx127x.bin build/rnode_firmware_t3s3_sx127x.bootloader build/rnode_firmware_t3s3_sx127x.partitions
|
||||
rm -r build
|
||||
|
||||
release-tdeck:
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3B\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tdeck.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_tdeck.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tdeck.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tdeck.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_tdeck.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tdeck.boot_app0 build/rnode_firmware_tdeck.bin build/rnode_firmware_tdeck.bootloader build/rnode_firmware_tdeck.partitions
|
||||
rm -r build
|
||||
|
||||
release-tbeam_supreme:
|
||||
arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3D\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam_supreme.boot_app0
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_tbeam_supreme.bin
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam_supreme.bootloader
|
||||
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam_supreme.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_tbeam_supreme.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam_supreme.boot_app0 build/rnode_firmware_tbeam_supreme.bin build/rnode_firmware_tbeam_supreme.bootloader build/rnode_firmware_tbeam_supreme.partitions
|
||||
rm -r build
|
||||
|
||||
release-featheresp32: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:featheresp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x34\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_featheresp32.boot_app0
|
||||
cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.bin build/rnode_firmware_featheresp32.bin
|
||||
cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_featheresp32.bootloader
|
||||
cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_featheresp32.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_featheresp32.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_featheresp32.boot_app0 build/rnode_firmware_featheresp32.bin build/rnode_firmware_featheresp32.bootloader build/rnode_firmware_featheresp32.partitions
|
||||
rm -r build
|
||||
|
||||
release-genericesp32: check_bt_buffers
|
||||
arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_esp32_generic.boot_app0
|
||||
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bin build/rnode_firmware_esp32_generic.bin
|
||||
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_esp32_generic.bootloader
|
||||
cp build/esp32.esp32.esp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_esp32_generic.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_esp32_generic.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_esp32_generic.boot_app0 build/rnode_firmware_esp32_generic.bin build/rnode_firmware_esp32_generic.bootloader build/rnode_firmware_esp32_generic.partitions
|
||||
rm -r build
|
||||
|
||||
release-mega2560:
|
||||
arduino-cli compile --fqbn arduino:avr:mega -e --build-property "compiler.cpp.extra_flags=\"-DMODEM=0x01\""
|
||||
cp build/arduino.avr.mega/RNode_Firmware.ino.hex Release/rnode_firmware_m2560.hex
|
||||
rm -r build
|
||||
|
||||
release-rak4631:
|
||||
arduino-cli compile --fqbn rakwireless:nrf52:WisCoreRAK4631Board -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x51\""
|
||||
cp build/rakwireless.nrf52.WisCoreRAK4631Board/RNode_Firmware.ino.hex build/rnode_firmware_rak4631.hex
|
||||
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_rak4631.hex Release/rnode_firmware_rak4631.zip
|
||||
|
||||
release-heltec_t114:
|
||||
arduino-cli compile --fqbn Heltec_nRF52:Heltec_nRF52:HT-n5262 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3C\""
|
||||
cp build/Heltec_nRF52.Heltec_nRF52.HT-n5262/RNode_Firmware.ino.hex build/rnode_firmware_heltec_t114.hex
|
||||
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_heltec_t114.hex Release/rnode_firmware_heltec_t114.zip
|
||||
|
||||
release-techo:
|
||||
arduino-cli compile --log --fqbn adafruit:nrf52:pca10056 -e --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x44\""
|
||||
cp build/adafruit.nrf52.pca10056/RNode_Firmware.ino.hex build/rnode_firmware_techo.hex
|
||||
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_techo.hex Release/rnode_firmware_techo.zip
|
||||
|
||||
release-xiao_s3:
|
||||
arduino-cli compile --fqbn "esp32:esp32:XIAO_ESP32S3" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3E\""
|
||||
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_xiao_esp32s3.boot_app0
|
||||
cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bin build/rnode_firmware_xiao_esp32s3.bin
|
||||
cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_xiao_esp32s3.bootloader
|
||||
cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_xiao_esp32s3.partitions
|
||||
zip --junk-paths ./Release/rnode_firmware_xiao_esp32s3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_xiao_esp32s3.boot_app0 build/rnode_firmware_xiao_esp32s3.bin build/rnode_firmware_xiao_esp32s3.bootloader build/rnode_firmware_xiao_esp32s3.partitions
|
||||
rm -r build
|
||||
4
Modem.h
Executable file
@@ -0,0 +1,4 @@
|
||||
#define SX1276 0x01
|
||||
#define SX1278 0x02
|
||||
#define SX1262 0x03
|
||||
#define SX1280 0x04
|
||||
38
NoopFileSystem.h
Executable file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_RNS
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <Bytes.h>
|
||||
#include <Type.h>
|
||||
|
||||
class NoopFileSystem : public RNS::FileSystemImpl {
|
||||
|
||||
public:
|
||||
NoopFileSystem() {}
|
||||
|
||||
bool init() { return true; }
|
||||
bool format() { return false; }
|
||||
bool reformat() { return false; }
|
||||
|
||||
public:
|
||||
// CBA Debug
|
||||
static void listDir(const char* dir, const char* prefix = "") {}
|
||||
static void dumpDir(const char* dir) {}
|
||||
|
||||
public:
|
||||
virtual bool file_exists(const char* file_path) { return false; }
|
||||
virtual size_t read_file(const char* file_path, RNS::Bytes& data) { return 0; }
|
||||
virtual size_t write_file(const char* file_path, const RNS::Bytes& data) { return 0; }
|
||||
virtual bool remove_file(const char* file_path) { return false; }
|
||||
virtual bool rename_file(const char* from_file_path, const char* to_file_path) { return false; }
|
||||
virtual bool directory_exists(const char* directory_path) { return false; }
|
||||
virtual bool create_directory(const char* directory_path) { return false; }
|
||||
virtual bool remove_directory(const char* directory_path) { return false; }
|
||||
virtual std::list<std::string> list_directory(const char* directory_path) { return std::list<std::string>(); }
|
||||
virtual size_t storage_size() { return 0; }
|
||||
virtual size_t storage_available() { return 0; }
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
650
Power.h
Executable file
@@ -0,0 +1,650 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#define PMU_TEMP_MIN -30
|
||||
#define PMU_TEMP_MAX 90
|
||||
#define PMU_TEMP_OFFSET 120
|
||||
bool pmu_temp_sensor_ready = false;
|
||||
float pmu_temperature = PMU_TEMP_MIN-1;
|
||||
|
||||
#if BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#include <XPowersLib.h>
|
||||
XPowersLibInterface* PMU = NULL;
|
||||
|
||||
#ifndef PMU_WIRE_PORT
|
||||
#if BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#define PMU_WIRE_PORT Wire1
|
||||
#else
|
||||
#define PMU_WIRE_PORT Wire
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.14
|
||||
|
||||
void disablePeripherals() {
|
||||
if (PMU) {
|
||||
// GNSS RTC PowerVDD
|
||||
PMU->enablePowerOutput(XPOWERS_VBACKUP);
|
||||
|
||||
// LoRa VDD
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO2);
|
||||
|
||||
// GNSS VDD
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO3);
|
||||
}
|
||||
}
|
||||
|
||||
bool pmuInterrupt;
|
||||
void setPmuFlag()
|
||||
{
|
||||
pmuInterrupt = true;
|
||||
}
|
||||
#elif BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.3
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 5
|
||||
const uint8_t pin_vbat = 35;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_T3S3
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.217
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 5
|
||||
const uint8_t pin_vbat = 1;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_TDECK
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.3
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 5
|
||||
const uint8_t pin_vbat = 4;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
// Unless we implement some real voodoo
|
||||
// on these boards, we can't say with
|
||||
// any certainty whether we are actually
|
||||
// charging and have reached a charge
|
||||
// complete state. The *only* data point
|
||||
// we have to go from is the bus voltage.
|
||||
// The BAT_V_CHG and BAT_V_FLOAT values
|
||||
// are set high here to avoid the display
|
||||
// indication confusingly flapping
|
||||
// between charge completed, charging and
|
||||
// discharging states.
|
||||
// Update: Vodoo implemented. Hopefully
|
||||
// it will work accross different boards.
|
||||
#define BAT_V_MIN 3.05
|
||||
#define BAT_V_MAX 4.0
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 7
|
||||
const uint8_t pin_vbat = 1;
|
||||
const uint8_t pin_ctrl = 37;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
#define BAT_V_MIN 3.05
|
||||
#define BAT_V_MAX 4.0
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 7
|
||||
const uint8_t pin_vbat = 1;
|
||||
const uint8_t pin_ctrl = 37;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.165
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 7
|
||||
const uint8_t pin_vbat = 4;
|
||||
const uint8_t pin_ctrl = 6;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#elif BOARD_MODEL == BOARD_TECHO
|
||||
#define BAT_V_MIN 3.15
|
||||
#define BAT_V_MAX 4.16
|
||||
#define BAT_V_CHG 4.48
|
||||
#define BAT_V_FLOAT 4.33
|
||||
#define BAT_SAMPLES 7
|
||||
const uint8_t pin_vbat = 4;
|
||||
float bat_p_samples[BAT_SAMPLES];
|
||||
float bat_v_samples[BAT_SAMPLES];
|
||||
uint8_t bat_samples_count = 0;
|
||||
int bat_discharging_samples = 0;
|
||||
int bat_charging_samples = 0;
|
||||
int bat_charged_samples = 0;
|
||||
bool bat_voltage_dropping = false;
|
||||
float bat_delay_v = 0;
|
||||
float bat_state_change_v = 0;
|
||||
#endif
|
||||
|
||||
uint32_t last_pmu_update = 0;
|
||||
uint8_t pmu_target_pps = 1;
|
||||
int pmu_update_interval = 1000/pmu_target_pps;
|
||||
uint8_t pmu_charged_ascertain = 0;
|
||||
uint8_t pmu_rc = 0;
|
||||
uint8_t pmu_sc = 0;
|
||||
float bat_delay_diff = 0;
|
||||
bool bat_diff_positive = false;
|
||||
#define PMU_R_INTERVAL 5
|
||||
#define PMU_SCV_RESET_INTERVAL 3
|
||||
void kiss_indicate_battery();
|
||||
void kiss_indicate_temperature();
|
||||
|
||||
void measure_temperature() {
|
||||
#if PLATFORM == PLATFORM_ESP32
|
||||
if (pmu_temp_sensor_ready) { pmu_temperature = temperatureRead(); } else { pmu_temperature = PMU_TEMP_MIN-1; }
|
||||
#endif
|
||||
}
|
||||
|
||||
void measure_battery() {
|
||||
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TECHO
|
||||
battery_installed = true;
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
battery_indeterminate = false;
|
||||
#else
|
||||
battery_indeterminate = true;
|
||||
#endif
|
||||
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.0041;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.00418;
|
||||
#elif BOARD_MODEL == BOARD_T3S3
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*6.7828;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.017165;
|
||||
#elif BOARD_MODEL == BOARD_TECHO
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.007067;
|
||||
#else
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*7.26;
|
||||
#endif
|
||||
|
||||
bat_v_samples[bat_samples_count%BAT_SAMPLES] = battery_measurement;
|
||||
bat_p_samples[bat_samples_count%BAT_SAMPLES] = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0;
|
||||
|
||||
bat_samples_count++;
|
||||
if (!battery_ready && bat_samples_count >= BAT_SAMPLES) {
|
||||
battery_ready = true;
|
||||
}
|
||||
|
||||
if (battery_ready) {
|
||||
|
||||
battery_percent = 0;
|
||||
for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) {
|
||||
battery_percent += bat_p_samples[bi];
|
||||
}
|
||||
battery_percent = battery_percent/BAT_SAMPLES;
|
||||
|
||||
battery_voltage = 0;
|
||||
for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) {
|
||||
battery_voltage += bat_v_samples[bi];
|
||||
}
|
||||
battery_voltage = battery_voltage/BAT_SAMPLES;
|
||||
|
||||
if (bat_delay_v == 0) bat_delay_v = battery_voltage;
|
||||
if (bat_state_change_v == 0) bat_state_change_v = battery_voltage;
|
||||
if (battery_percent > 100.0) battery_percent = 100.0;
|
||||
if (battery_percent < 0.0) battery_percent = 0.0;
|
||||
|
||||
if (bat_samples_count%BAT_SAMPLES == 0) {
|
||||
pmu_sc++;
|
||||
bat_delay_diff = battery_voltage-bat_state_change_v;
|
||||
|
||||
if (battery_voltage < bat_delay_v && battery_voltage < BAT_V_FLOAT) {
|
||||
if (bat_voltage_dropping == false) {
|
||||
if (bat_delay_diff < -0.008) {
|
||||
bat_voltage_dropping = true;
|
||||
bat_state_change_v = battery_voltage;
|
||||
}
|
||||
} else {
|
||||
if (pmu_sc%PMU_SCV_RESET_INTERVAL == 0) { bat_state_change_v = battery_voltage; }
|
||||
}
|
||||
} else {
|
||||
if (bat_voltage_dropping == true) {
|
||||
if (bat_delay_diff > 0.01) {
|
||||
bat_voltage_dropping = false;
|
||||
bat_state_change_v = battery_voltage;
|
||||
}
|
||||
}
|
||||
}
|
||||
bat_samples_count = 0;
|
||||
bat_delay_v = battery_voltage;
|
||||
}
|
||||
|
||||
if (bat_voltage_dropping && battery_voltage < BAT_V_FLOAT) {
|
||||
// if (battery_state != BATTERY_STATE_DISCHARGING) { SerialBT.printf("STATE CHANGE to DISCHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); }
|
||||
battery_state = BATTERY_STATE_DISCHARGING;
|
||||
pmu_charged_ascertain = 0;
|
||||
} else {
|
||||
if (pmu_charged_ascertain < 8) { pmu_charged_ascertain++; }
|
||||
else {
|
||||
if (battery_percent < 100.0) {
|
||||
// if (battery_state != BATTERY_STATE_CHARGING) { SerialBT.printf("STATE CHANGE to CHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); }
|
||||
battery_state = BATTERY_STATE_CHARGING;
|
||||
} else {
|
||||
// if (battery_state != BATTERY_STATE_CHARGED) { SerialBT.printf("STATE CHANGE to CHARGED at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); }
|
||||
battery_state = BATTERY_STATE_CHARGED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if MCU_VARIANT == MCU_NRF52
|
||||
if (bt_state != BT_STATE_OFF) { blebas.write(battery_percent); }
|
||||
#endif
|
||||
|
||||
// if (bt_state == BT_STATE_CONNECTED) {
|
||||
// SerialBT.printf("\nBus voltage %.3fv. Unfiltered %.3fv. Diff %.3f", battery_voltage, bat_v_samples[BAT_SAMPLES-1], bat_delay_diff);
|
||||
// if (bat_voltage_dropping) { SerialBT.printf("\n Voltage is dropping. Percentage %.1f%%.", battery_percent); }
|
||||
// else { SerialBT.printf("\n Voltage is not dropping. Percentage %.1f%%.", battery_percent); }
|
||||
// if (battery_state == BATTERY_STATE_DISCHARGING) { SerialBT.printf("\n Battery discharging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); }
|
||||
// if (battery_state == BATTERY_STATE_CHARGING) { SerialBT.printf("\n Battery charging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); }
|
||||
// if (battery_state == BATTERY_STATE_CHARGED) { SerialBT.print("\n Battery is charged."); }
|
||||
// SerialBT.print("\n");
|
||||
// }
|
||||
}
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
if (PMU) {
|
||||
float discharge_current = 0;
|
||||
float charge_current = 0;
|
||||
float ext_voltage = 0;
|
||||
float ext_current = 0;
|
||||
if (PMU->getChipModel() == XPOWERS_AXP192) {
|
||||
discharge_current = ((XPowersAXP192*)PMU)->getBattDischargeCurrent();
|
||||
charge_current = ((XPowersAXP192*)PMU)->getBatteryChargeCurrent();
|
||||
battery_voltage = PMU->getBattVoltage()/1000.0;
|
||||
// battery_percent = PMU->getBattPercentage()*1.0;
|
||||
battery_installed = PMU->isBatteryConnect();
|
||||
external_power = PMU->isVbusIn();
|
||||
ext_voltage = PMU->getVbusVoltage()/1000.0;
|
||||
ext_current = ((XPowersAXP192*)PMU)->getVbusCurrent();
|
||||
}
|
||||
else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||
battery_voltage = PMU->getBattVoltage()/1000.0;
|
||||
// battery_percent = PMU->getBattPercentage()*1.0;
|
||||
battery_installed = PMU->isBatteryConnect();
|
||||
external_power = PMU->isVbusIn();
|
||||
ext_voltage = PMU->getVbusVoltage()/1000.0;
|
||||
}
|
||||
|
||||
if (battery_installed) {
|
||||
if (PMU->isCharging()) {
|
||||
battery_state = BATTERY_STATE_CHARGING;
|
||||
battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0;
|
||||
} else {
|
||||
if (PMU->isDischarge()) {
|
||||
battery_state = BATTERY_STATE_DISCHARGING;
|
||||
battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0;
|
||||
} else {
|
||||
battery_state = BATTERY_STATE_CHARGED;
|
||||
battery_percent = 100.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
battery_state = BATTERY_STATE_UNKNOWN;
|
||||
battery_percent = 0.0;
|
||||
battery_voltage = 0.0;
|
||||
}
|
||||
|
||||
if (battery_percent > 100.0) battery_percent = 100.0;
|
||||
if (battery_percent < 0.0) battery_percent = 0.0;
|
||||
|
||||
float charge_watts = battery_voltage*(charge_current/1000.0);
|
||||
float discharge_watts = battery_voltage*(discharge_current/1000.0);
|
||||
float ext_watts = ext_voltage*(ext_current/1000.0);
|
||||
|
||||
battery_ready = true;
|
||||
|
||||
// if (bt_state == BT_STATE_CONNECTED) {
|
||||
// if (battery_installed) {
|
||||
// if (external_power) {
|
||||
// SerialBT.printf("External power connected, drawing %.2fw, %.1fmA at %.1fV\n", ext_watts, ext_current, ext_voltage);
|
||||
// } else {
|
||||
// SerialBT.println("Running on battery");
|
||||
// }
|
||||
// SerialBT.printf("Battery percentage %.1f%%\n", battery_percent);
|
||||
// SerialBT.printf("Battery voltage %.2fv\n", battery_voltage);
|
||||
// // SerialBT.printf("Temperature %.1f%\n", auxillary_temperature);
|
||||
|
||||
// if (battery_state == BATTERY_STATE_CHARGING) {
|
||||
// SerialBT.printf("Charging with %.2fw, %.1fmA at %.1fV\n", charge_watts, charge_current, battery_voltage);
|
||||
// } else if (battery_state == BATTERY_STATE_DISCHARGING) {
|
||||
// SerialBT.printf("Discharging at %.2fw, %.1fmA at %.1fV\n", discharge_watts, discharge_current, battery_voltage);
|
||||
// } else if (battery_state == BATTERY_STATE_CHARGED) {
|
||||
// SerialBT.printf("Battery charged\n");
|
||||
// }
|
||||
// } else {
|
||||
// SerialBT.println("No battery installed");
|
||||
// }
|
||||
// SerialBT.println("");
|
||||
// }
|
||||
}
|
||||
else {
|
||||
battery_ready = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (battery_ready) {
|
||||
pmu_rc++;
|
||||
if (pmu_rc%PMU_R_INTERVAL == 0) {
|
||||
kiss_indicate_battery();
|
||||
if (pmu_temp_sensor_ready) { kiss_indicate_temperature(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_pmu() {
|
||||
if (millis()-last_pmu_update >= pmu_update_interval) {
|
||||
measure_battery();
|
||||
measure_temperature();
|
||||
last_pmu_update = millis();
|
||||
}
|
||||
}
|
||||
|
||||
bool init_pmu() {
|
||||
#if IS_ESP32S3
|
||||
pmu_temp_sensor_ready = true;
|
||||
#endif
|
||||
|
||||
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_TECHO
|
||||
pinMode(pin_vbat, INPUT);
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
// there are three version of V3: V3, V3.1, and V3.2
|
||||
// V3 and V3.1 have a pull up on pin_ctrl and are active low
|
||||
// V3.2 has a transistor and active high
|
||||
// put the pin input mode and read it. if it's high, we have V3 or V3.1
|
||||
// other wise, it's a V3.2
|
||||
uint16_t pin_ctrl_value;
|
||||
uint8_t pin_ctrl_active = LOW;
|
||||
pinMode(pin_ctrl, INPUT);
|
||||
pin_ctrl_value = digitalRead(pin_ctrl);
|
||||
if(pin_ctrl_value == HIGH) {
|
||||
// We have either a V3 or V3.1
|
||||
pin_ctrl_active = LOW;
|
||||
}
|
||||
else {
|
||||
// We have a V3.2
|
||||
pin_ctrl_active = HIGH;
|
||||
}
|
||||
pinMode(pin_ctrl,OUTPUT);
|
||||
digitalWrite(pin_ctrl, pin_ctrl_active);
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
pinMode(pin_ctrl,OUTPUT);
|
||||
digitalWrite(pin_ctrl, HIGH);
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
pinMode(pin_ctrl,OUTPUT);
|
||||
digitalWrite(pin_ctrl, HIGH);
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_TBEAM
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
|
||||
if (!PMU) {
|
||||
PMU = new XPowersAXP2101(PMU_WIRE_PORT);
|
||||
if (!PMU->init()) {
|
||||
delete PMU;
|
||||
PMU = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PMU) {
|
||||
PMU = new XPowersAXP192(PMU_WIRE_PORT);
|
||||
if (!PMU->init()) {
|
||||
delete PMU;
|
||||
PMU = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PMU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure charging indicator
|
||||
PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF);
|
||||
|
||||
pinMode(PMU_IRQ, INPUT_PULLUP);
|
||||
attachInterrupt(PMU_IRQ, setPmuFlag, FALLING);
|
||||
|
||||
if (PMU->getChipModel() == XPOWERS_AXP192) {
|
||||
|
||||
// Turn off unused power sources to save power
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC1);
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||
PMU->disablePowerOutput(XPOWERS_LDO2);
|
||||
PMU->disablePowerOutput(XPOWERS_LDO3);
|
||||
|
||||
// Set the power of LoRa and GPS module to 3.3V
|
||||
// LoRa
|
||||
PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300);
|
||||
// GPS
|
||||
PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300);
|
||||
// OLED
|
||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||
|
||||
// Turn on LoRa
|
||||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||||
|
||||
// Turn on GPS
|
||||
//PMU->enablePowerOutput(XPOWERS_LDO3);
|
||||
|
||||
// protected oled power source
|
||||
PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||||
// protected esp32 power source
|
||||
PMU->setProtectedChannel(XPOWERS_DCDC3);
|
||||
// enable oled power
|
||||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||||
|
||||
PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ);
|
||||
|
||||
PMU->enableIRQ(XPOWERS_AXP192_VBUS_REMOVE_IRQ |
|
||||
XPOWERS_AXP192_VBUS_INSERT_IRQ |
|
||||
XPOWERS_AXP192_BAT_CHG_DONE_IRQ |
|
||||
XPOWERS_AXP192_BAT_CHG_START_IRQ |
|
||||
XPOWERS_AXP192_BAT_REMOVE_IRQ |
|
||||
XPOWERS_AXP192_BAT_INSERT_IRQ |
|
||||
XPOWERS_AXP192_PKEY_SHORT_IRQ
|
||||
);
|
||||
|
||||
}
|
||||
else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||
|
||||
// Turn off unused power sources to save power
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC3);
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC4);
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC5);
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO1);
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO2);
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO3);
|
||||
PMU->disablePowerOutput(XPOWERS_ALDO4);
|
||||
PMU->disablePowerOutput(XPOWERS_BLDO1);
|
||||
PMU->disablePowerOutput(XPOWERS_BLDO2);
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO1);
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO2);
|
||||
PMU->disablePowerOutput(XPOWERS_VBACKUP);
|
||||
|
||||
// Set the power of LoRa and GPS module to 3.3V
|
||||
// LoRa
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||||
// GPS
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||||
PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300);
|
||||
|
||||
// ESP32 VDD
|
||||
// ! No need to set, automatically open , Don't close it
|
||||
// PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||
// PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||||
PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||||
|
||||
// LoRa VDD
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||||
|
||||
// GNSS VDD
|
||||
//PMU->enablePowerOutput(XPOWERS_ALDO3);
|
||||
|
||||
// GNSS RTC PowerVDD
|
||||
//PMU->enablePowerOutput(XPOWERS_VBACKUP);
|
||||
}
|
||||
|
||||
PMU->enableSystemVoltageMeasure();
|
||||
PMU->enableVbusVoltageMeasure();
|
||||
PMU->enableBattVoltageMeasure();
|
||||
// It is necessary to disable the detection function of the TS pin on the board
|
||||
// without the battery temperature detection function, otherwise it will cause abnormal charging
|
||||
PMU->disableTSPinMeasure();
|
||||
|
||||
// Set the time of pressing the button to turn off
|
||||
PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S);
|
||||
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
Wire1.begin(I2C_SDA, I2C_SCL);
|
||||
|
||||
if (!PMU) {
|
||||
PMU = new XPowersAXP2101(PMU_WIRE_PORT);
|
||||
if (!PMU->init()) {
|
||||
delete PMU;
|
||||
PMU = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PMU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* gnss module power channel
|
||||
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
|
||||
* initialization
|
||||
*/
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||||
|
||||
// lora radio power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO3);
|
||||
|
||||
// m.2 interface
|
||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_DCDC3);
|
||||
|
||||
/**
|
||||
* ALDO2 cannot be turned off.
|
||||
* It is a necessary condition for sensor communication.
|
||||
* It must be turned on to properly access the sensor and screen
|
||||
* It is also responsible for the power supply of PCF8563
|
||||
*/
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||||
|
||||
// 6-axis , magnetometer ,bme280 , oled screen power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO1);
|
||||
|
||||
// sdcard power channle
|
||||
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_BLDO1);
|
||||
|
||||
// PMU->setPowerChannelVoltage(XPOWERS_DCDC4, 3300);
|
||||
// PMU->enablePowerOutput(XPOWERS_DCDC4);
|
||||
|
||||
// not use channel
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist
|
||||
PMU->disablePowerOutput(XPOWERS_VBACKUP);
|
||||
|
||||
// Configure charging
|
||||
PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||
// TODO: Reset
|
||||
PMU->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);
|
||||
|
||||
// Set the time of pressing the button to turn off
|
||||
PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S);
|
||||
PMU->setPowerKeyPressOnTime(XPOWERS_POWERON_128MS);
|
||||
|
||||
// disable all axp chip interrupt
|
||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
PMU->clearIrqStatus();
|
||||
|
||||
// It is necessary to disable the detection function of the TS pin on the board
|
||||
// without the battery temperature detection function, otherwise it will cause abnormal charging
|
||||
PMU->disableTSPinMeasure();
|
||||
PMU->enableVbusVoltageMeasure();
|
||||
PMU->enableBattVoltageMeasure();
|
||||
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
47
Python Module/Example.py
Executable file
@@ -0,0 +1,47 @@
|
||||
# This is a short example program that
|
||||
# demonstrates the bare minimum of using
|
||||
# RNode in a Python program.
|
||||
#
|
||||
# The example and the RNode.py library is
|
||||
# written for Python 3, so be sure to run
|
||||
# it with: python3 Example.py
|
||||
|
||||
# First we'll import the RNodeInterface class.
|
||||
from RNode import RNodeInterface
|
||||
|
||||
# We'll also define which serial port the
|
||||
# RNode is attached to.
|
||||
serialPort = "/dev/ttyUSB0"
|
||||
|
||||
# This function gets called every time a
|
||||
# packet is received
|
||||
def gotPacket(data, rnode):
|
||||
message = data.decode("utf-8")
|
||||
print("Received a packet: "+message)
|
||||
print("RSSI: "+str(rnode.r_stat_rssi)+" dBm")
|
||||
print("SNR: "+str(rnode.r_stat_snr)+" dB")
|
||||
|
||||
# Create an RNode instance. This configures
|
||||
# and powers up the radio.
|
||||
rnode = RNodeInterface(
|
||||
callback = gotPacket,
|
||||
name = "My RNode",
|
||||
port = serialPort,
|
||||
frequency = 868000000,
|
||||
bandwidth = 125000,
|
||||
txpower = 2,
|
||||
sf = 7,
|
||||
cr = 5,
|
||||
loglevel = RNodeInterface.LOG_DEBUG)
|
||||
|
||||
# Enter a loop waiting for user input.
|
||||
try:
|
||||
print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit")
|
||||
while True:
|
||||
input()
|
||||
message = "Hello World!"
|
||||
data = message.encode("utf-8")
|
||||
rnode.send(data)
|
||||
except KeyboardInterrupt as e:
|
||||
print("")
|
||||
exit()
|
||||
536
Python Module/RNode.py
Executable file
@@ -0,0 +1,536 @@
|
||||
# RNode interface class for Python 3
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020 Mark Qvist - unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import math
|
||||
|
||||
class KISS():
|
||||
FEND = 0xC0
|
||||
FESC = 0xDB
|
||||
TFEND = 0xDC
|
||||
TFESC = 0xDD
|
||||
CMD_UNKNOWN = 0xFE
|
||||
CMD_DATA = 0x00
|
||||
CMD_FREQUENCY = 0x01
|
||||
CMD_BANDWIDTH = 0x02
|
||||
CMD_TXPOWER = 0x03
|
||||
CMD_SF = 0x04
|
||||
CMD_CR = 0x05
|
||||
CMD_RADIO_STATE = 0x06
|
||||
CMD_RADIO_LOCK = 0x07
|
||||
CMD_DETECT = 0x08
|
||||
CMD_PROMISC = 0x0E
|
||||
CMD_READY = 0x0F
|
||||
CMD_STAT_RX = 0x21
|
||||
CMD_STAT_TX = 0x22
|
||||
CMD_STAT_RSSI = 0x23
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FW_VERSION = 0x50
|
||||
CMD_ROM_READ = 0x51
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||
return data
|
||||
|
||||
|
||||
class RNodeInterface():
|
||||
MTU = 500
|
||||
MAX_CHUNK = 32768
|
||||
FREQ_MIN = 137000000
|
||||
FREQ_MAX = 1020000000
|
||||
|
||||
LOG_CRITICAL = 0
|
||||
LOG_ERROR = 1
|
||||
LOG_WARNING = 2
|
||||
LOG_NOTICE = 3
|
||||
LOG_INFO = 4
|
||||
LOG_VERBOSE = 5
|
||||
LOG_DEBUG = 6
|
||||
LOG_EXTREME = 7
|
||||
|
||||
FREQ_MIN = 137000000
|
||||
FREQ_MAX = 1020000000
|
||||
|
||||
RSSI_OFFSET = 157
|
||||
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = LOG_NOTICE, flow_control = False, id_interval = None, id_callsign = None):
|
||||
self.serial = None
|
||||
self.loglevel = loglevel
|
||||
self.callback = callback
|
||||
self.name = name
|
||||
self.port = port
|
||||
self.speed = 115200
|
||||
self.databits = 8
|
||||
self.parity = serial.PARITY_NONE
|
||||
self.stopbits = 1
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
self.txpower = txpower
|
||||
self.sf = sf
|
||||
self.cr = cr
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
|
||||
self.last_id = 0
|
||||
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
self.r_txpower = None
|
||||
self.r_sf = None
|
||||
self.r_cr = None
|
||||
self.r_state = None
|
||||
self.r_lock = None
|
||||
self.r_stat_rx = None
|
||||
self.r_stat_tx = None
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
self.r_random = None
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
|
||||
self.validcfg = True
|
||||
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
||||
self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.txpower < 0 or self.txpower > 17):
|
||||
self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.bandwidth < 7800 or self.bandwidth > 500000):
|
||||
self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.sf < 7 or self.sf > 12):
|
||||
self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.cr < 5 or self.cr > 8):
|
||||
self.log("Invalid coding rate configured for "+str(self), RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if id_interval != None and id_callsign != None:
|
||||
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
||||
self.should_id = True
|
||||
self.id_callsign = id_callsign
|
||||
self.id_interval = id_interval
|
||||
else:
|
||||
self.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
else:
|
||||
self.id_interval = None
|
||||
self.id_callsign = None
|
||||
|
||||
if (not self.validcfg):
|
||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||
|
||||
try:
|
||||
self.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
self.log("Serial port "+self.port+" is now open")
|
||||
self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE)
|
||||
self.initRadio()
|
||||
if (self.validateRadioState()):
|
||||
self.interface_ready = True
|
||||
self.log(str(self)+" is configured and powered up")
|
||||
sleep(1.0)
|
||||
else:
|
||||
self.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR)
|
||||
self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR)
|
||||
self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass validation")
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
def log(self, message, level):
|
||||
pass
|
||||
|
||||
def initRadio(self):
|
||||
self.setFrequency()
|
||||
self.setBandwidth()
|
||||
self.setTXPower()
|
||||
self.setSpreadingFactor()
|
||||
self.setCodingRate()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
def setFrequency(self):
|
||||
c1 = self.frequency >> 24
|
||||
c2 = self.frequency >> 16 & 0xFF
|
||||
c3 = self.frequency >> 8 & 0xFF
|
||||
c4 = self.frequency & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring frequency for "+self(str))
|
||||
|
||||
def setBandwidth(self):
|
||||
c1 = self.bandwidth >> 24
|
||||
c2 = self.bandwidth >> 16 & 0xFF
|
||||
c3 = self.bandwidth >> 8 & 0xFF
|
||||
c4 = self.bandwidth & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
|
||||
|
||||
def setTXPower(self):
|
||||
txp = bytes([self.txpower])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring TX power for "+self(str))
|
||||
|
||||
def setSpreadingFactor(self):
|
||||
sf = bytes([self.sf])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
|
||||
|
||||
def setCodingRate(self):
|
||||
cr = bytes([self.cr])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
||||
|
||||
def setRadioState(self, state):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
||||
|
||||
def validateRadioState(self):
|
||||
self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE)
|
||||
sleep(0.25);
|
||||
if (self.frequency != self.r_frequency):
|
||||
self.log("Frequency mismatch", RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
if (self.bandwidth != self.r_bandwidth):
|
||||
self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
if (self.txpower != self.r_txpower):
|
||||
self.log("TX power mismatch", RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
if (self.sf != self.r_sf):
|
||||
self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.validcfg):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def setPromiscuousMode(self, state):
|
||||
if state == True:
|
||||
kiss_command = bytes([KISS.FEND,KISS.CMD_PROMISC, 0x01, KISS.FEND])
|
||||
else:
|
||||
kiss_command = bytes([KISS.FEND,KISS.CMD_PROMISC, 0x00, KISS.FEND])
|
||||
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str))
|
||||
|
||||
|
||||
def updateBitrate(self):
|
||||
try:
|
||||
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
||||
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
|
||||
self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG)
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.callback(data, self)
|
||||
|
||||
def send(self, data):
|
||||
self.processOutgoing(data)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
if self.flow_control:
|
||||
self.interface_ready = False
|
||||
|
||||
frame = b""
|
||||
|
||||
if self.id_interval != None and self.id_callsign != None:
|
||||
if self.last_id + self.id_interval < time.time():
|
||||
self.last_id = time.time()
|
||||
frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0])
|
||||
|
||||
data = KISS.escape(data)
|
||||
frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||
written = self.serial.write(frame)
|
||||
|
||||
if written != len(frame):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
else:
|
||||
self.queue(data)
|
||||
|
||||
def queue(self, data):
|
||||
self.packet_queue.append(data)
|
||||
|
||||
def process_queue(self):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
def readLoop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
while self.serial.is_open:
|
||||
if self.serial.in_waiting:
|
||||
byte = ord(self.serial.read(1))
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNodeInterface.MTU):
|
||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||
command = byte
|
||||
elif (command == KISS.CMD_DATA):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
elif (command == KISS.CMD_FREQUENCY):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
||||
self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG)
|
||||
self.updateBitrate()
|
||||
|
||||
elif (command == KISS.CMD_BANDWIDTH):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
||||
self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG)
|
||||
self.updateBitrate()
|
||||
|
||||
elif (command == KISS.CMD_TXPOWER):
|
||||
self.r_txpower = byte
|
||||
self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_SF):
|
||||
self.r_sf = byte
|
||||
self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG)
|
||||
self.updateBitrate()
|
||||
elif (command == KISS.CMD_CR):
|
||||
self.r_cr = byte
|
||||
self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG)
|
||||
self.updateBitrate()
|
||||
elif (command == KISS.CMD_RADIO_STATE):
|
||||
self.r_state = byte
|
||||
elif (command == KISS.CMD_RADIO_LOCK):
|
||||
self.r_lock = byte
|
||||
elif (command == KISS.CMD_STAT_RX):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
||||
|
||||
elif (command == KISS.CMD_STAT_TX):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
||||
|
||||
elif (command == KISS.CMD_STAT_RSSI):
|
||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||
elif (command == KISS.CMD_STAT_SNR):
|
||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_ERROR):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
self.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
self.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
else:
|
||||
self.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
elif (command == KISS.CMD_READY):
|
||||
self.process_queue()
|
||||
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||
self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG)
|
||||
data_buffer = b""
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
escape = False
|
||||
sleep(0.08)
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR)
|
||||
self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR)
|
||||
|
||||
def log(self, msg, level=3):
|
||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||
if self.loglevel >= level:
|
||||
timestamp = time.time()
|
||||
logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
|
||||
print(logstring)
|
||||
|
||||
def loglevelname(self, level):
|
||||
if (level == RNodeInterface.LOG_CRITICAL):
|
||||
return "Critical"
|
||||
if (level == RNodeInterface.LOG_ERROR):
|
||||
return "Error"
|
||||
if (level == RNodeInterface.LOG_WARNING):
|
||||
return "Warning"
|
||||
if (level == RNodeInterface.LOG_NOTICE):
|
||||
return "Notice"
|
||||
if (level == RNodeInterface.LOG_INFO):
|
||||
return "Info"
|
||||
if (level == RNodeInterface.LOG_VERBOSE):
|
||||
return "Verbose"
|
||||
if (level == RNodeInterface.LOG_DEBUG):
|
||||
return "Debug"
|
||||
if (level == RNodeInterface.LOG_EXTREME):
|
||||
return "Extra"
|
||||
|
||||
def hexrep(data, delimit=True):
|
||||
delimiter = ":"
|
||||
if not delimit:
|
||||
delimiter = ""
|
||||
hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
|
||||
return hexrep
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+self.name+"]"
|
||||
|
||||
295
README.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# RNodeTHV4 — Reticulum Boundary Node for Heltec WiFi LoRa 32 V4
|
||||
|
||||
A custom firmware for the **Heltec WiFi LoRa 32 V4** (ESP32-S3 + SX1262) that operates as a **Boundary Node** — bridging a local LoRa radio network with a remote TCP/IP backbone (such as [rmap.world](https://rmap.world)) over WiFi.
|
||||
|
||||
```
|
||||
Android / Sideband Remote
|
||||
┌──────────┐ ┌──────────────┐ WiFi Reticulum
|
||||
│ Sideband │◄── BT ──►│ RNode (V4) │◄── TCP ──────────► Backbone
|
||||
│ App │ │ Boundary Mode│ ▲ (rnsd /
|
||||
└──────────┘ └──────┬───────┘ │ rmap.world)
|
||||
│ ┌───┴───┐
|
||||
LoRa Radio │ Router │
|
||||
│ └───────┘
|
||||
◄── RF mesh ──►
|
||||
Other RNodes
|
||||
```
|
||||
|
||||
Built on [microReticulum](https://github.com/attermann/microReticulum) (a C++ port of the [Reticulum](https://reticulum.network/) network stack) and the [RNode firmware](https://github.com/markqvist/RNode_Firmware) by Mark Qvist.
|
||||
|
||||
## Features
|
||||
|
||||
- **Bidirectional LoRa ↔ TCP bridging** — local LoRa mesh nodes can reach the global Reticulum backbone and vice versa
|
||||
- **Web-based configuration portal** — WiFi SSID/password, backbone host/port, LoRa parameters, all configurable via captive portal
|
||||
- **OLED status display** — real-time status indicators for LoRa, WiFi, WAN (backbone), LAN (local TCP), plus IP address, port, and airtime
|
||||
- **Optional local TCP server** — serve local devices on your WiFi in addition to the backbone connection
|
||||
- **Automatic reconnection** — WiFi and TCP connections recover from drops with exponential backoff
|
||||
- **ESP32 memory-optimized** — table sizes, timeouts, and caching tuned for the constrained MCU environment
|
||||
|
||||
## Hardware
|
||||
|
||||
| Component | Spec |
|
||||
|-----------|------|
|
||||
| **Board** | Heltec WiFi LoRa 32 V4 |
|
||||
| **MCU** | ESP32-S3, 2MB PSRAM, 16MB Flash |
|
||||
| **Radio** | SX1262 + GC1109 PA (up to 28 dBm) |
|
||||
| **Display** | SSD1306 OLED 128×64 |
|
||||
| **WiFi** | 2.4 GHz 802.11 b/g/n |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [PlatformIO](https://platformio.org/) installed (via VS Code extension or CLI)
|
||||
- Heltec WiFi LoRa 32 V4 connected via USB
|
||||
|
||||
### Build & Flash
|
||||
|
||||
```bash
|
||||
# Clone this repo
|
||||
git clone https://github.com/jrl290/RNodeTHV4.git
|
||||
cd RNodeTHV4
|
||||
|
||||
# Build
|
||||
pio run -e heltec_V4_boundary
|
||||
|
||||
# Flash
|
||||
pio run -e heltec_V4_boundary -t upload
|
||||
|
||||
# Monitor serial output (optional)
|
||||
pio device monitor -e heltec_V4_boundary
|
||||
```
|
||||
|
||||
On first boot (or if no configuration is found), the device automatically enters the **Configuration Portal**.
|
||||
|
||||
## Configuration Portal
|
||||
|
||||
### Entering Config Mode
|
||||
|
||||
The config portal activates automatically on:
|
||||
- **First boot** — when no saved configuration exists
|
||||
- **Button hold >5 seconds** — hold the PRG button for 5+ seconds, the device reboots into config mode
|
||||
|
||||
When active, the device creates a WiFi access point named **`RNode-Boundary-Setup`** (open network). Connect to it and browse to `http://192.168.4.1`.
|
||||
|
||||
### Config Page Options
|
||||
|
||||
The web form has four sections:
|
||||
|
||||
#### 📶 WiFi Network
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **WiFi** | Enable/Disable (disable for LoRa-only repeater mode) |
|
||||
| **SSID** | Your WiFi network name |
|
||||
| **Password** | WiFi password |
|
||||
|
||||
#### 🌐 TCP Backbone
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **Mode** | `Disabled` or `Client (connect to backbone)` |
|
||||
| **Backbone Host** | IP address or hostname of backbone server (e.g. `rmap.world`) |
|
||||
| **Backbone Port** | TCP port (default: `4242`) |
|
||||
|
||||
#### 📡 Local TCP Server (optional)
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **Local TCP Server** | Enable/Disable — runs a TCP server on your WiFi for local Reticulum nodes to connect |
|
||||
| **TCP Port** | Port to listen on (default: `4242`) |
|
||||
|
||||
#### 📻 LoRa Radio
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **Frequency** | e.g. `867.200` MHz — must match your other RNodes |
|
||||
| **Bandwidth** | 7.8 kHz – 500 kHz (typically `125 kHz`) |
|
||||
| **Spreading Factor** | SF6 – SF12 (typically `SF7` for backbone, `SF10` for long range) |
|
||||
| **Coding Rate** | 4/5 – 4/8 |
|
||||
| **TX Power** | 2 – 22 dBm |
|
||||
|
||||
After saving, the device reboots with the new configuration applied.
|
||||
|
||||
## OLED Display Layout
|
||||
|
||||
The 128×64 OLED is split into two panels:
|
||||
|
||||
### Left Panel — Status Indicators (64×64)
|
||||
|
||||
```
|
||||
● LORA ← filled circle = radio online
|
||||
○ wifi ← unfilled circle = WiFi disconnected
|
||||
● WAN ← filled = backbone TCP connected
|
||||
○ LAN ← unfilled = no local TCP clients
|
||||
────────────────
|
||||
Air:0.3% ← current LoRa airtime
|
||||
▓▓▓▓▓ ||||||| ← battery, signal quality
|
||||
```
|
||||
|
||||
- **Filled circle (●)** = active/connected
|
||||
- **Unfilled circle (○)** = inactive/disconnected
|
||||
- Labels are UPPERCASE when active, lowercase when inactive (except LAN which is always uppercase)
|
||||
|
||||
### Right Panel — Device Info (64×64)
|
||||
|
||||
```
|
||||
▓▓ RNodeTHV4 ▓▓ ← title bar (inverted)
|
||||
867.200MHz ← LoRa frequency
|
||||
SF7 125k ← spreading factor & bandwidth
|
||||
──────────────── ← separator
|
||||
192.168.1.42 ← WiFi IP address (or "No WiFi")
|
||||
Port:4242 ← backbone TCP port
|
||||
──────────────── ← separator
|
||||
```
|
||||
|
||||
## Interface Modes
|
||||
|
||||
The firmware runs **two RNS interfaces** simultaneously, using different interface modes to control announce propagation and routing behavior:
|
||||
|
||||
### LoRa Interface — `MODE_ACCESS_POINT`
|
||||
|
||||
The LoRa radio operates in **Access Point mode**. In Reticulum, this means:
|
||||
- The interface broadcasts its own announces but **blocks rebroadcast of remote announces** from crossing to LoRa
|
||||
- This prevents backbone announces (hundreds of remote destinations) from flooding the limited-bandwidth LoRa channel
|
||||
- Local nodes discover the boundary node directly; the boundary node answers path requests for remote destinations from its cache
|
||||
|
||||
### TCP Backbone Interface — `MODE_BOUNDARY`
|
||||
|
||||
The TCP backbone connection uses a custom **Boundary mode** (`0x20`), a new interface mode added to microReticulum for this firmware. Boundary mode means:
|
||||
- Incoming announces from the backbone are received and cached, but **not stored in the path table by default** — only stored when specifically requested via a path request from a local LoRa node
|
||||
- This prevents the path table (limited to 48 entries on ESP32) from being overwhelmed by thousands of backbone destinations
|
||||
- When the path table needs to be culled, **Boundary-mode paths are evicted first**, preserving locally-needed LoRa paths
|
||||
|
||||
### Optional Local TCP Server — `MODE_ACCESS_POINT`
|
||||
|
||||
If enabled, a TCP server on the WiFi network allows local Reticulum nodes to connect. It also uses Access Point mode, with the same announce filtering as LoRa.
|
||||
|
||||
## Routing & Memory Customizations
|
||||
|
||||
The ESP32-S3 has limited RAM compared to a desktop Reticulum node. Several customizations were made to the microReticulum library to operate reliably within these constraints:
|
||||
|
||||
### Table Size Limits
|
||||
|
||||
| Table | Default (Desktop) | RNodeTHV4 | Rationale |
|
||||
|-------|-------------------|-----------|-----------|
|
||||
| Path table (`_destination_table`) | Unbounded | **48 entries** | Prevents unbounded growth; boundary paths evicted first |
|
||||
| Hash list (`_hashlist`) | 1,000,000 | **32** | Packet dedup list; small is fine for low-throughput LoRa |
|
||||
| Path request tags (`_max_pr_tags`) | 32,000 | **32** | Pending path requests rarely exceed a few dozen |
|
||||
| Known destinations | 100 | **24** | Identity cache; rarely need more on a boundary node |
|
||||
| Max queued announces | 16 | **4** | Outbound announce queue; LoRa is slow, no point queuing many |
|
||||
| Max receipts | 1,024 | **20** | Packet receipt tracking |
|
||||
|
||||
### Timeout Reductions
|
||||
|
||||
| Setting | Default | RNodeTHV4 | Rationale |
|
||||
|---------|---------|-----------|-----------|
|
||||
| Destination timeout | 7 days | **1 day** | Free memory faster; stale paths re-resolve automatically |
|
||||
| Pathfinder expiry | 7 days | **1 day** | Same as above |
|
||||
| AP path time | 24 hours | **6 hours** | AP paths go stale faster in mesh environments |
|
||||
| Roaming path time | 6 hours | **1 hour** | Mobile nodes change paths frequently |
|
||||
| Table cull interval | 5 seconds | **60 seconds** | Less CPU overhead on culling |
|
||||
| Job/Clean/Persist intervals | 5m/15m/12h | **60s/60s/60s** | More frequent housekeeping for MCU stability |
|
||||
|
||||
### Selective Backbone Caching
|
||||
|
||||
The most critical optimization: **backbone announces are not stored in the path table by default**. A backbone like `rmap.world` may advertise hundreds of destinations. Storing them all would evict every local LoRa path.
|
||||
|
||||
Instead:
|
||||
1. Backbone announces are received and their packets cached to flash storage
|
||||
2. When a local LoRa node requests a path, the boundary checks its cache and responds directly
|
||||
3. Only **specifically requested** paths get a path table entry
|
||||
4. Path table culling prioritizes evicting backbone entries over local ones
|
||||
|
||||
### Default Route Forwarding
|
||||
|
||||
When a transport-addressed packet arrives from LoRa but the boundary has no path table entry for it, the firmware:
|
||||
1. Strips the transport headers (converts `HEADER_2` → `HEADER_1/BROADCAST`)
|
||||
2. Forwards the raw packet to the backbone interface
|
||||
3. Creates reverse-table entries so proofs can route back to the sender
|
||||
|
||||
This acts as a **default route** — any packet the boundary can't route locally gets forwarded to the backbone.
|
||||
|
||||
### Cached Packet Unpacking Fix
|
||||
|
||||
The original microReticulum `get_cached_packet()` function called `update_hash()` after deserializing cached packets from flash. However, `update_hash()` only computes the packet hash — it does **not** parse the raw bytes into fields like `destination_hash`, `data`, `flags`, etc.
|
||||
|
||||
This was changed to call `unpack()` instead, which parses all packet fields AND computes the hash. Without this fix, path responses contained empty destination hashes and were silently dropped by LoRa nodes.
|
||||
|
||||
## Connecting to the Backbone
|
||||
|
||||
### Example: Connect to rmap.world
|
||||
|
||||
In the configuration portal:
|
||||
1. Set WiFi SSID and password
|
||||
2. Set TCP Backbone Mode to **Client**
|
||||
3. Set Backbone Host to `rmap.world`
|
||||
4. Set Backbone Port to `4242`
|
||||
5. Save and reboot
|
||||
|
||||
### Example: Local rnsd Server
|
||||
|
||||
On your server, configure `rnsd` with a TCP Server Interface in `~/.reticulum/config`:
|
||||
|
||||
```ini
|
||||
[interfaces]
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
listen_host = 0.0.0.0
|
||||
listen_port = 4242
|
||||
```
|
||||
|
||||
Then configure the boundary node as a **Client** pointing to your server's IP.
|
||||
|
||||
### Example: rnsd Connects to Boundary
|
||||
|
||||
On your server, configure `rnsd` with a TCP Client Interface:
|
||||
|
||||
```ini
|
||||
[interfaces]
|
||||
[[TCP Client to Boundary]]
|
||||
type = TCPClientInterface
|
||||
target_host = <boundary-node-ip>
|
||||
target_port = 4242
|
||||
```
|
||||
|
||||
Set the boundary node's **Local TCP Server** to **Enabled** (port 4242).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `RNode_Firmware.ino` | Main firmware — boundary mode initialization, interface setup, button handling |
|
||||
| `BoundaryMode.h` | Boundary state struct, EEPROM load/save, configuration defaults |
|
||||
| `BoundaryConfig.h` | Web-based captive portal for configuration |
|
||||
| `TcpInterface.h` | TCP backbone interface (implements `RNS::InterfaceImpl`) with HDLC framing |
|
||||
| `Display.h` | OLED display layout — boundary-specific status page |
|
||||
| `Boards.h` | Board variant definition for `heltec32v4_boundary` |
|
||||
| `platformio.ini` | Build targets: `heltec_V4_boundary` and `heltec_V4_boundary-local` |
|
||||
|
||||
### Library Patches
|
||||
|
||||
The firmware depends on [microReticulum](https://github.com/attermann/microReticulum) `0.2.4`, automatically fetched by PlatformIO on first build. After the first build, the library sources under `.pio/libdeps/heltec_V4_boundary/microReticulum/src/` need the patches described in "Routing & Memory Customizations" above. Key files modified:
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `Transport.cpp` | Selective caching, default route forwarding, boundary-aware culling, `get_cached_packet()` unpack fix, memory limits |
|
||||
| `Transport.h` | `MODE_BOUNDARY`, `PacketEntry`, `Callbacks`, `cull_path_table()`, configurable table sizes |
|
||||
| `Identity.cpp` | `_known_destinations_maxsize` = 24, `cull_known_destinations()` |
|
||||
| `Type.h` | `MODE_BOUNDARY` = 0x20, reduced `MAX_QUEUED_ANNOUNCES`, `MAX_RECEIPTS`, shorter timeouts |
|
||||
|
||||
### Memory Usage (typical)
|
||||
|
||||
| Resource | Used | Available |
|
||||
|----------|------|-----------|
|
||||
| RAM | ~21.7% | 320 KB |
|
||||
| Flash | ~18.1% | 16 MB |
|
||||
| PSRAM | Dynamic | 2 MB |
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **GNU General Public License v3.0** — see [LICENSE](LICENSE) for details.
|
||||
|
||||
Based on:
|
||||
- [RNode Firmware](https://github.com/markqvist/RNode_Firmware) by Mark Qvist (GPL-3.0)
|
||||
- [microReticulum](https://github.com/attermann/microReticulum) by Chris Attermann (GPL-3.0)
|
||||
- [Reticulum](https://reticulum.network/) by Mark Qvist (MIT)
|
||||
|
||||
2569
RNode_Firmware.ino
Executable file
63
ROM.h
Executable file
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2024, Mark Qvist
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef ROM_H
|
||||
#define ROM_H
|
||||
#define CHECKSUMMED_SIZE 0x0B
|
||||
|
||||
// ROM address map ///////////////
|
||||
#define ADDR_PRODUCT 0x00
|
||||
#define ADDR_MODEL 0x01
|
||||
#define ADDR_HW_REV 0x02
|
||||
#define ADDR_SERIAL 0x03
|
||||
#define ADDR_MADE 0x07
|
||||
#define ADDR_CHKSUM 0x0B
|
||||
#define ADDR_SIGNATURE 0x1B
|
||||
#define ADDR_INFO_LOCK 0x9B
|
||||
|
||||
#define ADDR_CONF_SF 0x9C
|
||||
#define ADDR_CONF_CR 0x9D
|
||||
#define ADDR_CONF_TXP 0x9E
|
||||
#define ADDR_CONF_BW 0x9F
|
||||
#define ADDR_CONF_FREQ 0xA3
|
||||
#define ADDR_CONF_OK 0xA7
|
||||
|
||||
#define ADDR_CONF_BT 0xB0
|
||||
#define ADDR_CONF_DSET 0xB1
|
||||
#define ADDR_CONF_DINT 0xB2
|
||||
#define ADDR_CONF_DADR 0xB3
|
||||
#define ADDR_CONF_DBLK 0xB4
|
||||
#define ADDR_CONF_DROT 0xB8
|
||||
#define ADDR_CONF_PSET 0xB5
|
||||
#define ADDR_CONF_PINT 0xB6
|
||||
#define ADDR_CONF_BSET 0xB7
|
||||
#define ADDR_CONF_DIA 0xB9
|
||||
#define ADDR_CONF_WIFI 0xBA
|
||||
#define ADDR_CONF_WCHN 0xBB
|
||||
|
||||
#define INFO_LOCK_BYTE 0x73
|
||||
#define CONF_OK_BYTE 0x73
|
||||
#define BT_ENABLE_BYTE 0x73
|
||||
|
||||
#define EEPROM_RESERVED 200
|
||||
|
||||
#define CONFIG_SIZE 256
|
||||
#define ADDR_CONF_SSID 0x00
|
||||
#define ADDR_CONF_PSK 0x21
|
||||
#define ADDR_CONF_IP 0x42
|
||||
#define ADDR_CONF_NM 0x46
|
||||
//////////////////////////////////
|
||||
|
||||
#endif
|
||||
10
Release/README.md
Executable file
@@ -0,0 +1,10 @@
|
||||
# Precompiled Firmware
|
||||
You can download and flash the firmware to supported boards using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil). All firmware releases are now handled and installed directly through `rnodeconf`, which is inclueded in the `rns` package. It can be installed via `pip`:
|
||||
|
||||
```
|
||||
# Install rnodeconf via rns package
|
||||
pip install rns --upgrade
|
||||
|
||||
# Install the firmware on a board with the install guide
|
||||
rnodeconf --autoinstall
|
||||
```
|
||||