1818 lines
52 KiB
C
Executable File
1818 lines
52 KiB
C
Executable File
/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems)
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option)
|
|
* any later version.
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <md5.h>
|
|
#include <openssl/sha.h>
|
|
#include <libgen.h>
|
|
#include <time.h>
|
|
|
|
#include "librnode.h"
|
|
#include "libs/framing.h"
|
|
#include "libs/serial.h"
|
|
#include "libs/logging/log.h"
|
|
#include "libs/util.h"
|
|
#include "libs/slip_enc.h"
|
|
#include "libs/eeprom.h"
|
|
#include "libs/zip.h"
|
|
|
|
// flashing
|
|
#include "libs/flashers/nrf/dfu.h"
|
|
#include "libs/flashers/nrf/uart_drv.h"
|
|
#include "libs/flashers/esp32/esputil.h"
|
|
|
|
/* Handles incoming RNode communications from serial
|
|
* Scope: private
|
|
* Returns:
|
|
* > 0 - number of bytes read from port
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_handle_resp(struct RNode* rn, bool* in_frame, uint8_t* cmd, uint8_t* cmd_buf, uint8_t* cmd_buf_l, uint16_t* frame_len) {
|
|
int len;
|
|
bool escape = false;
|
|
uint8_t sbyte;
|
|
uint8_t resp_buf[RESP_BUF_SIZE] = {0};
|
|
|
|
len = read_port(rn->fd, resp_buf, RESP_BUF_SIZE);
|
|
|
|
if (len > 0) {
|
|
log_trace("Read %d bytes", len);
|
|
for (int i = 0; i < len; i++) {
|
|
log_trace("Byte: %02x", resp_buf[i]);
|
|
if (!(*in_frame) && resp_buf[i] == FEND) {
|
|
*in_frame = true;
|
|
*cmd = CMD_UNKNOWN;
|
|
sbyte = 0;
|
|
*cmd_buf_l = 0;
|
|
} else if (*in_frame) {
|
|
if (*frame_len == 0) {
|
|
*cmd = resp_buf[i];
|
|
(*frame_len)++;
|
|
continue;
|
|
} else {
|
|
sbyte = resp_buf[i];
|
|
}
|
|
if (sbyte == FEND && *cmd == CMD_ROM_READ) {
|
|
//rn->product = cmd_buf[0];
|
|
//rn->model = cmd_buf[1];
|
|
//rn->hw_rev = cmd_buf[2];
|
|
|
|
//memcpy(rn->serial, cmd_buf+3, SERIAL_SIZE);
|
|
//memcpy(rn->made, cmd_buf+3+SERIAL_SIZE, MADE_SIZE);
|
|
|
|
//memcpy(rn->checksum, cmd_buf+3+SERIAL_SIZE+MADE_SIZE, CHECKSUM_SIZE);
|
|
//memcpy(rn->signature, cmd_buf+3+SERIAL_SIZE+MADE_SIZE, CHECKSUM_SIZE);
|
|
|
|
|
|
memcpy(rn->r_eeprom, cmd_buf, EEPROM_SIZE);
|
|
|
|
*in_frame = false;
|
|
*frame_len = 0;
|
|
*cmd = CMD_UNKNOWN;
|
|
continue;
|
|
} else if (sbyte == FEND) {
|
|
*in_frame = false;
|
|
*frame_len = 0;
|
|
*cmd = CMD_UNKNOWN;
|
|
*cmd_buf_l = 0;
|
|
continue;
|
|
} else if (*cmd == CMD_ROM_READ) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
}
|
|
} else if (*cmd == CMD_DETECT && sbyte == DETECT_RESP) {
|
|
rn->connected = true;
|
|
} else if (*cmd == CMD_PLATFORM) {
|
|
rn->platform = sbyte;
|
|
} else if (*cmd == CMD_MCU) {
|
|
rn->mcu = sbyte;
|
|
} else if (*cmd == CMD_FW_VERSION) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
if (*cmd_buf_l == 2) {
|
|
rn->fw_ver = cmd_buf[0] + cmd_buf[1] / 100.0;
|
|
*cmd_buf_l = 0;
|
|
}
|
|
}
|
|
} else if (*cmd == CMD_INTERFACES) {
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
if (*cmd_buf_l == 2) {
|
|
rn->interfaces[cmd_buf[0]] = cmd_buf[1];
|
|
*cmd_buf_l = 0;
|
|
}
|
|
} else if (*cmd == CMD_FREQUENCY) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
if (*cmd_buf_l == 4) {
|
|
rn->freq = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3];
|
|
*cmd_buf_l = 0;
|
|
}
|
|
}
|
|
} else if (*cmd == CMD_BANDWIDTH) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
if (*cmd_buf_l == 4) {
|
|
rn->bw = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3];
|
|
*cmd_buf_l = 0;
|
|
}
|
|
}
|
|
} else if (*cmd == CMD_TXPOWER) {
|
|
rn->txp = sbyte;
|
|
} else if (*cmd == CMD_SF) {
|
|
rn->sf = sbyte;
|
|
} else if (*cmd == CMD_CR) {
|
|
rn->cr = sbyte;
|
|
} else if (*cmd == CMD_ST_ALOCK) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
}
|
|
if (*cmd_buf_l == 2) {
|
|
float at = cmd_buf[0] << 8 | cmd_buf[1];
|
|
|
|
rn->st_alock = at / 100.0;
|
|
*cmd_buf_l = 0;
|
|
}
|
|
} else if (*cmd == CMD_LT_ALOCK) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
}
|
|
if (*cmd_buf_l == 2) {
|
|
float at = cmd_buf[0] << 8 | cmd_buf[1];
|
|
|
|
rn->lt_alock = at / 100.0;
|
|
*cmd_buf_l = 0;
|
|
}
|
|
} else if (*cmd == CMD_RADIO_STATE) {
|
|
rn->int_state[rn->sel_int] = sbyte;
|
|
} else if (*cmd == CMD_ROM_READ) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
}
|
|
} else if (*cmd == CMD_BT_PIN) {
|
|
if (sbyte == FESC) {
|
|
escape = true;
|
|
} else {
|
|
if (escape) {
|
|
if (sbyte == TFEND) {
|
|
sbyte = FEND;
|
|
} else if (sbyte == TFESC) {
|
|
sbyte = FESC;
|
|
}
|
|
escape = false;
|
|
}
|
|
cmd_buf[*cmd_buf_l] = sbyte;
|
|
(*cmd_buf_l)++;
|
|
}
|
|
if (*cmd_buf_l == 4) {
|
|
rn->bt_pairing_pin = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3];
|
|
*cmd_buf_l = 0;
|
|
}
|
|
}
|
|
(*frame_len)++;
|
|
}
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* Detects an RNode
|
|
* Scope: private
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_detect(struct RNode* rn) {
|
|
int err_code;
|
|
uint8_t tx_buf[16];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[16]){FEND, CMD_DETECT, DETECT_REQ, FEND, FEND, CMD_FW_VERSION, 0x00, FEND, FEND, CMD_PLATFORM, 0x00, FEND, FEND, CMD_MCU, 0x00, FEND}, 16*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 16);
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
return (err_code == -1) ? -1 : rn->connected;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Establishes communication with an RNode
|
|
* Params: port (e.g. /dev/ttyACM0), baud rate (e.g. 115200), detect (attempt
|
|
* to communicate with RNode), force_detect (fail if cannot communicate with
|
|
* RNode)
|
|
* Scope: public
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_init(struct RNode* rn, char* port, uint32_t baud, bool detect, bool force_detect) {
|
|
rn->port = port;
|
|
rn->baud = baud;
|
|
rn->fd = open_port(rn->port, baud);
|
|
|
|
if (rn->fd > 0) {
|
|
if (detect) {
|
|
if (rnode_detect(rn)) {
|
|
log_info("RNode detected successfully on port %s", port);
|
|
} else {
|
|
if (force_detect) {
|
|
log_error("RNode could not be detected on port %s", port);
|
|
return -1;
|
|
} else {
|
|
log_info("RNode could not be detected on port %s, this isn't a problem.", port);
|
|
}
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Resets an RNode
|
|
* Scope: public
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_reset(struct RNode* rn) {
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_RESET, CMD_RESET_BYTE, FEND}, 4*sizeof(uint8_t));
|
|
return write_port(rn->fd, tx_buf, 4);
|
|
}
|
|
|
|
/* Sets an RNode's platform attribute manually
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_platform(struct RNode* rn, uint8_t platform) {
|
|
rn->platform = platform;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enable / disable Bluetooth on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_bt(struct RNode* rn, uint8_t val) {
|
|
int err_code;
|
|
if (val == BT_ON) {
|
|
log_info("Enabling Bluetooth...");
|
|
} else if (val == BT_PAIRING) {
|
|
log_info("Starting Bluetooth pairing...");
|
|
} else {
|
|
log_info("Disabling Bluetooth...");
|
|
}
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_BT_CTRL, val, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Attempt to retrieve the BT pairing pin of an RNode. Blocking function,
|
|
* should be run in a separate thread.
|
|
* Params: timeout (in ms, minimum value 200)
|
|
* Scope: public
|
|
* Returns
|
|
* > 0 - bluetooth pairing code
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -4 - rnode did not respond
|
|
*/
|
|
int rnode_get_bt_pin(struct RNode* rn, uint32_t timeout) {
|
|
int err_code = 0;
|
|
time_t ltime;
|
|
time(<ime);
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
unsigned int start = ltime; unsigned int current = ltime;
|
|
|
|
if (timeout < 200) {
|
|
return -1;
|
|
}
|
|
|
|
while (current <= start + timeout) {
|
|
if (err_code >= 0) {
|
|
sleep_ms(50);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
time(<ime); current = ltime;
|
|
if (rn->bt_pairing_pin != 0) {
|
|
// If we successfully retrieved the pairing pin, return it
|
|
return rn->bt_pairing_pin;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
return -4;
|
|
}
|
|
|
|
/* Display related functions */
|
|
|
|
/* Set display intensity on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_disp_int(struct RNode* rn, uint8_t disp_int) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_INT, disp_int, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Set display timeout on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_disp_timeout(struct RNode* rn, uint8_t timeout) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_INT, timeout, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Set display address on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_disp_addr(struct RNode* rn, uint8_t addr) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_ADDR, addr, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Set display rotation on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_disp_rot(struct RNode* rn, uint8_t rot) {
|
|
int err_code = 0;
|
|
if (rot < 0) rot = 0;
|
|
else if (rot > 3) rot = 3;
|
|
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_ROT, rot, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Start display reconditioning on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_start_disp_recon(struct RNode* rn) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_RCND, 1, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Set neopixel intensity on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_np_int(struct RNode* rn, uint8_t np_int) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_NP_INT, np_int, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Get available interfaces on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_interfaces(struct RNode* rn) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_INTERFACES, 0x00, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
}
|
|
|
|
if (err_code > 0) {
|
|
return 0;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/* Radio configuration functions */
|
|
|
|
/* Select interface on an RNode (in preparation for running rnode_set_freq, etc)
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -13 - chosen interface does not exist (did you run rnode_get_interfaces)?
|
|
*/
|
|
int rnode_select_interface(struct RNode* rn, uint8_t interface) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
|
|
if (rn->interfaces[interface] != 0) {
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SEL_INT, interface, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
} else {
|
|
return err_code;
|
|
}
|
|
|
|
|
|
if (!err_code) {
|
|
rn->sel_int = interface;
|
|
}
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Set frequency on an RNode (in preparation for TNC mode)
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_freq(struct RNode* rn, uint32_t freq) {
|
|
uint8_t tx_buf[11];
|
|
uint8_t escaped_freq[8];
|
|
uint32_t escaped_freq_size;
|
|
uint8_t freq_a[4];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(freq_a, (uint8_t[4]){freq >> 24, freq >> 16 & 0xFF, freq >> 8 & 0xFF, freq & 0xFF}, 4*sizeof(uint8_t));
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_FREQUENCY}, 2*sizeof(uint8_t));
|
|
encode_slip(escaped_freq, &escaped_freq_size, freq_a, 4, false);
|
|
memcpy(tx_buf + 2, escaped_freq, escaped_freq_size*sizeof(uint8_t));
|
|
tx_buf[2 + escaped_freq_size] = FEND;
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 2 + escaped_freq_size + 1);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->freq;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get frequency on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_freq(struct RNode* rn) {
|
|
uint8_t tx_buf[7];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[7]){FEND, CMD_FREQUENCY, 0, 0, 0, 0, FEND}, 7*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 7);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->freq;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set bandwidth on an RNode (in preparation for TNC mode)
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_bw(struct RNode* rn, uint32_t bw) {
|
|
int err_code;
|
|
uint8_t tx_buf[11];
|
|
uint8_t escaped_bw[8];
|
|
uint32_t escaped_bw_size;
|
|
uint8_t bw_a[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(bw_a, (uint8_t[4]){bw >> 24, bw >> 16 & 0xFF, bw >> 8 & 0xFF, bw & 0xFF}, 4*sizeof(uint8_t));
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_BANDWIDTH}, 2*sizeof(uint8_t));
|
|
encode_slip(escaped_bw, &escaped_bw_size, bw_a, 4, false);
|
|
memcpy(tx_buf + 2, escaped_bw, escaped_bw_size*sizeof(uint8_t));
|
|
tx_buf[2 + escaped_bw_size] = FEND;
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 2 + escaped_bw_size + 1);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->bw;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get bandwidth on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_bw(struct RNode* rn) {
|
|
uint8_t tx_buf[7];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[7]){FEND, CMD_BANDWIDTH, 0, 0, 0, 0, FEND}, 7*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 7);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->bw;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set transmission power on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_txp(struct RNode* rn, uint8_t txp) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_TXPOWER, txp, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return txp == rn->txp ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get transmission power on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_txp(struct RNode* rn) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_TXPOWER, 0xFF, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->txp;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set spreading factor on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_sf(struct RNode* rn, uint8_t sf) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SF, sf, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return sf == rn->sf ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get spreading factor on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* >=5 - spreading factor
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_sf(struct RNode* rn) {
|
|
uint8_t tx_buf[11];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SF, 0xFF, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (!err_code) {
|
|
return rn->sf;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set coding rate on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_cr(struct RNode* rn, uint8_t cr) {
|
|
uint8_t tx_buf[4];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CR, cr, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return cr == rn->cr ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get coding rate on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* >=5 - coding rate
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_cr(struct RNode* rn) {
|
|
uint8_t tx_buf[4];
|
|
int err_code;
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CR, 0xFF, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return rn->cr;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set short term airtime limit on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
float rnode_set_st_alock(struct RNode* rn, float at_l) {
|
|
int err_code;
|
|
int at = at_l * 100;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_ST_ALOCK}, 2*sizeof(uint8_t));
|
|
memcpy(tx_buf + 2, (uint8_t[2]){(at >> 8 & 0xFF), (at & 0xFF)}, 1*sizeof(uint8_t));
|
|
tx_buf[2 + 1] = FEND;
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 2 + 1 + 1);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return at_l == rn->st_alock ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Set long term airtime limit on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
float rnode_set_lt_alock(struct RNode* rn, float at_l) {
|
|
int err_code;
|
|
int at = at_l * 100;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_LT_ALOCK}, 2*sizeof(uint8_t));
|
|
memcpy(tx_buf + 2, (uint8_t[2]){(at >> 8 & 0xFF), (at & 0xFF)}, 1*sizeof(uint8_t));
|
|
tx_buf[2 + 1] = FEND;
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 2 + 1 + 1);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return at_l == rn->lt_alock ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Start or stop selected interface on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_interface_state(struct RNode* rn, bool state) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[10] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_RADIO_STATE, state, FEND}, 4*sizeof(uint8_t));
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
if (err_code > 0) {
|
|
return state == rn->int_state[rn->sel_int] ? 0 : -1;
|
|
} else {
|
|
return err_code;
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
}
|
|
|
|
/* Get selected interface state on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_interface_state(struct RNode* rn) {
|
|
return rn->int_state[rn->sel_int];
|
|
}
|
|
|
|
/* Mode selection functions */
|
|
|
|
/* Enable the host-controlled (normal) mode of operation on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_normal_mode(struct RNode* rn) {
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CONF_DELETE, 0x00, FEND}, 4*sizeof(uint8_t));
|
|
return write_port(rn->fd, tx_buf, 4);
|
|
}
|
|
|
|
/* Enable the serial TNC mode of operation on an RNode
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_tnc_mode(struct RNode* rn) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CONF_SAVE, 0x00, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Misc functions */
|
|
int rnode_set_int_avoid(struct RNode* rn, bool avoid) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DIS_IA, !avoid, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* EEPROM related functions */
|
|
|
|
/* Sets the firmware hash on an RNode.
|
|
* Scope: public
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_fw_hash(struct RNode* rn, uint8_t* hash) {
|
|
uint8_t tx_buf[2 + (FW_HASH_SIZE * 2) + 1];
|
|
uint8_t escaped_hash[FW_HASH_SIZE*2];
|
|
uint32_t escaped_hash_size;
|
|
unsigned char hash_str[FW_HASH_SIZE * 2 + 1] = "";
|
|
|
|
log_debug("Setting firmware hash...");
|
|
|
|
for (int i = 0; i < FW_HASH_SIZE; i++) {
|
|
sprintf(hash_str+i*2, "%02x", hash[i]);
|
|
}
|
|
|
|
log_trace("Firmware hash is %s", hash_str);
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_FW_HASH}, 2*sizeof(uint8_t));
|
|
encode_slip(escaped_hash, &escaped_hash_size, hash, FW_HASH_SIZE, false);
|
|
memcpy(tx_buf + 2, escaped_hash, escaped_hash_size*sizeof(uint8_t));
|
|
tx_buf[2 + escaped_hash_size] = FEND;
|
|
return write_port(rn->fd, tx_buf, 2 + escaped_hash_size + 1);
|
|
}
|
|
|
|
/* Write to an arbitrary address in an RNode's EEPROM
|
|
* Scope: private
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_write_eeprom(struct RNode* rn, uint8_t address, uint8_t value) {
|
|
int err_code = 0;
|
|
uint8_t tx_buf[3 + 2*2];
|
|
uint8_t data[2];
|
|
uint8_t escaped_data[2*2];
|
|
uint32_t escaped_data_size;
|
|
|
|
data[0] = address;
|
|
data[1] = value;
|
|
|
|
memcpy(tx_buf, (uint8_t[2]){FEND, CMD_ROM_WRITE}, 2*sizeof(uint8_t));
|
|
|
|
encode_slip(escaped_data, &escaped_data_size, data, 2, false);
|
|
|
|
memcpy(tx_buf+2, escaped_data, escaped_data_size*sizeof(uint8_t));
|
|
|
|
tx_buf[2 + escaped_data_size] = FEND;
|
|
|
|
err_code = write_port(rn->fd, tx_buf, 2 + escaped_data_size + 1);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(6);
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
/* Sets an RNode's product in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_product(struct RNode* rn, uint8_t product) {
|
|
rn->product = product;
|
|
|
|
return rnode_write_eeprom(rn, ADDR_PRODUCT, product);
|
|
}
|
|
|
|
/* Sets an RNode's model in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_model(struct RNode* rn, uint8_t model) {
|
|
rn->model = model;
|
|
|
|
return rnode_write_eeprom(rn, ADDR_MODEL, model);
|
|
}
|
|
|
|
/* Sets an RNode's hardware revision in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_hw_rev(struct RNode* rn, uint8_t hw_rev) {
|
|
rn->hw_rev = hw_rev;
|
|
|
|
return rnode_write_eeprom(rn, ADDR_HW_REV, hw_rev);
|
|
}
|
|
|
|
/* Sets an RNode's serial in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_serial(struct RNode* rn, uint8_t* serial) {
|
|
int err_code = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
err_code = rnode_write_eeprom(rn, ADDR_SERIAL+i, serial[i]);
|
|
rn->serial[i] = serial[i];
|
|
if (err_code) {
|
|
break;
|
|
}
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
/* Sets an RNode's manufacture time in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_made_time(struct RNode* rn, uint32_t time) {
|
|
int err_code = 0;
|
|
uint8_t data[4];
|
|
|
|
data[3] = time & 0x000000FF;
|
|
data[2] = time >> 8 & 0x000000FF;
|
|
data[1] = time >> 16 & 0x000000FF;
|
|
data[0] = time >> 24 & 0x000000FF;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
err_code = rnode_write_eeprom(rn, ADDR_MADE+i, data[i]);
|
|
rn->made[i] = data[i];
|
|
if (err_code) {
|
|
break;
|
|
}
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
/* Calculates an RNode's checksum for its EEPROM. Must ONLY be called AFTER
|
|
* rnode_set_product, rnode_set_model, rnode_set_hw_rev, rnode_set_serial and
|
|
* rnode_set_made_time are all called. OR alternatively, after rnode_get_eeprom.
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -3 - provided array was too small
|
|
*/
|
|
int rnode_calculate_checksum(struct RNode* rn, uint8_t* checksum) {
|
|
uint8_t data[11];
|
|
int temp_val;
|
|
unsigned char checksum_s[CHECKSUM_SIZE * 2];
|
|
|
|
data[0] = rn->product;
|
|
data[1] = rn->model;
|
|
data[2] = rn->hw_rev;
|
|
data[3] = rn->serial[0];
|
|
data[4] = rn->serial[1];
|
|
data[5] = rn->serial[2];
|
|
data[6] = rn->serial[3];
|
|
data[7] = rn->made[0];
|
|
data[8] = rn->made[1];
|
|
data[9] = rn->made[2];
|
|
data[10] = rn->made[3];
|
|
|
|
MD5Data(data, 11, checksum_s);
|
|
|
|
for (int i = 0; i < CHECKSUM_SIZE && sscanf(checksum_s + i * 2, "%2x", &temp_val); i++) {
|
|
checksum[i] = temp_val;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Sets an RNode's checksum in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_checksum(struct RNode* rn, uint8_t* checksum) {
|
|
int err_code = 0;
|
|
for (int i = 0; i < CHECKSUM_SIZE; i++) {
|
|
err_code = rnode_write_eeprom(rn, ADDR_CHKSUM+i, checksum[i]);
|
|
if (err_code) {
|
|
break;
|
|
}
|
|
}
|
|
memcpy(rn->checksum, checksum, CHECKSUM_SIZE);
|
|
return err_code;
|
|
}
|
|
|
|
/* Generate's an RNode's EEPROM signature from the checksum
|
|
* THE RETURNED POINTER MUST BE OPENSSL_free()'D!
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -3 - malloc failure
|
|
*/
|
|
void* rnode_generate_signature(struct RNode* rn, EVP_PKEY *signing_key) {
|
|
EVP_PKEY_CTX *ctx;
|
|
size_t hash_len = 256 / 8, sig_len;
|
|
uint8_t hash[256 / 8];
|
|
void* signature;
|
|
|
|
/*
|
|
* NB: assumes signing_key and md are set up before the next
|
|
* step. signing_key must be an RSA private key and md must
|
|
* point to the SHA-256 digest to be signed.
|
|
*/
|
|
ctx = EVP_PKEY_CTX_new(signing_key, NULL /* no engine */);
|
|
if (ctx == NULL) return NULL;
|
|
|
|
if (EVP_PKEY_sign_init(ctx) <= 0) return NULL;
|
|
|
|
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) return NULL;
|
|
|
|
if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) return NULL;
|
|
|
|
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_MAX) <= 0) return NULL;
|
|
|
|
if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) return NULL;
|
|
|
|
SHA256(rn->checksum, CHECKSUM_SIZE, hash);
|
|
|
|
/* Determine buffer length */
|
|
if (EVP_PKEY_sign(ctx, NULL, &sig_len, hash, hash_len) <= 0) return NULL;
|
|
|
|
// todo need to free signature
|
|
signature = OPENSSL_malloc(sig_len);
|
|
|
|
if (signature == NULL) return NULL;
|
|
|
|
if (EVP_PKEY_sign(ctx, signature, &sig_len, hash, hash_len) <= 0) return NULL;
|
|
|
|
/* Signature is siglen bytes written to buffer sig */
|
|
|
|
EVP_PKEY_CTX_free(ctx);
|
|
|
|
return signature;
|
|
}
|
|
|
|
/* Sets an RNode's signature in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_signature(struct RNode* rn, uint8_t* data) {
|
|
int err_code = 0;
|
|
for (int i = 0; i < SIGNATURE_SIZE; i++) {
|
|
err_code = rnode_write_eeprom(rn, ADDR_SIGNATURE+i, data[i]);
|
|
if (err_code) {
|
|
break;
|
|
}
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
/* Sets an RNode's lock byte in its EEPROM
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_set_lock(struct RNode* rn) {
|
|
return rnode_write_eeprom(rn, ADDR_INFO_LOCK, INFO_LOCK_BYTE);
|
|
}
|
|
|
|
/* Retrieve an RNode's EEPROM values into its struct
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_get_eeprom(struct RNode* rn) {
|
|
int err_code = rnode_dump_eeprom(rn);
|
|
|
|
// Populate remote EEPROM values
|
|
memcpy(&rn->product, rn->r_eeprom, 1);
|
|
memcpy(&rn->model, rn->r_eeprom+1, 1);
|
|
memcpy(&rn->hw_rev, rn->r_eeprom+2, 1);
|
|
memcpy(&rn->serial, rn->r_eeprom+3, 4);
|
|
memcpy(&rn->made, rn->r_eeprom+7, 4);
|
|
memcpy(&rn->checksum, rn->r_eeprom+11, 16);
|
|
memcpy(&rn->signature, rn->r_eeprom+27, 128);
|
|
memcpy(&rn->lock_byte, rn->r_eeprom+155, 1);
|
|
memcpy(&rn->sf, rn->r_eeprom+156, 1);
|
|
memcpy(&rn->cr, rn->r_eeprom+157, 1);
|
|
memcpy(&rn->txp, rn->r_eeprom+158, 1);
|
|
memcpy(&rn->bw, rn->r_eeprom+159, 4);
|
|
memcpy(&rn->freq, rn->r_eeprom+163, 4);
|
|
memcpy(&rn->cfg_ok, rn->r_eeprom+167, 4);
|
|
memcpy(&rn->bt, rn->r_eeprom+168, 1);
|
|
memcpy(&rn->disp_set, rn->r_eeprom+169, 1);
|
|
memcpy(&rn->disp_int, rn->r_eeprom+170, 1);
|
|
memcpy(&rn->disp_addr, rn->r_eeprom+171, 1);
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Dump an RNode's entire EEPROM to r_eeprom
|
|
* Note: This will populate r_eeprom but not the cached EEPROM values. If you
|
|
* want to update the cached values, call rnode_get_eeprom instead.
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* -1 - generic error
|
|
*/
|
|
int rnode_dump_eeprom(struct RNode* rn) {
|
|
int err_code;
|
|
uint8_t tx_buf[4];
|
|
bool in_frame = false;
|
|
uint8_t cmd = CMD_UNKNOWN;
|
|
uint8_t cmd_buf[256] = {0};
|
|
uint8_t cmd_buf_l = 0;
|
|
uint16_t frame_len = 0;
|
|
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_ROM_READ, 0x00, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(100);
|
|
err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len);
|
|
}
|
|
|
|
return err_code > 0 ? 0 : -1;
|
|
}
|
|
|
|
/* Verify an RNode's EEPROM matches the expected values
|
|
* Scope: public
|
|
* Returns
|
|
* 0 - success
|
|
* value > 0 or value < 0 - EEPROM invalid
|
|
*/
|
|
int rnode_verify_eeprom(struct RNode* rn) {
|
|
int err_code;
|
|
|
|
err_code = rnode_dump_eeprom(rn);
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->product, rn->r_eeprom, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->model, rn->r_eeprom+1, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->hw_rev, rn->r_eeprom+2, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->serial, rn->r_eeprom+3, sizeof(4));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->made, rn->r_eeprom+7, sizeof(4));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->checksum, rn->r_eeprom+11, sizeof(16));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->signature, rn->r_eeprom+27, sizeof(128));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->lock_byte, rn->r_eeprom+155, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->sf, rn->r_eeprom+156, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->cr, rn->r_eeprom+157, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->txp, rn->r_eeprom+158, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->bw, rn->r_eeprom+159, sizeof(4));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->freq, rn->r_eeprom+163, sizeof(4));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->cfg_ok, rn->r_eeprom+167, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->bt, rn->r_eeprom+168, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->disp_set, rn->r_eeprom+169, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->disp_int, rn->r_eeprom+170, sizeof(1));
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = memcmp(&rn->disp_addr, rn->r_eeprom+171, sizeof(1));
|
|
}
|
|
|
|
return err_code;
|
|
}
|
|
|
|
/* Wipes an RNode's EEPROM.
|
|
* Scope: public
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -2 - rnode not supported (firmware too old, please update)
|
|
*/
|
|
int rnode_wipe_eeprom(struct RNode* rn) {
|
|
log_info("Erasing EEPROM, please switch off your device IMMEDIATELY if you did not intend to do this!");
|
|
int err_code = 0;
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_UNLOCK_ROM, 0xF8, FEND}, 4*sizeof(uint8_t));
|
|
if (rn->fw_ver != 0 && rn->fw_ver < MIN_FW_VER) {
|
|
log_trace("RNode firmware version too low!");
|
|
return -2;
|
|
} else {
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
}
|
|
|
|
if (err_code) {
|
|
return err_code;
|
|
}
|
|
|
|
err_code = close_port(rn->fd);
|
|
|
|
if (!err_code) {
|
|
sleep_ms(13000);
|
|
|
|
if (rn->platform == PLATFORM_NRF52) {
|
|
// Due to the current janky emulated EEPROM implementation for the
|
|
// RAK4631, extra time must be given to allow for writing.
|
|
sleep_ms(10000);
|
|
}
|
|
} else {
|
|
return err_code;
|
|
}
|
|
|
|
rn->fd = open_port(rn->port, rn->baud);
|
|
|
|
return (err_code == 0) ? 0 : -1;
|
|
}
|
|
|
|
int rnode_flash_progress_cb(struct RNode* rn, void (*ptr)(uint8_t)) {
|
|
rn->prog_cb = ptr;
|
|
}
|
|
|
|
/* Flashes an RNode.
|
|
* Scope: public
|
|
* Returns:
|
|
* 0 - success
|
|
* -1 - generic error
|
|
* -9 - ESP32 image invalid, SHA256 digest incorrect
|
|
*/
|
|
int rnode_flash(struct RNode* rn, char* zip_path, bool update, uint8_t* serial, EVP_PKEY* priv_key, bool touch) {
|
|
int err_code = 0;
|
|
uint8_t hash[FW_HASH_SIZE];
|
|
|
|
if (update) {
|
|
// show update logo on RNode display
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_FW_UPD, 0x01, FEND}, 4*sizeof(uint8_t));
|
|
err_code = write_port(rn->fd, tx_buf, 4);
|
|
}
|
|
|
|
// Close port to prepare for flashing
|
|
if (!err_code) {
|
|
err_code = close_port(rn->fd);
|
|
}
|
|
|
|
if (!err_code) {
|
|
switch (rn->platform) {
|
|
case PLATFORM_ESP32:
|
|
uint8_t img_hash[32];
|
|
struct zip_t *zip_pkg;
|
|
char* bin_ext = ".bin";
|
|
char* bootloader_ext = ".bootloader";
|
|
char* boot_app0_ext = ".boot_app0";
|
|
char* partitions_ext = ".partitions";
|
|
char* console_image_name = "console_image.bin";
|
|
char *bin, *bootloader, *boot_app0, *partitions, *console_image = 0, *bin_path, *bootloader_path, *boot_app0_path, *partitions_path, *console_image_path, *filename, *filename_no_ext, *dir, *zip_path_cpy;
|
|
size_t bin_size, bootloader_size, boot_app0_size, partitions_size, console_image_size = 0, bin_path_size, bootloader_path_size, boot_app0_path_size, partitions_path_size, console_image_path_size, filename_no_ext_size;
|
|
zip_pkg = zip_open(zip_path, 0, 'r');
|
|
if (zip_pkg == NULL)
|
|
{
|
|
log_error("Cannot open ZIP firmware package file!");
|
|
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
zip_path_cpy = strdup(zip_path);
|
|
filename = basename(zip_path_cpy);
|
|
|
|
for (int i = 0; i < strlen(filename); i++) {
|
|
if (filename[i] == '.') {
|
|
filename_no_ext_size = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
filename_no_ext = malloc(filename_no_ext_size+2);
|
|
|
|
memcpy(filename_no_ext, filename, filename_no_ext_size);
|
|
|
|
filename_no_ext[filename_no_ext_size+1] = '\0';
|
|
|
|
// Retrieve bin file
|
|
bin_path_size = filename_no_ext_size+strlen(bin_ext);
|
|
bin_path = malloc(bin_path_size+1);
|
|
strcpy(bin_path, filename_no_ext);
|
|
strcat(bin_path, bin_ext);
|
|
|
|
if (zip_entry_open(zip_pkg, bin_path)) {
|
|
return -1;
|
|
} else {
|
|
bin = malloc(FILE_MAX_SIZE);
|
|
if (zip_entry_read(zip_pkg, (void**)&bin, &bin_size)) {
|
|
return -1;
|
|
} else {
|
|
if (zip_entry_close(zip_pkg)) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve bootloader
|
|
bootloader_path_size = filename_no_ext_size+strlen(bootloader_ext);
|
|
bootloader_path = malloc(bootloader_path_size+1);
|
|
strcpy(bootloader_path, filename_no_ext);
|
|
strcat(bootloader_path, bootloader_ext);
|
|
|
|
if (zip_entry_open(zip_pkg, bootloader_path)) {
|
|
return -1;
|
|
} else {
|
|
bootloader = malloc(FILE_MAX_SIZE);
|
|
if (zip_entry_read(zip_pkg, (void**)&bootloader, &bootloader_size)) {
|
|
return -1;
|
|
} else {
|
|
if (zip_entry_close(zip_pkg)) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve boot_app0
|
|
boot_app0_path_size = filename_no_ext_size+strlen(boot_app0_ext);
|
|
boot_app0_path = malloc(boot_app0_path_size+1);
|
|
strcpy(boot_app0_path, filename_no_ext);
|
|
strcat(boot_app0_path, boot_app0_ext);
|
|
|
|
if (zip_entry_open(zip_pkg, boot_app0_path)) {
|
|
return -1;
|
|
} else {
|
|
boot_app0 = malloc(FILE_MAX_SIZE);
|
|
if (zip_entry_read(zip_pkg, (void**)&boot_app0, &boot_app0_size)) {
|
|
return -1;
|
|
} else {
|
|
if (zip_entry_close(zip_pkg)) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve partitions
|
|
partitions_path_size = filename_no_ext_size+strlen(partitions_ext);
|
|
partitions_path = malloc(partitions_path_size+1);
|
|
strcpy(partitions_path, filename_no_ext);
|
|
strcat(partitions_path, partitions_ext);
|
|
|
|
if (zip_entry_open(zip_pkg, partitions_path)) {
|
|
return -1;
|
|
} else {
|
|
partitions = malloc(FILE_MAX_SIZE);
|
|
if (zip_entry_read(zip_pkg, (void**)&partitions, &partitions_size)) {
|
|
return -1;
|
|
} else {
|
|
if (zip_entry_close(zip_pkg)) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve console image
|
|
if (zip_entry_open(zip_pkg, console_image_name)) {
|
|
// No console image on this build, skipping
|
|
} else {
|
|
console_image = malloc(FILE_MAX_SIZE);
|
|
if (zip_entry_read(zip_pkg, (void**)&console_image, &console_image_size)) {
|
|
return -1;
|
|
} else {
|
|
if (zip_entry_close(zip_pkg)) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check firmware hash
|
|
memcpy(img_hash, (bin + bin_size) - 32, 32);
|
|
SHA256(bin, bin_size-32, hash);
|
|
|
|
if (memcmp(hash, img_hash, 32*sizeof(uint8_t) != 0)) {
|
|
return -9;
|
|
}
|
|
|
|
|
|
flash_full(rn, boot_app0, boot_app0_size, bootloader, bootloader_size, bin, bin_size, partitions, partitions_size, console_image, console_image_size);
|
|
|
|
free(console_image);
|
|
free(partitions);
|
|
free(partitions_path);
|
|
free(boot_app0);
|
|
free(boot_app0_path);
|
|
free(bootloader);
|
|
free(bootloader_path);
|
|
free(bin);
|
|
free(bin_path);
|
|
free(filename_no_ext);
|
|
free(zip_path_cpy);
|
|
}
|
|
break;
|
|
case PLATFORM_NRF52:
|
|
{
|
|
// No space for console image on flash on this target currently
|
|
dfu_param_t dfu_param;
|
|
uart_drv_t uart_drv;
|
|
uart_drv.p_PortName = rn->port;
|
|
err_code = uart_slip_open(&uart_drv, touch);
|
|
|
|
if (!err_code) {
|
|
dfu_param.p_uart = &uart_drv;
|
|
dfu_param.p_pkg_file = zip_path;
|
|
err_code = dfu_send_package(&dfu_param, rn, hash);
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
err_code = uart_slip_close(&uart_drv);
|
|
}
|
|
}
|
|
}
|
|
// Wait for reset and boot. This value may have to be adjusted in the
|
|
// future if firmware becomes more complex.
|
|
sleep_ms(15000);
|
|
|
|
rn->fd = open_port(rn->port, rn->baud);
|
|
|
|
if (rn->fd > 0) {
|
|
if (!update) {
|
|
uint8_t checksum[CHECKSUM_SIZE];
|
|
void* signature;
|
|
|
|
log_info("Bootstrapping RNode EEPROM...");
|
|
// Provision EEPROM
|
|
err_code = rnode_set_product(rn, rn->product);
|
|
if (!err_code) {
|
|
err_code = rnode_set_model(rn, rn->model);
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = rnode_set_hw_rev(rn, rn->hw_rev);
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = rnode_set_serial(rn, serial);
|
|
}
|
|
|
|
if (!err_code) {
|
|
time_t current = time(NULL);
|
|
err_code = rnode_set_made_time(rn, current);
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = rnode_calculate_checksum(rn, checksum);
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = rnode_set_checksum(rn, checksum);
|
|
}
|
|
|
|
if (!err_code) {
|
|
signature = rnode_generate_signature(rn, priv_key);
|
|
}
|
|
|
|
if (signature != NULL) {
|
|
err_code = rnode_set_signature(rn, signature);
|
|
}
|
|
|
|
if (!err_code) {
|
|
OPENSSL_free(signature);
|
|
err_code = rnode_set_lock(rn);
|
|
}
|
|
}
|
|
} else {
|
|
err_code = -1;
|
|
}
|
|
|
|
if (!err_code) {
|
|
sleep_ms(750);
|
|
err_code = rnode_set_fw_hash(rn, hash);
|
|
} else {
|
|
return err_code;
|
|
}
|
|
|
|
|
|
if (!err_code) {
|
|
log_info("Waiting 10 seconds for RNode to come online...");
|
|
sleep_ms(10000);
|
|
if (rn->platform == PLATFORM_NRF52) {
|
|
err_code = close_port(rn->fd);
|
|
// Due to the current janky emulated EEPROM implementation for the
|
|
// RAK4631, extra time must be given to allow for writing.
|
|
log_info("Waiting an extra 15 seconds for RNode to come online...");
|
|
sleep_ms(15000);
|
|
} else if (rn->platform == PLATFORM_ESP32) {
|
|
rnode_reset(rn);
|
|
err_code = close_port(rn->fd);
|
|
log_info("Waiting for ESP32 reset...");
|
|
sleep_ms(7000);
|
|
}
|
|
err_code = rnode_init(rn, rn->port, rn->baud, true, true);
|
|
} else {
|
|
return err_code;
|
|
}
|
|
|
|
if (!err_code) {
|
|
err_code = rnode_verify_eeprom(rn);
|
|
}
|
|
|
|
return err_code;
|
|
}
|
|
|
|
int rnode_disconnect(struct RNode* rn) {
|
|
uint8_t tx_buf[4];
|
|
memcpy(tx_buf, (uint8_t[4]){FEND, CMD_LEAVE, 0xFF, FEND}, 4*sizeof(uint8_t));
|
|
return write_port(rn->fd, tx_buf, 4);
|
|
}
|
|
|
|
int rnode_cleanup(struct RNode* rn) {
|
|
close_port(rn->fd);
|
|
}
|