Newer
Older
pyenergenie / doc / classes.txt
Initial Proposal for modelling devices as classes.

The purpose is to be able to write expressive and compact applications that talk in the land of physical devices such as
'myRadiator' and 'myPlug' or even second level names such as 'myKettle' (which is plugged into 'myPlug'). Thus hiding
the detail of how messages get encoded and transported, and focusing an application more on it's intents than on it's
implementation.

each different device has a class 'cookie cutter' (e.g. Energenie.MiHomeAdaptorPlus(),Energenie.eTRV())

each instance of a device is an object (e.g. plug, rad...)

each device instance can be named and abstracted as appropriate to it's purpose (e.g. kitchenKettle(plug),
hallwayRad(rad))

Outgoing command messages modelled by function calls and/or properties (e.g. kitchenPlug.turnOn() or even
kettle.turnOn()...)

Incoming data modelled by function call getters and/or properties, perhaps with a cache of the last received value
(e.g. hallRad.getTemperature(), hallRad.temperature)

meta-model driven proxy classes for device types not in the device dictionary (e.g. an unknown device sends it's
temperature, so you can still say unknownDevice.getTemperature() or unknownDevice.temperature)

a low dependency link from the device object instances to the transport that sends and receives messages on their
behalf (e.g. kitchenPlug = Energenie.MiHomeAdaptorPlus(OpenHEMS(myRadio))

an outwards link via OpenHEMS(radio) for queueing transmit messages. An inwards link from OpenHEMS(radio) back to
the object, so that when a message is received from a specific (mfrid, prodid, sensorid) the data is routed to the
correct object instance.

perhaps a catch-all object that receives messages from unknown or unregistered devices, with a way to later morph
those into real device instances (e.g. this could be how the discovery service is created, by all messages for
unknown sensorid's going to a catch all, and later the app constructing appropriate object abstractions around
them and them being removed from 'unknown'






Also note that I intend this design to work directly with the (to be coded) message sequencer to ensure that
messages are transmitted at 'quiet' times in the schedule to prevent collisions with reports from other devices.
I also plan to completely abstract away in the device classes used by app whether the device is a MiHome (FSK) or
Legacy (OOK) and automatically switch the radio modulation configuration and safely schedule outgoing messages.

This will allow building systems combined with both MiHome and Legacy devices, and treating them all as first class
citizens in any user app.

I won't be starting work on this until I have finished the hardening of the physical layer.




I'm thinking of a device-agnostic interface, so you can just have python variables for any connected device
(green button or purple), with a simple device registry. Then instigate methods on them. This will knit up to the
radio interface under the bonnet for you, and switch between OOK and FSK mode automatically - mostly leaving the
radio idling in FSK receive mode to increase the chances of hearing a report. Later I will add a message scheduler
that learns the reporting schedule, and defers FSK or OOK transmits to 'free timeslots' to improve the performance
of the overall system.

So something a bit like this maybe:

(when configuring the system)
print("Press the learn button on the TV device")
energenie.start_learn(house_code=0xABCDE, index=2)
raw_input("Press enter when done")
energenie.stop_learn()

energenie.create_device("tv", energenie.device.ENER002, index=2, house_code=0xABCDE)
energenie.create_device("aquarium", energenie.device.MIHO005, address=0x1234)

(when running the system)

tv = energenie.get("tv")
aquarium = energenie.get("aquarium")

tv.off()
aquarium.on()
time.sleep(10)
if aquarium.power > 20:
print("Has the pump motor stalled??")

def just_turned_off(device):
print("Your user just turned off %s" % device)
print("I'm turning it back on!")
device.on()

aquarium.when_turned_off(just_turned_off)







This is four separate pieces of work:

1) a persistent device registry
To allow device types and modulation (ENER, MIHO) and (FSK, OOK) plus addressing information, to be associated with a
friendly name 'tv', and persisted to a disk version of a registry database. This database to be queryable so that
runtime device classes can be reconstructed from their name in this database.

2) a device class for every supported device
To allow device specific intents to be mapped onto on air messages, and for incoming data to be cached locally in RAM
to allow simple on demand query semantics.

3) device class to radio interface
To allow device classes to be controlled by intents such as tv.turn_on() and for that intent to trigger some on-air
activity in the radio driver. Also, for received messages from the on-air interface to be routed to the device class
that handles that device, so that when the MiHome adaptor plus address 0x123 reports it's energy, that energy report
is cached inside the 'tv' object, allowing deferred queries like tv.is_on() and tv.get_power()

4) A dynamic message scheduler that intelligently switches the radio between transmit and receive and OOK and FSK modes.
It will learn the reporting times of each device that is in earshot, build a timing plan, and use that timing plan to
defer later transits to timeslots that are not already occupied by a regular report for a device, and thus decreasing
the probability of missing a rx due to being in tx, and therefore improving the performance of the overall system.





One of the other things about this, is that the registry record for a device instance can relate it to a device class
(in Devices.py). Classes in Devices.py could then define the radio parameters needed in order to communicate with that
type of device (e.g. a purple mihome could be (FSK, repeats=1) and a green button could be (OOK, repeats=8) etc).

Then when the app tries to use tv.on() it really consults the parameters in it's parent class, a MIHO005, which
configures the radio as FSK,repeats=1 and sends the message. When the app tries to use fan.on() it consults the parent
class, gets an ENER002 which configures the radio as OOK,repeats=8 and sends the message.

These device classes could also estimate based on the message you are sending, how long it will take to send that
message, and it can use that when (later) consulting an intelligent message scheduler to work out a timeslot that it
can use that will be long enough to not collide with a known report time from another MiHome device that is known to
be sending regular reports.

Also, the device instance (tv, fan) can then store 'last heard from time' to work out if the device is working or not,
and also 'last values' such as to service any requests such as get_power() and get_voltage() and get_frequency() - i.e.
these accessors will read the last known value (or raise an error if no value is known) rather than trying to request
it on demand - many devices do not support request on demand, so they have to be cached. They can be cached with a
receipt timestamp, so that the app can always put an expectation on how up to date the data is that it requires.