From: jacob.eva Date: Mon, 20 May 2024 11:36:43 +0000 (+0100) Subject: Start community edition project X-Git-Url: https://git.tnoah.ca/?a=commitdiff_plain;h=c807e3337f3b98be04dd0a60a794d521165c19bb;p=lora-radio.git Start community edition project --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3f8802e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Board support +If you wish to add support for a specific board to the project, all you have to do (if it's ESP32 or nRF52), is write an additional entry for `Boards.h`. + +This entry should include, at a minimum, the following: +* whether the device has bluetooth / BLE +* whether the device has a PMU +* whether the device has an EEPROM (false in all cases for nRF52, true for ESP32) +* pin mappings for SPI NSS, SCLK, MOSI, MISO, modem reset and dio0 +* the type of modem on the board (if undefined it defaults to SX127x) +* whether the modem has a busy pin +* RX and TX leds (preferably LEDs of different colours) + +An example of a minimal entry can be seen below: +` +#elif BOARD_MODEL == BOARD_MY_WICKED_BOARD + #define HAS_BLUETOOTH true + #define HAS_PMU true + #define HAS_EEPROM true + #define EEPROM_SIZE 296 // minimum EEPROM size + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + const int pin_cs = 20; + const int pin_reset = 19; + // const int pin_cs = 1; not needed here + // const int pin_sclk = 2; not needed here + // const int pin_mosi = 3; not needed here + // const int pin_miso = 4; not needed here + const int pin_dio = 18; + // const int pin_busy = 0; not present + const int pin_led_rx = 5; + const int pin_led_tx = 6; +` + +In some cases the SPI pins will not be required, as they will be the default pins for the SPI library supporting the board anyway, and therefore do not need overriding in the config. + +If the SX1262 is being used the following should also be considered: +* whether DIO2 should be used as the RF switch (DIO2_AS_RF_SWITCH) +* whether an rf on/off switch also has to be operated (through the pin pin_rxen) +* whether a TCXO is connected to the modem (HAS_TCXO) +* the enable pin for the TCXO (if present) + +An example of an entry using the SX1262 modem can be seen below: +` +#elif BOARD_MODEL == BOARD_MY_WICKED_BOARD + #define HAS_BLUETOOTH true + #define HAS_PMU true + #define HAS_EEPROM true + #define EEPROM_SIZE 296 // minimum EEPROM size + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_TCXO true + #define HAS_BUSY true + const int pin_cs = 20; + const int pin_reset = 19; + const int pin_rxen = 10; + // const int pin_cs = 1; not needed here + // const int pin_sclk = 2; not needed here + // const int pin_mosi = 3; not needed here + // const int pin_miso = 4; not needed here + const int pin_dio = 18; + const int pin_busy = 7; + const int pin_tcxo_enable = -1; + const int pin_led_rx = 5; + const int pin_led_tx = 6; +` + +If the SX1280 is being used, the following should also be added: +* the TXEN and RXEN pins + +An example of an entry using the SX1280 modem can be seen below: +` +#elif BOARD_MODEL == BOARD_MY_WICKED_BOARD + #define HAS_BLUETOOTH true + #define HAS_PMU true + #define HAS_EEPROM true + #define EEPROM_SIZE 296 // minimum EEPROM size + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define MODEM SX1280 + #define HAS_BUSY true + #define HAS_RF_SWITCH_RX_TX true + const int pin_cs = 20; + const int pin_reset = 19; + const int pin_rxen = 10; + const int pin_txen = 11; + // const int pin_cs = 1; not needed here + // const int pin_sclk = 2; not needed here + // const int pin_mosi = 3; not needed here + // const int pin_miso = 4; not needed here + const int pin_dio = 18; + const int pin_busy = 7; + const int pin_tcxo_enable = -1; + const int pin_led_rx = 5; + const int pin_led_tx = 6; +` + +Please submit this, and any other support in different areas of the project your board may require, as a PR for my consideration. + +# Feature request +Feature requests are welcomed, given that those requesting it are happy to write it themselves, or a contributor considers it to be important enough to them to write it themselves. They must be written and **properly** tested before being proposed as a pull request for the project on [GitHub](https://github.com/liberatedsystems/RNode_Firmware_CE). **Manufacturers are encouraged to contribute support for their products back to this repository**, and such support will be receieved gladly, given it does not effect support for other products or boards. + +# Caveat +All contributions must not be written using **any** LLM (ChatGPT, etc.), please handwrite them **only**. Any PRs with proposed contributions which have been discovered to be written using an LLM will **NOT** be merged. The contributor concerned may rewrite their entire pull request **by hand** and it may be reconsidered for merging in the future. diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..5e3888d --- /dev/null +++ b/FAQ.md @@ -0,0 +1,8 @@ +# Frequently asked questions +## Can I produce and sell my own RNodes using this project? +Yes! Feel free to use the firmware in this repository for your produced RNodes. You do not have to pay a license to use this software commercially, as it is licensed under the GPLv3. If you fork it, 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. Please also consider contributing additional features you design to this repository, to allow others to also use them. + +It is also likely worth you reading [this material from Mark Qvist](https://unsigned.io/sell_rnodes.html). Do consider donating to him if your venture is successful, without him this project would simply be a schizo fever dream. + +## Write a hecking code of conduct!! +no diff --git a/README.md b/README.md index 34a7cdb..97ea587 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# RNode Firmware +# RNode Firmware - Community Edition -This is the open firmware that powers RNode devices. +This is the community maintained fork of the open firmware which powers RNode devices. It has been created to continue to expand development and support for more hardware devices, as the upstream repository is no longer accepting PRs for new hardware support. The original repository by Mark Qvist can be found [here](https://github.com/markqvist/RNode_Firmware). 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). @@ -14,28 +14,47 @@ The RNode system is primarily software, which *transforms* different kinds of av ## Latest Release -The latest release, installable through `rnodeconf`, is version `1.68`. This release brings the following changes: +The latest release, installable through `rnodeconf`, is version `1.72`. This release brings the following changes: -- Fixed a bug in high-bandwidth optimisation -- Fixed a potential memory overflow during firmware update -- Fixed a bug in LoRa modem status updates -- Updated bootstrap console with latest packages +- Added support for flashing T3S3 boards +- Added deep sleep support on T3S3 +- Various quality updates for nRF / RAK4631 +- Fixed a bug with antenna switch utilisation on RAK4631 +- Updated console image to include latest packages -You must have at least version `2.1.3` of `rnodeconf` installed to update the RNode Firmware to version `1.67`. Get it by updating the `rns` package to at least version `0.6.4`. +You must have at least version `2.1.3` of `rnodeconf` installed to update the RNode Firmware to version `1.72`. Get it by updating the `rns` package to at least version `0.6.4`. -## A Self-Replicating System +## Supported products and boards -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. +### Products +| Name | Manufacturer | Link | Transceiver | MCU | Description | +| :---: | :---: | :---: | :---: | :---: | :---: | +| Handheld v2.x RNodes | [Mark Qvist](https://unsigned.io) | [Buy here](https://unsigned.io/shop/product/handheld-rnode) | SX1276 | ESP32 | +| Original v1.x RNodes | [Mark Qvist](https://unsigned.io) | [Buy here](https://unsigned.io/shop/product/rnode) | SX1276 | ESP32 | This product is discontinued | -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. +### Homebrew devices +| Board name | Link | Transceiver | MCU | Description | +| :--- | :---: | :---: | :---: | :---: | +| LilyGO LoRa32 v1.0 | [Buy here](https://www.lilygo.cc/products/lora32-v1-0) | SX1276/8 | ESP32 | +| LilyGO T-BEAM v1.1 | [Buy here](https://www.lilygo.cc/products/t-beam-v1-1-esp32-lora-module) | SX1276/8 | ESP32 | +| LilyGO LoRa32 v2.0 | No link | SX1276/8 | ESP32 | Discontinued? | +| LilyGO LoRa32 v2.1 | [Buy here](https://www.lilygo.cc/products/lora3) | SX1276/8 | ESP32 | With and without TCXO | +| Heltec LoRa32 v2 | No link | SX1276/8 | ESP32 | Discontinued? | +| Heltec LoRa32 v3 | [Buy here](https://heltec.org/project/wifi-lora-32-v3/) | SX1276/8 | ESP32 | +| Homebrew ESP32 boards | | Any supported | ESP32 | This can be any board with an Adafruit Feather (or generic) ESP32 chip | - +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](/CONTRIBUTING.md) and help create a board definition and pin mapping for it! -
Where there is no Internet, RNodes will still communicate


+ -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. +## Supported Transceiver Modules +The RNode Firmware supports all transceiver modules based on the following chips: +* Semtech SX1262 +* Semtech SX1276 +* Semtech SX1278 +* Semtech SX1280 -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. +These also must have an **SPI interface** and **DIO_0** pin connected to the MCU directly. ## One Tool, Many Uses @@ -54,40 +73,29 @@ Depending on configuration, it can be used for local networking purposes, or to ## Types & Performance -RNodes can be made in many different configurations, and can use many different radio bands, but they will generally operate in the **433 MHz**, **868 MHz**, **915 MHZ** and **2.4 GHz** bands. They will usually offer configurable on-air data speeds between just a **few hundred bits per second**, up to **a couple of megabits per second**. Maximum output power will depend on the transceiver and PA setup used, but will generally lie between **17 dbm** and **27 dBm**. +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 **hundreds of kilobits per second**. Maximum output power will depend on the transceiver and PA setup used, but will generally lie between **17 dbm (50mW)** and **27 dBm (500mW)**. The RNode system has been designed to allow reliable systems for basic human communications, over very wide areas, while using very little power, being cheap to build, free to operate, and near impossible to censor. While **speeds are lower** than WiFi, typical communication **ranges are many times higher**. Several kilometers can be acheived with usable bitrates, even in urban areas, and over **100 kilometers** can be achieved in line-of-sight conditions. -## Supported Boards & Devices -It's easy to create your own RNodes from one of the supported development boards and devices. If a device or board you want to use is not yet supported, you are welcome to join the effort and help creating a board definition and pin mapping for it! +## A Self-Replicating System + +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. + +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. - + -The RNode Firmware supports the following boards: +
Where there is no Internet, RNodes will still communicate


