|
|
|
#include "esp8266.h"
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
#include "sbmp_config.h"
|
|
|
|
#include "sbmp_logging.h"
|
|
|
|
#include "sbmp_checksum.h"
|
|
|
|
#include "sbmp_frame.h"
|
|
|
|
|
|
|
|
// protos
|
|
|
|
static void call_frame_rx_callback(SBMP_FrmInst *frm);
|
|
|
|
|
|
|
|
|
|
|
|
/** Allocate the state struct & init all fields */
|
|
|
|
SBMP_FrmInst FLASH_FN *sbmp_frm_init(
|
|
|
|
SBMP_FrmInst *frm,
|
|
|
|
uint8_t *buffer,
|
|
|
|
uint16_t buffer_size,
|
|
|
|
void (*rx_handler)(uint8_t *, uint16_t, void *),
|
|
|
|
void (*tx_func)(uint8_t))
|
|
|
|
{
|
|
|
|
|
|
|
|
#if SBMP_MALLOC
|
|
|
|
|
|
|
|
if (frm == NULL) {
|
|
|
|
// caller wants us to allocate it
|
|
|
|
frm = malloc(sizeof(SBMP_FrmInst));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buffer == NULL) {
|
|
|
|
// caller wants us to allocate it
|
|
|
|
buffer = malloc(buffer_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
if (frm == NULL || buffer == NULL) {
|
|
|
|
return NULL; // malloc not enabled, fail
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
frm->rx_handler = rx_handler;
|
|
|
|
|
|
|
|
frm->rx_buffer = buffer;
|
|
|
|
frm->rx_buffer_cap = buffer_size;
|
|
|
|
|
|
|
|
frm->user_token = NULL; // NULL if not set
|
|
|
|
|
|
|
|
frm->tx_func = tx_func;
|
|
|
|
|
|
|
|
frm->rx_enabled = false;
|
|
|
|
frm->tx_enabled = false;
|
|
|
|
|
|
|
|
sbmp_frm_reset(frm);
|
|
|
|
|
|
|
|
return frm;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Reset the internal state */
|
|
|
|
void FLASH_FN sbmp_frm_reset(SBMP_FrmInst *frm)
|
|
|
|
{
|
|
|
|
sbmp_frm_reset_rx(frm);
|
|
|
|
sbmp_frm_reset_tx(frm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Enable or disable Rx */
|
|
|
|
void FLASH_FN sbmp_frm_enable_rx(SBMP_FrmInst *frm, bool enable)
|
|
|
|
{
|
|
|
|
frm->rx_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Enable or disable Tx */
|
|
|
|
void FLASH_FN sbmp_frm_enable_tx(SBMP_FrmInst *frm, bool enable)
|
|
|
|
{
|
|
|
|
frm->tx_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Enable or disable both Rx and Tx */
|
|
|
|
void FLASH_FN sbmp_frm_enable(SBMP_FrmInst *frm, bool enable)
|
|
|
|
{
|
|
|
|
sbmp_frm_enable_rx(frm, enable);
|
|
|
|
sbmp_frm_enable_tx(frm, enable);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set user token */
|
|
|
|
void FLASH_FN sbmp_frm_set_user_token(SBMP_FrmInst *frm, void *token)
|
|
|
|
{
|
|
|
|
frm->user_token = token;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Reset the receiver state */
|
|
|
|
void FLASH_FN sbmp_frm_reset_rx(SBMP_FrmInst *frm)
|
|
|
|
{
|
|
|
|
frm->rx_buffer_i = 0;
|
|
|
|
frm->rx_length = 0;
|
|
|
|
frm->mb_buf = 0;
|
|
|
|
frm->mb_cnt = 0;
|
|
|
|
frm->rx_hdr_xor = 0;
|
|
|
|
frm->rx_cksum_scratch = 0;
|
|
|
|
frm->rx_cksum_type = SBMP_CKSUM_NONE;
|
|
|
|
frm->rx_status = FRM_STATE_IDLE;
|
|
|
|
// printf("---- RX RESET STATE ----\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Reset the transmitter state */
|
|
|
|
void FLASH_FN sbmp_frm_reset_tx(SBMP_FrmInst *frm)
|
|
|
|
{
|
|
|
|
frm->tx_status = FRM_STATE_IDLE;
|
|
|
|
frm->tx_remain = 0;
|
|
|
|
frm->tx_cksum_scratch = 0;
|
|
|
|
frm->tx_cksum_type = SBMP_CKSUM_NONE;
|
|
|
|
// printf("---- TX RESET STATE ----\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Update a header XOR */
|
|
|
|
static inline FLASH_FN
|
|
|
|
void hdrxor_update(SBMP_FrmInst *frm, uint8_t rxbyte)
|
|
|
|
{
|
|
|
|
frm->rx_hdr_xor ^= rxbyte;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check header xor against received value */
|
|
|
|
static inline FLASH_FN
|
|
|
|
bool hdrxor_verify(SBMP_FrmInst *frm, uint8_t rx_xor)
|
|
|
|
{
|
|
|
|
return frm->rx_hdr_xor == rx_xor;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Append a byte to the rx buffer */
|
|
|
|
static inline FLASH_FN
|
|
|
|
void append_rx_byte(SBMP_FrmInst *frm, uint8_t b)
|
|
|
|
{
|
|
|
|
frm->rx_buffer[frm->rx_buffer_i++] = b;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set n-th byte (0 = LSM) to a value */
|
|
|
|
static inline
|
|
|
|
void FLASH_FN set_byte(uint32_t *acc, uint8_t pos, uint8_t byte)
|
|
|
|
{
|
|
|
|
*acc |= (uint32_t)(byte << (pos * 8));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call the message handler with the payload.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void FLASH_FN call_frame_rx_callback(SBMP_FrmInst *frm)
|
|
|
|
{
|
|
|
|
if (frm->rx_handler == NULL) {
|
|
|
|
sbmp_error("frame_handler is null!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->rx_status = FRM_STATE_WAIT_HANDLER;
|
|
|
|
frm->rx_handler(frm->rx_buffer, frm->rx_length, frm->user_token);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Receive a byte
|
|
|
|
*
|
|
|
|
* SOF 8 | CKSUM_TYPE 8 | LEN 16 | PAYLOAD | CKSUM 0/4
|
|
|
|
*
|
|
|
|
* @param state
|
|
|
|
* @param rxbyte
|
|
|
|
* @return status
|
|
|
|
*/
|
|
|
|
SBMP_RxStatus FLASH_FN sbmp_frm_receive(SBMP_FrmInst *frm, uint8_t rxbyte)
|
|
|
|
{
|
|
|
|
if (! frm->rx_enabled) {
|
|
|
|
return SBMP_RX_DISABLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
SBMP_RxStatus retval = SBMP_RX_OK;
|
|
|
|
|
|
|
|
switch (frm->rx_status) {
|
|
|
|
case FRM_STATE_WAIT_HANDLER:
|
|
|
|
retval = SBMP_RX_BUSY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_IDLE:
|
|
|
|
// can be first byte of a packet
|
|
|
|
|
|
|
|
if (rxbyte == 0x01) { // start byte
|
|
|
|
|
|
|
|
hdrxor_update(frm, rxbyte);
|
|
|
|
|
|
|
|
frm->rx_status = FRM_STATE_CKSUM_TYPE;
|
|
|
|
} else {
|
|
|
|
// bad char
|
|
|
|
retval = SBMP_RX_INVALID;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_CKSUM_TYPE:
|
|
|
|
frm->rx_cksum_type = rxbyte; // checksum type received
|
|
|
|
|
|
|
|
hdrxor_update(frm, rxbyte);
|
|
|
|
|
|
|
|
// next will be length
|
|
|
|
frm->rx_status = FRM_STATE_LENGTH;
|
|
|
|
// clear MB for 2-byte length
|
|
|
|
frm->mb_buf = 0;
|
|
|
|
frm->mb_cnt = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_LENGTH:
|
|
|
|
// append to the multi-byte buffer
|
|
|
|
set_byte(&frm->mb_buf, frm->mb_cnt++, rxbyte);
|
|
|
|
|
|
|
|
hdrxor_update(frm, rxbyte);
|
|
|
|
|
|
|
|
// if last of the MB field
|
|
|
|
if (frm->mb_cnt == 2) {
|
|
|
|
|
|
|
|
// next will be the payload
|
|
|
|
uint16_t len = (uint16_t) frm->mb_buf;
|
|
|
|
frm->rx_length = len;
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
sbmp_error("Rx packet with no payload!");
|
|
|
|
sbmp_frm_reset_rx(frm); // abort
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->rx_status = FRM_STATE_HDRXOR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_HDRXOR:
|
|
|
|
if (! hdrxor_verify(frm, rxbyte)) {
|
|
|
|
sbmp_error("Header XOR mismatch!");
|
|
|
|
sbmp_frm_reset_rx(frm); // abort
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if not too long
|
|
|
|
if (frm->rx_length > frm->rx_buffer_cap) {
|
|
|
|
sbmp_error("Rx packet too long - %d!", (uint16_t)frm->rx_length);
|
|
|
|
// discard the rest + checksum
|
|
|
|
frm->rx_status = FRM_STATE_DISCARD;
|
|
|
|
frm->rx_length += chksum_length(frm->rx_cksum_type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->rx_status = FRM_STATE_PAYLOAD;
|
|
|
|
cksum_begin(frm->rx_cksum_type, &frm->rx_cksum_scratch);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_DISCARD:
|
|
|
|
frm->rx_buffer_i++;
|
|
|
|
if (frm->rx_buffer_i == frm->rx_length) {
|
|
|
|
// done
|
|
|
|
sbmp_frm_reset_rx(frm); // go IDLE
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_PAYLOAD:
|
|
|
|
append_rx_byte(frm, rxbyte);
|
|
|
|
cksum_update(frm->rx_cksum_type, &frm->rx_cksum_scratch, rxbyte);
|
|
|
|
|
|
|
|
if (frm->rx_buffer_i == frm->rx_length) {
|
|
|
|
// payload rx complete
|
|
|
|
|
|
|
|
if (frm->rx_cksum_type != SBMP_CKSUM_NONE) {
|
|
|
|
// receive the checksum
|
|
|
|
frm->rx_status = FRM_STATE_CKSUM;
|
|
|
|
|
|
|
|
// clear MB for the 4-byte length
|
|
|
|
frm->mb_buf = 0;
|
|
|
|
frm->mb_cnt = 0;
|
|
|
|
} else {
|
|
|
|
// no checksum
|
|
|
|
// fire the callback
|
|
|
|
call_frame_rx_callback(frm);
|
|
|
|
|
|
|
|
// clear
|
|
|
|
sbmp_frm_reset_rx(frm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FRM_STATE_CKSUM:
|
|
|
|
// append to the multi-byte buffer
|
|
|
|
set_byte(&frm->mb_buf, frm->mb_cnt++, rxbyte);
|
|
|
|
|
|
|
|
// if last of the MB field
|
|
|
|
if (frm->mb_cnt == chksum_length(frm->rx_cksum_type)) {
|
|
|
|
|
|
|
|
if (cksum_verify(frm->rx_cksum_type, &frm->rx_cksum_scratch, frm->mb_buf)) {
|
|
|
|
call_frame_rx_callback(frm);
|
|
|
|
} else {
|
|
|
|
sbmp_error("Rx checksum mismatch!");
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear, enter IDLE
|
|
|
|
sbmp_frm_reset_rx(frm);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Send a frame header */
|
|
|
|
bool FLASH_FN sbmp_frm_start(SBMP_FrmInst *frm, SBMP_CksumType cksum_type, uint16_t length)
|
|
|
|
{
|
|
|
|
if (! frm->tx_enabled) {
|
|
|
|
sbmp_error("Can't tx, not enabled.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frm->tx_status != FRM_STATE_IDLE) {
|
|
|
|
sbmp_error("Can't tx, busy.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frm->tx_func == NULL) {
|
|
|
|
sbmp_error("Can't tx, no tx func!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cksum_type == SBMP_CKSUM_CRC32 && !SBMP_HAS_CRC32) {
|
|
|
|
sbmp_error("CRC32 disabled, using XOR for Tx.");
|
|
|
|
cksum_type = SBMP_CKSUM_XOR;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbmp_frm_reset_tx(frm);
|
|
|
|
|
|
|
|
frm->tx_cksum_type = cksum_type;
|
|
|
|
frm->tx_remain = length;
|
|
|
|
frm->tx_status = FRM_STATE_PAYLOAD;
|
|
|
|
|
|
|
|
// Send the header
|
|
|
|
|
|
|
|
uint16_t len = (uint16_t) length;
|
|
|
|
|
|
|
|
uint8_t hdr[4] = {
|
|
|
|
0x01,
|
|
|
|
cksum_type,
|
|
|
|
len & 0xFF,
|
|
|
|
(len >> 8) & 0xFF
|
|
|
|
};
|
|
|
|
|
|
|
|
uint8_t hdr_xor = 0;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
hdr_xor ^= hdr[i];
|
|
|
|
frm->tx_func(hdr[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->tx_func(hdr_xor);
|
|
|
|
|
|
|
|
cksum_begin(frm->tx_cksum_type, &frm->tx_cksum_scratch);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** End frame and enter idle mode */
|
|
|
|
static void FLASH_FN end_frame(SBMP_FrmInst *frm)
|
|
|
|
{
|
|
|
|
if (!frm->tx_enabled) {
|
|
|
|
sbmp_error("Can't tx, not enabled.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cksum_end(frm->tx_cksum_type, &frm->tx_cksum_scratch);
|
|
|
|
|
|
|
|
uint32_t cksum = frm->tx_cksum_scratch;
|
|
|
|
|
|
|
|
switch (frm->tx_cksum_type) {
|
|
|
|
case SBMP_CKSUM_NONE:
|
|
|
|
break; // do nothing
|
|
|
|
|
|
|
|
case SBMP_CKSUM_XOR:
|
|
|
|
// 1-byte checksum
|
|
|
|
frm->tx_func(cksum & 0xFF);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SBMP_CKSUM_CRC32:
|
|
|
|
frm->tx_func(cksum & 0xFF);
|
|
|
|
frm->tx_func((cksum >> 8) & 0xFF);
|
|
|
|
frm->tx_func((cksum >> 16) & 0xFF);
|
|
|
|
frm->tx_func((cksum >> 24) & 0xFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->tx_status = FRM_STATE_IDLE; // tx done
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Send a byte in the currently open frame */
|
|
|
|
bool FLASH_FN sbmp_frm_send_byte(SBMP_FrmInst *frm, uint8_t byte)
|
|
|
|
{
|
|
|
|
if (!frm->tx_enabled) {
|
|
|
|
sbmp_error("Can't tx, not enabled.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frm->tx_remain == 0 || frm->tx_status != FRM_STATE_PAYLOAD) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
frm->tx_func(byte);
|
|
|
|
cksum_update(frm->tx_cksum_type, &frm->tx_cksum_scratch, byte);
|
|
|
|
frm->tx_remain--;
|
|
|
|
|
|
|
|
if (frm->tx_remain == 0) {
|
|
|
|
end_frame(frm); // checksum & go idle
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Send a buffer in the currently open frame */
|
|
|
|
uint16_t FLASH_FN sbmp_frm_send_buffer(SBMP_FrmInst *frm, const uint8_t *buffer, uint16_t length)
|
|
|
|
{
|
|
|
|
if (! frm->tx_enabled) {
|
|
|
|
sbmp_error("Can't tx, not enabled.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frm->tx_status != FRM_STATE_PAYLOAD) {
|
|
|
|
return false; // invalid call
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length == 0) {
|
|
|
|
end_frame(frm); // checksum & go idle
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frm->tx_remain == 0) {
|
|
|
|
return false; // write past EOF (this shouldn't happen)
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t remain = length;
|
|
|
|
while (frm->tx_status == FRM_STATE_PAYLOAD && remain-- > 0) {
|
|
|
|
if (! sbmp_frm_send_byte(frm, *buffer++)) {
|
|
|
|
remain++; // "push back"
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (length - remain);
|
|
|
|
}
|