API Reference

Direct (non-cloud) interface to Powersensor devices.

This package contains various abstractions for interacting with Powersensor devices on the local network.

The library provides two types of abstractions: per-plug event streams, and a shared-across-all-devices event stream. Neither is “best”, it depends on the particular use case. Device discovery can be done in two ways. The preferred way is via mDNS/Zeroconf. There is also a legacy method relying on a simpler, custom, discovery protocol, but there should be no reason to use it for new projects.

The simplest approach is to use the shared event stream, and do discovery via Zeroconf. This is available out-of-the-box in the PowersensorZeroconfDevices class, which encapsulates the Zeroconf machinations and merging of plug event streams.

A legacy abstraction is also provided via PowersensorLegacyDevices, which uses an older way of discovering plugs. It is more fragile than Zeroconf, but may be slightly more responsive in certain situations, as the user is fully in control over when to scan for devices. In general it is not recommended for new use.

If wanting individual event streams from each plug, the PlugApi class is the one to use. Device discovery is left up to the user in this case.

The recommended approach is to use mDNS to discover plugs via their service “_powersensor._udp.local” (or “_powersensor._tcp.local” for TCP transport), and then instantiate a PlugApi to obtain the event stream from each plug. Note that the plugs are only capable of handling a single TCP connection at a time, so UDP is the preferred transport. Up to 5 concurrent subscriptions are supported over UDP. Internally, the PlugApi uses either PlugListenerUdp or PlugListenerTcp for connecting to the plugs. The interfaces are identical; switching between UDP and TCP should be invisible to the user of the PlugApi.

Additionally, a convenience abstraction for translating some of the events into a household view is available in VirtualHousehold.

Quick overview:

The ‘plugevents’ and ‘rawplug’ modules are helper utilities provided as debug aids, which get installed under the names ps-plugevents and ps-rawplug respectively. There is also the legacy ‘events’ debug aid which get installed nder the names ps-events, and offers up the events from PowersensorDevices. Similarly, ‘zc_events’ offers up events from PowersensorZeroconfDevices, installed as ‘ps-zcevents’, again, for debug use only.

Type hints for emitted data are available in Event/Events/Message.

type AsyncEventCallback = Callable[[Event], Coroutine[None, None, None]]
class CommonEventsDocs[source]

Bases: object

The following is a list of common events that may be emitted.

The first few are discovery events, while the rest are data events.

device_found

A device has been discovered or re-discovered.

Note that due to device hardware limitations, role information is NOT reliably available at this time, and therefore not included in this message. There are situations where role information will never become available, and therefore the API can make no promises otherwise. If a user requires role information, they must manage that themselves and also provide a mechanism for handling the situation of a device not being able to provide role information in the first place. If a device does supply a role at any point, it should be considered authoritative and override any user provided value. The event data comprises:

  • “event”: “device_found”

  • “mac”: The MAC address of the device.

  • “device_type”: One of “plug” or “sensor”.

device_lost

A device has definitively disappeared (plug), or been inactive long enough to expire (plug or sensor). An expired device that becomes active again is announced with a new device_found. The event data comprises:

  • “event”: “device_lost”

  • “mac”: The MAC address of the device.

average_power

An event denoting the average power seen over a short duration, such as 1 or 30 seconds typically. Both plugs and powersensors may originate these. The event data comprises:

  • “event”: “average_power”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug, if from a sensor.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “duration_s”: The number of seconds the reading is calculated over.

  • “watts”: The average power, in Watts. May be negative for e.g. solar sensors and house sensors when exporting solar to the grid.

average_power_components

An plug-only event providing additional information on the power components feeding into the power measurement. To be read in conjunction with the “average_power” message with the same starttime_utc value. Comprises:

  • “event”: “average_power_components”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “apparent_current”: The apparent current, in Amperes.

  • “active_current”: The active current component, in Amperes.

  • “reactive_current”: The reactive current component, in Amperes.

  • “volts”: The mains voltage as seen by the plug, in Volts.

summation_energy

An event reporting an energy summation value. Issued for both plugs and sensors. Comprises:

  • “event”: “summation_energy”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug, if from a sensor.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “summation_joules”: The summation value, in Joules (Watt seconds). This value may go backwards (and even become negative) if solar export is present. The summation may increment or decrement depending on whether energy is being imported from or exported to the grid.

  • “summation_resettime_utc”: A timestamp denoting the last time the summation value reset. Summation values from before the reset time can not be safely compared or diffed with ones from after the reset time. The format is the same as for “starttime_utc” — seconds since the Unix Epoch, in UTC.

