The responses I've received thus far have helped me formulate this TLDR:

Currently the Observer Pattern is implemented to get information from the sensor-actuator modules to the individual Bluetooth services and LwM2M objects. The actuators should also be controllable from Bluetooth/LwM2M. This can be solved by just having all individual Bluetooth services / LwM2M objects have direct connections to the sensor-actuator modules.

This leads to very many links between modules, whereas many of these services/objects require the same data. This approach seems unscalable and ugly when services/objects require the same data.

So the problem (which perhaps is just a design flaw in the services/objects design):
When trying to reduce the amount of links between the modules and services/objects by moving the data acquisition up a layer, and dispatching that data to the dependent services/objects, the actuator-part becomes harder, because the services/modules lost their direct link to those sensor-actuator modules.

Original (long) problem description:

I have a system with two interfaces to the outside world: LwM2M and Bluetooth.

The system is Zephyr RTOS based, and both Bluetooth and LwM2M should send notifications matching with what they receive from the data-providing threads that obtain data from the (hardware) modules they control. Also, the Bluetooth and LwM2M interfaces can trigger actuators on the system. (All communication is bi-directional)

In particular: Bluetooth has to read a battery level from the battery management thread. The system also has another sensor-actuator service, from which the latest readings should be obtained and the actuator should be controllable from Bluetooth. No problems here, the services could just include the header file containing the API for the required modules. The services do not overlap in which threads they access.

The problem comes with LwM2M. Some of the objects require information and should be able to trigger actions from the same threads. I could let all objects directly interface from/to the required threads, but perhaps it is more elegant to let a single LwM2M module talk with the other threads, and act as a bridge between the objects and the threads. However, this would require much of the internals of the objects to be exposed, and extra communication because of the middle-man module.

I have made a simplified schematic overview of my situation to help illustrate my struggle (Sensor-actuator 2 goes to two (actually more) different LwM2M objects):

Simplified illustration of the problem

What would be the most elegant way to solve this? I would like to have a similar solution for both Bluetooth and LwM2M (perhaps this is my mistake).

I have thought and partially implemented the following ideas, but each time I end up discouraged that it's the best approach:

  • The different Bluetooth services and LwM2M objects all have direct access to the required threads. Programming seems relatively easy, but this introduces many direct dependencies / accesses to overlapping data from the target modules/threads.
  • Have the main Bluetooth module and the main LwM2M module control all the data flow between the services /objects and the target modules/threads. This would reduce the data-read dependencies between the services/objects and the target modules/threads, but it increases complexity quite severely when data is received from Bluetooth/LwM2M and something has to through the additional main Bluetooth/LwM2M module.
  • Have data-reads go through the Bluetooth and main LwM2M modules, such that those two modules listen for important data-updates from the modules/threads, which then update the services/objects. But as opposed to the first option, also add direct access in the services/objects so that they can directly trigger actions from the target modules/threads. This also feels odd. Programming is likely quite easy in this way, but there are more direct dependencies from/to the modules/threads.

I'm sure I'm overlooking a better and more elegant solution, I would very much appreciate your insight!

  • Can you tell which RTOS specifically? FreeRTOS? Dec 9 '20 at 9:43
  • Also, do the comm threads need to send a notification or do they just need to reply with the data? Dec 9 '20 at 10:09
  • Hi Jan, thanks you for your response. The RTOS is Zephyr RTOS. Both Bluetooth and LwM2M should support notifications upon receiving data updates. I will also update the original thread accordingly.
    – Kodiak
    Dec 9 '20 at 10:30

Let's take a look at the publish/subscribe and observer patterns. One or both of these may be helpful unless you are resource constrained and counting cycles.

(from https://en.wikipedia.org/wiki/Observer_pattern)

The observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

From the updated problem statement we see that this is already in place. Great, let's move on.

(from https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)

In software architecture, publish–subscribe is a messaging pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers, but instead categorize published messages into classes without knowledge of which subscribers, if any, there may be. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system.

So, in your context sensors and/or actuators could post messages to an internal 'event queue' and interested components can receive and react accordingly. It's a nice way to decouple arbitrary sets of senders and receivers. You may or may not find value in categorizing the events/messages.

Updates from the OP bemoan the loss of direct connections, which is one of the main points of pub/sub. Direct, one-way connections work well enough with a very small number (n) of participating nodes. However, they grow exponentially (n * n -1) and so do not scale. With pub/sub, this growth is linear (n * 2) and scalable as a result.

In the OP's original question there are only 4 nodes. Direct connection is a viable approach (12 one-way paths). The costs of a more elegant solution (like pub/sub) could be deferred until the next sensor or actuator is added. It may be helpful to ask WHY you are looking for something more elegant and WHEN is the right time to make such an investment.

  • 1
    Can you edit details of the patterns into your answer? The link can be a helpful reference/citation, but the answer should be able to stand on its own (because the link can break or be changed). Dec 10 '20 at 3:01
  • 1
    Hi Jason, thank you for your response. Currently the Observer pattern is implemented, but that's where the struggle and reason for my post starts. The sensor part of the threads is perfectly served by the Observer pattern. However, the actuator part are not. I would also like the Bluetooth services / LwM2M objects to be able to activate the actuators depending on what is received in those Bluetooth services / LwM2M objects. This could be handled in each service / objects separately, but some objects use the same information from the same sensor-actuators. I would like to neatly group this.
    – Kodiak
    Dec 10 '20 at 8:18
  • Edited to stand-alone (more), as suggested by @1201ProgramAlarm. Dec 10 '20 at 14:01
  • The first thing I thought of was pub/sub. The second thing I thought of was implementing it as MQTT
    – Peter M
    Dec 10 '20 at 14:48
  • @PeterM Interesting (was unaware of this standard)! With the sensors and actuators all on the same physical host MQTT seems like overkill. That said, I could be missing something. Dec 10 '20 at 23:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.