Compare commits
No commits in common. "false_preamble" and "revert-4-patch-1" have entirely different histories.
false_prea
...
revert-4-p
11
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,11 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ✨ Feature Request or Idea
|
||||
url: https://github.com/markqvist/Reticulum/discussions/new?category=ideas
|
||||
about: Propose and discuss features and ideas
|
||||
- name: 💬 Questions, Help & Discussion
|
||||
about: Ask anything, or get help
|
||||
url: https://github.com/markqvist/Reticulum/discussions/new/choose
|
||||
- name: 📖 Read the Reticulum Manual
|
||||
url: https://markqvist.github.io/Reticulum/manual/
|
||||
about: The complete documentation for Reticulum
|
||||
39
.github/ISSUE_TEMPLATE/🐛-bug-report.md
vendored
|
|
@ -1,39 +0,0 @@
|
|||
---
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Report a reproducible bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Read the Contribution Guidelines**
|
||||
Before creating a bug report on this issue tracker, you **must** read the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md). Issues that do not follow the contribution guidelines **will be deleted without comment**.
|
||||
|
||||
- The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**.
|
||||
- Ideas and feature requests can be made on the [Discussions](https://github.com/markqvist/Reticulum/discussions). **Only** feature requests accepted by maintainers and developers are tracked and included on the issue tracker. **Do not post feature requests here**.
|
||||
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), delete this section from your bug report.
|
||||
|
||||
**Describe the Bug**
|
||||
First of all: Is this really a bug? Is it reproducible?
|
||||
|
||||
If this is a request for help because something is not working as you expected, stop right here, and go to the [discussions](https://github.com/markqvist/Reticulum/discussions) instead, where you can post your questions and get help from other users.
|
||||
|
||||
If this really is a bug or issue with the software, remove this section of the template, and provide **a clear and concise description of what the bug is**.
|
||||
|
||||
**To Reproduce**
|
||||
Describe in detail how to reproduce the bug.
|
||||
|
||||
**Expected Behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs & Screenshots**
|
||||
Please include any relevant log output. If applicable, also add screenshots to help explain your problem. In most cases, without any relevant log output, we will not be able to determine the cause of the bug, or reproduce it.
|
||||
|
||||
**System Information**
|
||||
- OS and version
|
||||
- Python version
|
||||
- Program version
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
6
.gitignore
vendored
|
|
@ -2,8 +2,4 @@
|
|||
*.hex
|
||||
*.pyc
|
||||
TODO
|
||||
Release/*.hex
|
||||
Release/*.zip
|
||||
Release/*.json
|
||||
Console/build
|
||||
build/*
|
||||
|
||||
|
|
|
|||
152
BLESerial.cpp
|
|
@ -1,152 +0,0 @@
|
|||
// 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::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();
|
||||
|
||||
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::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
|
||||
133
BLESerial.h
|
|
@ -1,133 +0,0 @@
|
|||
// 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 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
|
||||
559
Bluetooth.h
|
|
@ -1,559 +0,0 @@
|
|||
// 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;
|
||||
#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
|
||||
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_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);
|
||||
bt_ssp_pin = passkey;
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
kiss_indicate_btpin();
|
||||
}
|
||||
|
||||
bool bt_confirm_pin_callback(uint32_t pin) {
|
||||
// Serial.printf("Confirm PIN callback: %d\n", pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
void bt_security_setup() {
|
||||
uint32_t passkey = bt_passkey_callback();
|
||||
|
||||
// Serial.printf("Executing BT security setup, 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));
|
||||
}
|
||||
|
||||
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;
|
||||
bt_state = BT_STATE_CONNECTED;
|
||||
} else {
|
||||
// Serial.println("Authentication fail");
|
||||
ble_authenticated = false;
|
||||
bt_state = BT_STATE_ON;
|
||||
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;
|
||||
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() {
|
||||
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_start() {
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) {
|
||||
bt_state = BT_STATE_ON;
|
||||
SerialBT.begin(bt_devname);
|
||||
SerialBT.setTimeout(10);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_stop() {
|
||||
display_unblank();
|
||||
if (bt_state != BT_STATE_OFF) {
|
||||
bt_allow_pairing = false;
|
||||
bt_state = BT_STATE_OFF;
|
||||
SerialBT.end();
|
||||
}
|
||||
}
|
||||
|
||||
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 bt_enable_pairing() {
|
||||
display_unblank();
|
||||
if (bt_state == BT_STATE_OFF) bt_start();
|
||||
|
||||
bt_security_setup();
|
||||
//bt_debond_all();
|
||||
//bt_update_passkey();
|
||||
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
}
|
||||
|
||||
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
|
||||
uint8_t eeprom_read(uint32_t mapped_addr);
|
||||
|
||||
void 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() {
|
||||
bt_allow_pairing = false;
|
||||
bt_ssp_pin = 0;
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
|
||||
void bt_pairing_complete(uint16_t conn_handle, uint8_t auth_status) {
|
||||
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
|
||||
BLEConnection* connection = Bluefruit.Connection(conn_handle);
|
||||
|
||||
ble_gap_conn_sec_mode_t security = connection->getSecureMode();
|
||||
|
||||
// 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) {
|
||||
bt_state = BT_STATE_CONNECTED;
|
||||
cable_state = CABLE_STATE_DISCONNECTED;
|
||||
bt_disable_pairing();
|
||||
} else {
|
||||
if (connection->bonded()) {
|
||||
connection->removeBondKey();
|
||||
}
|
||||
connection->disconnect();
|
||||
}
|
||||
} else {
|
||||
bt_ssp_pin = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// multiply by tens however many times needed to make numbers appear in order
|
||||
bt_ssp_pin += ((int)passkey[i] - 48) * pow(10, 5-i);
|
||||
}
|
||||
kiss_indicate_btpin();
|
||||
if (bt_allow_pairing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void bt_connect_callback(uint16_t conn_handle) {
|
||||
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) {
|
||||
if (reason != BLE_GAP_SEC_STATUS_SUCCESS) {
|
||||
bt_state = BT_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_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()) {
|
||||
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.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() {
|
||||
if (bt_state == BT_STATE_OFF) {
|
||||
Bluefruit.setName(bt_devname);
|
||||
bledis.setManufacturer(BLE_MANUFACTURER);
|
||||
bledis.setModel(BLE_MODEL);
|
||||
// start device information service
|
||||
bledis.begin();
|
||||
|
||||
SerialBT.bufferTXD(true); // enable buffering
|
||||
|
||||
SerialBT.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // enable encryption for BLE serial
|
||||
SerialBT.begin();
|
||||
|
||||
blebas.begin();
|
||||
|
||||
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() {
|
||||
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() {
|
||||
if (bt_state == BT_STATE_OFF) bt_start();
|
||||
bt_allow_pairing = true;
|
||||
bt_pairing_started = millis();
|
||||
bt_state = BT_STATE_PAIRING;
|
||||
}
|
||||
|
||||
void update_bt() {
|
||||
if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) {
|
||||
bt_disable_pairing();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
818
Boards.h
|
|
@ -1,818 +0,0 @@
|
|||
// 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_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_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_TCXO false
|
||||
#define HAS_PMU false
|
||||
#define HAS_NP false
|
||||
#define HAS_EEPROM false
|
||||
#define HAS_INPUT false
|
||||
#define HAS_SLEEP 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 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 = 36;
|
||||
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 0x38
|
||||
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 = 36;
|
||||
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_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 0x38
|
||||
|
||||
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_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;
|
||||
const int SD_MISO = 2;
|
||||
const int SD_MOSI = 15;
|
||||
const int SD_CLK = 14;
|
||||
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_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_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 0x38
|
||||
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_CONSOLE 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
|
||||
|
||||
#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 0x38
|
||||
#endif
|
||||
|
||||
#ifndef NP_M
|
||||
#define NP_M 0.15
|
||||
#endif
|
||||
|
||||
#endif
|
||||
264
Config.h
|
|
@ -1,224 +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/>.
|
||||
|
||||
#include "ROM.h"
|
||||
#include "Boards.h"
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define MAJ_VERS 0x01
|
||||
#define MIN_VERS 0x51
|
||||
#define MIN_VERS 0x0A
|
||||
|
||||
#define MCU_328P 0x90
|
||||
#define MCU_1284P 0x91
|
||||
|
||||
#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;
|
||||
#if defined(__AVR_ATmega328P__)
|
||||
#define MCU_VARIANT MCU_328P
|
||||
#warning "Firmware is being compiled for atmega328p based boards"
|
||||
#elif defined(__AVR_ATmega1284P__)
|
||||
#define MCU_VARIANT MCU_1284P
|
||||
#warning "Firmware is being compiled for atmega1284p based boards"
|
||||
#else
|
||||
#error "The firmware cannot be compiled for the selected MCU variant"
|
||||
#endif
|
||||
|
||||
#define M_FRQ_S 27388122
|
||||
#define M_FRQ_R 27388061
|
||||
bool console_active = false;
|
||||
bool modem_installed = false;
|
||||
|
||||
#define MTU 508
|
||||
#define MTU 500
|
||||
#define SINGLE_MTU 255
|
||||
#define HEADER_L 1
|
||||
#define MIN_L 1
|
||||
#define CMD_L 64
|
||||
#define CMD_L 4
|
||||
|
||||
bool mw_radio_online = false;
|
||||
// MCU dependent configuration parameters
|
||||
#if MCU_VARIANT == MCU_328P
|
||||
const int pin_cs = 7;
|
||||
const int pin_reset = 6;
|
||||
const int pin_dio = 2;
|
||||
const int pin_led_rx = 5;
|
||||
const int pin_led_tx = 4;
|
||||
|
||||
#define FLOW_CONTROL_ENABLED true
|
||||
#define QUEUE_SIZE 0
|
||||
|
||||
#define EEPROM_SIZE 512
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
#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 FLOW_CONTROL_ENABLED true
|
||||
#define QUEUE_SIZE 24
|
||||
#define QUEUE_BUF_SIZE (QUEUE_SIZE+1)
|
||||
#define QUEUE_MEM QUEUE_BUF_SIZE * MTU
|
||||
|
||||
#define EEPROM_SIZE 4096
|
||||
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||
#endif
|
||||
|
||||
#define eeprom_addr(a) (a+EEPROM_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;
|
||||
const int rssi_offset = 292;
|
||||
|
||||
// SX1276 RSSI offset to get dBm value from
|
||||
// packet RSSI register
|
||||
const int rssi_offset = 157;
|
||||
const int lora_rx_turnaround_ms = 50;
|
||||
|
||||
// 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
|
||||
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;
|
||||
|
||||
// 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 12
|
||||
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;
|
||||
int lora_sf = 0;
|
||||
int lora_cr = 5;
|
||||
int lora_txp = 0xFF;
|
||||
uint32_t lora_bw = 0;
|
||||
uint32_t lora_freq = 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;
|
||||
bool radio_locked = true;
|
||||
bool radio_online = false;
|
||||
bool hw_ready = false;
|
||||
bool promisc = false;
|
||||
|
||||
uint8_t op_mode = MODE_HOST;
|
||||
uint8_t model = 0x00;
|
||||
uint8_t hwrev = 0x00;
|
||||
|
||||
#define NOISE_FLOOR_SAMPLES 64
|
||||
int noise_floor = -292;
|
||||
int current_rssi = -292;
|
||||
|
||||
int last_rssi = -292;
|
||||
uint8_t last_rssi_raw = 0x00;
|
||||
uint8_t last_snr_raw = 0x80;
|
||||
size_t read_len = 0;
|
||||
uint8_t seq = 0xFF;
|
||||
uint16_t read_len = 0;
|
||||
|
||||
// Incoming packet buffer
|
||||
uint8_t pbuf[MTU];
|
||||
uint8_t sbuf[MTU];
|
||||
uint8_t cbuf[CMD_L];
|
||||
|
||||
// KISS command buffer
|
||||
uint8_t cmdbuf[CMD_L];
|
||||
|
||||
// LoRa transmit buffer
|
||||
uint8_t tbuf[MTU];
|
||||
#if QUEUE_SIZE > 0
|
||||
uint8_t tbuf[MTU];
|
||||
uint8_t qbuf[QUEUE_MEM];
|
||||
size_t queued_lengths[QUEUE_BUF_SIZE];
|
||||
#endif
|
||||
|
||||
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 outbound_ready = false;
|
||||
size_t queue_head = 0;
|
||||
size_t queue_tail = 0;
|
||||
|
||||
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;
|
||||
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;
|
||||
uint16_t dcd_count = 0;
|
||||
uint16_t dcd_threshold = 15;
|
||||
|
||||
uint32_t status_interval_ms = STATUS_INTERVAL_MS;
|
||||
uint32_t status_interval_ms = 3;
|
||||
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;
|
||||
// Status flags
|
||||
const uint8_t SIG_DETECT = 0x01;
|
||||
const uint8_t SIG_SYNCED = 0x02;
|
||||
const uint8_t RX_ONGOING = 0x04;
|
||||
|
||||
// Boot flags
|
||||
#define START_FROM_BOOTLOADER 0x01
|
||||
#define START_FROM_POWERON 0x02
|
||||
#define START_FROM_BROWNOUT 0x03
|
||||
#define START_FROM_JTAG 0x04
|
||||
|
||||
#endif
|
||||
#endif
|
||||
203
Console.h
|
|
@ -1,203 +0,0 @@
|
|||
// 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);
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
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
|
||||
|
Before Width: | Height: | Size: 806 B |
|
Before Width: | Height: | Size: 955 B |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
355
Console/build.py
|
|
@ -1,355 +0,0 @@
|
|||
import markdown
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
packages = {
|
||||
"rns": "rns-0.8.9-py3-none-any.whl",
|
||||
"nomadnet": "nomadnet-0.5.6-py3-none-any.whl",
|
||||
"lxmf": "lxmf-0.5.8-py3-none-any.whl",
|
||||
"rnsh": "rnsh-0.1.4-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><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=["markdown.extensions.fenced_code", "sane_lists"]).replace("<p></p>", "")
|
||||
page_html = markdown.markdown(md, extensions=["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 = 45
|
||||
scale_imgs = [
|
||||
("_images/board_rnodev2.png", pm),
|
||||
("_images/board_rnode.png", pm),
|
||||
("_images/board_heltec32v20.png", pm),
|
||||
("_images/board_heltec32v30.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
|
||||
resize = "convert "+fp+" -quality 25 -resize "+str(s)+" "+fp
|
||||
print(resize)
|
||||
subprocess.call(shlex.split(resize), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
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",
|
||||
# "_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()
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[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!
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
[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!
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[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!
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[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!
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
[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>
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
[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)
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
[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**.
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
[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.
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
[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.)
|
||||
<div class="article_date">{DATE}</div>
|
||||
# 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** or **Semtech SX1278** LoRa transceiver IC. These come in several different variants, for all frequency bands from about 150 MHz to about 1100 MHz.
|
||||
- Support for **SX1262**, **SX1268** and **SX1280**-based modules is coming soon, but until that is released, only **SX1276** and **SX1278** modules will work.
|
||||
- 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** 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` file of the [RNode Firmware]({ASSET_PATH}pkg/rnode_firmware.zip).
|
||||
|
||||
## 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]({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.
|
||||
|
||||
> *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.
|
||||
|
||||
> **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`.
|
||||
|
||||
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).
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
[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.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
[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)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
## 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>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
[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}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
[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).
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[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}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
[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>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
[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.
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
[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`
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
[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`
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
[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).
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
[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`
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
[title]: <> (Sideband)
|
||||
## Sideband
|
||||
Sideband is an LXMF client for Android, Linux 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 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)
|
||||
- The [IzzyOnDroid repository for F-Droid](https://android.izzysoft.de/repo/apk/io.unsigned.sideband)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
[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.
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
[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>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
[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
|
|
@ -1,267 +0,0 @@
|
|||
// 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
|
||||
BIN
Documentation/RNode_Manual.pdf
Normal file
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 112 KiB |
131
Fonts/Org_01.h
|
|
@ -1,131 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
#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
|
||||
148
Framing.h
|
|
@ -1,110 +1,62 @@
|
|||
// 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 FRAMING_H
|
||||
|
||||
#define FEND 0xC0
|
||||
#define FESC 0xDB
|
||||
#define TFEND 0xDC
|
||||
#define TFESC 0xDD
|
||||
#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_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_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_BLINK 0x30
|
||||
#define CMD_RANDOM 0x40
|
||||
#define CMD_STAT_RX 0x21
|
||||
#define CMD_STAT_TX 0x22
|
||||
#define CMD_STAT_RSSI 0x23
|
||||
#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_PIN 0x62
|
||||
#define CMD_DIS_IA 0x69
|
||||
#define CMD_FW_VERSION 0x50
|
||||
#define CMD_ROM_READ 0x51
|
||||
#define CMD_ROM_WRITE 0x52
|
||||
#define CMD_CONF_SAVE 0x53
|
||||
#define CMD_CONF_DELETE 0x54
|
||||
#define CMD_UNLOCK_ROM 0x59
|
||||
#define ROM_UNLOCK_BYTE 0xF8
|
||||
|
||||
#define CMD_BOARD 0x47
|
||||
#define CMD_PLATFORM 0x48
|
||||
#define CMD_MCU 0x49
|
||||
#define CMD_FW_VERSION 0x50
|
||||
#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 DETECT_REQ 0x73
|
||||
#define DETECT_RESP 0x46
|
||||
|
||||
#define DETECT_REQ 0x73
|
||||
#define DETECT_RESP 0x46
|
||||
#define RADIO_STATE_OFF 0x00
|
||||
#define RADIO_STATE_ON 0x01
|
||||
|
||||
#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 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 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;
|
||||
// Serial framing variables
|
||||
size_t frame_len;
|
||||
bool IN_FRAME = false;
|
||||
bool ESCAPE = false;
|
||||
bool SERIAL_READING = false;
|
||||
uint8_t command = CMD_UNKNOWN;
|
||||
uint32_t last_serial_read = 0;
|
||||
uint32_t serial_read_timeout_ms = 60;
|
||||
|
||||
#endif
|
||||
434
Graphics.h
|
|
@ -1,434 +0,0 @@
|
|||
// 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_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
|
|
@ -1,95 +0,0 @@
|
|||
// 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
|
||||
695
LICENSE
|
|
@ -1,674 +1,21 @@
|
|||
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>.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 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.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
# 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.
|
||||
# RNode in a Python program. First we'll
|
||||
# import the RNodeInterface class.
|
||||
from RNode import RNodeInterface
|
||||
|
||||
# We'll also define which serial port the
|
||||
|
|
@ -16,10 +11,7 @@ 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")
|
||||
print "Received a packet: "+data
|
||||
|
||||
# Create an RNode instance. This configures
|
||||
# and powers up the radio.
|
||||
|
|
@ -36,12 +28,10 @@ rnode = RNodeInterface(
|
|||
|
||||
# Enter a loop waiting for user input.
|
||||
try:
|
||||
print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit")
|
||||
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)
|
||||
raw_input()
|
||||
rnode.send("Hello World!")
|
||||
except KeyboardInterrupt as e:
|
||||
print("")
|
||||
print ""
|
||||
exit()
|
||||
479
Libraries/RNode.py
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
from __future__ import print_function
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import math
|
||||
|
||||
class KISS():
|
||||
FEND = chr(0xC0)
|
||||
FESC = chr(0xDB)
|
||||
TFEND = chr(0xDC)
|
||||
TFESC = chr(0xDD)
|
||||
|
||||
CMD_UNKNOWN = chr(0xFE)
|
||||
CMD_DATA = chr(0x00)
|
||||
CMD_FREQUENCY = chr(0x01)
|
||||
CMD_BANDWIDTH = chr(0x02)
|
||||
CMD_TXPOWER = chr(0x03)
|
||||
CMD_SF = chr(0x04)
|
||||
CMD_CR = chr(0x05)
|
||||
CMD_RADIO_STATE = chr(0x06)
|
||||
CMD_RADIO_LOCK = chr(0x07)
|
||||
CMD_DETECT = chr(0x08)
|
||||
CMD_PROMISC = chr(0x0E)
|
||||
CMD_READY = chr(0x0F)
|
||||
CMD_STAT_RX = chr(0x21)
|
||||
CMD_STAT_TX = chr(0x22)
|
||||
CMD_STAT_RSSI = chr(0x23)
|
||||
CMD_BLINK = chr(0x30)
|
||||
CMD_RANDOM = chr(0x40)
|
||||
CMD_FW_VERSION = chr(0x50)
|
||||
CMD_ROM_READ = chr(0x51)
|
||||
|
||||
DETECT_REQ = chr(0x73)
|
||||
DETECT_RESP = chr(0x46)
|
||||
|
||||
RADIO_STATE_OFF = chr(0x00)
|
||||
RADIO_STATE_ON = chr(0x01)
|
||||
RADIO_STATE_ASK = chr(0xFF)
|
||||
|
||||
CMD_ERROR = chr(0x90)
|
||||
ERROR_INITRADIO = chr(0x01)
|
||||
ERROR_TXFAILED = chr(0x02)
|
||||
ERROR_EEPROM_LOCKED = chr(0x03)
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd))
|
||||
data = data.replace(chr(0xc0), chr(0xdb)+chr(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
|
||||
|
||||
RSSI_OFFSET = 292
|
||||
|
||||
def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = -1, flow_control = True):
|
||||
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.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_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 (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 actual 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.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(chr(c1)+chr(c2)+chr(c3)+chr(c4))
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_FREQUENCY+data+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(chr(c1)+chr(c2)+chr(c3)+chr(c4))
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+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 = chr(self.txpower)
|
||||
kiss_command = KISS.FEND+KISS.CMD_TXPOWER+txp+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 = chr(self.sf)
|
||||
kiss_command = KISS.FEND+KISS.CMD_SF+sf+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 = chr(self.cr)
|
||||
kiss_command = KISS.FEND+KISS.CMD_CR+cr+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 = KISS.FEND+KISS.CMD_RADIO_STATE+state+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 = KISS.FEND+KISS.CMD_PROMISC+chr(0x01)+KISS.FEND
|
||||
else:
|
||||
kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(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.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
|
||||
|
||||
data = KISS.escape(data)
|
||||
frame = chr(0xc0)+chr(0x00)+data+chr(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 = ""
|
||||
command_buffer = ""
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
while self.serial.is_open:
|
||||
if self.serial.in_waiting:
|
||||
byte = 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 = ""
|
||||
command_buffer = ""
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = ""
|
||||
command_buffer = ""
|
||||
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+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+byte
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_frequency = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(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+byte
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_bandwidth = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(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 = ord(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 = ord(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 = ord(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 = ord(byte)
|
||||
elif (command == KISS.CMD_RADIO_LOCK):
|
||||
self.r_lock = ord(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+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+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 = ord(byte)-self.RSSI_OFFSET
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = ord(byte)
|
||||
elif (command == KISS.CMD_ERROR):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
self.log(str(self)+" hardware initialisation error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
self.log(str(self)+" hardware TX error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
else:
|
||||
self.log(str(self)+" hardware error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
|
||||
elif (command == KISS.CMD_READY):
|
||||
# TODO: add timeout and reset if ready
|
||||
# command never arrives
|
||||
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 = ""
|
||||
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+"]"
|
||||
|
||||
617
LoRa.cpp
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
// Copyright (c) Sandeep Mistry. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Modifications and additions copyright 2018 by Mark Qvist
|
||||
// Obviously still under the MIT license.
|
||||
|
||||
#include "LoRa.h"
|
||||
|
||||
// Registers
|
||||
#define REG_FIFO 0x00
|
||||
#define REG_OP_MODE 0x01
|
||||
#define REG_FRF_MSB 0x06
|
||||
#define REG_FRF_MID 0x07
|
||||
#define REG_FRF_LSB 0x08
|
||||
#define REG_PA_CONFIG 0x09
|
||||
#define REG_LNA 0x0c
|
||||
#define REG_FIFO_ADDR_PTR 0x0d
|
||||
#define REG_FIFO_TX_BASE_ADDR 0x0e
|
||||
#define REG_FIFO_RX_BASE_ADDR 0x0f
|
||||
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
||||
#define REG_IRQ_FLAGS 0x12
|
||||
#define REG_RX_NB_BYTES 0x13
|
||||
#define REG_MODEM_STAT 0x18
|
||||
#define REG_PKT_SNR_VALUE 0x19
|
||||
#define REG_PKT_RSSI_VALUE 0x1a
|
||||
#define REG_MODEM_CONFIG_1 0x1d
|
||||
#define REG_MODEM_CONFIG_2 0x1e
|
||||
#define REG_PREAMBLE_MSB 0x20
|
||||
#define REG_PREAMBLE_LSB 0x21
|
||||
#define REG_PAYLOAD_LENGTH 0x22
|
||||
#define REG_MODEM_CONFIG_3 0x26
|
||||
#define REG_FREQ_ERROR_MSB 0x28
|
||||
#define REG_FREQ_ERROR_MID 0x29
|
||||
#define REG_FREQ_ERROR_LSB 0x2a
|
||||
#define REG_RSSI_WIDEBAND 0x2c
|
||||
#define REG_DETECTION_OPTIMIZE 0x31
|
||||
#define REG_DETECTION_THRESHOLD 0x37
|
||||
#define REG_SYNC_WORD 0x39
|
||||
#define REG_DIO_MAPPING_1 0x40
|
||||
#define REG_VERSION 0x42
|
||||
|
||||
// Modes
|
||||
#define MODE_LONG_RANGE_MODE 0x80
|
||||
#define MODE_SLEEP 0x00
|
||||
#define MODE_STDBY 0x01
|
||||
#define MODE_TX 0x03
|
||||
#define MODE_RX_CONTINUOUS 0x05
|
||||
#define MODE_RX_SINGLE 0x06
|
||||
|
||||
// PA config
|
||||
#define PA_BOOST 0x80
|
||||
|
||||
// IRQ masks
|
||||
#define IRQ_TX_DONE_MASK 0x08
|
||||
#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
|
||||
#define IRQ_RX_DONE_MASK 0x40
|
||||
|
||||
#define MAX_PKT_LENGTH 255
|
||||
|
||||
LoRaClass::LoRaClass() :
|
||||
_spiSettings(8E6, MSBFIRST, SPI_MODE0),
|
||||
_ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN),
|
||||
_frequency(0),
|
||||
_packetIndex(0),
|
||||
_implicitHeaderMode(0),
|
||||
_onReceive(NULL)
|
||||
{
|
||||
// overide Stream timeout value
|
||||
setTimeout(0);
|
||||
}
|
||||
|
||||
int LoRaClass::begin(long frequency)
|
||||
{
|
||||
// setup pins
|
||||
pinMode(_ss, OUTPUT);
|
||||
// set SS high
|
||||
digitalWrite(_ss, HIGH);
|
||||
|
||||
if (_reset != -1) {
|
||||
pinMode(_reset, OUTPUT);
|
||||
|
||||
// perform reset
|
||||
digitalWrite(_reset, LOW);
|
||||
delay(10);
|
||||
digitalWrite(_reset, HIGH);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// start SPI
|
||||
SPI.begin();
|
||||
|
||||
// check version
|
||||
uint8_t version = readRegister(REG_VERSION);
|
||||
if (version != 0x12) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// put in sleep mode
|
||||
sleep();
|
||||
|
||||
// set frequency
|
||||
setFrequency(frequency);
|
||||
|
||||
// set base addresses
|
||||
writeRegister(REG_FIFO_TX_BASE_ADDR, 0);
|
||||
writeRegister(REG_FIFO_RX_BASE_ADDR, 0);
|
||||
|
||||
// set LNA boost
|
||||
writeRegister(REG_LNA, readRegister(REG_LNA) | 0x03);
|
||||
|
||||
// set auto AGC
|
||||
writeRegister(REG_MODEM_CONFIG_3, 0x04);
|
||||
|
||||
// set output power to 2 dBm
|
||||
setTxPower(2);
|
||||
|
||||
// put in standby mode
|
||||
idle();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LoRaClass::end()
|
||||
{
|
||||
// put in sleep mode
|
||||
sleep();
|
||||
|
||||
// stop SPI
|
||||
SPI.end();
|
||||
}
|
||||
|
||||
int LoRaClass::beginPacket(int implicitHeader)
|
||||
{
|
||||
// put in standby mode
|
||||
idle();
|
||||
|
||||
if (implicitHeader) {
|
||||
implicitHeaderMode();
|
||||
} else {
|
||||
explicitHeaderMode();
|
||||
}
|
||||
|
||||
// reset FIFO address and paload length
|
||||
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
||||
writeRegister(REG_PAYLOAD_LENGTH, 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LoRaClass::endPacket()
|
||||
{
|
||||
// put in TX mode
|
||||
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);
|
||||
|
||||
// wait for TX done
|
||||
while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {
|
||||
yield();
|
||||
}
|
||||
|
||||
// clear IRQ's
|
||||
writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LoRaClass::parsePacket(int size)
|
||||
{
|
||||
int packetLength = 0;
|
||||
int irqFlags = readRegister(REG_IRQ_FLAGS);
|
||||
|
||||
if (size > 0) {
|
||||
implicitHeaderMode();
|
||||
|
||||
writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);
|
||||
} else {
|
||||
explicitHeaderMode();
|
||||
}
|
||||
|
||||
// clear IRQ's
|
||||
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
||||
|
||||
if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
|
||||
// received a packet
|
||||
_packetIndex = 0;
|
||||
|
||||
// read packet length
|
||||
if (_implicitHeaderMode) {
|
||||
packetLength = readRegister(REG_PAYLOAD_LENGTH);
|
||||
} else {
|
||||
packetLength = readRegister(REG_RX_NB_BYTES);
|
||||
}
|
||||
|
||||
// set FIFO address to current RX address
|
||||
writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
|
||||
|
||||
// put in standby mode
|
||||
idle();
|
||||
} else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {
|
||||
// not currently in RX mode
|
||||
|
||||
// reset FIFO address
|
||||
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
||||
|
||||
// put in single RX mode
|
||||
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);
|
||||
}
|
||||
|
||||
return packetLength;
|
||||
}
|
||||
|
||||
uint8_t LoRaClass::modemStatus() {
|
||||
return readRegister(REG_MODEM_STAT);
|
||||
}
|
||||
|
||||
uint8_t LoRaClass::packetRssiRaw() {
|
||||
uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE);
|
||||
return pkt_rssi_value;
|
||||
}
|
||||
|
||||
int LoRaClass::packetRssi() {
|
||||
int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE);
|
||||
// TODO: change this to look at the actual model code
|
||||
if (_frequency < 820E6) pkt_rssi -= 7;
|
||||
pkt_rssi -= 157;
|
||||
|
||||
return pkt_rssi;
|
||||
}
|
||||
|
||||
float LoRaClass::packetSnr()
|
||||
{
|
||||
return ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25;
|
||||
}
|
||||
|
||||
long LoRaClass::packetFrequencyError()
|
||||
{
|
||||
int32_t freqError = 0;
|
||||
freqError = static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MSB) & B111);
|
||||
freqError <<= 8L;
|
||||
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MID));
|
||||
freqError <<= 8L;
|
||||
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_LSB));
|
||||
|
||||
if (readRegister(REG_FREQ_ERROR_MSB) & B1000) { // Sign bit is on
|
||||
freqError -= 524288; // B1000'0000'0000'0000'0000
|
||||
}
|
||||
|
||||
const float fXtal = 32E6; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
|
||||
const float fError = ((static_cast<float>(freqError) * (1L << 24)) / fXtal) * (getSignalBandwidth() / 500000.0f); // p. 37
|
||||
|
||||
return static_cast<long>(fError);
|
||||
}
|
||||
|
||||
size_t LoRaClass::write(uint8_t byte)
|
||||
{
|
||||
return write(&byte, sizeof(byte));
|
||||
}
|
||||
|
||||
size_t LoRaClass::write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
int currentLength = readRegister(REG_PAYLOAD_LENGTH);
|
||||
|
||||
// check size
|
||||
if ((currentLength + size) > MAX_PKT_LENGTH) {
|
||||
size = MAX_PKT_LENGTH - currentLength;
|
||||
}
|
||||
|
||||
// write data
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
writeRegister(REG_FIFO, buffer[i]);
|
||||
}
|
||||
|
||||
// update length
|
||||
writeRegister(REG_PAYLOAD_LENGTH, currentLength + size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int LoRaClass::available()
|
||||
{
|
||||
return (readRegister(REG_RX_NB_BYTES) - _packetIndex);
|
||||
}
|
||||
|
||||
int LoRaClass::read()
|
||||
{
|
||||
if (!available()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
_packetIndex++;
|
||||
|
||||
return readRegister(REG_FIFO);
|
||||
}
|
||||
|
||||
int LoRaClass::peek()
|
||||
{
|
||||
if (!available()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// store current FIFO address
|
||||
int currentAddress = readRegister(REG_FIFO_ADDR_PTR);
|
||||
|
||||
// read
|
||||
uint8_t b = readRegister(REG_FIFO);
|
||||
|
||||
// restore FIFO address
|
||||
writeRegister(REG_FIFO_ADDR_PTR, currentAddress);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void LoRaClass::flush()
|
||||
{
|
||||
}
|
||||
|
||||
void LoRaClass::onReceive(void(*callback)(int))
|
||||
{
|
||||
_onReceive = callback;
|
||||
|
||||
if (callback) {
|
||||
pinMode(_dio0, INPUT);
|
||||
|
||||
writeRegister(REG_DIO_MAPPING_1, 0x00);
|
||||
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
||||
SPI.usingInterrupt(digitalPinToInterrupt(_dio0));
|
||||
#endif
|
||||
attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
|
||||
} else {
|
||||
detachInterrupt(digitalPinToInterrupt(_dio0));
|
||||
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
||||
SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaClass::receive(int size)
|
||||
{
|
||||
if (size > 0) {
|
||||
implicitHeaderMode();
|
||||
|
||||
writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);
|
||||
} else {
|
||||
explicitHeaderMode();
|
||||
}
|
||||
|
||||
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);
|
||||
}
|
||||
|
||||
void LoRaClass::idle()
|
||||
{
|
||||
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
|
||||
}
|
||||
|
||||
void LoRaClass::sleep()
|
||||
{
|
||||
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);
|
||||
}
|
||||
|
||||
void LoRaClass::setTxPower(int level, int outputPin)
|
||||
{
|
||||
if (PA_OUTPUT_RFO_PIN == outputPin) {
|
||||
// RFO
|
||||
if (level < 0) {
|
||||
level = 0;
|
||||
} else if (level > 14) {
|
||||
level = 14;
|
||||
}
|
||||
|
||||
writeRegister(REG_PA_CONFIG, 0x70 | level);
|
||||
} else {
|
||||
// PA BOOST
|
||||
if (level < 2) {
|
||||
level = 2;
|
||||
} else if (level > 17) {
|
||||
level = 17;
|
||||
}
|
||||
|
||||
writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaClass::setFrequency(long frequency) {
|
||||
_frequency = frequency;
|
||||
|
||||
uint32_t frf = ((uint64_t)frequency << 19) / 32000000;
|
||||
|
||||
writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16));
|
||||
writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8));
|
||||
writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0));
|
||||
}
|
||||
|
||||
uint32_t LoRaClass::getFrequency() {
|
||||
uint8_t msb = readRegister(REG_FRF_MSB);
|
||||
uint8_t mid = readRegister(REG_FRF_MID);
|
||||
uint8_t lsb = readRegister(REG_FRF_LSB);
|
||||
|
||||
uint32_t frf = ((uint32_t)msb << 16) | ((uint32_t)mid << 8) | (uint32_t)lsb;
|
||||
uint64_t frm = (uint64_t)frf*32000000;
|
||||
uint32_t frequency = (frm >> 19);
|
||||
|
||||
return frequency;
|
||||
}
|
||||
|
||||
void LoRaClass::setSpreadingFactor(int sf)
|
||||
{
|
||||
if (sf < 6) {
|
||||
sf = 6;
|
||||
} else if (sf > 12) {
|
||||
sf = 12;
|
||||
}
|
||||
|
||||
if (sf == 6) {
|
||||
writeRegister(REG_DETECTION_OPTIMIZE, 0xc5);
|
||||
writeRegister(REG_DETECTION_THRESHOLD, 0x0c);
|
||||
} else {
|
||||
writeRegister(REG_DETECTION_OPTIMIZE, 0xc3);
|
||||
writeRegister(REG_DETECTION_THRESHOLD, 0x0a);
|
||||
}
|
||||
|
||||
writeRegister(REG_MODEM_CONFIG_2, (readRegister(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0));
|
||||
|
||||
handleLowDataRate();
|
||||
}
|
||||
|
||||
long LoRaClass::getSignalBandwidth()
|
||||
{
|
||||
byte bw = (readRegister(REG_MODEM_CONFIG_1) >> 4);
|
||||
switch (bw) {
|
||||
case 0: return 7.8E3;
|
||||
case 1: return 10.4E3;
|
||||
case 2: return 15.6E3;
|
||||
case 3: return 20.8E3;
|
||||
case 4: return 31.25E3;
|
||||
case 5: return 41.7E3;
|
||||
case 6: return 62.5E3;
|
||||
case 7: return 125E3;
|
||||
case 8: return 250E3;
|
||||
case 9: return 500E3;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaClass::handleLowDataRate(){
|
||||
int sf = (readRegister(REG_MODEM_CONFIG_2) >> 4);
|
||||
if ( long( (1<<sf) / (getSignalBandwidth()/1000)) > 16) {
|
||||
// set auto AGC and LowDataRateOptimize
|
||||
writeRegister(REG_MODEM_CONFIG_3, (1<<3)|(1<<2));
|
||||
}
|
||||
else {
|
||||
// set auto AGC
|
||||
writeRegister(REG_MODEM_CONFIG_3, (1<<2));
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaClass::setSignalBandwidth(long sbw)
|
||||
{
|
||||
int bw;
|
||||
|
||||
if (sbw <= 7.8E3) {
|
||||
bw = 0;
|
||||
} else if (sbw <= 10.4E3) {
|
||||
bw = 1;
|
||||
} else if (sbw <= 15.6E3) {
|
||||
bw = 2;
|
||||
} else if (sbw <= 20.8E3) {
|
||||
bw = 3;
|
||||
} else if (sbw <= 31.25E3) {
|
||||
bw = 4;
|
||||
} else if (sbw <= 41.7E3) {
|
||||
bw = 5;
|
||||
} else if (sbw <= 62.5E3) {
|
||||
bw = 6;
|
||||
} else if (sbw <= 125E3) {
|
||||
bw = 7;
|
||||
} else if (sbw <= 250E3) {
|
||||
bw = 8;
|
||||
} else /*if (sbw <= 250E3)*/ {
|
||||
bw = 9;
|
||||
}
|
||||
|
||||
writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0x0f) | (bw << 4));
|
||||
|
||||
handleLowDataRate();
|
||||
}
|
||||
|
||||
void LoRaClass::setCodingRate4(int denominator)
|
||||
{
|
||||
if (denominator < 5) {
|
||||
denominator = 5;
|
||||
} else if (denominator > 8) {
|
||||
denominator = 8;
|
||||
}
|
||||
|
||||
int cr = denominator - 4;
|
||||
|
||||
writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1));
|
||||
}
|
||||
|
||||
void LoRaClass::setPreambleLength(long length)
|
||||
{
|
||||
writeRegister(REG_PREAMBLE_MSB, (uint8_t)(length >> 8));
|
||||
writeRegister(REG_PREAMBLE_LSB, (uint8_t)(length >> 0));
|
||||
}
|
||||
|
||||
void LoRaClass::setSyncWord(int sw)
|
||||
{
|
||||
writeRegister(REG_SYNC_WORD, sw);
|
||||
}
|
||||
|
||||
void LoRaClass::enableCrc()
|
||||
{
|
||||
writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04);
|
||||
}
|
||||
|
||||
void LoRaClass::disableCrc()
|
||||
{
|
||||
writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb);
|
||||
}
|
||||
|
||||
byte LoRaClass::random()
|
||||
{
|
||||
return readRegister(REG_RSSI_WIDEBAND);
|
||||
}
|
||||
|
||||
void LoRaClass::setPins(int ss, int reset, int dio0)
|
||||
{
|
||||
_ss = ss;
|
||||
_reset = reset;
|
||||
_dio0 = dio0;
|
||||
}
|
||||
|
||||
void LoRaClass::setSPIFrequency(uint32_t frequency)
|
||||
{
|
||||
_spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0);
|
||||
}
|
||||
|
||||
void LoRaClass::dumpRegisters(Stream& out)
|
||||
{
|
||||
for (int i = 0; i < 128; i++) {
|
||||
out.print("0x");
|
||||
out.print(i, HEX);
|
||||
out.print(": 0x");
|
||||
out.println(readRegister(i), HEX);
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaClass::explicitHeaderMode()
|
||||
{
|
||||
_implicitHeaderMode = 0;
|
||||
|
||||
writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe);
|
||||
}
|
||||
|
||||
void LoRaClass::implicitHeaderMode()
|
||||
{
|
||||
_implicitHeaderMode = 1;
|
||||
|
||||
writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01);
|
||||
}
|
||||
|
||||
void LoRaClass::handleDio0Rise()
|
||||
{
|
||||
int irqFlags = readRegister(REG_IRQ_FLAGS);
|
||||
|
||||
// clear IRQ's
|
||||
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
||||
|
||||
if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
|
||||
// received a packet
|
||||
_packetIndex = 0;
|
||||
|
||||
// read packet length
|
||||
int packetLength = _implicitHeaderMode ? readRegister(REG_PAYLOAD_LENGTH) : readRegister(REG_RX_NB_BYTES);
|
||||
|
||||
// set FIFO address to current RX address
|
||||
writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
|
||||
|
||||
if (_onReceive) {
|
||||
_onReceive(packetLength);
|
||||
}
|
||||
|
||||
// reset FIFO address
|
||||
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t LoRaClass::readRegister(uint8_t address)
|
||||
{
|
||||
return singleTransfer(address & 0x7f, 0x00);
|
||||
}
|
||||
|
||||
void LoRaClass::writeRegister(uint8_t address, uint8_t value)
|
||||
{
|
||||
singleTransfer(address | 0x80, value);
|
||||
}
|
||||
|
||||
uint8_t LoRaClass::singleTransfer(uint8_t address, uint8_t value)
|
||||
{
|
||||
uint8_t response;
|
||||
|
||||
digitalWrite(_ss, LOW);
|
||||
|
||||
SPI.beginTransaction(_spiSettings);
|
||||
SPI.transfer(address);
|
||||
response = SPI.transfer(value);
|
||||
SPI.endTransaction();
|
||||
|
||||
digitalWrite(_ss, HIGH);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void LoRaClass::onDio0Rise()
|
||||
{
|
||||
LoRa.handleDio0Rise();
|
||||
}
|
||||
|
||||
LoRaClass LoRa;
|
||||
|
|
@ -1,31 +1,22 @@
|
|||
// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva.
|
||||
// Licensed under the MIT license.
|
||||
// Copyright (c) Sandeep Mistry. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
#ifndef SX1276_H
|
||||
#define SX1276_H
|
||||
#ifndef LORA_H
|
||||
#define LORA_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include "Modem.h"
|
||||
|
||||
#define LORA_DEFAULT_SS_PIN 10
|
||||
#define LORA_DEFAULT_RESET_PIN 9
|
||||
#define LORA_DEFAULT_DIO0_PIN 2
|
||||
#define LORA_DEFAULT_BUSY_PIN -1
|
||||
|
||||
#define PA_OUTPUT_RFO_PIN 0
|
||||
#define PA_OUTPUT_PA_BOOST_PIN 1
|
||||
|
||||
#define RSSI_OFFSET 157
|
||||
|
||||
// Modem status flags
|
||||
#define SIG_DETECT 0x01
|
||||
#define SIG_SYNCED 0x02
|
||||
#define RX_ONGOING 0x04
|
||||
|
||||
class sx127x : public Stream {
|
||||
class LoRaClass : public Stream {
|
||||
public:
|
||||
sx127x();
|
||||
LoRaClass();
|
||||
|
||||
int begin(long frequency);
|
||||
void end();
|
||||
|
|
@ -35,11 +26,7 @@ public:
|
|||
|
||||
int parsePacket(int size = 0);
|
||||
int packetRssi();
|
||||
int packetRssi(uint8_t pkt_snr_raw);
|
||||
int currentRssi();
|
||||
uint8_t packetRssiRaw();
|
||||
uint8_t currentRssiRaw();
|
||||
uint8_t packetSnrRaw();
|
||||
float packetSnr();
|
||||
long packetFrequencyError();
|
||||
|
||||
|
|
@ -56,31 +43,33 @@ public:
|
|||
void onReceive(void(*callback)(int));
|
||||
|
||||
void receive(int size = 0);
|
||||
void standby();
|
||||
void idle();
|
||||
void sleep();
|
||||
|
||||
bool preInit();
|
||||
uint8_t getTxPower();
|
||||
void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
|
||||
uint32_t getFrequency();
|
||||
void setFrequency(unsigned long frequency);
|
||||
void setFrequency(long frequency);
|
||||
void setSpreadingFactor(int sf);
|
||||
long getSignalBandwidth();
|
||||
void setSignalBandwidth(long sbw);
|
||||
void setCodingRate4(int denominator);
|
||||
void setPreambleLength(long preamble_symbols);
|
||||
void setSyncWord(uint8_t sw);
|
||||
bool dcd();
|
||||
void setPreambleLength(long length);
|
||||
void setSyncWord(int sw);
|
||||
uint8_t modemStatus();
|
||||
void enableCrc();
|
||||
void disableCrc();
|
||||
void enableTCXO();
|
||||
void disableTCXO();
|
||||
|
||||
// deprecated
|
||||
void crc() { enableCrc(); }
|
||||
void noCrc() { disableCrc(); }
|
||||
|
||||
byte random();
|
||||
|
||||
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN);
|
||||
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
|
||||
void setSPIFrequency(uint32_t frequency);
|
||||
|
||||
void dumpRegisters(Stream& out);
|
||||
|
||||
private:
|
||||
void explicitHeaderMode();
|
||||
void implicitHeaderMode();
|
||||
|
|
@ -94,21 +83,18 @@ private:
|
|||
static void onDio0Rise();
|
||||
|
||||
void handleLowDataRate();
|
||||
void optimizeModemSensitivity();
|
||||
|
||||
private:
|
||||
SPISettings _spiSettings;
|
||||
int _ss;
|
||||
int _reset;
|
||||
int _dio0;
|
||||
int _busy;
|
||||
long _frequency;
|
||||
int _packetIndex;
|
||||
int _implicitHeaderMode;
|
||||
bool _preinit_done;
|
||||
void (*_onReceive)(int);
|
||||
};
|
||||
|
||||
extern sx127x sx127x_modem;
|
||||
extern LoRaClass LoRa;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
477
Makefile
|
|
@ -1,477 +0,0 @@
|
|||
# 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
|
||||
|
||||
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 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
|
||||
|
||||
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-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\""
|
||||
|
||||
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/ttyUSB1 --fqbn esp32:esp32:heltec_wifi_lora_32_V3
|
||||
@sleep 1
|
||||
rnodeconf /dev/ttyUSB1 --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/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-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)
|
||||
|
||||
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_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-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:
|
||||
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_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
|
||||
4
Modem.h
|
|
@ -1,4 +0,0 @@
|
|||
#define SX1276 0x01
|
||||
#define SX1278 0x02
|
||||
#define SX1262 0x03
|
||||
#define SX1280 0x04
|
||||
558
Power.h
|
|
@ -1,558 +0,0 @@
|
|||
// 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 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
|
||||
#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 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_rc = 0;
|
||||
#define PMU_R_INTERVAL 5
|
||||
void kiss_indicate_battery();
|
||||
|
||||
void measure_battery() {
|
||||
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TECHO
|
||||
battery_installed = true;
|
||||
battery_indeterminate = true;
|
||||
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.0041;
|
||||
#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) {
|
||||
float bat_delay_diff = bat_state_change_v-battery_voltage;
|
||||
if (bat_delay_diff < 0) { bat_delay_diff *= -1; }
|
||||
|
||||
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;
|
||||
// SerialBT.printf("STATE CHANGE to DISCHARGE at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (bat_voltage_dropping == true) {
|
||||
if (bat_delay_diff > 0.01) {
|
||||
bat_voltage_dropping = false;
|
||||
bat_state_change_v = battery_voltage;
|
||||
// SerialBT.printf("STATE CHANGE to CHARGE at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
bat_samples_count = 0;
|
||||
bat_delay_v = battery_voltage;
|
||||
}
|
||||
|
||||
if (bat_voltage_dropping && battery_voltage < BAT_V_FLOAT) {
|
||||
battery_state = BATTERY_STATE_DISCHARGING;
|
||||
} else {
|
||||
if (battery_percent < 100.0) {
|
||||
battery_state = BATTERY_STATE_CHARGING;
|
||||
} else {
|
||||
battery_state = BATTERY_STATE_CHARGED;
|
||||
}
|
||||
}
|
||||
|
||||
// if (bt_state == BT_STATE_CONNECTED) {
|
||||
// SerialBT.printf("Bus voltage %.3fv. Unfiltered %.3fv.", battery_voltage, bat_v_samples[BAT_SAMPLES-1]);
|
||||
// if (bat_voltage_dropping) { SerialBT.printf(" Voltage is dropping. Percentage %.1f%%.", battery_percent); }
|
||||
// else { SerialBT.printf(" Voltage is not dropping. Percentage %.1f%%.", battery_percent); }
|
||||
// if (battery_state == BATTERY_STATE_DISCHARGING) { SerialBT.printf(" Battery discharging. delay_v %.3fv", bat_delay_v); }
|
||||
// if (battery_state == BATTERY_STATE_CHARGING) { SerialBT.printf(" Battery charging. delay_v %.3fv", bat_delay_v); }
|
||||
// if (battery_state == BATTERY_STATE_CHARGED) { SerialBT.print(" 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_pmu() {
|
||||
if (millis()-last_pmu_update >= pmu_update_interval) {
|
||||
measure_battery();
|
||||
last_pmu_update = millis();
|
||||
}
|
||||
}
|
||||
|
||||
bool init_pmu() {
|
||||
#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
|
||||
pinMode(pin_ctrl,OUTPUT);
|
||||
digitalWrite(pin_ctrl, LOW);
|
||||
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
|
||||
}
|
||||
2
Precompiled/README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Precompiled Firmware
|
||||
This directory contains the latest version of the precompiled firmware. You can download and flash this file to your RNode using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil).
|
||||
1435
Precompiled/rnode_firmware_latest.hex
Normal file
|
|
@ -1,536 +0,0 @@
|
|||
# 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+"]"
|
||||
|
||||
188
README.md
|
|
@ -1,144 +1,86 @@
|
|||
***Important!** This repository is currently functioning as a stable reference for the default RNode Firmware, and only receives bugfix and security updates. Further development, new features and expanded board support is now happening at the [RNode Firmware Community Edition](https://github.com/liberatedsystems/RNode_Firmware_CE) repository, and is maintained by [Liberated Embedded Systems](https://github.com/liberatedsystems). Thanks for all contributions so far!*
|
||||
|
||||
# RNode Firmware
|
||||
|
||||
This is the open firmware that powers RNode devices.
|
||||
This is the firmware for [RNode](https://unsigned.io/rnode), a very flexible LoRa-based communication device. RNode can functions as a:
|
||||
|
||||
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](https://reticulum.network).
|
||||
- Network adapter for [RNS](https://github.com/markqvist/Reticulum)
|
||||
- LoRa interface for your computer (or any device with a USB or serial port)
|
||||
- Generic [LoRa-based network adapter](https://unsigned.io/15-kilometre-ssh-link-with-rnode/) for your Linux devices
|
||||
- [Packet sniffer](https://github.com/markqvist/LoRaMon) for LoRa networks
|
||||
- LoRa development board
|
||||
- A LoRa-based KISS-compatible TNC
|
||||
- A flexible platform for experiementing with LoRa technology
|
||||
|
||||
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, privacy and ability to communicate and exchange data and ideas freely.
|
||||
RNode is controlled by a powerful ATmega1284p MCU, and is fully Arduino compatible. You can use this firmware, or it can be programmed any way you like, either from the Arduino IDE, or using any of the available tools for AVR development. This firmware can also be edited and compiled directly from the Arduino IDE.
|
||||
|
||||
<img src="Documentation/images/rnv21_bgp.webp" width="100%">
|
||||
For adding RNode to your Arduino environment, please see [this post](https://unsigned.io/board-support-in-arduino-ide/).
|
||||
|
||||
<center><i>An RNode made from readily available and cheap parts, in a durable 3D printed case</i></center><br/><br/>
|
||||
For configuring an RNode with this firmware, please have a look at the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil).
|
||||
|
||||
The RNode system is primarily software, which *transforms* different kinds of 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 built to suit the specific time, locale and environment they need to exist in.
|
||||
## Current Status
|
||||
The RNode firmware is relatively well tested, but there's a lot of features being added, so you might still find a bug. If you do, please report it as an issue here, so I can fix it!
|
||||
|
||||
## Latest Release
|
||||
## Operating Modes
|
||||
RNode can operate in two modes, host-controlled (default) and TNC mode:
|
||||
|
||||
The latest release, installable through `rnodeconf`, is version `1.80`. You must have at least version `2.3.0` of `rnodeconf` installed to update the RNode Firmware to version `1.80`. Get it by updating the `rns` package to at least version `0.8.9`.
|
||||
- When RNode is in host-controlled mode, it will stay in standby when powered on, until the host specifies frequency, bandwidth, transmit power and other required parameters. This mode can be enabled by using the -N option of this utility. In host-controlled mode, promiscuous mode can be activated to sniff any LoRa frames.
|
||||
|
||||
## A Self-Replicating System
|
||||
- When RNode is in TNC mode, it will configure itself on powerup and enable the radio immediately. This mode can be enabled by using the -T option of this utility (the utility will guide you through the settings if you don't specify them directly).
|
||||
|
||||
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, the [RNode Bootstrap Console](https://unsigned.io/rnode_bootstrap_console), which is hosted locally on every RNode, and can be activated and accesses at any time - no Internet required.
|
||||
## USB and Serial Protocol
|
||||
You can communicate with RNode either via the on-board USB connector, or using the serial pins on the board (labeled RX0 and TX0). RNode uses a standard FTDI USB chip, so it works out of the box without additional drivers in most operating systems.
|
||||
|
||||
The designs, guides and software stored within allows users to create more RNodes, and even to bootstrap entire communications networks, completely independently of existing infrastructure, or in situations where infrastructure has become unreliable or is broken.
|
||||
All communications to and from the board uses [KISS framing](https://en.wikipedia.org/wiki/KISS_(TNC)) with a custom command set. RNode also does not use HDLC ports in the command byte, and as such uses the full 8 bits of the command byte is available for the actual command. Please see table below for supported commands.
|
||||
|
||||
<img src="Documentation/images/126dcfe92fb7.webp" width="100%"/>
|
||||
| Command | Byte | Description
|
||||
| -----------------|------| -----------------------------------------------
|
||||
| Data frame | 0x00 | A data packet to or from the device
|
||||
| Frequency | 0x01 | Sets or queries the frequency
|
||||
| Bandwidth | 0x02 | Sets or queries the bandwidth
|
||||
| TX Power | 0x03 | Sets or queries the TX power
|
||||
| Spreading Factor | 0x04 | Sets or queries the spreading factor
|
||||
| Coding Rate | 0x05 | Sets or queries the coding rate
|
||||
| Radio State | 0x06 | Sets or queries radio state
|
||||
| Radio Lock | 0x07 | Sets or queries the radio lock
|
||||
| Device Detect | 0x08 | Probe command for device detection
|
||||
| Promiscuous | 0x0E | Sets or queries promiscuous mode
|
||||
| Ready | 0x0F | Flow control command indicating ready for TX
|
||||
| RX Stats | 0x21 | Queries received bytes
|
||||
| TX Stats | 0x22 | Queries transmitted bytes
|
||||
| Last RSSI | 0x23 | Indicates RSSI of last packet received
|
||||
| Blink | 0x30 | Blinks LEDs
|
||||
| Random | 0x40 | Queries for a random number
|
||||
| Firmware Version | 0x50 | Queries for installed firmware version
|
||||
| ROM Read | 0x51 | Read EEPROM byte
|
||||
| ROM Write | 0x52 | Write EEPROM byte
|
||||
| TNC Mode | 0x53 | Enables TNC mode
|
||||
| Normal Mode | 0x54 | Enables host-controlled mode
|
||||
| ROM Erase | 0x59 | Completely erases EEPROM
|
||||
| Error | 0x90 | Indicates an error
|
||||
|
||||
<center><i>Where there is no Internet, RNodes will still communicate</i></center><br/><br/>
|
||||
## Programming Libraries
|
||||
Have a look in the "Libraries" folder for includes to let you easily use RNode in your own software.
|
||||
|
||||
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.
|
||||
Here's a Python example:
|
||||
|
||||
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.
|
||||
```python
|
||||
from RNode import RNodeInterface
|
||||
|
||||
## One Tool, Many Uses
|
||||
def gotPacket(data, rnode):
|
||||
print "Received a packet: "+data
|
||||
|
||||
The RNode design is meant to flexible and hackable. At it's core, it is a low-power, but extremely long-range digital radio transceiver. Coupled with Reticulum, it provides encrypted and secure communications.
|
||||
rnode = RNodeInterface(
|
||||
callback = gotPacket,
|
||||
name = "My RNode",
|
||||
port = "/dev/ttyUSB0",
|
||||
frequency = 868000000,
|
||||
bandwidth = 125000,
|
||||
txpower = 2,
|
||||
sf = 7,
|
||||
cr = 5,
|
||||
loglevel = RNodeInterface.LOG_DEBUG)
|
||||
|
||||
Depending on configuration, it can be used for local networking purposes, or to send data over very long distances. Once you have an RNode, there is a wide variety of possible uses:
|
||||
|
||||
- As a network adapter for [Reticulum](https://reticulum.network)
|
||||
- Messaging using [Sideband](https://unsigned.io/software/Sideband.html)
|
||||
- Information sharing and communication using [Nomad Network](https://unsigned.io/software/Nomad_Network.html)
|
||||
- LoRa-based [KISS-compatible amateur radio TNC](https://unsigned.io/guides/2020_05_03_using_rnodes_with_amateur_radio_software.html)
|
||||
- LoRa development platform
|
||||
- [Packet sniffer](https://unsigned.io/software/LoRaMon.html) for LoRa networks
|
||||
- Long range [Ethernet and IP network interface](https://unsigned.io/guides/2020_05_27_ethernet-and-ip-over-packet-radio-tncs.html) on Linux
|
||||
- As a general-purpose long-range data radio
|
||||
|
||||
## Types & Performance
|
||||
|
||||
RNodes can be made in many different configurations, and can use many different radio bands, but they will generally operate in the **433 MHz**, **868 MHz**, **915 MHZ** and **2.4 GHz** bands. They will usually offer configurable on-air data speeds between just a **few hundred bits per second**, up to **a couple of megabits per second**. Maximum output power will depend on the transceiver and PA setup used, but will generally lie between **17 dbm** and **27 dBm**.
|
||||
|
||||
The RNode system has been designed to allow reliable systems for basic human communications, over very wide areas, while using very little power, being cheap to build, free to operate, and near impossible to censor.
|
||||
|
||||
While **speeds are lower** than WiFi, typical communication **ranges are many times higher**. Several kilometers can be acheived with usable bitrates, even in urban areas, and over **100 kilometers** can be achieved in line-of-sight conditions.
|
||||
|
||||
## Supported Boards & Devices
|
||||
It's easy to create your own RNodes from one of the supported development boards and devices. If a device or board you want to use is not yet supported, you are welcome to join the effort and help creating a board definition and pin mapping for it!
|
||||
|
||||
<img src="Documentation/images/devboards_1.webp" width="100%"/>
|
||||
|
||||
The RNode Firmware supports the following boards:
|
||||
|
||||
- LilyGO T-Beam v1.1 devices with SX1276/8 LoRa chips
|
||||
- LilyGO T-Beam v1.1 devices with SX1262/8 LoRa chips
|
||||
- LilyGO T-Beam Supreme devices
|
||||
- LilyGO T-Deck devices (currently display is disabled)
|
||||
- LilyGO LoRa32 v1.0 devices
|
||||
- LilyGO LoRa32 v2.0 devices
|
||||
- LilyGO LoRa32 v2.1 devices (with and without TCXO)
|
||||
- LilyGO T3S3 devices with SX1276/8 LoRa chips
|
||||
- LilyGO T3S3 devices with SX1262/8 LoRa chips
|
||||
- LilyGO T3S3 devices with SX1280 LoRa chips
|
||||
- Heltec LoRa32 v2 devices
|
||||
- Heltec LoRa32 v3 devices
|
||||
- Heltec T114 devices
|
||||
- RAK4631 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.
|
||||
|
||||
Support for **SX1262**, **SX1268** and **SX1280** is being implemented. Please support the project with donations if you want this faster!
|
||||
|
||||
## Getting Started Fast
|
||||
You can download and flash the firmware to all the supported boards using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil). All firmware releases are now handled and installed directly through the `rnodeconf` utility, which is included 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
|
||||
rnode.send("Hello World!")
|
||||
```
|
||||
|
||||
For most of the supported device types, it is also possible to use [Liam Cottle's Web-based RNode Flasher](https://liamcottle.github.io/rnode-flasher/). This option may be easier if you're not familiar with using a command line interface.
|
||||
|
||||
For more detailed instruction and in-depth guides, you can have a look at some of these resources:
|
||||
|
||||
- Create a [basic RNode from readily available development boards](https://unsigned.io/guides/2022_01_25_installing-rnode-firmware-on-supported-devices.html)
|
||||
- Follow a complete build recipe for [making a handheld RNode](https://unsigned.io/guides/2023_01_14_Making_A_Handheld_RNode.html), like the one pictured above
|
||||
- Learn the basics on how to [create and build your own RNode designs](https://unsigned.io/guides/2022_01_26_how-to-make-your-own-rnodes.html) from scratch
|
||||
- Once you've got the hang of it, start building RNodes for your community, or [even for selling them](https://unsigned.io/sell_rnodes.html)
|
||||
|
||||
If you would rather just buy a pre-made unit, you can visit one of the community vendors that produce and sell RNodes:
|
||||
|
||||
- [Liberated Embedded Systems](https://store.liberatedsystems.co.uk/)
|
||||
- [Simply Equipped](https://simplyequipped.com/)
|
||||
|
||||
If you'd like to have your shop added to this list, let me know.
|
||||
|
||||
## Support RNode Development
|
||||
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
|
||||
|
||||
- Monero:
|
||||
```
|
||||
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
|
||||
```
|
||||
- Ethereum
|
||||
```
|
||||
0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73
|
||||
```
|
||||
- Bitcoin
|
||||
```
|
||||
35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH
|
||||
```
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
## License & Use
|
||||
The RNode Firmware is Copyright © 2024 Mark Qvist / [unsigned.io](https://unsigned.io), and is made available under the **GNU General Public License v3.0**. The source code includes an SX1276 driver that is released under MIT License, and Copyright © 2018 Sandeep Mistry / Mark Qvist.
|
||||
|
||||
You can obtain the source code from [git.unsigned.io](https://git.unsigned.io/markqvist/RNode_Firmware) or [GitHub](https://github.com/markqvist/rnode_firmware).
|
||||
|
||||
Every RNode also includes an internal copy of it's own firmware source code, that can be downloaded through the [RNode Bootstrap Console](https://unsigned.io/rnode_bootstrap_console), by putting the RNode into Console Mode (which can be activated by pressing the reset button two times within two seconds).
|
||||
|
||||
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.
|
||||
|
||||
If you distribute or modify this work, you **must** adhere to the terms of the GPLv3, including, but not limited to, providing up-to-date source code upon distribution, displaying appropriate copyright and license notices in prominent positions of all conveyed works, and making users aware of their rights to the software under the GPLv3.
|
||||
|
||||
In practice, this means that you can use the firmware commercially, but you must understand your obligation to provide all future users of the system with the same rights, that you have been provided by the GPLv3. If you intend using the RNode Firmware commercially, it is worth reading [this page](https://unsigned.io/sell_rnodes.html).
|
||||
## Promiscuous Mode and LoRa Sniffing
|
||||
RNode can be put into LoRa promiscuous mode, which will dump raw LoRa frames to the host. Raw LoRa frames can also be sent in this mode, and have the standard LoRa payload size of 255 bytes. To enable promiscuous mode send the "Promiscuous" command to the board, or use one of the programming libraries. You can also use the example program [LoRaMon](https://github.com/markqvist/LoRaMon) for an easy to use LoRa packet sniffer.
|
||||
1796
RNode_Firmware.ino
71
ROM.h
|
|
@ -1,55 +1,30 @@
|
|||
// 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
|
||||
#define ROM_H
|
||||
|
||||
// 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 CHECKSUMMED_SIZE 0x0B
|
||||
|
||||
#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 PRODUCT_RNODE 0x03
|
||||
#define MODEL_A4 0xA4
|
||||
#define MODEL_A9 0xA9
|
||||
|
||||
#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_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 INFO_LOCK_BYTE 0x73
|
||||
#define CONF_OK_BYTE 0x73
|
||||
#define BT_ENABLE_BYTE 0x73
|
||||
#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 EEPROM_RESERVED 200
|
||||
//////////////////////////////////
|
||||
#define INFO_LOCK_BYTE 0x73
|
||||
#define CONF_OK_BYTE 0x73
|
||||
|
||||
#endif
|
||||
#define EEPROM_RESERVED 200
|
||||
#endif
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# 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
|
||||
```
|
||||
|
|
@ -1,595 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# ESP32 partition table generation tool
|
||||
#
|
||||
# Converts partition tables to/from CSV and binary formats.
|
||||
#
|
||||
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html
|
||||
# for explanation of partition table structure and uses.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
|
||||
MD5_PARTITION_BEGIN = b'\xEB\xEB' + b'\xFF' * 14 # The first 2 bytes are like magic numbers for MD5 sum
|
||||
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
|
||||
|
||||
MIN_PARTITION_SUBTYPE_APP_OTA = 0x10
|
||||
NUM_PARTITION_SUBTYPE_APP_OTA = 16
|
||||
|
||||
__version__ = '1.2'
|
||||
|
||||
APP_TYPE = 0x00
|
||||
DATA_TYPE = 0x01
|
||||
|
||||
TYPES = {
|
||||
'app': APP_TYPE,
|
||||
'data': DATA_TYPE,
|
||||
}
|
||||
|
||||
|
||||
def get_ptype_as_int(ptype):
|
||||
""" Convert a string which might be numeric or the name of a partition type to an integer """
|
||||
try:
|
||||
return TYPES[ptype]
|
||||
except KeyError:
|
||||
try:
|
||||
return int(ptype, 0)
|
||||
except TypeError:
|
||||
return ptype
|
||||
|
||||
|
||||
# Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h
|
||||
SUBTYPES = {
|
||||
APP_TYPE: {
|
||||
'factory': 0x00,
|
||||
'test': 0x20,
|
||||
},
|
||||
DATA_TYPE: {
|
||||
'ota': 0x00,
|
||||
'phy': 0x01,
|
||||
'nvs': 0x02,
|
||||
'coredump': 0x03,
|
||||
'nvs_keys': 0x04,
|
||||
'efuse': 0x05,
|
||||
'undefined': 0x06,
|
||||
'esphttpd': 0x80,
|
||||
'fat': 0x81,
|
||||
'spiffs': 0x82,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_subtype_as_int(ptype, subtype):
|
||||
""" Convert a string which might be numeric or the name of a partition subtype to an integer """
|
||||
try:
|
||||
return SUBTYPES[get_ptype_as_int(ptype)][subtype]
|
||||
except KeyError:
|
||||
try:
|
||||
return int(subtype, 0)
|
||||
except TypeError:
|
||||
return subtype
|
||||
|
||||
|
||||
ALIGNMENT = {
|
||||
APP_TYPE: 0x10000,
|
||||
DATA_TYPE: 0x1000,
|
||||
}
|
||||
|
||||
|
||||
def get_alignment_for_type(ptype):
|
||||
return ALIGNMENT.get(ptype, ALIGNMENT[DATA_TYPE])
|
||||
|
||||
|
||||
def get_partition_type(ptype):
|
||||
if ptype == 'app':
|
||||
return APP_TYPE
|
||||
if ptype == 'data':
|
||||
return DATA_TYPE
|
||||
raise InputError('Invalid partition type')
|
||||
|
||||
|
||||
def add_extra_subtypes(csv):
|
||||
for line_no in csv:
|
||||
try:
|
||||
fields = [line.strip() for line in line_no.split(',')]
|
||||
for subtype, subtype_values in SUBTYPES.items():
|
||||
if (int(fields[2], 16) in subtype_values.values() and subtype == get_partition_type(fields[0])):
|
||||
raise ValueError('Found duplicate value in partition subtype')
|
||||
SUBTYPES[TYPES[fields[0]]][fields[1]] = int(fields[2], 16)
|
||||
except InputError as err:
|
||||
raise InputError('Error parsing custom subtypes: %s' % err)
|
||||
|
||||
|
||||
quiet = False
|
||||
md5sum = True
|
||||
secure = False
|
||||
offset_part_table = 0
|
||||
|
||||
|
||||
def status(msg):
|
||||
""" Print status message to stderr """
|
||||
if not quiet:
|
||||
critical(msg)
|
||||
|
||||
|
||||
def critical(msg):
|
||||
""" Print critical message to stderr """
|
||||
sys.stderr.write(msg)
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
||||
class PartitionTable(list):
|
||||
def __init__(self):
|
||||
super(PartitionTable, self).__init__(self)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read()
|
||||
data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES
|
||||
if data_is_binary:
|
||||
status('Parsing binary partition input...')
|
||||
return cls.from_binary(data), True
|
||||
|
||||
data = data.decode()
|
||||
status('Parsing CSV input...')
|
||||
return cls.from_csv(data), False
|
||||
|
||||
@classmethod
|
||||
def from_csv(cls, csv_contents):
|
||||
res = PartitionTable()
|
||||
lines = csv_contents.splitlines()
|
||||
|
||||
def expand_vars(f):
|
||||
f = os.path.expandvars(f)
|
||||
m = re.match(r'(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)', f)
|
||||
if m:
|
||||
raise InputError("unknown variable '%s'" % m.group(1))
|
||||
return f
|
||||
|
||||
for line_no in range(len(lines)):
|
||||
line = expand_vars(lines[line_no]).strip()
|
||||
if line.startswith('#') or len(line) == 0:
|
||||
continue
|
||||
try:
|
||||
res.append(PartitionDefinition.from_csv(line, line_no + 1))
|
||||
except InputError as err:
|
||||
raise InputError('Error at line %d: %s\nPlease check extra_partition_subtypes.inc file in build/config directory' % (line_no + 1, err))
|
||||
except Exception:
|
||||
critical('Unexpected error parsing CSV line %d: %s' % (line_no + 1, line))
|
||||
raise
|
||||
|
||||
# fix up missing offsets & negative sizes
|
||||
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
|
||||
for e in res:
|
||||
if e.offset is not None and e.offset < last_end:
|
||||
if e == res[0]:
|
||||
raise InputError('CSV Error at line %d: Partitions overlap. Partition sets offset 0x%x. '
|
||||
'But partition table occupies the whole sector 0x%x. '
|
||||
'Use a free offset 0x%x or higher.'
|
||||
% (e.line_no, e.offset, offset_part_table, last_end))
|
||||
else:
|
||||
raise InputError('CSV Error at line %d: Partitions overlap. Partition sets offset 0x%x. Previous partition ends 0x%x'
|
||||
% (e.line_no, e.offset, last_end))
|
||||
if e.offset is None:
|
||||
pad_to = get_alignment_for_type(e.type)
|
||||
if last_end % pad_to != 0:
|
||||
last_end += pad_to - (last_end % pad_to)
|
||||
e.offset = last_end
|
||||
if e.size < 0:
|
||||
e.size = -e.size - e.offset
|
||||
last_end = e.offset + e.size
|
||||
|
||||
return res
|
||||
|
||||
def __getitem__(self, item):
|
||||
""" Allow partition table access via name as well as by
|
||||
numeric index. """
|
||||
if isinstance(item, str):
|
||||
for x in self:
|
||||
if x.name == item:
|
||||
return x
|
||||
raise ValueError("No partition entry named '%s'" % item)
|
||||
else:
|
||||
return super(PartitionTable, self).__getitem__(item)
|
||||
|
||||
def find_by_type(self, ptype, subtype):
|
||||
""" Return a partition by type & subtype, returns
|
||||
None if not found """
|
||||
# convert ptype & subtypes names (if supplied this way) to integer values
|
||||
ptype = get_ptype_as_int(ptype)
|
||||
subtype = get_subtype_as_int(ptype, subtype)
|
||||
|
||||
for p in self:
|
||||
if p.type == ptype and p.subtype == subtype:
|
||||
yield p
|
||||
return
|
||||
|
||||
def find_by_name(self, name):
|
||||
for p in self:
|
||||
if p.name == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
def verify(self):
|
||||
# verify each partition individually
|
||||
for p in self:
|
||||
p.verify()
|
||||
|
||||
# check on duplicate name
|
||||
names = [p.name for p in self]
|
||||
duplicates = set(n for n in names if names.count(n) > 1)
|
||||
|
||||
# print sorted duplicate partitions by name
|
||||
if len(duplicates) != 0:
|
||||
critical('A list of partitions that have the same name:')
|
||||
for p in sorted(self, key=lambda x:x.name):
|
||||
if len(duplicates.intersection([p.name])) != 0:
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('Partition names must be unique')
|
||||
|
||||
# check for overlaps
|
||||
last = None
|
||||
for p in sorted(self, key=lambda x:x.offset):
|
||||
if p.offset < offset_part_table + PARTITION_TABLE_SIZE:
|
||||
raise InputError('Partition offset 0x%x is below 0x%x' % (p.offset, offset_part_table + PARTITION_TABLE_SIZE))
|
||||
if last is not None and p.offset < last.offset + last.size:
|
||||
raise InputError('Partition at 0x%x overlaps 0x%x-0x%x' % (p.offset, last.offset, last.offset + last.size - 1))
|
||||
last = p
|
||||
|
||||
# check that otadata should be unique
|
||||
otadata_duplicates = [p for p in self if p.type == TYPES['data'] and p.subtype == SUBTYPES[DATA_TYPE]['ota']]
|
||||
if len(otadata_duplicates) > 1:
|
||||
for p in otadata_duplicates:
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('Found multiple otadata partitions. Only one partition can be defined with type="data"(1) and subtype="ota"(0).')
|
||||
|
||||
if len(otadata_duplicates) == 1 and otadata_duplicates[0].size != 0x2000:
|
||||
p = otadata_duplicates[0]
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('otadata partition must have size = 0x2000')
|
||||
|
||||
def flash_size(self):
|
||||
""" Return the size that partitions will occupy in flash
|
||||
(ie the offset the last partition ends at)
|
||||
"""
|
||||
try:
|
||||
last = sorted(self, reverse=True)[0]
|
||||
except IndexError:
|
||||
return 0 # empty table!
|
||||
return last.offset + last.size
|
||||
|
||||
def verify_size_fits(self, flash_size_bytes: int) -> None:
|
||||
""" Check that partition table fits into the given flash size.
|
||||
Raises InputError otherwise.
|
||||
"""
|
||||
table_size = self.flash_size()
|
||||
if flash_size_bytes < table_size:
|
||||
mb = 1024 * 1024
|
||||
raise InputError('Partitions tables occupies %.1fMB of flash (%d bytes) which does not fit in configured '
|
||||
"flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." %
|
||||
(table_size / mb, table_size, flash_size_bytes / mb))
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, b):
|
||||
md5 = hashlib.md5()
|
||||
result = cls()
|
||||
for o in range(0,len(b),32):
|
||||
data = b[o:o + 32]
|
||||
if len(data) != 32:
|
||||
raise InputError('Partition table length must be a multiple of 32 bytes')
|
||||
if data == b'\xFF' * 32:
|
||||
return result # got end marker
|
||||
if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: # check only the magic number part
|
||||
if data[16:] == md5.digest():
|
||||
continue # the next iteration will check for the end marker
|
||||
else:
|
||||
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
|
||||
else:
|
||||
md5.update(data)
|
||||
result.append(PartitionDefinition.from_binary(data))
|
||||
raise InputError('Partition table is missing an end-of-table marker')
|
||||
|
||||
def to_binary(self):
|
||||
result = b''.join(e.to_binary() for e in self)
|
||||
if md5sum:
|
||||
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
|
||||
if len(result) >= MAX_PARTITION_LENGTH:
|
||||
raise InputError('Binary partition table length (%d) longer than max' % len(result))
|
||||
result += b'\xFF' * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing
|
||||
return result
|
||||
|
||||
def to_csv(self, simple_formatting=False):
|
||||
rows = ['# ESP-IDF Partition Table',
|
||||
'# Name, Type, SubType, Offset, Size, Flags']
|
||||
rows += [x.to_csv(simple_formatting) for x in self]
|
||||
return '\n'.join(rows) + '\n'
|
||||
|
||||
|
||||
class PartitionDefinition(object):
|
||||
MAGIC_BYTES = b'\xAA\x50'
|
||||
|
||||
# dictionary maps flag name (as used in CSV flags list, property name)
|
||||
# to bit set in flags words in binary format
|
||||
FLAGS = {
|
||||
'encrypted': 0
|
||||
}
|
||||
|
||||
# add subtypes for the 16 OTA slot values ("ota_XX, etc.")
|
||||
for ota_slot in range(NUM_PARTITION_SUBTYPE_APP_OTA):
|
||||
SUBTYPES[TYPES['app']]['ota_%d' % ota_slot] = MIN_PARTITION_SUBTYPE_APP_OTA + ota_slot
|
||||
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.type = None
|
||||
self.subtype = None
|
||||
self.offset = None
|
||||
self.size = None
|
||||
self.encrypted = False
|
||||
|
||||
@classmethod
|
||||
def from_csv(cls, line, line_no):
|
||||
""" Parse a line from the CSV """
|
||||
line_w_defaults = line + ',,,,' # lazy way to support default fields
|
||||
fields = [f.strip() for f in line_w_defaults.split(',')]
|
||||
|
||||
res = PartitionDefinition()
|
||||
res.line_no = line_no
|
||||
res.name = fields[0]
|
||||
res.type = res.parse_type(fields[1])
|
||||
res.subtype = res.parse_subtype(fields[2])
|
||||
res.offset = res.parse_address(fields[3])
|
||||
res.size = res.parse_address(fields[4])
|
||||
if res.size is None:
|
||||
raise InputError("Size field can't be empty")
|
||||
|
||||
flags = fields[5].split(':')
|
||||
for flag in flags:
|
||||
if flag in cls.FLAGS:
|
||||
setattr(res, flag, True)
|
||||
elif len(flag) > 0:
|
||||
raise InputError("CSV flag column contains unknown flag '%s'" % (flag))
|
||||
|
||||
return res
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.type == other.type \
|
||||
and self.subtype == other.subtype and self.offset == other.offset \
|
||||
and self.size == other.size
|
||||
|
||||
def __repr__(self):
|
||||
def maybe_hex(x):
|
||||
return '0x%x' % x if x is not None else 'None'
|
||||
return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0,
|
||||
maybe_hex(self.offset), maybe_hex(self.size))
|
||||
|
||||
def __str__(self):
|
||||
return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1)
|
||||
|
||||
def __cmp__(self, other):
|
||||
return self.offset - other.offset
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.offset < other.offset
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.offset > other.offset
|
||||
|
||||
def __le__(self, other):
|
||||
return self.offset <= other.offset
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.offset >= other.offset
|
||||
|
||||
def parse_type(self, strval):
|
||||
if strval == '':
|
||||
raise InputError("Field 'type' can't be left empty.")
|
||||
return parse_int(strval, TYPES)
|
||||
|
||||
def parse_subtype(self, strval):
|
||||
if strval == '':
|
||||
if self.type == TYPES['app']:
|
||||
raise InputError('App partition cannot have an empty subtype')
|
||||
return SUBTYPES[DATA_TYPE]['undefined']
|
||||
return parse_int(strval, SUBTYPES.get(self.type, {}))
|
||||
|
||||
def parse_address(self, strval):
|
||||
if strval == '':
|
||||
return None # PartitionTable will fill in default
|
||||
return parse_int(strval)
|
||||
|
||||
def verify(self):
|
||||
if self.type is None:
|
||||
raise ValidationError(self, 'Type field is not set')
|
||||
if self.subtype is None:
|
||||
raise ValidationError(self, 'Subtype field is not set')
|
||||
if self.offset is None:
|
||||
raise ValidationError(self, 'Offset field is not set')
|
||||
align = get_alignment_for_type(self.type)
|
||||
if self.offset % align:
|
||||
raise ValidationError(self, 'Offset 0x%x is not aligned to 0x%x' % (self.offset, align))
|
||||
if self.size % align and secure and self.type == APP_TYPE:
|
||||
raise ValidationError(self, 'Size 0x%x is not aligned to 0x%x' % (self.size, align))
|
||||
if self.size is None:
|
||||
raise ValidationError(self, 'Size field is not set')
|
||||
|
||||
if self.name in TYPES and TYPES.get(self.name, '') != self.type:
|
||||
critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's "
|
||||
'type (0x%x). Mistake in partition table?' % (self.name, self.type))
|
||||
all_subtype_names = []
|
||||
for names in (t.keys() for t in SUBTYPES.values()):
|
||||
all_subtype_names += names
|
||||
if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, '') != self.subtype:
|
||||
critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has "
|
||||
'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype))
|
||||
|
||||
STRUCT_FORMAT = b'<2sBBLL16sL'
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, b):
|
||||
if len(b) != 32:
|
||||
raise InputError('Partition definition length must be exactly 32 bytes. Got %d bytes.' % len(b))
|
||||
res = cls()
|
||||
(magic, res.type, res.subtype, res.offset,
|
||||
res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b)
|
||||
if b'\x00' in res.name: # strip null byte padding from name string
|
||||
res.name = res.name[:res.name.index(b'\x00')]
|
||||
res.name = res.name.decode()
|
||||
if magic != cls.MAGIC_BYTES:
|
||||
raise InputError('Invalid magic bytes (%r) for partition definition' % magic)
|
||||
for flag,bit in cls.FLAGS.items():
|
||||
if flags & (1 << bit):
|
||||
setattr(res, flag, True)
|
||||
flags &= ~(1 << bit)
|
||||
if flags != 0:
|
||||
critical('WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?' % flags)
|
||||
return res
|
||||
|
||||
def get_flags_list(self):
|
||||
return [flag for flag in self.FLAGS.keys() if getattr(self, flag)]
|
||||
|
||||
def to_binary(self):
|
||||
flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list())
|
||||
return struct.pack(self.STRUCT_FORMAT,
|
||||
self.MAGIC_BYTES,
|
||||
self.type, self.subtype,
|
||||
self.offset, self.size,
|
||||
self.name.encode(),
|
||||
flags)
|
||||
|
||||
def to_csv(self, simple_formatting=False):
|
||||
def addr_format(a, include_sizes):
|
||||
if not simple_formatting and include_sizes:
|
||||
for (val, suffix) in [(0x100000, 'M'), (0x400, 'K')]:
|
||||
if a % val == 0:
|
||||
return '%d%s' % (a // val, suffix)
|
||||
return '0x%x' % a
|
||||
|
||||
def lookup_keyword(t, keywords):
|
||||
for k,v in keywords.items():
|
||||
if simple_formatting is False and t == v:
|
||||
return k
|
||||
return '%d' % t
|
||||
|
||||
def generate_text_flags():
|
||||
""" colon-delimited list of flags """
|
||||
return ':'.join(self.get_flags_list())
|
||||
|
||||
return ','.join([self.name,
|
||||
lookup_keyword(self.type, TYPES),
|
||||
lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})),
|
||||
addr_format(self.offset, False),
|
||||
addr_format(self.size, True),
|
||||
generate_text_flags()])
|
||||
|
||||
|
||||
def parse_int(v, keywords={}):
|
||||
"""Generic parser for integer fields - int(x,0) with provision for
|
||||
k/m/K/M suffixes and 'keyword' value lookup.
|
||||
"""
|
||||
try:
|
||||
for letter, multiplier in [('k', 1024), ('m', 1024 * 1024)]:
|
||||
if v.lower().endswith(letter):
|
||||
return parse_int(v[:-1], keywords) * multiplier
|
||||
return int(v, 0)
|
||||
except ValueError:
|
||||
if len(keywords) == 0:
|
||||
raise InputError('Invalid field value %s' % v)
|
||||
try:
|
||||
return keywords[v.lower()]
|
||||
except KeyError:
|
||||
raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ', '.join(keywords)))
|
||||
|
||||
|
||||
def main():
|
||||
global quiet
|
||||
global md5sum
|
||||
global offset_part_table
|
||||
global secure
|
||||
parser = argparse.ArgumentParser(description='ESP32 partition table utility')
|
||||
|
||||
parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash',
|
||||
nargs='?', choices=['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB'])
|
||||
parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true')
|
||||
parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true')
|
||||
parser.add_argument('--verify', '-v', help='Verify partition table fields (deprecated, this behaviour is '
|
||||
'enabled by default and this flag does nothing.', action='store_true')
|
||||
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
|
||||
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
|
||||
parser.add_argument('--secure', help='Require app partitions to be suitable for secure boot', action='store_true')
|
||||
parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*')
|
||||
parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb'))
|
||||
parser.add_argument('output', help='Path to output converted binary or CSV file. Will use stdout if omitted.',
|
||||
nargs='?', default='-')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
quiet = args.quiet
|
||||
md5sum = not args.disable_md5sum
|
||||
secure = args.secure
|
||||
offset_part_table = int(args.offset, 0)
|
||||
if args.extra_partition_subtypes:
|
||||
add_extra_subtypes(args.extra_partition_subtypes)
|
||||
|
||||
table, input_is_binary = PartitionTable.from_file(args.input)
|
||||
|
||||
if not args.no_verify:
|
||||
status('Verifying table...')
|
||||
table.verify()
|
||||
|
||||
if args.flash_size:
|
||||
size_mb = int(args.flash_size.replace('MB', ''))
|
||||
table.verify_size_fits(size_mb * 1024 * 1024)
|
||||
|
||||
# Make sure that the output directory is created
|
||||
output_dir = os.path.abspath(os.path.dirname(args.output))
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
try:
|
||||
os.makedirs(output_dir)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
if input_is_binary:
|
||||
output = table.to_csv()
|
||||
with sys.stdout if args.output == '-' else open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
else:
|
||||
output = table.to_binary()
|
||||
try:
|
||||
stdout_binary = sys.stdout.buffer # Python 3
|
||||
except AttributeError:
|
||||
stdout_binary = sys.stdout
|
||||
with stdout_binary if args.output == '-' else open(args.output, 'wb') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
class InputError(RuntimeError):
|
||||
def __init__(self, e):
|
||||
super(InputError, self).__init__(e)
|
||||
|
||||
|
||||
class ValidationError(InputError):
|
||||
def __init__(self, partition, message):
|
||||
super(ValidationError, self).__init__(
|
||||
'Partition %s invalid: %s' % (partition.name, message))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except InputError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
|
@ -1,593 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# spiffsgen is a tool used to generate a spiffs image from a directory
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import division, print_function
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import struct
|
||||
|
||||
try:
|
||||
import typing
|
||||
|
||||
TSP = typing.TypeVar('TSP', bound='SpiffsObjPageWithIdx')
|
||||
ObjIdsItem = typing.Tuple[int, typing.Type[TSP]]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
SPIFFS_PH_FLAG_USED_FINAL_INDEX = 0xF8
|
||||
SPIFFS_PH_FLAG_USED_FINAL = 0xFC
|
||||
|
||||
SPIFFS_PH_FLAG_LEN = 1
|
||||
SPIFFS_PH_IX_SIZE_LEN = 4
|
||||
SPIFFS_PH_IX_OBJ_TYPE_LEN = 1
|
||||
SPIFFS_TYPE_FILE = 1
|
||||
|
||||
# Based on typedefs under spiffs_config.h
|
||||
SPIFFS_OBJ_ID_LEN = 2 # spiffs_obj_id
|
||||
SPIFFS_SPAN_IX_LEN = 2 # spiffs_span_ix
|
||||
SPIFFS_PAGE_IX_LEN = 2 # spiffs_page_ix
|
||||
SPIFFS_BLOCK_IX_LEN = 2 # spiffs_block_ix
|
||||
|
||||
|
||||
class SpiffsBuildConfig(object):
|
||||
def __init__(self,
|
||||
page_size, # type: int
|
||||
page_ix_len, # type: int
|
||||
block_size, # type: int
|
||||
block_ix_len, # type: int
|
||||
meta_len, # type: int
|
||||
obj_name_len, # type: int
|
||||
obj_id_len, # type: int
|
||||
span_ix_len, # type: int
|
||||
packed, # type: bool
|
||||
aligned, # type: bool
|
||||
endianness, # type: str
|
||||
use_magic, # type: bool
|
||||
use_magic_len, # type: bool
|
||||
aligned_obj_ix_tables # type: bool
|
||||
):
|
||||
if block_size % page_size != 0:
|
||||
raise RuntimeError('block size should be a multiple of page size')
|
||||
|
||||
self.page_size = page_size
|
||||
self.block_size = block_size
|
||||
self.obj_id_len = obj_id_len
|
||||
self.span_ix_len = span_ix_len
|
||||
self.packed = packed
|
||||
self.aligned = aligned
|
||||
self.obj_name_len = obj_name_len
|
||||
self.meta_len = meta_len
|
||||
self.page_ix_len = page_ix_len
|
||||
self.block_ix_len = block_ix_len
|
||||
self.endianness = endianness
|
||||
self.use_magic = use_magic
|
||||
self.use_magic_len = use_magic_len
|
||||
self.aligned_obj_ix_tables = aligned_obj_ix_tables
|
||||
|
||||
self.PAGES_PER_BLOCK = self.block_size // self.page_size
|
||||
self.OBJ_LU_PAGES_PER_BLOCK = int(math.ceil(self.block_size / self.page_size * self.obj_id_len / self.page_size))
|
||||
self.OBJ_USABLE_PAGES_PER_BLOCK = self.PAGES_PER_BLOCK - self.OBJ_LU_PAGES_PER_BLOCK
|
||||
|
||||
self.OBJ_LU_PAGES_OBJ_IDS_LIM = self.page_size // self.obj_id_len
|
||||
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN = self.obj_id_len + self.span_ix_len + SPIFFS_PH_FLAG_LEN
|
||||
|
||||
pad = 4 - (4 if self.OBJ_DATA_PAGE_HEADER_LEN % 4 == 0 else self.OBJ_DATA_PAGE_HEADER_LEN % 4)
|
||||
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED = self.OBJ_DATA_PAGE_HEADER_LEN + pad
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD = pad
|
||||
self.OBJ_DATA_PAGE_CONTENT_LEN = self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN
|
||||
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN = (self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED + SPIFFS_PH_IX_SIZE_LEN +
|
||||
SPIFFS_PH_IX_OBJ_TYPE_LEN + self.obj_name_len + self.meta_len)
|
||||
if aligned_obj_ix_tables:
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = (self.OBJ_INDEX_PAGES_HEADER_LEN + SPIFFS_PAGE_IX_LEN - 1) & ~(SPIFFS_PAGE_IX_LEN - 1)
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED - self.OBJ_INDEX_PAGES_HEADER_LEN
|
||||
else:
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = self.OBJ_INDEX_PAGES_HEADER_LEN
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = 0
|
||||
|
||||
self.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM = (self.page_size - self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED) // self.block_ix_len
|
||||
self.OBJ_INDEX_PAGES_OBJ_IDS_LIM = (self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED) // self.block_ix_len
|
||||
|
||||
|
||||
class SpiffsFullError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class SpiffsPage(object):
|
||||
_endianness_dict = {
|
||||
'little': '<',
|
||||
'big': '>'
|
||||
}
|
||||
|
||||
_len_dict = {
|
||||
1: 'B',
|
||||
2: 'H',
|
||||
4: 'I',
|
||||
8: 'Q'
|
||||
}
|
||||
|
||||
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
|
||||
self.build_config = build_config
|
||||
self.bix = bix
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SpiffsObjPageWithIdx(SpiffsPage):
|
||||
def __init__(self, obj_id, build_config): # type: (int, SpiffsBuildConfig) -> None
|
||||
super(SpiffsObjPageWithIdx, self).__init__(0, build_config)
|
||||
self.obj_id = obj_id
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SpiffsObjLuPage(SpiffsPage):
|
||||
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
|
||||
SpiffsPage.__init__(self, bix, build_config)
|
||||
|
||||
self.obj_ids_limit = self.build_config.OBJ_LU_PAGES_OBJ_IDS_LIM
|
||||
self.obj_ids = list() # type: typing.List[ObjIdsItem]
|
||||
|
||||
def _calc_magic(self, blocks_lim): # type: (int) -> int
|
||||
# Calculate the magic value mirroring computation done by the macro SPIFFS_MAGIC defined in
|
||||
# spiffs_nucleus.h
|
||||
magic = 0x20140529 ^ self.build_config.page_size
|
||||
if self.build_config.use_magic_len:
|
||||
magic = magic ^ (blocks_lim - self.bix)
|
||||
# narrow the result to build_config.obj_id_len bytes
|
||||
mask = (2 << (8 * self.build_config.obj_id_len)) - 1
|
||||
return magic & mask
|
||||
|
||||
def register_page(self, page): # type: (TSP) -> None
|
||||
if not self.obj_ids_limit > 0:
|
||||
raise SpiffsFullError()
|
||||
|
||||
obj_id = (page.obj_id, page.__class__)
|
||||
self.obj_ids.append(obj_id)
|
||||
self.obj_ids_limit -= 1
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
img = b''
|
||||
|
||||
for (obj_id, page_type) in self.obj_ids:
|
||||
if page_type == SpiffsObjIndexPage:
|
||||
obj_id ^= (1 << ((self.build_config.obj_id_len * 8) - 1))
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len], obj_id)
|
||||
|
||||
assert len(img) <= self.build_config.page_size
|
||||
|
||||
img += b'\xFF' * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
def magicfy(self, blocks_lim): # type: (int) -> None
|
||||
# Only use magic value if no valid obj id has been written to the spot, which is the
|
||||
# spot taken up by the last obj id on last lookup page. The parent is responsible
|
||||
# for determining which is the last lookup page and calling this function.
|
||||
remaining = self.obj_ids_limit
|
||||
empty_obj_id_dict = {
|
||||
1: 0xFF,
|
||||
2: 0xFFFF,
|
||||
4: 0xFFFFFFFF,
|
||||
8: 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
if remaining >= 2:
|
||||
for i in range(remaining):
|
||||
if i == remaining - 2:
|
||||
self.obj_ids.append((self._calc_magic(blocks_lim), SpiffsObjDataPage))
|
||||
break
|
||||
else:
|
||||
self.obj_ids.append((empty_obj_id_dict[self.build_config.obj_id_len], SpiffsObjDataPage))
|
||||
self.obj_ids_limit -= 1
|
||||
|
||||
|
||||
class SpiffsObjIndexPage(SpiffsObjPageWithIdx):
|
||||
def __init__(self, obj_id, span_ix, size, name, build_config
|
||||
): # type: (int, int, int, str, SpiffsBuildConfig) -> None
|
||||
super(SpiffsObjIndexPage, self).__init__(obj_id, build_config)
|
||||
self.span_ix = span_ix
|
||||
self.name = name
|
||||
self.size = size
|
||||
|
||||
if self.span_ix == 0:
|
||||
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM
|
||||
else:
|
||||
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_LIM
|
||||
|
||||
self.pages = list() # type: typing.List[int]
|
||||
|
||||
def register_page(self, page): # type: (SpiffsObjDataPage) -> None
|
||||
if not self.pages_lim > 0:
|
||||
raise SpiffsFullError
|
||||
|
||||
self.pages.append(page.offset)
|
||||
self.pages_lim -= 1
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
obj_id = self.obj_id ^ (1 << ((self.build_config.obj_id_len * 8) - 1))
|
||||
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len] +
|
||||
SpiffsPage._len_dict[self.build_config.span_ix_len] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
obj_id,
|
||||
self.span_ix,
|
||||
SPIFFS_PH_FLAG_USED_FINAL_INDEX)
|
||||
|
||||
# Add padding before the object index page specific information
|
||||
img += b'\xFF' * self.build_config.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD
|
||||
|
||||
# If this is the first object index page for the object, add filname, type
|
||||
# and size information
|
||||
if self.span_ix == 0:
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_IX_SIZE_LEN] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
self.size,
|
||||
SPIFFS_TYPE_FILE)
|
||||
|
||||
img += self.name.encode() + (b'\x00' * (
|
||||
(self.build_config.obj_name_len - len(self.name))
|
||||
+ self.build_config.meta_len
|
||||
+ self.build_config.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD))
|
||||
|
||||
# Finally, add the page index of daa pages
|
||||
for page in self.pages:
|
||||
page = page >> int(math.log(self.build_config.page_size, 2))
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.page_ix_len], page)
|
||||
|
||||
assert len(img) <= self.build_config.page_size
|
||||
|
||||
img += b'\xFF' * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsObjDataPage(SpiffsObjPageWithIdx):
|
||||
def __init__(self, offset, obj_id, span_ix, contents, build_config
|
||||
): # type: (int, int, int, bytes, SpiffsBuildConfig) -> None
|
||||
super(SpiffsObjDataPage, self).__init__(obj_id, build_config)
|
||||
self.span_ix = span_ix
|
||||
self.contents = contents
|
||||
self.offset = offset
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len] +
|
||||
SpiffsPage._len_dict[self.build_config.span_ix_len] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
self.obj_id,
|
||||
self.span_ix,
|
||||
SPIFFS_PH_FLAG_USED_FINAL)
|
||||
|
||||
img += self.contents
|
||||
|
||||
assert len(img) <= self.build_config.page_size
|
||||
|
||||
img += b'\xFF' * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsBlock(object):
|
||||
def _reset(self): # type: () -> None
|
||||
self.cur_obj_index_span_ix = 0
|
||||
self.cur_obj_data_span_ix = 0
|
||||
self.cur_obj_id = 0
|
||||
self.cur_obj_idx_page = None # type: typing.Optional[SpiffsObjIndexPage]
|
||||
|
||||
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
|
||||
self.build_config = build_config
|
||||
self.offset = bix * self.build_config.block_size
|
||||
self.remaining_pages = self.build_config.OBJ_USABLE_PAGES_PER_BLOCK
|
||||
self.pages = list() # type: typing.List[SpiffsPage]
|
||||
self.bix = bix
|
||||
|
||||
lu_pages = list()
|
||||
for i in range(self.build_config.OBJ_LU_PAGES_PER_BLOCK):
|
||||
page = SpiffsObjLuPage(self.bix, self.build_config)
|
||||
lu_pages.append(page)
|
||||
|
||||
self.pages.extend(lu_pages)
|
||||
|
||||
self.lu_page_iter = iter(lu_pages)
|
||||
self.lu_page = next(self.lu_page_iter)
|
||||
|
||||
self._reset()
|
||||
|
||||
def _register_page(self, page): # type: (TSP) -> None
|
||||
if isinstance(page, SpiffsObjDataPage):
|
||||
assert self.cur_obj_idx_page is not None
|
||||
self.cur_obj_idx_page.register_page(page) # can raise SpiffsFullError
|
||||
|
||||
try:
|
||||
self.lu_page.register_page(page)
|
||||
except SpiffsFullError:
|
||||
self.lu_page = next(self.lu_page_iter)
|
||||
try:
|
||||
self.lu_page.register_page(page)
|
||||
except AttributeError: # no next lookup page
|
||||
# Since the amount of lookup pages is pre-computed at every block instance,
|
||||
# this should never occur
|
||||
raise RuntimeError('invalid attempt to add page to a block when there is no more space in lookup')
|
||||
|
||||
self.pages.append(page)
|
||||
|
||||
def begin_obj(self, obj_id, size, name, obj_index_span_ix=0, obj_data_span_ix=0
|
||||
): # type: (int, int, str, int, int) -> None
|
||||
if not self.remaining_pages > 0:
|
||||
raise SpiffsFullError()
|
||||
self._reset()
|
||||
|
||||
self.cur_obj_id = obj_id
|
||||
self.cur_obj_index_span_ix = obj_index_span_ix
|
||||
self.cur_obj_data_span_ix = obj_data_span_ix
|
||||
|
||||
page = SpiffsObjIndexPage(obj_id, self.cur_obj_index_span_ix, size, name, self.build_config)
|
||||
self._register_page(page)
|
||||
|
||||
self.cur_obj_idx_page = page
|
||||
|
||||
self.remaining_pages -= 1
|
||||
self.cur_obj_index_span_ix += 1
|
||||
|
||||
def update_obj(self, contents): # type: (bytes) -> None
|
||||
if not self.remaining_pages > 0:
|
||||
raise SpiffsFullError()
|
||||
page = SpiffsObjDataPage(self.offset + (len(self.pages) * self.build_config.page_size),
|
||||
self.cur_obj_id, self.cur_obj_data_span_ix, contents, self.build_config)
|
||||
|
||||
self._register_page(page)
|
||||
|
||||
self.cur_obj_data_span_ix += 1
|
||||
self.remaining_pages -= 1
|
||||
|
||||
def end_obj(self): # type: () -> None
|
||||
self._reset()
|
||||
|
||||
def is_full(self): # type: () -> bool
|
||||
return self.remaining_pages <= 0
|
||||
|
||||
def to_binary(self, blocks_lim): # type: (int) -> bytes
|
||||
img = b''
|
||||
|
||||
if self.build_config.use_magic:
|
||||
for (idx, page) in enumerate(self.pages):
|
||||
if idx == self.build_config.OBJ_LU_PAGES_PER_BLOCK - 1:
|
||||
assert isinstance(page, SpiffsObjLuPage)
|
||||
page.magicfy(blocks_lim)
|
||||
img += page.to_binary()
|
||||
else:
|
||||
for page in self.pages:
|
||||
img += page.to_binary()
|
||||
|
||||
assert len(img) <= self.build_config.block_size
|
||||
|
||||
img += b'\xFF' * (self.build_config.block_size - len(img))
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsFS(object):
|
||||
def __init__(self, img_size, build_config): # type: (int, SpiffsBuildConfig) -> None
|
||||
if img_size % build_config.block_size != 0:
|
||||
raise RuntimeError('image size should be a multiple of block size')
|
||||
|
||||
self.img_size = img_size
|
||||
self.build_config = build_config
|
||||
|
||||
self.blocks = list() # type: typing.List[SpiffsBlock]
|
||||
self.blocks_lim = self.img_size // self.build_config.block_size
|
||||
self.remaining_blocks = self.blocks_lim
|
||||
self.cur_obj_id = 1 # starting object id
|
||||
|
||||
def _create_block(self): # type: () -> SpiffsBlock
|
||||
if self.is_full():
|
||||
raise SpiffsFullError('the image size has been exceeded')
|
||||
|
||||
block = SpiffsBlock(len(self.blocks), self.build_config)
|
||||
self.blocks.append(block)
|
||||
self.remaining_blocks -= 1
|
||||
return block
|
||||
|
||||
def is_full(self): # type: () -> bool
|
||||
return self.remaining_blocks <= 0
|
||||
|
||||
def create_file(self, img_path, file_path): # type: (str, str) -> None
|
||||
if len(img_path) > self.build_config.obj_name_len:
|
||||
raise RuntimeError("object name '%s' too long" % img_path)
|
||||
|
||||
name = img_path
|
||||
|
||||
with open(file_path, 'rb') as obj:
|
||||
contents = obj.read()
|
||||
|
||||
stream = io.BytesIO(contents)
|
||||
|
||||
try:
|
||||
block = self.blocks[-1]
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name)
|
||||
except (IndexError, SpiffsFullError):
|
||||
block = self._create_block()
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name)
|
||||
|
||||
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
|
||||
|
||||
while contents_chunk:
|
||||
try:
|
||||
block = self.blocks[-1]
|
||||
try:
|
||||
# This can fail because either (1) all the pages in block have been
|
||||
# used or (2) object index has been exhausted.
|
||||
block.update_obj(contents_chunk)
|
||||
except SpiffsFullError:
|
||||
# If its (1), use the outer exception handler
|
||||
if block.is_full():
|
||||
raise SpiffsFullError
|
||||
# If its (2), write another object index page
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name,
|
||||
obj_index_span_ix=block.cur_obj_index_span_ix,
|
||||
obj_data_span_ix=block.cur_obj_data_span_ix)
|
||||
continue
|
||||
except (IndexError, SpiffsFullError):
|
||||
# All pages in the block have been exhausted. Create a new block, copying
|
||||
# the previous state of the block to a new one for the continuation of the
|
||||
# current object
|
||||
prev_block = block
|
||||
block = self._create_block()
|
||||
block.cur_obj_id = prev_block.cur_obj_id
|
||||
block.cur_obj_idx_page = prev_block.cur_obj_idx_page
|
||||
block.cur_obj_data_span_ix = prev_block.cur_obj_data_span_ix
|
||||
block.cur_obj_index_span_ix = prev_block.cur_obj_index_span_ix
|
||||
continue
|
||||
|
||||
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
|
||||
|
||||
block.end_obj()
|
||||
|
||||
self.cur_obj_id += 1
|
||||
|
||||
def to_binary(self): # type: () -> bytes
|
||||
img = b''
|
||||
all_blocks = []
|
||||
for block in self.blocks:
|
||||
all_blocks.append(block.to_binary(self.blocks_lim))
|
||||
bix = len(self.blocks)
|
||||
if self.build_config.use_magic:
|
||||
# Create empty blocks with magic numbers
|
||||
while self.remaining_blocks > 0:
|
||||
block = SpiffsBlock(bix, self.build_config)
|
||||
all_blocks.append(block.to_binary(self.blocks_lim))
|
||||
self.remaining_blocks -= 1
|
||||
bix += 1
|
||||
else:
|
||||
# Just fill remaining spaces FF's
|
||||
all_blocks.append(b'\xFF' * (self.img_size - len(all_blocks) * self.build_config.block_size))
|
||||
img += b''.join([blk for blk in all_blocks])
|
||||
return img
|
||||
|
||||
|
||||
class CustomHelpFormatter(argparse.HelpFormatter):
|
||||
"""
|
||||
Similar to argparse.ArgumentDefaultsHelpFormatter, except it
|
||||
doesn't add the default value if "(default:" is already present.
|
||||
This helps in the case of options with action="store_false", like
|
||||
--no-magic or --no-magic-len.
|
||||
"""
|
||||
def _get_help_string(self, action): # type: (argparse.Action) -> str
|
||||
if action.help is None:
|
||||
return ''
|
||||
if '%(default)' not in action.help and '(default:' not in action.help:
|
||||
if action.default is not argparse.SUPPRESS:
|
||||
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
|
||||
if action.option_strings or action.nargs in defaulting_nargs:
|
||||
return action.help + ' (default: %(default)s)'
|
||||
return action.help
|
||||
|
||||
|
||||
def main(): # type: () -> None
|
||||
parser = argparse.ArgumentParser(description='SPIFFS Image Generator',
|
||||
formatter_class=CustomHelpFormatter)
|
||||
|
||||
parser.add_argument('image_size',
|
||||
help='Size of the created image')
|
||||
|
||||
parser.add_argument('base_dir',
|
||||
help='Path to directory from which the image will be created')
|
||||
|
||||
parser.add_argument('output_file',
|
||||
help='Created image output file path')
|
||||
|
||||
parser.add_argument('--page-size',
|
||||
help='Logical page size. Set to value same as CONFIG_SPIFFS_PAGE_SIZE.',
|
||||
type=int,
|
||||
default=256)
|
||||
|
||||
parser.add_argument('--block-size',
|
||||
help="Logical block size. Set to the same value as the flash chip's sector size (g_rom_flashchip.sector_size).",
|
||||
type=int,
|
||||
default=4096)
|
||||
|
||||
parser.add_argument('--obj-name-len',
|
||||
help='File full path maximum length. Set to value same as CONFIG_SPIFFS_OBJ_NAME_LEN.',
|
||||
type=int,
|
||||
default=32)
|
||||
|
||||
parser.add_argument('--meta-len',
|
||||
help='File metadata length. Set to value same as CONFIG_SPIFFS_META_LENGTH.',
|
||||
type=int,
|
||||
default=4)
|
||||
|
||||
parser.add_argument('--use-magic',
|
||||
dest='use_magic',
|
||||
help='Use magic number to create an identifiable SPIFFS image. Specify if CONFIG_SPIFFS_USE_MAGIC.',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--no-magic',
|
||||
dest='use_magic',
|
||||
help='Inverse of --use-magic (default: --use-magic is enabled)',
|
||||
action='store_false')
|
||||
|
||||
parser.add_argument('--use-magic-len',
|
||||
dest='use_magic_len',
|
||||
help='Use position in memory to create different magic numbers for each block. Specify if CONFIG_SPIFFS_USE_MAGIC_LENGTH.',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--no-magic-len',
|
||||
dest='use_magic_len',
|
||||
help='Inverse of --use-magic-len (default: --use-magic-len is enabled)',
|
||||
action='store_false')
|
||||
|
||||
parser.add_argument('--follow-symlinks',
|
||||
help='Take into account symbolic links during partition image creation.',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--big-endian',
|
||||
help='Specify if the target architecture is big-endian. If not specified, little-endian is assumed.',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--aligned-obj-ix-tables',
|
||||
action='store_true',
|
||||
help='Use aligned object index tables. Specify if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES is set.')
|
||||
|
||||
parser.set_defaults(use_magic=True, use_magic_len=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.base_dir):
|
||||
raise RuntimeError('given base directory %s does not exist' % args.base_dir)
|
||||
|
||||
with open(args.output_file, 'wb') as image_file:
|
||||
image_size = int(args.image_size, 0)
|
||||
spiffs_build_default = SpiffsBuildConfig(args.page_size, SPIFFS_PAGE_IX_LEN,
|
||||
args.block_size, SPIFFS_BLOCK_IX_LEN, args.meta_len,
|
||||
args.obj_name_len, SPIFFS_OBJ_ID_LEN, SPIFFS_SPAN_IX_LEN,
|
||||
True, True, 'big' if args.big_endian else 'little',
|
||||
args.use_magic, args.use_magic_len, args.aligned_obj_ix_tables)
|
||||
|
||||
spiffs = SpiffsFS(image_size, spiffs_build_default)
|
||||
|
||||
for root, dirs, files in os.walk(args.base_dir, followlinks=args.follow_symlinks):
|
||||
for f in files:
|
||||
full_path = os.path.join(root, f)
|
||||
spiffs.create_file('/' + os.path.relpath(full_path, args.base_dir).replace('\\', '/'), full_path)
|
||||
|
||||
image = spiffs.to_binary()
|
||||
|
||||
image_file.write(image)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||