average_flow

An event denoting the average flow rate seen over a short duration, such as 1 or 30 seconds typically. Only originated by water sensors. The event data comprises:

  • “event”: “average_flow”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “duration_s”: The number of seconds the reading is calculated over.

  • “litres_per_minute”: The average flow detected, in litres/min..

summation_volume

An event reporting a volume summation value. Issued only for water sensors. Comprises:

  • “event”: “summation_volume”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “summation_litres”: The summation value, in litres.

  • “summation_resettime_utc”: A timestamp denoting the last time the summation value reset. Summation values from before the reset time can not be safely compared or diffed with ones from after the reset time. The format is the same as for “starttime_utc” — seconds since the Unix Epoch, in UTC.

uncalibrated_average_reading

An event denoting an reading with unknown unit, for an average over a short duration, such as 1 or 30 seconds typically. Issued by powersensors, prior to calibration. The event data comprises:

  • “event”: “uncalibrated_average_reading”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “duration_s”: The number of seconds the reading is calculated over.

  • “value”: The value, in unspecified units. The only valid thing to use this value for is display it relative to other uncalibrated values from the same device. The magnitude of the value is an indication of the strength of the signal seen by the sensor, but the unit is most definitely NOT in Watts. For most purposes, this event can (and should be) ignored. Once the backend has successfully calibrated the sensor against one or more plugs, these events will be replaced by “average_power” events instead.

battery_level

An event reporting the battery level of a sensor. The event data comprises:

  • “event”: “battery_level”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “volts”: The current battery level, in Volts. Sensors operate on 3.7V nominally, with a fully charged battery at around 4.2V. Precise battery curves vary individually. It is intentional that we do not attempt to map these to a percentage value here, as between individual differences and environmental conditions they are bound to be inaccurate. Short of characterising each battery in its environment, any such mapping will be inaccurate. It can be argued that users wishing a simple percentage display are best off using a simple linear extrapolation across the middle part of the curve, e.g. 3.3V and 4.15V. It’s not entirely accurate, but it’s also not useless.

radio_signal_quality

An event reporting radio signal quality for a sensor. Note that this is for the long-range radio comms with the plug, not for the WiFi signal. The event comprises:

  • “event”: “radio_signal_quality”

  • “mac”: The MAC address of the device.

  • “role”: The assigned role of the device, if known.

  • “via”: The id of the relaying plug.

  • “starttime_utc”: Seconds since the Unix Epoch, in UTC.

  • “duration_s”: The number of seconds the reading is calculated over.

  • “average_rssi”: The average of the RSSI in messages received during the reporting window. Values below -90 are considered poor reception.

  • “last_rssi”: The most recent RSSI value.

type Event = dict[str, str | int | float | bool]
type Events = dict[str, Event]
type Message = dict[str, str | int | float | bool]
class PlugApi(mac, ip, port=49476, proto='udp', logger=None)[source]

Bases: AsyncEventEmitter

The primary interface to access the interpreted event stream from a plug.

The plug may be relaying messages from one or more sensors, in addition to its own reports.

Acts as an AsyncEventEmitter. Events which can be registered for are documented in xlatemsg.translate_raw_message.

connect()[source]

Initiates a connection to the plug.

Will automatically retry on failure or if the connection is lost, until such a time disconnect() is called.

Return type:

None

async disconnect()[source]

Disconnects from the plug and stops further connection attempts.

Return type:

None

async emit(event_name, *args)

Emits an event to all registered listeners for that event type. Additional arguments may be supplied with event as appropriate. Each event handler is awaited before delivering the event to the next. If an event handler raises an exception it is logged (if a logger was provided), and discarded.

Return type:

None

property ip_address: str

Return the IP address provided on construction.

Returns:

The IP address configured for the listener.

Return type:

str

property port: int

Return the port number provided on construction.

Returns:

The TCP/UDP port configured for the listener.

Return type:

int

subscribe(event_name, callback)

Registers an event handler for the given event key. The handler must be async. Duplicate registrations are ignored.

Return type:

None

unsubscribe(event_name, callback)

Unregisters the given event handler from the given event type.

Return type:

