You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
13 KiB
420 lines
13 KiB
#include "flserial.h"
|
|
#include "native_libs/include/serial/serial.h"
|
|
#include "native_libs/include/tinycthread.h"
|
|
#include "native_libs/include/ringbuffer.h"
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
// Offband (#702/#710): the 8 KB ring overwrote the oldest bytes whenever the
|
|
// radio sent faster than Dart drained it, so a 52 KB contact sync arrived
|
|
// mostly lost. The ring is now 256 KB (a power of two, as the ring's mask
|
|
// needs), the thread never reads more than the ring has room for, and Dart is
|
|
// told about data during a burst rather than only when the port goes idle.
|
|
#define FL_SERIAL_BUFF_LEN (256 * 1024)
|
|
#define FL_SERIAL_READ_CHUNK (8 * 1024)
|
|
#define FL_NOTIFY_BYTES (4 * 1024)
|
|
#define FL_NOTIFY_INTERVAL std::chrono::milliseconds(10)
|
|
|
|
typedef struct _flserial_
|
|
{
|
|
char portname[MAX_PORT_NAME_LEN + 1];
|
|
int baudrate;
|
|
serial::Serial *serialport;
|
|
enum FlError lasterror;
|
|
char lasterrormsg[512];
|
|
thrd_t cthread;
|
|
int breakThread;
|
|
ring_buffer_t cinfifo;
|
|
ring_buffer_t coutfifo;
|
|
char inbuff[FL_SERIAL_BUFF_LEN];
|
|
char outbuff[FL_SERIAL_BUFF_LEN];
|
|
mtx_t inFifo_mutex;
|
|
mtx_t outFifo_mutex;
|
|
flcallback callback;
|
|
} FlSerial;
|
|
|
|
int SerialThread(void *aArg)
|
|
{
|
|
FlSerial *serial = (FlSerial *)aArg;
|
|
ring_buffer_size_t res = 0;
|
|
uint8_t buff[FL_SERIAL_READ_CHUNK];
|
|
ring_buffer_size_t len = sizeof(buff);
|
|
ring_buffer_size_t total = 0;
|
|
auto lastNotify = std::chrono::steady_clock::now();
|
|
|
|
while (!serial->breakThread)
|
|
{
|
|
try
|
|
{
|
|
// Read only what the ring can hold. When it is full, leave the
|
|
// bytes in the OS driver: USB flow control holds the sender, and
|
|
// nothing is overwritten.
|
|
mtx_lock(&serial->inFifo_mutex);
|
|
ring_buffer_size_t room = RING_BUFFER_MASK((&serial->cinfifo)) -
|
|
ring_buffer_num_items(&serial->cinfifo);
|
|
mtx_unlock(&serial->inFifo_mutex);
|
|
|
|
res = 0;
|
|
if (room > 0)
|
|
{
|
|
res = (int)serial->serialport->read(
|
|
(uint8_t *)buff, (size_t)(std::min)(len, room));
|
|
}
|
|
if (res > 0)
|
|
{
|
|
mtx_lock(&serial->inFifo_mutex);
|
|
ring_buffer_queue_arr(&serial->cinfifo, (const char *)buff, res);
|
|
mtx_unlock(&serial->inFifo_mutex);
|
|
total += res;
|
|
}
|
|
|
|
bool idle = !serial->serialport->available();
|
|
auto now = std::chrono::steady_clock::now();
|
|
if (total > 0 &&
|
|
(idle || room == 0 || total >= FL_NOTIFY_BYTES ||
|
|
now - lastNotify >= FL_NOTIFY_INTERVAL))
|
|
{
|
|
serial->callback(0, (unsigned int)total);
|
|
total = 0;
|
|
lastNotify = now;
|
|
}
|
|
|
|
if (room == 0)
|
|
{
|
|
// Give Dart time to drain before trying again.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
else if (res > 0 && !idle)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mtx_lock(&serial->outFifo_mutex);
|
|
res = (ring_buffer_size_t)ring_buffer_dequeue_arr(&serial->coutfifo, (char *)buff, (int)len);
|
|
mtx_unlock(&serial->outFifo_mutex);
|
|
if (res > 0) {
|
|
res = (int)serial->serialport->write((uint8_t*)buff, (size_t)res);
|
|
}
|
|
|
|
|
|
}
|
|
catch (const serial::IOException &ioe)
|
|
{
|
|
serial->lasterror = FL_ERROR_IO;
|
|
strncpy(serial->lasterrormsg , ioe.what(), sizeof(serial->lasterrormsg) - 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FlSerial *flserial_tab[MAX_PORT_COUNT];
|
|
int flserial_count;
|
|
int current_port;
|
|
|
|
/// Internal helper: close a single slot and null it out.
|
|
/// Safe to call with a NULL entry — returns immediately.
|
|
/// Sets breakThread=1 then calls thrd_join() so the SerialThread is
|
|
/// guaranteed to have exited before this function returns. This is the
|
|
/// critical ordering that prevents the Dart NativeCallable from being
|
|
/// invoked after the isolate has freed it ("Callback invoked after it has
|
|
/// been deleted").
|
|
static void _fl_close_slot(int slot)
|
|
{
|
|
FlSerial *port = flserial_tab[slot];
|
|
if (port == NULL)
|
|
return;
|
|
|
|
port->breakThread = 1;
|
|
|
|
try
|
|
{
|
|
if (port->cthread != 0)
|
|
thrd_join(port->cthread, NULL);
|
|
|
|
port->serialport->close();
|
|
|
|
mtx_destroy(&port->inFifo_mutex);
|
|
mtx_destroy(&port->outFifo_mutex);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
}
|
|
|
|
delete port->serialport;
|
|
port->serialport = NULL;
|
|
delete port;
|
|
flserial_tab[slot] = NULL;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_set_callback(int flh, flcallback cb)
|
|
{
|
|
FlSerial *port = flserial_tab[flh];
|
|
port->callback = cb;
|
|
return 0;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_init(int portCount)
|
|
{
|
|
if (portCount > MAX_PORT_COUNT)
|
|
return 1;
|
|
|
|
flserial_count = portCount;
|
|
current_port = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_open(int flh, char *portname, int baudrate)
|
|
{
|
|
int porth = flh;
|
|
|
|
if (porth < 0)
|
|
porth = ++current_port;
|
|
|
|
// If a previous FlSerial (and its SerialThread) is alive in this slot,
|
|
// join and free it before overwriting. On Dart hot restart the isolate
|
|
// dies without calling fl_close(), leaving the old thread running with a
|
|
// dangling NativeCallable pointer. Joining here prevents the crash.
|
|
_fl_close_slot(porth);
|
|
|
|
FlSerial *port = new FlSerial();
|
|
port->breakThread = 0;
|
|
// Before anything can fail or start the reader thread: the thread locks
|
|
// inFifo_mutex on its first pass, and _fl_close_slot destroys both even
|
|
// when open() threw.
|
|
mtx_init(&port->inFifo_mutex, mtx_plain);
|
|
mtx_init(&port->outFifo_mutex, mtx_plain);
|
|
|
|
flserial_tab[porth] = port;
|
|
|
|
strncpy(port->portname, portname, MAX_PORT_NAME_LEN);
|
|
|
|
port->baudrate = baudrate;
|
|
port->lasterror = FL_ERROR_OK;
|
|
|
|
port->serialport = new serial::Serial();
|
|
#if !defined(__linux__) && !defined(__APPLE__)
|
|
port->serialport->setTimeout(serial::Timeout(0, 2, 0, 0, 0));
|
|
#endif // #if defined(__linux__)
|
|
port->serialport->setPort(portname);
|
|
port->serialport->setBaudrate(baudrate);
|
|
|
|
try
|
|
{
|
|
port->serialport->open();
|
|
|
|
ring_buffer_init(&port->cinfifo, port->inbuff, FL_SERIAL_BUFF_LEN );
|
|
ring_buffer_init(&port->coutfifo, port->outbuff, FL_SERIAL_BUFF_LEN );
|
|
|
|
port->serialport->setBytesize(serial::bytesize_t::eightbits);
|
|
port->serialport->setParity(serial::parity_t::parity_none);
|
|
port->serialport->setStopbits(serial::stopbits_one);
|
|
thrd_create(&port->cthread, SerialThread, (void *)port);
|
|
}
|
|
catch (const serial::IOException &ioe)
|
|
{
|
|
strncpy(port->lasterrormsg, ioe.what(), sizeof(port->lasterrormsg) - 1);
|
|
|
|
switch (ioe.getErrorNumber())
|
|
{
|
|
case 1:
|
|
port->lasterror = FlError::FL_ERROR_PORT_NOT_EXIST;
|
|
break;
|
|
case 2:
|
|
port->lasterror = FlError::FL_ERROR_PORT_ALLREADY_OPEN;
|
|
break;
|
|
default:
|
|
port->lasterror = FlError::FL_ERROR_UNKNOW;
|
|
break;
|
|
}
|
|
}
|
|
catch (const std::exception)
|
|
{
|
|
port->lasterror = FlError::FL_ERROR_UNKNOW;
|
|
}
|
|
|
|
return porth;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_ports(int index, int buffsize, char *buff)
|
|
{
|
|
auto list = serial::list_ports();
|
|
if (serial::list_ports().size() <= index)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
serial::PortInfo info = list[index];
|
|
return snprintf(buff, buffsize, "%s - %s - %s", info.port.c_str(), info.description.c_str(), info.hardware_id.c_str());
|
|
}
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_read(int flh, int len, char *buff)
|
|
{
|
|
FlSerial *port = flserial_tab[flh];
|
|
ring_buffer_size_t res = 0;
|
|
|
|
char *buf = NULL;
|
|
ring_buffer_size_t size = 0;
|
|
ring_buffer_size_t read = 0;
|
|
|
|
mtx_lock(&port->inFifo_mutex);
|
|
read = ring_buffer_dequeue_arr(&port->cinfifo, buff, (ring_buffer_size_t) len);
|
|
mtx_unlock(&port->inFifo_mutex);
|
|
|
|
return (int)read;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_write(int flh, int len, char *data)
|
|
{
|
|
FlSerial *port = flserial_tab[flh];
|
|
int res = 0;
|
|
try
|
|
{
|
|
mtx_lock(&port->outFifo_mutex);
|
|
ring_buffer_queue_arr(&port->coutfifo, data, len);
|
|
mtx_unlock(&port->outFifo_mutex);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
port->lasterror = FlError::FL_ERROR_PORT_ALLREADY_OPEN;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_close(int flh)
|
|
{
|
|
_fl_close_slot(flh);
|
|
return 0;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_ctrl(int flh, FlCtrl param, int value)
|
|
{
|
|
|
|
int result = -1;
|
|
FlSerial *port = flserial_tab[flh];
|
|
|
|
try
|
|
{
|
|
switch (param)
|
|
{
|
|
case FL_CTRL_IS_PORT_OPEN:
|
|
result = port->serialport->isOpen() ? 1 : 0;
|
|
break;
|
|
case FL_CTRL_LAST_ERROR:
|
|
result = port->lasterror;
|
|
break;
|
|
case FL_CTRL_BREAK:
|
|
port->serialport->setBreak();
|
|
result = FL_ERROR_OK;
|
|
break;
|
|
case FL_CTRL_SET_RTS:
|
|
port->serialport->setRTS(value > 0 ? true : false);
|
|
result = FL_ERROR_OK;
|
|
break;
|
|
case FL_CTRL_GET_CTS:
|
|
result = port->serialport->getCTS() ? 1 : 0;
|
|
break;
|
|
case FL_CTRL_SET_DTR:
|
|
port->serialport->setDTR(value > 0 ? true : false);
|
|
result = FL_ERROR_OK;
|
|
break;
|
|
case FL_CTRL_GET_DSR:
|
|
result = port->serialport->getDSR() ? 1 : 0;
|
|
break;
|
|
|
|
case FL_CTRL_SET_BYTESIZE_5:
|
|
port->serialport->setBytesize(serial::bytesize_t::fivebits);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_BYTESIZE_6:
|
|
port->serialport->setBytesize(serial::bytesize_t::sixbits);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_BYTESIZE_7:
|
|
port->serialport->setBytesize(serial::bytesize_t::sevenbits);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_BYTESIZE_8:
|
|
port->serialport->setBytesize(serial::bytesize_t::eightbits);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_PARITY_NONE:
|
|
port->serialport->setParity(serial::parity_t::parity_none);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_PARITY_ODD:
|
|
port->serialport->setParity(serial::parity_t::parity_odd);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_PARITY_EVEN:
|
|
port->serialport->setParity(serial::parity_t::parity_even);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_PARITY_MARK:
|
|
port->serialport->setParity(serial::parity_t::parity_mark);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_PARITY_SPACE:
|
|
port->serialport->setParity(serial::parity_t::parity_space);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_STOPBITS_ONE:
|
|
port->serialport->setStopbits(serial::stopbits_t::stopbits_one);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_STOPBITS_TWO:
|
|
port->serialport->setStopbits(serial::stopbits_t::stopbits_two);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_STOPBITS_ONE_POINT_FIVE:
|
|
port->serialport->setStopbits(serial::stopbits_t::stopbits_one_point_five);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_FLOWCONTROL_NONE:
|
|
port->serialport->setFlowcontrol(serial::flowcontrol_t::flowcontrol_none);
|
|
result = 1;
|
|
break;
|
|
case FL_CTRL_SET_FLOWCONTROL_HARDWARE:
|
|
port->serialport->setFlowcontrol(serial::flowcontrol_t::flowcontrol_hardware);
|
|
result = 1;
|
|
break;
|
|
port->serialport->setFlowcontrol(serial::flowcontrol_t::flowcontrol_software);
|
|
result = 1;
|
|
case FL_CTRL_SET_FLOWCONTROL_SOFTWARE:
|
|
break;
|
|
case FL_CTRL_GET_STATUS_CHANGE:
|
|
port->serialport->waitForChange();
|
|
result = 1;
|
|
break;
|
|
|
|
default:
|
|
result = -1;
|
|
break;
|
|
}
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
port->lasterror = FlError::FL_ERROR_PORT_ALLREADY_OPEN;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FFI_PLUGIN_EXPORT int fl_free(void)
|
|
{
|
|
// Join every live SerialThread before the Dart isolate tears down on hot
|
|
// restart. Without this, threads holding freed NativeCallable pointers
|
|
// crash with "Callback invoked after it has been deleted".
|
|
for (int i = 0; i < MAX_PORT_COUNT; i++)
|
|
_fl_close_slot(i);
|
|
|
|
flserial_count = 0;
|
|
current_port = -1;
|
|
return 0;
|
|
}
|