This PR prepares the firmware for implementation of the Event Synchronization application-level protocol layer by refactoring the firmware's implementation of the State Synchronization protocol layer (but without changing the State Synchronization protocol specification), specifically:
By replacing the Protocols::Application::StateSynchronizer class with a more general Protocols::Application::SequentialStateSender class with more general functionality and without timing-related functionality.
While the StateSynchronizer took a Store and a schedule consisting of MessageType & entry_duration pairs, our application just uses a constant entry duration of 10 ms for all schedule entries, which made StateSynchronizer more complicated than we need. Timing-related functionality has now been moved to the Backend protocol implementation (which has also been refactored, see below).
The SequentialStateSender takes a schedule consisting of an array of enum values and an object implementing the IndexedStateSender interface. An IndexedStateSender object can be given an enum value to produce a StateSegment to send; then the SequentialStateSender just rotates through the schedule to get enum values and uses them to return StateSegment outputs from the IndexedStateSender object. The Application::Store class implements the IndexedStateSender interface, so a simple state sender can be constructed with an array of MessageType enum values and a reference to the Application::Store object - this would be a replacement of the previous StateSynchronizer functionality, except that timing functionality has been removed.
By adding a MappedStateSenders class which implements the IndexedStateSender interface and takes an EnumMap of pointers to StateSender objects. When a MappedStateSenders object is given an enum value to produce a StateSegment object, it looks up the corresponding StateSender object from the enum value and uses that StateSender object to produce a StateSegment. Then it outputs that resulting StateSegment.
Because SequentialStateSender implements the StateSender interface and takes an IndexedStateSender object, and MappedStateSender implements the IndexedStateSender interface and takes an EnumMap of StateSender objects, it's now possible to nest sequential schedules within several layers. Thus, a root-level schedule for a root-level SequentialStateSender object can be a sequence of enum values referring to child SequentialStateSender objects (where the mapping of enum values to objects is held by a MappedStateSenders object), so that each child sender object only produces a StateSegment when the root-level sender object is scheduled to request a StateSegment from the child sender object.
Making schedules nestable is required for implementation of the Event Synchronization protocol, but it also makes our schedules for State Synchronization more maintainable. For example, to send a SensorMeasurements every 30 ms and the other state segments every 90 ms, the previous implementation required us to specify the schedule this way:
Schedule:
Message Type
duration
SensorMeasurements
10 ms
CycleMeasurements
10 ms
Parameters
10 ms
SensorMeasurements
10 ms
AlarmLimits
10 ms
NextLogEvents
10 ms
SensorMeasurements
10 ms
ActiveLogEvents
10 ms
AlarmMute
10 ms
While the new implementation allows us to specify the same schedule this way:
Root schedule interval: 10 ms
Root schedule: fast schedule, slow child schedule, slow child schedule
Fast child schedule: SensorMeasurements (from the store)
Slow child schedule: CycleMeasurements, Parameters, AlarmLimits, NextLogEvents, ActiveLogEvents, AlarmMute (all from the store)
The Event Synchronization implementation will use SequentialStateSenders for a significant part of its implementation: there will be an EventSender class which implements the StateSender interface, and it will have its own internal SequentialStateSenders (though the SequentialStateSender will be further modified to allow skipping over schedule entries from which no output is available).
This PR also performs some other refactoring and interface changes:
Changes the max_key template parameter on EnumMap to a capacity parameter with the same semantics as max_size: previously EnumMap was templated on max_key which is equivalent to capacity - 1, now EnumMap is templated on capacity which is equivalent to max_key + 1. This makes size-related template parameters have the same meaning across containers.
Changing the insert/find methods of EnumMap and OrderedMap to have more consistent semantics with each other and with Application::Store: now all three classes have input(Key key, const Value &value) and output(Key key, Value &value) for those respective operations.
Moving the protobuf message descriptors object and the Sender and Receiver classes from the Driver/Serial/Backend/Backend.h file into a new Driver/Serial/Backend/Transport.h file
Moving the state synchronization schedule and the list of message types which the backend is allowed to receive from the Driver/Serial/Backend/Backend.h file into a new Driver/Serial/Backend/States.h file
Decomposing application-level communication protocol layers (state synchronization and list synchronization) out from the Backend class to a new Synchronizers class. Then the Synchronizers class (which takes and produces StateSegments) is roughly equivalent to the Python backend server's ventserver.protocols.backend.backend module, while the Backend class (which takes and produces bytes) is roughly equivalent to the Python Backend server's ventserver.protocols.backend.server module.
Because all unit tests written for StateSynchronizer were about testing its timing-related functionality, and timing-related functionality is no longer provided in its replacement class SequentialStateSender, I have fully removed the unit tests which were written for StateSynchronizer. We should hold off on writing unit tests for Protocols/Application/States.h, as the interfaces of the classes may undergo further changes for implementation of the Event Synchronization protocol layer.
@rohanpurohit For this PR, let's merge after you test this to look for any regressions - the firmware should still behave the same as before this PR.
For records-keeping:
This project is licensed under Apache License v2.0 for any software, and Solderpad Hardware License v2.1 for any hardware - do you agree that your contributions to this project will be under these licenses, too? Yes
Were any of these contributions also part of work you did for an employer or a client? No
Does this work include, or is it based on, any third-party work which you did not create? No
This PR prepares the firmware for implementation of the Event Synchronization application-level protocol layer by refactoring the firmware's implementation of the State Synchronization protocol layer (but without changing the State Synchronization protocol specification), specifically:
Protocols::Application::StateSynchronizer
class with a more generalProtocols::Application::SequentialStateSender
class with more general functionality and without timing-related functionality.StateSynchronizer
took aStore
and a schedule consisting ofMessageType
&entry_duration
pairs, our application just uses a constant entry duration of 10 ms for all schedule entries, which madeStateSynchronizer
more complicated than we need. Timing-related functionality has now been moved to the Backend protocol implementation (which has also been refactored, see below).SequentialStateSender
takes a schedule consisting of an array of enum values and an object implementing theIndexedStateSender
interface. AnIndexedStateSender
object can be given an enum value to produce aStateSegment
to send; then theSequentialStateSender
just rotates through the schedule to get enum values and uses them to returnStateSegment
outputs from theIndexedStateSender
object. TheApplication::Store
class implements theIndexedStateSender
interface, so a simple state sender can be constructed with an array ofMessageType
enum values and a reference to theApplication::Store
object - this would be a replacement of the previousStateSynchronizer
functionality, except that timing functionality has been removed.MappedStateSenders
class which implements theIndexedStateSender
interface and takes anEnumMap
of pointers toStateSender
objects. When aMappedStateSenders
object is given an enum value to produce aStateSegment
object, it looks up the correspondingStateSender
object from the enum value and uses thatStateSender
object to produce aStateSegment
. Then it outputs that resultingStateSegment
.SequentialStateSender
implements theStateSender
interface and takes anIndexedStateSender
object, andMappedStateSender
implements theIndexedStateSender
interface and takes anEnumMap
ofStateSender
objects, it's now possible to nest sequential schedules within several layers. Thus, a root-level schedule for a root-levelSequentialStateSender
object can be a sequence of enum values referring to childSequentialStateSender
objects (where the mapping of enum values to objects is held by aMappedStateSenders
object), so that each child sender object only produces aStateSegment
when the root-level sender object is scheduled to request aStateSegment
from the child sender object.Making schedules nestable is required for implementation of the Event Synchronization protocol, but it also makes our schedules for State Synchronization more maintainable. For example, to send a
SensorMeasurements
every 30 ms and the other state segments every 90 ms, the previous implementation required us to specify the schedule this way:SensorMeasurements
CycleMeasurements
Parameters
SensorMeasurements
AlarmLimits
NextLogEvents
SensorMeasurements
ActiveLogEvents
AlarmMute
While the new implementation allows us to specify the same schedule this way:
SensorMeasurements
(from the store)CycleMeasurements
,Parameters
,AlarmLimits
,NextLogEvents
,ActiveLogEvents
,AlarmMute
(all from the store)The Event Synchronization implementation will use
SequentialStateSender
s for a significant part of its implementation: there will be anEventSender
class which implements theStateSender
interface, and it will have its own internalSequentialStateSender
s (though theSequentialStateSender
will be further modified to allow skipping over schedule entries from which no output is available).This PR also performs some other refactoring and interface changes:
max_key
template parameter onEnumMap
to acapacity
parameter with the same semantics asmax_size
: previouslyEnumMap
was templated onmax_key
which is equivalent tocapacity - 1
, nowEnumMap
is templated oncapacity
which is equivalent tomax_key + 1
. This makessize
-related template parameters have the same meaning across containers.insert
/find
methods ofEnumMap
andOrderedMap
to have more consistent semantics with each other and withApplication::Store
: now all three classes haveinput(Key key, const Value &value)
andoutput(Key key, Value &value)
for those respective operations.Sender
andReceiver
classes from theDriver/Serial/Backend/Backend.h
file into a newDriver/Serial/Backend/Transport.h
fileDriver/Serial/Backend/Backend.h
file into a newDriver/Serial/Backend/States.h
fileBackend
class to a newSynchronizers
class. Then theSynchronizers
class (which takes and producesStateSegment
s) is roughly equivalent to the Python backend server'sventserver.protocols.backend.backend
module, while theBackend
class (which takes and produces bytes) is roughly equivalent to the Python Backend server'sventserver.protocols.backend.server
module.Because all unit tests written for
StateSynchronizer
were about testing its timing-related functionality, and timing-related functionality is no longer provided in its replacement classSequentialStateSender
, I have fully removed the unit tests which were written forStateSynchronizer
. We should hold off on writing unit tests forProtocols/Application/States.h
, as the interfaces of the classes may undergo further changes for implementation of the Event Synchronization protocol layer.