├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ └── 🐛-bug-report.md ├── .gitignore ├── BLESerial.cpp ├── BLESerial.h ├── Bluetooth.h ├── Boards.h ├── Builds └── Handheld RNode │ ├── Case_Battery_Door.stl │ ├── Case_Bottom_Large_Battery.stl │ ├── Case_Bottom_No_Battery.stl │ ├── Case_Bottom_Small_Battery.stl │ ├── Case_Top.stl │ ├── Handheld_RNode_Recipe.pdf │ ├── LED_Guide.stl │ ├── LED_Window.stl │ └── Power_Switch.stl ├── Config.h ├── Console.h ├── Console ├── Makefile ├── assets │ ├── css │ │ ├── water.css │ │ └── yond.woff2 │ ├── gfx │ │ ├── cs.webp │ │ ├── icon.png │ │ ├── nn.webp │ │ ├── ph.png │ │ ├── rnode_iso.webp │ │ └── sideband.webp │ ├── images │ │ ├── 3_conv.webp │ │ ├── an1.webp │ │ ├── bg1ds1.webp │ │ ├── bg1ds2.webp │ │ ├── bg_h_1.webp │ │ ├── bg_h_2.webp │ │ ├── g1p.webp │ │ ├── g2p.webp │ │ ├── g3p.webp │ │ ├── g4p.webp │ │ ├── lora_rnodes.webp │ │ ├── nn_an.webp │ │ ├── nn_conv.webp │ │ └── nn_init.webp │ └── stl │ │ └── Handheld_RNode_Parts.7z ├── build.py └── source │ ├── builds │ ├── ap.md │ ├── handheld.md │ ├── micropylon.md │ └── wallmount.md │ ├── contact.md │ ├── contribute.md │ ├── guides │ ├── install_firmware.md │ ├── loracomms.md │ ├── make_rnodes.md │ └── tnc_mode.md │ ├── help.md │ ├── index.md │ ├── learn.md │ ├── qa.md │ ├── recipes.md │ ├── replicate.md │ ├── rnode.md │ ├── s_lxmf.md │ ├── s_nn.md │ ├── s_rns.md │ ├── s_rnsh.md │ ├── s_sideband.md │ ├── sell_rnodes.md │ ├── software.md │ └── supported.md ├── Device.h ├── Display.h ├── Documentation ├── README.md ├── RNode_v1_Manual.pdf ├── images │ ├── 126dcfe92fb7.webp │ ├── devboards_1.webp │ └── rnv21_bgp.webp └── rnfw_1.jpg ├── Fonts ├── Org_01.h └── PicoPixel.h ├── Framing.h ├── Graphics.h ├── Input.h ├── LICENSE ├── MD5.cpp ├── MD5.h ├── Makefile ├── Modem.h ├── Power.h ├── Python Module ├── Example.py └── RNode.py ├── README.md ├── RNode_Firmware.ino ├── ROM.h ├── Release ├── README.md ├── console_image.bin └── esptool │ ├── esptool.py │ ├── gen_esp32part.py │ └── spiffsgen.py ├── ST7789.h ├── Utilities.h ├── arduino-cli.yaml ├── esp32_btbufs.py ├── partition_hashes ├── release_hashes.py ├── sx126x.cpp ├── sx126x.h ├── sx127x.cpp ├── sx127x.h ├── sx128x.cpp └── sx128x.h /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ✨ Feature Request or Idea 4 | url: https://github.com/markqvist/Reticulum/discussions/new?category=ideas 5 | about: Propose and discuss features and ideas 6 | - name: 💬 Questions, Help & Discussion 7 | about: Ask anything, or get help 8 | url: https://github.com/markqvist/Reticulum/discussions/new/choose 9 | - name: 📖 Read the Reticulum Manual 10 | url: https://markqvist.github.io/Reticulum/manual/ 11 | about: The complete documentation for Reticulum 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/🐛-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug Report" 3 | about: Report a reproducible bug 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Read the Contribution Guidelines** 11 | 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**. 12 | 13 | - The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**. 14 | - 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**. 15 | - After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), delete this section from your bug report. 16 | 17 | **Describe the Bug** 18 | First of all: Is this really a bug? Is it reproducible? 19 | 20 | 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. 21 | 22 | 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**. 23 | 24 | **To Reproduce** 25 | Describe in detail how to reproduce the bug. 26 | 27 | **Expected Behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Logs & Screenshots** 31 | 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. 32 | 33 | **System Information** 34 | - OS and version 35 | - Python version 36 | - Program version 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.hex 3 | *.pyc 4 | TODO 5 | Release/*.hex 6 | Release/*.zip 7 | Release/*.json 8 | Console/build 9 | build/* 10 | -------------------------------------------------------------------------------- /BLESerial.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "Boards.h" 17 | 18 | #if PLATFORM != PLATFORM_NRF52 19 | #if HAS_BLE 20 | 21 | #include "BLESerial.h" 22 | 23 | uint32_t bt_passkey_callback(); 24 | void bt_passkey_notify_callback(uint32_t passkey); 25 | bool bt_security_request_callback(); 26 | void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result); 27 | bool bt_confirm_pin_callback(uint32_t pin); 28 | void bt_connect_callback(BLEServer *server); 29 | void bt_disconnect_callback(BLEServer *server); 30 | bool bt_client_authenticated(); 31 | 32 | uint32_t BLESerial::onPassKeyRequest() { return bt_passkey_callback(); } 33 | void BLESerial::onPassKeyNotify(uint32_t passkey) { bt_passkey_notify_callback(passkey); } 34 | bool BLESerial::onSecurityRequest() { return bt_security_request_callback(); } 35 | void BLESerial::onAuthenticationComplete(esp_ble_auth_cmpl_t auth_result) { bt_authentication_complete_callback(auth_result); } 36 | void BLESerial::onConnect(BLEServer *server) { bt_connect_callback(server); } 37 | void BLESerial::onDisconnect(BLEServer *server) { bt_disconnect_callback(server); ble_server->startAdvertising(); } 38 | bool BLESerial::onConfirmPIN(uint32_t pin) { return bt_confirm_pin_callback(pin); }; 39 | bool BLESerial::connected() { return ble_server->getConnectedCount() > 0; } 40 | 41 | int BLESerial::read() { 42 | int result = this->rx_buffer.pop(); 43 | if (result == '\n') { this->numAvailableLines--; } 44 | return result; 45 | } 46 | 47 | size_t BLESerial::readBytes(uint8_t *buffer, size_t bufferSize) { 48 | int i = 0; 49 | while (i < bufferSize && available()) { buffer[i] = (uint8_t)this->rx_buffer.pop(); i++; } 50 | return i; 51 | } 52 | 53 | int BLESerial::peek() { 54 | if (this->rx_buffer.getLength() == 0) return -1; 55 | return this->rx_buffer.get(0); 56 | } 57 | 58 | int BLESerial::available() { return this->rx_buffer.getLength(); } 59 | 60 | size_t BLESerial::print(const char *str) { 61 | if (ble_server->getConnectedCount() <= 0) return 0; 62 | size_t written = 0; for (size_t i = 0; str[i] != '\0'; i++) { written += this->write(str[i]); } 63 | flush(); 64 | 65 | return written; 66 | } 67 | 68 | size_t BLESerial::write(const uint8_t *buffer, size_t bufferSize) { 69 | if (ble_server->getConnectedCount() <= 0) { return 0; } else { 70 | size_t written = 0; for (int i = 0; i < bufferSize; i++) { written += this->write(buffer[i]); } 71 | flush(); 72 | 73 | return written; 74 | } 75 | } 76 | 77 | size_t BLESerial::write(uint8_t byte) { 78 | if (bt_client_authenticated()) { 79 | if (ble_server->getConnectedCount() <= 0) { return 0; } else { 80 | this->transmitBuffer[this->transmitBufferLength] = byte; 81 | this->transmitBufferLength++; 82 | if (this->transmitBufferLength == maxTransferSize) { flush(); } 83 | return 1; 84 | } 85 | } else { 86 | return 0; 87 | } 88 | } 89 | 90 | void BLESerial::flush() { 91 | if (this->transmitBufferLength > 0) { 92 | TxCharacteristic->setValue(this->transmitBuffer, this->transmitBufferLength); 93 | this->transmitBufferLength = 0; 94 | this->lastFlushTime = millis(); 95 | TxCharacteristic->notify(true); 96 | } 97 | } 98 | 99 | void BLESerial::disconnect() { 100 | if (ble_server->getConnectedCount() > 0) { 101 | uint16_t conn_id = ble_server->getConnId(); 102 | // Serial.printf("Have connected: %d\n", conn_id); 103 | ble_server->disconnect(conn_id); 104 | // Serial.println("Disconnected"); 105 | } else { 106 | // Serial.println("No connected"); 107 | } 108 | } 109 | 110 | void BLESerial::begin(const char *name) { 111 | ConnectedDeviceCount = 0; 112 | BLEDevice::init(name); 113 | 114 | esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); 115 | esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9); 116 | esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN ,ESP_PWR_LVL_P9); 117 | 118 | ble_server = BLEDevice::createServer(); 119 | ble_server->setCallbacks(this); 120 | BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); 121 | BLEDevice::setSecurityCallbacks(this); 122 | 123 | SetupSerialService(); 124 | this->startAdvertising(); 125 | } 126 | 127 | void BLESerial::startAdvertising() { 128 | ble_adv = BLEDevice::getAdvertising(); 129 | ble_adv->addServiceUUID(BLE_SERIAL_SERVICE_UUID); 130 | ble_adv->setMinPreferred(0x20); 131 | ble_adv->setMaxPreferred(0x40); 132 | ble_adv->setScanResponse(true); 133 | ble_adv->start(); 134 | } 135 | 136 | void BLESerial::stopAdvertising() { 137 | ble_adv = BLEDevice::getAdvertising(); 138 | ble_adv->stop(); 139 | } 140 | 141 | void BLESerial::end() { BLEDevice::deinit(); } 142 | 143 | void BLESerial::onWrite(BLECharacteristic *characteristic) { 144 | if (characteristic->getUUID().toString() == BLE_RX_UUID) { 145 | auto value = characteristic->getValue(); 146 | for (int i = 0; i < value.length(); i++) { rx_buffer.push(value[i]); } 147 | } 148 | } 149 | 150 | void BLESerial::SetupSerialService() { 151 | SerialService = ble_server->createService(BLE_SERIAL_SERVICE_UUID); 152 | 153 | RxCharacteristic = SerialService->createCharacteristic(BLE_RX_UUID, BLECharacteristic::PROPERTY_WRITE); 154 | RxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM); 155 | RxCharacteristic->addDescriptor(new BLE2902()); 156 | RxCharacteristic->setWriteProperty(true); 157 | RxCharacteristic->setCallbacks(this); 158 | 159 | TxCharacteristic = SerialService->createCharacteristic(BLE_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY); 160 | TxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); 161 | TxCharacteristic->addDescriptor(new BLE2902()); 162 | TxCharacteristic->setNotifyProperty(true); 163 | TxCharacteristic->setReadProperty(true); 164 | 165 | SerialService->start(); 166 | } 167 | 168 | BLESerial::BLESerial() { } 169 | 170 | #endif 171 | #endif -------------------------------------------------------------------------------- /BLESerial.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "Boards.h" 17 | 18 | #if PLATFORM != PLATFORM_NRF52 19 | #if HAS_BLE 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | template 29 | class BLEFIFO { 30 | private: 31 | uint8_t buffer[n]; 32 | int head = 0; 33 | int tail = 0; 34 | 35 | public: 36 | void push(uint8_t value) { 37 | buffer[head] = value; 38 | head = (head + 1) % n; 39 | if (head == tail) { tail = (tail + 1) % n; } 40 | } 41 | 42 | int pop() { 43 | if (head == tail) { 44 | return -1; 45 | } else { 46 | uint8_t value = buffer[tail]; 47 | tail = (tail + 1) % n; 48 | return value; 49 | } 50 | } 51 | 52 | void clear() { head = 0; tail = 0; } 53 | 54 | int get(size_t index) { 55 | if (index >= this->getLength()) { 56 | return -1; 57 | } else { 58 | return buffer[(tail + index) % n]; 59 | } 60 | } 61 | 62 | size_t getLength() { 63 | if (head >= tail) { 64 | return head - tail; 65 | } else { 66 | return n - tail + head; 67 | } 68 | } 69 | }; 70 | 71 | #define RX_BUFFER_SIZE 6144 72 | #define BLE_BUFFER_SIZE 512 // Must fit in max GATT attribute length 73 | #define MIN_MTU 50 74 | 75 | class BLESerial : public BLECharacteristicCallbacks, public BLEServerCallbacks, public BLESecurityCallbacks, public Stream { 76 | public: 77 | BLESerial(); 78 | 79 | void begin(const char *name); 80 | void end(); 81 | void disconnect(); 82 | void startAdvertising(); 83 | void stopAdvertising(); 84 | void onWrite(BLECharacteristic *characteristic); 85 | int available(); 86 | int peek(); 87 | int read(); 88 | size_t readBytes(uint8_t *buffer, size_t bufferSize); 89 | size_t write(uint8_t byte); 90 | size_t write(const uint8_t *buffer, size_t bufferSize); 91 | size_t print(const char *value); 92 | void flush(); 93 | void onConnect(BLEServer *server); 94 | void onDisconnect(BLEServer *server); 95 | 96 | uint32_t onPassKeyRequest(); 97 | void onPassKeyNotify(uint32_t passkey); 98 | bool onSecurityRequest(); 99 | void onAuthenticationComplete(esp_ble_auth_cmpl_t); 100 | bool onConfirmPIN(uint32_t pin); 101 | 102 | bool connected(); 103 | 104 | BLEServer *ble_server; 105 | BLEAdvertising *ble_adv; 106 | BLEService *SerialService; 107 | BLECharacteristic *TxCharacteristic; 108 | BLECharacteristic *RxCharacteristic; 109 | size_t transmitBufferLength; 110 | unsigned long long lastFlushTime; 111 | 112 | private: 113 | BLESerial(BLESerial const &other) = delete; 114 | void operator=(BLESerial const &other) = delete; 115 | 116 | BLEFIFO rx_buffer; 117 | size_t numAvailableLines; 118 | uint8_t transmitBuffer[BLE_BUFFER_SIZE]; 119 | 120 | int ConnectedDeviceCount; 121 | void SetupSerialService(); 122 | 123 | uint16_t peerMTU; 124 | uint16_t maxTransferSize = BLE_BUFFER_SIZE; 125 | 126 | bool checkMTU(); 127 | 128 | const char *BLE_SERIAL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; 129 | const char *BLE_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; 130 | const char *BLE_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; 131 | 132 | bool started = false; 133 | }; 134 | 135 | #endif 136 | #endif -------------------------------------------------------------------------------- /Builds/Handheld RNode/Case_Battery_Door.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Case_Battery_Door.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Case_Bottom_Large_Battery.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Case_Bottom_Large_Battery.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Case_Bottom_No_Battery.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Case_Bottom_No_Battery.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Case_Bottom_Small_Battery.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Case_Bottom_Small_Battery.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Case_Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Case_Top.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Handheld_RNode_Recipe.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Handheld_RNode_Recipe.pdf -------------------------------------------------------------------------------- /Builds/Handheld RNode/LED_Guide.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/LED_Guide.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/LED_Window.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/LED_Window.stl -------------------------------------------------------------------------------- /Builds/Handheld RNode/Power_Switch.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Builds/Handheld RNode/Power_Switch.stl -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "ROM.h" 17 | #include "Boards.h" 18 | 19 | #ifndef CONFIG_H 20 | #define CONFIG_H 21 | 22 | #define MAJ_VERS 0x01 23 | #define MIN_VERS 0x52 24 | 25 | #define MODE_HOST 0x11 26 | #define MODE_TNC 0x12 27 | 28 | #define CABLE_STATE_DISCONNECTED 0x00 29 | #define CABLE_STATE_CONNECTED 0x01 30 | uint8_t cable_state = CABLE_STATE_DISCONNECTED; 31 | 32 | #define BT_STATE_NA 0xff 33 | #define BT_STATE_OFF 0x00 34 | #define BT_STATE_ON 0x01 35 | #define BT_STATE_PAIRING 0x02 36 | #define BT_STATE_CONNECTED 0x03 37 | uint8_t bt_state = BT_STATE_NA; 38 | uint32_t bt_ssp_pin = 0; 39 | bool bt_ready = false; 40 | bool bt_enabled = false; 41 | bool bt_allow_pairing = false; 42 | 43 | #define M_FRQ_S 27388122 44 | #define M_FRQ_R 27388061 45 | bool console_active = false; 46 | bool modem_installed = false; 47 | 48 | #define MTU 508 49 | #define SINGLE_MTU 255 50 | #define HEADER_L 1 51 | #define MIN_L 1 52 | #define CMD_L 64 53 | 54 | bool mw_radio_online = false; 55 | 56 | #define eeprom_addr(a) (a+EEPROM_OFFSET) 57 | 58 | #if (MODEM == SX1262 || MODEM == SX1280) && defined(NRF52840_XXAA) 59 | SPIClass spiModem(NRF_SPIM2, pin_miso, pin_sclk, pin_mosi); 60 | #endif 61 | 62 | // MCU independent configuration parameters 63 | const long serial_baudrate = 115200; 64 | 65 | // SX1276 RSSI offset to get dBm value from 66 | // packet RSSI register 67 | const int rssi_offset = 157; 68 | 69 | // Default LoRa settings 70 | #define PHY_HEADER_LORA_SYMBOLS 20 71 | #define PHY_CRC_LORA_BITS 16 72 | #define LORA_PREAMBLE_SYMBOLS_MIN 18 73 | #define LORA_PREAMBLE_TARGET_MS 24 74 | #define LORA_PREAMBLE_FAST_DELTA 18 75 | #define LORA_FAST_THRESHOLD_BPS 30E3 76 | #define LORA_LIMIT_THRESHOLD_BPS 60E3 77 | long lora_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; 78 | long lora_preamble_time_ms = 0; 79 | long lora_header_time_ms = 0; 80 | float lora_symbol_time_ms = 0.0; 81 | float lora_symbol_rate = 0.0; 82 | float lora_us_per_byte = 0.0; 83 | bool lora_low_datarate = false; 84 | bool lora_limit_rate = false; 85 | 86 | // CSMA Parameters 87 | #define CSMA_SIFS_MS 0 88 | #define CSMA_POST_TX_YIELD_SLOTS 3 89 | #define CSMA_SLOT_MAX_MS 100 90 | #define CSMA_SLOT_MIN_MS 24 91 | #define CSMA_SLOT_MIN_FAST_DELTA 18 92 | #define CSMA_SLOT_SYMBOLS 12 93 | #define CSMA_CW_BANDS 4 94 | #define CSMA_CW_MIN 0 95 | #define CSMA_CW_PER_BAND_WINDOWS 15 96 | #define CSMA_BAND_1_MAX_AIRTIME 7 97 | #define CSMA_BAND_N_MIN_AIRTIME 85 98 | #define CSMA_INFR_THRESHOLD_DB 12 99 | bool interference_detected = false; 100 | bool avoid_interference = true; 101 | int csma_slot_ms = CSMA_SLOT_MIN_MS; 102 | unsigned long difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms; 103 | unsigned long difs_wait_start = -1; 104 | unsigned long cw_wait_start = -1; 105 | unsigned long cw_wait_target = -1; 106 | unsigned long cw_wait_passed = 0; 107 | int csma_cw = -1; 108 | uint8_t cw_band = 1; 109 | uint8_t cw_min = 0; 110 | uint8_t cw_max = CSMA_CW_PER_BAND_WINDOWS; 111 | 112 | // LoRa settings 113 | int lora_sf = 0; 114 | int lora_cr = 5; 115 | int lora_txp = 0xFF; 116 | uint32_t lora_bw = 0; 117 | uint32_t lora_freq = 0; 118 | uint32_t lora_bitrate = 0; 119 | 120 | // Operational variables 121 | bool radio_locked = true; 122 | bool radio_online = false; 123 | bool community_fw = true; 124 | bool hw_ready = false; 125 | bool radio_error = false; 126 | bool disp_ready = false; 127 | bool pmu_ready = false; 128 | bool promisc = false; 129 | bool implicit = false; 130 | bool memory_low = false; 131 | uint8_t implicit_l = 0; 132 | 133 | uint8_t op_mode = MODE_HOST; 134 | uint8_t model = 0x00; 135 | uint8_t hwrev = 0x00; 136 | 137 | #define NOISE_FLOOR_SAMPLES 64 138 | int noise_floor = -292; 139 | int current_rssi = -292; 140 | int last_rssi = -292; 141 | uint8_t last_rssi_raw = 0x00; 142 | uint8_t last_snr_raw = 0x80; 143 | uint8_t seq = 0xFF; 144 | uint16_t read_len = 0; 145 | 146 | // Incoming packet buffer 147 | uint8_t pbuf[MTU]; 148 | 149 | // KISS command buffer 150 | uint8_t cmdbuf[CMD_L]; 151 | 152 | // LoRa transmit buffer 153 | uint8_t tbuf[MTU]; 154 | 155 | uint32_t stat_rx = 0; 156 | uint32_t stat_tx = 0; 157 | 158 | #define STATUS_INTERVAL_MS 3 159 | #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 160 | #define DCD_SAMPLES 2500 161 | #define UTIL_UPDATE_INTERVAL_MS 1000 162 | #define UTIL_UPDATE_INTERVAL (UTIL_UPDATE_INTERVAL_MS/STATUS_INTERVAL_MS) 163 | #define AIRTIME_LONGTERM 3600 164 | #define AIRTIME_LONGTERM_MS (AIRTIME_LONGTERM*1000) 165 | #define AIRTIME_BINLEN_MS (STATUS_INTERVAL_MS*DCD_SAMPLES) 166 | #define AIRTIME_BINS ((AIRTIME_LONGTERM*1000)/AIRTIME_BINLEN_MS) 167 | bool util_samples[DCD_SAMPLES]; 168 | uint16_t airtime_bins[AIRTIME_BINS]; 169 | float longterm_bins[AIRTIME_BINS]; 170 | int dcd_sample = 0; 171 | float local_channel_util = 0.0; 172 | float total_channel_util = 0.0; 173 | float longterm_channel_util = 0.0; 174 | float airtime = 0.0; 175 | float longterm_airtime = 0.0; 176 | #define current_airtime_bin(void) (millis()%AIRTIME_LONGTERM_MS)/AIRTIME_BINLEN_MS 177 | #endif 178 | float st_airtime_limit = 0.0; 179 | float lt_airtime_limit = 0.0; 180 | bool airtime_lock = false; 181 | 182 | bool stat_signal_detected = false; 183 | bool stat_signal_synced = false; 184 | bool stat_rx_ongoing = false; 185 | bool dcd = false; 186 | bool dcd_led = false; 187 | bool dcd_waiting = false; 188 | long dcd_wait_until = 0; 189 | uint16_t dcd_count = 0; 190 | uint16_t dcd_threshold = 2; 191 | 192 | uint32_t status_interval_ms = STATUS_INTERVAL_MS; 193 | uint32_t last_status_update = 0; 194 | uint32_t last_dcd = 0; 195 | 196 | // Power management 197 | #define BATTERY_STATE_UNKNOWN 0x00 198 | #define BATTERY_STATE_DISCHARGING 0x01 199 | #define BATTERY_STATE_CHARGING 0x02 200 | #define BATTERY_STATE_CHARGED 0x03 201 | bool battery_installed = false; 202 | bool battery_indeterminate = false; 203 | bool external_power = false; 204 | bool battery_ready = false; 205 | float battery_voltage = 0.0; 206 | float battery_percent = 0.0; 207 | uint8_t battery_state = 0x00; 208 | uint8_t display_intensity = 0xFF; 209 | uint8_t display_addr = 0xFF; 210 | volatile bool display_updating = false; 211 | bool display_blanking_enabled = false; 212 | bool display_diagnostics = true; 213 | bool device_init_done = false; 214 | bool eeprom_ok = false; 215 | bool firmware_update_mode = false; 216 | bool serial_in_frame = false; 217 | 218 | // Boot flags 219 | #define START_FROM_BOOTLOADER 0x01 220 | #define START_FROM_POWERON 0x02 221 | #define START_FROM_BROWNOUT 0x03 222 | #define START_FROM_JTAG 0x04 223 | 224 | #endif 225 | -------------------------------------------------------------------------------- /Console.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "SD.h" 22 | #include "SPI.h" 23 | 24 | #if HAS_SD 25 | SPIClass *spi = NULL; 26 | #endif 27 | 28 | 29 | #if CONFIG_IDF_TARGET_ESP32 30 | #include "esp32/rom/rtc.h" 31 | #elif CONFIG_IDF_TARGET_ESP32S2 32 | #include "esp32s2/rom/rtc.h" 33 | #elif CONFIG_IDF_TARGET_ESP32C3 34 | #include "esp32c3/rom/rtc.h" 35 | #elif CONFIG_IDF_TARGET_ESP32S3 36 | #include "esp32s3/rom/rtc.h" 37 | #else 38 | #error Target CONFIG_IDF_TARGET is not supported 39 | #endif 40 | 41 | WebServer server(80); 42 | 43 | void console_dbg(String msg) { 44 | Serial.print("[Webserver] "); 45 | Serial.println(msg); 46 | } 47 | 48 | bool exists(String path){ 49 | bool yes = false; 50 | File file = SPIFFS.open(path, "r"); 51 | if(!file.isDirectory()){ 52 | yes = true; 53 | } 54 | file.close(); 55 | return yes; 56 | } 57 | 58 | String console_get_content_type(String filename) { 59 | if (server.hasArg("download")) { 60 | return "application/octet-stream"; 61 | } else if (filename.endsWith(".htm")) { 62 | return "text/html"; 63 | } else if (filename.endsWith(".html")) { 64 | return "text/html"; 65 | } else if (filename.endsWith(".css")) { 66 | return "text/css"; 67 | } else if (filename.endsWith(".js")) { 68 | return "application/javascript"; 69 | } else if (filename.endsWith(".png")) { 70 | return "image/png"; 71 | } else if (filename.endsWith(".gif")) { 72 | return "image/gif"; 73 | } else if (filename.endsWith(".jpg")) { 74 | return "image/jpeg"; 75 | } else if (filename.endsWith(".ico")) { 76 | return "image/x-icon"; 77 | } else if (filename.endsWith(".xml")) { 78 | return "text/xml"; 79 | } else if (filename.endsWith(".pdf")) { 80 | return "application/x-pdf"; 81 | } else if (filename.endsWith(".zip")) { 82 | return "application/x-zip"; 83 | } else if (filename.endsWith(".gz")) { 84 | return "application/x-gzip"; 85 | } else if (filename.endsWith(".whl")) { 86 | return "application/octet-stream"; 87 | } 88 | return "text/plain"; 89 | } 90 | 91 | bool console_serve_file(String path) { 92 | console_dbg("Request for: "+path); 93 | if (path.endsWith("/")) { 94 | path += "index.html"; 95 | } 96 | 97 | if (path == "/r/manual/index.html") { 98 | path = "/m.html"; 99 | } 100 | if (path == "/r/manual/Reticulum Manual.pdf") { 101 | path = "/h.html"; 102 | } 103 | 104 | 105 | String content_type = console_get_content_type(path); 106 | String pathWithGz = path + ".gz"; 107 | if (exists(pathWithGz) || exists(path)) { 108 | if (exists(pathWithGz)) { 109 | path += ".gz"; 110 | } 111 | 112 | File file = SPIFFS.open(path, "r"); 113 | console_dbg("Serving file to client"); 114 | server.streamFile(file, content_type); 115 | file.close(); 116 | 117 | console_dbg("File serving done\n"); 118 | return true; 119 | } else { 120 | int spos = pathWithGz.lastIndexOf('/'); 121 | if (spos > 0) { 122 | String remap_path = "/d"; 123 | remap_path.concat(pathWithGz.substring(spos)); 124 | Serial.println(remap_path); 125 | 126 | if (exists(remap_path)) { 127 | File file = SPIFFS.open(remap_path, "r"); 128 | console_dbg("Serving remapped file to client"); 129 | server.streamFile(file, content_type); 130 | console_dbg("Closing file"); 131 | file.close(); 132 | 133 | console_dbg("File serving done\n"); 134 | return true; 135 | } 136 | } 137 | } 138 | 139 | console_dbg("Error: Could not open file for serving\n"); 140 | return false; 141 | } 142 | 143 | void console_register_pages() { 144 | server.onNotFound([]() { 145 | if (!console_serve_file(server.uri())) { 146 | server.send(404, "text/plain", "Not Found"); 147 | } 148 | }); 149 | } 150 | 151 | void console_start() { 152 | Serial.println(""); 153 | console_dbg("Starting Access Point..."); 154 | WiFi.softAP(bt_devname); 155 | delay(150); 156 | IPAddress ip(10, 0, 0, 1); 157 | IPAddress nm(255, 255, 255, 0); 158 | WiFi.softAPConfig(ip, ip, nm); 159 | 160 | if(!SPIFFS.begin(true)){ 161 | console_dbg("Error: Could not mount SPIFFS"); 162 | return; 163 | } else { 164 | console_dbg("SPIFFS Ready"); 165 | } 166 | 167 | #if HAS_SD 168 | spi = new SPIClass(HSPI); 169 | spi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS); 170 | if(!SD.begin(SD_CS, *spi)){ 171 | console_dbg("No SD card inserted"); 172 | } else { 173 | uint8_t cardType = SD.cardType(); 174 | if(cardType == CARD_NONE){ 175 | console_dbg("No SD card type"); 176 | } else { 177 | console_dbg("SD Card Type: "); 178 | if(cardType == CARD_MMC){ 179 | console_dbg("MMC"); 180 | } else if(cardType == CARD_SD){ 181 | console_dbg("SDSC"); 182 | } else if(cardType == CARD_SDHC){ 183 | console_dbg("SDHC"); 184 | } else { 185 | console_dbg("UNKNOWN"); 186 | } 187 | uint64_t cardSize = SD.cardSize() / (1024 * 1024); 188 | Serial.printf("SD Card Size: %lluMB\n", cardSize); 189 | } 190 | } 191 | #endif 192 | 193 | console_register_pages(); 194 | server.begin(); 195 | led_indicate_console(); 196 | } 197 | 198 | void console_loop(){ 199 | server.handleClient(); 200 | // Internally, this yields the thread and allows 201 | // other tasks to run. 202 | delay(2); 203 | } -------------------------------------------------------------------------------- /Console/Makefile: -------------------------------------------------------------------------------- 1 | PATH_RETICULUM_WEBSITE=../../sites/reticulum.network 2 | PATH_PACKAGES=../../dist_archive 3 | 4 | clean: 5 | @echo Cleaning... 6 | @-rm -rf ./build 7 | 8 | dirs: 9 | @mkdir -p ./build 10 | @mkdir -p ./build/3d 11 | @mkdir -p ./build/pkg 12 | @mkdir -p ./build/css 13 | @mkdir -p ./build/gfx 14 | @mkdir -p ./build/images 15 | 16 | pages: 17 | python ./build.py 18 | 19 | pages-debug: 20 | python ./build.py --no-gz --no-remap 21 | 22 | sourcepack: 23 | @echo Packing firmware sources... 24 | 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 25 | 26 | data: 27 | @echo Including assets... 28 | @cp assets/css/* build/css/ 29 | @cp assets/gfx/* build/gfx/ 30 | @cp assets/images/* build/images/ 31 | @cp assets/stl/* build/3d/ 32 | #@cp assets/pkg/* build/pkg/ 33 | # @cp assets/scripts/* build/scripts/ 34 | # @cp -r ../../Reticulum/docs/manual/* build/reticulum_manual/ 35 | # @cp -r ../../Reticulum/docs/Reticulum\ Manual.pdf build/reticulum_manual/ 36 | 37 | external: 38 | make -C $(PATH_RETICULUM_WEBSITE) clean website 39 | -rm -r $(PATH_PACKAGES)/reticulum.network 40 | cp -r $(PATH_RETICULUM_WEBSITE)/build $(PATH_PACKAGES)/reticulum.network 41 | 42 | site: clean external dirs data sourcepack pages 43 | 44 | local: clean external dirs data sourcepack pages-debug 45 | 46 | serve: 47 | python -m http.server 7777 --bind 127.0.0.1 --directory ./build 48 | -------------------------------------------------------------------------------- /Console/assets/css/yond.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/css/yond.woff2 -------------------------------------------------------------------------------- /Console/assets/gfx/cs.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/cs.webp -------------------------------------------------------------------------------- /Console/assets/gfx/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/icon.png -------------------------------------------------------------------------------- /Console/assets/gfx/nn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/nn.webp -------------------------------------------------------------------------------- /Console/assets/gfx/ph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/ph.png -------------------------------------------------------------------------------- /Console/assets/gfx/rnode_iso.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/rnode_iso.webp -------------------------------------------------------------------------------- /Console/assets/gfx/sideband.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/gfx/sideband.webp -------------------------------------------------------------------------------- /Console/assets/images/3_conv.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/3_conv.webp -------------------------------------------------------------------------------- /Console/assets/images/an1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/an1.webp -------------------------------------------------------------------------------- /Console/assets/images/bg1ds1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/bg1ds1.webp -------------------------------------------------------------------------------- /Console/assets/images/bg1ds2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/bg1ds2.webp -------------------------------------------------------------------------------- /Console/assets/images/bg_h_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/bg_h_1.webp -------------------------------------------------------------------------------- /Console/assets/images/bg_h_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/bg_h_2.webp -------------------------------------------------------------------------------- /Console/assets/images/g1p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/g1p.webp -------------------------------------------------------------------------------- /Console/assets/images/g2p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/g2p.webp -------------------------------------------------------------------------------- /Console/assets/images/g3p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/g3p.webp -------------------------------------------------------------------------------- /Console/assets/images/g4p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/g4p.webp -------------------------------------------------------------------------------- /Console/assets/images/lora_rnodes.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/lora_rnodes.webp -------------------------------------------------------------------------------- /Console/assets/images/nn_an.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/nn_an.webp -------------------------------------------------------------------------------- /Console/assets/images/nn_conv.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/nn_conv.webp -------------------------------------------------------------------------------- /Console/assets/images/nn_init.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/images/nn_init.webp -------------------------------------------------------------------------------- /Console/assets/stl/Handheld_RNode_Parts.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Console/assets/stl/Handheld_RNode_Parts.7z -------------------------------------------------------------------------------- /Console/build.py: -------------------------------------------------------------------------------- 1 | import markdown 2 | import os 3 | import sys 4 | import shutil 5 | 6 | packages = { 7 | "rns": "rns-0.9.1-py3-none-any.whl", 8 | "nomadnet": "nomadnet-0.5.7-py3-none-any.whl", 9 | "lxmf": "lxmf-0.6.0-py3-none-any.whl", 10 | "rnsh": "rnsh-0.1.5-py3-none-any.whl", 11 | } 12 | 13 | DEFAULT_TITLE = "RNode Bootstrap Console" 14 | SOURCES_PATH="./source" 15 | BUILD_PATH="./build" 16 | PACKAGES_PATH = "../../dist_archive" 17 | RNS_SOURCE_PATH = "../../Reticulum" 18 | INPUT_ENCODING="utf-8" 19 | OUTPUT_ENCODING="utf-8" 20 | 21 | LXMF_ADDRESS = "8dd57a738226809646089335a6b03695" 22 | 23 | document_start = """ 24 | 25 | 26 | 27 | 28 | 29 | 30 | {PAGE_TITLE} 31 | 32 | 33 | 34 |
35 | 36 | {MENU}
""" 37 | 38 | document_end = """""" 39 | 40 | menu_md = """
[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)
""" 41 | 42 | manual_redirect = """ 43 | 44 | 45 | 46 | 47 | 48 | 49 | """ 50 | help_redirect = """ 51 | 52 | 53 | 54 | 55 | 56 | 57 | """ 58 | 59 | url_maps = [ 60 | # { "path": "", "target": "/.md"}, 61 | ] 62 | 63 | def scan_pages(base_path): 64 | files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "."] 65 | directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."] 66 | 67 | page_sources = [] 68 | 69 | for file in files: 70 | if file.endswith(".md"): 71 | page_sources.append(base_path+"/"+file) 72 | 73 | for directory in directories: 74 | page_sources.extend(scan_pages(base_path+"/"+directory)) 75 | 76 | return page_sources 77 | 78 | def get_prop(md, prop): 79 | try: 80 | pt = "["+prop+"]: <> (" 81 | pp = md.find(pt) 82 | if pp != -1: 83 | ps = pp+len(pt) 84 | pe = md.find(")", ps) 85 | return md[ps:pe] 86 | else: 87 | return None 88 | 89 | except Exception as e: 90 | print("Error while extracting topic property: "+str(e)) 91 | return None 92 | 93 | def list_topic(topic): 94 | base_path = SOURCES_PATH+"/"+topic 95 | 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"] 96 | 97 | topic_entries = [] 98 | for file in files: 99 | if file.endswith(".md"): 100 | fp = base_path+"/"+file 101 | f = open(fp, "rb") 102 | link_path = fp.replace(SOURCES_PATH, ".").replace(".md", ".html") 103 | 104 | md = f.read().decode(INPUT_ENCODING) 105 | topic_entries.append({ 106 | "title": get_prop(md, "title"), 107 | "image": get_prop(md, "image"), 108 | "date": get_prop(md, "date"), 109 | "excerpt": get_prop(md, "excerpt"), 110 | "md": md, 111 | "file": link_path 112 | }) 113 | 114 | topic_entries.sort(key=lambda e: e["date"], reverse=True) 115 | return topic_entries 116 | 117 | def render_topic(topic_entries): 118 | md = "" 119 | for topic in topic_entries: 120 | md += "" 121 | md += "" 122 | md += "" 123 | md += ""+str(topic["title"])+"" 124 | #md += ""+str(topic["date"])+"" 125 | md += ""+str(topic["excerpt"])+"" 126 | md += "" 127 | md += "" 128 | 129 | 130 | return md 131 | 132 | def generate_html(f, root_path): 133 | md = f.read().decode(INPUT_ENCODING) 134 | 135 | page_title = get_prop(md, "title") 136 | if page_title == None: 137 | page_title = DEFAULT_TITLE 138 | else: 139 | page_title += " | "+DEFAULT_TITLE 140 | 141 | tt = "{TOPIC:" 142 | tp = md.find(tt) 143 | if tp != -1: 144 | ts = tp+len(tt) 145 | te = md.find("}", ts) 146 | topic = md[ts:te] 147 | 148 | rt = tt+topic+"}" 149 | tl = render_topic(list_topic(topic)) 150 | print("Found topic: "+str(topic)+", rt "+str(rt)) 151 | md = md.replace(rt, tl) 152 | 153 | menu_html = markdown.markdown(menu_md.replace("{CONTENT_PATH}", root_path), extensions=["markdown.extensions.fenced_code", "sane_lists"]).replace("

", "") 154 | page_html = markdown.markdown(md, extensions=["markdown.extensions.fenced_code"]).replace("{ASSET_PATH}", root_path) 155 | page_html = page_html.replace("{LXMF_ADDRESS}", LXMF_ADDRESS) 156 | for pkg_name in packages: 157 | page_html = page_html.replace("{PKG_"+pkg_name+"}", "pkg/"+pkg_name+".zip") 158 | page_html = page_html.replace("{PKG_BASE_"+pkg_name+"}", pkg_name+".zip") 159 | page_html = page_html.replace("{PKG_NAME_"+pkg_name+"}", packages[pkg_name]) 160 | 161 | page_date = get_prop(md, "date") 162 | if page_date != None: 163 | page_html = page_html.replace("{DATE}", page_date) 164 | 165 | return document_start.replace("{ASSET_PATH}", root_path).replace("{MENU}", menu_html).replace("{PAGE_TITLE}", page_title) + page_html + document_end 166 | 167 | source_files = scan_pages(SOURCES_PATH) 168 | 169 | mf = open(BUILD_PATH+"/m.html", "w") 170 | mf.write(manual_redirect) 171 | mf.close() 172 | mf = open(BUILD_PATH+"/h.html", "w") 173 | mf.write(help_redirect) 174 | mf.close() 175 | 176 | def optimise_manual(path): 177 | pm = 90 178 | scale_imgs = [ 179 | ("_images/board_rnodev2.png", pm), 180 | ("_images/board_rnode.png", pm), 181 | ("_images/board_heltec32v20.png", pm), 182 | ("_images/board_heltec32v30.png", pm), 183 | ("_images/board_t3v21.png", pm), 184 | ("_images/board_t3v20.png", pm), 185 | ("_images/board_t3v10.png", pm), 186 | ("_images/board_t3s3.png", pm), 187 | ("_images/board_tbeam.png", pm), 188 | ("_images/board_tdeck.png", pm), 189 | ("_images/board_rak4631.png", pm), 190 | ("_images/board_tbeam_supreme.png", pm), 191 | ("_images/sideband_devices.webp", pm), 192 | ("_images/nomadnet_3.png", pm), 193 | ("_images/meshchat_1.webp", pm), 194 | ("_images/radio_is5ac.png", pm), 195 | ("_images/radio_rblhg5.png", pm), 196 | ("_static/rns_logo_512.png", 256), 197 | ("../images/bg_h_1.webp", pm), 198 | ] 199 | 200 | import subprocess 201 | import shlex 202 | for i,s in scale_imgs: 203 | fp = path+"/"+i 204 | resize = "convert "+fp+" -quality 25 -resize "+str(s)+" "+fp 205 | print(resize) 206 | subprocess.call(shlex.split(resize), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 207 | 208 | remove_files = [ 209 | "objects.inv", 210 | "Reticulum Manual.pdf", 211 | "Reticulum Manual.epub", 212 | "_static/styles/furo.css.map", 213 | "_static/scripts/furo.js.map", 214 | "_static/jquery-3.6.0.js", 215 | "_static/jquery.js", 216 | "static/underscore-1.13.1.js", 217 | "_static/_sphinx_javascript_frameworks_compat.js", 218 | "_static/scripts/furo.js.LICENSE.txt", 219 | "_static/styles/furo-extensions.css.map", 220 | # "_static/pygments.css", 221 | # "_static/language_data.js", 222 | # "_static/searchtools.js", 223 | # "searchindex.js", 224 | ] 225 | for file in remove_files: 226 | fp = path+"/"+file 227 | print("Removing file: "+str(fp)) 228 | try: 229 | os.unlink(fp) 230 | except Exception as e: 231 | print("An error occurred while attempting to unlink "+str(fp)+": "+str(e)) 232 | 233 | remove_dirs = [ 234 | "_sources", 235 | ] 236 | for d in remove_dirs: 237 | fp = path+"/"+d 238 | print("Removing dir: "+str(fp)) 239 | shutil.rmtree(fp) 240 | 241 | shutil.move(path, BUILD_PATH+"/m") 242 | 243 | def fetch_reticulum_site(): 244 | r_site_path = BUILD_PATH+"/r" 245 | if not os.path.isdir(r_site_path): 246 | shutil.copytree(PACKAGES_PATH+"/reticulum.network", r_site_path) 247 | if os.path.isdir(r_site_path+"/manual"): 248 | optimise_manual(r_site_path+"/manual") 249 | remove_files = [ 250 | "gfx/reticulum_logo_512.png", 251 | ] 252 | for file in remove_files: 253 | fp = r_site_path+"/"+file 254 | print("Removing file: "+str(fp)) 255 | os.unlink(fp) 256 | replace_paths() 257 | 258 | def replace_paths(): 259 | repls = [ 260 | ("gfx/reticulum_logo_512.png", "/m/_static/rns_logo_512.png") 261 | ] 262 | for root, dirs, files in os.walk(BUILD_PATH): 263 | for file in files: 264 | fpath = root+"/"+file 265 | if fpath.endswith(".html"): 266 | print("Performing replacements in "+fpath+"") 267 | f = open(fpath, "rb") 268 | html = f.read().decode("utf-8") 269 | f.close() 270 | for s,r in repls: 271 | html = html.replace(s,r) 272 | f = open(fpath, "wb") 273 | f.write(html.encode("utf-8")) 274 | f.close() 275 | 276 | # if not os.path.isdir(BUILD_PATH+"/d"): 277 | # os.makedirs(BUILD_PATH+"/d") 278 | # shutil.move(fpath, BUILD_PATH+"/d/") 279 | 280 | 281 | def remap_names(): 282 | for root, dirs, files in os.walk(BUILD_PATH): 283 | for file in files: 284 | fpath = root+"/"+file 285 | spath = fpath.replace(BUILD_PATH, "") 286 | if len(spath) > 31: 287 | print("Path "+spath+" is too long, remapping...") 288 | if not os.path.isdir(BUILD_PATH+"/d"): 289 | os.makedirs(BUILD_PATH+"/d") 290 | shutil.move(fpath, BUILD_PATH+"/d/") 291 | 292 | 293 | 294 | def gz_all(): 295 | import gzip 296 | for root, dirs, files in os.walk(BUILD_PATH): 297 | for file in files: 298 | fpath = root+"/"+file 299 | print("Gzipping "+fpath+"...") 300 | f = open(fpath, "rb") 301 | g = gzip.open(fpath+".gz", "wb") 302 | g.writelines(f) 303 | g.close() 304 | f.close() 305 | os.unlink(fpath) 306 | 307 | from zipfile import ZipFile 308 | for pkg_name in packages: 309 | pkg_file = packages[pkg_name] 310 | pkg_full_path = PACKAGES_PATH+"/"+pkg_file 311 | if os.path.isfile(pkg_full_path): 312 | print("Including "+pkg_file) 313 | z = ZipFile(BUILD_PATH+"/pkg/"+pkg_name+".zip", "w") 314 | z.write(pkg_full_path, pkg_full_path[len(PACKAGES_PATH+"/"):]) 315 | z.close() 316 | # shutil.copy(pkg_full_path, BUILD_PATH+"/"+pkg_name) 317 | 318 | else: 319 | print("Could not find "+pkg_full_path) 320 | exit(1) 321 | 322 | for um in url_maps: 323 | with open(SOURCES_PATH+"/"+um["target"], "rb") as f: 324 | of = BUILD_PATH+um["target"].replace(SOURCES_PATH, "").replace(".md", ".html") 325 | root_path = "../" 326 | html = generate_html(f, root_path) 327 | 328 | print("Map path : "+str(um["path"])) 329 | print("Map target : "+str(um["target"])) 330 | print("Mapped root path: "+str(root_path)) 331 | 332 | if not os.path.isdir(BUILD_PATH+"/"+um["path"]): 333 | os.makedirs(BUILD_PATH+"/"+um["path"], exist_ok=True) 334 | 335 | with open(BUILD_PATH+"/"+um["path"]+"/index.html", "wb") as wf: 336 | wf.write(html.encode(OUTPUT_ENCODING)) 337 | 338 | for mdf in source_files: 339 | with open(mdf, "rb") as f: 340 | of = BUILD_PATH+mdf.replace(SOURCES_PATH, "").replace(".md", ".html") 341 | root_path = "../"*(len(of.replace(BUILD_PATH+"/", "").split("/"))-1) 342 | html = generate_html(f, root_path) 343 | 344 | if not os.path.isdir(os.path.dirname(of)): 345 | os.makedirs(os.path.dirname(of), exist_ok=True) 346 | 347 | with open(of, "wb") as wf: 348 | wf.write(html.encode(OUTPUT_ENCODING)) 349 | 350 | fetch_reticulum_site() 351 | if not "--no-gz" in sys.argv: 352 | gz_all() 353 | 354 | if not "--no-remap" in sys.argv: 355 | remap_names() 356 | -------------------------------------------------------------------------------- /Console/source/builds/ap.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-12) 2 | [title]: <> (Outdoor RNode) 3 | [image]: <> (gfx/cs.webp) 4 | [excerpt]: <> (An outdoor-mountable RNode suitable for Access Point or network extension operation. Also supports high-capacity batteries and solar charging.) 5 | ## Outdoor AP RNode 6 | 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. 7 | 8 | This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! 9 | -------------------------------------------------------------------------------- /Console/source/builds/handheld.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-14) 2 | [title]: <> (Handheld RNode) 3 | [image]: <> (gfx/rnode_iso.webp) 4 | [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.) 5 | ## Handheld RNode Recipe 6 | *Version 2.1* 7 | 8 | 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. 9 | 10 | 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). 11 | 12 | Completed Handheld RNode 13 |
*A completed Handheld RNode*
14 | 15 | ### Table of Contents 16 | 17 | 1. [Preparation](#prep) 18 | 2. [Supported Boards](#devboard) 19 | 3. [Materials](#materials) 20 | 4. [Print Parts](#parts) 21 | 5. [Install Tools](#tools) 22 | 6. [Firmware Setup](#firmware) 23 | 7. [Assembly](#assembly) 24 | 25 | 26 | ### Step 1: Preparation 27 | 28 | 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. 29 | 30 | To complete this build recipe, you will need access to the following items: 31 | 32 | - A computer with a functional operating system, such as Linux, BSD or macOS 33 | - One of the [supported development boards](#devboard) for this recipe 34 | - A suitable USB cable for connecting the development board to your computer 35 | - A 3D printer and the necessary amount of material for printing the [device parts](#parts) 36 | - 6 pieces of M2x6mm screws to assemble the case 37 | - A suitable antenna 38 | - An optional NeoPixel RGB LED 39 | - An optional [battery](#battery) 40 | - 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. 41 | 42 | ### Step 2: Supported Development Boards 43 | 44 | 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. 45 | 46 | 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. 47 | 48 | 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. 49 | 50 | 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. 51 | 52 | Compatible board 53 |
*The correct board version for this RNode build recipe*
54 | 55 | 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). 56 | 57 | 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. 58 | 59 | 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. 60 | 61 | Refer to the following two pictures to locate the resistor that needs moving: 62 | 63 | Before desoldering 64 | After desoldering 65 |
*Before and after removing the SMA connector and moving the resistor*
66 | 67 | 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. 68 | 69 | 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. 70 | 71 | ### Step 3: Obtain Materials 72 | 73 | In addition to the board, you will need a few other components to build this RNode. 74 | 75 | - A suitable **antenna**. Most boards purchased online include a passable antenna, but you may want to upgrade it to a better one. 76 | - 6 pieces of **M2x6mm screws** for assembling the case. Can be bought in most hardware stores or from online vendors. 77 | - 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. 78 | - 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. 79 | - An optional **lithium-polymer battery**. 80 | - This RNode supports **3.7v**, **single-cell** LiPo batteries with a **1.25mm JST connector** 81 | - The standard case can fit up to a 700mAh LP602248 battery 82 | - Maximum battery dimensions for this case is 50mm x 25mm x 6mm 83 | - There is a larger bottom casing available that fits 1100mAh batteries 84 | - Maximum battery dimensions for this case is 50mm x 25mm x 12mm 85 | 86 | ### Step 4: 3D Print Parts 87 | 88 | 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. 89 | 90 | - Two of the parts are LED light-guides, and should be printed in a semi-translucent material: 91 | - The `LED_Window.stl` file is a light-guide for the NeoPixel LED, mounted in the circular cutout at the top of the device. 92 | - 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. 93 | - The rest of the parts can be printed in any material, but for durability and heat-resistance, PETG is recommended. 94 | - The `Power_Switch.stl` file is a small power-switch slider, mounted in the matching grove on the bottom-left of the device. 95 | - 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. 96 | - The `Case_Bottom_Small_Battery.stl` file is the default bottom shell of the case. It holds batteries up to approximately 700mAh. 97 | - The `Case_Bottom_Large_Battery.stl` file is an alternative bottom shell for the case. It holds batteries up to approximately 1100mAh. 98 | - 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. 99 | - 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. 100 | 101 | All files are dimensioned to fit together perfectly without any scaling on a well-tuned 3D-printer. 102 | 103 | The recommended layer height for all files is 0.15mm for FDM printers. 104 | 105 | ### Step 5: Install Tools 106 | 107 | 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. 108 | 109 | 110 | ### Step 6: Firmware Setup 111 | 112 | 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: 113 | 114 | ``` 115 | rnodeconf --autoinstall 116 | ``` 117 | 118 | 1. The program will ask you to connect your device to an USB-port on your computer. Do so, and hit enter. 119 | 2. Select the serial port the device is connected as. 120 | 3. You will now be asked what device this is, select the option **A Specific Kind of RNode**. 121 | 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. 122 | 5. The installer will display a summary of your choices. If you are satisfied, confirm your selection. 123 | 6. The installer will now automatically install and configure the firmware and prepare the device for use. 124 | 125 | > **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. 126 | 127 | > 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. 128 | 129 | If you need to extract the firmware from an existing RNode, run the following command: 130 | 131 | ``` 132 | rnodeconf --extract 133 | ``` 134 | 135 | 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: 136 | 137 | ``` 138 | rnodeconf --autoinstall --use-extracted 139 | ``` 140 | 141 | 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`. 142 | 143 | ### Step 7: Assembly 144 | 145 | With the firmware installed and configured, and the case parts printed, it's time to put it all together. 146 | 147 | 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. 148 | 2. Thread the cable of the **SMA to u.FL** pigtail adapter into the matching grove, and run it out of the bottom opening. 149 | 3. Mount the **power-switch slider** into the matching slot, in the bottom-left part of the bottom shell. 150 | 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. 151 | 5. Optionally mount the **NeoPixel LED**: 152 | - Measure out cables that matches lenghts between the NeoPixel mounting slot, and the corresponding pins on the board. 153 | - Solder the **V+**, **GND** and **DATA** cables to the NeoPixel. 154 | - Solder the **V+** cable to the **3.3v** pin on the board. 155 | - Solder the **GND** cable to the **GND** pin on the board. 156 | - Solder the **DATA** cable to **IO Pin 12** on the board. 157 | - Mount the **NeoPixel** in the circular slot in the top part of the top shell. 158 | 6. Carefully mount the OLED display in the rectangular slot in the middle part of the top shell. 159 | 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. 160 | 8. Mount the 6 **M2x6mm screws** into the mounting holes, until the two shells of the case are tightly and securely connected. 161 | 9. Flip over the device. 162 | 10. Connect the male **u.FL** connector to the female **u.FL** socket on the **board**. 163 | 11. Optionally, connect the male JST connector of the **battery** to the female JST connector on the **board**. 164 | 12. Fit the **battery door** into place. 165 | 166 | Congratulations, Your Handheld RNode is now complete! 167 | 168 | Flip the power switch, and start using it! -------------------------------------------------------------------------------- /Console/source/builds/micropylon.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-09) 2 | [title]: <> (Reticulum MicroPylon) 3 | [image]: <> (gfx/cs.webp) 4 | [excerpt]: <> (A powerful, solar-powered multi-transceiver RNode-based radio system for autonomous and self-configuring Reticulum network deployments.) 5 | ## Reticulum MicroPylon 6 | 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. 7 | 8 | This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! 9 | -------------------------------------------------------------------------------- /Console/source/builds/wallmount.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-10) 2 | [title]: <> (Wall-Mount RNode) 3 | [image]: <> (gfx/cs.webp) 4 | [excerpt]: <> (A sleek, wall-mountable RNode, suitable for permanent installation and operation indoors, or in a semi-protected environment outdoors.) 5 | ## Wall-Mount RNode 6 | This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! 7 | -------------------------------------------------------------------------------- /Console/source/contact.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Contact) 2 | # Contact Me 3 | 4 | **Hello!** I am the creator of the RNode ecosystem. 5 | 6 | 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: 7 | 8 | - The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub 9 | - The [Reticulum Matrix Channel](#reticulum:matrix.org) at `#reticulum:matrix.org` 10 | - The [Reticulum subreddit](https://reddit.com/r/reticulum) 11 | 12 | To get in touch with me personally, you can use one of the following methods, in order of preference: 13 | 14 | - LXMF at `8dd57a738226809646089335a6b03695` 15 | - Matrix using `@unsignedmark:matrix.org` 16 | - Email by using the address mark at unsigned dot io 17 | 18 | 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. 19 | 20 |
`3502`
-------------------------------------------------------------------------------- /Console/source/contribute.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Donate) 2 | ## Keep Communications Free and Open 3 | Please take part in keeping the continued development, maintenance and distribution of the RNode ecosystem possible by donating via one of the following channels: 4 | 5 | - Monero
6 | ``` 7 | 84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w 8 | ``` 9 |

10 | - Ethereum
11 | ``` 12 | 0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73 13 | ``` 14 |

15 | - Bitcoin
16 | ``` 17 | 35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH 18 | ``` 19 |

20 | - Ko-Fi
21 | `https://ko-fi.com/markqvist` 22 | 23 | ## Spread Knowledge and Awareness 24 | Another great way to contribute, is to spread awareness about the RNode project. Here's some ideas: 25 | 26 | - Introduce the concepts of Free & Open Communications Systems to your community 27 | - Teach others to build and use RNodes, and how to set up resilient and private communications systems 28 | - Learn about using Reticulum to set up resilient communications networks, and teach these skills to people in your area that need them 29 | 30 | ## Contribute Code & Material 31 | 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. 32 | 33 | 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. 34 | 35 | You can find us the following places: 36 | 37 | - The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org` 38 | - The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub 39 | - The [Reticulum subreddit](https://reddit.com/r/reticulum) -------------------------------------------------------------------------------- /Console/source/guides/install_firmware.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-12) 2 | [title]: <> (Installing RNode Firmware on Supported Devices) 3 | [image]: <> (images/g2p.webp) 4 | [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.) 5 | 6 | # Installing RNode Firmware on Supported Devices 7 | 8 | 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. 9 | 10 | With the firmware installed, you can use your newly created RNode as: 11 | 12 | - A [LoRa interface for Reticulum]({ASSET_PATH}m/interfaces.html#rnode-lora-interface) 13 | - A LoRa packet sniffer with [LoRaMon](https://unsigned.io/loramon/) 14 | - A Linux network interface using the [tncattach program]({ASSET_PATH}pkg/tncattach.zip) 15 | - A LoRa-based TNC for almost any amateur radio packet application 16 | 17 | 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. 18 | 19 | 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**. 20 | 21 | ![Compatible LoRa devices]({ASSET_PATH}images/g2p.webp) 22 |
*Some of the device types compatible with this installation guide*
23 | 24 | ## Device Variations 25 | 26 | 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. 27 | 28 | > **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). 29 | 30 | ## Preparations 31 | 32 | 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). 33 | 34 | 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. 35 | 36 | ## Install The Firmware 37 | 38 | We are now ready to start installing the firmware. To install the RNode Firmware on your devices, run the RNode autoinstaller using this command: 39 | 40 | ```txt 41 | rnodeconf --autoinstall 42 | ``` 43 | 44 | 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. 45 | 46 | If the install goes well, you will be greated with a success message telling you that your device is now ready. 47 | 48 | > **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. 49 | 50 | > 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. 51 | 52 | If you need to extract the firmware from an existing RNode, run the following command: 53 | 54 | ``` 55 | rnodeconf --extract 56 | ``` 57 | 58 | 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: 59 | 60 | ``` 61 | rnodeconf --autoinstall --use-extracted 62 | ``` 63 | 64 | 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`. 65 | 66 | ## Verify Installation 67 | To confirm everything is OK, you can query the device info with: 68 | 69 | ```txt 70 | rnodeconf --info /dev/ttyUSB0 71 | ``` 72 | 73 | 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: 74 | 75 | ```txt 76 | [20:11:22] Opening serial port /dev/ttyUSB0... 77 | [20:11:25] Device connected 78 | [20:11:25] Current firmware version: 1.26 79 | [20:11:25] Reading EEPROM... 80 | [20:11:25] EEPROM checksum correct 81 | [20:11:25] Device signature validated 82 | [20:11:25] 83 | [20:11:25] Device info: 84 | [20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36) 85 | [20:11:25] Device signature : Validated - Local signature 86 | [20:11:25] Firmware version : 1.26 87 | [20:11:25] Hardware revision : 1 88 | [20:11:25] Serial number : 00:00:00:02 89 | [20:11:25] Frequency range : 850.0 MHz - 950.0 MHz 90 | [20:11:25] Max TX power : 17 dBm 91 | [20:11:25] Manufactured : 2022-01-27 20:10:32 92 | [20:11:25] Device mode : Normal (host-controlled) 93 | ``` 94 | 95 | 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. 96 | 97 | 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. 98 | 99 | 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). 100 | 101 | ## External RGB LED 102 | 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: 103 | 104 | - Connect the NeoPixel **V+** pin to the **3.3v** pin on the board. 105 | - Connect the NeoPixel **GND** pin to the **GND** pin on the board. 106 | - Connect the NeoPixel **DATA** pin to **IO Pin 12** on the board. 107 | 108 | For the firmware to activate the NeoPixel LED, you must also make specific choices in the autoinstaller guide: 109 | 110 | - When asked what type of device you have, select **A specific kind of RNode**. 111 | - When asked what model the device is, select the **Handheld v2.x RNode** that matches the frequency of your board. 112 | 113 | ## External Display & LEDs 114 | If you are using a **LilyGO T-Beam** device, you can connect an external **SSD1306 OLED** display using the following setup: 115 | 116 | - The **SSD1306**-based display must be set to use **I2C** and address `0x3D` 117 | - Connect display **GND** to T-Beam **GND** 118 | - Connect display **Vin** to suitable power-supplying pin on the T-Beam 119 | - Connect display **RST** to T-Beam **Pin 13** 120 | - Connect display **I2C CLK** to T-Beam **SCL** / **Pin 22** 121 | - Connect display **I2C DATA** to T-Beam **SDA** / **Pin 21** 122 | 123 | On **T-Beam** devices, you can also connect external RX/TX LEDs to **Pin 2** and **Pin 4**. 124 | -------------------------------------------------------------------------------- /Console/source/guides/loracomms.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-14) 2 | [title]: <> (Private, Secure and Uncensorable Messaging Over a LoRa Mesh) 3 | [image]: <> (images/g1p.webp) 4 | [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.) 5 | 6 | # Private, Secure and Uncensorable Messaging Over a LoRa Mesh 7 | *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.* 8 | 9 | ![]({ASSET_PATH}images/g1p.webp) 10 | 11 | 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: 12 | 13 | - Allows both real-time and asynchronous text message communication between Alice and Bob. 14 | - 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. 15 | - 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. 16 | 17 | 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. 18 | 19 | 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: 20 | 21 | - 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). 22 | - 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). 23 | - 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. 24 | 25 | As you might have already guessed, the "magic glue" that acutally makes this entire system possible is [Reticulum]({ASSET_PATH}r/index.html). 26 | 27 | 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. 28 | 29 | 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). 30 | 31 | 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. 32 | 33 | Let's get started. 34 | 35 | # LoRa Radio Setup 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ![]({ASSET_PATH}images/lora_rnodes.webp) 43 | 44 | 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: 45 | 46 | ``` 47 | pip install rnodeconf 48 | ``` 49 | 50 | 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`. 51 | 52 | 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: 53 | 54 | ``` 55 | rnodeconf --autoinstall 56 | ``` 57 | 58 | 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. 59 | 60 | # Installation at Alices House 61 | 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. 62 | 63 | At her computer (running Ubuntu Linux), she installs the Nomad Network program by entering the following command in a terminal: 64 | 65 | ``` 66 | pip install nomadnet 67 | ``` 68 | 69 | 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: 70 | 71 | ``` 72 | nomadnet 73 | ``` 74 | 75 | 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: 76 | 77 | ![]({ASSET_PATH}images/nn_init.webp) 78 | 79 | 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. 80 | 81 | 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: 82 | 83 | ``` 84 | [reticulum] 85 | enable_transport = False 86 | share_instance = Yes 87 | 88 | shared_instance_port = 37428 89 | instance_control_port = 37429 90 | 91 | panic_on_interface_error = No 92 | 93 | [logging] 94 | loglevel = 4 95 | 96 | [interfaces] 97 | [[Default Interface]] 98 | type = AutoInterface 99 | interface_enabled = True 100 | 101 | [[RNode LoRa Interface]] 102 | type = RNodeInterface 103 | interface_enabled = True 104 | port = /dev/ttyUSB0 105 | frequency = 867200000 106 | bandwidth = 125000 107 | txpower = 7 108 | spreadingfactor = 8 109 | codingrate = 5 110 | ``` 111 | 112 | *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.* 113 | 114 | 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. 115 | 116 | # Installation at Bobs Apartment 117 | 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. 118 | 119 | 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. 120 | 121 | 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. 122 | 123 | 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. 124 | 125 | Bob installs a fresh copy of Raspberry Pi OS on the small computer, and in the terminal issues the following command to install Reticulum: 126 | 127 | ``` 128 | pip install rns 129 | ``` 130 | 131 | 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: 132 | 133 | ``` 134 | rnsd 135 | ``` 136 | 137 | 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: 138 | 139 | ``` 140 | [2022-03-26 17:14:05] [Notice] Could not load config file, creating default configuration file... 141 | [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. 142 | [2022-03-26 17:14:09] [Notice] Started rnsd version 0.3.3 143 | ``` 144 | 145 | 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. 146 | 147 | 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. 148 | 149 | 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: 150 | 151 | ``` 152 | [reticulum] 153 | enable_transport = Yes 154 | share_instance = Yes 155 | 156 | shared_instance_port = 37428 157 | instance_control_port = 37429 158 | 159 | panic_on_interface_error = No 160 | 161 | [logging] 162 | loglevel = 4 163 | 164 | [interfaces] 165 | [[Default Interface]] 166 | type = AutoInterface 167 | interface_enabled = True 168 | 169 | [[RNode LoRa Interface]] 170 | type = RNodeInterface 171 | interface_enabled = True 172 | port = /dev/ttyUSB0 173 | frequency = 867200000 174 | bandwidth = 125000 175 | txpower = 7 176 | spreadingfactor = 8 177 | codingrate = 5 178 | ``` 179 | 180 | 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: 181 | 182 | ``` 183 | [2022-03-26 18:17:43] [Debug] Bringing up system interfaces... 184 | [2022-03-26 18:17:43] [Verbose] AutoInterface[Default Interface] discovering peers for 1.8 seconds... 185 | [2022-03-26 18:17:45] [Notice] Opening serial port /dev/ttyUSB0... 186 | [2022-03-26 18:17:47] [Notice] Serial port /dev/ttyUSB0 is now open 187 | [2022-03-26 18:17:47] [Verbose] Configuring RNode interface... 188 | [2022-03-26 18:17:47] [Verbose] Wating for radio configuration validation for RNodeInterface[RNode LoRa Interface]... 189 | [2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting frequency is 867.2 MHz 190 | [2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting bandwidth is 125 KHz 191 | [2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting TX power is 7 dBm 192 | [2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting spreading factor is 8 193 | [2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting coding rate is 5 194 | [2022-03-26 18:17:47] [Verbose] RNodeInterface[RNode LoRa Interface] On-air bitrate is now 3.1 kbps 195 | [2022-03-26 18:17:47] [Notice] RNodeInterface[RNode LoRa Interface] is configured and powered up 196 | [2022-03-26 18:17:48] [Debug] System interfaces are ready 197 | [2022-03-26 18:17:48] [Verbose] Configuration loaded from /home/bob/.reticulum/config 198 | [2022-03-26 18:17:50] [Verbose] Loaded 0 path table entries from storage 199 | [2022-03-26 18:17:50] [Verbose] Loaded 0 tunnel table entries from storage 200 | [2022-03-26 18:17:50] [Verbose] Transport instance started 201 | [2022-03-26 18:17:50] [Notice] Started rnsd version 0.3.3 202 | ``` 203 | 204 | 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. 205 | 206 | # Communication 207 | 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*. 208 | 209 | 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. 210 | 211 | An LXMF addresses consist of 32 hexadecimal characters, and are usually encapsulated in single angle quotation marks like this: `<9824f6367015b30f2d7b8a24bc6205d7>`. 212 | 213 | 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. 214 | 215 | 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. 216 | 217 | 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. 218 | 219 | To make sure his public cryptographic key is known by the network, Bob taps the **Announce** button in the Sideband app: 220 | 221 |

222 | 223 | After a few seconds, Bobs announce shows up in the **Announce Stream** section of the Nomad Network program on Alices computer: 224 | 225 |

226 | 227 | 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: 228 | 229 |

230 | 231 | And on Alices computer running Nomad Network, it looks like this: 232 | 233 |

234 | 235 | 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. 236 | 237 | To learn more, take a look at the [Learn]({ASSET_PATH}learn.html) section. 238 | -------------------------------------------------------------------------------- /Console/source/guides/make_rnodes.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-10) 2 | [title]: <> (How To Make Your Own RNodes) 3 | [image]: <> (images/g3p.webp) 4 | [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.) 5 | # How To Make Your Own RNodes 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | 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). 12 | 13 | ![A Homemade RNode]({ASSET_PATH}images/g3p.webp) 14 |
*A homemade RNode, based on an ESP32 board and a transceiver module, ready for use*
15 | 16 | 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: 17 | 18 | - A supported microcontroller board 19 | - A supported transceiver module 20 | - A way to mount and connect the two 21 | 22 | ## Preparing the Hardware 23 | 24 | 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: 25 | 26 | - 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. 27 | - 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. 28 | - 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. 29 | - 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. 30 | 31 | 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: 32 | 33 | - The RNode firmware needs a module based on the **Semtech SX1276**, **Semtech SX1278**, **SX1262**, **SX1268** and **SX1280** LoRa transceiver ICs. These come in several different variants, for all frequency bands from about 150 MHz to 2500 MHz. 34 | - The module *must* expose the direct SPI bus to the transceiver chip. UART based modules that add their own communications layer will not work. 35 | - The module must also expose the *reset* line of the chip, and provide the **DIO0** (or other relevant) interrupt signal *from* the chip. 36 | - 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. 37 | 38 | Keeping those things in mind, you should be able to select a suitable combination of microcontroller board and transceiver module. 39 | 40 | ## Assembling the RNode 41 | 42 | 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. 43 | 44 | 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: 45 | 46 | 1. Connect the GND pin of the microcontroller board to the GND rail of the breadboard. 47 | 2. Connect the GND pin of the transceiver module to the GND rail of the breadboard. 48 | 3. Connect the 3.3 volt output line of the microcontroller board to the V_IN pin of the transceiver module. 49 | 4. Connect the *chip select* pin of the microcontroller board to the *chip select* pin of the transceiver module. 50 | 5. Connect the *SPI clock* pin of the microcontroller board to the *SPI clock* pin of the transceiver module. 51 | 6. Connect the *MOSI* pin of microcontroller board to the *MOSI* pin of the transceiver module. 52 | 7. Connect the *MISO* pin of the microcontroller board to the *MISO* pin of the transceiver module. 53 | 8. Connect the *transceiver reset* pin of the microcontroller board to the *reset* pin of the transceiver module. 54 | 9. Connect the *DIO0* pin of the transceiver module to the *DIO0 interrupt pin* of the microcontroller board. 55 | 10. You can optionally connect transmit and receiver LEDs to the corresponding pins of the microcontroller board. 56 | 57 | The pin layouts of your transceiver module and microcontroller board will vary, but you can look up the correct pin assignments for your processor type and board layout in the [Config.h](https://github.com/markqvist/RNode_Firmware/blob/master/Config.h) file of the [RNode Firmware](https://unsigned.io/rnode_firmware). 58 | 59 | ### Loading the Firmware 60 | Once the hardware is assembled, you are ready to load the firmware onto the board and configure the configuration parameters in the boards EEPROM. Luckily, this process is completely automated by the [RNode Configuration Utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnodeconf-utility). To prepare for loading the firmware, make sure that `python` and `pip` is installed on your system, then install the `rns` package (which includes the `rnodeconf` program) by issuing the command: 61 | 62 | 63 | ```txt 64 | pip install rns 65 | ``` 66 | 67 | If installation goes well, you can now move on to the next step. 68 | 69 | > *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! 70 | 71 | 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: 72 | 73 | ```txt 74 | rnodeconf --autoinstall 75 | ``` 76 | 77 | 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. 78 | 79 | 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: 80 | 81 | ```txt 82 | rnodeconf --info /dev/ttyUSB0 83 | ``` 84 | 85 | 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: 86 | 87 | ```txt 88 | [2022-01-27 20:11:22] Opening serial port /dev/ttyUSB0... 89 | [2022-01-27 20:11:25] Device connected 90 | [2022-01-27 20:11:25] Current firmware version: 1.26 91 | [2022-01-27 20:11:25] Reading EEPROM... 92 | [2022-01-27 20:11:25] EEPROM checksum correct 93 | [2022-01-27 20:11:25] Device signature validated 94 | [2022-01-27 20:11:25] 95 | [2022-01-27 20:11:25] Device info: 96 | [2022-01-27 20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36) 97 | [2022-01-27 20:11:25] Device signature : Validated - Local signature 98 | [2022-01-27 20:11:25] Firmware version : 1.26 99 | [2022-01-27 20:11:25] Hardware revision : 1 100 | [2022-01-27 20:11:25] Serial number : 00:00:00:02 101 | [2022-01-27 20:11:25] Frequency range : 850.0 MHz - 950.0 MHz 102 | [2022-01-27 20:11:25] Max TX power : 17 dBm 103 | [2022-01-27 20:11:25] Manufactured : 2022-01-27 20:10:32 104 | [2022-01-27 20:11:25] Device mode : Normal (host-controlled) 105 | ``` 106 | 107 | 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. 108 | 109 | 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. 110 | 111 | 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). 112 | -------------------------------------------------------------------------------- /Console/source/guides/tnc_mode.md: -------------------------------------------------------------------------------- 1 | [date]: <> (2023-01-07) 2 | [title]: <> (Using an RNode With Amateur Radio Software) 3 | [image]: <> (images/g4p.webp) 4 | [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.) 5 | 6 | # Using an RNode With Amateur Radio Software 7 | 8 | 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. 9 | 10 | 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. 11 | 12 | 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. 13 | 14 | With the `rnodeconf` program installed, you can put your RNode into TNC mode simply by entering the command: 15 | 16 | ``` 17 | rnodeconf -T /dev/ttyUSB0 18 | ``` 19 | 20 | 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. 21 | 22 | That's all there is to it! Your RNode is now configured in TNC mode, and ready for use with amateur radio applications. 23 | -------------------------------------------------------------------------------- /Console/source/help.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Get Help) 2 | ## Get Help 3 | If you are having trouble, or if something is not working, this RNode contains a number of useful resources. 4 | 5 | - Read [Questions & Answers](qa.html) section 6 | - Read the [Reticulum Manual](m/index.html) stored on this RNode 7 | - Browse a copy of the [Reticulum Website](r/index.html) stored on this RNode 8 | 9 | ## Community & Support 10 | If things still aren't working as expected here are some great places to ask for help: 11 | 12 | - The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub 13 | - The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org` 14 | - The [Reticulum subreddit](https://reddit.com/r/reticulum) 15 | -------------------------------------------------------------------------------- /Console/source/index.md: -------------------------------------------------------------------------------- 1 | ## Hello! 2 | 3 | 4 | 5 | 12 | 14 | 15 | 16 |
6 | You have connected to the RNode Bootstrap Console.
7 |
8 | The tools and information contained in this RNode will allow you to replicate the RNode design, build more RNodes and grow your communications ecosystems.
9 |
10 | This repository also contains tools, software and information necessary to bootstrap networks and communications systems based on RNodes and Reticulum. 11 |
13 |
17 |
18 |
19 |

What would you like to do?

20 |
You can browse this repository freely, or jump straight into a task-oriented workflow by selecting one of the starting points below.
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /Console/source/learn.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Learn More) 2 | ## Learn More 3 | 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: 4 | 5 | - Reading the [What is an RNode?](rnode.html) page 6 | - Checking the [Questions & Answers](qa.html) section 7 | - Reading the [Reticulum Manual](m/index.html) stored on this RNode 8 | - Browsing a copy of the [Reticulum Website]({ASSET_PATH}r/index.html) stored on this RNode 9 | - Visiting the [unsigned.io](https://unsigned.io/) website 10 | - You can also find **unsigned.io** on Nomad Network, at `ec58b0e430cd9628907383954feea068` 11 | 12 | ## Guides 13 | 14 | {TOPIC:guides} 15 | -------------------------------------------------------------------------------- /Console/source/qa.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Questions & Answers) 2 | ## Questions & Answers 3 | This section contains a list of common questions, and associated answers. 4 | 5 | - **What are the system requirements for running Reticulum?** 6 | 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. 7 | - **Does Reticulum work without the Internet?** 8 | 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. 9 | - **Who owns and controls the addresses I use on a Reticulum network?** 10 | You do. Every address is in complete ownership and control of the person that created it. 11 | - **If nobody centrally controls the addresses, will my address still be globally reachable?** 12 | 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. 13 | - **Is communication over Reticulum encrypted?** 14 | 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. 15 | - **Could you build a global Internet with Reticulum instead of IP?** 16 | 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! 17 | - **Is Reticulum as fast and optimised as my favorite TCP/IP stack?** 18 | 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. 19 | - **Who created all of this?** 20 | The Reticulum protocol, and the RNode system was created by [Mark Qvist]({ASSET_PATH}contact.html), of [unsigned.io](https://unsigned.io). -------------------------------------------------------------------------------- /Console/source/recipes.md: -------------------------------------------------------------------------------- 1 | [title]: <> (RNode Recipes) 2 | ## RNode Build Recipes 3 | 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. 4 | 5 | {TOPIC:builds} 6 | -------------------------------------------------------------------------------- /Console/source/replicate.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Replicate) 2 | ## Create RNodes 3 | 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. 4 | 5 | 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. 6 | 7 | ### Firmware Source Code 8 | 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. 9 | 10 | ### Getting Started 11 | To create your own RNodes, there are generally three distinct paths you can take: 12 | 13 | - 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. 14 | - 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. 15 | - 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. 16 | 17 | 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. 18 | 19 | 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. 20 |

21 |
22 |

Choose a path to get started

23 |
24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /Console/source/rnode.md: -------------------------------------------------------------------------------- 1 | [title]: <> (What is an RNode?) 2 | ## What is an RNode? 3 | 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. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | 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. 12 | -------------------------------------------------------------------------------- /Console/source/s_lxmf.md: -------------------------------------------------------------------------------- 1 | [title]: <> (LXMF) 2 | ## LXMF 3 | 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. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | **Local Installation** 10 | 11 | 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. 12 | 13 | - If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first 14 | - Download the [{PKG_BASE_lxmf}]({ASSET_PATH}{PKG_lxmf}) package from this RNode and unzip it 15 | - Install it with the command `pip install ./{PKG_NAME_lxmf}` 16 | - Verify the installed Reticulum version by running `lxmd --version` 17 | 18 | **Online Installation** 19 | 20 | If you are connected to the Internet, you can try to install the latest version of LXMF via the `pip` package manager. 21 | 22 | - Install Nomad Network by running the command `pip install lxmf` 23 | - Verify the installed Reticulum version by running `lxmd --version` 24 | -------------------------------------------------------------------------------- /Console/source/s_nn.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Nomad Network) 2 | ## Nomad Network 3 | Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy. 4 | 5 | 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. 6 | 7 | ![Screenshot]({ASSET_PATH}gfx/nn.webp) 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | ### Local Installation 14 | 15 | 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. 16 | 17 | - 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 18 | - Download the [{PKG_BASE_nomadnet}]({ASSET_PATH}{PKG_nomadnet}) package from this RNode and unzip it 19 | - Install it with the command `pip install ./{PKG_NAME_nomadnet}` 20 | - Verify the installed Nomad Network version by running `nomadnet --version` 21 | 22 | ### Online Installation 23 | 24 | If you are connected to the Internet, you can try to install the latest version of Nomad Network via the `pip` package manager. 25 | 26 | - Install Nomad Network by running the command `pip install nomadnet` 27 | - Verify the installed Nomad Network version by running `nomadnet --version` 28 | -------------------------------------------------------------------------------- /Console/source/s_rns.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Reticulum) 2 | ## Reticulum 3 | 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. 4 | 5 |

6 | 7 | This packages requires you have `python` and `pip` installed on your computer. This should come as standard on most operating systems released since 2020. 8 | 9 | ### Local Installation 10 | 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. 11 | 12 | - Download the [{PKG_BASE_rns}]({ASSET_PATH}{PKG_rns}) package from this RNode and unzip it 13 | - Install it with the command `pip install ./{PKG_NAME_rns}` 14 | - Verify the installed Reticulum version by running `rnstatus --version` 15 | 16 | ### Online Installation 17 | If you are connected to the Internet, you can try to install the latest version of Reticulum via the `pip` package manager. 18 | 19 | - Install Reticulum by running the command `pip install rns` 20 | - Verify the installed Reticulum version by running `rnstatus --version` 21 | 22 | ### Dependencies 23 | If the installation has problems resolving dependencies, first try installing the `python-cryptography`, `python-netifaces` and `python-pyserial` packages from your systems package manager. 24 | 25 | 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: 26 | 27 | `pip install --no-dependencies ./{PKG_NAME_rns}` 28 | 29 | 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. 30 | 31 | 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. 32 | 33 | For more detailed information, please read the entire [Getting Started section of the Reticulum Manual]({ASSET_PATH}m/gettingstartedfast.html). 34 | -------------------------------------------------------------------------------- /Console/source/s_rnsh.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Shell Over Reticulum) 2 | ## Shell Over Reticulum 3 | 4 | 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. 5 | 6 | ### Local Installation 7 | 8 | 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. 9 | 10 | - If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first 11 | - Download the [{PKG_BASE_rnsh}]({ASSET_PATH}{PKG_rnsh}) package from this RNode and unzip it 12 | - Install it with the command `pip install ./{PKG_NAME_rnsh}` 13 | - Verify the installed `rnsh` version by running `rnsh --version` 14 | 15 | ### Online Installation 16 | 17 | If you are connected to the Internet, you can try to install the latest version of `rnsh` via the `pip` package manager. 18 | 19 | - Install `rnsh` by running the command `pip install rnsh` 20 | - Verify the installed `rnsh` version by running `rnsh --version` 21 | -------------------------------------------------------------------------------- /Console/source/s_sideband.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Sideband) 2 | ## Sideband 3 | 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. 4 | 5 | Sideband also supports exchanging messages through encrypted QR-codes on paper, or through messages embedded directly in lxm:// links. 6 | 7 | ![Screenshot]({ASSET_PATH}gfx/sideband.webp) 8 | 9 | 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: 10 | 11 | - The [Sideband page](https://unsigned.io/sideband/) on [unsigned.io](https://unsigned.io/) 12 | - The [GitHub release page for Sideband](https://github.com/markqvist/Sideband/releases/latest) 13 | - The [IzzyOnDroid repository for F-Droid](https://android.izzysoft.de/repo/apk/io.unsigned.sideband) 14 | -------------------------------------------------------------------------------- /Console/source/sell_rnodes.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Sell RNodes) 2 | ## Build & Sell RNodes 3 | 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. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | 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. -------------------------------------------------------------------------------- /Console/source/software.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Software) 2 | ## Software 3 | This RNode contains a repository of downloadable software and utilities, that are useful for bootstrapping communications networks, and for replicating RNodes. 4 | 5 | **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. 6 | 7 | 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). 8 | 9 | **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*! 10 | 11 | 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`). 12 | 13 | You can also download the copy of the [RNode Firmware source-code]({ASSET_PATH}pkg/rnode_firmware.zip) that is stored in this RNode. 14 |

15 |
16 |

Choose a software package to get started

17 |
18 | 19 | 20 | 21 | 22 | 23 |
-------------------------------------------------------------------------------- /Console/source/supported.md: -------------------------------------------------------------------------------- 1 | [title]: <> (Supported Hardware) 2 | ## Supported Boards & Devices 3 | The RNode Firmware supports the following boards: 4 | 5 | - Handheld v2.x RNodes from [unsigned.io](https://unsigned.io/shop/product/handheld-rnode) 6 | - Original v1.x RNodes from [unsigned.io](https://unsigned.io/shop/product/rnode) 7 | - LilyGO T-Beam v1.1 devices 8 | - LilyGO LoRa32 v2.0 devices 9 | - LilyGO LoRa32 v2.1 devices 10 | - Heltec LoRa32 v2 devices 11 | - Homebrew RNodes based on ATmega1284p boards 12 | - Homebrew RNodes based on ATmega2560 boards 13 | - Homebrew RNodes based on Adafruit Feather ESP32 boards 14 | - Homebrew RNodes based on generic ESP32 boards 15 | 16 | ## Supported Transceiver Modules 17 | 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. -------------------------------------------------------------------------------- /Device.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | 18 | #if MCU_VARIANT == MCU_ESP32 19 | #include "mbedtls/md.h" 20 | #include "esp_ota_ops.h" 21 | #include "esp_flash_partitions.h" 22 | #include "esp_partition.h" 23 | 24 | #elif MCU_VARIANT == MCU_NRF52 25 | #include "Adafruit_nRFCrypto.h" 26 | 27 | // size of chunk to retrieve from flash sector 28 | #define CHUNK_SIZE 128 29 | 30 | #define END_SECTION_SIZE 256 31 | 32 | #if defined(NRF52840_XXAA) 33 | // https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map 34 | // each section follows along from one another, in this order 35 | // this is always at the start of the memory map 36 | #define APPLICATION_START 0x26000 37 | 38 | #define USER_DATA_START 0xED000 39 | 40 | #define IMG_SIZE_START 0xFF008 41 | #endif 42 | 43 | #endif 44 | 45 | // Forward declaration from Utilities.h 46 | void eeprom_update(int mapped_addr, uint8_t byte); 47 | uint8_t eeprom_read(uint32_t addr); 48 | void hard_reset(void); 49 | 50 | #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 51 | void eeprom_flush(); 52 | #endif 53 | 54 | const uint8_t dev_keys [] PROGMEM = { 55 | 0x0f, 0x15, 0x86, 0x74, 0xa0, 0x7d, 0xf2, 0xde, 0x32, 0x11, 0x29, 0xc1, 0x0d, 0xda, 0xcc, 0xc3, 56 | 0xe1, 0x9b, 0xac, 0xf2, 0x27, 0x06, 0xee, 0x89, 0x1f, 0x7a, 0xfc, 0xc3, 0x6a, 0xf5, 0x38, 0x08 57 | }; 58 | 59 | #define DEV_SIG_LEN 64 60 | uint8_t dev_sig[DEV_SIG_LEN]; 61 | 62 | #define DEV_KEY_LEN 32 63 | uint8_t dev_k_prv[DEV_KEY_LEN]; 64 | uint8_t dev_k_pub[DEV_KEY_LEN]; 65 | 66 | #define DEV_HASH_LEN 32 67 | uint8_t dev_hash[DEV_HASH_LEN]; 68 | uint8_t dev_partition_table_hash[DEV_HASH_LEN]; 69 | uint8_t dev_bootloader_hash[DEV_HASH_LEN]; 70 | uint8_t dev_firmware_hash[DEV_HASH_LEN]; 71 | uint8_t dev_firmware_hash_target[DEV_HASH_LEN]; 72 | 73 | #define EEPROM_SIG_LEN 128 74 | uint8_t dev_eeprom_signature[EEPROM_SIG_LEN]; 75 | 76 | bool dev_signature_validated = false; 77 | bool fw_signature_validated = true; 78 | 79 | #define DEV_SIG_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN 80 | #define dev_sig_addr(a) (a+DEV_SIG_OFFSET) 81 | 82 | #define DEV_FWHASH_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN-DEV_HASH_LEN 83 | #define dev_fwhash_addr(a) (a+DEV_FWHASH_OFFSET) 84 | 85 | bool device_signatures_ok() { 86 | return dev_signature_validated && fw_signature_validated; 87 | } 88 | 89 | void device_validate_signature() { 90 | int n_keys = sizeof(dev_keys)/DEV_KEY_LEN; 91 | bool valid_signature_found = false; 92 | for (int i = 0; i < n_keys; i++) { 93 | memcpy(dev_k_pub, dev_keys+DEV_KEY_LEN*i, DEV_KEY_LEN); 94 | if (Ed25519::verify(dev_sig, dev_k_pub, dev_hash, DEV_HASH_LEN)) { 95 | valid_signature_found = true; 96 | } 97 | } 98 | 99 | if (valid_signature_found) { 100 | dev_signature_validated = true; 101 | } else { 102 | dev_signature_validated = false; 103 | } 104 | } 105 | 106 | void device_save_signature() { 107 | device_validate_signature(); 108 | if (dev_signature_validated) { 109 | for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { 110 | eeprom_update(dev_sig_addr(i), dev_sig[i]); 111 | } 112 | } 113 | } 114 | 115 | void device_load_signature() { 116 | for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { 117 | #if HAS_EEPROM 118 | dev_sig[i] = EEPROM.read(dev_sig_addr(i)); 119 | #elif MCU_VARIANT == MCU_NRF52 120 | dev_sig[i] = eeprom_read(dev_sig_addr(i)); 121 | #endif 122 | } 123 | } 124 | 125 | void device_load_firmware_hash() { 126 | for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { 127 | #if HAS_EEPROM 128 | dev_firmware_hash_target[i] = EEPROM.read(dev_fwhash_addr(i)); 129 | #elif MCU_VARIANT == MCU_NRF52 130 | dev_firmware_hash_target[i] = eeprom_read(dev_fwhash_addr(i)); 131 | #endif 132 | } 133 | } 134 | 135 | void device_save_firmware_hash() { 136 | for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { 137 | eeprom_update(dev_fwhash_addr(i), dev_firmware_hash_target[i]); 138 | } 139 | #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 140 | eeprom_flush(); 141 | #endif 142 | if (!fw_signature_validated) hard_reset(); 143 | } 144 | 145 | #if MCU_VARIANT == MCU_NRF52 146 | uint32_t retrieve_application_size() { 147 | uint8_t bytes[4]; 148 | memcpy(bytes, (const void*)IMG_SIZE_START, 4); 149 | uint32_t fw_len = bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24; 150 | return fw_len; 151 | } 152 | 153 | void calculate_region_hash(unsigned long long start, unsigned long long end, uint8_t* return_hash) { 154 | // this function calculates the hash digest of a region of memory, 155 | // currently it is only designed to work for the application region 156 | uint8_t chunk[CHUNK_SIZE] = {0}; 157 | 158 | // to store potential last chunk of program 159 | uint8_t chunk_next[CHUNK_SIZE] = {0}; 160 | nRFCrypto_Hash hash; 161 | 162 | hash.begin(CRYS_HASH_SHA256_mode); 163 | 164 | uint8_t size; 165 | 166 | while (start < end ) { 167 | const void* src = (const void*)start; 168 | if (start + CHUNK_SIZE >= end) { 169 | size = end - start; 170 | } 171 | else { 172 | size = CHUNK_SIZE; 173 | } 174 | 175 | memcpy(chunk, src, CHUNK_SIZE); 176 | 177 | hash.update(chunk, size); 178 | 179 | start += CHUNK_SIZE; 180 | } 181 | hash.end(return_hash); 182 | } 183 | #endif 184 | 185 | void device_validate_partitions() { 186 | device_load_firmware_hash(); 187 | #if MCU_VARIANT == MCU_ESP32 188 | esp_partition_t partition; 189 | partition.address = ESP_PARTITION_TABLE_OFFSET; 190 | partition.size = ESP_PARTITION_TABLE_MAX_LEN; 191 | partition.type = ESP_PARTITION_TYPE_DATA; 192 | esp_partition_get_sha256(&partition, dev_partition_table_hash); 193 | partition.address = ESP_BOOTLOADER_OFFSET; 194 | partition.size = ESP_PARTITION_TABLE_OFFSET; 195 | partition.type = ESP_PARTITION_TYPE_APP; 196 | esp_partition_get_sha256(&partition, dev_bootloader_hash); 197 | esp_partition_get_sha256(esp_ota_get_running_partition(), dev_firmware_hash); 198 | #elif MCU_VARIANT == MCU_NRF52 199 | // todo, add bootloader, partition table, or softdevice? 200 | calculate_region_hash(APPLICATION_START, APPLICATION_START+retrieve_application_size(), dev_firmware_hash); 201 | #endif 202 | #if VALIDATE_FIRMWARE 203 | for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { 204 | if (dev_firmware_hash_target[i] != dev_firmware_hash[i]) { 205 | fw_signature_validated = false; 206 | break; 207 | } 208 | } 209 | #endif 210 | } 211 | 212 | bool device_firmware_ok() { 213 | return fw_signature_validated; 214 | } 215 | 216 | #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 217 | bool device_init() { 218 | if (bt_ready) { 219 | #if MCU_VARIANT == MCU_ESP32 220 | for (uint8_t i=0; i 3 | 4 | // Org_v01 by Orgdot (www.orgdot.com/aliasfonts). A tiny, 5 | // stylized font with all characters within a 6 pixel height. 6 | 7 | const uint8_t Org_01Bitmaps[] PROGMEM = { 8 | 0xE8, 0xA0, 0x57, 0xD5, 0xF5, 0x00, 0xFD, 0x3E, 0x5F, 0x80, 0x88, 0x88, 9 | 0x88, 0x80, 0xF4, 0xBF, 0x2E, 0x80, 0x80, 0x6A, 0x40, 0x95, 0x80, 0xAA, 10 | 0x80, 0x5D, 0x00, 0xC0, 0xF0, 0x80, 0x08, 0x88, 0x88, 0x00, 0xFC, 0x63, 11 | 0x1F, 0x80, 0xF8, 0xF8, 0x7F, 0x0F, 0x80, 0xF8, 0x7E, 0x1F, 0x80, 0x8C, 12 | 0x7E, 0x10, 0x80, 0xFC, 0x3E, 0x1F, 0x80, 0xFC, 0x3F, 0x1F, 0x80, 0xF8, 13 | 0x42, 0x10, 0x80, 0xFC, 0x7F, 0x1F, 0x80, 0xFC, 0x7E, 0x1F, 0x80, 0x90, 14 | 0xB0, 0x2A, 0x22, 0xF0, 0xF0, 0x88, 0xA8, 0xF8, 0x4E, 0x02, 0x00, 0xFD, 15 | 0x6F, 0x0F, 0x80, 0xFC, 0x7F, 0x18, 0x80, 0xF4, 0x7D, 0x1F, 0x00, 0xFC, 16 | 0x21, 0x0F, 0x80, 0xF4, 0x63, 0x1F, 0x00, 0xFC, 0x3F, 0x0F, 0x80, 0xFC, 17 | 0x3F, 0x08, 0x00, 0xFC, 0x2F, 0x1F, 0x80, 0x8C, 0x7F, 0x18, 0x80, 0xF9, 18 | 0x08, 0x4F, 0x80, 0x78, 0x85, 0x2F, 0x80, 0x8D, 0xB1, 0x68, 0x80, 0x84, 19 | 0x21, 0x0F, 0x80, 0xFD, 0x6B, 0x5A, 0x80, 0xFC, 0x63, 0x18, 0x80, 0xFC, 20 | 0x63, 0x1F, 0x80, 0xFC, 0x7F, 0x08, 0x00, 0xFC, 0x63, 0x3F, 0x80, 0xFC, 21 | 0x7F, 0x29, 0x00, 0xFC, 0x3E, 0x1F, 0x80, 0xF9, 0x08, 0x42, 0x00, 0x8C, 22 | 0x63, 0x1F, 0x80, 0x8C, 0x62, 0xA2, 0x00, 0xAD, 0x6B, 0x5F, 0x80, 0x8A, 23 | 0x88, 0xA8, 0x80, 0x8C, 0x54, 0x42, 0x00, 0xF8, 0x7F, 0x0F, 0x80, 0xEA, 24 | 0xC0, 0x82, 0x08, 0x20, 0x80, 0xD5, 0xC0, 0x54, 0xF8, 0x80, 0xF1, 0xFF, 25 | 0x8F, 0x99, 0xF0, 0xF8, 0x8F, 0x1F, 0x99, 0xF0, 0xFF, 0x8F, 0x6B, 0xA4, 26 | 0xF9, 0x9F, 0x10, 0x8F, 0x99, 0x90, 0xF0, 0x55, 0xC0, 0x8A, 0xF9, 0x90, 27 | 0xF8, 0xFD, 0x63, 0x10, 0xF9, 0x99, 0xF9, 0x9F, 0xF9, 0x9F, 0x80, 0xF9, 28 | 0x9F, 0x20, 0xF8, 0x88, 0x47, 0x1F, 0x27, 0xC8, 0x42, 0x00, 0x99, 0x9F, 29 | 0x99, 0x97, 0x8C, 0x6B, 0xF0, 0x96, 0x69, 0x99, 0x9F, 0x10, 0x2E, 0x8F, 30 | 0x2B, 0x22, 0xF8, 0x89, 0xA8, 0x0F, 0xE0}; 31 | 32 | const GFXglyph Org_01Glyphs[] PROGMEM = {{0, 0, 0, 6, 0, 1}, // 0x20 ' ' 33 | {0, 1, 5, 2, 0, -4}, // 0x21 '!' 34 | {1, 3, 1, 4, 0, -4}, // 0x22 '"' 35 | {2, 5, 5, 6, 0, -4}, // 0x23 '#' 36 | {6, 5, 5, 6, 0, -4}, // 0x24 '$' 37 | {10, 5, 5, 6, 0, -4}, // 0x25 '%' 38 | {14, 5, 5, 6, 0, -4}, // 0x26 '&' 39 | {18, 1, 1, 2, 0, -4}, // 0x27 ''' 40 | {19, 2, 5, 3, 0, -4}, // 0x28 '(' 41 | {21, 2, 5, 3, 0, -4}, // 0x29 ')' 42 | {23, 3, 3, 4, 0, -3}, // 0x2A '*' 43 | {25, 3, 3, 4, 0, -3}, // 0x2B '+' 44 | {27, 1, 2, 2, 0, 0}, // 0x2C ',' 45 | {28, 4, 1, 5, 0, -2}, // 0x2D '-' 46 | {29, 1, 1, 2, 0, 0}, // 0x2E '.' 47 | {30, 5, 5, 6, 0, -4}, // 0x2F '/' 48 | {34, 5, 5, 6, 0, -4}, // 0x30 '0' 49 | {38, 1, 5, 2, 0, -4}, // 0x31 '1' 50 | {39, 5, 5, 6, 0, -4}, // 0x32 '2' 51 | {43, 5, 5, 6, 0, -4}, // 0x33 '3' 52 | {47, 5, 5, 6, 0, -4}, // 0x34 '4' 53 | {51, 5, 5, 6, 0, -4}, // 0x35 '5' 54 | {55, 5, 5, 6, 0, -4}, // 0x36 '6' 55 | {59, 5, 5, 6, 0, -4}, // 0x37 '7' 56 | {63, 5, 5, 6, 0, -4}, // 0x38 '8' 57 | {67, 5, 5, 6, 0, -4}, // 0x39 '9' 58 | {71, 1, 4, 2, 0, -3}, // 0x3A ':' 59 | {72, 1, 4, 2, 0, -3}, // 0x3B ';' 60 | {73, 3, 5, 4, 0, -4}, // 0x3C '<' 61 | {75, 4, 3, 5, 0, -3}, // 0x3D '=' 62 | {77, 3, 5, 4, 0, -4}, // 0x3E '>' 63 | {79, 5, 5, 6, 0, -4}, // 0x3F '?' 64 | {83, 5, 5, 6, 0, -4}, // 0x40 '@' 65 | {87, 5, 5, 6, 0, -4}, // 0x41 'A' 66 | {91, 5, 5, 6, 0, -4}, // 0x42 'B' 67 | {95, 5, 5, 6, 0, -4}, // 0x43 'C' 68 | {99, 5, 5, 6, 0, -4}, // 0x44 'D' 69 | {103, 5, 5, 6, 0, -4}, // 0x45 'E' 70 | {107, 5, 5, 6, 0, -4}, // 0x46 'F' 71 | {111, 5, 5, 6, 0, -4}, // 0x47 'G' 72 | {115, 5, 5, 6, 0, -4}, // 0x48 'H' 73 | {119, 5, 5, 6, 0, -4}, // 0x49 'I' 74 | {123, 5, 5, 6, 0, -4}, // 0x4A 'J' 75 | {127, 5, 5, 6, 0, -4}, // 0x4B 'K' 76 | {131, 5, 5, 6, 0, -4}, // 0x4C 'L' 77 | {135, 5, 5, 6, 0, -4}, // 0x4D 'M' 78 | {139, 5, 5, 6, 0, -4}, // 0x4E 'N' 79 | {143, 5, 5, 6, 0, -4}, // 0x4F 'O' 80 | {147, 5, 5, 6, 0, -4}, // 0x50 'P' 81 | {151, 5, 5, 6, 0, -4}, // 0x51 'Q' 82 | {155, 5, 5, 6, 0, -4}, // 0x52 'R' 83 | {159, 5, 5, 6, 0, -4}, // 0x53 'S' 84 | {163, 5, 5, 6, 0, -4}, // 0x54 'T' 85 | {167, 5, 5, 6, 0, -4}, // 0x55 'U' 86 | {171, 5, 5, 6, 0, -4}, // 0x56 'V' 87 | {175, 5, 5, 6, 0, -4}, // 0x57 'W' 88 | {179, 5, 5, 6, 0, -4}, // 0x58 'X' 89 | {183, 5, 5, 6, 0, -4}, // 0x59 'Y' 90 | {187, 5, 5, 6, 0, -4}, // 0x5A 'Z' 91 | {191, 2, 5, 3, 0, -4}, // 0x5B '[' 92 | {193, 5, 5, 6, 0, -4}, // 0x5C '\' 93 | {197, 2, 5, 3, 0, -4}, // 0x5D ']' 94 | {199, 3, 2, 4, 0, -4}, // 0x5E '^' 95 | {200, 5, 1, 6, 0, 1}, // 0x5F '_' 96 | {201, 1, 1, 2, 0, -4}, // 0x60 '`' 97 | {202, 4, 4, 5, 0, -3}, // 0x61 'a' 98 | {204, 4, 5, 5, 0, -4}, // 0x62 'b' 99 | {207, 4, 4, 5, 0, -3}, // 0x63 'c' 100 | {209, 4, 5, 5, 0, -4}, // 0x64 'd' 101 | {212, 4, 4, 5, 0, -3}, // 0x65 'e' 102 | {214, 3, 5, 4, 0, -4}, // 0x66 'f' 103 | {216, 4, 5, 5, 0, -3}, // 0x67 'g' 104 | {219, 4, 5, 5, 0, -4}, // 0x68 'h' 105 | {222, 1, 4, 2, 0, -3}, // 0x69 'i' 106 | {223, 2, 5, 3, 0, -3}, // 0x6A 'j' 107 | {225, 4, 5, 5, 0, -4}, // 0x6B 'k' 108 | {228, 1, 5, 2, 0, -4}, // 0x6C 'l' 109 | {229, 5, 4, 6, 0, -3}, // 0x6D 'm' 110 | {232, 4, 4, 5, 0, -3}, // 0x6E 'n' 111 | {234, 4, 4, 5, 0, -3}, // 0x6F 'o' 112 | {236, 4, 5, 5, 0, -3}, // 0x70 'p' 113 | {239, 4, 5, 5, 0, -3}, // 0x71 'q' 114 | {242, 4, 4, 5, 0, -3}, // 0x72 'r' 115 | {244, 4, 4, 5, 0, -3}, // 0x73 's' 116 | {246, 5, 5, 6, 0, -4}, // 0x74 't' 117 | {250, 4, 4, 5, 0, -3}, // 0x75 'u' 118 | {252, 4, 4, 5, 0, -3}, // 0x76 'v' 119 | {254, 5, 4, 6, 0, -3}, // 0x77 'w' 120 | {257, 4, 4, 5, 0, -3}, // 0x78 'x' 121 | {259, 4, 5, 5, 0, -3}, // 0x79 'y' 122 | {262, 4, 4, 5, 0, -3}, // 0x7A 'z' 123 | {264, 3, 5, 4, 0, -4}, // 0x7B '{' 124 | {266, 1, 5, 2, 0, -4}, // 0x7C '|' 125 | {267, 3, 5, 4, 0, -4}, // 0x7D '}' 126 | {269, 5, 3, 6, 0, -3}}; // 0x7E '~' 127 | 128 | const GFXfont Org_01 PROGMEM = {(uint8_t *)Org_01Bitmaps, 129 | (GFXglyph *)Org_01Glyphs, 0x20, 0x7E, 7}; 130 | 131 | // Approx. 943 bytes 132 | -------------------------------------------------------------------------------- /Fonts/PicoPixel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // Picopixel by Sebastian Weber. A tiny font 5 | // with all characters within a 6 pixel height. 6 | 7 | const uint8_t PicopixelBitmaps[] PROGMEM = { 8 | 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, 9 | 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, 10 | 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, 11 | 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, 12 | 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, 13 | 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, 14 | 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, 15 | 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, 16 | 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, 17 | 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, 18 | 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, 19 | 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, 20 | 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, 21 | 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, 22 | 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; 23 | 24 | const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' 25 | {0, 1, 5, 2, 0, -4}, // 0x21 '!' 26 | {1, 3, 2, 4, 0, -4}, // 0x22 '"' 27 | {2, 5, 5, 6, 0, -4}, // 0x23 '#' 28 | {6, 3, 6, 4, 0, -4}, // 0x24 '$' 29 | {9, 3, 5, 4, 0, -4}, // 0x25 '%' 30 | {11, 4, 5, 5, 0, -4}, // 0x26 '&' 31 | {14, 1, 2, 2, 0, -4}, // 0x27 ''' 32 | {15, 2, 5, 3, 0, -4}, // 0x28 '(' 33 | {17, 2, 5, 3, 0, -4}, // 0x29 ')' 34 | {19, 3, 3, 4, 0, -3}, // 0x2A '*' 35 | {21, 3, 3, 4, 0, -3}, // 0x2B '+' 36 | {23, 2, 2, 3, 0, 0}, // 0x2C ',' 37 | {24, 3, 1, 4, 0, -2}, // 0x2D '-' 38 | {25, 1, 1, 2, 0, 0}, // 0x2E '.' 39 | {26, 3, 5, 4, 0, -4}, // 0x2F '/' 40 | {28, 3, 5, 4, 0, -4}, // 0x30 '0' 41 | {30, 2, 5, 3, 0, -4}, // 0x31 '1' 42 | {32, 3, 5, 4, 0, -4}, // 0x32 '2' 43 | {34, 3, 5, 4, 0, -4}, // 0x33 '3' 44 | {36, 3, 5, 4, 0, -4}, // 0x34 '4' 45 | {38, 3, 5, 4, 0, -4}, // 0x35 '5' 46 | {40, 3, 5, 4, 0, -4}, // 0x36 '6' 47 | {42, 3, 5, 4, 0, -4}, // 0x37 '7' 48 | {44, 3, 5, 4, 0, -4}, // 0x38 '8' 49 | {46, 3, 5, 4, 0, -4}, // 0x39 '9' 50 | {48, 1, 3, 2, 0, -3}, // 0x3A ':' 51 | {49, 2, 4, 3, 0, -3}, // 0x3B ';' 52 | {50, 2, 3, 3, 0, -3}, // 0x3C '<' 53 | {51, 3, 3, 4, 0, -3}, // 0x3D '=' 54 | {53, 2, 3, 3, 0, -3}, // 0x3E '>' 55 | {54, 3, 5, 4, 0, -4}, // 0x3F '?' 56 | {56, 3, 5, 4, 0, -4}, // 0x40 '@' 57 | {58, 3, 5, 4, 0, -4}, // 0x41 'A' 58 | {60, 3, 5, 4, 0, -4}, // 0x42 'B' 59 | {62, 3, 5, 4, 0, -4}, // 0x43 'C' 60 | {64, 3, 5, 4, 0, -4}, // 0x44 'D' 61 | {66, 3, 5, 4, 0, -4}, // 0x45 'E' 62 | {68, 3, 5, 4, 0, -4}, // 0x46 'F' 63 | {70, 3, 5, 4, 0, -4}, // 0x47 'G' 64 | {72, 3, 5, 4, 0, -4}, // 0x48 'H' 65 | {74, 1, 5, 2, 0, -4}, // 0x49 'I' 66 | {75, 3, 5, 4, 0, -4}, // 0x4A 'J' 67 | {77, 3, 5, 4, 0, -4}, // 0x4B 'K' 68 | {79, 3, 5, 4, 0, -4}, // 0x4C 'L' 69 | {81, 5, 5, 6, 0, -4}, // 0x4D 'M' 70 | {85, 4, 5, 5, 0, -4}, // 0x4E 'N' 71 | {88, 3, 5, 4, 0, -4}, // 0x4F 'O' 72 | {90, 3, 5, 4, 0, -4}, // 0x50 'P' 73 | {92, 3, 6, 4, 0, -4}, // 0x51 'Q' 74 | {95, 3, 5, 4, 0, -4}, // 0x52 'R' 75 | {97, 3, 5, 4, 0, -4}, // 0x53 'S' 76 | {99, 3, 5, 4, 0, -4}, // 0x54 'T' 77 | {101, 3, 5, 4, 0, -4}, // 0x55 'U' 78 | {103, 3, 5, 4, 0, -4}, // 0x56 'V' 79 | {105, 5, 5, 6, 0, -4}, // 0x57 'W' 80 | {109, 3, 5, 4, 0, -4}, // 0x58 'X' 81 | {111, 3, 5, 4, 0, -4}, // 0x59 'Y' 82 | {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' 83 | {115, 2, 5, 3, 0, -4}, // 0x5B '[' 84 | {117, 3, 5, 4, 0, -4}, // 0x5C '\' 85 | {119, 2, 5, 3, 0, -4}, // 0x5D ']' 86 | {121, 3, 2, 4, 0, -4}, // 0x5E '^' 87 | {122, 4, 1, 4, 0, 1}, // 0x5F '_' 88 | {123, 2, 2, 3, 0, -4}, // 0x60 '`' 89 | {124, 3, 4, 4, 0, -3}, // 0x61 'a' 90 | {126, 3, 5, 4, 0, -4}, // 0x62 'b' 91 | {128, 3, 3, 4, 0, -2}, // 0x63 'c' 92 | {130, 3, 5, 4, 0, -4}, // 0x64 'd' 93 | {132, 3, 4, 4, 0, -3}, // 0x65 'e' 94 | {134, 2, 5, 3, 0, -4}, // 0x66 'f' 95 | {136, 3, 5, 4, 0, -3}, // 0x67 'g' 96 | {138, 3, 5, 4, 0, -4}, // 0x68 'h' 97 | {140, 1, 5, 2, 0, -4}, // 0x69 'i' 98 | {141, 2, 6, 3, 0, -4}, // 0x6A 'j' 99 | {143, 3, 5, 4, 0, -4}, // 0x6B 'k' 100 | {145, 2, 5, 3, 0, -4}, // 0x6C 'l' 101 | {147, 5, 3, 6, 0, -2}, // 0x6D 'm' 102 | {149, 3, 3, 4, 0, -2}, // 0x6E 'n' 103 | {151, 3, 3, 4, 0, -2}, // 0x6F 'o' 104 | {153, 3, 4, 4, 0, -2}, // 0x70 'p' 105 | {155, 3, 4, 4, 0, -2}, // 0x71 'q' 106 | {157, 2, 3, 3, 0, -2}, // 0x72 'r' 107 | {158, 3, 4, 4, 0, -3}, // 0x73 's' 108 | {160, 2, 5, 3, 0, -4}, // 0x74 't' 109 | {162, 3, 3, 4, 0, -2}, // 0x75 'u' 110 | {164, 3, 3, 4, 0, -2}, // 0x76 'v' 111 | {166, 5, 3, 6, 0, -2}, // 0x77 'w' 112 | {168, 3, 3, 4, 0, -2}, // 0x78 'x' 113 | {170, 3, 4, 4, 0, -2}, // 0x79 'y' 114 | {172, 3, 4, 4, 0, -3}, // 0x7A 'z' 115 | {174, 3, 5, 4, 0, -4}, // 0x7B '{' 116 | {176, 1, 6, 2, 0, -4}, // 0x7C '|' 117 | {177, 3, 5, 4, 0, -4}, // 0x7D '}' 118 | {179, 4, 2, 5, 0, -3}}; // 0x7E '~' 119 | 120 | const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps, 121 | (GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7}; 122 | 123 | // Approx. 852 bytes -------------------------------------------------------------------------------- /Framing.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #ifndef FRAMING_H 17 | #define FRAMING_H 18 | 19 | #define FEND 0xC0 20 | #define FESC 0xDB 21 | #define TFEND 0xDC 22 | #define TFESC 0xDD 23 | 24 | #define CMD_UNKNOWN 0xFE 25 | #define CMD_DATA 0x00 26 | #define CMD_FREQUENCY 0x01 27 | #define CMD_BANDWIDTH 0x02 28 | #define CMD_TXPOWER 0x03 29 | #define CMD_SF 0x04 30 | #define CMD_CR 0x05 31 | #define CMD_RADIO_STATE 0x06 32 | #define CMD_RADIO_LOCK 0x07 33 | #define CMD_DETECT 0x08 34 | #define CMD_IMPLICIT 0x09 35 | #define CMD_LEAVE 0x0A 36 | #define CMD_ST_ALOCK 0x0B 37 | #define CMD_LT_ALOCK 0x0C 38 | #define CMD_PROMISC 0x0E 39 | #define CMD_READY 0x0F 40 | 41 | #define CMD_STAT_RX 0x21 42 | #define CMD_STAT_TX 0x22 43 | #define CMD_STAT_RSSI 0x23 44 | #define CMD_STAT_SNR 0x24 45 | #define CMD_STAT_CHTM 0x25 46 | #define CMD_STAT_PHYPRM 0x26 47 | #define CMD_STAT_BAT 0x27 48 | #define CMD_STAT_CSMA 0x28 49 | #define CMD_BLINK 0x30 50 | #define CMD_RANDOM 0x40 51 | 52 | #define CMD_FB_EXT 0x41 53 | #define CMD_FB_READ 0x42 54 | #define CMD_FB_WRITE 0x43 55 | #define CMD_FB_READL 0x44 56 | #define CMD_DISP_READ 0x66 57 | #define CMD_DISP_INT 0x45 58 | #define CMD_DISP_ADDR 0x63 59 | #define CMD_DISP_BLNK 0x64 60 | #define CMD_DISP_ROT 0x67 61 | #define CMD_DISP_RCND 0x68 62 | #define CMD_NP_INT 0x65 63 | #define CMD_BT_CTRL 0x46 64 | #define CMD_BT_UNPAIR 0x70 65 | #define CMD_BT_PIN 0x62 66 | #define CMD_DIS_IA 0x69 67 | 68 | #define CMD_BOARD 0x47 69 | #define CMD_PLATFORM 0x48 70 | #define CMD_MCU 0x49 71 | #define CMD_FW_VERSION 0x50 72 | #define CMD_ROM_READ 0x51 73 | #define CMD_ROM_WRITE 0x52 74 | #define CMD_CONF_SAVE 0x53 75 | #define CMD_CONF_DELETE 0x54 76 | #define CMD_DEV_HASH 0x56 77 | #define CMD_DEV_SIG 0x57 78 | #define CMD_FW_HASH 0x58 79 | #define CMD_HASHES 0x60 80 | #define CMD_FW_UPD 0x61 81 | #define CMD_UNLOCK_ROM 0x59 82 | #define ROM_UNLOCK_BYTE 0xF8 83 | #define CMD_RESET 0x55 84 | #define CMD_RESET_BYTE 0xF8 85 | 86 | #define CMD_LOG 0x80 87 | #define CMD_TIME 0x81 88 | #define CMD_MUX_CHAIN 0x82 89 | #define CMD_MUX_DSCVR 0x83 90 | 91 | #define DETECT_REQ 0x73 92 | #define DETECT_RESP 0x46 93 | 94 | #define RADIO_STATE_OFF 0x00 95 | #define RADIO_STATE_ON 0x01 96 | 97 | #define NIBBLE_SEQ 0xF0 98 | #define NIBBLE_FLAGS 0x0F 99 | #define FLAG_SPLIT 0x01 100 | #define SEQ_UNSET 0xFF 101 | 102 | #define CMD_ERROR 0x90 103 | #define ERROR_INITRADIO 0x01 104 | #define ERROR_TXFAILED 0x02 105 | #define ERROR_EEPROM_LOCKED 0x03 106 | #define ERROR_QUEUE_FULL 0x04 107 | #define ERROR_MEMORY_LOW 0x05 108 | #define ERROR_MODEM_TIMEOUT 0x06 109 | 110 | // Serial framing variables 111 | size_t frame_len; 112 | bool IN_FRAME = false; 113 | bool ESCAPE = false; 114 | uint8_t command = CMD_UNKNOWN; 115 | 116 | #endif -------------------------------------------------------------------------------- /Input.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #ifndef INPUT_H 17 | #define INPUT_H 18 | 19 | #define PIN_BUTTON pin_btn_usr1 20 | 21 | #define PRESSED LOW 22 | #define RELEASED HIGH 23 | 24 | #define EVENT_ALL 0x00 25 | #define EVENT_CLICKS 0x01 26 | #define EVENT_BUTTON_DOWN 0x11 27 | #define EVENT_BUTTON_UP 0x12 28 | #define EVENT_BUTTON_CLICK 0x13 29 | #define EVENT_BUTTON_DOUBLE_CLICK 0x14 30 | #define EVENT_BUTTON_TRIPLE_CLICK 0x15 31 | 32 | int button_events = EVENT_CLICKS; 33 | int button_state = RELEASED; 34 | int debounce_state = button_state; 35 | unsigned long button_debounce_last = 0; 36 | unsigned long button_debounce_delay = 25; 37 | unsigned long button_down_last = 0; 38 | unsigned long button_up_last = 0; 39 | 40 | // Forward declaration 41 | void button_event(uint8_t event, unsigned long duration); 42 | 43 | void input_init() { 44 | pinMode(PIN_BUTTON, INPUT_PULLUP); 45 | } 46 | 47 | void input_get_all_events() { 48 | button_events = EVENT_ALL; 49 | } 50 | 51 | void input_get_click_events() { 52 | button_events = EVENT_CLICKS; 53 | } 54 | 55 | void input_read() { 56 | int button_reading = digitalRead(PIN_BUTTON); 57 | if (button_reading != debounce_state) { 58 | button_debounce_last = millis(); 59 | debounce_state = button_reading; 60 | } 61 | 62 | if ((millis() - button_debounce_last) > button_debounce_delay) { 63 | if (button_reading != button_state) { 64 | // State changed 65 | int previous_state = button_state; 66 | button_state = button_reading; 67 | 68 | if (button_events == EVENT_ALL) { 69 | if (button_state == PRESSED) { 70 | button_event(EVENT_BUTTON_DOWN, 0); 71 | } else if (button_state == RELEASED) { 72 | button_event(EVENT_BUTTON_UP, 0); 73 | } 74 | } else if (button_events == EVENT_CLICKS) { 75 | if (previous_state == PRESSED && button_state == RELEASED) { 76 | button_up_last = millis(); 77 | button_event(EVENT_BUTTON_CLICK, button_up_last-button_down_last); 78 | } else if (previous_state == RELEASED && button_state == PRESSED) { 79 | button_down_last = millis(); 80 | } 81 | } 82 | } 83 | } 84 | 85 | } 86 | 87 | bool button_pressed() { 88 | if (button_state == PRESSED) { 89 | return true; 90 | } else { 91 | return false; 92 | } 93 | } 94 | 95 | #endif -------------------------------------------------------------------------------- /MD5.cpp: -------------------------------------------------------------------------------- 1 | #include "MD5.h" 2 | 3 | MD5::MD5() 4 | { 5 | //nothing 6 | return; 7 | } 8 | 9 | char* MD5::make_digest(const unsigned char *digest, int len) /* {{{ */ 10 | { 11 | char * md5str = (char*) malloc(sizeof(char)*(len*2+1)); 12 | static const char hexits[17] = "0123456789abcdef"; 13 | int i; 14 | 15 | for (i = 0; i < len; i++) { 16 | md5str[i * 2] = hexits[digest[i] >> 4]; 17 | md5str[(i * 2) + 1] = hexits[digest[i] & 0x0F]; 18 | } 19 | md5str[len * 2] = '\0'; 20 | return md5str; 21 | } 22 | 23 | /* 24 | * The basic MD5 functions. 25 | * 26 | * E and G are optimized compared to their RFC 1321 definitions for 27 | * architectures that lack an AND-NOT instruction, just like in Colin Plumb's 28 | * implementation. 29 | * E() has been used instead of F() because F() is already defined in the Arduino core 30 | */ 31 | #define E(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) 32 | #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) 33 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 34 | #define I(x, y, z) ((y) ^ ((x) | ~(z))) 35 | 36 | /* 37 | * The MD5 transformation for all four rounds. 38 | */ 39 | #define STEP(f, a, b, c, d, x, t, s) \ 40 | (a) += f((b), (c), (d)) + (x) + (t); \ 41 | (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ 42 | (a) += (b); 43 | 44 | /* 45 | * SET reads 4 input bytes in little-endian byte order and stores them 46 | * in a properly aligned word in host byte order. 47 | * 48 | * The check for little-endian architectures that tolerate unaligned 49 | * memory accesses is just an optimization. Nothing will break if it 50 | * doesn't work. 51 | */ 52 | #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) 53 | # define SET(n) \ 54 | (*(MD5_u32plus *)&ptr[(n) * 4]) 55 | # define GET(n) \ 56 | SET(n) 57 | #else 58 | # define SET(n) \ 59 | (ctx->block[(n)] = \ 60 | (MD5_u32plus)ptr[(n) * 4] | \ 61 | ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ 62 | ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ 63 | ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) 64 | # define GET(n) \ 65 | (ctx->block[(n)]) 66 | #endif 67 | 68 | /* 69 | * This processes one or more 64-byte data blocks, but does NOT update 70 | * the bit counters. There are no alignment requirements. 71 | */ 72 | const void *MD5::body(void *ctxBuf, const void *data, size_t size) 73 | { 74 | MD5_CTX *ctx = (MD5_CTX*)ctxBuf; 75 | const unsigned char *ptr; 76 | MD5_u32plus a, b, c, d; 77 | MD5_u32plus saved_a, saved_b, saved_c, saved_d; 78 | 79 | ptr = (unsigned char*)data; 80 | 81 | a = ctx->a; 82 | b = ctx->b; 83 | c = ctx->c; 84 | d = ctx->d; 85 | 86 | do { 87 | saved_a = a; 88 | saved_b = b; 89 | saved_c = c; 90 | saved_d = d; 91 | 92 | /* Round 1 93 | * E() has been used instead of F() because F() is already defined in the Arduino core 94 | */ 95 | STEP(E, a, b, c, d, SET(0), 0xd76aa478, 7) 96 | STEP(E, d, a, b, c, SET(1), 0xe8c7b756, 12) 97 | STEP(E, c, d, a, b, SET(2), 0x242070db, 17) 98 | STEP(E, b, c, d, a, SET(3), 0xc1bdceee, 22) 99 | STEP(E, a, b, c, d, SET(4), 0xf57c0faf, 7) 100 | STEP(E, d, a, b, c, SET(5), 0x4787c62a, 12) 101 | STEP(E, c, d, a, b, SET(6), 0xa8304613, 17) 102 | STEP(E, b, c, d, a, SET(7), 0xfd469501, 22) 103 | STEP(E, a, b, c, d, SET(8), 0x698098d8, 7) 104 | STEP(E, d, a, b, c, SET(9), 0x8b44f7af, 12) 105 | STEP(E, c, d, a, b, SET(10), 0xffff5bb1, 17) 106 | STEP(E, b, c, d, a, SET(11), 0x895cd7be, 22) 107 | STEP(E, a, b, c, d, SET(12), 0x6b901122, 7) 108 | STEP(E, d, a, b, c, SET(13), 0xfd987193, 12) 109 | STEP(E, c, d, a, b, SET(14), 0xa679438e, 17) 110 | STEP(E, b, c, d, a, SET(15), 0x49b40821, 22) 111 | 112 | /* Round 2 */ 113 | STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) 114 | STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) 115 | STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) 116 | STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) 117 | STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) 118 | STEP(G, d, a, b, c, GET(10), 0x02441453, 9) 119 | STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) 120 | STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) 121 | STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) 122 | STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) 123 | STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) 124 | STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) 125 | STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) 126 | STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) 127 | STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) 128 | STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) 129 | 130 | /* Round 3 */ 131 | STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) 132 | STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) 133 | STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) 134 | STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) 135 | STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) 136 | STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) 137 | STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) 138 | STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) 139 | STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) 140 | STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) 141 | STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) 142 | STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) 143 | STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) 144 | STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) 145 | STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) 146 | STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) 147 | 148 | /* Round 4 */ 149 | STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) 150 | STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) 151 | STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) 152 | STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) 153 | STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) 154 | STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) 155 | STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) 156 | STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) 157 | STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) 158 | STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) 159 | STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) 160 | STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) 161 | STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) 162 | STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) 163 | STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) 164 | STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) 165 | 166 | a += saved_a; 167 | b += saved_b; 168 | c += saved_c; 169 | d += saved_d; 170 | 171 | ptr += 64; 172 | } while (size -= 64); 173 | 174 | ctx->a = a; 175 | ctx->b = b; 176 | ctx->c = c; 177 | ctx->d = d; 178 | 179 | return ptr; 180 | } 181 | 182 | void MD5::MD5Init(void *ctxBuf) 183 | { 184 | MD5_CTX *ctx = (MD5_CTX*)ctxBuf; 185 | ctx->a = 0x67452301; 186 | ctx->b = 0xefcdab89; 187 | ctx->c = 0x98badcfe; 188 | ctx->d = 0x10325476; 189 | 190 | ctx->lo = 0; 191 | ctx->hi = 0; 192 | 193 | memset(ctx->block, 0, sizeof(ctx->block)); 194 | memset(ctx->buffer, 0, sizeof(ctx->buffer)); 195 | } 196 | 197 | void MD5::MD5Update(void *ctxBuf, const void *data, size_t size) 198 | { 199 | MD5_CTX *ctx = (MD5_CTX*)ctxBuf; 200 | MD5_u32plus saved_lo; 201 | MD5_u32plus used, free; 202 | 203 | saved_lo = ctx->lo; 204 | if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { 205 | ctx->hi++; 206 | } 207 | ctx->hi += size >> 29; 208 | 209 | used = saved_lo & 0x3f; 210 | 211 | if (used) { 212 | free = 64 - used; 213 | 214 | if (size < free) { 215 | memcpy(&ctx->buffer[used], data, size); 216 | return; 217 | } 218 | 219 | memcpy(&ctx->buffer[used], data, free); 220 | data = (unsigned char *)data + free; 221 | size -= free; 222 | body(ctx, ctx->buffer, 64); 223 | } 224 | 225 | if (size >= 64) { 226 | data = body(ctx, data, size & ~(size_t)0x3f); 227 | size &= 0x3f; 228 | } 229 | 230 | memcpy(ctx->buffer, data, size); 231 | } 232 | 233 | void MD5::MD5Final(unsigned char *result, void *ctxBuf) 234 | { 235 | MD5_CTX *ctx = (MD5_CTX*)ctxBuf; 236 | MD5_u32plus used, free; 237 | 238 | used = ctx->lo & 0x3f; 239 | 240 | ctx->buffer[used++] = 0x80; 241 | 242 | free = 64 - used; 243 | 244 | if (free < 8) { 245 | memset(&ctx->buffer[used], 0, free); 246 | body(ctx, ctx->buffer, 64); 247 | used = 0; 248 | free = 64; 249 | } 250 | 251 | memset(&ctx->buffer[used], 0, free - 8); 252 | 253 | ctx->lo <<= 3; 254 | ctx->buffer[56] = ctx->lo; 255 | ctx->buffer[57] = ctx->lo >> 8; 256 | ctx->buffer[58] = ctx->lo >> 16; 257 | ctx->buffer[59] = ctx->lo >> 24; 258 | ctx->buffer[60] = ctx->hi; 259 | ctx->buffer[61] = ctx->hi >> 8; 260 | ctx->buffer[62] = ctx->hi >> 16; 261 | ctx->buffer[63] = ctx->hi >> 24; 262 | 263 | body(ctx, ctx->buffer, 64); 264 | 265 | result[0] = ctx->a; 266 | result[1] = ctx->a >> 8; 267 | result[2] = ctx->a >> 16; 268 | result[3] = ctx->a >> 24; 269 | result[4] = ctx->b; 270 | result[5] = ctx->b >> 8; 271 | result[6] = ctx->b >> 16; 272 | result[7] = ctx->b >> 24; 273 | result[8] = ctx->c; 274 | result[9] = ctx->c >> 8; 275 | result[10] = ctx->c >> 16; 276 | result[11] = ctx->c >> 24; 277 | result[12] = ctx->d; 278 | result[13] = ctx->d >> 8; 279 | result[14] = ctx->d >> 16; 280 | result[15] = ctx->d >> 24; 281 | 282 | memset(ctx, 0, sizeof(*ctx)); 283 | } 284 | unsigned char* MD5::make_hash(char *arg) 285 | { 286 | MD5_CTX context; 287 | unsigned char * hash = (unsigned char *) malloc(16); 288 | MD5Init(&context); 289 | MD5Update(&context, arg, strlen(arg)); 290 | MD5Final(hash, &context); 291 | return hash; 292 | } 293 | unsigned char* MD5::make_hash(char *arg,size_t size) 294 | { 295 | MD5_CTX context; 296 | unsigned char * hash = (unsigned char *) malloc(16); 297 | MD5Init(&context); 298 | MD5Update(&context, arg, size); 299 | MD5Final(hash, &context); 300 | return hash; 301 | } -------------------------------------------------------------------------------- /MD5.h: -------------------------------------------------------------------------------- 1 | #ifndef MD5_h 2 | #define MD5_h 3 | 4 | #include "Arduino.h" 5 | 6 | /* 7 | * This is an OpenSSL-compatible implementation of the RSA Data Security, 8 | * Inc. MD5 Message-Digest Algorithm (RFC 1321). 9 | * 10 | * Written by Solar Designer in 2001, and placed 11 | * in the public domain. There's absolutely no warranty. 12 | * 13 | * This differs from Colin Plumb's older public domain implementation in 14 | * that no 32-bit integer data type is required, there's no compile-time 15 | * endianness configuration, and the function prototypes match OpenSSL's. 16 | * The primary goals are portability and ease of use. 17 | * 18 | * This implementation is meant to be fast, but not as fast as possible. 19 | * Some known optimizations are not included to reduce source code size 20 | * and avoid compile-time configuration. 21 | */ 22 | 23 | /* 24 | * Updated by Scott MacVicar for arduino 25 | * 26 | */ 27 | 28 | #include 29 | 30 | typedef unsigned long MD5_u32plus; 31 | 32 | typedef struct { 33 | MD5_u32plus lo, hi; 34 | MD5_u32plus a, b, c, d; 35 | unsigned char buffer[64]; 36 | MD5_u32plus block[16]; 37 | } MD5_CTX; 38 | 39 | class MD5 40 | { 41 | public: 42 | MD5(); 43 | static unsigned char* make_hash(char *arg); 44 | static unsigned char* make_hash(char *arg,size_t size); 45 | static char* make_digest(const unsigned char *digest, int len); 46 | static const void *body(void *ctxBuf, const void *data, size_t size); 47 | static void MD5Init(void *ctxBuf); 48 | static void MD5Final(unsigned char *result, void *ctxBuf); 49 | static void MD5Update(void *ctxBuf, const void *data, size_t size); 50 | }; 51 | 52 | #endif -------------------------------------------------------------------------------- /Modem.h: -------------------------------------------------------------------------------- 1 | #define SX1276 0x01 2 | #define SX1278 0x02 3 | #define SX1262 0x03 4 | #define SX1280 0x04 5 | -------------------------------------------------------------------------------- /Python Module/Example.py: -------------------------------------------------------------------------------- 1 | # This is a short example program that 2 | # demonstrates the bare minimum of using 3 | # RNode in a Python program. 4 | # 5 | # The example and the RNode.py library is 6 | # written for Python 3, so be sure to run 7 | # it with: python3 Example.py 8 | 9 | # First we'll import the RNodeInterface class. 10 | from RNode import RNodeInterface 11 | 12 | # We'll also define which serial port the 13 | # RNode is attached to. 14 | serialPort = "/dev/ttyUSB0" 15 | 16 | # This function gets called every time a 17 | # packet is received 18 | def gotPacket(data, rnode): 19 | message = data.decode("utf-8") 20 | print("Received a packet: "+message) 21 | print("RSSI: "+str(rnode.r_stat_rssi)+" dBm") 22 | print("SNR: "+str(rnode.r_stat_snr)+" dB") 23 | 24 | # Create an RNode instance. This configures 25 | # and powers up the radio. 26 | rnode = RNodeInterface( 27 | callback = gotPacket, 28 | name = "My RNode", 29 | port = serialPort, 30 | frequency = 868000000, 31 | bandwidth = 125000, 32 | txpower = 2, 33 | sf = 7, 34 | cr = 5, 35 | loglevel = RNodeInterface.LOG_DEBUG) 36 | 37 | # Enter a loop waiting for user input. 38 | try: 39 | print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit") 40 | while True: 41 | input() 42 | message = "Hello World!" 43 | data = message.encode("utf-8") 44 | rnode.send(data) 45 | except KeyboardInterrupt as e: 46 | print("") 47 | exit() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***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!* 2 | 3 | # RNode Firmware 4 | 5 | This is the open firmware that powers RNode devices. 6 | 7 | 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). 8 | 9 | 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. 10 | 11 | 12 | 13 |
An RNode made from readily available and cheap parts, in a durable 3D printed case


14 | 15 | 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. 16 | 17 | ## Latest Release 18 | 19 | The latest release, installable through `rnodeconf`, is version `1.82`. You must have at least version `2.4.1` of `rnodeconf` installed to update the RNode Firmware to version `1.82`. Get it by updating the `rns` package to at least version `0.9.4`. 20 | 21 | ## A Self-Replicating System 22 | 23 | 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. 24 | 25 | 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. 26 | 27 | 28 | 29 |
Where there is no Internet, RNodes will still communicate


30 | 31 | 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. 32 | 33 | 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. 34 | 35 | ## One Tool, Many Uses 36 | 37 | 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. 38 | 39 | 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: 40 | 41 | - As a network adapter for [Reticulum](https://reticulum.network) 42 | - Messaging using [Sideband](https://unsigned.io/software/Sideband.html) 43 | - Information sharing and communication using [Nomad Network](https://unsigned.io/software/Nomad_Network.html) 44 | - LoRa-based [KISS-compatible amateur radio TNC](https://unsigned.io/guides/2020_05_03_using_rnodes_with_amateur_radio_software.html) 45 | - LoRa development platform 46 | - [Packet sniffer](https://unsigned.io/software/LoRaMon.html) for LoRa networks 47 | - Long range [Ethernet and IP network interface](https://unsigned.io/guides/2020_05_27_ethernet-and-ip-over-packet-radio-tncs.html) on Linux 48 | - As a general-purpose long-range data radio 49 | 50 | ## Types & Performance 51 | 52 | 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**. 53 | 54 | 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. 55 | 56 | 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. 57 | 58 | ## Supported Boards & Devices 59 | 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! 60 | 61 | 62 | 63 | The RNode Firmware supports the following boards: 64 | 65 | - LilyGO T-Beam v1.1 devices with SX1276/8 LoRa chips 66 | - LilyGO T-Beam v1.1 devices with SX1262/8 LoRa chips 67 | - LilyGO T-Beam Supreme devices 68 | - LilyGO T-Deck devices (currently display is disabled) 69 | - LilyGO LoRa32 v1.0 devices 70 | - LilyGO LoRa32 v2.0 devices 71 | - LilyGO LoRa32 v2.1 devices (with and without TCXO) 72 | - LilyGO T3S3 devices with SX1276/8 LoRa chips 73 | - LilyGO T3S3 devices with SX1262/8 LoRa chips 74 | - LilyGO T3S3 devices with SX1280 LoRa chips 75 | - LilyGO T-Echo devices 76 | - Heltec LoRa32 v2 devices 77 | - Heltec LoRa32 v3 devices 78 | - Heltec T114 devices 79 | - RAK4631 devices 80 | - SeeedStudio XIAO ESP32S3 devices (with Wio-SX1262) 81 | - Homebrew RNodes based on ATmega1284p boards 82 | - Homebrew RNodes based on ATmega2560 boards 83 | - Homebrew RNodes based on Adafruit Feather ESP32 boards 84 | - Homebrew RNodes based on generic ESP32 boards 85 | 86 | ## Supported Transceiver Modules 87 | The RNode Firmware supports all transceiver modules based on Semtech **SX1276**, **SX1278**, **SX1262**, **SX1268** and **SX1280** chips, that have an **SPI interface** and expose the relevant **DIO** interrupt pins from the chip. 88 | 89 | ## Getting Started Fast 90 | 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`: 91 | 92 | ``` 93 | # Install rnodeconf via rns package 94 | pip install rns --upgrade 95 | 96 | # Install the firmware on a board with the install guide 97 | rnodeconf --autoinstall 98 | ``` 99 | 100 | 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. 101 | 102 | For more detailed instruction and in-depth guides, you can have a look at some of these resources: 103 | 104 | - Create a [basic RNode from readily available development boards](https://unsigned.io/guides/2022_01_25_installing-rnode-firmware-on-supported-devices.html) 105 | - 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 106 | - 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 107 | - 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) 108 | 109 | If you would rather just buy a pre-made unit, you can visit one of the community vendors that produce and sell RNodes: 110 | 111 | - [Liberated Embedded Systems](https://store.liberatedsystems.co.uk/) 112 | - [Simply Equipped](https://simplyequipped.com/) 113 | 114 | If you'd like to have your shop added to this list, let me know. 115 | 116 | ## Support RNode Development 117 | You can help support the continued development of open, free and private communications systems by donating via one of the following channels: 118 | 119 | - Monero: 120 | ``` 121 | 84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w 122 | ``` 123 | - Bitcoin 124 | ``` 125 | bc1p4a6axuvl7n9hpapfj8sv5reqj8kz6uxa67d5en70vzrttj0fmcusgxsfk5 126 | ``` 127 | - Ethereum 128 | ``` 129 | 0xae89F3B94fC4AD6563F0864a55F9a697a90261ff 130 | ``` 131 | - Ko-Fi: https://ko-fi.com/markqvist 132 | 133 | 134 | ## License & Use 135 | 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. 136 | 137 | 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). 138 | 139 | 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). 140 | 141 | 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. 142 | 143 | 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. 144 | 145 | 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). 146 | -------------------------------------------------------------------------------- /ROM.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024, Mark Qvist 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #ifndef ROM_H 17 | #define ROM_H 18 | #define CHECKSUMMED_SIZE 0x0B 19 | 20 | // ROM address map /////////////// 21 | #define ADDR_PRODUCT 0x00 22 | #define ADDR_MODEL 0x01 23 | #define ADDR_HW_REV 0x02 24 | #define ADDR_SERIAL 0x03 25 | #define ADDR_MADE 0x07 26 | #define ADDR_CHKSUM 0x0B 27 | #define ADDR_SIGNATURE 0x1B 28 | #define ADDR_INFO_LOCK 0x9B 29 | 30 | #define ADDR_CONF_SF 0x9C 31 | #define ADDR_CONF_CR 0x9D 32 | #define ADDR_CONF_TXP 0x9E 33 | #define ADDR_CONF_BW 0x9F 34 | #define ADDR_CONF_FREQ 0xA3 35 | #define ADDR_CONF_OK 0xA7 36 | 37 | #define ADDR_CONF_BT 0xB0 38 | #define ADDR_CONF_DSET 0xB1 39 | #define ADDR_CONF_DINT 0xB2 40 | #define ADDR_CONF_DADR 0xB3 41 | #define ADDR_CONF_DBLK 0xB4 42 | #define ADDR_CONF_DROT 0xB8 43 | #define ADDR_CONF_PSET 0xB5 44 | #define ADDR_CONF_PINT 0xB6 45 | #define ADDR_CONF_BSET 0xB7 46 | #define ADDR_CONF_DIA 0xB9 47 | 48 | #define INFO_LOCK_BYTE 0x73 49 | #define CONF_OK_BYTE 0x73 50 | #define BT_ENABLE_BYTE 0x73 51 | 52 | #define EEPROM_RESERVED 200 53 | ////////////////////////////////// 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Release/README.md: -------------------------------------------------------------------------------- 1 | # Precompiled Firmware 2 | 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`: 3 | 4 | ``` 5 | # Install rnodeconf via rns package 6 | pip install rns --upgrade 7 | 8 | # Install the firmware on a board with the install guide 9 | rnodeconf --autoinstall 10 | ``` 11 | -------------------------------------------------------------------------------- /Release/console_image.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markqvist/RNode_Firmware/fff96fb5c0f6e14f948ec2d92c408ae2cc1ddb65/Release/console_image.bin -------------------------------------------------------------------------------- /ST7789.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn 5 | * Copyright (c) 2018 by Fabrice Weinberg 6 | * Copyright (c) 2024 by Heltec AutoMation 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | * ThingPulse invests considerable time and money to develop these open source libraries. 26 | * Please support us by buying our products (and not the clones) from 27 | * https://thingpulse.com 28 | * 29 | */ 30 | 31 | #ifndef ST7789Spi_h 32 | #define ST7789Spi_h 33 | 34 | #include "OLEDDisplay.h" 35 | #include 36 | 37 | 38 | #define ST_CMD_DELAY 0x80 // special signifier for command lists 39 | 40 | #define ST77XX_NOP 0x00 41 | #define ST77XX_SWRESET 0x01 42 | #define ST77XX_RDDID 0x04 43 | #define ST77XX_RDDST 0x09 44 | 45 | #define ST77XX_SLPIN 0x10 46 | #define ST77XX_SLPOUT 0x11 47 | #define ST77XX_PTLON 0x12 48 | #define ST77XX_NORON 0x13 49 | 50 | #define ST77XX_INVOFF 0x20 51 | #define ST77XX_INVON 0x21 52 | #define ST77XX_DISPOFF 0x28 53 | #define ST77XX_DISPON 0x29 54 | #define ST77XX_CASET 0x2A 55 | #define ST77XX_RASET 0x2B 56 | #define ST77XX_RAMWR 0x2C 57 | #define ST77XX_RAMRD 0x2E 58 | 59 | #define ST77XX_PTLAR 0x30 60 | #define ST77XX_TEOFF 0x34 61 | #define ST77XX_TEON 0x35 62 | #define ST77XX_MADCTL 0x36 63 | #define ST77XX_COLMOD 0x3A 64 | 65 | #define ST77XX_MADCTL_MY 0x80 66 | #define ST77XX_MADCTL_MX 0x40 67 | #define ST77XX_MADCTL_MV 0x20 68 | #define ST77XX_MADCTL_ML 0x10 69 | #define ST77XX_MADCTL_RGB 0x00 70 | 71 | #define ST77XX_RDID1 0xDA 72 | #define ST77XX_RDID2 0xDB 73 | #define ST77XX_RDID3 0xDC 74 | #define ST77XX_RDID4 0xDD 75 | 76 | // Some ready-made 16-bit ('565') color settings: 77 | #define ST77XX_BLACK 0x0000 78 | #define ST77XX_WHITE 0xFFFF 79 | #define ST77XX_RED 0xF800 80 | #define ST77XX_GREEN 0x07E0 81 | #define ST77XX_BLUE 0x001F 82 | #define ST77XX_CYAN 0x07FF 83 | #define ST77XX_MAGENTA 0xF81F 84 | #define ST77XX_YELLOW 0xFFE0 85 | #define ST77XX_ORANGE 0xFC00 86 | 87 | #define LED_A_ON LOW 88 | 89 | #ifdef ESP_PLATFORM 90 | #undef LED_A_ON 91 | #define LED_A_ON HIGH 92 | #define rtos_free free 93 | #define rtos_malloc malloc 94 | //SPIClass SPI1(HSPI); 95 | #endif 96 | class ST7789Spi : public OLEDDisplay { 97 | private: 98 | uint8_t _rst; 99 | uint8_t _dc; 100 | uint8_t _cs; 101 | uint8_t _ledA; 102 | int _miso; 103 | int _mosi; 104 | int _clk; 105 | SPIClass * _spi; 106 | SPISettings _spiSettings; 107 | uint16_t _RGB=0xFFFF; 108 | uint8_t _buffheight; 109 | public: 110 | /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ 111 | ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=320,int mosi=-1,int miso=-1,int clk=-1) { 112 | this->_spi = spiClass; 113 | this->_rst = _rst; 114 | this->_dc = _dc; 115 | this->_cs = _cs; 116 | this->_mosi=mosi; 117 | this->_miso=miso; 118 | this->_clk=clk; 119 | //this->_ledA = _ledA; 120 | _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); 121 | setGeometry(g,width,height); 122 | } 123 | 124 | bool connect(){ 125 | this->_buffheight=displayHeight / 8; 126 | this->_buffheight+=displayHeight % 8 ? 1:0; 127 | pinMode(_cs, OUTPUT); 128 | pinMode(_dc, OUTPUT); 129 | //pinMode(_ledA, OUTPUT); 130 | if (_cs != (uint8_t) -1) { 131 | pinMode(_cs, OUTPUT); 132 | } 133 | pinMode(_rst, OUTPUT); 134 | 135 | #ifdef ESP_PLATFORM 136 | _spi->begin(_clk,_miso,_mosi,-1); 137 | #else 138 | _spi->begin(); 139 | #endif 140 | _spi->setClockDivider (SPI_CLOCK_DIV2); 141 | 142 | // Pulse Reset low for 10ms 143 | digitalWrite(_rst, HIGH); 144 | delay(1); 145 | digitalWrite(_rst, LOW); 146 | delay(10); 147 | digitalWrite(_rst, HIGH); 148 | _spi->begin (); 149 | //digitalWrite(_ledA, LED_A_ON); 150 | return true; 151 | } 152 | 153 | void display(void) { 154 | #ifdef OLEDDISPLAY_DOUBLE_BUFFER 155 | 156 | uint16_t minBoundY = UINT16_MAX; 157 | uint16_t maxBoundY = 0; 158 | 159 | uint16_t minBoundX = UINT16_MAX; 160 | uint16_t maxBoundX = 0; 161 | 162 | uint16_t x, y; 163 | 164 | // Calculate the Y bounding box of changes 165 | // and copy buffer[pos] to buffer_back[pos]; 166 | for (y = 0; y < _buffheight; y++) { 167 | for (x = 0; x < displayWidth; x++) { 168 | //Serial.printf("x %d y %d\r\n",x,y); 169 | uint16_t pos = x + y * displayWidth; 170 | if (buffer[pos] != buffer_back[pos]) { 171 | minBoundY = min(minBoundY, y); 172 | maxBoundY = max(maxBoundY, y); 173 | minBoundX = min(minBoundX, x); 174 | maxBoundX = max(maxBoundX, x); 175 | } 176 | buffer_back[pos] = buffer[pos]; 177 | } 178 | yield(); 179 | } 180 | 181 | // If the minBoundY wasn't updated 182 | // we can savely assume that buffer_back[pos] == buffer[pos] 183 | // holdes true for all values of pos 184 | if (minBoundY == UINT16_MAX) return; 185 | 186 | set_CS(LOW); 187 | _spi->beginTransaction(_spiSettings); 188 | 189 | for (y = minBoundY; y <= maxBoundY; y++) 190 | { 191 | for(int temp = 0; temp<8;temp++) 192 | { 193 | //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); 194 | setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); 195 | //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1); 196 | uint32_t const pixbufcount = maxBoundX-minBoundX+1; 197 | uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); 198 | for (x = minBoundX; x <= maxBoundX; x++) 199 | { 200 | pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; 201 | } 202 | #ifdef ESP_PLATFORM 203 | _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); 204 | #else 205 | _spi->transfer(pixbuf, NULL, 2 * pixbufcount); 206 | #endif 207 | rtos_free(pixbuf); 208 | } 209 | } 210 | _spi->endTransaction(); 211 | set_CS(HIGH); 212 | 213 | #else 214 | set_CS(LOW); 215 | _spi->beginTransaction(_spiSettings); 216 | uint8_t x, y; 217 | for (y = 0; y < _buffheight; y++) 218 | { 219 | for(int temp = 0; temp<8;temp++) 220 | { 221 | //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); 222 | //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); 223 | setAddrWindow(y*8+temp,0,1,displayWidth); 224 | uint32_t const pixbufcount = displayWidth; 225 | uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); 226 | for (x = 0; x < displayWidth; x++) 227 | { 228 | pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; 229 | } 230 | #ifdef ESP_PLATFORM 231 | _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); 232 | #else 233 | _spi->transfer(pixbuf, NULL, 2 * pixbufcount); 234 | #endif 235 | rtos_free(pixbuf); 236 | } 237 | } 238 | _spi->endTransaction(); 239 | set_CS(HIGH); 240 | 241 | #endif 242 | } 243 | 244 | virtual void resetOrientation() { 245 | uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; 246 | sendCommand(ST77XX_MADCTL); 247 | WriteData(madctl); 248 | delay(10); 249 | } 250 | 251 | virtual void flipScreenVertically() { 252 | uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; 253 | sendCommand(ST77XX_MADCTL); 254 | WriteData(madctl); 255 | delay(10); 256 | } 257 | 258 | virtual void mirrorScreen() { 259 | uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; 260 | sendCommand(ST77XX_MADCTL); 261 | WriteData(madctl); 262 | delay(10); 263 | } 264 | 265 | virtual void setRotation(uint8_t r) { 266 | uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; 267 | if (r == 1) { madctl = 0xC0; } 268 | if (r == 2) { madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; } 269 | if (r == 3) { madctl = 0x00; } 270 | sendCommand(ST77XX_MADCTL); 271 | WriteData(madctl); 272 | delay(10); 273 | } 274 | 275 | void setRGB(uint16_t c) 276 | { 277 | 278 | this->_RGB=0x00|c>>8|c<<8&0xFF00; 279 | } 280 | 281 | void displayOn(void) { 282 | //sendCommand(DISPLAYON); 283 | } 284 | 285 | void displayOff(void) { 286 | //sendCommand(DISPLAYOFF); 287 | } 288 | 289 | //#define ST77XX_MADCTL_MY 0x80 290 | //#define ST77XX_MADCTL_MX 0x40 291 | //#define ST77XX_MADCTL_MV 0x20 292 | //#define ST77XX_MADCTL_ML 0x10 293 | protected: 294 | // Send all the init commands 295 | virtual void sendInitCommands() 296 | { 297 | sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay 298 | delay(150); 299 | 300 | sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay 301 | delay(10); 302 | 303 | sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color 304 | WriteData(0x55); 305 | delay(10); 306 | 307 | sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh 308 | WriteData(0x08); 309 | 310 | sendCommand(ST77XX_CASET); // 5: Column addr set, 311 | WriteData(0x00); 312 | WriteData(0x00); // XSTART = 0 313 | WriteData(0x00); 314 | WriteData(240); // XEND = 240 315 | 316 | sendCommand(ST77XX_RASET); // 6: Row addr set, 317 | WriteData(0x00); 318 | WriteData(0x00); // YSTART = 0 319 | WriteData(320>>8); 320 | WriteData(320&0xFF); // YSTART = 320 321 | 322 | sendCommand(ST77XX_SLPOUT); // 7: hack 323 | delay(10); 324 | 325 | sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay 326 | delay(10); 327 | 328 | sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay 329 | delay(10); 330 | 331 | sendCommand(ST77XX_INVON); // 10: invert 332 | delay(10); 333 | 334 | //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; 335 | uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; 336 | sendCommand(ST77XX_MADCTL); 337 | WriteData(madctl); 338 | delay(10); 339 | setRGB(ST77XX_GREEN); 340 | } 341 | 342 | 343 | private: 344 | 345 | void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 346 | x += (320-displayWidth)/2; 347 | y += (240-displayHeight)/2; 348 | uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); 349 | uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); 350 | 351 | writeCommand(ST77XX_CASET); // Column addr set 352 | SPI_WRITE32(xa); 353 | 354 | writeCommand(ST77XX_RASET); // Row addr set 355 | SPI_WRITE32(ya); 356 | 357 | writeCommand(ST77XX_RAMWR); // write to RAM 358 | } 359 | int getBufferOffset(void) { 360 | return 0; 361 | } 362 | inline void set_CS(bool level) { 363 | if (_cs != (uint8_t) -1) { 364 | digitalWrite(_cs, level); 365 | } 366 | }; 367 | inline void sendCommand(uint8_t com) __attribute__((always_inline)){ 368 | set_CS(HIGH); 369 | digitalWrite(_dc, LOW); 370 | set_CS(LOW); 371 | _spi->beginTransaction(_spiSettings); 372 | _spi->transfer(com); 373 | _spi->endTransaction(); 374 | set_CS(HIGH); 375 | digitalWrite(_dc, HIGH); 376 | } 377 | 378 | inline void WriteData(uint8_t data) __attribute__((always_inline)){ 379 | digitalWrite(_cs, LOW); 380 | _spi->beginTransaction(_spiSettings); 381 | _spi->transfer(data); 382 | _spi->endTransaction(); 383 | digitalWrite(_cs, HIGH); 384 | } 385 | void SPI_WRITE32(uint32_t l) 386 | { 387 | _spi->transfer(l >> 24); 388 | _spi->transfer(l >> 16); 389 | _spi->transfer(l >> 8); 390 | _spi->transfer(l); 391 | } 392 | void writeCommand(uint8_t cmd) { 393 | digitalWrite(_dc, LOW); 394 | _spi->transfer(cmd); 395 | digitalWrite(_dc, HIGH); 396 | } 397 | 398 | // Private functions 399 | void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { 400 | this->geometry = g; 401 | 402 | switch (g) { 403 | case GEOMETRY_128_128: 404 | this->displayWidth = 128; 405 | this->displayHeight = 128; 406 | break; 407 | case GEOMETRY_128_64: 408 | this->displayWidth = 128; 409 | this->displayHeight = 64; 410 | break; 411 | case GEOMETRY_128_32: 412 | this->displayWidth = 128; 413 | this->displayHeight = 32; 414 | break; 415 | case GEOMETRY_64_48: 416 | this->displayWidth = 64; 417 | this->displayHeight = 48; 418 | break; 419 | case GEOMETRY_64_32: 420 | this->displayWidth = 64; 421 | this->displayHeight = 32; 422 | break; 423 | case GEOMETRY_RAWMODE: 424 | this->displayWidth = width > 0 ? width : 128; 425 | this->displayHeight = height > 0 ? height : 64; 426 | break; 427 | } 428 | uint8_t tmp=displayHeight % 8; 429 | uint8_t _buffheight=displayHeight / 8; 430 | 431 | if(tmp!=0) 432 | _buffheight++; 433 | this->displayBufferSize = displayWidth * _buffheight ; 434 | } 435 | 436 | 437 | 438 | }; 439 | 440 | #endif -------------------------------------------------------------------------------- /arduino-cli.yaml: -------------------------------------------------------------------------------- 1 | board_manager: 2 | additional_urls: 3 | - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 4 | - https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json 5 | - https://github.com/HelTecAutomation/Heltec_nRF52/releases/download/1.7.0/package_heltec_nrf_index.json 6 | - https://adafruit.github.io/arduino-board-index/package_adafruit_index.json 7 | - http://unsigned.io/arduino/package_unsignedio_UnsignedBoards_index.json 8 | -------------------------------------------------------------------------------- /esp32_btbufs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import re 4 | 5 | try: 6 | target_path = sys.argv[1] 7 | rxbuf_size = 0; rxbuf_minsize = 6144 8 | txbuf_size = 0; txbuf_minsize = 384 9 | line_index = 0 10 | rx_line_index = 0 11 | tx_line_index = 0 12 | with open(target_path) as sf: 13 | for line in sf: 14 | line_index += 1 15 | if line.startswith("#define RX_QUEUE_SIZE"): 16 | ents = re.sub(" +", " ", line).split(" ") 17 | try: 18 | rxbuf_size = int(ents[2]) 19 | rx_line_index = line_index 20 | except Exception as e: 21 | print(f"Could not parse Bluetooth RX_QUEUE_SIZE: {e}") 22 | 23 | if line.startswith("#define TX_QUEUE_SIZE"): 24 | ents = re.sub(" +", " ", line).split(" ") 25 | try: 26 | txbuf_size = int(ents[2]) 27 | tx_line_index = line_index 28 | except Exception as e: 29 | print(f"Could not parse Bluetooth TX_QUEUE_SIZE: {e}") 30 | 31 | if rxbuf_size != 0 and txbuf_size != 0: 32 | break 33 | 34 | if rxbuf_size < rxbuf_minsize: 35 | print(f"Error: The configured ESP32 Bluetooth RX buffer size is too small, please set it to at least {rxbuf_minsize} and try compiling again.") 36 | print(f"The buffer configuration can be modified in line {rx_line_index} of: {target_path}") 37 | exit(1) 38 | 39 | if txbuf_size < txbuf_minsize: 40 | print(f"Error: The configured ESP32 Bluetooth TX buffer size is too small, please set it to at least {txbuf_minsize} and try compiling again.") 41 | print(f"The buffer configuration can be modified in line {tx_line_index} of: {target_path}") 42 | exit(1) 43 | 44 | exit(0) 45 | 46 | except Exception as e: 47 | print(f"Could not determine ESP32 Bluetooth buffer configuration: {e}") 48 | print("Please fix this error and try again") -------------------------------------------------------------------------------- /partition_hashes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2024, Mark Qvist 4 | 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import sys 20 | import RNS 21 | import json 22 | import hashlib 23 | import subprocess 24 | 25 | major_version = None 26 | minor_version = None 27 | target_version = None 28 | 29 | target_file = os.path.join(sys.argv[1]) 30 | 31 | if sys.argv[1] == "from_device": 32 | from_device = True 33 | else: 34 | from_device = False 35 | 36 | if not from_device: 37 | firmware_data = open(target_file, "rb").read() 38 | calc_hash = hashlib.sha256(firmware_data[0:-32]).digest() 39 | part_hash = firmware_data[-32:] 40 | 41 | if calc_hash == part_hash: 42 | print(RNS.hexrep(part_hash, delimit=False)) 43 | 44 | else: 45 | try: 46 | cmdresult = subprocess.run(["rnodeconf", sys.argv[2], "-L"], stdout=subprocess.PIPE).stdout.decode('utf-8') 47 | part_hash = cmdresult.split("The actual firmware hash is: ")[1].replace("\n", "") 48 | print(part_hash) 49 | except Exception as e: 50 | print("Could not get partition hash from device: "+str(e)) 51 | -------------------------------------------------------------------------------- /release_hashes.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | # Copyright (C) 2024, Mark Qvist 4 | 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import json 20 | import hashlib 21 | 22 | major_version = None 23 | minor_version = None 24 | target_version = None 25 | 26 | file = open("Config.h", "rb") 27 | config_data = file.read().splitlines() 28 | for line in config_data: 29 | dline = line.decode("utf-8").strip() 30 | components = dline.split() 31 | if dline.startswith("#define MAJ_VERS"): 32 | major_version = "%01d" % ord(bytes.fromhex(dline.split()[2].split("x")[1])) 33 | if dline.startswith("#define MIN_VERS"): 34 | minor_version = "%02d" % ord(bytes.fromhex(dline.split()[2].split("x")[1])) 35 | 36 | target_version = major_version+"."+minor_version 37 | 38 | release_hashes = {} 39 | target_dir = "./Release" 40 | files = os.listdir(target_dir) 41 | for filename in files: 42 | if os.path.isfile(os.path.join(target_dir, filename)): 43 | if filename.startswith("rnode_firmware"): 44 | file = open(os.path.join(target_dir, filename), "rb") 45 | release_hashes[filename] = { 46 | "hash": hashlib.sha256(file.read()).hexdigest(), 47 | "version": target_version 48 | } 49 | 50 | print(json.dumps(release_hashes)) -------------------------------------------------------------------------------- /sx126x.h: -------------------------------------------------------------------------------- 1 | // Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SX126X_H 5 | #define SX126X_H 6 | 7 | #include 8 | #include 9 | #include "Modem.h" 10 | 11 | #define LORA_DEFAULT_SS_PIN 10 12 | #define LORA_DEFAULT_RESET_PIN 9 13 | #define LORA_DEFAULT_DIO0_PIN 2 14 | #define LORA_DEFAULT_RXEN_PIN -1 15 | #define LORA_DEFAULT_TXEN_PIN -1 16 | #define LORA_DEFAULT_BUSY_PIN -1 17 | #define LORA_MODEM_TIMEOUT_MS 20E3 18 | 19 | #define PA_OUTPUT_RFO_PIN 0 20 | #define PA_OUTPUT_PA_BOOST_PIN 1 21 | 22 | #define RSSI_OFFSET 157 23 | 24 | class sx126x : public Stream { 25 | public: 26 | sx126x(); 27 | 28 | int begin(long frequency); 29 | void end(); 30 | 31 | int beginPacket(int implicitHeader = false); 32 | int endPacket(); 33 | 34 | int parsePacket(int size = 0); 35 | int packetRssi(); 36 | int packetRssi(uint8_t pkt_snr_raw); 37 | int currentRssi(); 38 | uint8_t packetRssiRaw(); 39 | uint8_t currentRssiRaw(); 40 | uint8_t packetSnrRaw(); 41 | float packetSnr(); 42 | long packetFrequencyError(); 43 | 44 | // from Print 45 | virtual size_t write(uint8_t byte); 46 | virtual size_t write(const uint8_t *buffer, size_t size); 47 | 48 | // from Stream 49 | virtual int available(); 50 | virtual int read(); 51 | virtual int peek(); 52 | virtual void flush(); 53 | 54 | void onReceive(void(*callback)(int)); 55 | 56 | void receive(int size = 0); 57 | void standby(); 58 | void sleep(); 59 | void reset(void); 60 | 61 | bool preInit(); 62 | uint8_t getTxPower(); 63 | void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); 64 | uint32_t getFrequency(); 65 | void setFrequency(long frequency); 66 | void setSpreadingFactor(int sf); 67 | long getSignalBandwidth(); 68 | void setSignalBandwidth(long sbw); 69 | void setCodingRate4(int denominator); 70 | void setPreambleLength(long preamble_symbols); 71 | void setSyncWord(uint16_t sw); 72 | bool dcd(); 73 | void enableCrc(); 74 | void disableCrc(); 75 | void enableTCXO(); 76 | void disableTCXO(); 77 | 78 | void rxAntEnable(); 79 | void loraMode(); 80 | void waitOnBusy(); 81 | void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size); 82 | void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size); 83 | void writeBuffer(const uint8_t* buffer, size_t size); 84 | void readBuffer(uint8_t* buffer, size_t size); 85 | void setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); 86 | 87 | void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro); 88 | 89 | // deprecated 90 | void crc() { enableCrc(); } 91 | void noCrc() { disableCrc(); } 92 | 93 | byte random(); 94 | 95 | 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, int rxen = LORA_DEFAULT_RXEN_PIN); 96 | void setSPIFrequency(uint32_t frequency); 97 | 98 | void dumpRegisters(Stream& out); 99 | 100 | private: 101 | void explicitHeaderMode(); 102 | void implicitHeaderMode(); 103 | 104 | void handleDio0Rise(); 105 | 106 | uint8_t readRegister(uint16_t address); 107 | void writeRegister(uint16_t address, uint8_t value); 108 | uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); 109 | 110 | static void onDio0Rise(); 111 | 112 | void handleLowDataRate(); 113 | void optimizeModemSensitivity(); 114 | 115 | void calibrate(void); 116 | void calibrate_image(long frequency); 117 | 118 | private: 119 | SPISettings _spiSettings; 120 | int _ss; 121 | int _reset; 122 | int _dio0; 123 | int _rxen; 124 | int _busy; 125 | long _frequency; 126 | int _txp; 127 | uint8_t _sf; 128 | uint8_t _bw; 129 | uint8_t _cr; 130 | uint8_t _ldro; 131 | int _packetIndex; 132 | int _preambleLength; 133 | int _implicitHeaderMode; 134 | int _payloadLength; 135 | int _crcMode; 136 | int _fifo_tx_addr_ptr; 137 | int _fifo_rx_addr_ptr; 138 | uint8_t _packet[255]; 139 | bool _preinit_done; 140 | void (*_onReceive)(int); 141 | }; 142 | 143 | extern sx126x sx126x_modem; 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /sx127x.h: -------------------------------------------------------------------------------- 1 | // Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SX1276_H 5 | #define SX1276_H 6 | 7 | #include 8 | #include 9 | #include "Modem.h" 10 | 11 | #define LORA_DEFAULT_SS_PIN 10 12 | #define LORA_DEFAULT_RESET_PIN 9 13 | #define LORA_DEFAULT_DIO0_PIN 2 14 | #define LORA_DEFAULT_BUSY_PIN -1 15 | 16 | #define PA_OUTPUT_RFO_PIN 0 17 | #define PA_OUTPUT_PA_BOOST_PIN 1 18 | 19 | #define RSSI_OFFSET 157 20 | 21 | // Modem status flags 22 | #define SIG_DETECT 0x01 23 | #define SIG_SYNCED 0x02 24 | #define RX_ONGOING 0x04 25 | 26 | class sx127x : public Stream { 27 | public: 28 | sx127x(); 29 | 30 | int begin(long frequency); 31 | void end(); 32 | 33 | int beginPacket(int implicitHeader = false); 34 | int endPacket(); 35 | 36 | int parsePacket(int size = 0); 37 | int packetRssi(); 38 | int packetRssi(uint8_t pkt_snr_raw); 39 | int currentRssi(); 40 | uint8_t packetRssiRaw(); 41 | uint8_t currentRssiRaw(); 42 | uint8_t packetSnrRaw(); 43 | float packetSnr(); 44 | long packetFrequencyError(); 45 | 46 | // from Print 47 | virtual size_t write(uint8_t byte); 48 | virtual size_t write(const uint8_t *buffer, size_t size); 49 | 50 | // from Stream 51 | virtual int available(); 52 | virtual int read(); 53 | virtual int peek(); 54 | virtual void flush(); 55 | 56 | void onReceive(void(*callback)(int)); 57 | 58 | void receive(int size = 0); 59 | void standby(); 60 | void sleep(); 61 | 62 | bool preInit(); 63 | uint8_t getTxPower(); 64 | void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); 65 | uint32_t getFrequency(); 66 | void setFrequency(unsigned long frequency); 67 | void setSpreadingFactor(int sf); 68 | long getSignalBandwidth(); 69 | void setSignalBandwidth(long sbw); 70 | void setCodingRate4(int denominator); 71 | void setPreambleLength(long preamble_symbols); 72 | void setSyncWord(uint8_t sw); 73 | bool dcd(); 74 | void enableCrc(); 75 | void disableCrc(); 76 | void enableTCXO(); 77 | void disableTCXO(); 78 | 79 | byte random(); 80 | 81 | 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); 82 | void setSPIFrequency(uint32_t frequency); 83 | 84 | private: 85 | void explicitHeaderMode(); 86 | void implicitHeaderMode(); 87 | 88 | void handleDio0Rise(); 89 | 90 | uint8_t readRegister(uint8_t address); 91 | void writeRegister(uint8_t address, uint8_t value); 92 | uint8_t singleTransfer(uint8_t address, uint8_t value); 93 | 94 | static void onDio0Rise(); 95 | 96 | void handleLowDataRate(); 97 | void optimizeModemSensitivity(); 98 | 99 | private: 100 | SPISettings _spiSettings; 101 | int _ss; 102 | int _reset; 103 | int _dio0; 104 | int _busy; 105 | long _frequency; 106 | int _packetIndex; 107 | int _implicitHeaderMode; 108 | bool _preinit_done; 109 | void (*_onReceive)(int); 110 | }; 111 | 112 | extern sx127x sx127x_modem; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /sx128x.h: -------------------------------------------------------------------------------- 1 | // Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SX128X_H 5 | #define SX128X_H 6 | 7 | #include 8 | #include 9 | #include "Modem.h" 10 | 11 | #define LORA_DEFAULT_SS_PIN 10 12 | #define LORA_DEFAULT_RESET_PIN 9 13 | #define LORA_DEFAULT_DIO0_PIN 2 14 | #define LORA_DEFAULT_RXEN_PIN -1 15 | #define LORA_DEFAULT_TXEN_PIN -1 16 | #define LORA_DEFAULT_BUSY_PIN -1 17 | #define LORA_MODEM_TIMEOUT_MS 15E3 18 | #define PA_OUTPUT_RFO_PIN 0 19 | #define PA_OUTPUT_PA_BOOST_PIN 1 20 | #define RSSI_OFFSET 157 21 | 22 | class sx128x : public Stream { 23 | public: 24 | sx128x(); 25 | 26 | int begin(unsigned long frequency); 27 | void end(); 28 | void reset(); 29 | 30 | int beginPacket(int implicitHeader = false); 31 | int endPacket(); 32 | 33 | int parsePacket(int size = 0); 34 | int packetRssi(); 35 | int packetRssi(uint8_t pkt_snr_raw); 36 | int currentRssi(); 37 | uint8_t packetRssiRaw(); 38 | uint8_t currentRssiRaw(); 39 | uint8_t packetSnrRaw(); 40 | float packetSnr(); 41 | long packetFrequencyError(); 42 | 43 | // from Print 44 | virtual size_t write(uint8_t byte); 45 | virtual size_t write(const uint8_t *buffer, size_t size); 46 | 47 | // from Stream 48 | virtual int available(); 49 | virtual int read(); 50 | virtual int peek(); 51 | virtual void flush(); 52 | 53 | void onReceive(void(*callback)(int)); 54 | 55 | void receive(int size = 0); 56 | void standby(); 57 | void sleep(); 58 | 59 | bool preInit(); 60 | uint8_t getTxPower(); 61 | void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); 62 | uint32_t getFrequency(); 63 | void setFrequency(uint32_t frequency); 64 | void setSpreadingFactor(int sf); 65 | uint8_t getSpreadingFactor(); 66 | uint32_t getSignalBandwidth(); 67 | void setSignalBandwidth(uint32_t sbw); 68 | void setCodingRate4(int denominator); 69 | uint8_t getCodingRate4(); 70 | void setPreambleLength(long preamble_symbols); 71 | void setSyncWord(int sw); 72 | bool dcd(); 73 | void clearIRQStatus(); 74 | void enableCrc(); 75 | void disableCrc(); 76 | void enableTCXO(); 77 | void disableTCXO(); 78 | 79 | void txAntEnable(); 80 | void rxAntEnable(); 81 | void loraMode(); 82 | void waitOnBusy(); 83 | void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size); 84 | void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size); 85 | void writeBuffer(const uint8_t* buffer, size_t size); 86 | void readBuffer(uint8_t* buffer, size_t size); 87 | void setPacketParams(uint32_t target_preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); 88 | void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr); 89 | 90 | void crc() { enableCrc(); } 91 | void noCrc() { disableCrc(); } 92 | 93 | 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, int rxen = LORA_DEFAULT_RXEN_PIN, int txen = LORA_DEFAULT_TXEN_PIN); 94 | void setSPIFrequency(uint32_t frequency); 95 | 96 | void dumpRegisters(Stream& out); 97 | 98 | private: 99 | void explicitHeaderMode(); 100 | void implicitHeaderMode(); 101 | 102 | bool getPacketValidity(); 103 | void handleDio0Rise(); 104 | 105 | uint8_t readRegister(uint16_t address); 106 | void writeRegister(uint16_t address, uint8_t value); 107 | uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); 108 | 109 | static void onDio0Rise(); 110 | 111 | void handleLowDataRate(); 112 | void optimizeModemSensitivity(); 113 | 114 | private: 115 | SPISettings _spiSettings; 116 | int _ss; 117 | int _reset; 118 | int _dio0; 119 | int _rxen; 120 | int _txen; 121 | int _busy; 122 | int _modem; 123 | unsigned long _frequency; 124 | int _txp; 125 | uint8_t _sf; 126 | uint8_t _bw; 127 | uint8_t _cr; 128 | int _packetIndex; 129 | uint32_t _preambleLength; 130 | int _implicitHeaderMode; 131 | int _payloadLength; 132 | int _crcMode; 133 | int _fifo_tx_addr_ptr; 134 | int _fifo_rx_addr_ptr; 135 | uint8_t _packet[256]; 136 | bool _preinit_done; 137 | bool _tcxo; 138 | bool _radio_online; 139 | int _rxPacketLength; 140 | uint32_t _bitrate; 141 | void (*_receive_callback)(int); 142 | }; 143 | 144 | extern sx128x sx128x_modem; 145 | 146 | #endif 147 | --------------------------------------------------------------------------------