diff --git a/CMakeLists.txt b/CMakeLists.txt index f431bd5..af86fb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ set(SOURCE_FILES gex/protocol/TinyFrame.c gex/protocol/TinyFrame.h gex/utils/type_coerce.h - gex/gex_defines.h) + gex/gex_defines.h gex/gex_unit.c gex/gex_unit.h) include_directories( gex diff --git a/gex/gex_client.c b/gex/gex_client.c index 6bbee3c..241800b 100644 --- a/gex/gex_client.c +++ b/gex/gex_client.c @@ -36,13 +36,30 @@ static TF_Result unit_report_lst(TinyFrame *tf, TF_Msg *msg) uint8_t callsign = msg->data[0]; uint8_t rpt_type = msg->data[1]; - struct gex_unit_lu *lu = gex_find_unit_by_callsign(gex, callsign); + struct gex_unit *lu = gex_find_unit_by_callsign(gex, callsign); + + // NULL object pattern - we use a fake unit if no unit matched. + GexUnit fbu = { + .callsign = 0, + .report_handler = NULL, + .type = "NONE", + .name = "FALLBACK", + .gex = gex, // gex must be available here - this is why we can't have this static or const. + .next = NULL, + }; + + GexMsg gexMsg = { + .session = msg->frame_id, + .payload = (uint8_t *) (msg->data + 2), + .len = (uint32_t) (msg->len - 2), + .type = rpt_type, + .unit = (lu == NULL) ? fbu : lu, + }; + if (lu && lu->report_handler) { - lu->report_handler(gex, lu->name, rpt_type, - msg->data+2, (uint32_t) (msg->len - 2)); + lu->report_handler(gexMsg); } else if (gex->fallback_report_handler) { - gex->fallback_report_handler(gex, (lu ? lu->name : "UNKNOWN"), rpt_type, - msg->data+2, (uint32_t) (msg->len - 2)); + gex->fallback_report_handler(gexMsg); } done: @@ -59,14 +76,14 @@ static TF_Result list_units_lst(TinyFrame *tf, TF_Msg *msg) PayloadParser pp = pp_start((uint8_t*)msg->data, msg->len, NULL); uint8_t count = pp_u8(&pp); char buf[100]; - struct gex_unit_lu *tail = NULL; + struct gex_unit *tail = NULL; for(int i = 0; i < count; i++) { uint8_t callsign = pp_u8(&pp); pp_string(&pp, buf, 100); fprintf(stderr, "- Found unit \"%s\" @ callsign %d\n", buf, callsign); // append - struct gex_unit_lu *lu = malloc(sizeof(struct gex_unit_lu)); + struct gex_unit *lu = malloc(sizeof(struct gex_unit)); lu->next = NULL; lu->type = "UNKNOWN"; lu->name = strdup(buf); @@ -84,19 +101,13 @@ static TF_Result list_units_lst(TinyFrame *tf, TF_Msg *msg) } /** Bind report listener */ -void GEX_OnReport(GexClient *gex, const char *unit_name, GEX_ReportListener lst) +void GEX_OnReport(GexClient *gex, GexUnit *unit, GexEventListener lst) { - if (!unit_name) { + if (!unit) { gex->fallback_report_handler = lst; } else { - struct gex_unit_lu *lu = gex_find_unit_by_name(gex, unit_name); - if (!lu) { - fprintf(stderr, "No unit named \"%s\", can't bind listener!", unit_name); - } - else { - lu->report_handler = lst; - } + unit->report_handler = lst; } } @@ -142,7 +153,6 @@ GexClient *GEX_Init(const char *device, int timeout_ms) return gex; } - /** Try to read from the serial port and process any received bytes with TF */ void GEX_Poll(GexClient *gex) { @@ -150,16 +160,14 @@ void GEX_Poll(GexClient *gex) assert(gex != NULL); - ssize_t len = read(gex->acm_fd, pollbuffer, TF_MAX_PAYLOAD_RX); + ssize_t len = read(gex->acm_fd, pollbuffer, TF_MAX_PAYLOAD_RX); // TODO wait only for expect amount? if (len < 0) { fprintf(stderr, "ERROR %d in GEX Poll: %s\n", errno, strerror(errno)); } else { - //hexDump("Received", pollbuffer, (uint32_t) len); TF_Accept(gex->tf, pollbuffer, (size_t) len); } } - /** Free the struct */ void GEX_DeInit(GexClient *gex) { @@ -169,69 +177,3 @@ void GEX_DeInit(GexClient *gex) TF_DeInit(gex->tf); free(gex); } - - -/** Query a unit */ -void GEX_Query(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len, - TF_Listener listener) -{ - uint8_t callsign = gex_find_callsign_by_name(gex, unit); - assert(callsign != 0); - uint8_t *pld = malloc(len + 2); - assert(pld != NULL); - { - // prefix the actual payload with the callsign and command bytes. - // TODO provide TF API for sending the payload externally in smaller chunks? Will avoid the malloc here - pld[0] = callsign; - pld[1] = cmd; - memcpy(pld + 2, payload, len); - - TF_Msg msg; - TF_ClearMsg(&msg); - msg.type = MSG_UNIT_REQUEST; - msg.data = pld; - msg.len = (TF_LEN) (len + 2); - TF_Query(gex->tf, &msg, listener, 0); - } - free(pld); - - if (NULL != listener) { - GEX_Poll(gex); - } -} - - -/** listener for the synchronous query functionality */ -static TF_Result sync_query_lst(TinyFrame *tf, TF_Msg *msg) -{ - GexClient *gex = tf->userdata; - // clone the message - memcpy(&gex->sync_query_response, msg, sizeof(TF_Msg)); - // clone the buffer - if (msg->len > 0) memcpy(gex->sync_query_buffer, msg->data, msg->len); - // re-link the buffer - gex->sync_query_response.data = gex->sync_query_buffer; - gex->sync_query_ok = true; -} - - -/** Query a unit. The response is expected to be relatively short. */ -TF_Msg *GEX_SyncQuery(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len) -{ - gex->sync_query_ok = false; - memset(&gex->sync_query_response, 0, sizeof(TF_Msg)); - GEX_Query(gex, unit, cmd, payload, len, sync_query_lst); - return gex->sync_query_ok ? &gex->sync_query_response : NULL; -} - -/** Command a unit (same like query, but without listener and without polling) */ -void GEX_Send(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len) -{ - GEX_Query(gex, unit, cmd, payload, len, NULL); -} diff --git a/gex/gex_client.h b/gex/gex_client.h index ea26a1c..f32a9a0 100644 --- a/gex/gex_client.h +++ b/gex/gex_client.h @@ -12,13 +12,15 @@ #include "gex_defines.h" /** - * Bind a report listener + * Bind a report listener. The listener is called with a message object when + * a spontaneous report is received. If no known unit matched the report, + * a dummy unit is provided to avoid NULL access. * * @param gex - client - * @param unit_name - name of the listened for unit, NULL to bind a fallback listener + * @param unit - the handled unit, NULL to bind a fallback listener (fallback may receive events from all unhandled units) * @param lst - the listener */ -void GEX_OnReport(GexClient *gex, const char *unit_name, GEX_ReportListener lst); +void GEX_OnReport(GexClient *gex, GexUnit *unit, GexEventListener lst); /** * Initialize the GEX client @@ -42,49 +44,4 @@ void GEX_Poll(GexClient *gex); */ void GEX_DeInit(GexClient *gex); -/** - * Query a unit - * - * @param gex - client instance - * @param unit - unit name - * @param cmd - command (hex) - * @param payload - payload for the unit - * @param len - number of bytes in the payload - * @param listener - TF listener function called for the response - */ -void GEX_Query(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len, - TF_Listener listener); - -/** - * Query a unit and return the response. The resulting message (including payload) is - * copied to a internal holder variable. Do not attempt to free it! - * - * @attention Calling `GEX_SyncQuery` destroys the previous sync query message and payload. - * - * @param gex - client instance - * @param unit - unit name - * @param cmd - command (hex) - * @param payload - payload for the unit - * @param len - number of bytes in the payload - * @return a clone of the response, or NULL if none arrived in time. -*/ -TF_Msg *GEX_SyncQuery(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len); - -/** - * Send a command with response no listener - * - * @param gex - client instance - * @param unit - unit name - * @param cmd - command (hex) - * @param payload - payload for the unit - * @param len - number of bytes in the payload - */ -void GEX_Send(GexClient *gex, - const char *unit, uint8_t cmd, - uint8_t *payload, uint32_t len); - #endif //GEX_CLIENT_GEX_CLIENT_H diff --git a/gex/gex_defines.h b/gex/gex_defines.h index 0920e40..8b617ed 100644 --- a/gex/gex_defines.h +++ b/gex/gex_defines.h @@ -8,9 +8,40 @@ #include #include -typedef struct gex_client_ GexClient; +typedef TF_ID GexSession; + +typedef struct gex_client GexClient; +typedef struct gex_unit GexUnit; +typedef struct gex_msg GexMsg; /** Callback for spontaneous reports from units */ -typedef void (*GEX_ReportListener)(GexClient *gex, const char *unit, uint8_t code, const uint8_t *payload, uint32_t len); +typedef void (*GexEventListener)(GexMsg msg); + +/** + * GEX message, used e.g. as a return value to static query. + * Contains all needed information to lead a multi-part dialogue. + */ +struct gex_msg { + GexUnit *unit; + uint8_t *payload; + uint32_t len; + GexSession session; + union { + uint8_t cmd; + uint8_t type; + }; +}; + +/** + * GEX unit instance, allocated based on configuration read from the GEX board. + */ +struct gex_unit { + GexClient *gex; //!< Client instance + char *name; //!< Unit name (loaded, malloc'd) + char *type; //!< Unit type (loaded, malloc'd) + uint8_t callsign; //!< Unit callsign + GexEventListener report_handler; //!< Report handling function + struct gex_unit *next; //!< Pointer to the next entry in this linked list, or NULL if none +}; #endif //GEX_CLIENT_GEX_DEFINES_H diff --git a/gex/gex_helpers.c b/gex/gex_helpers.c index f887c20..560ea60 100644 --- a/gex/gex_helpers.c +++ b/gex/gex_helpers.c @@ -10,9 +10,9 @@ /** Delete recursively all GEX callsign look-up table entries */ void gex_destroy_unit_lookup(GexClient *gex) { - struct gex_unit_lu *next = gex->ulu_head; + struct gex_unit *next = gex->ulu_head; while (next != NULL) { - struct gex_unit_lu *cur = next; + struct gex_unit *cur = next; next = next->next; free(cur); } @@ -20,9 +20,9 @@ void gex_destroy_unit_lookup(GexClient *gex) } /** Get lookup entry for unit name */ -struct gex_unit_lu *gex_find_unit_by_callsign(GexClient *gex, uint8_t callsign) +struct gex_unit *gex_find_unit_by_callsign(GexClient *gex, uint8_t callsign) { - struct gex_unit_lu *next = gex->ulu_head; + struct gex_unit *next = gex->ulu_head; while (next != NULL) { if (next->callsign == callsign) { return next; @@ -33,9 +33,9 @@ struct gex_unit_lu *gex_find_unit_by_callsign(GexClient *gex, uint8_t callsign) } /** Get lookup entry for unit name */ -struct gex_unit_lu *gex_find_unit_by_name(GexClient *gex, const char *name) +struct gex_unit *gex_find_unit_by_name(GexClient *gex, const char *name) { - struct gex_unit_lu *next = gex->ulu_head; + struct gex_unit *next = gex->ulu_head; while (next != NULL) { if (strcmp(next->name, name) == 0) { return next; @@ -48,6 +48,6 @@ struct gex_unit_lu *gex_find_unit_by_name(GexClient *gex, const char *name) /** Get callsign for unit name */ uint8_t gex_find_callsign_by_name(GexClient *gex, const char *name) { - struct gex_unit_lu *lu = gex_find_unit_by_name(gex, name); + struct gex_unit *lu = gex_find_unit_by_name(gex, name); return (uint8_t) ((lu == NULL) ? 0 : lu->callsign); } diff --git a/gex/gex_helpers.h b/gex/gex_helpers.h index 62575a4..f138ee1 100644 --- a/gex/gex_helpers.h +++ b/gex/gex_helpers.h @@ -12,10 +12,10 @@ void gex_destroy_unit_lookup(GexClient *gex); /** Get lookup entry for unit name */ -struct gex_unit_lu *gex_find_unit_by_callsign(GexClient *gex, uint8_t callsign); +struct gex_unit *gex_find_unit_by_callsign(GexClient *gex, uint8_t callsign); /** Get lookup entry for unit name */ -struct gex_unit_lu *gex_find_unit_by_name(GexClient *gex, const char *name); +struct gex_unit *gex_find_unit_by_name(GexClient *gex, const char *name); /** Get callsign for unit name */ uint8_t gex_find_callsign_by_name(GexClient *gex, const char *name); diff --git a/gex/gex_internal.h b/gex/gex_internal.h index 3bd29cb..8ed3380 100644 --- a/gex/gex_internal.h +++ b/gex/gex_internal.h @@ -9,15 +9,7 @@ #include #include "gex_client.h" -struct gex_unit_lu { - char *name; //!< Unit name - char *type; //!< Unit type - uint8_t callsign; //!< Unit callsign byte - GEX_ReportListener report_handler; //!< Report handling function - struct gex_unit_lu *next; //!< Pointer to the next entry in this linked list, or NULL if none -}; - -struct gex_client_ { +struct gex_client { TinyFrame *tf; //!< TinyFrame instance const char *acm_device; //!< Comport device name, might be used to reconnect (?) int acm_fd; //!< Open comport file descriptor @@ -25,12 +17,12 @@ struct gex_client_ { // synchronous query "hacks" bool sync_query_ok; //!< flag that the query response was received - TF_Msg sync_query_response; //!< response message, copied here + GexMsg sync_query_response; //!< response message, copied here uint8_t sync_query_buffer[TF_MAX_PAYLOAD_RX]; //!< buffer for the rx payload to be copied here - GEX_ReportListener fallback_report_handler; + GexEventListener fallback_report_handler; - struct gex_unit_lu *ulu_head; //!< Units look-up linked list + struct gex_unit *ulu_head; //!< Units look-up linked list }; #endif //GEX_CLIENT_GEX_CLIENT_INTERNAL_H diff --git a/gex/gex_unit.c b/gex/gex_unit.c new file mode 100644 index 0000000..6b29619 --- /dev/null +++ b/gex/gex_unit.c @@ -0,0 +1,126 @@ +// +// Created by MightyPork on 2017/12/19. +// + +#include +#include +#include "gex_defines.h" +#include "gex_helpers.h" +#include "gex_message_types.h" + +/** + * Low level query + * + * @param unit - GEX unit to address. The unit is available in userdata1 of the response message, if any + * @param cmd - command byte + * @param payload - payload buffer + * @param len - payload length + * @param lst - TF listener to handle the response, can be NULL + * @param userdata2 userdata2 argument for the TF listener's message + */ +static void GEX_LL_Query(GexUnit *unit, uint8_t cmd, uint8_t *payload, uint32_t len, TF_Listener lst, void *userdata2) +{ + GexClient *gex = unit->gex; + + uint8_t callsign = gex_find_callsign_by_name(gex, unit); + assert(callsign != 0); + uint8_t *pld = malloc(len + 2); + assert(pld != NULL); + { + // prefix the actual payload with the callsign and command bytes. + // TODO provide TF API for sending the payload externally in smaller chunks? Will avoid the malloc here + pld[0] = callsign; + pld[1] = cmd; + memcpy(pld + 2, payload, len); + + TF_Msg msg; + TF_ClearMsg(&msg); + msg.type = MSG_UNIT_REQUEST; + msg.data = pld; + msg.len = (TF_LEN) (len + 2); + msg.userdata = unit; + msg.userdata2 = userdata2; + TF_Query(gex->tf, &msg, lst, 0); + } + free(pld); +} + +/** Send with no listener, don't wait for response */ +void GEX_Send(GexUnit *unit, uint8_t cmd, uint8_t *payload, uint32_t len) +{ + GEX_LL_Query(unit, cmd, payload, len, NULL, NULL); +} + +/** listener for the synchronous query functionality */ +static TF_Result sync_query_lst(TinyFrame *tf, TF_Msg *msg) +{ + GexClient *gex = tf->userdata; + // clone the message + gex->sync_query_response.len = msg->len; + gex->sync_query_response.session = msg->frame_id; + gex->sync_query_response.unit = msg->userdata; + gex->sync_query_response.type = msg->type; + // clone the buffer + if (msg->len > 0) memcpy(gex->sync_query_buffer, msg->data, msg->len); + // re-link the buffer + gex->sync_query_response.payload = gex->sync_query_buffer; + gex->sync_query_ok = true; +} + +/** Static query */ +GexMsg GEX_Query(GexUnit *unit, uint8_t cmd, uint8_t *payload, uint32_t len) +{ + GexClient *gex = unit->gex; + gex->sync_query_ok = false; + + // Default response that will be used if nothing is received + gex->sync_query_response.unit = unit; + gex->sync_query_response.session = 0; + gex->sync_query_response.type = MSG_ERROR; + sprintf((char *) gex->sync_query_buffer, "TIMEOUT"); + gex->sync_query_response.len = (uint32_t) strlen("TIMEOUT"); + gex->sync_query_response.payload = gex->sync_query_buffer; + + GEX_LL_Query(unit, cmd, payload, len, sync_query_lst, NULL); + GEX_Poll(gex); + + if (!gex->sync_query_ok) { + fprintf(stderr, "No response to query of unit %s!", unit->name); + } + + return gex->sync_query_response; +} + + +/** listener for the synchronous query functionality */ +static TF_Result async_query_lst(TinyFrame *tf, TF_Msg *msg) +{ + GexMsg gexMsg; + + // clone the message + gexMsg.len = msg->len; + gexMsg.session = msg->frame_id; + gexMsg.unit = msg->userdata; // gex is accessible via the unit + gexMsg.type = msg->type; + gexMsg.payload = (uint8_t *) msg->data; + + GexEventListener lst = msg->userdata2; + assert(lst != NULL); + + lst(gexMsg); +} + +/** Sync query, without poll */ +void GEX_QueryAsync(GexUnit *unit, uint8_t cmd, uint8_t *payload, uint32_t len, GexEventListener lst) +{ + GexClient *gex = unit->gex; + gex->sync_query_ok = false; + memset(&gex->sync_query_response, 0, sizeof(GexMsg)); + + // Default response that will be used if nothing is received + gex->sync_query_response.type = MSG_ERROR; + sprintf((char *) gex->sync_query_buffer, "TIMEOUT"); + gex->sync_query_response.len = (uint32_t) strlen("TIMEOUT"); + + GEX_LL_Query(unit, cmd, payload, len, async_query_lst, lst); +} diff --git a/gex/gex_unit.h b/gex/gex_unit.h new file mode 100644 index 0000000..1463320 --- /dev/null +++ b/gex/gex_unit.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/12/19. +// + +#ifndef GEX_CLIENT_GEX_UNIT_H +#define GEX_CLIENT_GEX_UNIT_H + + + +#endif //GEX_CLIENT_GEX_UNIT_H