MicroAPRS/bertos/net/pocketbus.c

292 lines
7.6 KiB
C

/**
* \file
* <!--
* This file is part of BeRTOS.
*
* Bertos 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* As a special exception, you may use this file as part of a free software
* library without restriction. Specifically, if other files instantiate
* templates or use macros or inline functions from this file, or you compile
* this file and link it with other files to produce an executable, this
* file does not by itself cause the resulting executable to be covered by
* the GNU General Public License. This exception does not however
* invalidate any other reasons why the executable file might be covered by
* the GNU General Public License.
*
* Copyright 2007 Develer S.r.l. (http://www.develer.com/)
* -->
*
* \brief pocketBus protocol implementation.
*
* pocketBus protocol is a simple strictly master-slave protocol, usable
* in embedded systems.
* pocketBus frame is as follows:
* <pre>
* +----------------------------------------+
* | STX | VER | ADDR | PAYLOAD | CKS | ETX |
* +----------------------------------------+
* | | | | | | |
* + 1B + 1B + 2B + N Byte + 2B + 1B +
* </pre>
*
* - STX, 1 byte (0x02), packet start
* - VER, 1 byte, packet version
* - ADDR, 2 byte, slave address
* - PAYLOAD, N byte, data field
* - CKS, 2 byte, checksum
* - ETX, 1 byte, (0x03) packet end
*
* Protocol parsing start on STX reception. When the receiving routine
* finds an STX char, it starts to read characters from the bus
* until an ETX is received. Once a packet is received,
* the parser checks packet correctness and checksum. If all is OK
* the payload is returned.
*
* STX (0x02), ETX(0x03) and ESC(0x1B) are special characters and cannot be
* transmitted inside payload without escaping them.
* To escape a character you must precede it by the ESC char.
* E.G. STX -> ESC + STX
* ETX -> ESC + ETX
* ESC -> ESC + ESC
*
* In the ADDR field is always specified the slave address.
* In the case of master trasmitting, ADDR contains the slave destination
* address.
* In case of slave replying, ADDR contains the slave address itself.
* Thus, the master device does not have an address. Packet must be routed to
* master by hardware bus design.
*
* The checksum algorithm used is rotating hash algortihm, quite simple but more
* reliable than simple checksum.
* The checksum in computed on all fields excluding STX, ETX and CHK fields itself.
* Checksum is computed on the packet *before* escaping.
* Escape sequence counts for 1 character only (the escaped one).
*/
#include "pocketbus.h"
#include "cfg/cfg_pocketbus.h"
// Define logging setting (for cfg/log.h module).
#define LOG_LEVEL POCKETBUS_LOG_LEVEL
#define LOG_VERBOSITY POCKETBUS_LOG_FORMAT
#include <cfg/log.h>
#include <cfg/debug.h>
#include <cfg/macros.h>
#include <io/kfile.h>
#include <cpu/byteorder.h>
#include <string.h>
/**
* Send a character over pocketBus channel stream, handling escape mode.
*/
void pocketbus_putchar(struct PocketBusCtx *ctx, uint8_t c)
{
/* Update checksum */
rotating_update1(c, &ctx->out_cks);
/* Escape characters with special meaning */
if (c == POCKETBUS_ESC || c == POCKETBUS_STX || c == POCKETBUS_ETX)
kfile_putc(POCKETBUS_ESC, ctx->fd);
kfile_putc(c, ctx->fd);
}
/**
* Send pocketBus packet header.
*/
void pocketbus_begin(struct PocketBusCtx *ctx, pocketbus_addr_t addr)
{
PocketBusHdr hdr;
hdr.ver = POCKETBUS_VER;
hdr.addr = cpu_to_be16(addr);
rotating_init(&ctx->out_cks);
/* Send STX */
kfile_putc(POCKETBUS_STX, ctx->fd);
/* Send header */
pocketbus_write(ctx, &hdr, sizeof(hdr));
}
/**
* Send buffer \a _data over bus, handling escape.
*/
void pocketbus_write(struct PocketBusCtx *ctx, const void *_data, size_t len)
{
const uint8_t *data = (const uint8_t *)_data;
while (len--)
pocketbus_putchar(ctx, *data++);
}
/**
* Send pocketBus packet tail.
*/
void pocketbus_end(struct PocketBusCtx *ctx)
{
/* Send checksum */
rotating_t cks = cpu_to_be16(ctx->out_cks);
pocketbus_write(ctx, &cks, sizeof(cks));
/* Send ETX */
kfile_putc(POCKETBUS_ETX, ctx->fd);
}
/**
* Send buffer of \a data to address \a addr with a pocketBus packet over channel stream.
*/
void pocketbus_send(struct PocketBusCtx *ctx, pocketbus_addr_t addr, const void *data, size_t len)
{
pocketbus_begin(ctx, addr);
/* Send data */
pocketbus_write(ctx, data, len);
pocketbus_end(ctx);
}
/**
* Try to read a packet from the pocketBus.
* \return true if a packet is received, false otherwise.
*/
bool pocketbus_recv(struct PocketBusCtx *ctx, struct PocketMsg *msg)
{
int c;
/* Process incoming characters until buffer is not empty */
while ((c = kfile_getc(ctx->fd)) != EOF)
{
/* Look for STX char */
if (c == POCKETBUS_STX && !ctx->escape)
{
/* When an STX is found, inconditionally start a new packet */
if (ctx->sync)
kprintf("pocketBus double sync!\n");
ctx->sync = true;
ctx->len = 0;
rotating_init(&ctx->in_cks);
continue;
}
if (ctx->sync)
{
/* Handle escape mode */
if (c == POCKETBUS_ESC && !ctx->escape)
{
ctx->escape = true;
continue;
}
/* Handle message end */
if (c == POCKETBUS_ETX && !ctx->escape)
{
ctx->sync = false;
/* Check minimum size */
if (ctx->len < sizeof(PocketBusHdr) + sizeof(rotating_t))
{
kprintf("pocketBus short pkt!\n");
continue;
}
/* Remove checksum bytes from packet len */
ctx->len -= sizeof(rotating_t);
/* Compute checksum */
rotating_update(ctx->buf, ctx->len, &ctx->in_cks);
uint8_t cks_h = *(ctx->buf + ctx->len);
uint8_t cks_l = *(ctx->buf + ctx->len + 1);
rotating_t recv_cks = (cks_h << 8) | cks_l;
/* Checksum check */
if (recv_cks == ctx->in_cks)
{
PocketBusHdr *hdr = (PocketBusHdr *)ctx;
/* Check packet version */
if (hdr->ver == POCKETBUS_VER)
{
/* Packet received, set msg fields */
msg->payload = ctx->buf + sizeof(PocketBusHdr);
msg->addr = be16_to_cpu(hdr->addr);
msg->len = ctx->len - sizeof(PocketBusHdr);
msg->ctx = ctx;
return true;
}
else
{
kprintf("pocketBus version mismatch, here[%d], there[%d]\n", POCKETBUS_VER, hdr->ver);
continue;
}
}
else
{
kprintf("pocketBus cks error, here[%04X], there[%04X]\n", ctx->in_cks, recv_cks);
continue;
}
}
ctx->escape = false;
/* Check buffer overflow: simply ignore
received data and go to unsynced state. */
if (ctx->len >= CONFIG_POCKETBUS_BUFLEN)
{
kprintf("pocketBus buffer overflow\n");
ctx->sync = false;
continue;
}
/* Put received data in the buffer */
ctx->buf[ctx->len] = c;
ctx->len++;
}
}
/*
* Check stream status.
*/
if (kfile_error(ctx->fd))
{
LOG_ERR("fd status[%04X]\n", kfile_error(ctx->fd));
kfile_clearerr(ctx->fd);
}
return false;
}
/**
* Initialize pocketBus protocol handler.
*/
void pocketbus_init(struct PocketBusCtx *ctx, struct KFile *fd)
{
ASSERT(ctx);
ASSERT(fd);
memset(ctx, 0, sizeof(*ctx));
ctx->fd = fd;
}