From: Nathael Pajani Date: Fri, 3 Jun 2016 00:05:18 +0000 (+0200) Subject: Lighthouse host control app. This app should go toward MQTT implementation. See top... X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=00d4c50cb1795f027c9e345c2553b7fe5fdad1dd;p=soft%2Flpc122x%2Frfsub1GHz Lighthouse host control app. This app should go toward MQTT implementation. See top of handlers.c to get some basics of an "available informations" list management. TODO : Fisrt step : - support planned Basic protocol for Android app support Second step : - create protocol to discover available informations on top of DTPlug protocole (make it extensible to other protocols) - allow devices to send their info upon power-up ? - add MQTT protocol support --- diff --git a/host/lighthouse/.gitignore b/host/lighthouse/.gitignore new file mode 100644 index 0000000..47548e5 --- /dev/null +++ b/host/lighthouse/.gitignore @@ -0,0 +1 @@ +protocol_bridge diff --git a/host/lighthouse/Basic_UDP_Protocol.txt b/host/lighthouse/Basic_UDP_Protocol.txt new file mode 100644 index 0000000..a713df3 --- /dev/null +++ b/host/lighthouse/Basic_UDP_Protocol.txt @@ -0,0 +1,22 @@ +# Basic control protocol for LightHouse over UDP. +# Should be replaced by MQTT in the future + +# Set Led +SL addr branche [ledx R G B] [ledy R G B] [....] +# addr : 0 -> 16K (0 == broadcast) +# branche : 0 - 64 (port * 32 + pin) +# led : 0 -> 255 +# R, G et B : 0 -> 255 + +# Get Leds info +GI +# Replies : +GI addr branche nbleds + +# Get Leds Colors +GLC addr branche +# Reply : +GLC addr branche [R G B] [R G B] .... + + + diff --git a/host/lighthouse/Makefile b/host/lighthouse/Makefile new file mode 100644 index 0000000..e67cea7 --- /dev/null +++ b/host/lighthouse/Makefile @@ -0,0 +1,32 @@ +#CROSS_COMPILE ?= arm-linux-gnueabihf- +CC = $(CROSS_COMPILE)gcc + +CFLAGS = -Wall -O2 -Wextra + +EXEC = protocol_bridge + +all: $(EXEC) + + +OBJDIR = objs +SRC = $(shell find . -name \*.c) +OBJS = ${SRC:%.c=${OBJDIR}/%.o} +INCLUDES = includes/ + +${OBJDIR}/%.o: %.c + @mkdir -p $(dir $@) + @echo "-- compiling" $< + @$(CC) -MMD -MP -MF ${OBJDIR}/$*.d $(CFLAGS) $< -c -o $@ -I$(INCLUDES) + +$(EXEC): $(OBJS) + @echo "Linking $@ ..." + @$(CC) $(LDFLAGS) -o $@ $(OBJS) + @echo Done. + + +clean: + find ${OBJDIR} -name "*.o" -exec rm {} \; + find ${OBJDIR} -name "*.d" -exec rm {} \; + +mrproper: clean + rm -f $(EXEC) diff --git a/host/lighthouse/dtplug_protocol_defs.h b/host/lighthouse/dtplug_protocol_defs.h new file mode 120000 index 0000000..1c863aa --- /dev/null +++ b/host/lighthouse/dtplug_protocol_defs.h @@ -0,0 +1 @@ +../../include/lib/protocols/dtplug/defs.h \ No newline at end of file diff --git a/host/lighthouse/dtplug_protocol_host.c b/host/lighthouse/dtplug_protocol_host.c new file mode 100644 index 0000000..c9e2119 --- /dev/null +++ b/host/lighthouse/dtplug_protocol_host.c @@ -0,0 +1,221 @@ +/**************************************************************************** + * dtplug_protocol_host.c + * + * + * Copyright 2013-2015 Nathael Pajani + * + * + * This program 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, see . + * + ****************************************************************************/ + + +/* Host side implementation of the DTPlug communication protocol */ + +#include +#include +#include +#include "dtplug_protocol_host.h" + + +/******************************************************************************/ +/* Handle packet reception, including checksums */ +/* 'sum' is used to sum all the received characters, and if the last byte of sum is 0 for each + * part (header and data) then the packet is valid. + * 'full_size' is the size of the whole packet, including header, updated as soon as the header + * is checked and valid + * + * This function must be called for every received character. + * If the character is part of a packet but the packet is being built, then the function returns 0. + * When the character is the last one of a valid packet, then the function returns the packet size + * and the packet in rx_data->rx_packet is valid. + * If the character is the last one of a packet which has an invalid data checksum, this function + * returns -2 and the data is lost. + * If the character is not part of a packet it returns -1. The character may then be part of + * a debug message (and displayed by the host), or any other kind of communication. + * When a set of consecutive characters have been used to build a packet but the packet is + * not valid due to header error, then the function returns -3 (checksum error) or -4 (data size + * error). The data in rx_data->rx_packet is the received data but is not valid. + * The corresponding data size is always sizeof(struct header). + */ +int dtplug_protocol_decode(uint8_t c, struct line_transceiver* rx_data) +{ + struct header* info = (struct header*)(&(rx_data->rx_packet)); + int ret = 0; + + /* Do not start reception before receiving the packet start character */ + if ((rx_data->rx_ptr == 0) && (c != FIRST_PACKET_CHAR)) { + return -1; + } + + /* Store the new byte in the packet */ + ((uint8_t*)(&(rx_data->rx_packet)))[rx_data->rx_ptr++] = c; + rx_data->sum += c; + + /* Is this packet valid ? (at end of header reception) */ + if (rx_data->rx_ptr == sizeof(struct header)) { + /* Checksum OK ? */ + if (rx_data->sum != 0) { + rx_data->errors_count++; + ret = -3; + goto next_packet; + } + /* Start the new checksum for data (if any) */ + rx_data->sum = 0; + + rx_data->full_size = sizeof(struct header); + /* If the packet is not a quick data or error packet it has a data part. Get it's size */ + if (!(info->seq_num & QUICK_DATA_PACKET) && !(info->seq_num & PACKET_IS_ERROR)) { + rx_data->full_size += (info->data.size & PKT_SIZE_MASK); /* Don't care about big packets here */ + /* Make sure the packet will fit in the buffer */ + if (rx_data->full_size > sizeof(struct packet)) { + rx_data->errors_count++; + ret = -4; + goto next_packet; + } + } + } + + /* Did we receive the whole packet ? */ + if (rx_data->rx_ptr == rx_data->full_size) { + /* From here on, the packet is valid, we can provide some feedback */ + /* Check data checksum if we have a normal data packet */ + if (!(info->seq_num & QUICK_DATA_PACKET) && !(info->seq_num & PACKET_IS_ERROR)) { + if (rx_data->sum != info->data.checksum) { + rx_data->errors_count++; + ret = -2; + goto next_packet; + } + } + /* Count received packets */ + rx_data->packet_rx_count++; + ret = rx_data->full_size; + /* And get ready to receive the next packet */ + goto next_packet; + } + + return 0; + +next_packet: +#ifdef DEBUG + printf("Current rx pointer: %d, packet full size: %d\n", rx_data->rx_ptr, rx_data->full_size); + printf("Packets received count: %d, packets errors: %d\n", + rx_data->packet_rx_count, rx_data->errors_count); + if (rx_data->rx_ptr >= sizeof(struct header)) { + struct header* h = info; + printf("Pkt: type: %d, seq: %d, err: %d, quick: %d\n", h->type, h->seq_num, + (h->seq_num & PACKET_IS_ERROR), (h->seq_num & QUICK_DATA_PACKET)); + } +#endif + /* Wether the packet was OK or not doesn't matter, go on for a new one :) */ + rx_data->full_size = 0; + rx_data->rx_ptr = 0; + rx_data->sum = 0; + return ret; +} + + +/******************************************************************************/ +/* This function handles sending packets to slave + * It returns the number of bytes of data sent. + */ +int host_send_packet(struct line_transceiver* slave, uint8_t type, uint32_t size, uint8_t* data, int need_reply) +{ + struct packet cmd; + struct header* cmd_info = &(cmd.info); + unsigned int i = 0, sent = 0, len = sizeof(struct header); + uint8_t sum = 0; + unsigned int data_send_size = size, data_sent = 0; + + /* Loop to send all data, possibly split in several packets */ + do { + if (data_send_size > PACKET_DATA_SIZE) { + data_send_size = PACKET_DATA_SIZE; + } + /* Set packet header */ + cmd_info->start = FIRST_PACKET_CHAR; + cmd_info->type = type; + cmd_info->seq_num = (slave->sequence_number++ & SEQUENCE_MASK); + /* Set the need reply bit only on the last packet */ + if ((need_reply != 0) && ((data_sent + data_send_size) == size)) { + cmd_info->seq_num |= PACKET_NEEDS_REPLY; + } + /* Setup data */ + if (data != NULL) { + /* Only use quick data packet for short packet, not continued data */ + if (size && (size <= 2)) { + cmd_info->seq_num |= QUICK_DATA_PACKET; + cmd_info->quick_data[0] = data[0]; + cmd_info->quick_data[1] = data[1]; + } else { + /* Copy data, compute checksum (also OK for a data_send_size of 0) */ + for (i = 0; i < data_send_size; i++) { + cmd.data[i] = data[i]; + sum += data[i]; /* Build checksum */ + } + /* And update header information */ + cmd_info->data.size = data_send_size; + /* Will this packet be continued in the following one ? */ + if (data_send_size < (size - data_sent)) { + cmd_info->data.size |= BIG_DATA_PKT; + } + cmd_info->data.checksum = sum; + /* Update length of data to send on serial link */ + len += data_send_size; + } + } + + /* Compute header checksum */ + sum = 0; + cmd_info->checksum = 0; + for (i = 0; i < sizeof(struct header); i++) { + sum += ((uint8_t*)cmd_info)[i]; + } + cmd_info->checksum = ((uint8_t)(256 - sum)); + + /* And send the packet on the serial link */ + while (sent < len) { + int ret = write(slave->fd, (((char*)(&cmd)) + sent), (len - sent)); + if (ret >= 0) { + sent += ret; + } else { + /* Sending error ... */ + /* FIXME : handle / report errors */ + return data_sent; + } + } + data_sent += data_send_size; + + /* Need to send more ? */ + if (data && (data_sent <= size)) { + /* Move data pointer */ + data += data_send_size; + /* Update size to send. check against PACKET_DATA_SIZE is done at beginning of loop */ + data_send_size = (size - data_sent); + /* Set packet type to continued data packet for following packets */ + type = PKT_TYPE_CONTINUED_DATA; + /* And prepare sending of the next packet (reset internal loop counters) */ + sum = 0; + len = sizeof(struct header); + sent = 0; + } else { + /* No data, everything got sent */ + data_send_size = 0; + } + } while (data_send_size != 0); + + return data_sent; +} + + diff --git a/host/lighthouse/dtplug_protocol_host.h b/host/lighthouse/dtplug_protocol_host.h new file mode 100644 index 0000000..9fe3a2c --- /dev/null +++ b/host/lighthouse/dtplug_protocol_host.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * dtplug_protocol_host.h + * + * + * Copyright 2013-2015 Nathael Pajani + * + * + * This program 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, see . + * + ****************************************************************************/ + + +/* Host side implementation of the DTPlug communication protocol */ + +#ifndef DTPLUG_PROTOCOL_HOST_H +#define DTPLUG_PROTOCOL_HOST_H + + +#include +#include "dtplug_protocol_defs.h" + + +/******************************************************************************/ +/* Handle packet reception, including checksums */ +/* 'sum' is used to sum all the received characters, and if the last byte of sum is 0 for each + * part (header and data) then the packet is valid. + * 'full_size' is the size of the whole packet, including header, updated as soon as the header + * is checked and valid + */ +struct line_transceiver { + int fd; + /* The packet being built */ + struct packet rx_packet; + /* Sequence number for this line */ + uint8_t sequence_number; + /* Packet building data */ + uint8_t rx_ptr; + uint8_t sum; + uint8_t full_size; + /* packet counts */ + uint32_t packet_rx_count; + uint32_t packet_tx_count; + uint32_t errors_count; +}; + +/* This function must be called for every received character. + * If the character is part of a packet but the packet is being built, then the function returns 0. + * When the character is the last one of a valid packet, then the function returns the packet size + * and the packet in rx_data->rx_packet is valid. + * If the character is the last one of a packet which has an invalid data checksum, this function + * returns -2 and the data is lost. + * If the character is not part of a packet it returns -1. The character may then be part of + * a debug message (and displayed by the host), or any other kind of communication. + * When a set of consecutive characters have been used to build a packet but the packet is + * not valid due to header error, then the function returns -3 (checksum error) or -4 (data size + * error). The data in rx_data->rx_packet is the received data but is not valid. + * The corresponding data size is always sizeof(struct header). + */ +int dtplug_protocol_decode(uint8_t c, struct line_transceiver* rx_data); + +/* This function handles sending packets to slave + * It returns the number of bytes of data sent. + */ +int host_send_packet(struct line_transceiver* slave, uint8_t type, uint32_t size, uint8_t* data, int need_reply); + + +#endif /* DTPLUG_PROTOCOL_HOST_H */ + diff --git a/host/lighthouse/handlers.c b/host/lighthouse/handlers.c new file mode 100644 index 0000000..6ce9667 --- /dev/null +++ b/host/lighthouse/handlers.c @@ -0,0 +1,255 @@ +/**************************************************************************** + * LightHouse UDP to RF Sub1GHz module bridge. + * + * handlers.c + * + * + * Copyright 2016 Nathael Pajani + * + * + * This program 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, see . + * + ****************************************************************************/ + + +/* This protocol handler is designed to run on a host. */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "list.h" +#include "dtplug_protocol_host.h" + + +#define BUFF_LEN 60 + +struct info_data { + struct list_head head; + int type; /* Information type */ + int addr; /* Source address */ + int function; /* Information function */ + int number; /* Information number */ + int value; /* Information */ + struct timeval last_update; /* Information timestamp */ +}; + + +struct list_head* all_info = NULL; +int nb_info = 0; + +int create_info(int type, int addr, int func, int num) +{ + struct info_data* addinfo = malloc(sizeof(struct info_data)); + + if (addinfo == NULL) { + return -1; + } + + if (all_info == NULL) { + all_info = &(addinfo->head); + INIT_LIST_HEAD(&(addinfo->head)); + } else { + list_add_tail(&addinfo->head, all_info); + } + addinfo->type = type; + addinfo->addr = addr; + addinfo->function = func; + addinfo->number = num; + nb_info++; + + return 0; +} + +void delete_info(struct info_data* oldinfo) +{ + if (oldinfo == NULL) { + return; + } + list_del(&(oldinfo->head)); + if (list_empty(all_info)) { + all_info = NULL; + } + free(oldinfo); + nb_info--; +} + +/* Find exactly matching information */ +struct info_data* find_info(int type, int addr, int func, int num) +{ + struct info_data* info = NULL; + if (all_info == NULL) { + return NULL; + } + list_for_each_entry(info, all_info, head) { + if ((info->type == type) && (info->addr == addr) && (info->function == func) && (info->number == num)) { + return info; + } + } + return info; +} +/* TOTO : find all info_data matching given criteria */ + + + +int handle_module_data(struct line_transceiver* slave) +{ + struct header* head = &(slave->rx_packet.info); + /* If this packet holds no valid data, it indicates that there are errors */ + if (head->seq_num & PACKET_IS_ERROR) { + printf("Received a packet indicating errors (%d - %d)\n", + head->err.error_code, head->err.info); + /* Request the list of errors */ + host_send_packet(slave, PKT_TYPE_GET_ERRORS, 0, NULL, 1); + return 0; + } + /* Move data from "QUICK_DATA_PACKET" packets to the data part of the packet for easy handling */ + if (head->seq_num & QUICK_DATA_PACKET) { + slave->rx_packet.data[0] = head->quick_data[0]; + slave->rx_packet.data[1] = head->quick_data[1]; + if (head->seq_num & QUICK_DATA_PACKET_ONE_BYTE) { + head->data.size = 1; + } else { + head->data.size = 2; + } + } + + switch (head->type) { + case PKT_TYPE_PING: + break; + + case PKT_TYPE_GET_BOARD_INFO: + break; + + case PKT_TYPE_GET_NUM_PACKETS: + break; + + case PKT_TYPE_GET_ERRORS: + /* FIXME */ + printf("Received error list: ... implement error decoding.\n"); + break; + + case PKT_TYPE_GET_NUM_ERRORS: + /* FIXME */ + printf("Received number of errors: %d.\n", 0); + break; + + case PKT_TYPE_CONTINUED_DATA: + break; + + case PKT_TYPE_GET_TEMPERATURE: + { + struct info_data* info = find_info('T', 0, 0, 0); /* FIXME */ + uint16_t* tmp = (uint16_t*)(&(slave->rx_packet.data[0])); + gettimeofday(&(info->last_update), NULL); + info->value = (int16_t)ntohs(tmp[0]); + } + break; + + case PKT_TYPE_GET_ADC_VALUE: + break; + + default: + printf("Received packet type %d. Not handled.\n", head->type); + break; + } + return 0; +} + +#define REP_SIZE_MAX 1024 + +int handle_udp_request(char* buf, int len, struct sockaddr* addr, socklen_t addr_len, + int sock, struct line_transceiver* slave) +{ + struct info_data* info = NULL; + if ((len == 0) || (buf == NULL)) { + return -1; + } + if (buf[0] == 'G') { + char obuf[REP_SIZE_MAX]; + int len = 0; + /* GET command : Return the last updated value with the update timestamp */ + switch (buf[1]) { + case 'L': + info = find_info('L', 0, 0, 0); /* FIXME */ + if (info != NULL) { + len = snprintf(obuf, REP_SIZE_MAX, "GL %d %d %d : %d at %ld.%03ld", + info->addr, info->function, info->number, info->value, + info->last_update.tv_sec, (info->last_update.tv_usec / 1000)); + } + break; + case 'I': + list_for_each_entry(info, all_info, head) { + if (info->type == 'I') { + len = snprintf(obuf, REP_SIZE_MAX, "GI: %d %d %d\n", + info->addr, info->function, info->value); + sendto(sock, obuf, len, 0, addr, addr_len); + } + } + break; + } + sendto(sock, obuf, len, 0, addr, addr_len); + printf("Handled GET request type: %c\n", buf[1]); + } else if (buf[0] == 'U') { + uint8_t num = 0; + /* Request a value update */ + switch (buf[1]) { + case 'T': + host_send_packet(slave, PKT_TYPE_START_TEMP_CONVERSION, 0, NULL, 0); + usleep(200 * 1000); /* Temp conversion at 11 bits resolution is about 160ms */ + host_send_packet(slave, PKT_TYPE_GET_TEMPERATURE, 0, NULL, 1); + break; + case 'A': + num = (uint8_t)(strtoul(&buf[2], NULL, 10) & 0xFF); + host_send_packet(slave, PKT_TYPE_GET_ADC_VALUE, 1, &num, 1); + break; + } + printf("Handled UPDATE request type: %c\n", buf[1]); + } else if (buf[0] == 'S') { + /* Send new values to the micro-controller */ + uint8_t obuf[BUFF_LEN]; + char* tmp = NULL; + int i = 0; + + switch (buf[1]) { + case 'L': /* FIXME */ + obuf[0] = (strtoul(&buf[2], &tmp, 10) & 0xFF); /* Address */ + obuf[1] = (strtoul(tmp, &tmp, 10) & 0x3F); /* Function */ + for (i = 2; ((i < (BUFF_LEN - 4)) && (tmp < buf + len)); i += 4) { + obuf[i] = (strtoul(tmp, &tmp, 10) & 0xFF); /* Number */ + obuf[i + 1] = (strtoul(tmp, &tmp, 10) & 0xFF); /* Red Value */ + obuf[i + 2] = (strtoul(tmp, &tmp, 10) & 0xFF); /* Green Value */ + obuf[i + 3] = (strtoul(tmp, &tmp, 10) & 0xFF); /* Blue Value */ + } + host_send_packet(slave, PKT_TYPE_SET_RGB_LED, i, obuf, 0); + break; + } + printf("Handled SET request type: %c\n", buf[1]); + } else { + printf("Unhandled request type: '0x%02x'\n", buf[0]); + return -2; + } + + return 0; +} + + + diff --git a/host/lighthouse/handlers.h b/host/lighthouse/handlers.h new file mode 100644 index 0000000..2d3aa15 --- /dev/null +++ b/host/lighthouse/handlers.h @@ -0,0 +1,47 @@ +/**************************************************************************** + * LightHouse UDP to RF Sub1GHz module bridge. + * + * handlers.h + * + * + * Copyright 2013-2014 Nathael Pajani + * + * + * This program 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, see . + * + ****************************************************************************/ + + +/* This protocol handler is designed to run on a host replacing the DTPlug, + * but should be easy to use as a source for the protocol handling code for + * the DTPlug itself. + */ + +#ifndef HANDLERS_H +#define HANDLERS_H + +#include +#include +#include "dtplug_protocol_host.h" + + + +int handle_module_data(struct line_transceiver* slave); + + +int handle_udp_request(char* buf, int len, struct sockaddr* addr, socklen_t addr_len, + int sock, struct line_transceiver* slave); + + +#endif /* HANDLERS_H */ diff --git a/host/lighthouse/list.h b/host/lighthouse/list.h new file mode 100644 index 0000000..1be8f9a --- /dev/null +++ b/host/lighthouse/list.h @@ -0,0 +1,231 @@ +/* + * list.h + * + * This code is from the linux kernel. It is a very simple and powerfull doubly + * linked list implementation. + * Not everything has been taken from the original file. + */ + +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + + +/* linux/kernel.h */ + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + + + +/* linux/stddef.h */ + +#ifdef __compiler_offsetof +#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) +#else +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + + + +/* linux/list.h */ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void __list_del_entry(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_last_entry - get the last element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) + +/** + * list_next_entry - get the next element in list + * @pos: the type * to cursor + * @member: the name of the list_struct within the struct. + */ +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*(pos)), member) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_first_entry(head, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_next_entry(pos, member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_first_entry(head, typeof(*pos), member), \ + n = list_next_entry(pos, member); \ + &pos->member != (head); \ + pos = n, n = list_next_entry(n, member)) + +#endif /* _LINUX_LIST_H */ diff --git a/host/lighthouse/main.c b/host/lighthouse/main.c new file mode 100644 index 0000000..0a7c8e3 --- /dev/null +++ b/host/lighthouse/main.c @@ -0,0 +1,271 @@ +/**************************************************************************** + * LightHouse UDP to RF Sub1GHz module bridge. + * + * main.c + * + * + * Copyright 2013-2014 Nathael Pajani + * + * + * This program 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, see . + * + ****************************************************************************/ + + +/* this protocol handler is designed to run on a host replacing the DTPlug, + * but should be easy to use as a source for the protocol handling code for + * the DTPlug itself. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dtplug_protocol_host.h" +#include "serial_utils.h" +#include "sock_utils.h" +#include "handlers.h" + + +#define BUF_SIZE 1024 + +#define PROG_NAME "DTPlug protocol test bridge" +#define VERSION "0.1" + + +void help(char *prog_name) +{ + fprintf(stderr, "---------------- "PROG_NAME" --------------------------------\n"); + fprintf(stderr, "Usage: %s [options]\n" \ + " Available options:\n" \ + " \t -p | --port : Start listening for messages on this port\n" \ + " \t -d | --device : Serial device to use for serial communication with the module\n" \ + " \t -h | --help : Print this help\n" \ + " \t -v | --version : Print programm version\n" \ + " All other arguments are data for the command, handled depending on the command.\n", prog_name); + fprintf(stderr, "-----------------------------------------------------------------------\n"); +} + + +int main(int argc, char* argv[]) +{ + /* Serial */ + char* device = NULL; + struct line_transceiver slave; + /* TCP Server */ + int port = -1; + int server_socket = -1; + /* For select */ + struct timeval next_periodic_round; + struct timeval period; + fd_set read_fds; + int max_fd = 0; + + while(1) { + int option_index = 0; + int c = 0; + + struct option long_options[] = { + {"port", required_argument, 0, 'p'}, + {"device", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "p:d:hv", long_options, &option_index); + + /* no more options to parse */ + if (c == -1) break; + switch (c) { + /* a, board address */ + case 'p': + port = strtoul(optarg, NULL, 0); + break; + + /* c, command */ + case 'd': + device = optarg; + break; + + /* v, version */ + case 'v': + printf("%s Version %s\n", PROG_NAME, VERSION); + return 0; + break; + + /* h, help */ + case 'h': + default: + help(argv[0]); + return 0; + } + } + + + /* Need Serial port and destination file as parameter */ + if ((port == -1) || (device == NULL)) { + printf("Error, need both serial (tty) device and IP port number\n"); + help(argv[0]); + return -1; + } + + /* Open tty */ + memset(&slave, 0, sizeof(struct line_transceiver)); + slave.fd = serial_setup(device); + if (slave.fd < 0) { + printf("Unable to open specified serial port %s\n", device); + return -2; + } + + /* Create UDP server socket */ + server_socket = create_bound_udp_socket(port, NULL); + if (server_socket <= 0) { + printf("Unable to open the server UDP socket on port %d\n", port); + return -3; + } + + /* ************************************************* */ + /* Initial FD set setup */ + FD_ZERO(&read_fds); + FD_SET(STDIN_FILENO, &read_fds); /* Add stdin */ + FD_SET(slave.fd, &read_fds); /* Serial link */ + FD_SET(server_socket, &read_fds); /* New connexions */ + max_fd = server_socket + 1; /* No close, this one is the last open, so it's the higest */ + + /* Periodic actions setup */ + gettimeofday(&next_periodic_round, NULL); + period.tv_sec = 1; /* "Timer" granularitu is 1 second */ + period.tv_usec = 0; + timeradd(&next_periodic_round, &period, &next_periodic_round); + + + /* ************************************************* */ + /* And never stop handling data ! */ + while (1) { + int nb = 0, len = 0, ret = 0; + struct timeval timeout; + struct timeval now; + fd_set tmp_read_fds = read_fds; + char buf[BUF_SIZE]; + + /* Send periodic requests for temperature */ + gettimeofday(&now, NULL); + + if (timercmp(&now, &next_periodic_round, >=)) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } else { + timersub(&next_periodic_round, &now, &timeout); + } + + /* select() call .... be kind to other processes */ + nb = select(max_fd, &tmp_read_fds, NULL, NULL, &timeout); + /* Errors here are bad ones .. exit ?? */ + if (nb < 0) { + perror ("select failed"); + printf("Error: select failed, this is critical.\n"); + return -10; + } + /* In case of timeout ... perform periodic actions if a period has been defined */ + if (nb == 0) { + int misses = 0; + /* Setup next periodic request date */ + timeradd(&next_periodic_round, &period, &next_periodic_round); + gettimeofday(&now, NULL); + /* Did we miss some rounds ? */ + while (timercmp(&now, &next_periodic_round, >=)) { + /* Count misses (and catch up) */ + timeradd(&next_periodic_round, &period, &next_periodic_round); + misses++; + } + /* FIXME : parse the list of periodic actions for new packets */ + continue; + } + + /* Data from Ethernet side */ + if (FD_ISSET(server_socket, &tmp_read_fds)) { + struct sockaddr_in addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + + /* Receive the new data */ + memset(buf, 0, BUF_SIZE); + len = recvfrom(server_socket, buf, BUF_SIZE, 0, (struct sockaddr *)&addr, &addr_len); + if (len > 0) { + handle_udp_request(buf, len, (struct sockaddr *)&addr, addr_len, server_socket, &slave); + } else { + /* Wait .. we received something but nothing came in ? */ + perror("UDP receive error"); + printf("\nError on UDP packet reception (ret: %d)\n", len); + } + } + + /* Read user input if any */ + if (FD_ISSET(STDIN_FILENO, &tmp_read_fds)) { + memset(buf, 0, BUF_SIZE); + len = read(STDIN_FILENO, buf, BUF_SIZE); + /* Do not know how to handle it yet, nothing defined. */ + } + + /* Handle module messages */ + if (FD_ISSET(slave.fd, &tmp_read_fds)) { + int idx = 0; + memset(buf, 0, BUF_SIZE); + /* Get serial data and try to build a packet */ + len = read(slave.fd, buf, BUF_SIZE); + if (len < 0) { + perror("serial read error"); + /* FIXME : handle errors */ + } else if (len == 0) { + /* Wait .. we received something but nothing came in ? */ + printf("\nError, got activity on serial link, but no data ...\n"); + } + while (idx < len) { + ret = dtplug_protocol_decode(buf[idx], &slave); + /* Check return code to know if we have a valid packet */ + if (ret == -1) { + /* Anything that's not part of a packet is printed as is (debug output) */ + printf("%c", buf[idx]); + } else if (ret < 0) { + printf("\nError in received packet. (ret: %d)\n", ret); + /* FIXME : dump packet for debugging */ + } else if (ret == 0) { + /* Packet is being built */ + } else { + /* Valid packet received */ + handle_module_data(&slave); + } + idx++; + } + } + + } /* End of infinite loop */ + + close(slave.fd); + close(server_socket); + return 0; +} + + diff --git a/host/lighthouse/questions.pl b/host/lighthouse/questions.pl new file mode 100755 index 0000000..1f2e972 --- /dev/null +++ b/host/lighthouse/questions.pl @@ -0,0 +1,67 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +# Author : Nathael Pajani +# Copyright 2015 Nathael Pajani +# +# This program 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, see . + + +use IO::Socket::INET; +use Scalar::Util qw(looks_like_number); + + +####################################### +# Base package parts +my $server = 'localhost'; + + +# UDP Helper +sub send_to { + my ($port, $request, $type) = @_; + my $socket = new IO::Socket::INET(Proto => 'udp'); + my $servaddr = sockaddr_in($port, inet_aton($server)); + $socket->send($request, 0, $servaddr); + unless ($request =~ /G/) { + print "Request sent.\n"; + return; + } + my $data; + $socket->recv($data, 1024, 0); + print "Reply: $data\n"; + $socket->close(); +} + + +####################################### +my ($port, $req, $type, @args) = @ARGV; + +unless (defined($port) && looks_like_number($port) && defined($req) && defined($type)) { + print "Usage: $0 port request type [red green blue] ...\n"; + exit 1; +} + +my $full = "$req$type "; +my $sep = ' '; +foreach (@args) { + $full .= "$sep"; + $full .= "$_"; + #$sep = ', '; +} +print "Req to $port : $full\n"; + +send_to($port, $full, $type); + diff --git a/host/lighthouse/serial_utils.c b/host/lighthouse/serial_utils.c new file mode 100644 index 0000000..f5cafd3 --- /dev/null +++ b/host/lighthouse/serial_utils.c @@ -0,0 +1,59 @@ +/********************************************************************* + * + * Serial utility functions + * + * + * Copyright 2012-2014 Nathael Pajani + * + * This program 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, see . + * + *********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define SERIAL_BAUD B115200 + + +int serial_setup(char* name) +{ + struct termios tio; + int fd = -1; + + /* Open serial port */ + fd = open(name, O_RDWR); + if (fd < 0) { + perror("Unable to open communication with companion chip"); + return -1; + } + /* Setup serial port */ + memset(&tio, 0, sizeof(tio)); + tio.c_cflag = CS8 | CREAD | CLOCAL; /* 8n1, see termios.h for more information */ + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 5; + cfsetospeed(&tio, SERIAL_BAUD); + cfsetispeed(&tio, SERIAL_BAUD); + tcsetattr(fd, TCSANOW, &tio); + + return fd; +} + diff --git a/host/lighthouse/serial_utils.h b/host/lighthouse/serial_utils.h new file mode 100644 index 0000000..ebb1fa0 --- /dev/null +++ b/host/lighthouse/serial_utils.h @@ -0,0 +1,32 @@ +/********************************************************************* + * + * Serial utility functions + * + * + * Copyright 2012 Nathael Pajani + * + * This program 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, see . + * + *********************************************************************/ +#ifndef SERIAL_UTILS_H +#define SERIAL_UTILS_H + +/* Setup serial comunication, using name if given or saved name if name is NULL + * SERIAL_BAUD B38400 + * c_cflag (CS7 | PARENB | CREAD | CLOCAL) (7e1) + */ +int serial_setup(char* name); + +#endif /* SERIAL_UTILS_H */ + diff --git a/host/lighthouse/sock_utils.c b/host/lighthouse/sock_utils.c new file mode 100644 index 0000000..0f3bb7c --- /dev/null +++ b/host/lighthouse/sock_utils.c @@ -0,0 +1,200 @@ +/***************************************************************************** + * + * socket utils + * + * + * Copyright 2012 Nathael Pajani + * + * This program 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, see . + * + *****************************************************************************/ + +#include /* perror */ +#include /* memset */ +#include /* close, fcntl */ +#include /* F_SETFL and O_NONBLOCK for fcntl */ + +/* For socket stuff */ +#include /* For portability for socket stuff */ +#include +#include +#include /* For TCP_NODELAY to disable nagle algorithm */ + + +/* This is used to allow quicker sending of small packets on wires */ +static void sox_disable_nagle_algorithm(int socket) +{ + const int on = 1; + setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof( int )); +} + +/* Creates a TCP socket on the specified "port", bind to port and setup to + * accept connections with listen, with up to "nb_pending" pending connections + * in the queue. + * Returns the socket, after disabling nagle algorithm. + * If "our" is not null, it will be used to store the listening socket information. + * If "port" is 0, then "our" must contain all requiered information for bind call. + */ +int socket_tcp_server(int port, int nb_pending, struct sockaddr_in* our) +{ + struct sockaddr_in local; + struct sockaddr_in* tmp; + int s; + int optval = 1; + + if (our == NULL ) { + tmp = &local; + if (port == 0) { + fprintf(stderr, "No port and no address information provided, won't be able to bind, aborting\n"); + return -1; + } + } else { + tmp = our; + } + + if (port != 0) { + memset(tmp, 0, sizeof(struct sockaddr_in)); + tmp->sin_family = AF_INET; + tmp->sin_addr.s_addr = htonl(INADDR_ANY); + tmp->sin_port = htons((short)port); + } + + if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("Unable to create TCP socket"); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) == -1) { + perror("Unable to set reuseaddress on socket"); + goto err_close; + } + if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&optval, sizeof(int)) == -1) { + perror("Unable to set keepalive on socket"); + goto err_close; + } + + if (bind(s, (struct sockaddr *)tmp, sizeof(struct sockaddr_in)) < 0) { + perror("Unable to bind TCP port"); + goto err_close; + } + + if (listen(s, nb_pending)) { + perror("Unable to listen to TCP port"); + goto err_close; + } + + sox_disable_nagle_algorithm(s); + return s; + +err_close: + close(s); + return -1; +} + + +/* Creates an UDP socket on the specified "port" and bind to port but no specific interface. + * Returns the socket. + * If "our" is not null, it will be used to store the listening socket information. + * "port" must not be 0.. + */ +int create_bound_udp_socket(int port, struct sockaddr_in* our) +{ + struct sockaddr_in local; + struct sockaddr_in* tmp; + int s; + int optval = 1; + + if (port == 0) { + return -1; + } + + if (our == NULL ) { + tmp = &local; + } else { + tmp = our; + } + + memset(tmp, 0, sizeof(struct sockaddr_in)); + tmp->sin_family = AF_INET; + tmp->sin_addr.s_addr = htonl(INADDR_ANY); + tmp->sin_port = htons((short)port); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("Unable to create UDP socket"); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) == -1) { + perror("Unable to set reuseaddress on socket"); + goto err_close; + } + + if (bind(s, (struct sockaddr *)tmp, sizeof(struct sockaddr_in)) < 0) { + perror("Unable to bind UDP port"); + goto err_close; + } + + return s; + +err_close: + close(s); + return -1; +} + + + +/* Creates an UDP socket bound to no specific interface. + * The kernel choses the port to bind to. + * Returns the socket. + */ +int create_broadcast_udp_socket(void) +{ + struct sockaddr_in local; + struct sockaddr_in* tmp = &local; + int s; + int optval = 1; + + memset(tmp, 0, sizeof(struct sockaddr_in)); + tmp->sin_family = AF_INET; + tmp->sin_addr.s_addr = htonl(INADDR_ANY); + tmp->sin_port = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("Unable to create UDP socket"); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) == -1) { + perror("Unable to set reuseaddress on socket"); + goto err_close; + } + + if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, (void *)&optval, sizeof(int)) == -1) { + perror("Unable to set broadcast on socket"); + goto err_close; + } + + if (bind(s, (struct sockaddr *)tmp, sizeof(struct sockaddr_in)) < 0) { + perror("Unable to bind UDP port"); + goto err_close; + } + + return s; + +err_close: + close(s); + return -1; +} + + diff --git a/host/lighthouse/sock_utils.h b/host/lighthouse/sock_utils.h new file mode 100644 index 0000000..059cd17 --- /dev/null +++ b/host/lighthouse/sock_utils.h @@ -0,0 +1,52 @@ +/***************************************************************************** + * + * socket utils + * + * + * Copyright 2012 Nathael Pajani + * + * This program 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, see . + * + *****************************************************************************/ + +#ifndef SOCK_UTILS_H +#define SOCK_UTILS_H + +#include /* struct sockaddr_in */ + +/* Creates a socket on the specified "port", bind to port and setup to + accept connections with listen, with up to "nb_pending" pending connections + in the queue. + Returns the socket, after disabling nagle algorithm. + If "our" is not null, it will be used to store the listening socket information. + */ +int socket_tcp_server(int port, int nb_pending, struct sockaddr_in* our); + + +/* Creates an UDP socket on the specified "port" and bind to port but no specific interface. + * Returns the socket. + * If "our" is not null, it will be used to store the listening socket information. + * "port" must not be 0.. + */ +int create_bound_udp_socket(int port, struct sockaddr_in* our); + + +/* Creates an UDP socket bound to no specific interface. + * The kernel choses the port to bind to. + * Returns the socket. + */ +int create_broadcast_udp_socket(void); + +#endif /* SOCK_UTILS_H */ +