-- Handheld v2.x RNodes from [unsigned.io](https://unsigned.io/shop/product/handheld-rnode) -- Original v1.x RNodes from [unsigned.io](https://unsigned.io/shop/product/rnode) -- LilyGO T-Beam v1.1 devices with SX1276/8 LoRa chips -- LilyGO T-Beam v1.1 devices with SX1262/8 LoRa chips -- LilyGO LoRa32 v1.0 devices -- LilyGO LoRa32 v2.0 devices -- LilyGO LoRa32 v2.1 devices (with and without TCXO) -- Heltec LoRa32 v2 devices -- Heltec LoRa32 v3 devices -- Homebrew RNodes based on ATmega1284p boards -- Homebrew RNodes based on ATmega2560 boards -- Homebrew RNodes based on Adafruit Feather ESP32 boards -- Homebrew RNodes based on generic ESP32 boards +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. -## Supported Transceiver Modules -The RNode Firmware supports all transceiver modules based on **Semtech SX1276** or **Semtech SX1278** chips, that have an **SPI interface** and expose the **DIO_0** interrupt pin from the chip. +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. -Support for **SX1262**, **SX1268** and **SX1280** is being implemented. Please support the project with donations if you want this faster! ## Getting Started Fast -You can download and flash the firmware to all the supported boards using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil). All firmware releases are now handled and installed directly through the `rnodeconf` utility, which is included in the `rns` package. It can be installed via `pip`: +You can download and flash the firmware to all the supported boards using [rnodeconf](https://github.com/markqvist/Reticulum). All firmware releases are handled and installed directly through the `rnodeconf` utility, which is included in the `rns` package. It can be installed via `pip`: ``` # Install rnodeconf via rns package @@ -104,10 +112,18 @@ For more detailed instruction and in-depth guides, you can have a look at some o - Learn the basics on how to [create and build your own RNode designs](https://unsigned.io/guides/2022_01_26_how-to-make-your-own-rnodes.html) from scratch - Once you've got the hang of it, start building RNodes for your community, or [even for selling them](https://unsigned.io/sell_rnodes.html) -If you would rather just buy a pre-made unit, you can visit [my shop](https://unsigned.io/shop) and purchase my particular version of the [Handheld RNode](https://unsigned.io/shop/handheld-rnode/), which I can assure you is made to the highest quality, and with a lot of care. +## Support development +### Hardware donations +If you would like to see support added for a board which you possess, you may donate it to myself, Jacob Eva, so that I can implement support for it into this project. There will be no official timescale given for implementation however, but I will try my best when I have time :) Please [contact me (scroll to the bottom)](https://liberatedsystems.co.uk/about) if you wish to donate hardware to the project. + +### Purchasing products +You can support the development of the RNode Firmware CE project by purchasing RNodes (to come in the future) and other products from [Liberated Embedded systems](https://liberatedsystems.co.uk), my online business. For more information on my business, please see [here](https://liberatedsystems.co.uk/about/). + +From time to time the creator of the [Reticulum](https://reticulum.network) project, Mark Qvist, produces his own RNodes and sells them through his [shop](https://unsigned.io/shop). You may support the development of open, free and private communications systems through purchasing these from him. -## Support RNode Development -You can help support the continued development of open, free and private communications systems by donating via one of the following channels: +### Monetary donations +I, the maintainer of this fork, am currently not accepting monetary donations. These are instead better directed to [Mark Qvist](https://unsigned.io), who created the original repository this one is based on, and is the core developer of the [Reticulum](https://reticulum.network) project, which is what the RNode Firmware was designed to supplement as a network adapter. +You can help support the continued development of open, free and private communications systems by donating to him via one of the following channels: - Monero: ``` @@ -124,14 +140,15 @@ You can help support the continued development of open, free and private communi - Ko-Fi: https://ko-fi.com/markqvist ## License & Use -The RNode Firmware is Copyright © 2024 Mark Qvist / [unsigned.io](https://unsigned.io), and is made available under the **GNU General Public License v3.0**. The source code includes an SX1276 driver that is released under MIT License, and Copyright © 2018 Sandeep Mistry / Mark Qvist. +The upstream RNode Firmware is Copyright © 2024 Mark Qvist / [unsigned.io](https://unsigned.io). +The modified RNode Firmware CE (community edition) is Copyright © Jacob Eva / [Liberated Embedded Systems](https://liberatedsystems.co.uk) and is made available under the **GNU General Public License v3.0**. -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). +The source code includes an SX1276 driver that is released under MIT License, and Copyright © 2018 Sandeep Mistry / Mark Qvist. The SX126x and SX128x drivers are adaptations of this original driver. -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). +You can obtain the source code from [my business Git instance](https://git.liberatedsystems.co.uk/jacobeva/RNode_Firmware_CE) or [GitHub](https://github.com/liberatedsystems/RNode_Firmware_CE). + +Every RNode which supports the console functionality also includes an internal copy of it's own firmware source code, that can be downloaded through the [RNode Bootstrap Console](https://unsigned.io/rnode_bootstrap_console), by putting the RNode into Console Mode (which can be activated by pressing the reset button two times within two seconds). The RNode Ecosystem is free and non-proprietary, and actively seeks to distribute it's ownership and control. If you want to build RNodes for commercial purposes, including selling them, you must do so adhering to the Open Source licenses that the various parts of the RNode project is released under, and under your own responsibility. If you distribute or modify this work, you **must** adhere to the terms of the GPLv3, including, but not limited to, providing up-to-date source code upon distribution, displaying appropriate copyright and license notices in prominent positions of all conveyed works, and making users aware of their rights to the software under the GPLv3. - -In practice, this means that you can use the firmware commercially, but you must understand your obligation to provide all future users of the system with the same rights, that you have been provided by the GPLv3. If you intend using the RNode Firmware commercially, it is worth reading [this page](https://unsigned.io/sell_rnodes.html). diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino deleted file mode 100644 index 4410a91..0000000 --- a/RNode_Firmware.ino +++ /dev/null @@ -1,1433 +0,0 @@ -// Copyright (C) 2023, Mark Qvist - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include "Utilities.h" - -FIFOBuffer serialFIFO; -uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1]; - -FIFOBuffer16 packet_starts; -uint16_t packet_starts_buf[CONFIG_QUEUE_MAX_LENGTH+1]; - -FIFOBuffer16 packet_lengths; -uint16_t packet_lengths_buf[CONFIG_QUEUE_MAX_LENGTH+1]; - -uint8_t packet_queue[CONFIG_QUEUE_SIZE]; - -volatile uint8_t queue_height = 0; -volatile uint16_t queued_bytes = 0; -volatile uint16_t queue_cursor = 0; -volatile uint16_t current_packet_start = 0; -volatile bool serial_buffering = false; -#if HAS_BLUETOOTH || HAS_BLE == true - bool bt_init_ran = false; -#endif - -#if HAS_CONSOLE - #include "Console.h" -#endif - -char sbuf[128]; - -#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - bool packet_ready = false; -#endif - -void setup() { - #if MCU_VARIANT == MCU_ESP32 - boot_seq(); - EEPROM.begin(EEPROM_SIZE); - Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE); - #endif - - #if MCU_VARIANT == MCU_NRF52 - if (!eeprom_begin()) { - Serial.write("EEPROM initialisation failed.\r\n"); - } - #endif - - // Seed the PRNG for CSMA R-value selection - # if MCU_VARIANT == MCU_ESP32 - // On ESP32, get the seed value from the - // hardware RNG - int seed_val = (int)esp_random(); - #else - // Otherwise, get a pseudo-random seed - // value from an unconnected analog pin - int seed_val = analogRead(0); - #endif - randomSeed(seed_val); - - // Initialise serial communication - memset(serialBuffer, 0, sizeof(serialBuffer)); - fifo_init(&serialFIFO, serialBuffer, CONFIG_UART_BUFFER_SIZE); - - Serial.begin(serial_baudrate); - - #if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_RNODE_NG_22 - // Some boards need to wait until the hardware UART is set up before booting - // the full firmware. In the case of the RAK4631, the line below will wait - // until a serial connection is actually established with a master. Thus, it - // is disabled on this platform. - while (!Serial); - #endif - - serial_interrupt_init(); - - // Configure input and output pins - #if HAS_INPUT - input_init(); - #endif - - #if HAS_NP == false - pinMode(pin_led_rx, OUTPUT); - pinMode(pin_led_tx, OUTPUT); - #endif - - #if HAS_TCXO == true - if (pin_tcxo_enable != -1) { - pinMode(pin_tcxo_enable, OUTPUT); - digitalWrite(pin_tcxo_enable, HIGH); - } - #endif - - // Initialise buffers - memset(pbuf, 0, sizeof(pbuf)); - memset(cmdbuf, 0, sizeof(cmdbuf)); - - memset(packet_queue, 0, sizeof(packet_queue)); - - memset(packet_starts_buf, 0, sizeof(packet_starts_buf)); - fifo16_init(&packet_starts, packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH); - - memset(packet_lengths_buf, 0, sizeof(packet_starts_buf)); - fifo16_init(&packet_lengths, packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH); - - // Set chip select, reset and interrupt - // pins for the LoRa module - #if MODEM == SX1276 || MODEM == SX1278 - LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy); - #elif MODEM == SX1262 - LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen); - #elif MODEM == SX1280 - LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen); - #endif - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - init_channel_stats(); - - // Check installed transceiver chip and - // probe boot parameters. - if (LoRa->preInit()) { - modem_installed = true; - uint32_t lfr = LoRa->getFrequency(); - if (lfr == 0) { - // Normal boot - } else if (lfr == M_FRQ_R) { - // Quick reboot - #if HAS_CONSOLE - if (rtc_get_reset_reason(0) == POWERON_RESET) { - console_active = true; - } - #endif - } else { - // Unknown boot - } - LoRa->setFrequency(M_FRQ_S); - } else { - modem_installed = false; - } - #else - // Older variants only came with SX1276/78 chips, - // so assume that to be the case for now. - modem_installed = true; - #endif - - #if HAS_DISPLAY - #if HAS_EEPROM - if (EEPROM.read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { - #elif MCU_VARIANT == MCU_NRF52 - if (eeprom_read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { - #endif - eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE); - eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF); - } - disp_ready = display_init(); - update_display(); - #endif - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - #if HAS_PMU == true - pmu_ready = init_pmu(); - #endif - - #if HAS_BLUETOOTH || HAS_BLE == true - bt_init(); - bt_init_ran = true; - #endif - - if (console_active) { - #if HAS_CONSOLE - console_start(); - #else - kiss_indicate_reset(); - #endif - } else { - kiss_indicate_reset(); - } - #endif - - // Validate board health, EEPROM and config - validate_status(); - - if (op_mode != MODE_TNC) LoRa->setFrequency(0); -} - -void lora_receive() { - if (!implicit) { - LoRa->receive(); - } else { - LoRa->receive(implicit_l); - } -} - -inline void kiss_write_packet() { - serial_write(FEND); - serial_write(CMD_DATA); - for (uint16_t i = 0; i < read_len; i++) { - uint8_t byte = pbuf[i]; - if (byte == FEND) { serial_write(FESC); byte = TFEND; } - if (byte == FESC) { serial_write(FESC); byte = TFESC; } - serial_write(byte); - } - serial_write(FEND); - read_len = 0; - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - packet_ready = false; - #endif -} - -inline void getPacketData(uint16_t len) { - while (len-- && read_len < MTU) { - pbuf[read_len++] = LoRa->read(); - } -} - -void ISR_VECT receive_callback(int packet_size) { - if (!promisc) { - // The standard operating mode allows large - // packets with a payload up to 500 bytes, - // by combining two raw LoRa packets. - // We read the 1-byte header and extract - // packet sequence number and split flags - uint8_t header = LoRa->read(); packet_size--; - uint8_t sequence = packetSequence(header); - bool ready = false; - - if (isSplitPacket(header) && seq == SEQ_UNSET) { - // This is the first part of a split - // packet, so we set the seq variable - // and add the data to the buffer - read_len = 0; - seq = sequence; - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - last_rssi = LoRa->packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - #endif - - getPacketData(packet_size); - - } else if (isSplitPacket(header) && seq == sequence) { - // This is the second part of a split - // packet, so we add it to the buffer - // and set the ready flag. - - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - last_rssi = (last_rssi+LoRa->packetRssi())/2; - last_snr_raw = (last_snr_raw+LoRa->packetSnrRaw())/2; - #endif - - getPacketData(packet_size); - - seq = SEQ_UNSET; - ready = true; - - } else if (isSplitPacket(header) && seq != sequence) { - // This split packet does not carry the - // same sequence id, so we must assume - // that we are seeing the first part of - // a new split packet. - read_len = 0; - seq = sequence; - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - last_rssi = LoRa->packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - #endif - - getPacketData(packet_size); - - } else if (!isSplitPacket(header)) { - // This is not a split packet, so we - // just read it and set the ready - // flag to true. - - if (seq != SEQ_UNSET) { - // If we already had part of a split - // packet in the buffer, we clear it. - read_len = 0; - seq = SEQ_UNSET; - } - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - last_rssi = LoRa->packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - #endif - - getPacketData(packet_size); - ready = true; - } - - if (ready) { - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - // We first signal the RSSI of the - // recieved packet to the host. - kiss_indicate_stat_rssi(); - kiss_indicate_stat_snr(); - - // And then write the entire packet - kiss_write_packet(); - #else - packet_ready = true; - #endif - } - } else { - // In promiscuous mode, raw packets are - // output directly to the host - read_len = 0; - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - last_rssi = LoRa->packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - getPacketData(packet_size); - - // We first signal the RSSI of the - // recieved packet to the host. - kiss_indicate_stat_rssi(); - kiss_indicate_stat_snr(); - - // And then write the entire packet - kiss_write_packet(); - - #else - getPacketData(packet_size); - packet_ready = true; - #endif - } -} - -bool startRadio() { - update_radio_lock(); - if (!radio_online && !console_active) { - if (!radio_locked && hw_ready) { - if (!LoRa->begin(lora_freq)) { - // The radio could not be started. - // Indicate this failure over both the - // serial port and with the onboard LEDs - radio_error = true; - kiss_indicate_error(ERROR_INITRADIO); - led_indicate_error(0); - return false; - } else { - radio_online = true; - - init_channel_stats(); - - setTXPower(); - setBandwidth(); - setSpreadingFactor(); - setCodingRate(); - getFrequency(); - - LoRa->enableCrc(); - - LoRa->onReceive(receive_callback); - - lora_receive(); - - // Flash an info pattern to indicate - // that the radio is now on - kiss_indicate_radiostate(); - led_indicate_info(3); - return true; - } - - } else { - // Flash a warning pattern to indicate - // that the radio was locked, and thus - // not started - radio_online = false; - kiss_indicate_radiostate(); - led_indicate_warning(3); - return false; - } - } else { - // If radio is already on, we silently - // ignore the request. - kiss_indicate_radiostate(); - return true; - } -} - -void stopRadio() { - LoRa->end(); - radio_online = false; -} - -void update_radio_lock() { - if (lora_freq != 0 && lora_bw != 0 && lora_txp != 0xFF && lora_sf != 0) { - radio_locked = false; - } else { - radio_locked = true; - } -} - -bool queueFull() { - return (queue_height >= CONFIG_QUEUE_MAX_LENGTH || queued_bytes >= CONFIG_QUEUE_SIZE); -} - -volatile bool queue_flushing = false; -void flushQueue(void) { - if (!queue_flushing) { - queue_flushing = true; - - led_tx_on(); - uint16_t processed = 0; - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - while (!fifo16_isempty(&packet_starts)) { - #else - while (!fifo16_isempty_locked(&packet_starts)) { - #endif - - uint16_t start = fifo16_pop(&packet_starts); - uint16_t length = fifo16_pop(&packet_lengths); - - if (length >= MIN_L && length <= MTU) { - for (uint16_t i = 0; i < length; i++) { - uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; - tbuf[i] = packet_queue[pos]; - } - - transmit(length); - processed++; - } - } - - lora_receive(); - led_tx_off(); - post_tx_yield_timeout = millis()+(lora_post_tx_yield_slots*csma_slot_ms); - } - - queue_height = 0; - queued_bytes = 0; - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - update_airtime(); - #endif - queue_flushing = false; -} - -#define PHY_HEADER_LORA_SYMBOLS 8 -void add_airtime(uint16_t written) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - float packet_cost_ms = 0.0; - float payload_cost_ms = ((float)written * lora_us_per_byte)/1000.0; - packet_cost_ms += payload_cost_ms; - packet_cost_ms += (lora_preamble_symbols+4.25)*lora_symbol_time_ms; - packet_cost_ms += PHY_HEADER_LORA_SYMBOLS * lora_symbol_time_ms; - uint16_t cb = current_airtime_bin(); - uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } - airtime_bins[cb] += packet_cost_ms; - airtime_bins[nb] = 0; - #endif -} - -void update_airtime() { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - uint16_t cb = current_airtime_bin(); - uint16_t pb = cb-1; if (cb-1 < 0) { pb = AIRTIME_BINS-1; } - uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } - airtime_bins[nb] = 0; - airtime = (float)(airtime_bins[cb]+airtime_bins[pb])/(2.0*AIRTIME_BINLEN_MS); - - uint32_t longterm_airtime_sum = 0; - for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { - longterm_airtime_sum += airtime_bins[bin]; - } - longterm_airtime = (float)longterm_airtime_sum/(float)AIRTIME_LONGTERM_MS; - - float longterm_channel_util_sum = 0.0; - for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { - longterm_channel_util_sum += longterm_bins[bin]; - } - longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS; - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - update_csma_p(); - #endif - kiss_indicate_channel_stats(); - #endif -} - -void transmit(uint16_t size) { - if (radio_online) { - if (!promisc) { - uint16_t written = 0; - uint8_t header = random(256) & 0xF0; - - if (size > SINGLE_MTU - HEADER_L) { - header = header | FLAG_SPLIT; - } - - LoRa->beginPacket(); - LoRa->write(header); written++; - - for (uint16_t i=0; i < size; i++) { - LoRa->write(tbuf[i]); - - written++; - - if (written == 255) { - LoRa->endPacket(); add_airtime(written); - LoRa->beginPacket(); - LoRa->write(header); - written = 1; - } - } - - LoRa->endPacket(); add_airtime(written); - } else { - // In promiscuous mode, we only send out - // plain raw LoRa packets with a maximum - // payload of 255 bytes - led_tx_on(); - uint16_t written = 0; - - // Cap packets at 255 bytes - if (size > SINGLE_MTU) { - size = SINGLE_MTU; - } - - // If implicit header mode has been set, - // set packet length to payload data length - if (!implicit) { - LoRa->beginPacket(); - } else { - LoRa->beginPacket(size); - } - - for (uint16_t i=0; i < size; i++) { - LoRa->write(tbuf[i]); - - written++; - } - LoRa->endPacket(); add_airtime(written); - } - } else { - kiss_indicate_error(ERROR_TXFAILED); - led_indicate_error(5); - } -} - -void serialCallback(uint8_t sbyte) { - if (IN_FRAME && sbyte == FEND && command == CMD_DATA) { - IN_FRAME = false; - - if (!fifo16_isfull(&packet_starts) && queued_bytes < CONFIG_QUEUE_SIZE) { - uint16_t s = current_packet_start; - int16_t e = queue_cursor-1; if (e == -1) e = CONFIG_QUEUE_SIZE-1; - uint16_t l; - - if (s != e) { - l = (s < e) ? e - s + 1 : CONFIG_QUEUE_SIZE - s + e + 1; - } else { - l = 1; - } - - if (l >= MIN_L) { - queue_height++; - - fifo16_push(&packet_starts, s); - fifo16_push(&packet_lengths, l); - - current_packet_start = queue_cursor; - } - - } - - } else if (sbyte == FEND) { - IN_FRAME = true; - command = CMD_UNKNOWN; - frame_len = 0; - } else if (IN_FRAME && frame_len < MTU) { - // Have a look at the command byte first - if (frame_len == 0 && command == CMD_UNKNOWN) { - command = sbyte; - } else if (command == CMD_DATA) { - if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (queue_height < CONFIG_QUEUE_MAX_LENGTH && queued_bytes < CONFIG_QUEUE_SIZE) { - queued_bytes++; - packet_queue[queue_cursor++] = sbyte; - if (queue_cursor == CONFIG_QUEUE_SIZE) queue_cursor = 0; - } - } - } else if (command == CMD_FREQUENCY) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == 4) { - uint32_t freq = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; - - if (freq == 0) { - kiss_indicate_frequency(); - } else { - lora_freq = freq; - if (op_mode == MODE_HOST) setFrequency(); - kiss_indicate_frequency(); - } - } - } else if (command == CMD_BANDWIDTH) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == 4) { - uint32_t bw = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; - - if (bw == 0) { - kiss_indicate_bandwidth(); - } else { - lora_bw = bw; - if (op_mode == MODE_HOST) setBandwidth(); - kiss_indicate_bandwidth(); - } - } - } else if (command == CMD_TXPOWER) { - if (sbyte == 0xFF) { - kiss_indicate_txpower(); - } else { - int txp = sbyte; - #if MODEM == SX1262 - if (txp > 22) txp = 22; - #else - if (txp > 17) txp = 17; - #endif - - lora_txp = txp; - if (op_mode == MODE_HOST) setTXPower(); - kiss_indicate_txpower(); - } - } else if (command == CMD_SF) { - if (sbyte == 0xFF) { - kiss_indicate_spreadingfactor(); - } else { - int sf = sbyte; - if (sf < 5) sf = 5; - if (sf > 12) sf = 12; - - lora_sf = sf; - if (op_mode == MODE_HOST) setSpreadingFactor(); - kiss_indicate_spreadingfactor(); - } - } else if (command == CMD_CR) { - if (sbyte == 0xFF) { - kiss_indicate_codingrate(); - } else { - int cr = sbyte; - if (cr < 5) cr = 5; - if (cr > 8) cr = 8; - - lora_cr = cr; - if (op_mode == MODE_HOST) setCodingRate(); - kiss_indicate_codingrate(); - } - } else if (command == CMD_IMPLICIT) { - set_implicit_length(sbyte); - kiss_indicate_implicit_length(); - } else if (command == CMD_LEAVE) { - if (sbyte == 0xFF) { - cable_state = CABLE_STATE_DISCONNECTED; - current_rssi = -292; - last_rssi = -292; - last_rssi_raw = 0x00; - last_snr_raw = 0x80; - } - } else if (command == CMD_RADIO_STATE) { - if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; - if (sbyte == 0xFF) { - kiss_indicate_radiostate(); - } else if (sbyte == 0x00) { - stopRadio(); - kiss_indicate_radiostate(); - } else if (sbyte == 0x01) { - startRadio(); - kiss_indicate_radiostate(); - } - } else if (command == CMD_ST_ALOCK) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == 2) { - uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; - - if (at == 0) { - st_airtime_limit = 0.0; - } else { - st_airtime_limit = (float)at/(100.0*100.0); - if (st_airtime_limit >= 1.0) { st_airtime_limit = 0.0; } - } - kiss_indicate_st_alock(); - } - } else if (command == CMD_LT_ALOCK) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == 2) { - uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; - - if (at == 0) { - lt_airtime_limit = 0.0; - } else { - lt_airtime_limit = (float)at/(100.0*100.0); - if (lt_airtime_limit >= 1.0) { lt_airtime_limit = 0.0; } - } - kiss_indicate_lt_alock(); - } - } else if (command == CMD_STAT_RX) { - kiss_indicate_stat_rx(); - } else if (command == CMD_STAT_TX) { - kiss_indicate_stat_tx(); - } else if (command == CMD_STAT_RSSI) { - kiss_indicate_stat_rssi(); - } else if (command == CMD_RADIO_LOCK) { - update_radio_lock(); - kiss_indicate_radio_lock(); - } else if (command == CMD_BLINK) { - led_indicate_info(sbyte); - } else if (command == CMD_RANDOM) { - kiss_indicate_random(getRandom()); - } else if (command == CMD_DETECT) { - if (sbyte == DETECT_REQ) { - if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; - kiss_indicate_detect(); - } - } else if (command == CMD_PROMISC) { - if (sbyte == 0x01) { - promisc_enable(); - } else if (sbyte == 0x00) { - promisc_disable(); - } - kiss_indicate_promisc(); - } else if (command == CMD_READY) { - if (!queueFull()) { - kiss_indicate_ready(); - } else { - kiss_indicate_not_ready(); - } - } else if (command == CMD_UNLOCK_ROM) { - if (sbyte == ROM_UNLOCK_BYTE) { - unlock_rom(); - } - } else if (command == CMD_RESET) { - if (sbyte == CMD_RESET_BYTE) { - hard_reset(); - } - } else if (command == CMD_ROM_READ) { - kiss_dump_eeprom(); - } else if (command == CMD_ROM_WRITE) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == 2) { - eeprom_write(cmdbuf[0], cmdbuf[1]); - } - } else if (command == CMD_FW_VERSION) { - kiss_indicate_version(); - } else if (command == CMD_PLATFORM) { - kiss_indicate_platform(); - } else if (command == CMD_MCU) { - kiss_indicate_mcu(); - } else if (command == CMD_BOARD) { - kiss_indicate_board(); - } else if (command == CMD_CONF_SAVE) { - eeprom_conf_save(); - } else if (command == CMD_CONF_DELETE) { - eeprom_conf_delete(); - } else if (command == CMD_FB_EXT) { - #if HAS_DISPLAY == true - if (sbyte == 0xFF) { - kiss_indicate_fbstate(); - } else if (sbyte == 0x00) { - ext_fb_disable(); - kiss_indicate_fbstate(); - } else if (sbyte == 0x01) { - ext_fb_enable(); - kiss_indicate_fbstate(); - } - #endif - } else if (command == CMD_FB_WRITE) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - #if HAS_DISPLAY - if (frame_len == 9) { - uint8_t line = cmdbuf[0]; - if (line > 63) line = 63; - int fb_o = line*8; - memcpy(fb+fb_o, cmdbuf+1, 8); - } - #endif - } else if (command == CMD_FB_READ) { - if (sbyte != 0x00) { - kiss_indicate_fb(); - } - } else if (command == CMD_DEV_HASH) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - if (sbyte != 0x00) { - kiss_indicate_device_hash(); - } - #endif - } else if (command == CMD_DEV_SIG) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == DEV_SIG_LEN) { - memcpy(dev_sig, cmdbuf, DEV_SIG_LEN); - device_save_signature(); - } - #endif - } else if (command == CMD_FW_UPD) { - if (sbyte == 0x01) { - firmware_update_mode = true; - } else { - firmware_update_mode = false; - } - } else if (command == CMD_HASHES) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - if (sbyte == 0x01) { - kiss_indicate_target_fw_hash(); - } else if (sbyte == 0x02) { - kiss_indicate_fw_hash(); - } else if (sbyte == 0x03) { - kiss_indicate_bootloader_hash(); - } else if (sbyte == 0x04) { - kiss_indicate_partition_table_hash(); - } - #endif - } else if (command == CMD_FW_HASH) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; - } - - if (frame_len == DEV_HASH_LEN) { - memcpy(dev_firmware_hash_target, cmdbuf, DEV_HASH_LEN); - device_save_firmware_hash(); - } - #endif - } else if (command == CMD_BT_CTRL) { - #if HAS_BLUETOOTH || HAS_BLE - if (sbyte == 0x00) { - bt_stop(); - bt_conf_save(false); - } else if (sbyte == 0x01) { - bt_start(); - bt_conf_save(true); - } else if (sbyte == 0x02) { - bt_enable_pairing(); - } - #endif - } else if (command == CMD_DISP_INT) { - #if HAS_DISPLAY - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - display_intensity = sbyte; - di_conf_save(display_intensity); - } - - #endif - } else if (command == CMD_DISP_ADDR) { - #if HAS_DISPLAY - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } - display_addr = sbyte; - da_conf_save(display_addr); - } - - #endif - } - } -} - -#if MCU_VARIANT == MCU_ESP32 - portMUX_TYPE update_lock = portMUX_INITIALIZER_UNLOCKED; -#endif - -void updateModemStatus() { - #if MCU_VARIANT == MCU_ESP32 - portENTER_CRITICAL(&update_lock); - #elif MCU_VARIANT == MCU_NRF52 - portENTER_CRITICAL(); - #endif - - uint8_t status = LoRa->modemStatus(); - current_rssi = LoRa->currentRssi(); - last_status_update = millis(); - - #if MCU_VARIANT == MCU_ESP32 - portEXIT_CRITICAL(&update_lock); - #elif MCU_VARIANT == MCU_NRF52 - portEXIT_CRITICAL(); - #endif - - if ((status & SIG_DETECT) == SIG_DETECT) { stat_signal_detected = true; } else { stat_signal_detected = false; } - if ((status & SIG_SYNCED) == SIG_SYNCED) { stat_signal_synced = true; } else { stat_signal_synced = false; } - if ((status & RX_ONGOING) == RX_ONGOING) { stat_rx_ongoing = true; } else { stat_rx_ongoing = false; } - - // if (stat_signal_detected || stat_signal_synced || stat_rx_ongoing) { - if (stat_signal_detected || stat_signal_synced) { - if (stat_rx_ongoing) { - if (dcd_count < dcd_threshold) { - dcd_count++; - } else { - last_dcd = last_status_update; - dcd_led = true; - dcd = true; - } - } - } else { - #define DCD_LED_STEP_D 3 - if (dcd_count == 0) { - dcd_led = false; - } else if (dcd_count > DCD_LED_STEP_D) { - dcd_count -= DCD_LED_STEP_D; - } else { - dcd_count = 0; - } - - if (last_status_update > last_dcd+csma_slot_ms) { - dcd = false; - dcd_led = false; - dcd_count = 0; - } - } - - if (dcd_led) { - led_rx_on(); - } else { - if (airtime_lock) { - led_indicate_airtime_lock(); - } else { - led_rx_off(); - } - } -} - -void checkModemStatus() { - if (millis()-last_status_update >= status_interval_ms) { - updateModemStatus(); - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - util_samples[dcd_sample] = dcd; - dcd_sample = (dcd_sample+1)%DCD_SAMPLES; - if (dcd_sample % UTIL_UPDATE_INTERVAL == 0) { - int util_count = 0; - for (int ui = 0; ui < DCD_SAMPLES; ui++) { - if (util_samples[ui]) util_count++; - } - local_channel_util = (float)util_count / (float)DCD_SAMPLES; - total_channel_util = local_channel_util + airtime; - if (total_channel_util > 1.0) total_channel_util = 1.0; - - int16_t cb = current_airtime_bin(); - uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } - if (total_channel_util > longterm_bins[cb]) longterm_bins[cb] = total_channel_util; - longterm_bins[nb] = 0.0; - - update_airtime(); - } - #endif - } -} - -void validate_status() { - #if MCU_VARIANT == MCU_1284P - uint8_t boot_flags = OPTIBOOT_MCUSR; - uint8_t F_POR = PORF; - uint8_t F_BOR = BORF; - uint8_t F_WDR = WDRF; - #elif MCU_VARIANT == MCU_2560 - uint8_t boot_flags = OPTIBOOT_MCUSR; - if (boot_flags == 0x00) boot_flags = 0x03; - uint8_t F_POR = PORF; - uint8_t F_BOR = BORF; - uint8_t F_WDR = WDRF; - #elif MCU_VARIANT == MCU_ESP32 - // TODO: Get ESP32 boot flags - uint8_t boot_flags = 0x02; - uint8_t F_POR = 0x00; - uint8_t F_BOR = 0x00; - uint8_t F_WDR = 0x01; - #elif MCU_VARIANT == MCU_NRF52 - // TODO: Get NRF52 boot flags - uint8_t boot_flags = 0x02; - uint8_t F_POR = 0x00; - uint8_t F_BOR = 0x00; - uint8_t F_WDR = 0x01; - #endif - - if (hw_ready || device_init_done) { - hw_ready = false; - Serial.write("Error, invalid hardware check state\r\n"); - #if HAS_DISPLAY - if (disp_ready) { - device_init_done = true; - update_display(); - } - #endif - led_indicate_boot_error(); - } - - if (boot_flags & (1<packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - portEXIT_CRITICAL(&update_lock); - kiss_indicate_stat_rssi(); - kiss_indicate_stat_snr(); - kiss_write_packet(); - } - - airtime_lock = false; - if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; - if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; - - #elif MCU_VARIANT == MCU_NRF52 - if (packet_ready) { - portENTER_CRITICAL(); - last_rssi = LoRa->packetRssi(); - last_snr_raw = LoRa->packetSnrRaw(); - portEXIT_CRITICAL(); - kiss_indicate_stat_rssi(); - kiss_indicate_stat_snr(); - kiss_write_packet(); - } - - airtime_lock = false; - if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; - if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; - #endif - - checkModemStatus(); - if (!airtime_lock) { - if (queue_height > 0) { - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - long check_time = millis(); - if (check_time > post_tx_yield_timeout) { - if (dcd_waiting && (check_time >= dcd_wait_until)) { dcd_waiting = false; } - if (!dcd_waiting) { - for (uint8_t dcd_i = 0; dcd_i < dcd_threshold*2; dcd_i++) { - delay(STATUS_INTERVAL_MS); updateModemStatus(); - } - - if (!dcd) { - uint8_t csma_r = (uint8_t)random(256); - if (csma_p >= csma_r) { - flushQueue(); - } else { - dcd_waiting = true; - dcd_wait_until = millis()+csma_slot_ms; - } - } - } - } - - #else - if (!dcd_waiting) updateModemStatus(); - - if (!dcd && !dcd_led) { - if (dcd_waiting) delay(lora_rx_turnaround_ms); - - updateModemStatus(); - - if (!dcd) { - dcd_waiting = false; - flushQueue(); - } - - } else { - dcd_waiting = true; - } - #endif - } - } - - } else { - if (hw_ready) { - if (console_active) { - #if HAS_CONSOLE - console_loop(); - #endif - } else { - led_indicate_standby(); - } - } else { - - led_indicate_not_ready(); - stopRadio(); - } - } - - #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 - buffer_serial(); - if (!fifo_isempty(&serialFIFO)) serial_poll(); - #else - if (!fifo_isempty_locked(&serialFIFO)) serial_poll(); - #endif - - #if HAS_DISPLAY - if (disp_ready) update_display(); - #endif - - #if HAS_PMU - if (pmu_ready) update_pmu(); - #endif - - #if HAS_BLUETOOTH || HAS_BLE == true - if (!console_active && bt_ready) update_bt(); - #endif - - #if HAS_INPUT - input_read(); - #endif -} - -void sleep_now() { - #if HAS_SLEEP == true - #if BOARD_MODEL == BOARD_RNODE_NG_22 - display_intensity = 0; - update_display(true); - #endif - #if PIN_DISP_SLEEP >= 0 - pinMode(PIN_DISP_SLEEP, OUTPUT); - digitalWrite(PIN_DISP_SLEEP, DISP_SLEEP_LEVEL); - #endif - esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, WAKEUP_LEVEL); - esp_deep_sleep_start(); - #endif -} - -void button_event(uint8_t event, unsigned long duration) { - if (duration > 2000) { - sleep_now(); - } -} - -volatile bool serial_polling = false; -void serial_poll() { - serial_polling = true; - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - while (!fifo_isempty_locked(&serialFIFO)) { - #else - while (!fifo_isempty(&serialFIFO)) { - #endif - char sbyte = fifo_pop(&serialFIFO); - serialCallback(sbyte); - } - - serial_polling = false; -} - -#if MCU_VARIANT != MCU_ESP32 - #define MAX_CYCLES 20 -#else - #define MAX_CYCLES 10 -#endif -void buffer_serial() { - if (!serial_buffering) { - serial_buffering = true; - - uint8_t c = 0; - - #if HAS_BLUETOOTH || HAS_BLE == true - while ( - c < MAX_CYCLES && - ( (bt_state != BT_STATE_CONNECTED && Serial.available()) || (bt_state == BT_STATE_CONNECTED && SerialBT.available()) ) - ) - #else - while (c < MAX_CYCLES && Serial.available()) - #endif - { - c++; - - #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - if (!fifo_isfull_locked(&serialFIFO)) { - fifo_push_locked(&serialFIFO, Serial.read()); - } - #elif HAS_BLUETOOTH || HAS_BLE == true - if (bt_state == BT_STATE_CONNECTED) { - if (!fifo_isfull(&serialFIFO)) { - fifo_push(&serialFIFO, SerialBT.read()); - } - } else { - if (!fifo_isfull(&serialFIFO)) { - fifo_push(&serialFIFO, Serial.read()); - } - } - #else - if (!fifo_isfull(&serialFIFO)) { - fifo_push(&serialFIFO, Serial.read()); - } - #endif - } - - serial_buffering = false; - } -} - -void serial_interrupt_init() { - #if MCU_VARIANT == MCU_1284P - TCCR3A = 0; - TCCR3B = _BV(CS10) | - _BV(WGM33)| - _BV(WGM32); - - // Buffer incoming frames every 1ms - ICR3 = 16000; - - TIMSK3 = _BV(ICIE3); - - #elif MCU_VARIANT == MCU_2560 - // TODO: This should probably be updated for - // atmega2560 support. Might be source of - // reported issues from snh. - TCCR3A = 0; - TCCR3B = _BV(CS10) | - _BV(WGM33)| - _BV(WGM32); - - // Buffer incoming frames every 1ms - ICR3 = 16000; - - TIMSK3 = _BV(ICIE3); - - #elif MCU_VARIANT == MCU_ESP32 - // No interrupt-based polling on ESP32 - #endif - -} - -#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 - ISR(TIMER3_CAPT_vect) { - buffer_serial(); - } -#endif diff --git a/RNode_Firmware_CE.ino b/RNode_Firmware_CE.ino new file mode 100644 index 0000000..4410a91 --- /dev/null +++ b/RNode_Firmware_CE.ino @@ -0,0 +1,1433 @@ +// Copyright (C) 2023, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include "Utilities.h" + +FIFOBuffer serialFIFO; +uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1]; + +FIFOBuffer16 packet_starts; +uint16_t packet_starts_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +FIFOBuffer16 packet_lengths; +uint16_t packet_lengths_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +uint8_t packet_queue[CONFIG_QUEUE_SIZE]; + +volatile uint8_t queue_height = 0; +volatile uint16_t queued_bytes = 0; +volatile uint16_t queue_cursor = 0; +volatile uint16_t current_packet_start = 0; +volatile bool serial_buffering = false; +#if HAS_BLUETOOTH || HAS_BLE == true + bool bt_init_ran = false; +#endif + +#if HAS_CONSOLE + #include "Console.h" +#endif + +char sbuf[128]; + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + bool packet_ready = false; +#endif + +void setup() { + #if MCU_VARIANT == MCU_ESP32 + boot_seq(); + EEPROM.begin(EEPROM_SIZE); + Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE); + #endif + + #if MCU_VARIANT == MCU_NRF52 + if (!eeprom_begin()) { + Serial.write("EEPROM initialisation failed.\r\n"); + } + #endif + + // Seed the PRNG for CSMA R-value selection + # if MCU_VARIANT == MCU_ESP32 + // On ESP32, get the seed value from the + // hardware RNG + int seed_val = (int)esp_random(); + #else + // Otherwise, get a pseudo-random seed + // value from an unconnected analog pin + int seed_val = analogRead(0); + #endif + randomSeed(seed_val); + + // Initialise serial communication + memset(serialBuffer, 0, sizeof(serialBuffer)); + fifo_init(&serialFIFO, serialBuffer, CONFIG_UART_BUFFER_SIZE); + + Serial.begin(serial_baudrate); + + #if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_RNODE_NG_22 + // Some boards need to wait until the hardware UART is set up before booting + // the full firmware. In the case of the RAK4631, the line below will wait + // until a serial connection is actually established with a master. Thus, it + // is disabled on this platform. + while (!Serial); + #endif + + serial_interrupt_init(); + + // Configure input and output pins + #if HAS_INPUT + input_init(); + #endif + + #if HAS_NP == false + pinMode(pin_led_rx, OUTPUT); + pinMode(pin_led_tx, OUTPUT); + #endif + + #if HAS_TCXO == true + if (pin_tcxo_enable != -1) { + pinMode(pin_tcxo_enable, OUTPUT); + digitalWrite(pin_tcxo_enable, HIGH); + } + #endif + + // Initialise buffers + memset(pbuf, 0, sizeof(pbuf)); + memset(cmdbuf, 0, sizeof(cmdbuf)); + + memset(packet_queue, 0, sizeof(packet_queue)); + + memset(packet_starts_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_starts, packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH); + + memset(packet_lengths_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_lengths, packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH); + + // Set chip select, reset and interrupt + // pins for the LoRa module + #if MODEM == SX1276 || MODEM == SX1278 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy); + #elif MODEM == SX1262 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen); + #elif MODEM == SX1280 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + init_channel_stats(); + + // Check installed transceiver chip and + // probe boot parameters. + if (LoRa->preInit()) { + modem_installed = true; + uint32_t lfr = LoRa->getFrequency(); + if (lfr == 0) { + // Normal boot + } else if (lfr == M_FRQ_R) { + // Quick reboot + #if HAS_CONSOLE + if (rtc_get_reset_reason(0) == POWERON_RESET) { + console_active = true; + } + #endif + } else { + // Unknown boot + } + LoRa->setFrequency(M_FRQ_S); + } else { + modem_installed = false; + } + #else + // Older variants only came with SX1276/78 chips, + // so assume that to be the case for now. + modem_installed = true; + #endif + + #if HAS_DISPLAY + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #endif + eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF); + } + disp_ready = display_init(); + update_display(); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_PMU == true + pmu_ready = init_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + bt_init(); + bt_init_ran = true; + #endif + + if (console_active) { + #if HAS_CONSOLE + console_start(); + #else + kiss_indicate_reset(); + #endif + } else { + kiss_indicate_reset(); + } + #endif + + // Validate board health, EEPROM and config + validate_status(); + + if (op_mode != MODE_TNC) LoRa->setFrequency(0); +} + +void lora_receive() { + if (!implicit) { + LoRa->receive(); + } else { + LoRa->receive(implicit_l); + } +} + +inline void kiss_write_packet() { + serial_write(FEND); + serial_write(CMD_DATA); + for (uint16_t i = 0; i < read_len; i++) { + uint8_t byte = pbuf[i]; + if (byte == FEND) { serial_write(FESC); byte = TFEND; } + if (byte == FESC) { serial_write(FESC); byte = TFESC; } + serial_write(byte); + } + serial_write(FEND); + read_len = 0; + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + packet_ready = false; + #endif +} + +inline void getPacketData(uint16_t len) { + while (len-- && read_len < MTU) { + pbuf[read_len++] = LoRa->read(); + } +} + +void ISR_VECT receive_callback(int packet_size) { + if (!promisc) { + // The standard operating mode allows large + // packets with a payload up to 500 bytes, + // by combining two raw LoRa packets. + // We read the 1-byte header and extract + // packet sequence number and split flags + uint8_t header = LoRa->read(); packet_size--; + uint8_t sequence = packetSequence(header); + bool ready = false; + + if (isSplitPacket(header) && seq == SEQ_UNSET) { + // This is the first part of a split + // packet, so we set the seq variable + // and add the data to the buffer + read_len = 0; + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (isSplitPacket(header) && seq == sequence) { + // This is the second part of a split + // packet, so we add it to the buffer + // and set the ready flag. + + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = (last_rssi+LoRa->packetRssi())/2; + last_snr_raw = (last_snr_raw+LoRa->packetSnrRaw())/2; + #endif + + getPacketData(packet_size); + + seq = SEQ_UNSET; + ready = true; + + } else if (isSplitPacket(header) && seq != sequence) { + // This split packet does not carry the + // same sequence id, so we must assume + // that we are seeing the first part of + // a new split packet. + read_len = 0; + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (!isSplitPacket(header)) { + // This is not a split packet, so we + // just read it and set the ready + // flag to true. + + if (seq != SEQ_UNSET) { + // If we already had part of a split + // packet in the buffer, we clear it. + read_len = 0; + seq = SEQ_UNSET; + } + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + ready = true; + } + + if (ready) { + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + // We first signal the RSSI of the + // recieved packet to the host. + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + kiss_write_packet(); + #else + packet_ready = true; + #endif + } + } else { + // In promiscuous mode, raw packets are + // output directly to the host + read_len = 0; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + getPacketData(packet_size); + + // We first signal the RSSI of the + // recieved packet to the host. + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + kiss_write_packet(); + + #else + getPacketData(packet_size); + packet_ready = true; + #endif + } +} + +bool startRadio() { + update_radio_lock(); + if (!radio_online && !console_active) { + if (!radio_locked && hw_ready) { + if (!LoRa->begin(lora_freq)) { + // The radio could not be started. + // Indicate this failure over both the + // serial port and with the onboard LEDs + radio_error = true; + kiss_indicate_error(ERROR_INITRADIO); + led_indicate_error(0); + return false; + } else { + radio_online = true; + + init_channel_stats(); + + setTXPower(); + setBandwidth(); + setSpreadingFactor(); + setCodingRate(); + getFrequency(); + + LoRa->enableCrc(); + + LoRa->onReceive(receive_callback); + + lora_receive(); + + // Flash an info pattern to indicate + // that the radio is now on + kiss_indicate_radiostate(); + led_indicate_info(3); + return true; + } + + } else { + // Flash a warning pattern to indicate + // that the radio was locked, and thus + // not started + radio_online = false; + kiss_indicate_radiostate(); + led_indicate_warning(3); + return false; + } + } else { + // If radio is already on, we silently + // ignore the request. + kiss_indicate_radiostate(); + return true; + } +} + +void stopRadio() { + LoRa->end(); + radio_online = false; +} + +void update_radio_lock() { + if (lora_freq != 0 && lora_bw != 0 && lora_txp != 0xFF && lora_sf != 0) { + radio_locked = false; + } else { + radio_locked = true; + } +} + +bool queueFull() { + return (queue_height >= CONFIG_QUEUE_MAX_LENGTH || queued_bytes >= CONFIG_QUEUE_SIZE); +} + +volatile bool queue_flushing = false; +void flushQueue(void) { + if (!queue_flushing) { + queue_flushing = true; + + led_tx_on(); + uint16_t processed = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + while (!fifo16_isempty(&packet_starts)) { + #else + while (!fifo16_isempty_locked(&packet_starts)) { + #endif + + uint16_t start = fifo16_pop(&packet_starts); + uint16_t length = fifo16_pop(&packet_lengths); + + if (length >= MIN_L && length <= MTU) { + for (uint16_t i = 0; i < length; i++) { + uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; + tbuf[i] = packet_queue[pos]; + } + + transmit(length); + processed++; + } + } + + lora_receive(); + led_tx_off(); + post_tx_yield_timeout = millis()+(lora_post_tx_yield_slots*csma_slot_ms); + } + + queue_height = 0; + queued_bytes = 0; + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_airtime(); + #endif + queue_flushing = false; +} + +#define PHY_HEADER_LORA_SYMBOLS 8 +void add_airtime(uint16_t written) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + float packet_cost_ms = 0.0; + float payload_cost_ms = ((float)written * lora_us_per_byte)/1000.0; + packet_cost_ms += payload_cost_ms; + packet_cost_ms += (lora_preamble_symbols+4.25)*lora_symbol_time_ms; + packet_cost_ms += PHY_HEADER_LORA_SYMBOLS * lora_symbol_time_ms; + uint16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + airtime_bins[cb] += packet_cost_ms; + airtime_bins[nb] = 0; + #endif +} + +void update_airtime() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t cb = current_airtime_bin(); + uint16_t pb = cb-1; if (cb-1 < 0) { pb = AIRTIME_BINS-1; } + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + airtime_bins[nb] = 0; + airtime = (float)(airtime_bins[cb]+airtime_bins[pb])/(2.0*AIRTIME_BINLEN_MS); + + uint32_t longterm_airtime_sum = 0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { + longterm_airtime_sum += airtime_bins[bin]; + } + longterm_airtime = (float)longterm_airtime_sum/(float)AIRTIME_LONGTERM_MS; + + float longterm_channel_util_sum = 0.0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { + longterm_channel_util_sum += longterm_bins[bin]; + } + longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_csma_p(); + #endif + kiss_indicate_channel_stats(); + #endif +} + +void transmit(uint16_t size) { + if (radio_online) { + if (!promisc) { + uint16_t written = 0; + uint8_t header = random(256) & 0xF0; + + if (size > SINGLE_MTU - HEADER_L) { + header = header | FLAG_SPLIT; + } + + LoRa->beginPacket(); + LoRa->write(header); written++; + + for (uint16_t i=0; i < size; i++) { + LoRa->write(tbuf[i]); + + written++; + + if (written == 255) { + LoRa->endPacket(); add_airtime(written); + LoRa->beginPacket(); + LoRa->write(header); + written = 1; + } + } + + LoRa->endPacket(); add_airtime(written); + } else { + // In promiscuous mode, we only send out + // plain raw LoRa packets with a maximum + // payload of 255 bytes + led_tx_on(); + uint16_t written = 0; + + // Cap packets at 255 bytes + if (size > SINGLE_MTU) { + size = SINGLE_MTU; + } + + // If implicit header mode has been set, + // set packet length to payload data length + if (!implicit) { + LoRa->beginPacket(); + } else { + LoRa->beginPacket(size); + } + + for (uint16_t i=0; i < size; i++) { + LoRa->write(tbuf[i]); + + written++; + } + LoRa->endPacket(); add_airtime(written); + } + } else { + kiss_indicate_error(ERROR_TXFAILED); + led_indicate_error(5); + } +} + +void serialCallback(uint8_t sbyte) { + if (IN_FRAME && sbyte == FEND && command == CMD_DATA) { + IN_FRAME = false; + + if (!fifo16_isfull(&packet_starts) && queued_bytes < CONFIG_QUEUE_SIZE) { + uint16_t s = current_packet_start; + int16_t e = queue_cursor-1; if (e == -1) e = CONFIG_QUEUE_SIZE-1; + uint16_t l; + + if (s != e) { + l = (s < e) ? e - s + 1 : CONFIG_QUEUE_SIZE - s + e + 1; + } else { + l = 1; + } + + if (l >= MIN_L) { + queue_height++; + + fifo16_push(&packet_starts, s); + fifo16_push(&packet_lengths, l); + + current_packet_start = queue_cursor; + } + + } + + } else if (sbyte == FEND) { + IN_FRAME = true; + command = CMD_UNKNOWN; + frame_len = 0; + } else if (IN_FRAME && frame_len < MTU) { + // Have a look at the command byte first + if (frame_len == 0 && command == CMD_UNKNOWN) { + command = sbyte; + } else if (command == CMD_DATA) { + if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (queue_height < CONFIG_QUEUE_MAX_LENGTH && queued_bytes < CONFIG_QUEUE_SIZE) { + queued_bytes++; + packet_queue[queue_cursor++] = sbyte; + if (queue_cursor == CONFIG_QUEUE_SIZE) queue_cursor = 0; + } + } + } else if (command == CMD_FREQUENCY) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t freq = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (freq == 0) { + kiss_indicate_frequency(); + } else { + lora_freq = freq; + if (op_mode == MODE_HOST) setFrequency(); + kiss_indicate_frequency(); + } + } + } else if (command == CMD_BANDWIDTH) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t bw = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (bw == 0) { + kiss_indicate_bandwidth(); + } else { + lora_bw = bw; + if (op_mode == MODE_HOST) setBandwidth(); + kiss_indicate_bandwidth(); + } + } + } else if (command == CMD_TXPOWER) { + if (sbyte == 0xFF) { + kiss_indicate_txpower(); + } else { + int txp = sbyte; + #if MODEM == SX1262 + if (txp > 22) txp = 22; + #else + if (txp > 17) txp = 17; + #endif + + lora_txp = txp; + if (op_mode == MODE_HOST) setTXPower(); + kiss_indicate_txpower(); + } + } else if (command == CMD_SF) { + if (sbyte == 0xFF) { + kiss_indicate_spreadingfactor(); + } else { + int sf = sbyte; + if (sf < 5) sf = 5; + if (sf > 12) sf = 12; + + lora_sf = sf; + if (op_mode == MODE_HOST) setSpreadingFactor(); + kiss_indicate_spreadingfactor(); + } + } else if (command == CMD_CR) { + if (sbyte == 0xFF) { + kiss_indicate_codingrate(); + } else { + int cr = sbyte; + if (cr < 5) cr = 5; + if (cr > 8) cr = 8; + + lora_cr = cr; + if (op_mode == MODE_HOST) setCodingRate(); + kiss_indicate_codingrate(); + } + } else if (command == CMD_IMPLICIT) { + set_implicit_length(sbyte); + kiss_indicate_implicit_length(); + } else if (command == CMD_LEAVE) { + if (sbyte == 0xFF) { + cable_state = CABLE_STATE_DISCONNECTED; + current_rssi = -292; + last_rssi = -292; + last_rssi_raw = 0x00; + last_snr_raw = 0x80; + } + } else if (command == CMD_RADIO_STATE) { + if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; + if (sbyte == 0xFF) { + kiss_indicate_radiostate(); + } else if (sbyte == 0x00) { + stopRadio(); + kiss_indicate_radiostate(); + } else if (sbyte == 0x01) { + startRadio(); + kiss_indicate_radiostate(); + } + } else if (command == CMD_ST_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + st_airtime_limit = 0.0; + } else { + st_airtime_limit = (float)at/(100.0*100.0); + if (st_airtime_limit >= 1.0) { st_airtime_limit = 0.0; } + } + kiss_indicate_st_alock(); + } + } else if (command == CMD_LT_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + lt_airtime_limit = 0.0; + } else { + lt_airtime_limit = (float)at/(100.0*100.0); + if (lt_airtime_limit >= 1.0) { lt_airtime_limit = 0.0; } + } + kiss_indicate_lt_alock(); + } + } else if (command == CMD_STAT_RX) { + kiss_indicate_stat_rx(); + } else if (command == CMD_STAT_TX) { + kiss_indicate_stat_tx(); + } else if (command == CMD_STAT_RSSI) { + kiss_indicate_stat_rssi(); + } else if (command == CMD_RADIO_LOCK) { + update_radio_lock(); + kiss_indicate_radio_lock(); + } else if (command == CMD_BLINK) { + led_indicate_info(sbyte); + } else if (command == CMD_RANDOM) { + kiss_indicate_random(getRandom()); + } else if (command == CMD_DETECT) { + if (sbyte == DETECT_REQ) { + if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; + kiss_indicate_detect(); + } + } else if (command == CMD_PROMISC) { + if (sbyte == 0x01) { + promisc_enable(); + } else if (sbyte == 0x00) { + promisc_disable(); + } + kiss_indicate_promisc(); + } else if (command == CMD_READY) { + if (!queueFull()) { + kiss_indicate_ready(); + } else { + kiss_indicate_not_ready(); + } + } else if (command == CMD_UNLOCK_ROM) { + if (sbyte == ROM_UNLOCK_BYTE) { + unlock_rom(); + } + } else if (command == CMD_RESET) { + if (sbyte == CMD_RESET_BYTE) { + hard_reset(); + } + } else if (command == CMD_ROM_READ) { + kiss_dump_eeprom(); + } else if (command == CMD_ROM_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + eeprom_write(cmdbuf[0], cmdbuf[1]); + } + } else if (command == CMD_FW_VERSION) { + kiss_indicate_version(); + } else if (command == CMD_PLATFORM) { + kiss_indicate_platform(); + } else if (command == CMD_MCU) { + kiss_indicate_mcu(); + } else if (command == CMD_BOARD) { + kiss_indicate_board(); + } else if (command == CMD_CONF_SAVE) { + eeprom_conf_save(); + } else if (command == CMD_CONF_DELETE) { + eeprom_conf_delete(); + } else if (command == CMD_FB_EXT) { + #if HAS_DISPLAY == true + if (sbyte == 0xFF) { + kiss_indicate_fbstate(); + } else if (sbyte == 0x00) { + ext_fb_disable(); + kiss_indicate_fbstate(); + } else if (sbyte == 0x01) { + ext_fb_enable(); + kiss_indicate_fbstate(); + } + #endif + } else if (command == CMD_FB_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + #if HAS_DISPLAY + if (frame_len == 9) { + uint8_t line = cmdbuf[0]; + if (line > 63) line = 63; + int fb_o = line*8; + memcpy(fb+fb_o, cmdbuf+1, 8); + } + #endif + } else if (command == CMD_FB_READ) { + if (sbyte != 0x00) { + kiss_indicate_fb(); + } + } else if (command == CMD_DEV_HASH) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte != 0x00) { + kiss_indicate_device_hash(); + } + #endif + } else if (command == CMD_DEV_SIG) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_SIG_LEN) { + memcpy(dev_sig, cmdbuf, DEV_SIG_LEN); + device_save_signature(); + } + #endif + } else if (command == CMD_FW_UPD) { + if (sbyte == 0x01) { + firmware_update_mode = true; + } else { + firmware_update_mode = false; + } + } else if (command == CMD_HASHES) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == 0x01) { + kiss_indicate_target_fw_hash(); + } else if (sbyte == 0x02) { + kiss_indicate_fw_hash(); + } else if (sbyte == 0x03) { + kiss_indicate_bootloader_hash(); + } else if (sbyte == 0x04) { + kiss_indicate_partition_table_hash(); + } + #endif + } else if (command == CMD_FW_HASH) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_HASH_LEN) { + memcpy(dev_firmware_hash_target, cmdbuf, DEV_HASH_LEN); + device_save_firmware_hash(); + } + #endif + } else if (command == CMD_BT_CTRL) { + #if HAS_BLUETOOTH || HAS_BLE + if (sbyte == 0x00) { + bt_stop(); + bt_conf_save(false); + } else if (sbyte == 0x01) { + bt_start(); + bt_conf_save(true); + } else if (sbyte == 0x02) { + bt_enable_pairing(); + } + #endif + } else if (command == CMD_DISP_INT) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + display_intensity = sbyte; + di_conf_save(display_intensity); + } + + #endif + } else if (command == CMD_DISP_ADDR) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + display_addr = sbyte; + da_conf_save(display_addr); + } + + #endif + } + } +} + +#if MCU_VARIANT == MCU_ESP32 + portMUX_TYPE update_lock = portMUX_INITIALIZER_UNLOCKED; +#endif + +void updateModemStatus() { + #if MCU_VARIANT == MCU_ESP32 + portENTER_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); + #endif + + uint8_t status = LoRa->modemStatus(); + current_rssi = LoRa->currentRssi(); + last_status_update = millis(); + + #if MCU_VARIANT == MCU_ESP32 + portEXIT_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portEXIT_CRITICAL(); + #endif + + if ((status & SIG_DETECT) == SIG_DETECT) { stat_signal_detected = true; } else { stat_signal_detected = false; } + if ((status & SIG_SYNCED) == SIG_SYNCED) { stat_signal_synced = true; } else { stat_signal_synced = false; } + if ((status & RX_ONGOING) == RX_ONGOING) { stat_rx_ongoing = true; } else { stat_rx_ongoing = false; } + + // if (stat_signal_detected || stat_signal_synced || stat_rx_ongoing) { + if (stat_signal_detected || stat_signal_synced) { + if (stat_rx_ongoing) { + if (dcd_count < dcd_threshold) { + dcd_count++; + } else { + last_dcd = last_status_update; + dcd_led = true; + dcd = true; + } + } + } else { + #define DCD_LED_STEP_D 3 + if (dcd_count == 0) { + dcd_led = false; + } else if (dcd_count > DCD_LED_STEP_D) { + dcd_count -= DCD_LED_STEP_D; + } else { + dcd_count = 0; + } + + if (last_status_update > last_dcd+csma_slot_ms) { + dcd = false; + dcd_led = false; + dcd_count = 0; + } + } + + if (dcd_led) { + led_rx_on(); + } else { + if (airtime_lock) { + led_indicate_airtime_lock(); + } else { + led_rx_off(); + } + } +} + +void checkModemStatus() { + if (millis()-last_status_update >= status_interval_ms) { + updateModemStatus(); + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + util_samples[dcd_sample] = dcd; + dcd_sample = (dcd_sample+1)%DCD_SAMPLES; + if (dcd_sample % UTIL_UPDATE_INTERVAL == 0) { + int util_count = 0; + for (int ui = 0; ui < DCD_SAMPLES; ui++) { + if (util_samples[ui]) util_count++; + } + local_channel_util = (float)util_count / (float)DCD_SAMPLES; + total_channel_util = local_channel_util + airtime; + if (total_channel_util > 1.0) total_channel_util = 1.0; + + int16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + if (total_channel_util > longterm_bins[cb]) longterm_bins[cb] = total_channel_util; + longterm_bins[nb] = 0.0; + + update_airtime(); + } + #endif + } +} + +void validate_status() { + #if MCU_VARIANT == MCU_1284P + uint8_t boot_flags = OPTIBOOT_MCUSR; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_2560 + uint8_t boot_flags = OPTIBOOT_MCUSR; + if (boot_flags == 0x00) boot_flags = 0x03; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_ESP32 + // TODO: Get ESP32 boot flags + uint8_t boot_flags = 0x02; + uint8_t F_POR = 0x00; + uint8_t F_BOR = 0x00; + uint8_t F_WDR = 0x01; + #elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags + uint8_t boot_flags = 0x02; + uint8_t F_POR = 0x00; + uint8_t F_BOR = 0x00; + uint8_t F_WDR = 0x01; + #endif + + if (hw_ready || device_init_done) { + hw_ready = false; + Serial.write("Error, invalid hardware check state\r\n"); + #if HAS_DISPLAY + if (disp_ready) { + device_init_done = true; + update_display(); + } + #endif + led_indicate_boot_error(); + } + + if (boot_flags & (1<packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + portEXIT_CRITICAL(&update_lock); + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + kiss_write_packet(); + } + + airtime_lock = false; + if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; + if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; + + #elif MCU_VARIANT == MCU_NRF52 + if (packet_ready) { + portENTER_CRITICAL(); + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + portEXIT_CRITICAL(); + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + kiss_write_packet(); + } + + airtime_lock = false; + if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; + if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; + #endif + + checkModemStatus(); + if (!airtime_lock) { + if (queue_height > 0) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + long check_time = millis(); + if (check_time > post_tx_yield_timeout) { + if (dcd_waiting && (check_time >= dcd_wait_until)) { dcd_waiting = false; } + if (!dcd_waiting) { + for (uint8_t dcd_i = 0; dcd_i < dcd_threshold*2; dcd_i++) { + delay(STATUS_INTERVAL_MS); updateModemStatus(); + } + + if (!dcd) { + uint8_t csma_r = (uint8_t)random(256); + if (csma_p >= csma_r) { + flushQueue(); + } else { + dcd_waiting = true; + dcd_wait_until = millis()+csma_slot_ms; + } + } + } + } + + #else + if (!dcd_waiting) updateModemStatus(); + + if (!dcd && !dcd_led) { + if (dcd_waiting) delay(lora_rx_turnaround_ms); + + updateModemStatus(); + + if (!dcd) { + dcd_waiting = false; + flushQueue(); + } + + } else { + dcd_waiting = true; + } + #endif + } + } + + } else { + if (hw_ready) { + if (console_active) { + #if HAS_CONSOLE + console_loop(); + #endif + } else { + led_indicate_standby(); + } + } else { + + led_indicate_not_ready(); + stopRadio(); + } + } + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + buffer_serial(); + if (!fifo_isempty(&serialFIFO)) serial_poll(); + #else + if (!fifo_isempty_locked(&serialFIFO)) serial_poll(); + #endif + + #if HAS_DISPLAY + if (disp_ready) update_display(); + #endif + + #if HAS_PMU + if (pmu_ready) update_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + if (!console_active && bt_ready) update_bt(); + #endif + + #if HAS_INPUT + input_read(); + #endif +} + +void sleep_now() { + #if HAS_SLEEP == true + #if BOARD_MODEL == BOARD_RNODE_NG_22 + display_intensity = 0; + update_display(true); + #endif + #if PIN_DISP_SLEEP >= 0 + pinMode(PIN_DISP_SLEEP, OUTPUT); + digitalWrite(PIN_DISP_SLEEP, DISP_SLEEP_LEVEL); + #endif + esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, WAKEUP_LEVEL); + esp_deep_sleep_start(); + #endif +} + +void button_event(uint8_t event, unsigned long duration) { + if (duration > 2000) { + sleep_now(); + } +} + +volatile bool serial_polling = false; +void serial_poll() { + serial_polling = true; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + while (!fifo_isempty_locked(&serialFIFO)) { + #else + while (!fifo_isempty(&serialFIFO)) { + #endif + char sbyte = fifo_pop(&serialFIFO); + serialCallback(sbyte); + } + + serial_polling = false; +} + +#if MCU_VARIANT != MCU_ESP32 + #define MAX_CYCLES 20 +#else + #define MAX_CYCLES 10 +#endif +void buffer_serial() { + if (!serial_buffering) { + serial_buffering = true; + + uint8_t c = 0; + + #if HAS_BLUETOOTH || HAS_BLE == true + while ( + c < MAX_CYCLES && + ( (bt_state != BT_STATE_CONNECTED && Serial.available()) || (bt_state == BT_STATE_CONNECTED && SerialBT.available()) ) + ) + #else + while (c < MAX_CYCLES && Serial.available()) + #endif + { + c++; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + if (!fifo_isfull_locked(&serialFIFO)) { + fifo_push_locked(&serialFIFO, Serial.read()); + } + #elif HAS_BLUETOOTH || HAS_BLE == true + if (bt_state == BT_STATE_CONNECTED) { + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, SerialBT.read()); + } + } else { + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, Serial.read()); + } + } + #else + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, Serial.read()); + } + #endif + } + + serial_buffering = false; + } +} + +void serial_interrupt_init() { + #if MCU_VARIANT == MCU_1284P + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_2560 + // TODO: This should probably be updated for + // atmega2560 support. Might be source of + // reported issues from snh. + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_ESP32 + // No interrupt-based polling on ESP32 + #endif + +} + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + ISR(TIMER3_CAPT_vect) { + buffer_serial(); + } +#endif