diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | libtwn3.c | 242 | ||||
-rw-r--r-- | libtwn3.h | 25 | ||||
-rw-r--r-- | twn3_test.c | 45 |
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, ®data); + 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; +} |