summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2023-08-24 00:58:23 +0200
committercbdev <cb@cbcdn.com>2023-08-24 00:58:23 +0200
commite9b7a6ef8fc575e0ee4f55ae16169d1917be9dea (patch)
treec99e9698f408f48587360324fa33b66020adb95f
downloadlibtwn-e9b7a6ef8fc575e0ee4f55ae16169d1917be9dea.tar.gz
libtwn-e9b7a6ef8fc575e0ee4f55ae16169d1917be9dea.tar.bz2
libtwn-e9b7a6ef8fc575e0ee4f55ae16169d1917be9dea.zip
Initial commit
-rw-r--r--.gitignore3
-rw-r--r--Makefile21
-rw-r--r--libtwn3.c242
-rw-r--r--libtwn3.h25
-rw-r--r--twn3_test.c45
5 files changed, 336 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..be835db
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.so
+*.swp
+twn3_test
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..100896b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+.PHONY: all clean
+
+OBJS = libtwn3.so libtwn3.debug.so twn3_test
+CFLAGS = -g -Wall -Wpedantic
+
+%.so: CFLAGS += -shared -fPIC
+%.debug.so: CFLAGS += -DDEBUG
+
+%.so :: %.c %.h
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS)
+
+%.debug.so :: %.c %.h
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS)
+
+twn3_test: LDLIBS = -ltwn3.debug
+twn3_test: LDFLAGS = -L. -Wl,-rpath .
+
+all: $(OBJS)
+
+clean:
+ $(RM) $(OBJS)
diff --git a/libtwn3.c b/libtwn3.c
new file mode 100644
index 0000000..e07fbb5
--- /dev/null
+++ b/libtwn3.c
@@ -0,0 +1,242 @@
+#include <stdio.h>
+#include <stdint.h>
+
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#define DEFAULT_NODE 0xFF
+
+#ifdef DEBUG
+ #define DBG(x) (x)
+ static void xxd(uint8_t* buf, size_t len){
+ size_t n;
+ for(n = 0; n < len; n++){
+ printf("%02X ", buf[n]);
+ }
+ }
+#else
+ #define DBG(x)
+#endif
+
+#include "libtwn3.h"
+
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#pragma pack(push, 1)
+typedef struct /*_twn3_hdr_t*/ {
+ uint8_t stx; //start of transmission, fixed 0x02
+ uint8_t station;
+ uint8_t length; //bytes in data segment
+} twn3_hdr_t;
+
+typedef struct /*_twn3_trl_t*/ {
+ uint8_t bcc; //xor over all bytes between stx and etx
+ uint8_t etx; //fixed 0x03
+} twn3_trl_t;
+#pragma pack(pop)
+
+int twn3_open(char* device){
+ int fd = open(device, O_RDWR | O_CLOEXEC);
+ struct termios tty;
+ uint8_t recv_buf[1024];
+ ssize_t bytes;
+
+ if(fd < 0){
+ return -1;
+ }
+
+ if(tcgetattr(fd, &tty)){
+ return -1;
+ }
+
+ cfsetospeed(&tty, B9600);
+ cfsetispeed(&tty, B9600);
+ tty.c_iflag = IGNBRK | IGNPAR;
+ tty.c_oflag = 0;
+ tty.c_cflag |= CS8;
+
+ tty.c_lflag = 0;
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 3;
+
+ if(tcsetattr(fd, TCSAFLUSH, &tty)){
+ return -1;
+ }
+
+ tcflush(fd, TCIOFLUSH);
+
+ //the default config is for the reader to start in continuous read mode
+ //send two cancel commands to return to a known mode, at least one should produce '?'
+ twn3_send_command(fd, 0, (uint8_t*) "..", 2);
+ //then, read until the timeout elapses to clear the buffer.
+ //this is necessary because the default protocol does not have clear message boundaries
+ bytes = twn3_receive_response(fd, RX_FLAG_UNTIL_TIMEOUT, recv_buf, sizeof(recv_buf));
+ if(bytes <= 3 || recv_buf[bytes - 3] != '?'
+ || recv_buf[bytes - 2] != '\r'
+ || recv_buf[bytes - 1] != '\n'){
+ DBG(printf("Invalid initial response (%ld bytes): ", bytes));
+ DBG(xxd(recv_buf, bytes));
+ DBG(printf("\n"));
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+ssize_t twn3_send_command(int fd, uint8_t flags, uint8_t* data, size_t len){
+ size_t n;
+ twn3_hdr_t hdr = {
+ .stx = 0x02,
+ .station = DEFAULT_NODE,
+ .length = len
+ };
+ twn3_trl_t trl = {
+ .etx = 0x03
+ };
+
+ DBG(printf("Out: "));
+ if(flags & TX_FLAG_BINARY){
+ //calculate checksum
+ trl.bcc = hdr.station ^ hdr.length;
+ for(n = 0; n < len; n++){
+ trl.bcc ^= data[n];
+ }
+
+ DBG(xxd((uint8_t*) &hdr, sizeof(hdr)));
+
+ write(fd, &hdr, sizeof(hdr));
+ }
+
+ DBG(xxd(data, len));
+ write(fd, data, len);
+
+ if(flags & TX_FLAG_BINARY){
+ DBG(xxd((uint8_t*) &trl, sizeof(trl)));
+ write(fd, &trl, sizeof(trl));
+ }
+ DBG(printf("\n"));
+ return 0;
+}
+
+ssize_t twn3_receive_response(int fd, uint8_t flags, uint8_t* response, size_t max_length){
+ uint8_t recv_buffer[1024] = "";
+ ssize_t status, bytes = 0;
+
+ //TODO handle buffer overrun
+ //TODO handle binary protocol
+
+ while(1){
+ status = read(fd, recv_buffer + bytes, sizeof(recv_buffer) - bytes);
+ if(status < 0){
+ return -1;
+ }
+ if(status){
+ bytes += status;
+ if(!(flags & RX_FLAG_UNTIL_TIMEOUT) && bytes > 2){
+ //check if the last two buffer bytes are a newline
+ if(recv_buffer[bytes - 2] == '\r'
+ && recv_buffer[bytes - 1] == '\n'){
+ break;
+ }
+ }
+ }
+ else if(flags & RX_FLAG_UNTIL_TIMEOUT){
+ break;
+ }
+ }
+
+ DBG(printf("In%s: ", (flags & RX_FLAG_UNTIL_TIMEOUT) ? " (timeout)" : ""));
+ DBG(xxd(recv_buffer, bytes));
+ DBG(printf("\n"));
+ if(response && max_length){
+ memcpy(response, recv_buffer, MIN(bytes, max_length));
+ }
+ return bytes;
+}
+
+ssize_t twn3_sync_command(int fd, uint8_t flags, uint8_t* cmd, size_t cmd_len, uint8_t* response, size_t max_length){
+ twn3_send_command(fd, flags, cmd, cmd_len);
+ return twn3_receive_response(fd, flags, response, max_length);
+}
+
+int twn3_sync_read_version(int fd, uint8_t flags, char* version, size_t max_length){
+ ssize_t bytes = twn3_sync_command(fd, flags, (uint8_t*) "v", 1, (uint8_t*) version, max_length);
+ if(bytes >= max_length){
+ version[max_length] = 0;
+ }
+ else{
+ //strip newlines
+ while(bytes > 0 && !isprint(version[bytes - 1])){
+ version[bytes - 1] = 0;
+ bytes--;
+ }
+ }
+ return bytes;
+}
+
+int twn3_sync_read_eeprom(int fd, uint8_t flags, uint8_t reg, uint8_t* value){
+ char data[6];
+ ssize_t bytes;
+ snprintf(data, sizeof(data) - 1, "re%02X", reg);
+
+ bytes = twn3_sync_command(fd, flags, (uint8_t*) data, 4, (uint8_t*) data, 4);
+ if(bytes){
+ data[2] = 0;
+ if(value){
+ *value = strtoul(data, NULL, 16);
+ }
+ return 0;
+ }
+ return -1;
+}
+
+int twn3_sync_write_eeprom(int fd, uint8_t flags, uint8_t reg, uint8_t value){
+ char data[8];
+ ssize_t bytes;
+ snprintf(data, sizeof(data) - 1, "we%02X%02X", reg, value);
+
+ if(reg > 0x14){
+ return -1;
+ }
+
+ bytes = twn3_sync_command(fd, flags, (uint8_t*) data, 6, (uint8_t*) data, 4);
+ //failure may return `F\r\n`, which is also valid hex - genius!
+ if(bytes && data[1] != '\r'){
+ data[2] = 0;
+ bytes = strtoul(data, NULL, 16);
+ return (bytes == value) ? 0 : -1;
+ }
+ return -1;
+}
+
+int twn3_sync_storekey(int fd, uint8_t flags, uint8_t keyid, uint8_t key[6]){
+ char data[20];
+ snprintf(data, sizeof(data) - 1, "wm%02X%02X%02X%02X%02X%02X%02X",
+ keyid, key[0], key[1],
+ key[2], key[3],
+ key[4], key[5]);
+
+ if(keyid > 31){
+ return -1;
+ }
+
+ twn3_sync_command(fd, flags, (uint8_t*) data, 16, NULL, 0);
+ return 0;
+}
+
+int twn3_sync_antenna(int fd, uint8_t flags, uint8_t enable){
+ uint8_t data[5];
+ ssize_t bytes;
+
+ if(enable){
+ bytes = twn3_sync_command(fd, flags, (uint8_t*) "pon", 3, data, sizeof(data));
+ }
+ else{
+ bytes = twn3_sync_command(fd, flags, (uint8_t*) "poff", 4, data, sizeof(data));
+ }
+
+ return (bytes > 2 && data[0] == 'P') ? 0 : -1;
+}
diff --git a/libtwn3.h b/libtwn3.h
new file mode 100644
index 0000000..63e9b93
--- /dev/null
+++ b/libtwn3.h
@@ -0,0 +1,25 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+/* Open a device port */
+int twn3_open(char* device);
+
+/* Low-level command API */
+#define TX_FLAG_BINARY 1
+
+#define RX_FLAG_BINARY 1
+#define RX_FLAG_UNTIL_TIMEOUT 2
+
+ssize_t twn3_send_command(int fd, uint8_t flags, uint8_t* data, size_t len);
+ssize_t twn3_receive_response(int fd, uint8_t flags, uint8_t* response, size_t max_length);
+ssize_t twn3_sync_command(int fd, uint8_t flags, uint8_t* cmd, size_t cmd_len, uint8_t* response, size_t max_length);
+
+/* Synchronous API */
+/* These calls will return data directly, at the cost of blocking execution */
+int twn3_sync_read_version(int fd, uint8_t flags, char* version, size_t max_length);
+int twn3_sync_read_eeprom(int fd, uint8_t flags, uint8_t reg, uint8_t* data);
+int twn3_sync_write_eeprom(int fd, uint8_t flags, uint8_t reg, uint8_t data);
+int twn3_sync_storekey(int fd, uint8_t flags, uint8_t keyid, uint8_t key[6]);
+int twn3_sync_antenna(int fd, uint8_t flags, uint8_t enable);
+
diff --git a/twn3_test.c b/twn3_test.c
new file mode 100644
index 0000000..583deca
--- /dev/null
+++ b/twn3_test.c
@@ -0,0 +1,45 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "libtwn3.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+int main(int argc, char** argv){
+ char data[1024];
+ ssize_t bytes;
+ unsigned u;
+
+ int device = twn3_open("/dev/ttyACM0");
+
+ printf("Device is at fd %d\n", device);
+ if(device < 0){
+ printf("%s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ //check version
+ if(twn3_sync_read_version(device, 0, data, sizeof(data)) >= 0){
+ printf("Connected reader firmware version: %s\n", data);
+ }
+
+ //read eeprom
+ for(u = 0; u < 0x15; u++){
+ uint8_t regdata = 0;
+ twn3_sync_read_eeprom(device, 0, u, &regdata);
+ printf("Register %d has data %02X\n", u, regdata);
+ }
+
+ //write some userdata
+ twn3_sync_write_eeprom(device, 0, 19, 0xAA);
+
+ //store a key
+ uint8_t key[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ twn3_sync_storekey(device, 0, 12, key);
+
+ close(device);
+ return EXIT_SUCCESS;
+}