None

class PlugListenerTcp(ip, port=49476, logger=None)[source]

Bases: AsyncEventEmitter

Internal helper for accessing the event stream from a Powersensor plug.

The following events may be emitted:
  • (“connecting”) Whenever a connection attempt is made.

  • (“connected”) When a connection is successful.

  • (“disconnected”) When a connection is dropped, be it intentional or not.

  • (“message”,{…}) For each event message received from the plug. The plug’s JSON message is decoded into a dict which is passed as the second argument to the registered event handler(s).

  • (“malformed”,line) If JSON decoding of a message fails. The raw line is included (as a byte string).

The event handlers must be async.

connect()[source]

Initiates the connection to the plug. The object will automatically retry as necessary if/when it can’t connect to the plug, until such a time disconnect() is called.

Return type:

None

async disconnect()[source]

Goes through the disconnection process towards a plug. No further automatic reconnects will take place, until connect() is called.

Return type:

None

async emit(event_name, *args)

Emits an event to all registered listeners for that event type. Additional arguments may be supplied with event as appropriate. Each event handler is awaited before delivering the event to the next. If an event handler raises an exception it is logged (if a logger was provided), and discarded.

Return type:

None

property ip: str

Return the IP address this listener is bound to.

property port: int

Return the TCP port this listener is bound to.

subscribe(event_name, callback)

Registers an event handler for the given event key. The handler must be async. Duplicate registrations are ignored.

Return type:

None

unsubscribe(event_name, callback)

Unregisters the given event handler from the given event type.

Return type:

None

class PlugListenerUdp(ip, port=49476, logger=None)[source]

Bases: AsyncEventEmitter, DatagramProtocol

Internal helper for accessing the event stream from a Powersensor plug.

The following events may be emitted:
  • (“connecting”) Whenever a connection attempt is made.

  • (“connected”) When a connection is successful.

  • (“disconnected”) When a connection is dropped, be it intentional or not.

  • (“message”,{…}) For each event message received from the plug. The plug’s JSON message is decoded into a dict which is passed as the second argument to the registered event handler(s).

  • (“malformed”,line) If JSON decoding of a message fails. The raw line is included (as a byte string).

The event handlers must be async.

connect()[source]

Initiates the connection to the plug. The object will automatically retry as necessary if/when it can’t connect to the plug, until such a time disconnect() is called.

Return type:

None

connection_lost(exc)[source]

Called when the connection is lost or closed.

The argument is an exception object or None (the latter meaning a regular EOF is received or the connection was aborted or closed).

Return type:

None

connection_made(transport)[source]

Called when a connection is made.

The argument is the transport representing the pipe connection. To receive data, wait for data_received() calls. When the connection is closed, connection_lost() is called.

Return type:

None

datagram_received(data, addr)[source]

Called when some datagram is received.

Return type:

None

async disconnect()[source]

Goes through the disconnection process towards a plug. No further automatic reconnects will take place, until connect() is called.

Return type:

None

async emit(event_name, *args)

Emits an event to all registered listeners for that event type. Additional arguments may be supplied with event as appropriate. Each event handler is awaited before delivering the event to the next. If an event handler raises an exception it is logged (if a logger was provided), and discarded.

Return type:

None

error_received(exc)[source]

Called when a send or receive operation raises an OSError.

(Other than BlockingIOError or InterruptedError.)

Return type:

None

property ip: str

Return the IP address this listener is bound to.

pause_writing()

Called when the transport’s buffer goes over the high-water mark.

Pause and resume calls are paired – pause_writing() is called once when the buffer goes strictly over the high-water mark (even if subsequent writes increases the buffer size even more), and eventually resume_writing() is called once when the buffer size reaches the low-water mark.

Note that if the buffer size equals the high-water mark, pause_writing() is not called – it must go strictly over. Conversely, resume_writing() is called when the buffer size is equal or lower than the low-water mark. These end conditions are important to ensure that things go as expected when either mark is zero.

NOTE: This is the only Protocol callback that is not called through EventLoop.call_soon() – if it were, it would have no effect when it’s most needed (when the app keeps writing without yielding until pause_writing() is called).

property port: int

Return the TCP port this listener is bound to.

protocol_factory()[source]

UDP protocol factory for self.

Return type:

DatagramProtocol

resume_writing()

Called when the transport’s buffer drains below the low-water mark.

