-
Notifications
You must be signed in to change notification settings - Fork 6
RFC: Add ThingSet communication support framework #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
8dbf80c
to
2ef58ef
Compare
@b0661 this looks great. I'm currently hiking in the alps with very limited internet access and no laptop ;) Will have a closer look when I'm back in the office around Sept 16th. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for this nice work.
What about renaming the files to ts_core
instead of ts_context
? This terminology could also be applied to the specification to differentiate it from mappings to other protocols, etc.
In order to check if my understanding is correct: The struct ts_port
should be used to provide pointers to specific API implementations (e.g. serial or CAN), similar to drivers in Zephyr. But how will a port be started / initialized? With something similar to DEVICE_DEFINE
? Currently the different interfaces / ports just start their own thread and call ts_process
from it. Now with the shared buffers and abstracted interfaces we'd probably want a different approach.
And would you mind splitting the commit a bit more, e.g. put the ts_port.h
part into a dedicated commit so that one can find the introduction of this new concept by looking at the commit history?
ce45285
to
2bd61e0
Compare
done
A port will be started on it's first ts_port_open(). This should also do dynamic initialization. If this does not fit - e.g. the port has to be initialized on device startup - an extra ts_port_init() function has to be added to the port API. In zephyr this function may be called by SYS_INIT(). What do you think?
Zephyr's DEVICE_DEFINE has some hidden functionality that can only be provided by Zephyr. I would stay more generic and just provide the ts_port_init() function in the API and not do Zephyr specific macro and gen_xxx.py magic. The ts_port_init() call should be the task of the application developer in general. A Zephyr specific extension using e.g. SYS_INIT may be provided by the framework.
Why? The ports can still start their own threads. The ports' API functions have to provide thread synchronization - but that should be hidden from the application use of these functions.
done |
b614713
to
3012529
Compare
I think I did not quite understand what you mean by "different approach". I added a new context that now covers ThingSet data and communication info. You may have a look at it and check whether it fits to your approach. |
Well, I am not exactly sure about the approach either, but I thought that the current one is not feasible anymore. Current approach:
New approach:
Now, how is a port enabled, initialized and started? I can see below different options (assuming Zephyr environment):
We might not even need a dedicated thread for each port anymore. The data could be passed to the buffers from ISRs and after a complete package is received, the ThingSet processing is offloaded using the system work queue. |
I think it is good to distinguish between static (compile time) assignment and run time assignment.
Also the level of OS abstraction has to be defined.
Taking the above into account I would like a mixup of your scenarios 1 & 2.
EDIT: code examples added |
9ee3519
to
a6a01f2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand and I'm fine with most parts of the RFC. Please find questions to some specific commits below.
Commit 42db91d:
What is the reason behind splitting the subsets
from struct ts_data_object
into a dedicated struct? The commit message only states what has been done, but not why.
I see that storing the number of data objects together with the objects array in a dedicated database
struct can make the interface nicer. But putting the subsets
into a separate array will most probably increase the RAM footprint by 2 * ARRAY_SIZE(data_objects)
bytes, as sizeof(struct ts_data_object)
was a multiple of 32 bits and now the last 16 bits of each element will be wasted. (didn't actually test it, but I just remember that I took a lot of care to stay within the 32-bit multiples boundary for that struct)
Also I didn't immediately understand what _do
means (e.g. in the file name). If we go that route, I'd prefer _obj
.
Commit dc28c55:
What exactly is the difference between a port and a communication context. I was always thinking of the port as an abstraction for e.g. a serial interface. Now I'm wondering what exactly the communication context should add to that? And why does it need own functions like tsc_txt_statement
, which are already implemented in the core?
General question:
Could you maybe provide a code snippet similar to examples/basic
that explains how one would initialize and use the new ports and communication abstractions from an application? It doesn't need to be a fully working example, but I think it would help to understand your thinking behind the abstractions.
That's great.
To make the struct ts_data_object romable the mutable part has to be split out. Looking at the usage of data objects I saw only subsets changed during processing and therefor decided to split it out to keep it in ram.
Will change to _obj for "ThingSet Object".
Ports are the communication interfaces for a ThingSet context. Communication context is - as you noted - somehow misleading. It is a ThingSet context extended by communication features (aka. the ports and communication management). That's why the function prefix is tsc_ - ThingSet context 'ts' extended by communication 'c'. In C++ I would have created a derived class. In my view a ThingSet context extended by communication features may serve several ports. E.g. a CAN port and an interactive shell port for maintenance access.
Sorry this is a left over from an idea. Will be deleted in a coming update. I started to experiment an interface for functions that directly work on messages represented by a communication buffer. I did not want to change the current core interface and break compatibility (replacing all memory pointer uses with communication buffer usage). Most probably I will now extend the communication buffers interface with primitives to read and store cbor data.
Will do. The task of an application will be static configuration of the ports and at runtime the start of the ThingSet context communication processing. The static configuration will be a mixture of compile time configuration using kconfig (or a special header file) and the setup of the port objects table, similar to the ThingSet objects array. General comment: Keeping compatibility to a large extend dictates some design decisions. Breaking compatibility to a large would allow other designs:
|
Ahh, right, makes sense. Lots of macro magic necessary to do it, but in that case it's probably worth it. Do you know if the In the past I had the subsets (called pubsub channels back then) implemented as a dedicated array per subset instead of flags with one bit per subset stored together with the data object. However, it made the initial selection of data objects belonging to one subset error-prone, as you had to populate the array manually with the data object IDs. See 3d2da2a and ffaedc6 for details. Your approach combines both advantages (ROM storage and convenient assignment of subsets), which is nice. Could you please state the reason behind this change more clearly in the commit message? As it's a generic improvement, maybe also remove the "TS COM framework: prepare" in the commit message, so it's easier to find in the history in the future.
Could this not be achieved by introducing a pointer to the
Yeah, this is a difficult problem if we want to avoid memcpy operations. I'm not sure how difficult it would be to make the JSMN parser work with communication buffers instead of raw byte arrays. And there are also situations where the data more naturally comes as a simple byte array (e.g. configuration values stored in EEPROM). Maybe we should accept a bit of memcpy overhead for now and address buffer handling in a future PR so that we don't have too many changes at a time?
I would actually be fine with some breaking changes if it helps to keep interfaces much cleaner. To my knowledge the library is currently only used in Libre Solar firmware and by three other companies / organizations I've been working with. In all cases the libraries are included via git submodules or similar mechanisms which make sure that the module is not updated accidentally. So far I have not actively promoted the ThingSet library + protocol because we still identified several things to improve while using it in different environments. Now as the specification is becoming more stable and more people are using it, we could think about introducing SemVer for the library. But before we release v1.0 we can still take some more freedom regarding breaking changes in my opinion. Extending |
I know it works in Zephyr. I is still a beast and I currently have problems with my tests working but my application is complaining at compile time. I will try to get rid of it again. I think I will keep the subset info in ROM (as it was before) and copy it over to RAM at context initialization. All processing will be done on the RAM copy. This brings a slight waste of ROM memory, but will most probably allow to get rid of FOR_EACH and complex, error prone, hard to understand, hard to debug macro usage !-)
Shure.
Yes, the context could call the ports init() function and provide the context pointer. The port in turn could access the context afterwards. In contrary to that I think a port's only interface to the context should be ThingSet messages! More concrete message buffers that contain ThingSet messages. For example one might be tempted to provide direct context access to a complex MQTT gateway port. By this you restrict the MQTT gateway functionality to just this context. Whereas if the gateway works on ThingSet messages, the messages can originate from and go to also other ThingSet contexts. This is a much more versatile interface (and it fits much better to my mesh concept !-) Also the split of responsibility is quite clear - ports do not have to mangle with ThingSet context internals. A CAN port would just be forwarding messages between the context and the physical CAN interface. I has to do all the low level CAN stuff but does not have to access the context for ThingSet objects.
struct ts_context works on memory buffers whereas "struct tsc_context" is designed to work on communication buffers. Also "struct tsc_context" is ROMable. The direct extension of struct ts_context would also require to shift core functions from memory buffers to communication buffers. This seemed a too big step for me. |
c0aa3fa
to
c457b6e
Compare
3ed5cca
to
8f651fd
Compare
cca155c
to
2cb8d1b
Compare
c05e2c3
to
347dd58
Compare
Prepare for ThingSet Communication Context: - Add/ adapt JSMN JSON parser to even consume less memory. JSON parse information may become meta data that is associated to a communication message for all the message lifetime. Roughly half memory footprint, compared to current solution, for JSON tokens. Reduced storage need for tokens comes with restrictions: - The total length of the JSON data string is limited to a maximum of 2047 characters. - start: The start position of a token in the JSON data string is restricted to 0..2046 - end: The end position of a token must be withing 2047 bytes following the start position. - size: The number of tokens of a super token (eg. elements in an array) is restricted to 0..127. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
…ntation Prepare for ThingSet Communication Context: - Add ThingSet interface for remote two way queues. - Add Thingset remote two way queue implementation. RBBQ is a Single Producer Single Consumer, lockless, no_std, thread safe, two-way queue. The remote two-way queue connects the local end of the queue with a remote end. It abstracts away the communication between the queue ends. The two-way queue is made of two lock free ring buffers with contiguous reservations. The ring buffers are implementations of the [lock-free ring-buffer with contiguous reservations] (https://blog.systems.ethz.ch/blog/2019/the-design-and-implementation-of-a-lock-free-ring-buffer-with-contiguous-reservations.html) described by Andrea Lattuada and James Munns. RBBQ is designed (primarily) to be a First-In, First-Out queue for use with DMA on embedded systems. While Circular/ Ring Buffers allow you to send data between two threads (or from an interrupt to main code), you must push the data one piece at a time. With RBBQ, you instead are granted a block of contiguous memory, which can be filled (or emptied) by a DMA engine. RBBQ works on communication ports. Data may not only be exchanged between threads sharing the ring buffer memory but also between devices that are connected by some kind of communication. This mainly targets communication where you can exchange fixed size (one ring buffer size) data packets to synchronize the ring buffers of the remote two-way queues (e.g. by SPI). Here you gain the benefit of DMA transfers between the devices and DMA transfers to fill or empty the ring buffers. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add ThingSet interface for messages. - Add Thingset message implementation based on communication buffers (ts_buf.h) The ThingSet message implementation encapsulates all message specific functionality: - message allocation from the communication buffer pool (ts_buf.h) - message encoding/ decoding for text and binary messages including functions to add and pull value primitives (int, string, array, map, ...). CBOR encoding and decoding uses the TinyCBOR lib. JSON decoding uses ThingSet´s own JSMN decoder with reduced memory consumption. JSON encoding uses a simple JSON encoder that is part of the message implementation. - message status - message import and export - message log - message ThingSet protocol support to add and pull ThingSet request/ response/ statement messages in text and binary format The message communication buffers are organized in a special way to support above functionality without additional memory requirements: - scratchroom - Room always occupied by the scratchpad - headroom - data already processed - data - the payload - tailroom - space to fill additional payload - scratchroom - additional room occupied by the scratchpad for specific tasks The scratchpad varies depending on the actual use for e.g. decoding, encoding ... Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add ThingSet interface for communication ports. - Add macro to define a port THINGSET_PORT A port is an interface utilized by ThingSet for ThingSet communication. It abstracts the physical/logical interface. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add generic context that can be implemented by different specific contexts. - Add available context as specific core context. The core context feature set targets simple devices that does not need full communication support and basically only provides ThingSet object data. - Add specific communication context. The communication context feature set targets complex devices and applications that needs to use or control other remote devices. The context implementation allows to have several contexts on the same device. Each context may either be a core context or a communication context. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add application interface to communication context - Add application port implementation that connects the application to the communication context. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add a C++ interface to the core context functionality. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add simple loopback port that utilizes the new communication context port interface. A very basic loopback implementation that lacks a lot of features, e.g. thread safety. Use with most care. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add shell application that uses the new communication context app interface. - add build instructions for Native implementation - add build instructions for Zephyr implementation Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add test rigging and complex test assertions. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add tests for OSAL and enviroment - assert support - communication buffers - time support - low memory footprint JSMN JSON parser - add tests for communication and supporting contexts - generic context - core context - communication context - message handling - object data handling Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - Add tests that are specific to the Native implementation. - Add tests that are specific to the Zephyr implementation. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add test executable with CMake build that fits to native environment Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add examples executable with build that fits to Zephyr environment Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Prepare for ThingSet Communication Context: - add examples executable with CMake build that fits to native environment Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Activate ThingSet Communication Context: - Replace thingset.h with collection of new thingset headers - Adapt CMake build system for communication context and related changes/ additions. - Adapt Kconfig for communication context and related additions. - Add all configuration options to ts_config.h and and rename options to better the configuration purpose. - Remove files that are replaced or where the functions were propagated to other files. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Adapt testing to communication context changes and include additional tests for communication context. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
f3f3287
to
5b5e3f7
Compare
Good question - depends on what part of the communication support framework you are looking at:
Good Idea. Just send an email what channel you prefer. |
@b0661 That's really impressive! Thanks for the summary. I've sent you an email to schedule a call. If there is anyone else reading this who would like to join as well, let me know. |
Hey, I was lurking around and found this message, I'd love to join, mostly for listening and trying to understand what's going on. |
We've had the call already last week. But great to hear you are also interested in ThingSet. How are you using it or planning to use it? |
Too bad ! Next time :) |
@light411, this branch introduces an operating system abstraction layer (OSAL) for TS. You may have a look at this to adapt to e.g. FreeRTOS or TI-RTOS. FreeRTOS (the ESP32 variant) is on my todo list. OSAL already suppors native Linux (native/thingset/..) and Zephyr (zephyr/thingset/..). |
Request for Comment: Add ThingSet communication support framework.
Changes
Todo:
Comments/ changes welcome.
Signed-off-by: Bobby Noelte b0661n0e17e@gmail.com