BrewPi / firmware

Brewing temperature control firmware for the BrewPi Spark (Particle Photon inside)
http://www.brewpi.com
GNU Affero General Public License v3.0
97 stars 55 forks source link

Remove nested IDs and nested containers #92

Closed elcojacobs closed 6 years ago

elcojacobs commented 6 years ago

Controlbox was initially written with nested objects in mind, to which one could write data in a nested ID. Smart objects would be implemented as containers themselves, which each value having an ID at the nesting level.

This overlaps with protobuf as our chosen communication protocol for most objects, which has tagged fields and nested messages. Keeping the nested IDs complicates walking the object tree and processing data on the service side and makes the firmware more complex to maintain.

If we switch to a global object list, which can be a simple std::vector, adding objects, finding objects, removing objects is all much simpler and can use safe std implementations. We don't need custom container walkers and other custom object management implementations. If we do want to write to a nested ID inside an object, the stream parser of that object can handle that. It doesn't require supporting nesting through the entire object chain.

I propose a simpler global root container, which keeps an std::vector of object entries:

struct ObjectEntry {
   uint16_t object id,
   uint16_t object type,
   uint8_t active_in_profiles,
   unique_ptr<Object> obj // ptr to runtime object
};

A unique pointer is used to not allow other objects to reference an object directly. The should get the runtime pointer from the global list from the id each time they access it. This ensures objects die if removed from the list, no dangling objects. Failed lookups will get a safe backup object if not found (for example a sensor always returning an error temp). The object id does not need to be stored in the objects anymore, neither the type or active profiles.

An object lookup just iterates over the vector. This is probably plenty fast enough. If not, a map could be used. Inactive objects (no runtime instance) that are defined in EEPROM but not loaded can be added to the list. Their unique ptr will be nullptr.

This allows the firmware to quickly list which objects exist but are not loaded. The global list can be created during startup.

Objects will not store their EEPROM location. They can request a one-time use writer/reader from the EEPROM manager based on their ID. This allows the EEPROM manager to reshuffle/defragment the EEPROM if needed. The EEPROM writer finds the object in EEPROM and rewrites it, in the same or a new location.