See pause_writing() for details.

subscribe(event_name, callback)

Registers an event handler for the given event key. The handler must be async. Duplicate registrations are ignored.

Return type:

None

unsubscribe(event_name, callback)

Unregisters the given event handler from the given event type.

Return type:

None

PowersensorDevices

alias of PowersensorLegacyDevices

class PowersensorLegacyDevices(bcast_addr='<broadcast>', relay_now_relaying_for=False, logger=None)[source]

Bases: _PowersensorDevicesBase

Abstraction interface for the unified event stream from all Powersensor devices on the local network, using the legacy broadcast UDP discovery.

This is the original PowersensorDevices implementation, renamed to make room for PowersensorZeroconfDevices. The name PowersensorDevices is kept as an alias for backwards compatibility.

See CommonEventsDocs for the full list of known events. Additionally emits:

scan_complete

Indicates discovery has completed. { event: "scan_complete", gateway_count: N }

async rescan()[source]

Perform a fresh scan to discover added or moved devices.

Return type:

None

async start(async_event_cb)[source]

Register the async event callback and scan the local network.

Returns the number of gateway plugs found.

Return type:

int

async stop()

Stop event streaming and disconnect from all devices.

To restart, call start() (legacy) or add_plug() (zeroconf) again.

Return type:

None

subscribe(mac)

Subscribe to events from the device with the given MAC address.

Subscriptions are automatically removed if a device disappears, ensuring no accumulating resource leakage. Use the device_found message to resubscribe if a subscription is still desired when the device returns, if it returns.

Return type:

None

unsubscribe(mac)

Unsubscribe from events from the given MAC address.

Return type:

None

class PowersensorZeroconfDevices(*args, **kwargs)[source]

Bases: _PowersensorDevicesBase

Stub raised when the optional zeroconf package is not installed.

To use mDNS-based discovery, install the zeroconf extra:

pip install powersensor-local[zeroconf]
async stop()

Stop event streaming and disconnect from all devices.

To restart, call start() (legacy) or add_plug() (zeroconf) again.

Return type:

None

subscribe(mac)

Subscribe to events from the device with the given MAC address.

Subscriptions are automatically removed if a device disappears, ensuring no accumulating resource leakage. Use the device_found message to resubscribe if a subscription is still desired when the device returns, if it returns.

Return type:

None

unsubscribe(mac)

Unsubscribe from events from the given MAC address.

Return type:

None

class VirtualHousehold(with_solar, logger=None)[source]

Bases: AsyncEventEmitter

Class for processing average_power and summation_energy events into to/from grid, solar generation, and home usage events.

To use, simply feed the appropriate PlugApi events to the process_average_power_event and process_summation_event member functions.

Point-in-time power flow events include:

  • home_usage

  • from_grid

  • to_grid (only for solar kits)

  • solar_generation (only for solar kits)

These all have an event payload in the form:

{ timestamp_utc: , watts: }

Energy summation events include:

  • home_usage_summation

  • from_grid_summation

  • to_grid_summation (only for solar kits)

  • solar_generation_summation (only for solar kits)

These all have an event payload in the form:

{ timestamp_utc: , summation_resettime_utc: , summation_joules: }

Summations may reset at any time. Track the summation_resettime_utc field to take note of summation resets.

class Counters(resettime_utc, solar_generation, to_grid, from_grid, home_use)[source]

Bases: object

from_grid: float
home_use: float
resettime_utc: int
solar_generation: float
to_grid: float
class SummationInfo(solar_resettime, solar_last, housenet_resettime, housenet_last)[source]

Bases: object

housenet_last: float
housenet_resettime: int
solar_last: float
solar_resettime: int
async emit(event_name, *args)

Emits an event to all registered listeners for that event type. Additional arguments may be supplied with event as appropriate. Each event handler is awaited before delivering the event to the next. If an event handler raises an exception it is logged (if a logger was provided), and discarded.

Return type:

None

async process_average_power_event(ev)[source]

Ingests an event of type ‘average_power’.

Return type:

None

async process_summation_event(ev)[source]

Ingests an event of type ‘summation_energy’.

Return type:

None

subscribe(event_name, callback)

Registers an event handler for the given event key. The handler must be async. Duplicate registrations are ignored.

Return type:

None

unsubscribe(event_name, callback)

Unregisters the given event handler from the given event type.

Return type:

None