diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5abbd22 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "test/zoo/ThrowTheSwitch.Unity"] + path = test/zoo/ThrowTheSwitch.Unity + url = https://github.com/ThrowTheSwitch/Unity.git +[submodule "test/zoo/thinnect.node-platform"] + path = test/zoo/thinnect.node-platform + url = https://github.com/thinnect/node-platform.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..638bfe6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: c +sudo: false +addons: + apt: + packages: + - ruby + +script: + - make -C test/startstop + - make -C test/sleep diff --git a/LICENSE b/LICENSE index 4b72ce8..74a7302 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Thinnect +Copyright (c) 2021 Thinnect Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c08e197..877f551 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,370 @@ # mist-comm -Mist communications and security APIs. + +Mist communications APIs, abstractions for various communication interfaces. + +## Features + +The Mist Communication API abstracts the specifics of different communication +implementations from the higher layers. Applications can use +a consistent globally unique EUI-64 based addressing scheme and common functions +for sending and receiving messages, regardless of the underlying communication +technology. This makes it possible to replace the underlying communication +interface without making any changes to the application code. All API calls +are done through methods that include a pointer to the API layer and for basic +communication flows, it is usually not necessary to be aware of what the actual +underlying communications technology is. + + +## Included implementations + +The mist-comm library contains an adapter for adding the mist-comm API to +radio layers. This is used by the 802.15.4 implementations in +[node-platform](https://github.com/thinnect/node-platform) +and by the `serial_activemessage` and `serial_basicmessage` implementations +bundled with the library. + +The API has been intended to be platform independent, however, in practice +a CMSIS2 (cmsis_os2.h) environment is currently required for running the API in +most cases, since no other OS wrappers have been developed. It has, however, +also not been decided to exclusively rely on CMSIS2 and it should be possible to +set up a basic communications flow without an OS using for example +[radio_basic.c](https://github.com/thinnect/node-platform/blob/master/silabs/radio_basic.c). + + +## Addresses and identifiers + +### Addressing + +Messages are ideally addressed using the endpoint IEEE EUI-64 addresses, however, +the actual address structure is a composite of a global and a local address, +since the mist-comm API is mostly used on embedded devices and over communication +interfaces where it is necessary to map interface-specific addresses and global +identities. + +### Packet type - Active Message ID (AMID) + +The Mist AMID concept derives from the TinyOS Active Message ID, which is an +8-bit value. On the cloud side this has been extended to include the +TinyOS message identifier 0x3F as a prefix, with the goal of opening up the rest +of the 16-bit space in the future, as other protocols and tools are extended. +The mist-comm API currently uses the 8-bit value, a future version may move to +the full 16-bit identifier. + +### Packet group - Active Message Group / PAN ID + +The Mist Group ID concept derives from the TinyOS Active Message Group ID, +which itself relies on the 802.15.4 PAN ID, however, exposes only 8-bits of the +original 16-bit value. The mist-comm API uses the 16-bit value, however, the +high-byte currently needs to be 0, because the serial protocol only transfers +the lower 8 bits. + + +## Setting up a communications layer + +### Initialization + +Initializing a communications layer is usually a simple as calling the init or +setup function of a specific communications layer. A pointer to the layer is +returned or NULL if something went wrong. This is usually done once on startup, +allocates some resources and probably starts a background thread. Some layers +may allow de-initialization and re-initialization, but many implementations +lack support for this and subsequent calls to the setup function will fail. + +For example: +```C +comms_layer_t * p_radio = radio_setup(node_local_address, node_eui64); +if (NULL == radio) +{ + // panic +} + +// START the radio or apply a sleep-block +``` + +The communications layer may start automatically or may require `comms_start` +to be called or a sleep-block to be applied to actually start it. `comms_status` +can be used to check the status. + + +### Creation + +The mist-comm interface structure (`comms_layer_iface_t`) carries function +pointers for packet manipulation and messaging tasks. These functions are +accessed by the respective API functions. It is possible to create +a completely custom implementation, however, an adapter component exists for +common ActiveMessage layers (used by most 802.15.4 radio and UART implementations). + +`comms_am_create` initializes a layer structure with most necessary ActiveMessage +functions, only requiring the send and max packet length functions. Also +start and stop may be added, but are not needed for always-on implementations. + +Basic receiver management functionality can be added to a layer by calling +`comms_initialize_rcvr_management`, `comms_am_create` does this internally +already! + +When creating a new communications layer, it is a good idea to call `comms_verify_api` +first and check that it returns `true`. This will allow compile-time detection +of some incompatibilities, if static communications libraries are being linked +together. + + +## Sending a message + +Before using a `comms_msg_t` structure, it must be initialized for the layer +that it is going to be used with. This will clear all the contents and may +set some fields in the message necessary for the communications layer. Messages +may not be passed from one layer to another directly. + +```C +static comms_msg_t m_msg; +comms_init_message(p_radio, &m_msg); // A message must be initialized + +comms_address_t dest; // Send message to 1122334455667788 +memset(&dest, 0, sizeof(dest)); // Clear the structure +eui64_set(&dest.eui, "\x11\x22\x33\x44\x55\x66\x77\x88") + +comms_set_destination(p_radio, &m_msg, &dest); + +custom_packet_t * pcp = (custom_packet_t*)comms_get_payload(p_radio, &m_msg, sizeof(custom_packet_t)); +if (NULL != pcp) +{ + pcp->field1 = 1; + pcp->field2 = 2; + + comms_set_packet_type(p_radio, &m_msg, CUSTOM_PROTOCOL_AMID); + comms_set_payload_length(p_radio, &m_msg, sizeof(custom_packet_t)); + + comms_set_ack_required(p_radio, &m_msg, true); // Request an ack + + comms_error_t result = comms_send(radio, &m_msg, radio_send_done, NULL); + if (COMMS_SUCCESS == result) + { + sending = true; // wait for radio_send_done to be called + } +} +``` + +If `comms_send` returns COMMS_SUCCESS, the communications layer takes possession +of the message until it is returned with the send-done event. +*The message structure must not be modified by the user between send and send-done!* + +When sending a message, some fields, like the source address or group will default +to the values configured for the communications layer and do not normally need +setting. Some layers may not even support setting these fields to those of other +devices, but they do need to be set when for example bridging / routing messages. + +```C +static void radio_send_done (comms_layer_t * comms, comms_msg_t * msg, comms_error_t result, void * user) +{ + // Result can be checked from result + if (COMMS_SUCCESS != result) + { + // Something went wrong + } + // User carries what-ever pointer was passed as the final argument to comms_send ... NULL in this example + if (comms_ack_received(comms, msg)) + { + // Ack was received + } + + // The message can now be freed, put back in a pool or re-used (but not directly from send-done) + + // Signal your thread perhaps or clear the sending flag, for example: + osThreadFlagsSet(my_thread_id, MY_THREAD_FLAG_MESSAGE_SENT); +} +``` + +The send-done event should be treated like an interrupt - don't do too much in there +as it is usually fired from the communication layer thread and holding it up may +cause subsequent packets to be dropped. + + +## Receiving a message + +Receiving a message requires registering a receiver and a receive function. +A regular receiver will pass a message to the receive function when the type (AM ID) +of the message matches and the message destination is either the address of the +node or the broadcast address. Alternatively a snooper can be registered with +`comms_register_snooper`, which will pass all messages to the receive callback +regardless of type or destination address. Multiple receivers or snoopers may be +registered and each will get a copy of the message, if it matches the parameters. +The same receive function may be used for multiple receivers, but a receiver +structure can obly be registered once, unless de-registered first! + +```C +static comms_layer_t * mp_comms; +static comms_receiver_t m_rcvr1; +static comms_receiver_t m_rcvr2; + +void setup_function (comms_layer_t * p_comms) +{ + mp_comms = p_comms; + comms_register_recv_eui(mp_comms, &m_rcvr1, receive_message, NULL, AMID_PROTOCOL_1); + comms_register_recv(mp_comms, &m_rcvr2, receive_message, NULL, AMID_PROTOCOL_2); +} + +// Same function given to both receivers +static void receive_message (comms_layer_t * p_comms, const comms_msg_t * p_msg, void * p_user) +{ + am_id_t ptype = comms_get_packet_type(p_comms, msg); + if (AMID_PROTOCOL_1 == ptype) + { + comms_address_t source; + comms_get_source(p_comms, p_msg, &source); + if (eui64_is_zeros(&source.eui)) + { + return; // Don't know source EUI64 + } + // packet processing here + } + else if (AMID_PROTOCOL_2 == ptype) + { + // packet processing where EUI64 of source is not important + } +} + +``` + + +## Addressing + +The message sending example above uses global EUI64 addressing when sending the +message. In this case, the sender is starting with a blank `comms_address_t` +structure and knows the EUI64 for the destination. Alternatively a sender may +use a `comms_address_t` saved from a received message, which would already +contain the local address (if one is used). Sending without a local address +(when one is needed) will trigger a lookup from the address mapping table. +If the address is not in the table, then a discovery packet will be sent, +however, sending of the initial message will fail with `COMMS_NO_ADDR`. + +The message reception example above for the first message type sets up a receiver +through the `comms_register_recv_eui` function, meaning it will automatically +try to determine the EUI64 for all incoming messages, however, messages will be +delivered to the receive function even if the EUI64 is not known. It is therefore +important to always check the source address, if the source of the message is +important. The example for the second message type uses `comms_register_recv`, +which means messages of that type will not trigger an EUI64 lookup. + +Address discovery and caching needs to be set up for each communications layer +that needs it. + +```C + static am_addrdisco_t disco; + static comms_addr_cache_t cache; + comms_am_addrdisco_init(p_radio, &disco, &cache); // example for mist_comm_am +``` + + +## Message pool + +An allocated `comms_msg_t` structure is necessary for sending a message. In many +cases components can declare the message statically, but for many other components, +that rarely need to send a message, declaring a static message may be wasteful +in terms of overall memory usage. A message pool component is available for +cases where it is desirable to share the message structures among several components. +The message pool needs to be initialized, it normally allocates the message memory +using underlying OS memory allocation methods. + +```C +static comms_pool_t m_msg_pool; + +void setup_function (void) +{ + if (COMMS_SUCCESS != comms_pool_init(&m_msg_pool, 3)) // allocate 3 messages + { + // panic + } + custom_component_init(mp_comms, &m_msg_pool); +} + +// custom_component.c --------------------------------------------------------- + +static comms_pool_t * mp_pool; +static comms_layer_t * mp_comms; + +static void send_done (comms_layer_t * comms, comms_msg_t * p_msg, comms_error_t result, void * user) +{ + // signal something + comms_pool_put(mp_pool, p_msg); // Message not sent, put it back to pool +} + +void custom_component_function (void) +{ + comms_msg_t * p_msg = comms_pool_get(mp_pool, 0); // no wait + if (NULL != p_msg) + { + // format message, add payload ... + + comms_error_t result = comms_send(mp_comms, p_msg, send_done, NULL); + if (COMMS_SUCCESS != result) + { + comms_pool_put(mp_pool, p_msg); // Message not sent, put it back to pool! + } + } +} +``` + + +## Communications layer sleep management + +A communications layer may be always on (started after init) or switched either +by calling `comms_start` and `comms_stop` or through sleep controllers. +Sleep controllers control the layer using the start and stop functions so they +do not work together with direct external control. Therefore, it is recommended +to use sleep controllers if the communications layer needs to be switched +on from more than one place - for example to keep an exteremely low duty-cycle +device out of sleep for a slightly longer session. + +Sleep controllers need to be registered with a communications layer first and +then a sleep block may be applied. Starting a communications layer is not +instantaneous, so a callback is fired, if it was not running and will be started. +Allowing sleep, however, does not mean that the communications layer will +actually stop, since another component may be holding a block as well. + +```C +static comms_sleep_controller_t m_sleep_ctrl; + +// Add a sleep cotnroller +comms_register_sleep_controller(p_comms, &m_sleep_ctrl, radio_start_done, NULL); + +// Wake up a communications layer + comms_error_t rslt = comms_sleep_block(&m_sleep_ctrl); + if (rslt == COMMS_SUCCESS) + { + // radio_start_done will be called when actually started + } + else if (rslt == COMMS_ALREADY) + { + // it is already started, no callback will fire + } + +// Allow communications layer to sleep again + comms_sleep_allow(&m_sleep_ctrl); +``` + + +## Timestamps + +Received messages are timestamped as they arrive, transmitted messages are +timestamped when sent out and the timestamp can be inspected in the send-done +event. A combination of send and receive timestamping features can be used +to communicate an event timestamp (something in the past or the future) between +two devices that do not share a synchronized clock. Timestamps are relative +to the counter available from `comms_get_time_micro` and are always +represented in microseconds, though the underlying physical clock may have a +different granularity. + +**Timestamping features depend on the capabilities of the underlying +implementation and may not be supported for all communications layers!** + +### Message timestamps + +Message timestamps can be read with the combination of `comms_timestamp_valid` +and `comms_get_timestamp_micro`. It is up to the user to know if the message +was received or sent, to know if they are getting an RX or TX timestamp. + +### Event timestamps + +Event timestamps are set with the `comms_set_event_time_micro` function and +on reception, can be checked with `comms_event_time_valid` and `comms_get_event_time_micro`. +Event times must be within +- 30 minutes of the current moment. diff --git a/addrcache/mist_comm_addrcache.c b/addrcache/mist_comm_addrcache.c new file mode 100644 index 0000000..2e760a6 --- /dev/null +++ b/addrcache/mist_comm_addrcache.c @@ -0,0 +1,150 @@ +/** + * EUI-local address cache. + * + * Will discard older elements when it runs out of space. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#include "mist_comm_addrcache.h" + +#include "cmsis_os2_ext.h" // osCounterGetSecond, but should probably use an alternative time-source + +#include + +#include "loglevels.h" +#define __MODUUL__ "addrc" +#define __LOG_LEVEL__ (LOG_LEVEL_mist_comm_addrcache & BASE_LOG_LEVEL) +#include "log.h" + +bool comms_cache_get_local (comms_addr_cache_t * cache, const ieee_eui64_t * eui, comms_local_addr_t * local) +{ + if (NULL == cache) + { + return false; + } + + comms_mutex_acquire(cache->mutex); + for (int i=0;ieuis[i]), sizeof(ieee_eui64_t))) + { + memcpy(local, &(cache->locals[i]), sizeof(comms_local_addr_t)); + comms_mutex_release(cache->mutex); + return true; + } + } + comms_mutex_release(cache->mutex); + return false; +} + +bool comms_cache_get_eui (comms_addr_cache_t * cache, const comms_local_addr_t * local, ieee_eui64_t * eui) +{ + if (NULL == cache) + { + return false; + } + + comms_mutex_acquire(cache->mutex); + for (int i=0;ilocals[i]), sizeof(comms_local_addr_t))) + { + memcpy(eui, &(cache->euis[i]), sizeof(ieee_eui64_t)); + comms_mutex_release(cache->mutex); + return true; + } + } + comms_mutex_release(cache->mutex); + return false; +} + +void comms_cache_update (comms_addr_cache_t * cache, const ieee_eui64_t * eui, const comms_local_addr_t * local) +{ + int free_slot = -1; + + if (NULL == cache) + { + return; + } + + debugb1("update", eui->data, IEEE_EUI64_LENGTH); + + comms_mutex_acquire(cache->mutex); + + // Look for local address match, delete if different eui, otherwise update, can be only 1 + for (int i=0;ilocals[i]), sizeof(comms_local_addr_t))) + { + if (0 == memcmp(eui, &(cache->euis[i]), sizeof(ieee_eui64_t))) + { + debug("exists"); + cache->updated[i] = osCounterGetSecond(); + comms_mutex_release(cache->mutex); + return; + } + else + { + debug("eui change"); + memset(&(cache->euis[i]), 0, sizeof(ieee_eui64_t)); + memset(&(cache->locals[i]), 0, sizeof(comms_local_addr_t)); + cache->updated[i] = 0; + free_slot = i; + break; + } + } + } + + // Look for eui address match + for (int i=0;ieuis[i]), sizeof(ieee_eui64_t))) + { + debug("loc change"); + memcpy(&(cache->locals[i]), local, sizeof(comms_local_addr_t)); + cache->updated[i] = osCounterGetSecond(); + + comms_mutex_release(cache->mutex); + return; + } + } + + // Look for an empty or oldest entry + if(free_slot < 0) + { + uint32_t updated = UINT32_MAX; + for (int i=0;ieuis[i]))) + { + free_slot = i; + break; + } + else if(cache->updated[i] < updated) + { + updated = cache->updated[i]; + free_slot = i; + } + } + } + + debug("add %d", free_slot); + // Store the information in the slot that was found + memcpy(&(cache->euis[free_slot]), eui, sizeof(ieee_eui64_t)); + memcpy(&(cache->locals[free_slot]), local, sizeof(comms_local_addr_t)); + cache->updated[free_slot] = osCounterGetSecond(); + + comms_mutex_release(cache->mutex); +} + +void comms_cache_init (comms_addr_cache_t * cache) +{ + cache->mutex = comms_mutex_create(); + for(int i=0;ieuis[i]), 0, sizeof(ieee_eui64_t)); + memset(&(cache->locals[i]), 0, sizeof(comms_local_addr_t)); + cache->updated[i] = 0; + } +} diff --git a/addrcache/mist_comm_addrcache.h b/addrcache/mist_comm_addrcache.h new file mode 100644 index 0000000..f0c550c --- /dev/null +++ b/addrcache/mist_comm_addrcache.h @@ -0,0 +1,64 @@ +/** + * EUI64<->local address cache. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_ADDRCACHE_H_ +#define MIST_COMM_ADDRCACHE_H_ + +#include "mist_comm.h" + +#ifndef MIST_COMM_ADDRCACHE_SIZE +#define MIST_COMM_ADDRCACHE_SIZE 32 +#endif//MIST_COMM_ADDRCACHE_SIZE + +typedef struct comms_addr_cache comms_addr_cache_t; + +/** + * Initialize the cache structure. + */ +void comms_cache_init(comms_addr_cache_t * cache); + +/** + * Get the local address for the EUI. + * + * @param cache Pointer to the cache. + * @param eui EUI64 address. + * @param local Storage space for the local address. + * @return true if an address was available. + */ +bool comms_cache_get_local(comms_addr_cache_t * cache, const ieee_eui64_t * eui, comms_local_addr_t * local); + +/** + * Get the EUI for the local address. + * + * @param cache Pointer to the cache. + * @param local Local address. + * @param eui Storage space for the EUI64 address. + * @return true if an address was available. + */ +bool comms_cache_get_eui(comms_addr_cache_t * cache, const comms_local_addr_t * local, ieee_eui64_t * eui); + +/** + * Update the cached mapping. + * + * Only 1:1 possible, any conflicting mappings will get thrown out. + * + * @param cache Pointer to the cache. + * @param eui EUI64 address. + * @param local Local address. + */ +void comms_cache_update(comms_addr_cache_t * cache, const ieee_eui64_t * eui, const comms_local_addr_t * local); + + +//------------------------------------------------------------------------------ +struct comms_addr_cache +{ + commsMutexId_t mutex; + comms_local_addr_t locals[MIST_COMM_ADDRCACHE_SIZE]; + ieee_eui64_t euis[MIST_COMM_ADDRCACHE_SIZE]; + uint32_t updated[MIST_COMM_ADDRCACHE_SIZE]; +}; + +#endif//MIST_COMM_ADDRCACHE_H_ diff --git a/am/mist_comm_am.c b/am/mist_comm_am.c new file mode 100644 index 0000000..d730e87 --- /dev/null +++ b/am/mist_comm_am.c @@ -0,0 +1,426 @@ +/** + * TinyOS ActiveMessage transport layer implementation. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_iface.h" +#include "mist_comm_ref.h" +#include "mist_comm_am.h" +#include "mist_comm_addrcache.h" + +#include +#include + +am_addr_t comms_am_address (comms_layer_t * comms) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + return amcomms->am_address(amcomms); +} + +static am_addr_t am_comms_addr (comms_layer_am_t * comms) +{ + return comms->am_addr; +} + +static void am_comms_init_message (comms_layer_iface_t * comms, comms_msg_t * msg) +{ + comms_am_msg_metadata_t * metadata = (comms_am_msg_metadata_t*)(msg->body.metadata); + memset(&(msg->body.destination), 0, sizeof(msg->body.destination)); + memset(&(msg->body.source), 0, sizeof(msg->body.source)); + msg->body.type = 0; + msg->body.group = 0; + msg->body.length = 0; + metadata->timestamp_valid = false; + metadata->event_time_valid = false; + metadata->ack_required = false; + metadata->ack_received = false; + metadata->timeout = 0; + metadata->retries = 0; + metadata->sent = 0; + metadata->lqi= 0; + metadata->rssi = -128; +} + +static bool am_comms_deliver (comms_layer_iface_t * iface, comms_msg_t * msg) +{ + comms_layer_t * comms = (comms_layer_t*)iface; + comms_layer_am_t * amcomms = (comms_layer_am_t*)iface; + am_addr_t dest = comms_am_get_destination(comms, msg); + comms_error_t result; + + // Attach the eui address of the source + if (false == comms_cache_get_eui(amcomms->cache, + &(msg->body.source.local), &(msg->body.source.eui))) + { + eui64_set_zeros(&(msg->body.destination.eui)); + } + + // Attach the eui address of the destination + if (AM_BROADCAST_ADDR == dest) + { + eui64_set_ones(&(msg->body.destination.eui)); + } + else if (amcomms->am_addr == dest) + { + memcpy(&(msg->body.destination.eui), &(comms->eui), sizeof(ieee_eui64_t)); + } + else if (false == comms_cache_get_eui(amcomms->cache, + &(msg->body.destination.local), &(msg->body.destination.eui))) + { + eui64_set_zeros(&(msg->body.destination.eui)); + } + + result = comms_basic_deliver(comms, msg); + + // Request eui source address discovery, if it was necessary for receiver + if (COMMS_NO_ADDR == result) + { + comms_am_addrdisco_discover_eui(amcomms->disco, comms_am_get_source(comms, msg)); + } + + return result == COMMS_SUCCESS; +} + +static comms_error_t am_comms_send (comms_layer_iface_t * iface, comms_msg_t * msg, comms_send_done_f * sdf, void * user) +{ + comms_layer_t * comms = (comms_layer_t*)iface; + comms_layer_am_t * amcomms = (comms_layer_am_t*)iface; + + if (0 == comms_am_get_destination(comms, msg)) // Need to see if there is something in the address cache + { + if (eui64_is_ones(&(msg->body.destination.eui))) + { + comms_am_set_destination(comms, msg, AM_BROADCAST_ADDR); + } + else if (false == comms_cache_get_local(amcomms->cache, + &(msg->body.destination.eui), &(msg->body.destination.local))) + { + return COMMS_NO_ADDR; // Nothing retrieved from cache + } + } + return amcomms->link_sendf(iface, msg, sdf, user); +} + +static am_id_t am_comms_get_packet_type (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return 0xFF & msg->body.type; +} + +static void am_comms_set_packet_type (comms_layer_iface_t * comms, comms_msg_t * msg, am_id_t ptype) +{ + msg->body.type = 0x3F00 + ptype; +} + +static uint16_t am_comms_get_packet_group (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return msg->body.group; +} + +static void am_comms_set_packet_group (comms_layer_iface_t * comms, comms_msg_t * msg, uint16_t group) +{ + msg->body.group = group; +} + +static uint8_t am_comms_get_payload_max_length (comms_layer_iface_t * iface) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)iface; + return amcomms->link_plenf(iface); +} + +static uint8_t am_comms_get_payload_length (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return msg->body.length; +} + +static void am_comms_set_payload_length (comms_layer_iface_t * comms, comms_msg_t * msg, uint8_t length) +{ + msg->body.length = length; +} + +static void * am_comms_get_payload (comms_layer_iface_t * comms, const comms_msg_t * msg, uint8_t length) +{ + if (length < sizeof(msg->body.payload)) + { + return (void*)(msg->body.payload); + } + return NULL; +} + +static uint8_t am_comms_get_retries (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->retries; +} + +static comms_error_t am_comms_set_retries (comms_layer_iface_t * comms, comms_msg_t * msg, uint8_t count) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->retries = count; + return COMMS_SUCCESS; +} + +static uint8_t am_comms_get_retries_used (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->sent; +} + +static comms_error_t am_comms_set_retries_used (comms_layer_iface_t * comms, comms_msg_t * msg, uint8_t count) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->sent = count; + return COMMS_SUCCESS; +} + +static uint32_t am_comms_get_timeout (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->timeout; +} + +static comms_error_t am_comms_set_timeout (comms_layer_iface_t * comms, comms_msg_t * msg, uint32_t timeout) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->timeout = timeout; + return COMMS_SUCCESS; +} + +static bool am_comms_is_ack_required (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->ack_required; +} + +static comms_error_t am_comms_set_ack_required (comms_layer_iface_t * comms, comms_msg_t * msg, bool required) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->ack_required = required; + return COMMS_SUCCESS; +} + +static bool am_comms_ack_received (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->ack_received; +} + +static void am_comms_set_ack_received (comms_layer_iface_t * comms, comms_msg_t * msg, bool acked) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->ack_received = acked; +} + +static comms_error_t am_comms_set_timestamp (comms_layer_iface_t * comms, comms_msg_t * msg, uint32_t timestamp) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->timestamp = timestamp; + ((comms_am_msg_metadata_t*)(msg->body.metadata))->timestamp_valid = true; + return COMMS_SUCCESS; +} + +static uint32_t am_comms_get_timestamp (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->timestamp; +} + +static bool am_comms_timestamp_valid (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->timestamp_valid; +} + +static comms_error_t am_comms_set_event_time (comms_layer_iface_t * comms, comms_msg_t * msg, uint32_t evt) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->event_time = evt; + ((comms_am_msg_metadata_t*)(msg->body.metadata))->event_time_valid = true; + return COMMS_SUCCESS; +} + +static uint32_t am_comms_get_event_time (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->event_time; +} + +static bool am_comms_event_time_valid (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->event_time_valid; +} + +static uint8_t am_comms_get_lqi (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->lqi; +} + +static void am_comms_set_lqi (comms_layer_iface_t * comms, comms_msg_t * msg, uint8_t lqi) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->lqi = lqi; +} + +static int8_t am_comms_get_rssi (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->rssi; +} + +static void am_comms_set_rssi (comms_layer_iface_t * comms, comms_msg_t * msg, int8_t rssi) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->rssi = rssi; +} + +static uint8_t am_comms_get_priority (comms_layer_iface_t * comms, const comms_msg_t * msg) +{ + if (true == ((comms_am_msg_metadata_t*)(msg->body.metadata))->priority_valid) + { + return ((comms_am_msg_metadata_t*)(msg->body.metadata))->priority; + } + else + { + return 0xFF; + } +} + +static void am_comms_set_priority (comms_layer_iface_t * comms, comms_msg_t * msg, uint8_t priority) +{ + ((comms_am_msg_metadata_t*)(msg->body.metadata))->priority_valid = true; + ((comms_am_msg_metadata_t*)(msg->body.metadata))->priority = priority; +} + +static am_addr_t am_comms_get_destination (comms_layer_am_t * comms, const comms_msg_t * msg) +{ +#ifdef ALIGN_CM0 + return *((__packed am_addr_t*)msg->body.destination.local.data); +#else + return *((am_addr_t*)msg->body.destination.local.data); +#endif +} + +static void am_comms_set_destination (comms_layer_am_t * comms, comms_msg_t * msg, am_addr_t dest) +{ + memset(&(msg->body.destination.local), 0, sizeof(msg->body.destination.local)); +#ifdef ALIGN_CM0 + *((__packed am_addr_t*)msg->body.destination.local.data) = dest; +#else + *((am_addr_t*)msg->body.destination.local.data) = dest; +#endif + msg->body.destination.updated = 0; // FIXME - need a second-based counter + //msg->body.destination.updated) = now_s(); +} + +static am_addr_t am_comms_get_source (comms_layer_am_t * comms, const comms_msg_t * msg) +{ +#ifdef ALIGN_CM0 + return *((__packed am_addr_t*)msg->body.source.local.data); +#else + return *((am_addr_t*)msg->body.source.local.data); +#endif +} + +static void am_comms_set_source (comms_layer_am_t * comms, comms_msg_t * msg, am_addr_t source) +{ + memset(&(msg->body.source.local), 0, sizeof(msg->body.source.local)); +#ifdef ALIGN_CM0 + *((__packed am_addr_t*)msg->body.source.local.data) = source; +#else + *((am_addr_t*)msg->body.source.local.data) = source; +#endif + msg->body.source.updated = 0; // FIXME - need a second-based counter + //msg->body.source.updated = now_s(); +} + +am_addr_t comms_am_get_destination (comms_layer_t * comms, const comms_msg_t * msg) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + return amcomms->am_get_destination(amcomms, msg); +} + +void comms_am_set_destination (comms_layer_t * comms, comms_msg_t * msg, am_addr_t dest) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + amcomms->am_set_destination(amcomms, msg, dest); +} + +am_addr_t comms_am_get_source (comms_layer_t * comms, const comms_msg_t * msg) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + return amcomms->am_get_source(amcomms, msg); +} + +void comms_am_set_source (comms_layer_t * comms, comms_msg_t * msg, am_addr_t source) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + amcomms->am_set_source(amcomms, msg, source); +} + +comms_error_t comms_am_create (comms_layer_t * layer, am_addr_t address, + comms_send_f * sendf, comms_plen_f * plenf, + comms_get_time_micro_f * gtmf, + comms_start_f * startf, comms_stop_f * stopf) +{ + + comms_layer_iface_t * comms = (comms_layer_iface_t*)layer; + comms_layer_am_t * amcomms = (comms_layer_am_t*)layer; + + comms->start = startf; + comms->stop = stopf; + + comms->init_message = &am_comms_init_message; + + amcomms->link_sendf = sendf; + comms->sendf = &am_comms_send; + + comms->deliverf = &am_comms_deliver; + + comms->start_stop_mutex = comms_mutex_create(); + comms->controller_mutex = comms_mutex_create(); + comms->receiver_mutex = comms_mutex_create(); + + comms_initialize_rcvr_management(comms); + + comms->sleep_controller_deferred = NULL; + + comms->get_packet_type = &am_comms_get_packet_type; + comms->set_packet_type = &am_comms_set_packet_type; + + comms->get_packet_group = &am_comms_get_packet_group; + comms->set_packet_group = &am_comms_set_packet_group; + + amcomms->link_plenf = plenf; + comms->get_payload_max_length = &am_comms_get_payload_max_length; + comms->get_payload_length = &am_comms_get_payload_length; + comms->set_payload_length = &am_comms_set_payload_length; + comms->get_payload = &am_comms_get_payload; + + comms->get_retries = &am_comms_get_retries; + comms->set_retries = &am_comms_set_retries; + + comms->get_retries_used = &am_comms_get_retries_used; + comms->set_retries_used = &am_comms_set_retries_used; + + comms->get_timeout = &am_comms_get_timeout; + comms->set_timeout = &am_comms_set_timeout; + + comms->is_ack_required = &am_comms_is_ack_required; + comms->set_ack_required = &am_comms_set_ack_required; + comms->ack_received = &am_comms_ack_received; + comms->set_ack_received = &am_comms_set_ack_received; + + comms->get_time_micro = gtmf; + + comms->set_timestamp = &am_comms_set_timestamp; + comms->get_timestamp = &am_comms_get_timestamp; + comms->timestamp_valid = &am_comms_timestamp_valid; + + comms->set_event_time = &am_comms_set_event_time; + comms->get_event_time = &am_comms_get_event_time; + comms->event_time_valid = &am_comms_event_time_valid; + + comms->get_lqi = &am_comms_get_lqi; + comms->set_lqi = &am_comms_set_lqi; + + comms->get_rssi = &am_comms_get_rssi; + comms->set_rssi = &am_comms_set_rssi; + + comms->get_priority = &am_comms_get_priority; + comms->set_priority = &am_comms_set_priority; + + amcomms->am_get_destination = &am_comms_get_destination; + amcomms->am_set_destination = &am_comms_set_destination; + amcomms->am_get_source = &am_comms_get_source; + amcomms->am_set_source = &am_comms_set_source; + + amcomms->am_address = &am_comms_addr; + amcomms->am_addr = address; + + amcomms->cache = NULL; + + return COMMS_SUCCESS; +} diff --git a/am/mist_comm_am_addrdisco.c b/am/mist_comm_am_addrdisco.c new file mode 100644 index 0000000..3eca427 --- /dev/null +++ b/am/mist_comm_am_addrdisco.c @@ -0,0 +1,221 @@ +/** + * EUI and link-local address discovery implementation for + * the TinyOS ActiveMessage transport layer. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_am_addrdisco_protocol.h" +#include "mist_comm_am_addrdisco.h" +#include "mist_comm_am.h" + +#include + +#include "loglevels.h" +#define __MODUUL__ "addrd" +#define __LOG_LEVEL__ (LOG_LEVEL_mist_comm_am_addrdisco & BASE_LOG_LEVEL) +#include "log.h" +#include "sys_panic.h" + +#define AM_ADDRDISCO_FLAG_RESPOND (1 << 0) +#define AM_ADDRDISCO_FLAG_DISCOVER (1 << 1) +#define AM_ADDRDISCO_FLAG_SENT (1 << 2) +#define AM_ADDRDISCO_FLAGS (7) + +static void receive_message (comms_layer_t * comms, const comms_msg_t * msg, void * user) +{ + am_addrdisco_t * disco = (am_addrdisco_t*)user; + + uint8_t length = comms_get_payload_length(comms, msg); + if (length >= sizeof(am_addrdisco_packet_t)) + { + am_addrdisco_packet_t * packet = (am_addrdisco_packet_t*)comms_get_payload(comms, msg, sizeof(am_addrdisco_packet_t)); + + debugb1("rcv", packet, sizeof(am_addrdisco_packet_t)); + + switch(packet->header) + { + case GUIDDISCOVERY_REQUEST: + { + am_addr_t dest = comms_am_get_destination(comms, msg); + ieee_eui64_t eui; + eui64_set(&eui, packet->guid); + if ( // Broadcast and my EUI64 + ((AM_BROADCAST_ADDR == dest)&&(0 == eui64_compare(&eui, &(comms->eui)))) + || // My address and FFFFFFFFFFFFFFFF + ((comms_am_address(comms) == dest)&&eui64_is_ones(&eui)) + || // My address and my EUI64 + ((comms_am_address(comms) == dest)&&(0 == eui64_compare(&eui, &(comms->eui)))) + ) + { + // Must respond + am_addr_t source = comms_am_get_source(comms, msg); + if((0 != disco->respond)&&(source != disco->respond)) + { + // A response is pending for someone else, might as well send to broadcast + disco->respond = AM_BROADCAST_ADDR; + } + else + { + disco->respond = source; + } + + debug1("rq %04X", (unsigned int)disco->respond); + + osThreadFlagsSet(disco->thread, 1); + } + } + break; + + case GUIDDISCOVERY_RESPONSE: + { + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + comms_address_t source; + ieee_eui64_t eui; + comms_get_source(comms, msg, &source); + eui64_set(&eui, packet->guid); + + debugb1("rp %04X", packet->guid, IEEE_EUI64_LENGTH, comms_am_get_source(comms, msg)); + + comms_cache_update(amcomms->cache, &eui, &(source.local)); + } + break; + + default: + warnb1("?", packet, length); + break; + } + } + else warn1("size %u", (unsigned int)comms_get_payload_length(comms, msg)); +} + +static void radio_send_done (comms_layer_t * comms, comms_msg_t * msg, comms_error_t result, void * user) +{ + am_addrdisco_t * disco = (am_addrdisco_t*)user; + logger(result == COMMS_SUCCESS ? LOG_DEBUG1: LOG_WARN1, "snt %d", (int)result); + osThreadFlagsSet(disco->thread, AM_ADDRDISCO_FLAG_SENT); +} + +static void addrdisco_loop (void * arg) +{ + am_addrdisco_t * disco = (am_addrdisco_t*)arg; + + for(;;) + { + am_addr_t dest = 0; + + comms_init_message(disco->comms, &(disco->msg)); + am_addrdisco_packet_t * packet = comms_get_payload(disco->comms, &(disco->msg), sizeof(am_addrdisco_packet_t)); + + if (NULL == packet) + { + sys_panic("pckt"); + } + + while (osOK != osMutexAcquire(disco->mutex, osWaitForever)); + + if(0 != disco->respond) + { + dest = disco->respond; + packet->header = GUIDDISCOVERY_RESPONSE; + eui64_get(&(disco->comms->eui), packet->guid); + disco->respond = 0; + } + else if(0 != disco->query_addr) + { + dest = disco->query_addr; + packet->header = GUIDDISCOVERY_REQUEST; + memset(packet->guid, 0xff, sizeof(packet->guid)); + disco->query_addr = 0; + } + else if(false == eui64_is_zeros(&(disco->query_eui))) + { + dest = AM_BROADCAST_ADDR; + packet->header = GUIDDISCOVERY_REQUEST; + eui64_get(&(disco->query_eui), packet->guid); + eui64_set_zeros(&(disco->query_eui)); + } + + osMutexRelease(disco->mutex); + + if (0 != dest) + { + comms_am_set_destination(disco->comms, &(disco->msg), dest); + + comms_set_packet_type(disco->comms, &(disco->msg), AMID_ADDRESS_DISCOVERY); + comms_set_payload_length(disco->comms, &(disco->msg), sizeof(am_addrdisco_packet_t)); + + comms_error_t result = comms_send(disco->comms, &(disco->msg), radio_send_done, disco); + logger(result == COMMS_SUCCESS ? LOG_DEBUG1: LOG_WARN1, "snd %d", (int)result); + if (COMMS_SUCCESS == result) + { + osThreadFlagsWait(AM_ADDRDISCO_FLAG_SENT, osFlagsWaitAny, osWaitForever); + } + + // Message has been sent, now take a break for a bit + osDelay(ADDRDISCO_BACKOFF_MS); + } + else // No pending activities, wait for something to happen + { + osThreadFlagsWait(AM_ADDRDISCO_FLAGS, osFlagsWaitAny, osWaitForever); + } + } +} + +void comms_am_addrdisco_init (comms_layer_t * comms, am_addrdisco_t * disco, comms_addr_cache_t * cache) +{ + comms_layer_am_t * amcomms = (comms_layer_am_t*)comms; + disco->comms = comms; + disco->respond = 0; + + amcomms->disco = disco; + + comms_cache_init(cache); + amcomms->cache = cache; + + comms_register_recv(comms, &(disco->rcvr), receive_message, disco, AMID_ADDRESS_DISCOVERY); + + const osMutexAttr_t app_mutex_attr = { .attr_bits = osMutexPrioInherit }; + disco->mutex = osMutexNew(&app_mutex_attr); + + const osThreadAttr_t app_thread_attr = { .name = "disco", .stack_size = 1536 }; + disco->thread = osThreadNew(addrdisco_loop, disco, &app_thread_attr); +} + +void comms_am_addrdisco_deinit (comms_layer_t * comms) +{ + // TODO implement deinit + // Stop the thread, wait out any ongoing send + // Free the mutex + // Remove the receiver + sys_panic("TODO"); +} + +void comms_am_addrdisco_discover_eui (am_addrdisco_t * disco, am_addr_t local) +{ + if (NULL != disco) + { + while(osOK != osMutexAcquire(disco->mutex, osWaitForever)); + if(0 == disco->query_addr) + { + disco->query_addr = local; + } + osMutexRelease(disco->mutex); + osThreadFlagsSet(disco->thread, AM_ADDRDISCO_FLAG_DISCOVER); + } +} + +void comms_am_addrdisco_discover_local (am_addrdisco_t * disco, ieee_eui64_t * eui) +{ + if (NULL != disco) + { + while(osOK != osMutexAcquire(disco->mutex, osWaitForever)); + if(eui64_is_zeros(&(disco->query_eui))) + { + memcpy(&(disco->query_eui), eui, sizeof(ieee_eui64_t)); + } + osMutexRelease(disco->mutex); + osThreadFlagsSet(disco->thread, AM_ADDRDISCO_FLAG_DISCOVER); + } +} diff --git a/am/mist_comm_am_addrdisco.h b/am/mist_comm_am_addrdisco.h new file mode 100644 index 0000000..961037e --- /dev/null +++ b/am/mist_comm_am_addrdisco.h @@ -0,0 +1,58 @@ +/** + * EUI and link-local address discovery for the TinyOS ActiveMessage transport layer. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_AM_ADDRDISCO_H_ +#define MIST_COMM_AM_ADDRDISCO_H_ + +#include "mist_comm.h" +#include "mist_comm_iface.h" +#include "mist_comm_addrcache.h" + +#ifndef ADDRDISCO_BACKOFF_MS +#define ADDRDISCO_BACKOFF_MS 30000UL +#endif//ADDRDISCO_BACKOFF_MS + +typedef struct am_addrdisco am_addrdisco_t; + +/** + * Initialize address discovery and address cache. + * Should be called only once for a single comms layer. + * + * @param comms The comms layer for which to initialize discovery and cache. + * Needs to be of the AM type or a derivative. + * @param disco Memory for the discovery module. + * @param cache Memory for the cache module. + */ +void comms_am_addrdisco_init (comms_layer_t * comms, am_addrdisco_t * disco, comms_addr_cache_t * cache); + +/** + * Remove from layer and deinitialize address discovery and address cache. + * + * @param comms The comms layer from which to remove disco and cache modules. + */ +void comms_am_addrdisco_deinit (comms_layer_t * comms); + +/** + * Request that the EUI be discovered for the local address. + * + * @param disco The discovery component to use. + * @param local The local ActiveMessage address for which the EUI is needed. + */ +void comms_am_addrdisco_discover_eui (am_addrdisco_t * disco, am_addr_t local); + +/** + * Request that the local address be discovered for the EUI. + * + * @param disco The discovery component to use. + * @param local The EUI address for which the local address is needed. + */ +void comms_am_addrdisco_discover_local (am_addrdisco_t * disco, ieee_eui64_t * eui); + + +// Include implementation details (size info for allocation) +#include "mist_comm_am_addrdisco_private.h" + +#endif//MIST_COMM_AM_ADDRDISCO_H_ diff --git a/am/mist_comm_am_addrdisco_private.h b/am/mist_comm_am_addrdisco_private.h new file mode 100644 index 0000000..074982e --- /dev/null +++ b/am/mist_comm_am_addrdisco_private.h @@ -0,0 +1,28 @@ +/** + * Active Message address discovery object structure. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_AM_ADDRDISCO_PRIVATE_H_ +#define MIST_COMM_AM_ADDRDISCO_PRIVATE_H_ + +struct am_addrdisco +{ + comms_layer_t * comms; + + comms_receiver_t rcvr; + + comms_msg_t msg; // Memory for sending message + + am_addr_t respond; // Who to respond with my EUI + + // TODO Discoveries should probably have a queue + am_addr_t query_addr; // Whose EUI needs to be discovered + ieee_eui64_t query_eui; // Whose local address needs to be discovered + + osMutexId_t mutex; + osThreadId_t thread; // TODO having a dedicated thread here is wasteful +}; + +#endif//MIST_COMM_AM_ADDRDISCO_PRIVATE_H_ diff --git a/am/mist_comm_am_addrdisco_protocol.h b/am/mist_comm_am_addrdisco_protocol.h new file mode 100644 index 0000000..949f4af --- /dev/null +++ b/am/mist_comm_am_addrdisco_protocol.h @@ -0,0 +1,34 @@ +/** + * EUI vs link-local address discovery protocol for Mist over ActiveMessage. + * + * The message is sent to broadcast with a specific EUI64 in the payload + * to discover the link-local ActiveMessage address. + * The message is sent to unicast with the guid field set to all ones, to + * discover the EUI64 address. + * A broadcast message with the guid field set to all ones is forbidden. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_AM_ADDRDISCO_PROTOCOL_H_ +#define MIST_COMM_AM_ADDRDISCO_PROTOCOL_H_ + +#include "eui64.h" + +#define AMID_ADDRESS_DISCOVERY 0xFC + +enum GuidDiscoveryHeaders +{ + GUIDDISCOVERY_REQUEST = 1, + GUIDDISCOVERY_RESPONSE = 2 +}; + +#pragma pack(push, 1) +typedef struct am_addrdisco_packet +{ + uint8_t header; + uint8_t guid[IEEE_EUI64_LENGTH]; +} am_addrdisco_packet_t; +#pragma pack(pop) + +#endif//MIST_COMM_AM_ADDRDISCO_PROTOCOL_H_ diff --git a/api/mist_comm_api.c b/api/mist_comm_api.c new file mode 100644 index 0000000..fea0d33 --- /dev/null +++ b/api/mist_comm_api.c @@ -0,0 +1,495 @@ +/** + * Mist communications API. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm.h" +#include "mist_comm_iface.h" + +#include +#include + +volatile uint8_t MIST_COMM_VERSION_PASTER(g_mist_comm_version_major_, MIST_COMM_VERSION_MAJOR) = MIST_COMM_VERSION_MAJOR; +volatile uint8_t MIST_COMM_VERSION_PASTER(g_mist_comm_version_minor_, MIST_COMM_VERSION_MINOR) = MIST_COMM_VERSION_MINOR; + +static void comms_status_change_callback (comms_layer_t * comms, comms_status_t status, void * user) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + + comms_mutex_acquire(cl->start_stop_mutex); + cl->status_change_user_cb(comms, status, cl->status_change_user); + cl->status = status; + cl->status_change_user_cb = NULL; + cl->status_change_user = NULL; + comms_mutex_release(cl->start_stop_mutex); +} + +comms_error_t comms_start (comms_layer_t * comms, comms_status_change_f* start_done, void * user) +{ + if ((NULL != comms)&&(NULL != start_done)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + if (NULL != cl->start) + { + comms_mutex_acquire(cl->start_stop_mutex); + if (NULL != cl->status_change_user_cb) + { + comms_mutex_release(cl->start_stop_mutex); + return COMMS_EBUSY; + } + else if (COMMS_STARTED == cl->status) + { + comms_mutex_release(cl->start_stop_mutex); + return COMMS_ALREADY; + } + else + { + comms_status_t status = cl->status; + cl->status = COMMS_STARTING; + cl->status_change_user_cb = start_done; + cl->status_change_user = user; + comms_mutex_release(cl->start_stop_mutex); + + comms_error_t err = cl->start(cl, &comms_status_change_callback, NULL); + if (COMMS_SUCCESS != err) + { + comms_mutex_acquire(cl->start_stop_mutex); + cl->status = status; + cl->status_change_user_cb = NULL; + cl->status_change_user = NULL; + comms_mutex_release(cl->start_stop_mutex); + } + return err; + } + } + } + return COMMS_EINVAL; +} + +comms_error_t comms_stop (comms_layer_t * comms, comms_status_change_f* stop_done, void * user) +{ + if ((NULL != comms)&&(NULL != stop_done)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + if (NULL != cl->stop) + { + comms_mutex_acquire(cl->start_stop_mutex); + if (NULL != cl->status_change_user_cb) + { + comms_mutex_release(cl->start_stop_mutex); + return COMMS_EBUSY; + } + else if (COMMS_STOPPED == cl->status) + { + comms_mutex_release(cl->start_stop_mutex); + return COMMS_ALREADY; + } + else + { + comms_status_t status = cl->status; + cl->status = COMMS_STOPPING; + cl->status_change_user_cb = stop_done; + cl->status_change_user = user; + comms_mutex_release(cl->start_stop_mutex); + + comms_error_t err = cl->stop(cl, &comms_status_change_callback, NULL); + if (COMMS_SUCCESS != err) + { + comms_mutex_acquire(cl->start_stop_mutex); + cl->status = status; + cl->status_change_user_cb = NULL; + cl->status_change_user = NULL; + comms_mutex_release(cl->start_stop_mutex); + } + return err; + } + } + } + return COMMS_EINVAL; +} + +comms_status_t comms_status (comms_layer_t * comms) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + comms_status_t status; + comms_mutex_acquire(cl->start_stop_mutex); + status = cl->status; + comms_mutex_release(cl->start_stop_mutex); + return status; +} + +void comms_init_message (comms_layer_t * comms, comms_msg_t * msg) +{ + if ((NULL != comms)&&(NULL != msg)) + { + ((comms_layer_iface_t*)comms)->init_message((comms_layer_iface_t*)comms, msg); + } +} + +void comms_get_destination (comms_layer_t * comms, const comms_msg_t * msg, comms_address_t * destination) +{ + memcpy(destination, &(msg->body.destination), sizeof(comms_address_t)); +} + +void comms_set_destination (comms_layer_t * comms, comms_msg_t * msg, const comms_address_t * destination) +{ + memcpy(&(msg->body.destination), destination, sizeof(comms_address_t)); +} + +void comms_get_source (comms_layer_t * comms, const comms_msg_t * msg, comms_address_t * source) +{ + memcpy(source, &(msg->body.source), sizeof(comms_address_t)); +} + +void comms_set_source (comms_layer_t * comms, comms_msg_t * msg, const comms_address_t * source) +{ + memcpy(&(msg->body.source), source, sizeof(comms_address_t)); +} + +comms_error_t comms_send (comms_layer_t * comms, comms_msg_t * msg, comms_send_done_f* sdf, void * user) +{ + if (NULL != comms) + { + return ((comms_layer_iface_t*)comms)->sendf((comms_layer_iface_t*)comms, msg, sdf, user); + } + return COMMS_EINVAL; +} + +bool comms_deliver (comms_layer_t * comms, comms_msg_t * msg) +{ + comms_layer_iface_t * iface = (comms_layer_iface_t*)comms; + if ((iface != NULL)&&(iface->deliverf != NULL)) + { + return iface->deliverf(iface, msg); + } + return false; +} + +comms_error_t comms_register_recv (comms_layer_t * comms, comms_receiver_t* rcvr, comms_receive_f* func, void * user, am_id_t amid) +{ + if ((NULL != comms)&&(rcvr != NULL)&&(func != NULL)) + { + return ((comms_layer_iface_t*)comms)->register_recv((comms_layer_iface_t*)comms, rcvr, func, user, amid, false); + } + return COMMS_EINVAL; +} +comms_error_t comms_register_recv_eui (comms_layer_t * comms, comms_receiver_t* rcvr, comms_receive_f* func, void * user, am_id_t amid) +{ + if ((NULL != comms)&&(rcvr != NULL)&&(func != NULL)) + { + return ((comms_layer_iface_t*)comms)->register_recv((comms_layer_iface_t*)comms, rcvr, func, user, amid, true); + } + return COMMS_EINVAL; +} +comms_error_t comms_deregister_recv (comms_layer_t * comms, comms_receiver_t* rcvr) +{ + if (NULL != comms) + { + return ((comms_layer_iface_t*)comms)->deregister_recv((comms_layer_iface_t*)comms, rcvr); + } + return COMMS_EINVAL; +} + +comms_error_t comms_register_snooper (comms_layer_t * comms, comms_receiver_t* rcvr, comms_receive_f* func, void * user) +{ + if ((NULL != comms)&&(rcvr != NULL)&&(func != NULL)) + { + return ((comms_layer_iface_t*)comms)->register_snooper((comms_layer_iface_t*)comms, rcvr, func, user); + } + return COMMS_EINVAL; +} +comms_error_t comms_deregister_snooper (comms_layer_t * comms, comms_receiver_t* rcvr) +{ + if (NULL != comms) + { + return ((comms_layer_iface_t*)comms)->deregister_snooper((comms_layer_iface_t*)comms, rcvr); + } + return COMMS_EINVAL; +} + +am_id_t comms_get_packet_type (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_packet_type(cl, msg); + } + return 0; +} +void comms_set_packet_type (comms_layer_t * comms, comms_msg_t * msg, am_id_t ptype) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_packet_type(cl, msg, ptype); + } +} + +uint16_t comms_get_packet_group (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_packet_group(cl, msg); + } + return 0; +} +void comms_set_packet_group (comms_layer_t * comms, comms_msg_t * msg, uint16_t group) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_packet_group(cl, msg, group); + } +} + +uint8_t comms_get_payload_max_length (comms_layer_t * comms) +{ + return ((comms_layer_iface_t*)comms)->get_payload_max_length((comms_layer_iface_t*)comms); +} + +uint8_t comms_get_payload_length (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_payload_length(cl, msg); + } + return 0; +} +void comms_set_payload_length (comms_layer_t * comms, comms_msg_t * msg, uint8_t length) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_payload_length(cl, msg, length); + } +} +void * comms_get_payload (comms_layer_t * comms, const comms_msg_t * msg, uint8_t length) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_payload(cl, msg, length); + } + return NULL; +} + +uint8_t comms_get_retries (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_retries(cl, msg); + } + return 0; +} +comms_error_t comms_set_retries (comms_layer_t * comms, comms_msg_t * msg, uint8_t count) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_retries(cl, msg, count); + } + return COMMS_EINVAL; +} + +uint8_t comms_get_retries_used (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_retries_used(cl, msg); + } + return 0; +} +comms_error_t comms_set_retries_used (comms_layer_t * comms, comms_msg_t * msg, uint8_t count) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_retries_used(cl, msg, count); + } + return COMMS_EINVAL; +} + +uint32_t comms_get_timeout (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_timeout(cl, msg); + } + return 0; +} +comms_error_t comms_set_timeout (comms_layer_t * comms, comms_msg_t * msg, uint32_t timeout) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_timeout(cl, msg, timeout); + } + return COMMS_EINVAL; +} + +bool comms_is_ack_required (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->is_ack_required(cl, msg); + } + return false; +} +comms_error_t comms_set_ack_required (comms_layer_t * comms, comms_msg_t * msg, bool required) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_ack_required(cl, msg, required); + } + return COMMS_EINVAL; +} +bool comms_ack_received (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->ack_received(cl, msg); + } + return false; +} +void comms_set_ack_received (comms_layer_t * comms, comms_msg_t * msg, bool received) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_ack_received(cl, msg, received); + } +} + +uint32_t comms_get_time_micro (comms_layer_t * comms) +{ + if (NULL != comms) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + if (NULL != cl->get_time_micro) + { + return cl->get_time_micro(cl); + } + } + return 0; +} + +comms_error_t comms_set_timestamp_micro (comms_layer_t * comms, comms_msg_t * msg, uint32_t timestamp) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_timestamp(cl, msg, timestamp); + } + return COMMS_EINVAL; +} +uint32_t comms_get_timestamp_micro (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_timestamp(cl, msg); + } + return 0; +} +bool comms_timestamp_valid (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->timestamp_valid(cl, msg); + } + return false; +} + +comms_error_t comms_set_event_time_micro (comms_layer_t * comms, comms_msg_t * msg, uint32_t evt) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->set_event_time(cl, msg, evt); + } + return COMMS_EINVAL; +} +uint32_t comms_get_event_time_micro (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_event_time(cl, msg); + } + return 0; +} + +bool comms_event_time_valid (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->event_time_valid(cl, msg); + } + return false; +} + +uint8_t comms_get_lqi (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_lqi(cl, msg); + } + return 0; +} +void comms_set_lqi (comms_layer_t * comms, comms_msg_t * msg, uint8_t lqi) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_lqi(cl, msg, lqi); + } +} + +int8_t comms_get_rssi (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_rssi(cl, msg); + } + return -128; +} +void comms_set_rssi (comms_layer_t * comms, comms_msg_t * msg, int8_t rssi) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_rssi(cl, msg, rssi); + } +} + +int8_t comms_get_priority (comms_layer_t * comms, const comms_msg_t * msg) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + return cl->get_priority(cl, msg); + } + return 0xFF; +} +void comms_set_priority (comms_layer_t * comms, comms_msg_t * msg, int8_t priority) +{ + if ((NULL != msg)&&(NULL != comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + cl->set_priority(cl, msg, priority); + } +} diff --git a/api/mist_comm_rcv.c b/api/mist_comm_rcv.c new file mode 100644 index 0000000..2d43d00 --- /dev/null +++ b/api/mist_comm_rcv.c @@ -0,0 +1,140 @@ +#include "mist_comm_iface.h" +#include + +static comms_error_t rcv_comms_register_recv(comms_layer_iface_t* comms, + comms_receiver_t* rcvr, + comms_receive_f* func, void* user, + am_id_t amid, bool require_eui) { + comms_receiver_t** indirect; + + comms_mutex_acquire(comms->receiver_mutex); + + for(indirect=&(comms->receivers); NULL != *indirect; indirect = &((*indirect)->next)) { + if(*indirect == rcvr) { + comms_mutex_release(comms->receiver_mutex); + return COMMS_ALREADY; + } + } + *indirect = rcvr; + + rcvr->type = amid; + rcvr->callback = func; + rcvr->user = user; + rcvr->eui = require_eui; + rcvr->next = NULL; + + comms_mutex_release(comms->receiver_mutex); + return COMMS_SUCCESS; +} + +static comms_error_t rcv_comms_register_snooper(comms_layer_iface_t* comms, comms_receiver_t* rcvr, comms_receive_f* func, void* user) { + comms_receiver_t** indirect; + + comms_mutex_acquire(comms->receiver_mutex); + + for(indirect=&(comms->snoopers); NULL != *indirect; indirect = &((*indirect)->next)) { + if(*indirect == rcvr) { + comms_mutex_release(comms->receiver_mutex); + return COMMS_ALREADY; + } + } + *indirect = rcvr; + + rcvr->type = 0; // Not used + rcvr->callback = func; + rcvr->user = user; + rcvr->eui = false; + rcvr->next = NULL; + + comms_mutex_release(comms->receiver_mutex); + return COMMS_SUCCESS; +} + +static comms_error_t rcv_comms_deregister_recv(comms_layer_iface_t* comms, comms_receiver_t* rcvr) { + comms_receiver_t** indirect; + + comms_mutex_acquire(comms->receiver_mutex); + + for(indirect=&(comms->receivers); NULL != *indirect; indirect = &((*indirect)->next)) { + if(*indirect == rcvr) { + *indirect = rcvr->next; + comms_mutex_release(comms->receiver_mutex); + return COMMS_SUCCESS; + } + } + comms_mutex_release(comms->receiver_mutex); + return COMMS_FAIL; +} + +static comms_error_t rcv_comms_deregister_snooper(comms_layer_iface_t* comms, comms_receiver_t* rcvr) { + comms_receiver_t** indirect; + + comms_mutex_acquire(comms->receiver_mutex); + + for(indirect=&(comms->snoopers); NULL != *indirect; indirect = &((*indirect)->next)) { + if(*indirect == rcvr) { + *indirect = rcvr->next; + comms_mutex_release(comms->receiver_mutex); + return COMMS_SUCCESS; + } + } + comms_mutex_release(comms->receiver_mutex); + return COMMS_FAIL; +} + +comms_error_t comms_basic_deliver(comms_layer_t* layer, comms_msg_t* msg) { + comms_layer_iface_t* comms = (comms_layer_iface_t*)layer; + am_id_t ptype = comms_get_packet_type(layer, msg); + comms_receiver_t* receiver; + bool delivered = false; + bool resolve = false; + + comms_mutex_acquire(comms->receiver_mutex); + + // Receivers filter based on type + for(receiver=comms->receivers;receiver!=NULL;receiver=receiver->next) { + if(receiver->type == ptype) { + // Receiver needs the packet to have an EUI64 source + if(receiver->eui) + { + comms_address_t source; + comms_get_source(layer, msg, &source); + if(eui64_is_zeros(&(source.eui))) + { + resolve = true; + } + } + delivered = true; + receiver->callback(layer, msg, receiver->user); + } + } + + // Snoopers get everyting and don't mark delivered to true + for(receiver=comms->snoopers;receiver!=NULL;receiver=receiver->next) { + receiver->callback(layer, msg, receiver->user); + } + + comms_mutex_release(comms->receiver_mutex); + + if(resolve) + { + return COMMS_NO_ADDR; + } + if(delivered) + { + return COMMS_SUCCESS; + } + return COMMS_FAIL; +} + +comms_error_t comms_initialize_rcvr_management(comms_layer_iface_t* comms) { + comms_mutex_acquire(comms->receiver_mutex); + comms->receivers = NULL; + comms->snoopers = NULL; + comms->register_recv = &rcv_comms_register_recv; + comms->deregister_recv = &rcv_comms_deregister_recv; + comms->register_snooper = &rcv_comms_register_snooper; + comms->deregister_snooper = &rcv_comms_deregister_snooper; + comms_mutex_release(comms->receiver_mutex); + return COMMS_SUCCESS; +} diff --git a/bridge/mist_comm_bridge.c b/bridge/mist_comm_bridge.c new file mode 100644 index 0000000..e961ddf --- /dev/null +++ b/bridge/mist_comm_bridge.c @@ -0,0 +1,153 @@ +/** + * MistComm bridge implementation. + * Bridge messages between 2 mist-comm communication interfaces. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include + +#include "cmsis_os2.h" +#include "mist_comm.h" +#include "mist_comm_am.h" +#include "mist_comm_bridge.h" + +#include "loglevels.h" +#define __MODUUL__ "bridge" +#define __LOG_LEVEL__ (LOG_LEVEL_bridge & BASE_LOG_LEVEL) +#include "log.h" + +#define MIST_COMM_BRIDGE_FLAG_SEND_DONE (1 << 0) +#define MIST_COMM_BRIDGE_FLAG_TERMINATE (1 << 1) + +static void bridge_thread(void * param); +static void bridge_send_done(comms_layer_t * comms, comms_msg_t * msg, comms_error_t result, void * user); +static void bridge_am_snoop(comms_layer_t* comms, const comms_msg_t* msg, void* user); + +comms_error_t comms_bridge_init (comms_bridge_t * bridge, + comms_layer_t * a, + comms_layer_t * b) +{ + bridge->t1.rxqueue = osMessageQueueNew(10, sizeof(comms_msg_t), NULL); + bridge->t2.rxqueue = osMessageQueueNew(10, sizeof(comms_msg_t), NULL); + bridge->t1.txqueue = bridge->t2.rxqueue; + bridge->t2.txqueue = bridge->t1.rxqueue; + + const osThreadAttr_t bridge_thread_1_attr = { .name = "bt1" }; + bridge->t1.thread = osThreadNew(bridge_thread, &(bridge->t1), &bridge_thread_1_attr); + debug1("bt1 %p", bridge->t1.thread); + + const osThreadAttr_t bridge_thread_2_attr = { .name = "bt2" }; + bridge->t2.thread = osThreadNew(bridge_thread, &(bridge->t2), &bridge_thread_2_attr); + debug1("bt2 %p", bridge->t2.thread); + + bridge->t1.layer = a; + bridge->t2.layer = b; + + // set up snoopers + comms_register_snooper(bridge->t1.layer, &(bridge->t1.receiver), &bridge_am_snoop, &(bridge->t1)); + comms_register_snooper(bridge->t2.layer, &(bridge->t2.receiver), &bridge_am_snoop, &(bridge->t2)); + + return COMMS_SUCCESS; +} + +void comms_bridge_deinit (comms_bridge_t * bridge) +{ + // remove snoopers + comms_deregister_snooper(bridge->t1.layer, &(bridge->t1.receiver)); + comms_deregister_snooper(bridge->t2.layer, &(bridge->t2.receiver)); + + // stop threads + osThreadFlagsSet(bridge->t1.thread, MIST_COMM_BRIDGE_FLAG_TERMINATE); + osThreadFlagsSet(bridge->t2.thread, MIST_COMM_BRIDGE_FLAG_TERMINATE); + + // wait for threads to exit + while (osThreadTerminated != osThreadGetState(bridge->t1.thread)) + { + osDelay(1); + } + while (osThreadTerminated != osThreadGetState(bridge->t2.thread)) + { + osDelay(1); + } + + // empty the queues + while (osOK == osMessageQueueGet(bridge->t1.rxqueue, &(bridge->t1.msg), NULL, 0)) + { + // it has been removed, nothing else to do with it now, memory is handled by queue + } + while (osOK == osMessageQueueGet(bridge->t2.rxqueue, &(bridge->t2.msg), NULL, 0)) + { + // it has been removed, nothing else to do with it now, memory is handled by queue + } + + // delete the queues + osMessageQueueDelete(bridge->t1.rxqueue); + osMessageQueueDelete(bridge->t2.rxqueue); +} + +static void bridge_send_done (comms_layer_t * comms, comms_msg_t * msg, comms_error_t result, void * user) +{ + comms_bridge_thread_t * bt = (comms_bridge_thread_t*)user; + logger(result == COMMS_SUCCESS ? LOG_DEBUG3: LOG_WARN1, "%p snt %d", msg, result); + osThreadFlagsSet(bt->thread, MIST_COMM_BRIDGE_FLAG_SEND_DONE); +} + +static void bridge_am_snoop (comms_layer_t* comms, const comms_msg_t* msg, void* user) +{ + comms_bridge_thread_t * bt = (comms_bridge_thread_t*)user; + + #if (__LOG_LEVEL__ & LOG_DEBUG2) + uint8_t plen = comms_get_payload_length(comms, msg); + uint8_t* payload = comms_get_payload(comms, msg, plen); + debugb2("rcv %d", payload, plen, (unsigned int)plen); + #endif//debug + + if(osMessageQueuePut(bt->rxqueue, msg, 0, 0) != osOK) + { + warn1("drop %p", user); + } +} + +static void bridge_thread (void * param) +{ + comms_bridge_thread_t * bt = (comms_bridge_thread_t*)param; + + for(;;) + { + uint32_t flags = 0; + if (osOK == osMessageQueueGet(bt->txqueue, &(bt->msg), NULL, 5000)) + { + #if (__LOG_LEVEL__ & LOG_DEBUG1) + uint8_t plen = comms_get_payload_length(bt->layer, &(bt->msg)); + uint8_t* payload = comms_get_payload(bt->layer, &(bt->msg), plen); + //debugb1("snd %d", payload, plen, (unsigned int)plen); + + debugb1("{%02X}%04"PRIX16"->%04"PRIX16"[%02X]", + payload, plen, + DEFAULT_PAN_ID, + comms_am_get_source(bt->layer, &(bt->msg)), + comms_am_get_destination(bt->layer, &(bt->msg)), + comms_get_packet_type(bt->layer, &(bt->msg))); + #endif//debug + + if(COMMS_SUCCESS == comms_send(bt->layer, &(bt->msg), bridge_send_done, bt)) + { + debug4("%p snd", &(bt->msg)); + flags = osThreadFlagsWait(MIST_COMM_BRIDGE_FLAG_SEND_DONE, osFlagsWaitAny, osWaitForever); + } + else + { + warn1("snd"); + } + } + + // Check for termination, may have been(?) set already in previous flag wait, so keep existing flags + flags |= osThreadFlagsWait(MIST_COMM_BRIDGE_FLAG_TERMINATE, osFlagsWaitAny, 0); + if(flags & MIST_COMM_BRIDGE_FLAG_TERMINATE) + { + osThreadExit(); + } + } +} diff --git a/cmsis/mist_comm_defer.c b/cmsis/mist_comm_defer.c new file mode 100644 index 0000000..d1bdd40 --- /dev/null +++ b/cmsis/mist_comm_defer.c @@ -0,0 +1,36 @@ +/** + * MistComm deferred implementation on CMSIS with a timer. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_private.h" + +#include "cmsis_os2.h" + +/** + * Initialize a deferred. In this case deferreds are implemented through an + * os timer. + */ +void _comms_deferred_init (comms_layer_t * comms, void ** deferred, comms_deferred_f * cb) +{ + *deferred = osTimerNew(cb, osTimerOnce, (void*)comms, NULL); +} + +/** + * This is a slow implementation, will take at least a millisecond to run + * the deferred. + */ +void _comms_defer (void * deferred) +{ + osTimerStart((osTimerId_t)deferred, 1); +} + +/** + * Free the resources allocated for the deferred. + */ +void _comms_deferred_deinit (void * deferred) +{ + osTimerDelete((osTimerId_t)deferred); +} diff --git a/cmsis/mist_comm_mutex.c b/cmsis/mist_comm_mutex.c new file mode 100644 index 0000000..708fc1a --- /dev/null +++ b/cmsis/mist_comm_mutex.c @@ -0,0 +1,27 @@ +/** + * MistComm locking implementation with CMSIS mutexes. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_iface.h" +#include "mist_comm_private.h" +#include "cmsis_os2.h" + +static const osMutexAttr_t attr = {"comms", osMutexPrioInherit, NULL, 0U}; + +void comms_mutex_acquire (commsMutexId_t mutex) +{ + while(osOK != osMutexAcquire((osMutexId_t)(mutex), osWaitForever)); +} + +void comms_mutex_release (commsMutexId_t mutex) +{ + osMutexRelease((osMutexId_t)mutex); +} + +commsMutexId_t comms_mutex_create (void) +{ + return (commsMutexId_t)osMutexNew(&attr); +} diff --git a/cmsis/mist_comm_mutex.h b/cmsis/mist_comm_mutex.h new file mode 100644 index 0000000..0087136 --- /dev/null +++ b/cmsis/mist_comm_mutex.h @@ -0,0 +1,14 @@ +/** + * MistComm locking with CMSIS mutexes. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_MUTEX_H +#define MIST_COMM_MUTEX_H + +#include "cmsis_os2.h" + +typedef osMutexId_t commsMutexId_t; + +#endif//MIST_COMM_MUTEX_H diff --git a/cmsis/mist_comm_pool.c b/cmsis/mist_comm_pool.c new file mode 100644 index 0000000..919e781 --- /dev/null +++ b/cmsis/mist_comm_pool.c @@ -0,0 +1,45 @@ +/** + * Message pool for MistComm messages. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#include "mist_comm_pool.h" + + +comms_error_t comms_pool_init (comms_pool_t * p_pool, int num_messages) +{ + p_pool->pool = osMemoryPoolNew(num_messages, sizeof(comms_msg_t), NULL); + if (NULL == p_pool->pool) + { + return COMMS_ENOMEM; + } + return COMMS_SUCCESS; +} + + +comms_msg_t * comms_pool_get (comms_pool_t * p_pool, uint32_t timeout_ms) +{ + if (UINT32_MAX == timeout_ms) + { + return osMemoryPoolAlloc(p_pool->pool, osWaitForever); + } + else if (0 == timeout_ms) + { + return osMemoryPoolAlloc(p_pool->pool, 0); + } + else + { + return osMemoryPoolAlloc(p_pool->pool, timeout_ms * 1000 / osKernelGetTickFreq()); + } +} + + +comms_error_t comms_pool_put (comms_pool_t * p_pool, comms_msg_t * p_msg) +{ + if (osOK == osMemoryPoolFree(p_pool->pool, p_msg)) + { + return COMMS_SUCCESS; + } + return COMMS_FAIL; +} diff --git a/cmsis/mist_comm_pool_struct.h b/cmsis/mist_comm_pool_struct.h new file mode 100644 index 0000000..65c0ca8 --- /dev/null +++ b/cmsis/mist_comm_pool_struct.h @@ -0,0 +1,16 @@ +/** + * Message pool for MistComm messages on CMSIS OS2. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_POOL_STRUCT_H +#define MIST_COMM_POOL_STRUCT_H + +#include "cmsis_os2.h" + +typedef struct comms_pool { + osMemoryPoolId_t pool; +} comms_pool_t; + +#endif//MIST_COMM_POOL_STRUCT_H diff --git a/control/mist_comm_controller.c b/control/mist_comm_controller.c new file mode 100644 index 0000000..14ddfd0 --- /dev/null +++ b/control/mist_comm_controller.c @@ -0,0 +1,267 @@ +/** + * Sleep controller implementation. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm.h" +#include "mist_comm_iface.h" + +#include "loglevels.h" +#define __MODUUL__ "mctrl" +#define __LOG_LEVEL__ (LOG_LEVEL_mist_comm_controller & BASE_LOG_LEVEL) +#include "log.h" + +static bool unsafe_update_state (comms_layer_t * comms); +static bool unsafe_service_callbacks (comms_layer_t * comms, comms_status_t status); + +static void status_change_callback (comms_layer_t * comms, comms_status_t status, void * user) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + bool ok; + comms_mutex_acquire(cl->controller_mutex); + ok = unsafe_service_callbacks(comms, status); + comms_mutex_release(cl->controller_mutex); + if ( ! ok) + { + _comms_defer(cl->sleep_controller_deferred); + } +} + +static void deferred_callback (void * arg) +{ + comms_layer_t * comms = (comms_layer_t*)arg; + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + bool ok; + comms_mutex_acquire(cl->controller_mutex); + ok = unsafe_update_state(comms); + comms_mutex_release(cl->controller_mutex); + if ( ! ok) + { + _comms_defer(cl->sleep_controller_deferred); + } +} + +/** + * Cannot defer here, as mutex must be released. + * + * @return true if current state is good, false if state needs to be changed. + */ +static bool unsafe_service_callbacks (comms_layer_t * comms, comms_status_t status) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + comms_sleep_controller_t** indirect; + for (indirect=&(cl->sleep_controllers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if ((*indirect)->pending) + { + if (COMMS_STARTED == status) + { + (*indirect)->cb(comms, status, (*indirect)->user); + (*indirect)->pending = false; + } + else + { + return false; + } + } + if ((*indirect)->block) + { + if (COMMS_STOPPED == status) + { + return false; + } + } + } + return true; +} + +/** + * Cannot defer here, as mutex must be released. + * + * @return true if current state is good, false if state needs to be changed. + */ +static bool unsafe_update_state (comms_layer_t * comms) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + comms_status_t status = comms_status(comms); + comms_sleep_controller_t ** indirect; + for (indirect=&(cl->sleep_controllers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if ((*indirect)->block) // Somebody wants it to be ON + { + if (COMMS_STOPPED == status) + { + if (COMMS_SUCCESS != comms_start(comms, &status_change_callback, NULL)) + { + err1("start"); // Not really expecting resistance here, try again? + return false; + } + } + return true; // Can make decision based on first block encountered + } + } + + // Nobody actually wants it to be ON, so turn it OFF + if (COMMS_SUCCESS != comms_stop(comms, &status_change_callback, NULL)) + { + err1("stop"); // Not really expecting resistance here, try again? + return false; + } + + return true; +} + +comms_error_t comms_register_sleep_controller (comms_layer_t * comms, comms_sleep_controller_t * ctrl, + comms_status_change_f * start_done, void * user) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + comms_error_t err = COMMS_SUCCESS; + comms_sleep_controller_t** indirect; + + comms_mutex_acquire(cl->controller_mutex); + + if (NULL == cl->sleep_controller_deferred) + { + _comms_deferred_init(comms, &(cl->sleep_controller_deferred), deferred_callback); + } + + for (indirect=&(cl->sleep_controllers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if (*indirect == ctrl) + { + err = COMMS_ALREADY; + break; + } + } + + if (COMMS_SUCCESS == err) + { + *indirect = ctrl; + + ctrl->comms = comms; + ctrl->block = false; + ctrl->pending = false; + ctrl->user = user; + ctrl->cb = start_done; + ctrl->next = NULL; + } + + comms_mutex_release(cl->controller_mutex); + + return err; +} + +comms_error_t comms_deregister_sleep_controller (comms_layer_t * comms, comms_sleep_controller_t * ctrl) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)comms; + comms_error_t err = COMMS_FAIL; + comms_sleep_controller_t** indirect; + + comms_mutex_acquire(cl->controller_mutex); + + for(indirect=&(cl->sleep_controllers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if(*indirect == ctrl) + { + *indirect = ctrl->next; + + ctrl->comms = NULL; + err = COMMS_SUCCESS; + if (true != unsafe_update_state(comms)) + { + err1("panic"); + // TODO actual panic + } + break; + } + } + + comms_mutex_release(cl->controller_mutex); + + return err; +} + +// Block may be asynchronous, wakeup may take time, or EALREADY may be returned +comms_error_t comms_sleep_block (comms_sleep_controller_t * ctrl) +{ + comms_error_t err = COMMS_EINVAL; + if ((NULL != ctrl)&&(NULL != ctrl->comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)(ctrl->comms); + comms_mutex_acquire(cl->controller_mutex); + + if(ctrl->pending) + { + err = COMMS_EBUSY; + } + else if (true == ctrl->block) + { + err = COMMS_ALREADY; + } + else + { + ctrl->block = true; + if (COMMS_STARTED == comms_status(ctrl->comms)) + { + err = COMMS_ALREADY; + } + else + { + err = COMMS_SUCCESS; + ctrl->pending = true; + if (true != unsafe_update_state(ctrl->comms)) + { + ctrl->pending = false; + err = COMMS_ERETRY; + } + } + } + + comms_mutex_release(cl->controller_mutex); + } + return err; +} + +// Allow is synchronous, sleep is not guaranteed, but block is released immediately +comms_error_t comms_sleep_allow (comms_sleep_controller_t * ctrl) +{ + comms_error_t err = COMMS_EINVAL; + if ((NULL != ctrl)&&(NULL != ctrl->comms)) + { + comms_layer_iface_t * cl = (comms_layer_iface_t*)(ctrl->comms); + comms_mutex_acquire(cl->controller_mutex); + + if(false == ctrl->block) + { + err = COMMS_ALREADY; + } + else + { + bool pending = ctrl->pending; + err = COMMS_SUCCESS; + ctrl->block = false; + ctrl->pending = false; + if (true != unsafe_update_state(ctrl->comms)) + { + ctrl->block = true; + ctrl->pending = pending; + err = COMMS_ERETRY; + } + } + + comms_mutex_release(cl->controller_mutex); + } + return err; +} + +bool comms_sleep_blocked (comms_sleep_controller_t * ctrl) +{ + comms_layer_iface_t * cl = (comms_layer_iface_t*)(ctrl->comms); + bool blocked; + comms_mutex_acquire(cl->controller_mutex); + blocked = ctrl->block; + comms_mutex_release(cl->controller_mutex); + return blocked; +} diff --git a/include/compat/mist_comm_basics.h b/include/compat/mist_comm_basics.h new file mode 100644 index 0000000..c8f6b55 --- /dev/null +++ b/include/compat/mist_comm_basics.h @@ -0,0 +1,22 @@ +/** + * Reference basic variables definition. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_BASICS_H +#define MIST_COMM_BASICS_H + +#include + +#include "eui64.h" + +typedef uint8_t am_group_t; // This is actually PAN +typedef uint8_t am_id_t; +typedef uint16_t am_addr_t; + +typedef uint16_t nx_am_addr_t; + +#define AM_BROADCAST_ADDR ((uint16_t)0xFFFF) + +#endif//MIST_COMM_BASICS_H diff --git a/include/mist_comm.h b/include/mist_comm.h new file mode 100644 index 0000000..142b36a --- /dev/null +++ b/include/mist_comm.h @@ -0,0 +1,820 @@ +/** + * Mist communications API. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_H +#define MIST_COMM_H + +#include +#include +#include + +#include "eui64.h" + +#include "mist_comm_basics.h" +#include "mist_comm_mutex.h" + +#include "platform_msg.h" + +// ----------------------------------------------------------------------------- +#define MIST_COMM_VERSION_MAJOR 0 +#define MIST_COMM_VERSION_MINOR 1 +#define MIST_COMM_VERSION_PATCH 0 +// ----------------------------------------------------------------------------- + +/** + * Verify that the comms api layer seems to be correctly configured. + * + * Call this before setting up a comms layer and check that it returns true, + * panic if it does not. + * + * This should fail at compile-time for library-api version conflicts. + * + * @return true if API and library version matches, config appears correct. + */ +inline bool comms_verify_api (void) __attribute__((always_inline)); + +// ----------------------------------------------------------------------------- +// Result and status values ---------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Communication layer result values. + * Generally derived from TinyOS error_t, however, all bad things have + * a negative value and there is a positive ALREADY instead of EALREADY. + */ +typedef enum CommsErrors { + COMMS_UNINITIALIZED = -127, + COMMS_NOT_SUPPORTED = -126, + + COMMS_NO_ADDR = -15, + COMMS_NO_KEY = -14, + COMMS_NO_ROUTE = -13, + + COMMS_ETIMEOUT = -12, + COMMS_ENOACK = -11, + COMMS_ENOMEM = -10, +// COMMS_EALREADY = -9, // Use positive COMMS_ALREADY instead! + COMMS_ERESERVE = -8, + COMMS_ERETRY = -7, + COMMS_EINVAL = -6, + COMMS_EBUSY = -5, + COMMS_EOFF = -4, + COMMS_ECANCEL = -3, + COMMS_ESIZE = -2, + COMMS_FAIL = -1, + + COMMS_SUCCESS = 0, + COMMS_ALREADY = 1 +} comms_error_t; + +/** + * Communication layer status values (states). + */ +typedef enum CommsStatus { + COMMS_STOPPING = -2, + COMMS_STARTING = -1, + COMMS_STOPPED = 0, + COMMS_STARTED = 1 +} comms_status_t; + +// ----------------------------------------------------------------------------- +// Layer, address and message structures --------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Base structure for communications layers. Every interface has a type + * and an EUI-64 address. + */ +typedef struct comms_layer { + uint8_t type; // TODO type definitions for reflection + ieee_eui64_t eui; + // Everything else will embed at least this structure +} comms_layer_t; + +/** + * The comms local address structure. This carries a link-local address which + * may take different forms, depending on the environment. + */ +typedef struct comms_local_addr { + uint8_t data[COMMS_MSG_ADDRESSING_SIZE]; +} __attribute__((packed)) comms_local_addr_t; + +/** + * The comms address structure, carries mappings from globally unique EUI64 + * addresses to link-local platform/environment specific addresses. + * It should be assumed that the local address may change at runtime! + * It is possible that either the eui or local address is not known (all zeros)! + */ +typedef struct comms_address { + ieee_eui64_t eui; + comms_local_addr_t local; + uint32_t updated; // Timestamp, seconds, monotonic +} __attribute__((packed)) comms_address_t; + +/** + * The comms message structure. Should only be manipulated through the APIs. + */ +typedef struct comms_msg comms_msg_t; + +// ----------------------------------------------------------------------------- +// Callback definitions -------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Signalled when message transmission is completed to return the message object + * to the client. A function of this type must be passed with every call to the + * comms_send function and the passed function will be called in the future if + * the comms_send returns COMMS_SUCCESS (message is accepted). + * + * @param comms The layer that was used to send the message. + * @param msg The message that was sent. + * @param result The result of the transmission. + * @param user The user pointer provided to comms_send. + */ +typedef void comms_send_done_f (comms_layer_t * comms, comms_msg_t * msg, comms_error_t result, void * user); + +/** + * Signalled when a message is received. Functions of this type must first be + * registered with a communications layer with comms_register_recv. + * + * @param comms The layer where the message was received. + * @param msg The message, only valid while this call is running. + * @param user The user pointer given to comms_register_recv. + */ +typedef void comms_receive_f (comms_layer_t * comms, const comms_msg_t * msg, void * user); + +/** + * Signalled when a status change is requested and completed. + * + * @param comms The layer for which the status change took place. + * @param status The new status (may not be what was actually requested). + * @param user The user pointer. + */ +typedef void comms_status_change_f (comms_layer_t * comms, comms_status_t status, void * user); + +// ----------------------------------------------------------------------------- +// Communications control and status ------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Start a comms layer. Use this or sleep controllers, not both. + * + * @param comms A comms layer to start. + * @param start_done Status change callback. + * @param user Argument passed to the callback. + * @return COMMS_SUCCESS if the callback will be called in the future. + */ +comms_error_t comms_start (comms_layer_t * comms, comms_status_change_f * start_done, void * user); + +/** + * Stop a comms layer. Use this or sleep controllers, not both. + * @param comms A comms layer to stop. + * @param start_done Status change callback. + * @param user Argument passed to the callback. + * @return COMMS_SUCCESS if the callback will be called in the future. + */ +comms_error_t comms_stop (comms_layer_t * comms, comms_status_change_f * stop_done, void * user); + +/** + * Get current status of the comms layer. + * + * @param comms The comms layer to query. + * @return Current status. + */ +comms_status_t comms_status (comms_layer_t * comms); + +// ----------------------------------------------------------------------------- +// Message sending ------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Initialize a message structure for use on the specified communications + * layer. Must be called before any packet functions can be used. A message + * initialized with one comms layer must not be used with another layer, unless + * initialized again with that layer! + * + * @param comms The comms layer to use for initilization. + * @param msg The message to initialize. + */ +void comms_init_message (comms_layer_t * comms, comms_msg_t * msg); + +/** + * Send a message through the specified communications layer. + * + * The message must stay allocated and not be modified while the layer is + * processing it (until send-done is called). + * + * @param comms The comms layer to use for sending the message. + * @param msg The message to send. + * @param sdf Pointer to a send-done function. + * @param user Optional user pointer, returned in send-done. + * @return COMMS_SUCCESS, if the send-done function will be called some time in the future. + */ +comms_error_t comms_send (comms_layer_t * comms, comms_msg_t * msg, comms_send_done_f * sdf, void * user); + +// ----------------------------------------------------------------------------- +// Receiver registration ------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Receiver structure for registering a receive callback. + * Must not go out of scope before it is deregistered! + */ +typedef struct comms_receiver comms_receiver_t; + +/** + * Register to receive messages of a specific type. + * + * Must pass an unused comms_receiver_t object, guaranteeing that the + * memory remains allocated and is not used elsewhere until deregister is called. + * + * One receiver object may not be used in multiple roles at the same time + * + * The same receive function may be registered several times with different + * conditions and/or with a different user pointer. + * + * Multiple receivers may be registered for the same packet type, the message will + * be delivered to all receivers. + * + * @param comms Pointer to a comms layer. + * @param rcvr Receiver structure. + * @param func The receive function to call when a packet is received. + * @param user User pointer to be passed to the receive function on packet reception. + * @param amid The message type to register the receiver for. + */ +comms_error_t comms_register_recv (comms_layer_t * comms, + comms_receiver_t * rcvr, + comms_receive_f * func, void * user, + am_id_t amid); + +/** + * Register to receive messages, requesting the EUI64 addresses to be resolved. + * + * Receive callbacks will still be called, even if the EUI64 of the sender is not + * known, but the comms layer will attempt to resolve the address for subsequent + * messages. + * + * Must pass an unused comms_receiver_t object, guaranteeing that the + * memory remains allocated and is not used elsewhere until deregister is called. + * + * One receiver object may not be used in multiple roles at the same time + * + * The same receive function may be registered several times with different + * conditions and/or with a different user pointer. + * + * Multiple receivers may be registered for the same packet type, the message will + * be delivered to all receivers. + * + * @param comms Pointer to a comms layer. + * @param rcvr Receiver structure. + * @param func The receive function to call when a packet is received. + * @param user User pointer to be passed to the receive function on packet reception. + * @param amid The message type to register the receiver for. + * @return COMMS_SUCCESS if successfully registered. + */ +comms_error_t comms_register_recv_eui (comms_layer_t * comms, + comms_receiver_t * rcvr, + comms_receive_f * func, void * user, + am_id_t amid); + +/** + * Remove an already registered receiver. + * + * @param comms Pointer to a comms layer. + * @param rcvr Receiver structure to deregister. + * @return COMMS_SUCCESS if receiver has been removed and can be recycled. + */ +comms_error_t comms_deregister_recv (comms_layer_t * comms, comms_receiver_t * rcvr); + +/** + * Register to snoop for messages: + * All messages, regardless of AMID. + * All destinations, if underlying device is in promiscuous mode. + * + * Must pass an unused comms_receiver_t object, guaranteeing that the + * memory remains allocated and is not used elsewhere until deregister is called. + * + * One receiver object may not be used in multiple roles at the same time + * + * The same receive function may be registered several times with different + * conditions and/or with a different user pointer. + * + * Multiple receivers may be registered for the same packet type, the message will + * be delivered to all receivers. + * + * @param comms Pointer to a comms layer. + * @param rcvr Receiver structure. + * @param func The receive function to call when a packet is received. + * @param user User pointer to be passed to the receive function on packet reception. + */ +comms_error_t comms_register_snooper (comms_layer_t * comms, + comms_receiver_t * rcvr, + comms_receive_f * func, void * user); + +/** + * Remove an already registered snooper. + * + * @param comms Pointer to a comms layer. + * @param rcvr Receiver structure to deregister. + * @return COMMS_SUCCESS if receiver has been removed and can be recycled. + */ +comms_error_t comms_deregister_snooper (comms_layer_t * comms, comms_receiver_t * rcvr); + +// ----------------------------------------------------------------------------- +// Packet type ----------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get the packet type (AM ID - Active Message ID) of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Packet type (AM ID). + */ +am_id_t comms_get_packet_type (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the packet type (AM ID - Active Message ID) of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param ptype The type. + */ +void comms_set_packet_type (comms_layer_t * comms, comms_msg_t * msg, am_id_t ptype); + +// ----------------------------------------------------------------------------- +// Packet group (PAN ID, AM group, etc) ---------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get the packet group (AM group / PAN ID) of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Packet group (PAN ID). + */ +uint16_t comms_get_packet_group (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the packet group (AM group / PAN ID) of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param group Packet group (PAN ID). + */ +void comms_set_packet_group (comms_layer_t * comms, comms_msg_t * msg, uint16_t group); + +// ----------------------------------------------------------------------------- +// Addressing ------------------------------------------------------------------ +// ----------------------------------------------------------------------------- + +/** + * Get the destination of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param destination Pointer to a comms_address_t object for copying destination info. + */ +void comms_get_destination (comms_layer_t * comms, const comms_msg_t * msg, comms_address_t * destination); + +/** + * Set the destination of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param destination Pointer to an object holding the destination. + */ +void comms_set_destination (comms_layer_t * comms, comms_msg_t * msg, const comms_address_t * destination); + +/** + * Get the source of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param source Pointer to a comms_address_t object for copying source info. + */ +void comms_get_source (comms_layer_t * comms, const comms_msg_t * msg, comms_address_t * source); + +/** + * Set the source of the message. Source is also set automatically on send, if not set before, + * it needs to be set explicitly when for example forwarding messages and the source is not + * the sending node itself. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param source Pointer to an object holding the source. + */ +void comms_set_source (comms_layer_t * comms, comms_msg_t * msg, const comms_address_t * source); + +// ----------------------------------------------------------------------------- +// Message payload functions --------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get the maximum payload length supported by this communications layer. + * + * @param comms Pointer to a comms layer. + * @return Maximum supported payload length. + */ +uint8_t comms_get_payload_max_length (comms_layer_t * comms); + +/** + * Get the current payload length of this message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Payload length. + */ +uint8_t comms_get_payload_length (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the payload length for this message. The value must be <= comms_get_payload_max_length. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Payload length. + */ +void comms_set_payload_length (comms_layer_t * comms, comms_msg_t * msg, uint8_t length); + +/** + * Get a pointer to the payload. NULL is returned if the requested size cannot be + * accomodated by the comms layer. Actual size of the payload must be retrieved + * with comms_get_payload_length when reading the payload and set + * with comms_set_payload_length after writing the payload. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param length Expected max length of the payload. + * @return Pointer to payload or NULL + */ +void * comms_get_payload (comms_layer_t * comms, const comms_msg_t * msg, uint8_t length); + +// ----------------------------------------------------------------------------- +// PacketLink & Acknowledgements ----------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get the number of allowed retries for the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Number of allowed retries. + */ +uint8_t comms_get_retries (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the number of allowed retries for the message. Setting to > 0 will cause + * an acknowledgement to be requested when sent to unicast. For broadcast the + * the message will be repeated the specified number of times. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param count Number of allowed retries. + * @return COMMS_SUCCESS when retry parameters were valid. + */ +comms_error_t comms_set_retries (comms_layer_t * comms, comms_msg_t * msg, uint8_t count); + +/** + * Get the number of retries used for the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Number of used retries, when sending the message. + */ +uint8_t comms_get_retries_used(comms_layer_t* comms, const comms_msg_t* msg); + +/** + * Set the number of used retries. + * + * For communication layer implementation internal use! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Number of allowed retries. + */ +comms_error_t comms_set_retries_used(comms_layer_t* comms, comms_msg_t* msg, uint8_t count); + +/** + * Get the configured retry timeout. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Retry timeout (milliseconds). + */ +uint32_t comms_get_timeout(comms_layer_t* comms, const comms_msg_t* msg); + +/** + * Set the retry timeout for sending this message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param timeout Retry timeout (milliseconds). + * @return COMMS_SUCCESS if the timeout value was valid. + */ +comms_error_t comms_set_timeout(comms_layer_t* comms, comms_msg_t* msg, uint32_t timeout); + +/** + * Check if an ack has been requested for this message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return true if an ack was / will be requested for the message. + */ +bool comms_is_ack_required(comms_layer_t* comms, const comms_msg_t* msg); + +/** + * Set if the ack is required or not. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param required Set to true to request an ack. + * @return COMMS_SUCCESS if the requested ack configuration can be supported. + */ +comms_error_t comms_set_ack_required (comms_layer_t * comms, comms_msg_t * msg, bool required); + +/** + * Check if an ack was received for this message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return true if an acknowledgement was received. + */ +bool comms_ack_received (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the ack field in the message. + * + * For communication layer implementation internal use! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return true if an acknowledgement was received. + */ +void comms_set_ack_received (comms_layer_t * comms, comms_msg_t * msg, bool received); + +// ----------------------------------------------------------------------------- +// Message timestamping -------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get the current communications layer time. Timestamps of messages are set + * relative to this counter. The value is in microseconds, however, depending on + * the underlying implementation, ticks may be many microseconds - for example + * the clock may actually have millisecond precision, so the returned timestamps + * will jump in 1000 microsecond steps. + * + * @param comms Pointer to a comms layer. + * @return Current timestamp (microseconds). + */ +uint32_t comms_get_time_micro (comms_layer_t * comms); + +/** + * Check if the message has a valid timestamp. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return true if the message has been timestamped on RX or TX. + */ +bool comms_timestamp_valid (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Get the message timestamp: + * * RX timestamp, if message was received + * * TX timestamp, if the message was sent + * + * Always check first that the timestamp is valid with comms_timestamp_valid! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Message RX or TX timestamp. + */ +uint32_t comms_get_timestamp_micro (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the timestamp field in the message. + * Setting the timestamp will make future calls to comms_timestamp_valid return true. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param timestamp The timestamp. + * @return COMMS_SUCCESS unless something went horribly wrong. + */ +comms_error_t comms_set_timestamp_micro (comms_layer_t * comms, comms_msg_t * msg, uint32_t timestamp); + +// ----------------------------------------------------------------------------- +// TimeSync messaging ---------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Set an event time in respect to the comms_get_time_micro clock. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param timestamp The timestamp. + * @return COMMS_SUCCESS unless something went horribly wrong. + */ +comms_error_t comms_set_event_time_micro (comms_layer_t * comms, comms_msg_t * msg, uint32_t evt); + +/** + * Get the event time communicated with the message. + * + * Always check that event time is valid with comms_event_time_valid! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Event timestamp relative to comms_get_time_micro. + */ +uint32_t comms_get_event_time_micro (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Check if the message carries a valid event time. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return true if the message includes an event time. + */ +bool comms_event_time_valid (comms_layer_t * comms, const comms_msg_t * msg); + +// ----------------------------------------------------------------------------- +// Message Quality ------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Get message link-quality. 0-255, 255 is best. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Message quality (0-255, 255 is best). + */ +uint8_t comms_get_lqi (comms_layer_t* comms, const comms_msg_t* msg); + +/** + * Set message link-quality. + * + * For communication layer implementation internal use! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param lqi Message quality (0-255, 255 is best). + */ +void comms_set_lqi (comms_layer_t* comms, comms_msg_t* msg, uint8_t lqi); + +/** + * Get message RSSI - Received Signal Strength Indicator, dBm. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Message RSSI, dBm. + */ +int8_t comms_get_rssi (comms_layer_t* comms, const comms_msg_t* msg); + +/** + * Set message RSSI - Received Signal Strength Indicator, dBm. + * + * For communication layer implementation internal use! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param rssi Message RSSI, dBm. + */ +void comms_set_rssi (comms_layer_t* comms, comms_msg_t* msg, int8_t rssi); + +// ----------------------------------------------------------------------------- +// Message Priority ------------------------------------------------------------ +// ----------------------------------------------------------------------------- + +/** + * Get message priority, 0 is default. + * + * Priority handling mostly depends on the specific of the underlying + * implementation, but is relative to 0 ... higher value, higher priority. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Message priority. + */ +int8_t comms_get_priority (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set message priority, 0 for normal (default). + * + * Priority handling mostly depends on the specific of the underlying + * implementation, but is relative to 0 ... higher value, higher priority. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param priority Message priority. + */ +void comms_set_priority (comms_layer_t * comms, comms_msg_t * msg, int8_t priority); + +// ----------------------------------------------------------------------------- +// Received message delivery --------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Deliver a message to all registered receivers. + * + * @param comms The later to use. + * @param msg The message to deliver. + * @return true if the message was delivered to at least one receiver. +*/ +bool comms_deliver (comms_layer_t * comms, comms_msg_t * msg); + +// ----------------------------------------------------------------------------- +// Sleep management ------------------------------------------------------------ +// ----------------------------------------------------------------------------- + +/** + * Sleep management controller. + * + * If a sleep controller is registered for a comms layer, it will entirely take + * over start-stop management, the comms_start and comms_stop functions should + * be ignored altogether and only comms_sleep_block and comms_sleep_allow + * should be used. + */ +typedef struct comms_sleep_controller comms_sleep_controller_t; + +/** + * Register a sleep controller. + * @param comms Layer to register controller to. + * @param ctrl An unused controller. + * @param start_done Callback function called when a sleep block starts the comms layer. + * @param user User parameter passed with the callback. + * @return COMMS_SUCCESS if sleep controller registered. + */ +comms_error_t comms_register_sleep_controller (comms_layer_t * comms, comms_sleep_controller_t * ctrl, + comms_status_change_f * start_done, void * user); + +/** + * De-register a sleep controller. + * + * @param comms Layer to remove the controller from. + * @param ctrl A registered ccontroller. + * @return COMMS_SUCCESS when controller was removed. + */ +comms_error_t comms_deregister_sleep_controller (comms_layer_t * comms, comms_sleep_controller_t * ctrl); + +/** + * Request a sleep block and the layer to be started. May return COMMS_ALREADY if + * the block takes effect immediately or COMMS_SUCCESS if the layer needs to be + * started and therefore a state change callback will be called once ready. + * + * @param ctrl A registered ccontroller. + * @return COMMS_SUCCESS when callback will be called in the future, + * COMMS_ALREADY when block immediately successful. + */ +comms_error_t comms_sleep_block (comms_sleep_controller_t * ctrl); + +/** + * Release a sleep block. The block is released immediately but the layer shutdown + * will only happen if other blocks are not present ... it will also take time. + * No callback will be fired, it is ok to request another block immediately. + * + * @param ctrl A registered controller. + * @return COMMS_SUCCESS when block was released, + * COMMS_ALREADY when it was not event active. + */ +comms_error_t comms_sleep_allow (comms_sleep_controller_t * ctrl); + +/** + * Query current block status. Use comms_status to get actual status. + * + * @param ctrl A registered controller. + * @return true if block is active or pending. + */ +bool comms_sleep_blocked (comms_sleep_controller_t * ctrl); + +// ----------------------------------------------------------------------------- +// Locking, for internal use mostly -------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Create a mutex. + * + * @return A mutex ID or NULL for failure. + */ +commsMutexId_t comms_mutex_create (void); + +/** + * Acquire the specified mutex. Blocks until aquired. + * + * @param mutex - The mutex to acquire. + */ +void comms_mutex_acquire (commsMutexId_t mutex); + +/** + * Release the specified mutex. Returns "immediately". + * + * @param mutex - The mutex to release. + */ +void comms_mutex_release (commsMutexId_t mutex); + +// ----------------------------------------------------------------------------- +// Include implementation details. +// ----------------------------------------------------------------------------- +#include "mist_comm_private.h" + +#endif//MIST_COMM_H diff --git a/include/mist_comm_am.h b/include/mist_comm_am.h new file mode 100644 index 0000000..ada8884 --- /dev/null +++ b/include/mist_comm_am.h @@ -0,0 +1,113 @@ +/** + * Mist communications API for TinyOS ActiveMessage. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_AM_H +#define MIST_COMM_AM_H + +#include "mist_comm.h" +#include "mist_comm_iface.h" +#include "mist_comm_addrcache.h" +#include "mist_comm_am_addrdisco.h" + +/** + * Structure type definition for an ActiveMessage comms layer. + */ +typedef struct comms_layer_am comms_layer_am_t; + +/** + * ActiveMessage address of the communications layer. + * @param comms Pointer to a comms layer. + * @return ActiveMessage address. + */ +am_addr_t comms_am_address (comms_layer_t * comms); + +/** + * Get the destination of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Destination address. + */ +am_addr_t comms_am_get_destination (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the destination of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param dest Destination address. + */ +void comms_am_set_destination (comms_layer_t * comms, comms_msg_t * msg, am_addr_t dest); + +/** + * Get the source of the message. + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @return Source address. + */ +am_addr_t comms_am_get_source (comms_layer_t * comms, const comms_msg_t * msg); + +/** + * Set the source of the message. In most cases this is done automatically! + * + * @param comms Pointer to a comms layer. + * @param msg Pointer to a message. + * @param source Source address. + */ +void comms_am_set_source (comms_layer_t * comms, comms_msg_t * msg, am_addr_t source); + +/** + * Create an ActiveMessage comms layer. + * + * @param layer Pointer to a layer structure to initialize. + * @param address ActiveMessage address for this layer + * @param sendf Actual send function to use for sending the message. + * @param plenf Max payload length function for the underlying comms component. + * @param gtmf Time reference function. + * @param startf Layer start function (can be NULL, if always active). + * @param stopf Layer stop function (can be NULL, if always active). + * @return COMMS_SUCCESS if initialized correctly. + */ +comms_error_t comms_am_create (comms_layer_t * layer, am_addr_t address, + comms_send_f * sendf, comms_plen_f * plenf, + comms_get_time_micro_f * gtmf, + comms_start_f * startf, comms_stop_f * stopf); + + +// ----------------------------------------------------------------------------- + +typedef am_addr_t comms_am_addr_f (comms_layer_am_t*); + +typedef am_addr_t comms_am_get_destination_f(comms_layer_am_t*, const comms_msg_t*); +typedef void comms_am_set_destination_f(comms_layer_am_t*, comms_msg_t*, am_addr_t); + +typedef am_addr_t comms_am_get_source_f(comms_layer_am_t*, const comms_msg_t*); +typedef void comms_am_set_source_f(comms_layer_am_t*, comms_msg_t*, am_addr_t); + +struct comms_layer_am +{ + comms_layer_iface_t base; + + comms_am_get_destination_f * am_get_destination; + comms_am_set_destination_f * am_set_destination; + + comms_am_get_source_f * am_get_source; + comms_am_set_source_f * am_set_source; + + comms_am_addr_f * am_address; + + comms_send_f * link_sendf; + comms_plen_f * link_plenf; + + am_addr_t am_addr; + + comms_addr_cache_t * cache; + + am_addrdisco_t * disco; +}; + +#endif//MIST_COMM_AM_H diff --git a/include/mist_comm_am_msg.h b/include/mist_comm_am_msg.h new file mode 100644 index 0000000..4dfc0a9 --- /dev/null +++ b/include/mist_comm_am_msg.h @@ -0,0 +1,39 @@ +/** + * Mist communications API TinyOS ActiveMessage message structure. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_AM_MSG_H +#define MIST_COMM_AM_MSG_H + +#include "mist_comm_basics.h" + +typedef struct comms_am_msg_header { +} comms_am_msg_header_t; + +typedef struct comms_am_msg_footer { +} comms_am_msg_footer_t; + +typedef struct comms_am_msg_metadata { + int8_t rssi; + uint8_t lqi; + + uint8_t sent; + uint8_t retries; + uint32_t timeout; + + bool ack_required; + bool ack_received; + + bool timestamp_valid; + uint32_t timestamp; + + bool event_time_valid; + uint32_t event_time; + + bool priority_valid; + uint8_t priority; +}__attribute__((packed))comms_am_msg_metadata_t; + +#endif//MIST_COMM_AM_MSG_H diff --git a/include/mist_comm_bridge.h b/include/mist_comm_bridge.h new file mode 100644 index 0000000..e992df8 --- /dev/null +++ b/include/mist_comm_bridge.h @@ -0,0 +1,53 @@ +/** + * MistComm bridge. + * Bridge messages between 2 mist-comm communication interfaces. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_BRIDGE_H +#define MIST_COMM_BRIDGE_H + +#include "cmsis_os2.h" +#include "mist_comm.h" + +typedef struct comms_bridge_thread +{ + osThreadId_t thread; + + comms_receiver_t receiver; + comms_layer_t * layer; + + osMessageQueueId_t rxqueue; + osMessageQueueId_t txqueue; + comms_msg_t msg; +} comms_bridge_thread_t; + +typedef struct comms_bridge +{ + comms_bridge_thread_t t1; + comms_bridge_thread_t t2; +} comms_bridge_t; + +/** + * Initialize and start a bridge. + * + * @param bridge The bridge to initialize. + * @param a The first communications interface. + * @param b The second communications interface. + * @return COMMS_SUCCESS for success. + */ +comms_error_t comms_bridge_init (comms_bridge_t * bridge, + comms_layer_t * a, + comms_layer_t * b); + +/** + * Deinitialize a bridge, freeing all resources. + * + * MAY TAKE SOME TIME (10-15 seconds)! + * + * @param bridge The bridge to initialize. + */ +void comms_bridge_deinit (comms_bridge_t * bridge); + +#endif//MIST_COMM_BRIDGE_H diff --git a/include/mist_comm_iface.h b/include/mist_comm_iface.h new file mode 100644 index 0000000..7d5ed18 --- /dev/null +++ b/include/mist_comm_iface.h @@ -0,0 +1,158 @@ +/** + * Mist communications interface structure. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_IFACE_H +#define MIST_COMM_IFACE_H + +#include "mist_comm.h" + +typedef struct comms_layer_iface comms_layer_iface_t; + +typedef comms_status_t comms_status_f(comms_layer_iface_t*); +typedef comms_error_t comms_start_f(comms_layer_iface_t*, comms_status_change_f*, void*); +typedef comms_error_t comms_stop_f(comms_layer_iface_t*, comms_status_change_f*, void*); + +typedef void comms_init_message_f(comms_layer_iface_t*, comms_msg_t*); + +typedef comms_error_t comms_send_f(comms_layer_iface_t*, comms_msg_t*, comms_send_done_f*, void*); +typedef bool comms_deliver_f(comms_layer_iface_t*, comms_msg_t*); + +typedef uint8_t comms_plen_f(comms_layer_iface_t*); + +typedef comms_error_t comms_register_recv_f(comms_layer_iface_t*, comms_receiver_t*, comms_receive_f*, void*, am_id_t, bool); +typedef comms_error_t comms_deregister_recv_f(comms_layer_iface_t*, comms_receiver_t*); + +typedef comms_error_t comms_register_snooper_f(comms_layer_iface_t*, comms_receiver_t*, comms_receive_f*, void*); +typedef comms_error_t comms_deregister_snooper_f(comms_layer_iface_t*, comms_receiver_t*); + +typedef am_id_t comms_get_packet_type_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_packet_type_f(comms_layer_iface_t*, comms_msg_t*, am_id_t); + +typedef uint16_t comms_get_packet_group_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_packet_group_f(comms_layer_iface_t*, comms_msg_t*, uint16_t); + +typedef uint8_t comms_get_payload_max_length_f(comms_layer_iface_t*); +typedef uint8_t comms_get_payload_length_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_payload_length_f(comms_layer_iface_t*, comms_msg_t*, uint8_t); +typedef void* comms_get_payload_f(comms_layer_iface_t*, const comms_msg_t*, uint8_t); + +typedef uint8_t comms_get_retries_f(comms_layer_iface_t*, const comms_msg_t*); +typedef comms_error_t comms_set_retries_f(comms_layer_iface_t*, comms_msg_t*, uint8_t); + +typedef uint8_t comms_get_retries_used_f(comms_layer_iface_t*, const comms_msg_t*); +typedef comms_error_t comms_set_retries_used_f(comms_layer_iface_t*, comms_msg_t*, uint8_t); + +typedef uint32_t comms_get_timeout_f(comms_layer_iface_t*, const comms_msg_t*); +typedef comms_error_t comms_set_timeout_f(comms_layer_iface_t*, comms_msg_t*, uint32_t); + +typedef bool comms_is_ack_required_f(comms_layer_iface_t*, const comms_msg_t*); +typedef comms_error_t comms_set_ack_required_f(comms_layer_iface_t*, comms_msg_t*, bool); + +typedef bool comms_ack_received_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_ack_received_f(comms_layer_iface_t*, comms_msg_t*, bool); + +typedef uint32_t comms_get_time_micro_f(comms_layer_iface_t*); + +typedef comms_error_t comms_set_timestamp_f(comms_layer_iface_t*, comms_msg_t*, uint32_t); +typedef uint32_t comms_get_timestamp_f(comms_layer_iface_t*, const comms_msg_t*); +typedef bool comms_timestamp_valid_f(comms_layer_iface_t*, const comms_msg_t*); + +typedef comms_error_t comms_set_event_time_f(comms_layer_iface_t*, comms_msg_t*, uint32_t); +typedef uint32_t comms_get_event_time_f(comms_layer_iface_t*, const comms_msg_t*); +typedef bool comms_event_time_valid_f(comms_layer_iface_t*, const comms_msg_t*); + +typedef uint8_t comms_get_lqi_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_lqi_f(comms_layer_iface_t*, comms_msg_t*, uint8_t); + +typedef int8_t comms_get_rssi_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_rssi_f(comms_layer_iface_t*, comms_msg_t*, int8_t); + +typedef uint8_t comms_get_priority_f(comms_layer_iface_t*, const comms_msg_t*); +typedef void comms_set_priority_f(comms_layer_iface_t*, comms_msg_t*, uint8_t); +// ----------------------------------------------------------------------------- + +struct comms_layer_iface { + comms_layer_t layer; // Type info + + comms_start_f* start; + comms_stop_f* stop; + comms_status_change_f* status_change_user_cb; + void* status_change_user; + + comms_init_message_f* init_message; + + comms_send_f* sendf; + + comms_deliver_f* deliverf; + + comms_register_recv_f* register_recv; + comms_deregister_recv_f* deregister_recv; + comms_register_snooper_f* register_snooper; + comms_deregister_snooper_f* deregister_snooper; + + comms_get_packet_type_f* get_packet_type; + comms_set_packet_type_f* set_packet_type; + + comms_get_packet_group_f* get_packet_group; + comms_set_packet_group_f* set_packet_group; + + comms_get_payload_max_length_f* get_payload_max_length; + comms_get_payload_length_f* get_payload_length; + comms_set_payload_length_f* set_payload_length; + comms_get_payload_f* get_payload; + + comms_get_retries_f* get_retries; + comms_set_retries_f* set_retries; + + comms_get_retries_used_f* get_retries_used; + comms_set_retries_used_f* set_retries_used; + + comms_get_timeout_f* get_timeout; + comms_set_timeout_f* set_timeout; + + comms_is_ack_required_f* is_ack_required; + comms_set_ack_required_f* set_ack_required; + comms_ack_received_f* ack_received; + comms_set_ack_received_f* set_ack_received; + + comms_get_time_micro_f* get_time_micro; + + comms_set_timestamp_f* set_timestamp; + comms_get_timestamp_f* get_timestamp; + comms_timestamp_valid_f* timestamp_valid; + + comms_set_event_time_f* set_event_time; + comms_get_event_time_f* get_event_time; + comms_event_time_valid_f* event_time_valid; + + comms_get_lqi_f* get_lqi; + comms_set_lqi_f* set_lqi; + + comms_get_rssi_f* get_rssi; + comms_set_rssi_f* set_rssi; + + comms_get_priority_f* get_priority; + comms_set_priority_f* set_priority; + + // Receivers + comms_receiver_t* receivers; // List of registered receivers + + // Snoopers + comms_receiver_t* snoopers; // List of registered snoopers + + // Sleep controllers + comms_sleep_controller_t* sleep_controllers; // List of sleep controllers + void * sleep_controller_deferred; + + // Standard variables + comms_status_t status; + + commsMutexId_t controller_mutex; + commsMutexId_t start_stop_mutex; + commsMutexId_t receiver_mutex; +}; + +#endif//MIST_COMM_IFACE_H diff --git a/include/mist_comm_pool.h b/include/mist_comm_pool.h new file mode 100644 index 0000000..0b48e7b --- /dev/null +++ b/include/mist_comm_pool.h @@ -0,0 +1,42 @@ +/** + * Message pool for MistComm messages. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_POOL_H +#define MIST_COMM_POOL_H + +#include "mist_comm.h" + +#include "mist_comm_pool_struct.h" // Include a platform-specific structure for the pool definition + +/** + * Initialize a message pool. + * This will usually allocate a pool using the underlying OS heap. + * + * @param pool Pointer to a message pool that is to be initialized. + * @param num_messages Number of messages to be allocated. + * @return COMMS_SUCCESS or an error in case of failure. + */ +comms_error_t comms_pool_init (comms_pool_t * pool, int num_messages); + +/** + * Get a message from the pool. + * + * @param pool Pointer to a message pool. + * @param timeout_ms How long to wait for a message to become available, UINT32_MAX to wait forever. + * @return message pointer or NULL when a message was not available within the allowed timeout. + */ +comms_msg_t * comms_pool_get (comms_pool_t * pool, uint32_t timeout_ms); + +/** + * Return a message to the pool. + * + * @param pool Pointer to a message pool. + * @param p_msg Pointer to a message to be returned. + * @return COMMS_SUCCESS or an error, if the pool is full. + */ +comms_error_t comms_pool_put (comms_pool_t * pool, comms_msg_t * p_msg); + +#endif//MIST_COMM_POOL_H diff --git a/include/mist_comm_private.h b/include/mist_comm_private.h new file mode 100644 index 0000000..efbf62f --- /dev/null +++ b/include/mist_comm_private.h @@ -0,0 +1,100 @@ +/** + * Mist communications API details. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_PRIVATE_H +#define MIST_COMM_PRIVATE_H + +#include "mist_comm.h" + +// ----------------------------------------------------------------------------- +// API version checking -------------------------------------------------------- +// ----------------------------------------------------------------------------- + +/** + * Version check. Headers will check against major and minor variables that + * have the version number in the name. So if there is a version mismatch, + * it will cause linking to fail, because the variables will not be found. + */ + +#define MIST_COMM_VERSION_PASTER(x, y) x##y + +extern volatile uint8_t MIST_COMM_VERSION_PASTER(g_mist_comm_version_major_, MIST_COMM_VERSION_MAJOR); +extern volatile uint8_t MIST_COMM_VERSION_PASTER(g_mist_comm_version_minor_, MIST_COMM_VERSION_MINOR); + +inline bool comms_verify_api (void) +{ + if (MIST_COMM_VERSION_MAJOR != MIST_COMM_VERSION_PASTER(g_mist_comm_version_major_, MIST_COMM_VERSION_MAJOR)) + { + return false; + } + if (MIST_COMM_VERSION_MINOR != MIST_COMM_VERSION_PASTER(g_mist_comm_version_minor_, MIST_COMM_VERSION_MINOR)) + { + return false; + } + return true; +} + +// ----------------------------------------------------------------------------- +// Message structure ----------------------------------------------------------- +// ----------------------------------------------------------------------------- +struct comms_msg { + struct { + uint16_t type; // 0x3F00 + AMID + + uint16_t group; // PAN_ID - 0x0000 + AM Group + + comms_address_t source; + comms_address_t destination; + + uint8_t header[COMMS_MSG_HEADER_SIZE]; + + uint8_t length; + uint8_t payload[COMMS_MSG_PAYLOAD_SIZE]; + + uint8_t footer[COMMS_MSG_FOOTER_SIZE]; + uint8_t metadata[COMMS_MSG_METADATA_SIZE]; + } __attribute__((packed)) body; +}; + +// ----------------------------------------------------------------------------- +// Receiver structure ---------------------------------------------------------- +// ----------------------------------------------------------------------------- +struct comms_receiver { // Members are private, should not be accessed + am_id_t type; + comms_receive_f * callback; + void * user; + bool eui; + comms_receiver_t * next; +}; + +// ----------------------------------------------------------------------------- +// Sleep controller structure -------------------------------------------------- +// ----------------------------------------------------------------------------- +struct comms_sleep_controller { + comms_layer_t * comms; + + bool block; + bool pending; + + comms_status_change_f * cb; + void * user; + + comms_sleep_controller_t * next; +}; + +// ----------------------------------------------------------------------------- +// Deferred calls -------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +typedef void comms_deferred_f (void * argument); + +void _comms_deferred_init (comms_layer_t * comms, void ** deferred, comms_deferred_f * cb); + +void _comms_defer (void * deferred); + +void _comms_deferred_deinit (void * deferred); + +#endif//MIST_COMM_PRIVATE_H diff --git a/include/mist_comm_ref.h b/include/mist_comm_ref.h new file mode 100644 index 0000000..3bd7346 --- /dev/null +++ b/include/mist_comm_ref.h @@ -0,0 +1,27 @@ +/** + * Mist communications reference receiver manager. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_REF_H +#define MIST_COMM_REF_H + +/** + * Add default receiver handling functionality to a layer implementation. + * + * @param comms Pointer to a comms layer. + * @return COMMS_SUCCESS if no errors. + */ +comms_error_t comms_initialize_rcvr_management (comms_layer_iface_t * comms); + +/** + * Deliver a message to registered receivers. + * + * @param comms Pointer to a comms layer. + * @param msg The message to deliver. + * @return COMMS_SUCCESS or COMMS_NO_ADDR most often. + */ +comms_error_t comms_basic_deliver (comms_layer_t * comms, comms_msg_t * msg); + +#endif//MIST_COMM_REF_H diff --git a/include/mist_comm_routing.h b/include/mist_comm_routing.h new file mode 100644 index 0000000..8ccc43f --- /dev/null +++ b/include/mist_comm_routing.h @@ -0,0 +1,93 @@ +/** + * Routing info callbacks for the MistComm API. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_ROUTING_H +#define MIST_COMM_ROUTING_H + +#include "mist_comm_am.h" +#include + +// A communications layer that extends comms_layer_am_t with routing info. +typedef struct comms_layer_am_mh comms_layer_am_mh_t; + +// Structure for a routing info handler. +typedef struct comms_routing_info_handler comms_routing_info_handler_t; + +/** + * Initialize routing callbacks for the communication layer. + * + * @param comms Pointer to a comms layer. + * @return COMMS_SUCCESS if intialized, an error otherwise. + */ +comms_error_t comms_init_routing_result_callbacks (comms_layer_am_mh_t * comms); + +/** + * Notify routing info handlers about a routing result. + * + * @param comms Pointer to a comms layer. + * @param dest Destination that was routed. + * @param cost Route cost ... implementation specific. + * @param status COMMS_SUCCESS if routed successfully. + */ +void comms_routing_notify_routed (comms_layer_t * comms, + am_addr_t dest, uint16_t cost, comms_error_t status); + +/** + * Routing result callback type definition. + * + * @param comms Pointer to a comms layer. + * @param dest Destination that was routed. + * @param cost Route cost ... implementation specific. + * @param status COMMS_SUCCESS if routed successfully. + * @param user User pointer passed during registration. + */ +typedef void comms_am_routed_f (comms_layer_t * comms, + am_addr_t dest, uint16_t cost, comms_error_t result, + void * user); + +/** + * Register a routing info handler. + * + * @param comms Pointer to a comms layer. + * @param rih Pointer to routing info handler. + * @param func Routing info callback function. + * @param user User pointer to pass to callback, when called. + * @return COMMS_SUCCESS if registered. + */ +comms_error_t comms_routing_register_result_callback (comms_layer_am_mh_t * comms, + comms_routing_info_handler_t * rih, + comms_am_routed_f * func, void * user); + +/** + * De-register a routing info handler. + * + * @param comms Pointer to a comms layer. + * @param rih Pointer to routing info handler. + * @return COMMS_SUCCESS if no longer registered. + */ +comms_error_t comms_routing_deregister_result_callback (comms_layer_am_mh_t * comms, + comms_routing_info_handler_t * rih); + +/** + * Routing info handler structure. + */ +struct comms_routing_info_handler +{ + comms_am_routed_f * callback; + void * user; + comms_routing_info_handler_t * next; // A list of handlers is made for their storage +}; + +/** + * Routing info capable communications layer structure. + */ +struct comms_layer_am_mh +{ + comms_layer_am_t base; + comms_routing_info_handler_t * routing_info_handlers; +}; + +#endif//MIST_COMM_ROUTING_H diff --git a/include/tos/mist_comm_basics.h b/include/tos/mist_comm_basics.h new file mode 100644 index 0000000..82d57e6 --- /dev/null +++ b/include/tos/mist_comm_basics.h @@ -0,0 +1,15 @@ +#ifndef MIST_COMM_BASICS_H +#define MIST_COMM_BASICS_H + +#include "AM.h" +#include "IeeeEui64.h" +//typedef uint8_t am_group_t; // This is actually PAN +//typedef uint8_t am_id_t; +//typedef uint16_t am_addr_t; + +//typedef struct ieee_eui64 { +// uint8_t data[8]; +//} ieee_eui64_t; +// ??? this should come from some ieee_eui64.h header + +#endif//MIST_COMM_BASICS_H diff --git a/mock/mist_comm_mutex.c b/mock/mist_comm_mutex.c new file mode 100644 index 0000000..6fe9e2f --- /dev/null +++ b/mock/mist_comm_mutex.c @@ -0,0 +1,29 @@ +/** + * MistComm locking implementation with mock mutexes. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_iface.h" +#include "mist_comm_private.h" + +static volatile bool m_mutexes[8]; +static intptr_t m_mutex_count = 0; + +void comms_mutex_acquire (commsMutexId_t mutex) +{ + while (m_mutexes[(intptr_t)mutex]); + m_mutexes[(intptr_t)mutex] = true; +} + +void comms_mutex_release (commsMutexId_t mutex) +{ + m_mutexes[(intptr_t)mutex] = false; +} + +commsMutexId_t comms_mutex_create (void) +{ + m_mutexes[m_mutex_count] = false; + return (commsMutexId_t)m_mutex_count++; +} diff --git a/mock/mist_comm_mutex.h b/mock/mist_comm_mutex.h new file mode 100644 index 0000000..3f9f90f --- /dev/null +++ b/mock/mist_comm_mutex.h @@ -0,0 +1,12 @@ +/** + * MistComm locking with mock mutexes. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_COMM_MUTEX_H +#define MIST_COMM_MUTEX_H + +typedef int commsMutexId_t; + +#endif//MIST_COMM_MUTEX_H diff --git a/mock/mist_comm_pool.c b/mock/mist_comm_pool.c new file mode 100644 index 0000000..5d133c0 --- /dev/null +++ b/mock/mist_comm_pool.c @@ -0,0 +1,27 @@ +/** + * Mock message pool for MistComm messages. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#include "mist_comm_pool.h" + +#include + +comms_error_t comms_pool_init (comms_pool_t * p_pool, int num_messages) +{ + return COMMS_SUCCESS; +} + + +comms_msg_t * comms_pool_get (comms_pool_t * p_pool, uint32_t timeout_ms) +{ + return malloc(sizeof(comms_msg_t)); +} + + +comms_error_t comms_pool_put (comms_pool_t * p_pool, comms_msg_t * p_msg) +{ + free(p_msg); + return COMMS_SUCCESS; +} diff --git a/mock/mist_mock_radio_cmsis.c b/mock/mist_mock_radio_cmsis.c new file mode 100644 index 0000000..bdb28b0 --- /dev/null +++ b/mock/mist_mock_radio_cmsis.c @@ -0,0 +1,81 @@ +/** + * MistComm mock radio layer. + * Exports outgoing messages, giving send done events to users. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#include "mist_comm_am.h" + +#include "mist_mock_radio_cmsis.h" + +#include "cmsis_os2.h" + +#define RADIO_TOS_MAX_PAYLOAD_LENGTH 114 + +static comms_layer_am_t m_radio_iface; + +static osThreadId_t m_thread; +static osMutexId_t m_mutex; + +static bool m_send_busy = false; +static comms_msg_t * mp_send_msg; +static comms_send_done_f * mf_send_done; +static void * mp_send_user; + +static comms_send_f * mf_send_copy; + + +static comms_error_t radio_send (comms_layer_iface_t * iface, comms_msg_t * msg, + comms_send_done_f * send_done, void * user) +{ + while (osOK != osMutexAcquire(m_mutex, osWaitForever)); + if (false == m_send_busy) + { + m_send_busy = true; + mp_send_msg = msg; + mf_send_done = send_done; + mp_send_user = user; + osThreadFlagsSet(m_thread, 1); + osMutexRelease(m_mutex); + return COMMS_SUCCESS; + } + osMutexRelease(m_mutex); + return COMMS_EBUSY; +} + + +static void mock_radio_loop (void * arg) +{ + for (;;) + { + osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); + while (osOK != osMutexAcquire(m_mutex, osWaitForever)); + if (m_send_busy) + { + m_send_busy = false; + mf_send_copy((comms_layer_iface_t *)&m_radio_iface, mp_send_msg, NULL, mp_send_user); + mf_send_done((comms_layer_t *)&m_radio_iface, mp_send_msg, COMMS_SUCCESS, mp_send_user); + } + osMutexRelease(m_mutex); + } +} + +static uint8_t radio_max_length (comms_layer_iface_t * iface) +{ + return RADIO_TOS_MAX_PAYLOAD_LENGTH; +} + +comms_layer_t * mist_mock_cmsis_radio_init (am_addr_t address, comms_send_f * send_copy) +{ + mf_send_copy = send_copy; + + comms_am_create((comms_layer_t *)&m_radio_iface, address, radio_send, radio_max_length, NULL, NULL); + + m_mutex = osMutexNew(NULL); + + const osThreadAttr_t thread_attr = { .name = "radio", .stack_size = 3072 }; + m_thread = osThreadNew(mock_radio_loop, NULL, &thread_attr); + + return (comms_layer_t*)&m_radio_iface; +} diff --git a/mock/mist_mock_radio_cmsis.h b/mock/mist_mock_radio_cmsis.h new file mode 100644 index 0000000..06596e7 --- /dev/null +++ b/mock/mist_mock_radio_cmsis.h @@ -0,0 +1,21 @@ +/** + * MistComm mock radio layer. + * Exports outgoing messages, giving send done events to users. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#ifndef MIST_MOCK_RADIO_CMSIS_H +#define MIST_MOCK_RADIO_CMSIS_H + +#include "mist_comm_am.h" + +/** + * Create a mock radio. + * @param address - radio address. + * @param send_copy - function to call with outgoing messages. + * @return Mock radio pointer. + */ +comms_layer_t * mist_mock_cmsis_radio_init (am_addr_t address, comms_send_f * send_copy); + +#endif//MIST_MOCK_RADIO_CMSIS_H diff --git a/open-issues.txt b/open-issues.txt new file mode 100644 index 0000000..0b9d509 --- /dev/null +++ b/open-issues.txt @@ -0,0 +1,24 @@ + +* Addressing needs to be generalized - EUI addressing at the core, but support + for link locals. Special union typedef? void* pointer seems unclear, where do + you get the size of the actual thing on your platform? + +* Should am group be exposed in some generic way - do we need this concept for something? + +* Think AM id is too short, but if we expand it, how do we handle compatibility + with legacy applications. Protocol / application / type / port ... what would + be the best term to use for the general concept? + +* Timestamping should really be marked somehow in packet, previously "special" + AM ID 0x3D was used, but does not seem like a good way forward. Perhaps just + reserve the timestamp area always? + +* Timestamping needs clock source - must be provided somehow. + +* Thoughts about where to implement retries ... + * further down the line we probably want an e2e "reliable" transport + +* Radio startup and shutdown generalization - multistart & all stop? + +* Radio channels - this seems like a link-layer issue and would be handled + specifically - most apps should have no bussiness changing the channel? diff --git a/routing/mist_comm_routing.c b/routing/mist_comm_routing.c new file mode 100644 index 0000000..c599155 --- /dev/null +++ b/routing/mist_comm_routing.c @@ -0,0 +1,64 @@ +/** + * Routing info callback handlers for the MistComm API. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "mist_comm_routing.h" +#include + +comms_error_t comms_routing_register_result_callback ( + comms_layer_am_mh_t* comms, + comms_routing_info_handler_t* rih, + comms_am_routed_f* func, void* user) +{ + comms_routing_info_handler_t** indirect; + for (indirect=&(comms->routing_info_handlers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if (*indirect == rih) + { + return COMMS_FAIL; + } + } + *indirect = rih; + + rih->callback = func; + rih->user = user; + rih->next = NULL; + + return COMMS_SUCCESS; +} + +comms_error_t comms_routing_deregister_result_callback ( + comms_layer_am_mh_t* comms, + comms_routing_info_handler_t* rih) +{ + comms_routing_info_handler_t** indirect; + for (indirect=&(comms->routing_info_handlers); NULL != *indirect; indirect = &((*indirect)->next)) + { + if (*indirect == rih) + { + *indirect = rih->next; + return COMMS_SUCCESS; + } + } + return COMMS_FAIL; +} + +void comms_routing_notify_routed (comms_layer_t* comms, am_addr_t dest, uint16_t cost, comms_error_t status) +{ + comms_layer_am_mh_t * cl = (comms_layer_am_mh_t*)comms; + comms_routing_info_handler_t * rih; + + for (rih=cl->routing_info_handlers; NULL != rih; rih=rih->next) + { + rih->callback(comms, dest, cost, status, rih->user); + } +} + +comms_error_t comms_init_routing_result_callbacks (comms_layer_am_mh_t * comms) +{ + comms->routing_info_handlers = NULL; + return COMMS_SUCCESS; +} diff --git a/serial/serial_activemessage.c b/serial/serial_activemessage.c new file mode 100644 index 0000000..e42cd9a --- /dev/null +++ b/serial/serial_activemessage.c @@ -0,0 +1,350 @@ +/** +* MistComm SerialActiveMessage layer implementation. +* +* Copyright Thinnect Inc. 2021 +* @license MIT +*/ +#include +#include +#include +#include + +#include "cmsis_os2.h" + +#include "endianness.h" + +#include "mist_comm.h" +#include "serial_protocol.h" +#include "serial_activemessage.h" + +#include "loglevels.h" +#define __MODUUL__ "sam" +#define __LOG_LEVEL__ (LOG_LEVEL_serial_activemessage & BASE_LOG_LEVEL) +#include "log.h" + +#pragma pack(push, 1) +typedef struct tos_serial_message +{ + uint16_t destination; + uint16_t source; + uint8_t payload_length; // payload[] length + uint8_t group; + uint8_t amid; + uint8_t payload[]; + // footer with lqi/rssi +} tos_serial_message_t; +#pragma pack(pop) + +static comms_error_t serial_activemessage_send (comms_layer_iface_t * iface, + comms_msg_t * msg, + comms_send_done_f * send_done, void * user); +static uint8_t serial_activemessage_max_length (comms_layer_iface_t * iface); + +static bool serial_am_receive(uint8_t dspch, const uint8_t data[], uint8_t length, void* user); +static void serial_am_senddone(uint8_t dspch, const uint8_t data[], uint8_t length, bool acked, void* user); +static void serial_am_timer_cb(void * argument); + +comms_layer_t * serial_activemessage_init (serial_activemessage_t* sam, serial_protocol_t * spr, uint16_t pan_id, uint16_t address) +{ + if ( ! comms_verify_api()) + { + return NULL; + } + + sam->mutex = osMutexNew(NULL); + sam->timer = osTimerNew(&serial_am_timer_cb, osTimerOnce, sam, NULL); + + sam->pan_id = pan_id; + + // Initialize send queueing system + sam->send_busy = false; + sam->sending = NULL; + sam->send_queue = NULL; + sam->free_queue = &(sam->queue_memory[0]); + sam->free_queue->next = NULL; + for (uint8_t i=1; iqueue_memory)/sizeof(sam_queue_element_t); i++) + { + sam->queue_memory[i].next = sam->free_queue; + sam->free_queue = &(sam->queue_memory[i]); + } + + // Set up dispatcher + sam->protocol = spr; + serial_protocol_add_dispatcher(sam->protocol, 0x00, + &(sam->dispatcher), + &serial_am_receive, &serial_am_senddone, + sam); + + // Set up the mist-comm layer + // TODO start-stop handlers + comms_am_create((comms_layer_t *)sam, address, + &serial_activemessage_send, &serial_activemessage_max_length, + NULL, + NULL, NULL); + + // serial_activemessage_t is a valid comms_layer_t + return (comms_layer_t *)sam; +} + +bool serial_activemessage_deinit (serial_activemessage_t* sam) +{ + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + + if ((1 == osTimerIsRunning(sam->timer)) + ||(NULL != sam->sending)) + { + osMutexRelease(sam->mutex); + return false; + } + + serial_protocol_remove_dispatcher(sam->protocol, &(sam->dispatcher)); + osTimerDelete(sam->timer); + + osMutexRelease(sam->mutex); + osMutexDelete(sam->mutex); + + return true; +} + +static bool serial_am_receive(uint8_t dispatch, const uint8_t data[], uint8_t length, void * user) +{ + serial_activemessage_t * sam = (serial_activemessage_t*)user; + comms_layer_t * lyr = (comms_layer_t*)sam; + debugb1("%p data", data, length, sam); + + if (sizeof(tos_serial_message_t) > length) + { + warn1("short"); // Message is too short + return false; + } + + tos_serial_message_t * m = (tos_serial_message_t*)data; + if(length - sizeof(tos_serial_message_t) < m->payload_length) + { + warn1("p len big"); // Payload is listed as larger than available data + return false; + } + + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + + comms_msg_t msg; + comms_init_message(lyr, &msg); + + uint8_t* payload = comms_get_payload(lyr, &msg, m->payload_length); + if (NULL == payload) + { + osMutexRelease(sam->mutex); + err1("pl %d", m->payload_length); + return false; + } + + comms_set_packet_type(lyr, &msg, m->amid); + // SAM group is 8-bits, PAN is 16. Add higher bits according to configured PAN + comms_set_packet_group(lyr, &msg, m->group | (sam->pan_id & 0xFF00)); + comms_set_payload_length(lyr, &msg, m->payload_length); + memcpy(payload, (const void *)m->payload, m->payload_length); + + // comms_set_timestamp(lyr, &msg, timestamp); // TODO Set to now? Make the lowest layer register frame starts ...? + + comms_am_set_destination(lyr, &msg, ntoh16(m->destination)); + comms_am_set_source(lyr, &msg, ntoh16(m->source)); + + debugb1("rx {%02"PRIX16"}%04"PRIX16"->%04"PRIX16"[%02X]", + payload, comms_get_payload_length(lyr, &msg), + comms_get_packet_group(lyr, &msg), + comms_am_get_source(lyr, &msg), + comms_am_get_destination(lyr, &msg), + (unsigned int)comms_get_packet_type(lyr, &msg)); + + comms_deliver(lyr, &msg); + + osMutexRelease(sam->mutex); + + return true; +} + +/* +* Convert a comms message to a serial-protocol message buffer +* @param buffer - the buffer to store the message +* @param length - the length of the buffer +* @param msg - the message to convert +* @param lyr - the layer to use for interpreting the message +* @return length of the prepared message or 0 for errors +*/ +static uint16_t prepare_sp_message(uint8_t buffer[], uint16_t length, comms_msg_t * msg, serial_activemessage_t * sam) +{ + comms_layer_t * lyr = (comms_layer_t*)sam; + uint8_t plen = comms_get_payload_length(lyr, msg); + + if(plen + sizeof(tos_serial_message_t) <= length) + { + void * payload = comms_get_payload(lyr, msg, plen); + if (NULL != payload) + { + tos_serial_message_t* m = (tos_serial_message_t*)buffer; + uint16_t src = comms_am_get_source(lyr, msg); + uint16_t group = comms_get_packet_group(lyr, msg); + + if (0 == group) + { + group = sam->pan_id; + } + + if (0 == src) + { + src = comms_am_address(lyr); + } + + m->destination = hton16(comms_am_get_destination(lyr, msg)); + m->source = hton16(src); + m->group = 0xFF & group; // Group/PAN is 16-bits, however, the TinyOS SAM protocol only supports 8-bits. + m->amid = comms_get_packet_type(lyr, msg); + m->payload_length = plen; + memcpy(m->payload, payload, plen); + + // TODO check if LQI/RSSI is set in the packet + m->payload[plen] = comms_get_lqi(lyr, msg); + m->payload[plen+1] = comms_get_rssi(lyr, msg); + + return sizeof(tos_serial_message_t) + plen + 2; // + 2 for LQI/RSSI + } + else // Should not happen, unless platform configured incorrectly + { + err1("null pl"); + } + } + else + { + err1("pl size %d", (unsigned int)plen); + } + return 0; +} + +static void serial_am_senddone(uint8_t dispatch, const uint8_t data[], uint8_t length, bool acked, void * user) +{ + serial_activemessage_t * sam = (serial_activemessage_t*)user; + comms_layer_t * lyr = (comms_layer_t*)sam; + + debugb1("snt(a:%d) %02X", data, length, (int)acked, (unsigned int)dispatch); + + // Set flags on the message that was just sent + //comms_set_timestamp(lyr, sam->sending->msg, now()); + comms_set_ack_received(lyr, sam->sending->msg, acked); + + // Signal send done events + sam->sending->send_done(lyr, sam->sending->msg, COMMS_SUCCESS, sam->sending->user); + sam->sending->msg = NULL; + + // Mark sent and defer to send other pending messages + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + sam->send_busy = false; + osMutexRelease(sam->mutex); + osTimerStart(sam->timer, 1UL); +} + +static void serial_am_timer_cb(void * argument) +{ + serial_activemessage_t * sam = (serial_activemessage_t*)argument; + comms_layer_t * lyr = (comms_layer_t*)sam; + + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + if(sam->send_busy) + { + debug1("bsy"); // Must wait for last send to complete + osMutexRelease(sam->mutex); + return; + } + + if (NULL != sam->sending) + { + // Return the queue element to the free queue + sam->sending->next = sam->free_queue; + sam->free_queue = sam->sending; + sam->sending = NULL; + } + + if (NULL != sam->send_queue) + { + sam->sending = sam->send_queue; + sam->send_queue = sam->send_queue->next; + sam->send_busy = true; + } + osMutexRelease(sam->mutex); + + if (NULL != sam->sending) + { + uint16_t length = prepare_sp_message(sam->send_buffer, sizeof(sam->send_buffer), sam->sending->msg, sam); + if(length > 0) + { + if(true == serial_protocol_send(&(sam->dispatcher), sam->send_buffer, length, false)) + { + debug1("snd %p %p %p", sam->sending, sam->send_queue, sam->free_queue); + return; + } + else + { + err1("busy?"); + } + } + else + { + err1("prep"); + } + + // Something bad has happened, return message to user with error + sam->sending->send_done(lyr, sam->sending->msg, COMMS_EINVAL, sam->sending->user); + sam->sending->msg = NULL; // Pointer has been returned + + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + sam->send_busy = false; + osMutexRelease(sam->mutex); + osTimerStart(sam->timer, 1L); // Try next message + } + else + { + debug1("idle"); + } +} + +static comms_error_t serial_activemessage_send (comms_layer_iface_t * iface, + comms_msg_t * msg, + comms_send_done_f * send_done, void * user) +{ + comms_error_t result = COMMS_SUCCESS; + serial_activemessage_t * sam = (serial_activemessage_t*)iface; + while (osOK != osMutexAcquire(sam->mutex, osWaitForever)); + + debug1("snd %p %p", sam->send_queue, sam->free_queue); + if (NULL != sam->free_queue) + { + sam_queue_element_t ** qe = &(sam->send_queue); + while (NULL != *qe) + { + qe = &((*qe)->next); + } + + *qe = sam->free_queue; + sam->free_queue = sam->free_queue->next; + + (*qe)->msg = msg; + (*qe)->send_done = send_done; + (*qe)->user = user; + (*qe)->next = NULL; + + debug1("q %p %p", sam->send_queue, sam->free_queue); + osTimerStart(sam->timer, 1UL); + } + else // Queue full + { + result = COMMS_EBUSY; + } + + osMutexRelease(sam->mutex); + + return result; +} + +static uint8_t serial_activemessage_max_length (comms_layer_iface_t * iface) +{ + return COMMS_MSG_PAYLOAD_SIZE; +} diff --git a/serial/serial_activemessage.h b/serial/serial_activemessage.h new file mode 100644 index 0000000..8e25921 --- /dev/null +++ b/serial/serial_activemessage.h @@ -0,0 +1,89 @@ +/** +* MistComm SerialActiveMessage layer. +* +* Many instances can be created, each needs a serial_protocol. +* Instances will set up a dispatcher with dispatch ID 0x00. +* +* Copyright Thinnect Inc. 2021 +* @license MIT +*/ +#ifndef SERIAL_ACTIVEMESSAGE_H_ +#define SERIAL_ACTIVEMESSAGE_H_ + +#include "mist_comm_am.h" +#include "mist_comm.h" +#include "serial_protocol.h" + +#include "cmsis_os2.h" + +// The serial_activemessage instance structure +typedef struct serial_activemessage serial_activemessage_t; + +/** + * Initialize the SerialActiveMessage layer, providing it memory and access + * to a lower layer SerialProtocol. + * + * @param sam - The SerialActiveMessage to initialize, a pointer to a persistent + * memory structure. + * @param spr - Pointer to an initialized SerialProtocol layer. + * + * @return a MistComm instance that can be used for sending/receiving messages + * or NULL for failure. + */ +comms_layer_t* serial_activemessage_init (serial_activemessage_t * sam, + serial_protocol_t * spr, + uint16_t pan_id, uint16_t address); + +/** + * Deinitialize the SerialActiveMessage layer. The memory used by the instance + * may be freed after this. + * + * @param sam - An initialized SerialActiveMessage + * @return true if successful, false if busy + */ +bool serial_activemessage_deinit (serial_activemessage_t * sam); + + +// Internal details to allow memory allocation---------------------------------- + +// send queue structure +typedef struct sam_queue_element sam_queue_element_t; +struct sam_queue_element +{ + comms_msg_t* msg; + comms_send_done_f *send_done; + void *user; + sam_queue_element_t* next; +}; + +#ifndef SERIAL_ACTIVEMESSAGE_QUEUE_LENGTH +#define SERIAL_ACTIVEMESSAGE_QUEUE_LENGTH 3 +#endif//SERIAL_ACTIVEMESSAGE_QUEUE_LENGTH + +#ifndef SERIAL_ACTIVEMESSAGE_MAX_MESSAGE_LENGTH +#define SERIAL_ACTIVEMESSAGE_MAX_MESSAGE_LENGTH 128 +#endif//SERIAL_ACTIVEMESSAGE_MAX_MESSAGE_LENGTH + +struct serial_activemessage +{ + comms_layer_am_t base; + serial_dispatcher_t dispatcher; + serial_protocol_t* protocol; + + osMutexId_t mutex; + osTimerId_t timer; + + sam_queue_element_t * sending; + sam_queue_element_t * send_queue; + sam_queue_element_t * free_queue; + sam_queue_element_t queue_memory[SERIAL_ACTIVEMESSAGE_QUEUE_LENGTH]; + + am_addr_t am_addr; + + uint16_t pan_id; // only 8-bits currently get sent on the wire! + + bool send_busy; + uint8_t send_buffer[SERIAL_ACTIVEMESSAGE_MAX_MESSAGE_LENGTH]; +}; + +#endif//SERIAL_ACTIVEMESSAGE_H_ diff --git a/serial/serial_basicmessage.c b/serial/serial_basicmessage.c new file mode 100644 index 0000000..e8a63fb --- /dev/null +++ b/serial/serial_basicmessage.c @@ -0,0 +1,262 @@ +/** +* MistComm SerialBasicMessage layer implementation. +* +* Copyright Thinnect Inc. 2021 +* @license MIT +*/ +#include +#include +#include +#include + +#include "cmsis_os2.h" + +#include "mist_comm.h" +#include "serial_protocol.h" +#include "serial_basicmessage.h" + +#include "loglevels.h" +#define __MODUUL__ "sbm" +#define __LOG_LEVEL__ (LOG_LEVEL_serial_basicmessage & BASE_LOG_LEVEL) +#include "log.h" + +static comms_error_t serial_basicmessage_send (comms_layer_iface_t * iface, + comms_msg_t * msg, + comms_send_done_f * send_done, void * user); +static uint8_t serial_basicmessage_max_length (comms_layer_iface_t * iface); + +static bool serial_bm_receive(uint8_t dspch, const uint8_t data[], uint8_t length, void* user); +static void serial_bm_senddone(uint8_t dspch, const uint8_t data[], uint8_t length, bool acked, void* user); +static void serial_bm_timer_cb(void * argument); + +comms_layer_t * serial_basicmessage_init (serial_basicmessage_t * sbm, serial_protocol_t * spr, + uint8_t dispatch, am_id_t amid) +{ + if ( ! comms_verify_api()) + { + return NULL; + } + + sbm->mutex = osMutexNew(NULL); + sbm->timer = osTimerNew(&serial_bm_timer_cb, osTimerOnce, sbm, NULL); + + // Initialize send queueing system + sbm->send_busy = false; + sbm->sending = NULL; + sbm->send_queue = NULL; + sbm->free_queue = &(sbm->queue_memory[0]); + sbm->free_queue->next = NULL; + for (uint8_t i=1; iqueue_memory)/sizeof(sbm_queue_element_t); i++) + { + sbm->queue_memory[i].next = sbm->free_queue; + sbm->free_queue = &(sbm->queue_memory[i]); + } + sbm->amid = amid; + + // Set up dispatcher + sbm->protocol = spr; + serial_protocol_add_dispatcher(sbm->protocol, dispatch, + &(sbm->dispatcher), + &serial_bm_receive, &serial_bm_senddone, + sbm); + + // Set up the mist-comm layer + // TODO start-stop handlers + comms_am_create((comms_layer_t *)sbm, 0, + &serial_basicmessage_send, &serial_basicmessage_max_length, + NULL, + NULL, NULL); + + // serial_basicmessage_t is a semi-valid comms_layer_t + return (comms_layer_t *)sbm; +} + +bool serial_basicmessage_deinit (serial_basicmessage_t* sbm) +{ + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + + if ((1 == osTimerIsRunning(sbm->timer)) + ||(NULL != sbm->sending)) + { + osMutexRelease(sbm->mutex); + return false; + } + + serial_protocol_remove_dispatcher(sbm->protocol, &(sbm->dispatcher)); + osTimerDelete(sbm->timer); + + osMutexRelease(sbm->mutex); + osMutexDelete(sbm->mutex); + + return true; +} + +static bool serial_bm_receive(uint8_t dispatch, const uint8_t data[], uint8_t length, void * user) +{ + serial_basicmessage_t * sbm = (serial_basicmessage_t*)user; + comms_layer_t * lyr = (comms_layer_t*)sbm; + debugb1("%p data", data, length, sbm); + + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + + comms_msg_t msg; + comms_init_message(lyr, &msg); + + uint8_t* payload = comms_get_payload(lyr, &msg, length); + if (NULL == payload) + { + osMutexRelease(sbm->mutex); + err1("pl %d", length); + return false; + } + + comms_set_packet_type(lyr, &msg, sbm->amid); // Set to specified amid as it needs to pass through deliver + comms_set_payload_length(lyr, &msg, length); + memcpy(payload, data, length); + + // comms_set_timestamp(lyr, &msg, timestamp); // TODO Set to now? Make the lowest layer register frame starts ...? + + // Addresses are set to broadcast, as mist-comm may complain about 0 addresses + comms_am_set_destination(lyr, &msg, AM_BROADCAST_ADDR); + comms_am_set_source(lyr, &msg, AM_BROADCAST_ADDR); + + debugb1("rx {%02"PRIX8"}", + payload, comms_get_payload_length(lyr, &msg), + dispatch); + + comms_deliver(lyr, &msg); + + osMutexRelease(sbm->mutex); + + return true; +} + +static void serial_bm_senddone(uint8_t dispatch, const uint8_t data[], uint8_t length, bool acked, void * user) +{ + serial_basicmessage_t * sbm = (serial_basicmessage_t*)user; + comms_layer_t * lyr = (comms_layer_t*)sbm; + + debugb1("snt(a:%d) %02X", data, length, (int)acked, (unsigned int)dispatch); + + // Set flags on the message that was just sent + //comms_set_timestamp(lyr, sbm->sending->msg, now()); + comms_set_ack_received(lyr, sbm->sending->msg, acked); + + // Signal send done events + sbm->sending->send_done(lyr, sbm->sending->msg, COMMS_SUCCESS, sbm->sending->user); + sbm->sending->msg = NULL; + + // Mark sent and defer to send other pending messages + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + sbm->send_busy = false; + osMutexRelease(sbm->mutex); + osTimerStart(sbm->timer, 1UL); +} + +static void serial_bm_timer_cb(void * argument) +{ + serial_basicmessage_t * sbm = (serial_basicmessage_t*)argument; + comms_layer_t * lyr = (comms_layer_t*)sbm; + + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + if(sbm->send_busy) + { + debug1("bsy"); // Must wait for last send to complete + osMutexRelease(sbm->mutex); + return; + } + + if (NULL != sbm->sending) + { + // Return the queue element to the free queue + sbm->sending->next = sbm->free_queue; + sbm->free_queue = sbm->sending; + sbm->sending = NULL; + } + + if (NULL != sbm->send_queue) + { + sbm->sending = sbm->send_queue; + sbm->send_queue = sbm->send_queue->next; + sbm->send_busy = true; + } + osMutexRelease(sbm->mutex); + + if (NULL != sbm->sending) + { + uint16_t length = comms_get_payload_length(lyr, sbm->sending->msg); + void * payload = comms_get_payload(lyr, sbm->sending->msg, length); + if (NULL != payload) + { + if(true == serial_protocol_send(&(sbm->dispatcher), payload, length, false)) + { + debug1("snd %p %p %p", sbm->sending, sbm->send_queue, sbm->free_queue); + return; + } + else + { + err1("busy?"); + } + } + else + { + err1("payload"); + } + + // Something bad has happened, return message to user with error + sbm->sending->send_done(lyr, sbm->sending->msg, COMMS_EINVAL, sbm->sending->user); + sbm->sending->msg = NULL; // Pointer has been returned + + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + sbm->send_busy = false; + osMutexRelease(sbm->mutex); + osTimerStart(sbm->timer, 1L); // Try next message + } + else + { + debug1("idle"); + } +} + +static comms_error_t serial_basicmessage_send (comms_layer_iface_t * iface, + comms_msg_t * msg, + comms_send_done_f * send_done, void * user) +{ + comms_error_t result = COMMS_SUCCESS; + serial_basicmessage_t * sbm = (serial_basicmessage_t*)iface; + while (osOK != osMutexAcquire(sbm->mutex, osWaitForever)); + + debug1("snd %p %p", sbm->send_queue, sbm->free_queue); + if (NULL != sbm->free_queue) + { + sbm_queue_element_t ** qe = &(sbm->send_queue); + while (NULL != *qe) + { + qe = &((*qe)->next); + } + + *qe = sbm->free_queue; + sbm->free_queue = sbm->free_queue->next; + + (*qe)->msg = msg; + (*qe)->send_done = send_done; + (*qe)->user = user; + (*qe)->next = NULL; + + debug1("q %p %p", sbm->send_queue, sbm->free_queue); + osTimerStart(sbm->timer, 1UL); + } + else // Queue full + { + result = COMMS_EBUSY; + } + + osMutexRelease(sbm->mutex); + + return result; +} + +static uint8_t serial_basicmessage_max_length (comms_layer_iface_t * iface) +{ + return COMMS_MSG_PAYLOAD_SIZE; +} diff --git a/serial/serial_basicmessage.h b/serial/serial_basicmessage.h new file mode 100644 index 0000000..ba1566e --- /dev/null +++ b/serial/serial_basicmessage.h @@ -0,0 +1,84 @@ +/** +* MistComm SerialBasicMessage layer. +* +* Many instances can be created, each needs a serial_protocol. +* +* Copyright Thinnect Inc. 2021 +* @license MIT +*/ +#ifndef SERIAL_BASICMESSAGE_H_ +#define SERIAL_BASICMESSAGE_H_ + +#include "mist_comm_am.h" +#include "mist_comm.h" +#include "serial_protocol.h" + +#include "cmsis_os2.h" + +// The serial_basicmessage instance structure +typedef struct serial_basicmessage serial_basicmessage_t; + +/** + * Initialize the SerialbasicMessage layer, providing it memory and access + * to a lower layer SerialProtocol. + * + * @param sbm - The SerialBasicMessage to initialize, a pointer to a persistent + * memory structure. + * @param spr - Pointer to an initialized SerialProtocol layer. + * @param dispatch - The SerialProtocol dispatch ID to use. + * @param amid - The AM ID to use for RX messages (for compatibility). + * + * @return a MistComm instance that can be used for sending/receiving messages + * or NULL for failure. + */ +comms_layer_t* serial_basicmessage_init (serial_basicmessage_t * sbm, + serial_protocol_t * spr, + uint8_t dispatch, + am_id_t amid); + +/** + * Deinitialize the SerialbasicMessage layer. The memory used by the instance + * may be freed after this. + * + * @param sbm - An initialized SerialbasicMessage + * @return true if successful, false if busy + */ +bool serial_basicmessage_deinit (serial_basicmessage_t * sbm); + + +// Internal details to allow memory allocation---------------------------------- + +// send queue structure +typedef struct sbm_queue_element sbm_queue_element_t; +struct sbm_queue_element +{ + comms_msg_t* msg; + comms_send_done_f *send_done; + void *user; + sbm_queue_element_t* next; +}; + +#ifndef SERIAL_BASICMESSAGE_QUEUE_LENGTH +#define SERIAL_BASICMESSAGE_QUEUE_LENGTH 2 +#endif//SERIAL_BASICMESSAGE_QUEUE_LENGTH + +struct serial_basicmessage +{ + comms_layer_am_t base; + serial_dispatcher_t dispatcher; + serial_protocol_t* protocol; + + osMutexId_t mutex; + osTimerId_t timer; + + sbm_queue_element_t * sending; + sbm_queue_element_t * send_queue; + sbm_queue_element_t * free_queue; + sbm_queue_element_t queue_memory[SERIAL_BASICMESSAGE_QUEUE_LENGTH]; + + am_id_t amid; + + bool send_busy; +}; + +#endif//SERIAL_BASICMESSAGE_H_ diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +test diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..d3d6acf --- /dev/null +++ b/test/Makefile @@ -0,0 +1,6 @@ +# Makefile + +CFLAGS = -I. -I../mock -I../api -I../include -I../include/compat -std=c99 -Wall + +all: + gcc ${CFLAGS} main.c ../mock/mist_comm_mutex.c ../api/mist_comm_api.c ../api/mist_comm_rcv.c ../am/mist_comm_am.c -o test diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..5e2a7b3 --- /dev/null +++ b/test/main.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include // sleep + +#include "mist_comm.h" +#include "mist_comm_am.h" + +const char hello[] = "HelloWorld!"; + +comms_msg_t mmsg1; +comms_msg_t* mcopy1 = &mmsg1; +comms_msg_t* _msg1 = NULL; +comms_send_done_f* _sdf1 = NULL; +void* _user1 = NULL; + +comms_error_t fake_comms_send1(comms_layer_iface_t* comms, comms_msg_t* msg, comms_send_done_f* sdf, void* user) { + if(_msg1 == NULL) { + _msg1 = msg; + _sdf1 = sdf; + _user1 = user; + return COMMS_SUCCESS; + } + return COMMS_EBUSY; +} + +comms_msg_t mmsg2; +comms_msg_t* mcopy2 = &mmsg2; +comms_msg_t* _msg2 = NULL; +comms_send_done_f* _sdf2 = NULL; +void* _user2 = NULL; + +comms_error_t fake_comms_send2(comms_layer_iface_t* comms, comms_msg_t* msg, comms_send_done_f* sdf, void* user) { + if(_msg2 == NULL) { + _msg2 = msg; + _sdf2 = sdf; + _user2 = user; + return COMMS_SUCCESS; + } + return COMMS_EBUSY; +} + +void fake_comms_receive(comms_layer_t* comms, const comms_msg_t* msg, void* user) { + printf("rcv %p, %p, %p\n", comms, msg, user); + printf("%04X->%04X %s\n", comms_am_get_source(comms, msg), + comms_am_get_destination(comms, msg), + (char*)comms_get_payload(comms, msg, comms_get_payload_length(comms, msg))); +} + +void send_done(comms_layer_t* comms, comms_msg_t* msg, comms_error_t result, void* user) { + printf("sd %p, %p, %u, %p\n", comms, msg, result, user); +} + +bool test(comms_layer_t* radio) { + comms_msg_t msg; + + comms_init_message(radio, &msg); + + if(comms_timestamp_valid(radio, &msg) == true) { + return false; + } + comms_set_timestamp(radio, &msg, 1234); + if(comms_timestamp_valid(radio, &msg) == false) { + return false; + } + if(comms_get_timestamp(radio, &msg) != 1234) { + return false; + } + + if(comms_event_time_valid(radio, &msg) == true) { + return false; + } + comms_set_event_time(radio, &msg, 5678); + if(comms_event_time_valid(radio, &msg) == false) { + return false; + } + if(comms_get_event_time(radio, &msg) != 5678) { + return false; + } + + if(comms_is_ack_required(radio, &msg) != false) { + return false; + } + comms_set_ack_required(radio, &msg, true); + if(comms_is_ack_required(radio, &msg) != true) { + return false; + } + + if(comms_ack_received(radio, &msg) != false) { + return false; + } + _comms_set_ack_received(radio, &msg); + if(comms_ack_received(radio, &msg) != true) { + return false; + } + + return true; +} + +int main() { + comms_msg_t msg; + uint8_t r1[512]; + uint8_t r2[512]; + uint8_t* payload; + comms_layer_t* radio1 = (comms_layer_t*)r1; + comms_layer_t* radio2 = (comms_layer_t*)r2; + comms_receiver_t rcv1; + comms_receiver_t rcv2; + comms_error_t err; + uint8_t length = strlen(hello)+1; + + err = comms_am_create(radio1, 1, &fake_comms_send1, NULL, NULL); + printf("create1=%d\n", err); + err = comms_am_create(radio2, 2, &fake_comms_send2, NULL, NULL); + printf("create2=%d\n", err); + + printf("tests = %d\n", test(radio1)); + + comms_register_recv(radio1, &rcv1, &fake_comms_receive, NULL, 0xAB); + comms_register_recv(radio2, &rcv2, &fake_comms_receive, NULL, 0xAB); + + comms_init_message(radio1, &msg); + comms_set_packet_type(radio1, &msg, 0xAB); + comms_am_set_destination(radio1, &msg, 0xCDEF); + comms_am_set_source(radio1, &msg, 0x1234); + + payload = comms_get_payload(radio1, &msg, length); + if(payload == NULL) { + printf("payload NULL\n"); + return 1; + } + memcpy(payload, hello, length); + comms_set_payload_length(radio1, &msg, length); + + //printf("%x\n", comms_am_get_source(radio1, &msg)); + printf("snd %04X->%04X\n", comms_am_get_source(radio1, &msg), comms_am_get_destination(radio1, &msg)); + + err = comms_send(radio1, &msg, &send_done, NULL); + printf("send(%p)=%d\n", &msg, err); + if(err != COMMS_SUCCESS) { + return 1; + } + + while(true) { + sleep(1); + if(_msg1 != NULL) { + comms_msg_t* m = _msg1; + _msg1 = NULL; + memcpy(mcopy1, m, sizeof(comms_msg_t)); + mcopy1 = comms_deliver(radio2, mcopy1); + _sdf1(radio1, m, COMMS_SUCCESS, _user1); + } + if(_msg2 != NULL) { + comms_msg_t* m = _msg2; + _msg2 = NULL; + memcpy(mcopy2, m, sizeof(comms_msg_t)); + mcopy2 = comms_deliver(radio1, mcopy2); + _sdf2(radio2, m, COMMS_SUCCESS, _user2); + } + } + + return 0; +} diff --git a/test/platform_msg.h b/test/platform_msg.h new file mode 100644 index 0000000..68282c6 --- /dev/null +++ b/test/platform_msg.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_MSG_H +#define PLATFORM_MSG_H + +#include "mist_comm_am_msg.h" + +#define COMMS_MSG_ADDRESSING_SIZE 2 +#define COMMS_MSG_HEADER_SIZE 1 +#define COMMS_MSG_PAYLOAD_SIZE 128 +#define COMMS_MSG_FOOTER_SIZE 0 +#define COMMS_MSG_METADATA_SIZE sizeof(comms_am_msg_metadata_t) + +#endif//PLATFORM_MSG_H diff --git a/test/sleep/.gitignore b/test/sleep/.gitignore new file mode 100644 index 0000000..0977986 --- /dev/null +++ b/test/sleep/.gitignore @@ -0,0 +1,2 @@ +test_runners +test_sleep.out diff --git a/test/sleep/Makefile b/test/sleep/Makefile new file mode 100644 index 0000000..15e2369 --- /dev/null +++ b/test/sleep/Makefile @@ -0,0 +1,34 @@ +UNITY_ROOT=../zoo/ThrowTheSwitch.Unity + +CFLAGS = -std=c99 -g +CFLAGS += -Wall + +SRC_FILES = $(UNITY_ROOT)/src/unity.c test_sleep.c +SRC_FILES += test_runners/TestProductionCode_Runner.c +SRC_FILES += ../../api/mist_comm_api.c +SRC_FILES += ../../control/mist_comm_controller.c +SRC_FILES += ../../mock/mist_comm_mutex.c + + +INC_DIRS = -I. -I$(UNITY_ROOT)/src +INC_DIRS += -I.. +INC_DIRS += -I../../mock +INC_DIRS += -I../../include +INC_DIRS += -I../../include/compat + +INC_DIRS += -I../zoo/thinnect.node-platform/include + +TARGET = test_sleep.out + +all: clean default + +default: $(SRC_FILES) + gcc $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES) -o $(TARGET) + - ./$(TARGET) + +test_runners/TestProductionCode_Runner.c: test_sleep.c + mkdir -p test_runners + ruby $(UNITY_ROOT)/auto/generate_test_runner.rb test_sleep.c test_runners/TestProductionCode_Runner.c + +clean: + rm -f $(TARGET) diff --git a/test/sleep/log.h b/test/sleep/log.h new file mode 100644 index 0000000..1fc2669 --- /dev/null +++ b/test/sleep/log.h @@ -0,0 +1,6 @@ +#ifndef LOG_H_ +#define LOG_H_ + +void err1(char * s, ...); + +#endif//LOG_H_ diff --git a/test/sleep/loglevels.h b/test/sleep/loglevels.h new file mode 100644 index 0000000..e69de29 diff --git a/test/sleep/test_sleep.c b/test/sleep/test_sleep.c new file mode 100644 index 0000000..bad8c9e --- /dev/null +++ b/test/sleep/test_sleep.c @@ -0,0 +1,219 @@ +/** + * Unit-Tests for the MistComm start-stop functionality. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "unity.h" +#include "string.h" + +#include "mist_comm.h" +#include "mist_comm_iface.h" +#include "mist_comm_mutex.h" + +static int m_start_requested = 0; +static int m_stop_requested = 0; +static int m_change_called = 0; +static int m_errors = 0; +static comms_status_t m_status = COMMS_UNINITIALIZED; + +static void * m_start_user = NULL; +static void * m_stop_user = NULL; +static comms_status_change_f * m_start_cb = NULL; +static comms_status_change_f * m_stop_cb = NULL; + + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +comms_error_t comms_dummy_start (comms_layer_iface_t* lyr, comms_status_change_f * cb, void * user) +{ + m_start_requested = true; + m_start_cb = cb; + m_start_user = user; + return COMMS_SUCCESS; +} + +comms_error_t comms_dummy_stop (comms_layer_iface_t* lyr, comms_status_change_f * cb, void * user) +{ + m_stop_requested = true; + m_stop_cb = cb; + m_stop_user = user; + return COMMS_SUCCESS; +} + +void comms_status_changed (comms_layer_t* comms, comms_status_t status, void* user) +{ + m_change_called++; + m_status = status; +} + +typedef struct mocked_deferred +{ + comms_deferred_f * cb; + void * arg; + bool pending; +} mocked_deferred_t; + +mocked_deferred_t m_deferred = {NULL, NULL, false}; + +void _comms_deferred_init(comms_layer_t * comms, void ** deferred, comms_deferred_f * cb) +{ + *deferred = &m_deferred; + m_deferred.cb = cb; + m_deferred.arg = comms; + m_deferred.pending = false; +} + +void _comms_defer(void * deferred) +{ + ((mocked_deferred_t*)deferred)->pending = true; +} + +void _comms_deferred_deinit(void * deferred) +{ + ((mocked_deferred_t*)deferred)->pending = false; + ((mocked_deferred_t*)deferred)->arg = NULL; + ((mocked_deferred_t*)deferred)->cb = NULL; +} + +void err1(char * s, ...) +{ + m_errors++; +} + +void test_SleepControllers() +{ + comms_layer_iface_t base; + comms_sleep_controller_t scs[3]; + + base.start = &comms_dummy_start; + base.stop = &comms_dummy_stop; + base.status = COMMS_STOPPED; + base.sleep_controllers = NULL; + base.sleep_controller_deferred = NULL; + base.status_change_user_cb = NULL; + base.controller_mutex = comms_mutex_create(); + base.start_stop_mutex = comms_mutex_create(); + base.receiver_mutex = comms_mutex_create(); + + TEST_ASSERT_EQUAL_INT(COMMS_STOPPED, comms_status((comms_layer_t*)&base)); + + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_register_sleep_controller( + (comms_layer_t*)&base, + &(scs[0]), + comms_status_changed, + &(scs[0]))); + + TEST_ASSERT_EQUAL_INT(COMMS_STOPPED, comms_status((comms_layer_t*)&base)); + + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_block(&(scs[0]))); + + TEST_ASSERT_EQUAL(1, m_start_requested); + TEST_ASSERT_NOT_EQUAL(NULL, m_start_cb); + + m_start_cb((comms_layer_t*)&base, COMMS_STARTED, m_start_user); + + TEST_ASSERT_EQUAL(1, m_change_called); + TEST_ASSERT_EQUAL(COMMS_STARTED, m_status); + TEST_ASSERT_EQUAL_INT(COMMS_STARTED, comms_status((comms_layer_t*)&base)); + + // Add another and stop and start both + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_register_sleep_controller( + (comms_layer_t*)&base, + &(scs[1]), + comms_status_changed, + &(scs[1]))); + + m_change_called = 0; + TEST_ASSERT_EQUAL_INT(COMMS_ALREADY, comms_sleep_block(&(scs[1]))); + TEST_ASSERT_EQUAL(0, m_change_called); + + m_change_called = 0; + m_stop_requested = 0; + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_allow(&(scs[0]))); + TEST_ASSERT_EQUAL(0, m_change_called); + TEST_ASSERT_EQUAL(0, m_stop_requested); + + m_change_called = 0; + m_stop_requested = false; + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_allow(&(scs[1]))); + TEST_ASSERT_EQUAL(0, m_change_called); + TEST_ASSERT_EQUAL(1, m_stop_requested); + + m_change_called = 0; + m_stop_cb((comms_layer_t*)&base, COMMS_STOPPED, m_stop_user); + TEST_ASSERT_EQUAL(0, m_change_called); + TEST_ASSERT_EQUAL(COMMS_STOPPED, comms_status((comms_layer_t*)&base)); + + m_change_called = 0; + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_block(&(scs[1]))); + TEST_ASSERT_EQUAL(0, m_change_called); + + TEST_ASSERT_EQUAL(1, m_start_requested); + TEST_ASSERT_NOT_EQUAL(NULL, m_start_cb); + + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_block(&(scs[0]))); + TEST_ASSERT_EQUAL(0, m_change_called); + + TEST_ASSERT_EQUAL(1, m_start_requested); // Should not be called multiple times + + m_start_cb((comms_layer_t*)&base, COMMS_STARTED, m_start_user); + TEST_ASSERT_EQUAL(2, m_change_called); + TEST_ASSERT_EQUAL_INT(COMMS_STARTED, comms_status((comms_layer_t*)&base)); + + // Stop the current ones, add a third one and start it before the ongoing stop completes + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_register_sleep_controller( + (comms_layer_t*)&base, + &(scs[2]), + comms_status_changed, + &(scs[2]))); + + m_start_requested = 0; + m_stop_requested = 0; + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_allow(&(scs[0]))); + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_allow(&(scs[1]))); + TEST_ASSERT_EQUAL(1, m_stop_requested); + + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_sleep_block(&(scs[2]))); + TEST_ASSERT_EQUAL(0, m_start_requested); // Should not start, as still stopping + + m_change_called = 0; + m_stop_cb((comms_layer_t*)&base, COMMS_STOPPED, m_stop_user); + TEST_ASSERT_EQUAL(0, m_change_called); + + TEST_ASSERT_EQUAL(true, m_deferred.pending); + m_deferred.cb(m_deferred.arg); + + TEST_ASSERT_EQUAL(1, m_start_requested); + TEST_ASSERT_NOT_EQUAL(NULL, m_start_cb); + m_start_cb((comms_layer_t*)&base, COMMS_STARTED, m_start_user); + + TEST_ASSERT_EQUAL(1, m_change_called); + TEST_ASSERT_EQUAL(COMMS_STARTED, m_status); + TEST_ASSERT_EQUAL_INT(COMMS_STARTED, comms_status((comms_layer_t*)&base)); + + // Deregister all controllers, layer should be stopped + m_stop_requested = 0; + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_deregister_sleep_controller( + (comms_layer_t*)&base, + &(scs[0]))); + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_deregister_sleep_controller( + (comms_layer_t*)&base, + &(scs[1]))); + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_deregister_sleep_controller( + (comms_layer_t*)&base, + &(scs[2]))); + TEST_ASSERT_EQUAL(1, m_stop_requested); + m_stop_cb((comms_layer_t*)&base, COMMS_STOPPED, m_stop_user); + TEST_ASSERT_EQUAL_INT(COMMS_STOPPED, comms_status((comms_layer_t*)&base)); + + // Make sure we did not pick up any errors during the test + TEST_ASSERT_EQUAL(0, m_errors); +} diff --git a/test/startstop/.gitignore b/test/startstop/.gitignore new file mode 100644 index 0000000..ace99d2 --- /dev/null +++ b/test/startstop/.gitignore @@ -0,0 +1,2 @@ +test_runners +test_startstop.out diff --git a/test/startstop/Makefile b/test/startstop/Makefile new file mode 100644 index 0000000..3f1811e --- /dev/null +++ b/test/startstop/Makefile @@ -0,0 +1,32 @@ +UNITY_ROOT=../zoo/ThrowTheSwitch.Unity + +CFLAGS = -std=c99 -g +CFLAGS += -Wall + +SRC_FILES = $(UNITY_ROOT)/src/unity.c test_startstop.c +SRC_FILES += test_runners/TestProductionCode_Runner.c +SRC_FILES += ../../api/mist_comm_api.c +SRC_FILES += ../../mock/mist_comm_mutex.c + +INC_DIRS = -I. -I$(UNITY_ROOT)/src +INC_DIRS += -I../../mock +INC_DIRS += -I.. +INC_DIRS += -I../../include +INC_DIRS += -I../../include/compat + +INC_DIRS += -I../zoo/thinnect.node-platform/include + +TARGET = test_startstop.out + +all: clean default + +default: $(SRC_FILES) + gcc $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES) -o $(TARGET) + - ./$(TARGET) + +test_runners/TestProductionCode_Runner.c: test_startstop.c + mkdir -p test_runners + ruby $(UNITY_ROOT)/auto/generate_test_runner.rb test_startstop.c test_runners/TestProductionCode_Runner.c + +clean: + rm -f $(TARGET) diff --git a/test/startstop/test_startstop.c b/test/startstop/test_startstop.c new file mode 100644 index 0000000..f28e7a3 --- /dev/null +++ b/test/startstop/test_startstop.c @@ -0,0 +1,111 @@ +/** + * Unit-Tests for the MistComm start-stop functionality. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ + +#include "unity.h" +#include "string.h" + +#include "mist_comm.h" +#include "mist_comm_iface.h" + +static bool m_start_requested = false; +static bool m_stop_requested = false; +static bool m_started_called = false; +static bool m_stopped_called = false; +static comms_status_t m_status = COMMS_UNINITIALIZED; + +static void * m_start_user = NULL; +static void * m_stop_user = NULL; +static comms_status_change_f * m_start_cb = NULL; +static comms_status_change_f * m_stop_cb = NULL; + + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +comms_error_t comms_dummy_start (comms_layer_iface_t* lyr, comms_status_change_f * cb, void * user) +{ + m_start_requested = true; + m_start_cb = cb; + m_start_user = user; + return COMMS_SUCCESS; +} + +comms_error_t comms_dummy_stop (comms_layer_iface_t* lyr, comms_status_change_f * cb, void * user) +{ + m_stop_requested = true; + m_stop_cb = cb; + m_stop_user = user; + return COMMS_SUCCESS; +} + +void comms_start_done (comms_layer_t* comms, comms_status_t status, void* user) +{ + m_started_called = true; + m_status = status; +} + +void comms_stop_done (comms_layer_t* comms, comms_status_t status, void* user) +{ + m_stopped_called = true; + m_status = status; +} + +void test_StartAndStop() +{ + comms_layer_iface_t base; + + base.start = &comms_dummy_start; + base.stop = &comms_dummy_stop; + + base.status_change_user_cb = NULL; + base.controller_mutex = comms_mutex_create(); + base.start_stop_mutex = comms_mutex_create(); + base.receiver_mutex = comms_mutex_create(); + + // Test starting + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_start((comms_layer_t*)&base, &comms_start_done, NULL)); + + TEST_ASSERT_EQUAL(true, m_start_requested); + TEST_ASSERT_EQUAL(false, m_stop_requested); + TEST_ASSERT_EQUAL_INT(COMMS_STARTING, comms_status((comms_layer_t*)&base)); + + m_start_cb((comms_layer_t*)&base, COMMS_STARTED, m_start_user); + + TEST_ASSERT_EQUAL(true, m_started_called); + TEST_ASSERT_EQUAL(false, m_stopped_called); + TEST_ASSERT_EQUAL_INT(COMMS_STARTED, m_status); + TEST_ASSERT_EQUAL_INT(COMMS_STARTED, comms_status((comms_layer_t*)&base)); + TEST_ASSERT_NOT_EQUAL(NULL, m_start_cb); + + // Reset flags + m_start_cb = NULL; + m_stop_cb = NULL; + m_start_requested = false; + m_stop_requested = false; + m_started_called = false; + m_stopped_called = false; + + // Test stopping + TEST_ASSERT_EQUAL_INT(COMMS_SUCCESS, comms_stop((comms_layer_t*)&base, &comms_stop_done, NULL)); + + TEST_ASSERT_EQUAL(false, m_start_requested); + TEST_ASSERT_EQUAL(true, m_stop_requested); + TEST_ASSERT_EQUAL_INT(COMMS_STOPPING, comms_status((comms_layer_t*)&base)); + TEST_ASSERT_NOT_EQUAL(NULL, m_stop_cb); + + m_stop_cb((comms_layer_t*)&base, COMMS_STOPPED, m_stop_user); + + TEST_ASSERT_EQUAL(false, m_started_called); + TEST_ASSERT_EQUAL(true, m_stopped_called); + TEST_ASSERT_EQUAL_INT(COMMS_STOPPED, m_status); + TEST_ASSERT_EQUAL_INT(COMMS_STOPPED, comms_status((comms_layer_t*)&base)); +} diff --git a/test/zoo/ThrowTheSwitch.Unity b/test/zoo/ThrowTheSwitch.Unity new file mode 160000 index 0000000..e3132cd --- /dev/null +++ b/test/zoo/ThrowTheSwitch.Unity @@ -0,0 +1 @@ +Subproject commit e3132cdddd779cb5a813887b2d0d7facb7f3f90a diff --git a/test/zoo/thinnect.node-platform b/test/zoo/thinnect.node-platform new file mode 160000 index 0000000..5f76d7c --- /dev/null +++ b/test/zoo/thinnect.node-platform @@ -0,0 +1 @@ +Subproject commit 5f76d7cccb4cd63a0ec9e700da9a9199fd00c3c8 diff --git a/util/fragmenter_assembler.c b/util/fragmenter_assembler.c new file mode 100644 index 0000000..3c42b56 --- /dev/null +++ b/util/fragmenter_assembler.c @@ -0,0 +1,63 @@ +/** + * Fragmentation and assembly functions. + * Maximum fragment size needs to be known for assembly, all but the last + * fragment need to be of this size, the last one can be shorter. + * Maximum number of fragments is 8, maximum data size is 255 bytes. + * + * Copyright Thinnect Inc. 2021 + * @license MIT + */ +#include "fragmenter_assembler.h" + + +uint8_t data_fragments (uint8_t dataSize, uint8_t fragMaxSize) +{ + uint8_t frags = (uint8_t)(1 + ((((int16_t)dataSize) - 1) / fragMaxSize)); + if (frags == 0) + { + return 1; + } + return frags; +} + + +uint8_t data_fragmenter (uint8_t fragment[], uint8_t fragSize, uint8_t offset, + uint8_t data[], uint8_t dataSize) +{ + if (offset < dataSize) + { + if (dataSize - offset < fragSize) + { + fragSize = dataSize - offset; + } + memcpy(fragment, &data[offset], fragSize); + return fragSize; + } + return 0; +} + + +bool data_assembler (uint8_t fragMaxSize, uint8_t object[], uint8_t objectSize, + uint8_t fragment[], uint8_t fragSize, + uint8_t offset, uint8_t* fragMap) +{ + uint8_t frag = offset / fragMaxSize; + uint8_t frags = objectSize / fragMaxSize + (objectSize % fragMaxSize != 0 ? 1 : 0); + uint8_t i; + if ((uint16_t)offset + fragSize <= objectSize) // (uint16_t) to make sure that offset+fragSize does not wrap + { + if (offset % fragMaxSize == 0) // is properly fragmented + { + memcpy(&object[offset], fragment, fragSize); + *fragMap = *fragMap | (1 << frag); + } + } + for (i=0;i +#include +#include + +/** + * Compute the number of fragments needed to split dataSize over fragments of fragMaxSize. + * Supports sending empty messages - returns 1 for dataSize 0. + * + * @param dataSize - Number of bytes to send. + * @param fragMaxSize - Free space in one fragment. + * + * @return Number of fragments needed to send dataSize bytes of data. + */ +uint8_t data_fragments(uint8_t dataSize, uint8_t fragMaxSize); + +/** + * @return size of fragment + */ +uint8_t data_fragmenter(uint8_t fragment[], uint8_t fragSize, uint8_t offset, + uint8_t data[], uint8_t dataSize); + +/** + * @return true, if assembly complete, false if data still missing. + */ +bool data_assembler(uint8_t fragMaxSize, uint8_t object[], uint8_t objectSize, + uint8_t fragment[], uint8_t fragSize, + uint8_t offset, uint8_t* fragMap); + +#endif // FRAGMENTER_ASSEMBLER_H_