// Copyright (C) 2024, Mark Qvist // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include "Utilities.h" #if MCU_VARIANT == MCU_NRF52 #define INTERFACE_SPI #if BOARD_MODEL == BOARD_OPENCOM_XL // Required because on RAK4631, non-default SPI pins must be initialised when class is declared. SPIClass interface_spi[1] = { // SX1262 SPIClass( NRF_SPIM2, interface_pins[0][3], interface_pins[0][1], interface_pins[0][2] ) }; #elif BOARD_MODEL == BOARD_TECHO SPIClass interface_spi[1] = { // SX1262 SPIClass( NRF_SPIM3, interface_pins[0][3], interface_pins[0][1], interface_pins[0][2] ) }; #endif #endif #ifndef INTERFACE_SPI // INTERFACE_SPI is only required on NRF52 platforms, as the SPI pins are set in the class constructor and not by a setter method. // Even if custom SPI interfaces are not needed, the array must exist to prevent compilation errors. #define INTERFACE_SPI SPIClass interface_spi[1]; #endif FIFOBuffer serialFIFO; uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1]; uint16_t packet_starts_buf[(CONFIG_QUEUE_MAX_LENGTH)+1]; uint16_t packet_lengths_buf[(CONFIG_QUEUE_MAX_LENGTH)+1]; FIFOBuffer16 packet_starts[INTERFACE_COUNT]; FIFOBuffer16 packet_lengths[INTERFACE_COUNT]; volatile uint8_t queue_height[INTERFACE_COUNT] = {0}; volatile uint16_t queued_bytes[INTERFACE_COUNT] = {0}; volatile uint16_t queue_cursor[INTERFACE_COUNT] = {0}; volatile uint16_t current_packet_start[INTERFACE_COUNT] = {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 #define MODEM_QUEUE_SIZE 4*INTERFACE_COUNT typedef struct { size_t len; int rssi; int snr_raw; uint8_t interface; uint8_t data[]; } modem_packet_t; static xQueueHandle modem_packet_queue = NULL; char sbuf[128]; uint8_t *packet_queue[INTERFACE_COUNT]; void setup() { #if MCU_VARIANT == MCU_ESP32 boot_seq(); EEPROM.begin(EEPROM_SIZE); Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE); #if BOARD_MODEL == BOARD_TDECK pinMode(pin_poweron, OUTPUT); digitalWrite(pin_poweron, HIGH); pinMode(SD_CS, OUTPUT); pinMode(DISPLAY_CS, OUTPUT); digitalWrite(SD_CS, HIGH); digitalWrite(DISPLAY_CS, HIGH); pinMode(DISPLAY_BL_PIN, OUTPUT); #endif #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 HAS_NP led_init(); #endif #if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_RNODE_NG_22 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_OPENCOM_XL // Some boards need to wait until the hardware UART is set up before booting // the full firmware. In the case of the RAK4631/TECHO, 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 // Configure input and output pins #if HAS_INPUT input_init(); #endif #if HAS_BUZZER pinMode(PIN_BUZZER, OUTPUT); #endif #if HAS_NP == false pinMode(pin_led_rx, OUTPUT); pinMode(pin_led_tx, OUTPUT); #endif for (int i = 0; i < INTERFACE_COUNT; i++) { if (interface_pins[i][9] != -1) { pinMode(interface_pins[i][9], OUTPUT); digitalWrite(interface_pins[i][9], HIGH); } } // Initialise buffers memset(pbuf, 0, sizeof(pbuf)); memset(cmdbuf, 0, sizeof(cmdbuf)); memset(packet_starts_buf, 0, sizeof(packet_starts_buf)); memset(packet_lengths_buf, 0, sizeof(packet_starts_buf)); memset(seq, 0xFF, sizeof(seq)); modem_packet_queue = xQueueCreate(MODEM_QUEUE_SIZE, sizeof(modem_packet_t*)); for (int i = 0; i < INTERFACE_COUNT; i++) { fifo16_init(&packet_starts[i], packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH+1); fifo16_init(&packet_lengths[i], packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH+1); packet_queue[i] = (uint8_t*)malloc(getQueueSize(i)+1); } memset(packet_rdy_interfaces_buf, 0, sizeof(packet_rdy_interfaces_buf)); fifo_init(&packet_rdy_interfaces, packet_rdy_interfaces_buf, MAX_INTERFACES); // add call to init_channel_stats here? \todo // Create and configure interface objects for (uint8_t i = 0; i < INTERFACE_COUNT; i++) { switch (interfaces[i]) { case SX126X: case SX1262: { sx126x* obj; // if default spi enabled if (interface_cfg[i][0]) { obj = new sx126x(i, &SPI, interface_cfg[i][1], interface_cfg[i][2], interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4], interface_pins[i][8]); } else { obj = new sx126x(i, &interface_spi[i], interface_cfg[i][1], interface_cfg[i][2], interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4], interface_pins[i][8]); } interface_obj[i] = obj; interface_obj_sorted[i] = obj; break; } case SX127X: case SX1276: case SX1278: { sx127x* obj; // if default spi enabled if (interface_cfg[i][0]) { obj = new sx127x(i, &SPI, interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4]); } else { obj = new sx127x(i, &interface_spi[i], interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4]); } interface_obj[i] = obj; interface_obj_sorted[i] = obj; break; } case SX128X: case SX1280: { sx128x* obj; // if default spi enabled if (interface_cfg[i][0]) { obj = new sx128x(i, &SPI, interface_cfg[i][1], interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4], interface_pins[i][8], interface_pins[i][7]); } else { obj = new sx128x(i, &interface_spi[i], interface_cfg[i][1], interface_pins[i][0], interface_pins[i][1], interface_pins[i][2], interface_pins[i][3], interface_pins[i][6], interface_pins[i][5], interface_pins[i][4], interface_pins[i][8], interface_pins[i][7]); } interface_obj[i] = obj; interface_obj_sorted[i] = obj; break; } default: break; } } // Check installed transceiver chip(s) and probe boot parameters. If any of // the configured modems cannot be initialised, do not boot for (int i = 0; i < INTERFACE_COUNT; i++) { switch (interfaces[i]) { case SX126X: case SX1262: case SX127X: case SX1276: case SX1278: case SX128X: case SX1280: selected_radio = interface_obj[i]; break; default: modems_installed = false; break; } if (selected_radio->preInit()) { modems_installed = true; uint32_t lfr = selected_radio->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 } selected_radio->setFrequency(M_FRQ_S); } else { modems_installed = false; } if (!modems_installed) { break; } } #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); } #if DISPLAY == EINK_BW || DISPLAY == EINK_3C // Poll and process incoming serial commands whilst e-ink display is // refreshing to make device still seem responsive display_add_callback(process_serial); #endif disp_ready = display_init(); update_display(); #endif #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(); } // Validate board health, EEPROM and config validate_status(); } void lora_receive(RadioInterface* radio) { if (!implicit) { radio->receive(); } else { radio->receive(implicit_l); } } inline void kiss_write_packet(int index) { // We need to convert the interface index to the command byte representation uint8_t cmd_byte = getInterfaceCommandByte(index); serial_write(FEND); // Add index of interface the packet came from serial_write(cmd_byte); for (uint16_t i = 0; i < read_len; i++) { #if MCU_VARIANT == MCU_NRF52 portENTER_CRITICAL(); uint8_t byte = pbuf[i]; portEXIT_CRITICAL(); #else uint8_t byte = pbuf[i]; #endif 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 && HAS_BLE bt_flush(); #endif } inline void getPacketData(RadioInterface* radio, uint16_t len) { #if MCU_VARIANT != MCU_NRF52 while (len-- && read_len < MTU) { pbuf[read_len++] = radio->read(); } #else BaseType_t int_mask = taskENTER_CRITICAL_FROM_ISR(); while (len-- && read_len < MTU) { pbuf[read_len++] = radio->read(); } taskEXIT_CRITICAL_FROM_ISR(int_mask); #endif } inline bool queue_packet(RadioInterface* radio, uint8_t index) { // Allocate packet struct, but abort if there // is not enough memory available. modem_packet_t *modem_packet = (modem_packet_t*)malloc(sizeof(modem_packet_t) + read_len); if(!modem_packet) { memory_low = true; return false; } // Get packet RSSI and SNR modem_packet->snr_raw = radio->packetSnrRaw(); // Pass raw SNR to get RSSI as SX127X driver requires it for calculations modem_packet->rssi = radio->packetRssi(modem_packet->snr_raw); modem_packet->interface = index; // Send packet to event queue, but free the // allocated memory again if the queue is // unable to receive the packet. modem_packet->len = read_len; memcpy(modem_packet->data, pbuf, read_len); if (!modem_packet_queue || xQueueSendFromISR(modem_packet_queue, &modem_packet, NULL) != pdPASS) { free(modem_packet); return false; } return true; } void ISR_VECT receive_callback(uint8_t index, int packet_size) { selected_radio = interface_obj[index]; bool ready = false; BaseType_t int_mask; 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 = selected_radio->read(); packet_size--; uint8_t sequence = packetSequence(header); if (isSplitPacket(header) && seq[index] == SEQ_UNSET) { // This is the first part of a split // packet, so we set the seq variable // and add the data to the buffer #if MCU_VARIANT == MCU_NRF52 int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); #else read_len = 0; #endif seq[index] = sequence; getPacketData(selected_radio, packet_size); } else if (isSplitPacket(header) && seq[index] == sequence) { // This is the second part of a split // packet, so we add it to the buffer // and set the ready flag. getPacketData(selected_radio, packet_size); seq[index] = SEQ_UNSET; ready = true; } else if (isSplitPacket(header) && seq[index] != 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. #if MCU_VARIANT == MCU_NRF52 int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); #else read_len = 0; #endif seq[index] = sequence; getPacketData(selected_radio, 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[index] != SEQ_UNSET) { // If we already had part of a split // packet in the buffer, we clear it. #if MCU_VARIANT == MCU_NRF52 int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); #else read_len = 0; #endif seq[index] = SEQ_UNSET; } getPacketData(selected_radio, packet_size); ready = true; } } else { // In promiscuous mode, raw packets are // output directly to the host read_len = 0; getPacketData(selected_radio, packet_size); ready = true; } if (ready) { queue_packet(selected_radio, index); } last_rx = millis(); } bool startRadio(RadioInterface* radio) { update_radio_lock(radio); if (modems_installed && !console_active) { if (!radio->getRadioLock() && hw_ready) { if (!radio->begin()) { // The radio could not be started. // Indicate this failure over both the // serial port and with the onboard LEDs kiss_indicate_error(ERROR_INITRADIO); led_indicate_error(0); return false; } else { radio->enableCrc(); radio->onReceive(receive_callback); radio->updateBitrate(); sort_interfaces(); kiss_indicate_phy_stats(radio); lora_receive(radio); // Flash an info pattern to indicate // that the radio is now on kiss_indicate_radiostate(radio); led_indicate_info(3); return true; } } else { // Flash a warning pattern to indicate // that the radio was locked, and thus // not started kiss_indicate_radiostate(radio); led_indicate_warning(3); return false; } } else { // If radio is already on, we silently // ignore the request. kiss_indicate_radiostate(radio); return true; } } void stopRadio(RadioInterface* radio) { radio->end(); sort_interfaces(); kiss_indicate_radiostate(radio); } void update_radio_lock(RadioInterface* radio) { if (radio->getFrequency() != 0 && radio->getSignalBandwidth() != 0 && radio->getTxPower() != 0xFF && radio->getSpreadingFactor() != 0) { radio->setRadioLock(false); } else { radio->setRadioLock(true); } } // Check if the queue is full for the selected radio. // Returns true if full, false if not bool queueFull(RadioInterface* radio) { return (queue_height[radio->getIndex()] >= (CONFIG_QUEUE_MAX_LENGTH) || queued_bytes[radio->getIndex()] >= (getQueueSize(radio->getIndex()))); } volatile bool queue_flushing = false; // Flushes all packets for the interface void flushQueue(RadioInterface* radio) { uint8_t index = radio->getIndex(); if (!queue_flushing) { queue_flushing = true; led_tx_on(); uint16_t processed = 0; uint8_t data_byte; while (!fifo16_isempty(&packet_starts[index])) { #if HAS_BUZZER update_buzzer_tone(TX_HI_TONE, TX_LO_TONE); #endif uint16_t start = fifo16_pop(&packet_starts[index]); uint16_t length = fifo16_pop(&packet_lengths[index]); if (length >= MIN_L && length <= MTU) { for (uint16_t i = 0; i < length; i++) { uint16_t pos = (start+i)%(getQueueSize(index)); tbuf[i] = packet_queue[index][pos]; } transmit(radio, length); processed++; } } lora_receive(radio); led_tx_off(); radio->setPostTxYieldTimeout(millis()+(lora_post_tx_yield_slots*selected_radio->getCSMASlotMS())); } queue_height[index] = 0; queued_bytes[index] = 0; selected_radio->updateAirtime(); queue_flushing = false; #if HAS_DISPLAY display_tx = true; #endif } void transmit(RadioInterface* radio, uint16_t size) { if (radio->getRadioOnline()) { if (!promisc) { uint16_t written = 0; uint8_t header = random(256) & 0xF0; if (size > SINGLE_MTU - HEADER_L) { header = header | FLAG_SPLIT; } radio->beginPacket(); radio->write(header); written++; for (uint16_t i=0; i < size; i++) { radio->write(tbuf[i]); written++; // Only start a new packet if this is a split packet and it has // exceeded the length of a single packet if (written == 255 && header & 0x0F) { radio->endPacket(); radio->addAirtime(written); radio->beginPacket(); radio->write(header); written = 1; } } if (!radio->endPacket()) { kiss_indicate_error(ERROR_MODEM_TIMEOUT); kiss_indicate_error(ERROR_TXFAILED); led_indicate_error(5); hard_reset(); } radio->addAirtime(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) { radio->beginPacket(); } else { radio->beginPacket(size); } for (uint16_t i=0; i < size; i++) { radio->write(tbuf[i]); written++; } radio->endPacket(); radio->addAirtime(written); } last_tx = millis(); } else { kiss_indicate_error(ERROR_TXFAILED); led_indicate_error(5); } } void serialCallback(uint8_t sbyte) { if (IN_FRAME && sbyte == FEND && (command == CMD_INT0_DATA || command == CMD_INT1_DATA || command == CMD_INT2_DATA || command == CMD_INT3_DATA || command == CMD_INT4_DATA || command == CMD_INT5_DATA || command == CMD_INT6_DATA || command == CMD_INT7_DATA || command == CMD_INT8_DATA || command == CMD_INT9_DATA || command == CMD_INT10_DATA || command == CMD_INT11_DATA)) { IN_FRAME = false; if (getInterfaceIndex(command) < INTERFACE_COUNT) { uint8_t index = getInterfaceIndex(command); if (!fifo16_isfull(&packet_starts[index]) && (queued_bytes[index] < (getQueueSize(index)))) { uint16_t s = current_packet_start[index]; int32_t e = queue_cursor[index]-1; if (e == -1) e = (getQueueSize(index))-1; uint16_t l; if (s != e) { l = (s < e) ? e - s + 1: (getQueueSize(index)) - s + e + 1; } else { l = 1; } if (l >= MIN_L) { queue_height[index]++; fifo16_push(&packet_starts[index], s); fifo16_push(&packet_lengths[index], l); current_packet_start[index] = queue_cursor[index]; } } } } 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; if (command == CMD_SEL_INT0 || command == CMD_SEL_INT1 || command == CMD_SEL_INT2 || command == CMD_SEL_INT3 || command == CMD_SEL_INT4 || command == CMD_SEL_INT5 || command == CMD_SEL_INT6 || command == CMD_SEL_INT7 || command == CMD_SEL_INT8 || command == CMD_SEL_INT9 || command == CMD_SEL_INT10 || command == CMD_SEL_INT11) { interface = getInterfaceIndex(command); } } else if (command == CMD_INT0_DATA || command == CMD_INT1_DATA || command == CMD_INT2_DATA || command == CMD_INT3_DATA || command == CMD_INT4_DATA || command == CMD_INT5_DATA || command == CMD_INT6_DATA || command == CMD_INT7_DATA || command == CMD_INT8_DATA || command == CMD_INT9_DATA || command == CMD_INT10_DATA || command == CMD_INT11_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 (getInterfaceIndex(command) < INTERFACE_COUNT) { uint8_t index = getInterfaceIndex(command); if (queue_height[index] < CONFIG_QUEUE_MAX_LENGTH && queued_bytes[index] < (getQueueSize(index))) { queued_bytes[index]++; packet_queue[index][queue_cursor[index]++] = sbyte; if (queue_cursor[index] == (getQueueSize(index))) queue_cursor[index] = 0; } } } } else if (command == CMD_INTERFACES) { for (int i = 0; i < INTERFACE_COUNT; i++) { kiss_indicate_interface(i); } } 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]; selected_radio = interface_obj[interface]; if (freq == 0) { kiss_indicate_frequency(selected_radio); } else { if (op_mode == MODE_HOST) selected_radio->setFrequency(freq); kiss_indicate_frequency(selected_radio); } interface = 0; } } 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]; selected_radio = interface_obj[interface]; if (bw == 0) { kiss_indicate_bandwidth(selected_radio); } else { if (op_mode == MODE_HOST) selected_radio->setSignalBandwidth(bw); selected_radio->updateBitrate(); sort_interfaces(); kiss_indicate_bandwidth(selected_radio); kiss_indicate_phy_stats(selected_radio); } interface = 0; } } else if (command == CMD_TXPOWER) { selected_radio = interface_obj[interface]; if (sbyte == 0xFF) { kiss_indicate_txpower(selected_radio); } else { int8_t txp = (int8_t)sbyte; if (op_mode == MODE_HOST) setTXPower(selected_radio, txp); kiss_indicate_txpower(selected_radio); } interface = 0; } else if (command == CMD_SF) { selected_radio = interface_obj[interface]; if (sbyte == 0xFF) { kiss_indicate_spreadingfactor(selected_radio); } else { int sf = sbyte; if (sf < 5) sf = 5; if (sf > 12) sf = 12; if (op_mode == MODE_HOST) selected_radio->setSpreadingFactor(sf); selected_radio->updateBitrate(); sort_interfaces(); kiss_indicate_spreadingfactor(selected_radio); kiss_indicate_phy_stats(selected_radio); } interface = 0; } else if (command == CMD_CR) { selected_radio = interface_obj[interface]; if (sbyte == 0xFF) { kiss_indicate_codingrate(selected_radio); } else { int cr = sbyte; if (cr < 5) cr = 5; if (cr > 8) cr = 8; if (op_mode == MODE_HOST) selected_radio->setCodingRate4(cr); selected_radio->updateBitrate(); sort_interfaces(); kiss_indicate_codingrate(selected_radio); kiss_indicate_phy_stats(selected_radio); } interface = 0; } else if (command == CMD_IMPLICIT) { set_implicit_length(sbyte); kiss_indicate_implicit_length(); } else if (command == CMD_LEAVE) { if (sbyte == 0xFF) { //display_unblank(); 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) { selected_radio = interface_obj[interface]; if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; if (sbyte == 0xFF) { kiss_indicate_radiostate(selected_radio); } else if (sbyte == 0x00) { stopRadio(selected_radio); } else if (sbyte == 0x01) { startRadio(selected_radio); } interface = 0; } else if (command == CMD_ST_ALOCK) { selected_radio = interface_obj[interface]; 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) { selected_radio->setSTALock(0.0); } else { int st_airtime_limit = (float)at/(100.0*100.0); if (st_airtime_limit >= 1.0) { st_airtime_limit = 0.0; } selected_radio->setSTALock(st_airtime_limit); } kiss_indicate_st_alock(selected_radio); } interface = 0; } else if (command == CMD_LT_ALOCK) { selected_radio = interface_obj[interface]; 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) { selected_radio->setLTALock(0.0); } else { int lt_airtime_limit = (float)at/(100.0*100.0); if (lt_airtime_limit >= 1.0) { lt_airtime_limit = 0.0; } selected_radio->setLTALock(lt_airtime_limit); } kiss_indicate_lt_alock(selected_radio); } interface = 0; } 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) { selected_radio = interface_obj[interface]; update_radio_lock(selected_radio); kiss_indicate_radio_lock(selected_radio); interface = 0; } else if (command == CMD_BLINK) { led_indicate_info(sbyte); } else if (command == CMD_RANDOM) { // pick an interface at random to get data from int int_index = random(INTERFACE_COUNT); selected_radio = interface_obj[int_index]; kiss_indicate_random(getRandom(selected_radio)); interface = 0; } 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) { selected_radio = interface_obj[interface]; if (!queueFull(selected_radio)) { 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) { // todo: add extra space in EEPROM so this isn't hardcoded eeprom_conf_save(interface_obj[0]); } 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 (sbyte != 0x00) { kiss_indicate_device_hash(); } } else if (command == CMD_DEV_SIG) { 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(); } } else if (command == CMD_FW_UPD) { if (sbyte == 0x01) { firmware_update_mode = true; } else { firmware_update_mode = false; } } else if (command == CMD_HASHES) { 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(); } } else if (command == CMD_FW_HASH) { 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(); } } 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) { if (bt_state == BT_STATE_OFF) { bt_start(); bt_conf_save(true); } if (bt_state != BT_STATE_CONNECTED) { 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); //display_unblank(); } #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 } else if (command == CMD_FW_LENGTH) { 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 == FW_LENGTH_LEN) { set_fw_length(cmdbuf); } } else if (command == CMD_DISP_BLNK) { #if HAS_DISPLAY if (sbyte == FESC) { ESCAPE = true; } else { if (ESCAPE) { if (sbyte == TFEND) sbyte = FEND; if (sbyte == TFESC) sbyte = FESC; ESCAPE = false; } db_conf_save(sbyte); //display_unblank(); } #endif } else if (command == CMD_NP_INT) { #if HAS_NP if (sbyte == FESC) { ESCAPE = true; } else { if (ESCAPE) { if (sbyte == TFEND) sbyte = FEND; if (sbyte == TFESC) sbyte = FESC; ESCAPE = false; } sbyte; led_set_intensity(sbyte); np_int_conf_save(sbyte); } #endif } } } #if MCU_VARIANT == MCU_ESP32 portMUX_TYPE update_lock = portMUX_INITIALIZER_UNLOCKED; #endif void validate_status() { #if 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<len; last_rssi = modem_packet->rssi; last_snr_raw = modem_packet->snr_raw; packet_interface = modem_packet->interface; memcpy(&pbuf, modem_packet->data, modem_packet->len); free(modem_packet); modem_packet = NULL; kiss_indicate_stat_rssi(); kiss_indicate_stat_snr(); kiss_write_packet(packet_interface); } #elif MCU_VARIANT == MCU_NRF52 modem_packet_t *modem_packet = NULL; if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) { memcpy(&pbuf, modem_packet->data, modem_packet->len); read_len = modem_packet->len; last_rssi = modem_packet->rssi; last_snr_raw = modem_packet->snr_raw; packet_interface = modem_packet->interface; free(modem_packet); modem_packet = NULL; kiss_indicate_stat_rssi(); kiss_indicate_stat_snr(); kiss_write_packet(packet_interface); } #endif bool ready = false; for (int i = 0; i < INTERFACE_COUNT; i++) { selected_radio = interface_obj[i]; if (selected_radio->getRadioOnline()) { selected_radio->checkModemStatus(); ready = true; } } // If at least one radio is online then we can continue if (ready) { for (int i = 0; i < INTERFACE_COUNT; i++) { selected_radio = interface_obj_sorted[i]; if (selected_radio->calculateALock() || !selected_radio->getRadioOnline()) { // skip this interface continue; } if (queue_height[selected_radio->getIndex()] > 0) { uint32_t check_time = millis(); if (check_time > selected_radio->getPostTxYieldTimeout()) { if (selected_radio->getDCDWaiting() && (check_time >= selected_radio->getDCDWaitUntil())) { selected_radio->setDCDWaiting(false); } if (!selected_radio->getDCDWaiting()) { // todo, will the delay here slow down transmission with // multiple interfaces? needs investigation for (uint8_t dcd_i = 0; dcd_i < DCD_THRESHOLD*2; dcd_i++) { delay(STATUS_INTERVAL_MS); selected_radio->updateModemStatus(); } if (!selected_radio->getDCD()) { uint8_t csma_r = (uint8_t)random(256); if (selected_radio->getCSMAp() >= csma_r) { flushQueue(selected_radio); } else { selected_radio->setDCDWaiting(true); selected_radio->setDCDWaitUntil(millis()+selected_radio->getCSMASlotMS()); } } } } } } } else { if (hw_ready) { if (console_active) { #if HAS_CONSOLE console_loop(); #endif } else { led_indicate_standby(); } } else { led_indicate_not_ready(); // shut down all radio interfaces for (int i = 0; i < INTERFACE_COUNT; i++) { stopRadio(interface_obj[i]); } } } buffer_serial(); if (!fifo_isempty(&serialFIFO)) serial_poll(); #if HAS_DISPLAY #if DISPLAY == OLED if (disp_ready) update_display(); #elif DISPLAY == EINK_BW || DISPLAY == EINK_3C // Display refreshes take so long on e-paper displays that they can disrupt // the regular operation of the device. To combat this the time it is // chosen to do so must be strategically chosen. Particularly on the // RAK4631, the display and the potentially installed SX1280 modem share // the same SPI bus. Thus it is not possible to solve this by utilising the // callback functionality to poll the modem in this case. todo, this may be // able to be improved in the future. if (disp_ready) { if (millis() - last_tx >= 4000) { if (millis() - last_rx >= 1000) { update_display(); } } } #endif #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_BUZZER update_buzzer_notone(); #endif if (memory_low) { #if PLATFORM == PLATFORM_ESP32 if (esp_get_free_heap_size() < 8192) { kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; } else { memory_low = false; } #else kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; #endif } } void process_serial() { buffer_serial(); if (!fifo_isempty(&serialFIFO)) serial_poll(); } #if HAS_INPUT void button_event(uint8_t event, unsigned long duration) { if (duration > BUTTON_MIN_DURATION) { if (duration > BUTTON_9S_DURATION) { bt_bond_wipe(); } else if (duration > BUTTON_6S_DURATION) { bt_stop(); bt_conf_save(false); } else if (duration > BUTTON_3S_DURATION) { bt_enable_pairing(); } else { #if HAS_BUZZER_CTRL toggle_buzzer_enable(); #endif } } } #endif void poll_buffers() { process_serial(); } volatile bool serial_polling = false; void serial_poll() { serial_polling = true; while (!fifo_isempty(&serialFIFO)) { char sbyte = fifo_pop(&serialFIFO); serialCallback(sbyte); } serial_polling = false; } #define MAX_CYCLES 20 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 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; } }