Developing a Platform

This section will go over the basics of developing a platform, and also try to set out the device model as well as possible. It is somewhere in between a tutorial for creating your own custom device, and actual documentation, so I would advise keeping an implemented device model at hand for some decent comparisons.

Setting Up

It is possible to use custom devices with inkBoard. Say you have created a custom device called my_device. In your configuration folder, create a new folder called my_device. To use that device, you can set the value of platform to ./my_device. The / tells inkBoard it should look for a custom device folder. To get a custom device running, the folder should contains at least the following:

When implementing features, the device should make use of the inkBoard.platforms.InkboardDeviceFeatures named tuple to define them. The internal names for the features are contained within inkBoard.platforms.FEATURES, which can be passed to the DeviceFeatures as parameters.

When done, a platform.json file can be included, such that inkBoard can determine the packages required, as well as optional packages for optional features. This allows other users to quickly install the new platform.

class platformjson

Base dict that should be gathered from a platform.json file

Used to indicate the platforms version, requirements and the like.

description: str

Optional description of the platform

inkboard_requirements: inkboardrequirements

inkBoard specific requirements

optional_requirements: dict[str, list[str]]

Optional requirements that can be installed for i.e. implementing more features

requirements: list[str]

List with requirements. Follows conventions for pip install

version: str

Version string. Use x.x.x for versioning

The BaseDevice

The BaseDevice is used as an abstract baseclass for setting up devices. It ensures the basic functions required for an inkBoard device are present, and allows uniform validation for features. implementing certain features like a backlight requires the respective baseclass. Other features, like rotation, may require a specific function to be defined. Any function or property marked with abstract needs to be defined in the device class.

A good starting point when developing a device is to take a look at already implemented platforms. The desktop platform is relatively simple in its basics, and shows both how to develop a base device for PythonScreenStackManager and how to extend that device for inkBoard.

class BaseDevice

Base device class for inkBoard

__init__(features: DeviceFeatures, screenWidth: int, screenHeight: int, viewWidth: int, viewHeight: int, screenMode: str, imgMode: str, defaultColor: str | int | list | tuple[L, A] | tuple[R, G, B] | tuple[R, G, B, A], model: str | None = None, name: str | None = None, **kwargs)
async async_pol_features()

Pols the necessary features of the device to see if their state has changed.

The screen calls this automatically at preset intervals.

do_screen_clear()

Completely clears the screen

do_screen_refresh(isInverted=False, isFlashing=True, isInvertionPermanent=True, area=[(0, 0), ('W', 'H')], useFastInvertion=False)

DEPRECATED Refreshes the screen. On ereaders, this can help get rid of ghosting.

abstract async event_bindings(touch_queue: Queue | None = None)

async function that starts the print loop on the device as well the touch listerner, if able to.

Parameters:
  • touch_queue (asyncio.Queue, optional) – asyncio queue where touch events are put into. PSSM waits for items in this queue, by default None (for non interactive devices)

  • grabInput (bool, optional) – Prevent any other software from listening to touched, by default False. Not implemented for every device.

has_feature(feature: str | Literal['FEATURE_POWER', 'FEATURE_AUTOSTART', 'FEATURE_INTERACTIVE', 'FEATURE_BACKLIGHT', 'FEATURE_NETWORK', 'FEATURE_BATTERY', 'FEATURE_RESIZE', 'FEATURE_ROTATION', 'FEATURE_PRESS_RELEASE', 'FEATURE_CONNECTION'])

Returns true if the device has this feature (i.e. is it true in device.Features)

Parameters:

feature (str) – The feature to check. Use the constants from FEATURES to check, although some safeguarding is in place to convert passed values to valid feature strings.

power_off(*args)

Powers off the device. Does nothing if the device does not support the power feature.

abstract print_pil(imgData: Image, x: int, y: int, isInverted=False)

Prints a pillow image onto the screen at the provided coordinates.

Ensure the mode of the pillow image matches that of the screen.

Parameters:
  • imgData (Image.Image) – the image object to be printed

  • x (int) – x coordinates on the screen where the top left corner of the image will be placed.

  • y (int) – y coordinates on the screen where the top left corner of the image will be placed.

  • isInverted (bool, optional) – use hardware invertion on the printed area, by default False (E-reader leftover)

reboot(*args)

Reboots the device. Does nothing if the device does not support the power feature.

toggle_autostart(*args)

Toggles autostart. Logs a warning if not implemented

property Screen: PSSMScreen

The Screen instance attached to the device

property backlight: Backlight

The instance of the backlight of the device.

property battery: Battery

The Battery level and status of the device.

property colorType: str

Same as screenMode. Implemented for legacy purposes

property defaultColor: ColorType

Default background color of the screen (i.e. pixels that are turned off.).

property deviceName: str

The actual name of the device, as will be shown in the device menu.

property eventQueue: Queue

The queue where touch events are put into. Set by PSSM and defined in event_bindings.

property heightOffset: int

Viewable height of the screen (taking into account possible bezels e.g.)

property imgMode: <module 'PIL.ImageMode' from '/home/docs/checkouts/readthedocs.org/user_builds/inkboard-documentation/envs/latest/lib/python3.10/site-packages/PIL/ImageMode.py'>

Mode to initialise PILLOW images in for adequate building. Generally screenmode + A

property isRGB: bool

True if this device display in RGB

property last_printed_PIL: Image

Image that was last printed

property model: str | None

The model of the device

property name: str

The name of the device as set by the user.

property network: Network

The instance of the network of the device.

property parentPSSMScreen: PSSMScreen

The pssm screen objected associated with the device

property path_to_pssm_device: int

Path to the device file

property rotation: Literal['UR', 'CW', 'UD', 'CCW']

The rotation of the screen

property screenHeight: int

Height of the screen

property screenMode: str

Mode of the screen i.e. the mode a PILLOW image must be to be able to be displayed

property screenType: Literal['LCD', 'LED', 'OLED', 'E-Ink'] | None

If defined, the screen type of the device. List is not exhaustive.

property screenWidth: int

Width of the screen

property updateCondition: Condition

Asyncio condition that is notified when the device states updates have been called (so every config.device[“update_interval”]), or when the backlight changed.

For usage see: https://superfastpython.com/asyncio-condition-variable/#Wait_to_be_Notified

property viewHeight: int

Viewable height of the screen (taking into account possible bezels e.g.)

property viewWidth: int

Viewable width of the screen (taking into account possible bezels e.g.)

property widthOffset: int

Viewable height of the screen (taking into account possible bezels e.g.)

Implementing Features

Implementing features differs a bit depending on the feature. Some features require a few functions, other require making use of a base class.

Interactive

To implement interactivity, mark the device with the interactive feature. When doing so, the screen will pass a touch_queue to the device’s event_bindings() function. The device can put interaction events in this queue, and the screen will take care of dispatching it to the dashboard.

class TouchEvent

NamedTuple used to pass touches to the screen.

used by devices, this class can be put into touch_queue

touch_type: Literal['TOUCH-PRESSED', 'TOUCH-RELEASED', 'TOUCH-TAP', 'TOUCH-LONG']

The type of touch

x: int

The x-coordinate of the touch

y: int

The y-coordinate of the touch

The touch events put into touch_type are dependent on the features of the device. If it only has the interactive feature, the device may only support dispatching taps, or may support dispatching long touches. Anyhow, it will mean inkBoard does not dispatch any hold_release_action. The constants for these touch types are:

TOUCH_TAP = 'TOUCH-TAP'

Indicates a short touch event, for devices that do not support reporting both press and release events

TOUCH_LONG = 'TOUCH-LONG'

Indicates a long touch, for devices that do not support reporting both press and release events. Dispatches to element’s hold_action

If the device supports the press_release feature, the following constants should be used as touch types. The screen takes care of categorising them as being a short press or a long one.

TOUCH_PRESS = 'TOUCH-PRESSED'

Inidicates the touch event is an object pressing on the screen

TOUCH_RELEASE = 'TOUCH-RELEASED'

Indicates the touch event is the object leaving the screen

Network

This feature indicates that the device has an internet connection, and the baseclass is used to retrieve info on its connection. It has two flavors, one for devices that just retrieve network info, and one for devices that can also control the connection state.

To implement the connection feature a device should make use of the network baseclass. It can be imported from inkBoard.platforms.basedevice as BaseNetwork.

class Network

Base class to get information on a device’s network.

Gets IP Adress, network SSID etc. Properties: IP, wifiOn, connected, SSID

abstract __init__(device: PSSMdevice)
abstract async async_update_network_properties()

Method that updates the networks properties

property IP: str

Returns the IP adress

property SSID: str

Returns the SSID of the connected network

property connected: bool

Returns whether the device is connected to a wifi network

property macAddr: str

Returns the mac adress of the device

property signal: int | None

Wifi signal percentage, from 0-100, or None if unavailable.

property state: Literal['connected', 'disconnected', 'off']

State of the wifi radio. Shorthand to combine connected and wifiOn

property wifiOn: bool

Returns whether wifi is on

The BaseConnectionNetwork is used when a device has the connection feature. This feature extends the connection feature and provides functionality for a device to manage its network.

class BaseConnectionNetwork

Abstract base class to manage a device’s network connection.

abstract __init__(device: PSSMdevice)
abstract async async_connect(ssid: str | None = None, password: str | None = None)

Connects to a wifi network

Parameters:
  • ssid (str, optional) – Network to connect to, by default None

  • password (str, optional) – Password to use for connecting, by default None

abstract async async_disconnect()

Base async method to disconnect from the network.

abstract async async_update_network_properties()

Method that updates the networks properties

connect(ssid: str | None = None, password: str | None = None)

Connects to a wifi network

Parameters:
  • ssid (str, optional) – Network to connect to, by default None

  • password (str, optional) – Password to use for connecting, by default None

disconnect()

Disconnects to a wifi network

property IP: str

Returns the IP adress

property SSID: str

Returns the SSID of the connected network

property connected: bool

Returns whether the device is connected to a wifi network

property macAddr: str

Returns the mac adress of the device

property signal: int | None

Wifi signal percentage, from 0-100, or None if unavailable.

property state: Literal['connected', 'disconnected', 'off']

State of the wifi radio. Shorthand to combine connected and wifiOn

property wifiOn: bool

Returns whether wifi is on

Battery

If a device implements the battery feature, the base battery class should be used. It can be imported from inkBoard.platforms.basedevice as BaseBattery, and should be able to retrieve the battery’s state (charging, discharging and full), and its charge level.

class Battery

Base class for interfacing with a battery.

The battery of the device. Provides callbacks to get the battery state and charge level, as well as update it.

__init__(device: PSSMdevice, charge: int, state: Literal['full', 'charging', 'discharging'])
Parameters:
  • charge (int) – the initial charge level

  • state (Literal["full","charging","discharging"]) – the initial battery state

abstract async async_update_battery_state() tuple[int, str]

Update the battery state. Returns the result.

Returns:

Tuple with [charge percentage, state]

Return type:

tuple[int,str]

update_battery_state()

Updates the battery state and percentage. Check device if it returns anything.

property charge: int

The battery charge, in percentage (from 0 - 100)

property state: Literal['full', 'charging', 'discharging']

The state of the battery

Backlight

The backlight feature is for devices that can control the backlight of the screen. The name may be somewhat confusing, as this feature should also be used to, for example, control the screen’s brightness. inkBoard started on an E-Reader, which have often supply a backlight for the screen that can light it, and the name stuck (at least for now).

class Backlight

Baseclass to control a device’s backlight, screen brightness and the like.

The backlight of the device. Provides callbacks to the state, and functions to turn on, off, or toggle it. Upon initialising this class, the light will be set to 0 to ensure the level is correct. It should also allow setting the default transition values and the default brightness, although the screen instance will be in charge of managing those.

Depending on how the device handles, either turn_on or turn_on_async should be defined, and the other can simple call the defined function. But be careful with blocking the event loop. To keep connected elements in synch with the backlight’s state, whenever it is updated the notify_condition function can be awaited, which will ensure all elements are notified of the new state.

__init__(device: PSSMdevice, defaultBrightness: int = 50, defaultTransition: float = 0)
async notify_condition()

Acquires the lock and notifies all awaiting on _updateCondition

abstract toggle(brightness: int | None = None, transition: float | None = None)

Toggles the backlight, if it is off turns on to defined brightness

Parameters:
  • brightness (int, optional) – brightness (0-100) to set the light to if it is off, by default None

  • transition (float, optional) – transition time (in seconds) to take to get to brightness or turn off. (Ereaders are slow, so be aware that it will likely take longer), by default None

abstract toggle_async(brightness: int | None = None, transition: float | None = None)

Async method for toggling the backlight, if it is off turns on to defined brightness

Parameters:
  • brightness (int, optional) – brightness (0-100) to set the light to if it is off, by default None

  • transition (float, optional) – transition time (in seconds) to take to get to brightness or turn off. (Ereaders are slow, so be aware that it will likely take longer), by default None

abstract turn_off(transition: float | None = None)

Turns off the backlight to the set level

Parameters:

transition (float, optional) – transition time (in seconds) to take fully turn off (Ereaders are slow, so be aware that it will likely take longer), by default None

abstract async turn_off_async(transition: float | None = None)

Async method for turning off the backlight

Parameters:

transition (float, optional) – transition time (in seconds) to take fully turn off (Ereaders are slow, so be aware that it will likely take longer), by default None

abstract turn_on(brightness: int | None = None, transition: float | None = None)

Turn on the backlight to the set level

Parameters:
  • brightness (int, optional) – brightness (0-100) to set the light to, by default None

  • transition (float, optional) – transition time (in seconds) to take to get to brightness (Ereaders are slow, so be aware that it will likely take longer), by default None

abstract async turn_on_async(brightness: int | None = None, transition: float | None = None)

Async method for turning on the backlight to the set level

Parameters:
  • brightness (int, optional) – brightness (0-100) to set the light to, by default None

  • transition (float, optional) – transition time (in seconds) to take to get to brightness (Ereaders are slow, so be aware that it will likely take longer), by default None

property behaviour: Literal['Manual', 'On Interact', 'Always']

Backlight behaviour. Since it affects screen interaction, you can set this via the parent screen (set_backlight_behaviour).

property brightness: int

The brightness of the backlight (0 - 100)

property defaultBrightness: int

The default brightness to turn the backlight on to

property defaultTransition: float

The default transition time (in seconds)

property default_time_on: float | int | str

Default time to turn the backlight on for when calling the temporary backlight function. Controlled by parent screen

property minimumLevel: int

The minimum backlight/brightness level to turn the backlight on at.

I.e., if this value is 30, turning on the backlight at 1 will be the same brightness as having this value at 0 and turning it on at 30 brightness. Not yet implemented.

property state: bool

The state (on/off) of the backlight as a boolean (True/False)

Power

The power feature requires the device to implement two functions:

  • power_off, to power off the device

  • reboot, to reboot the device

When validating the feature, inkBoard checks if these functions are redefined, or are still the same as the baseclass.

Resize & Rotation

The features for resizing and rotation work via the same principle. Mainly since rotating the screen will likely mean the size of the screen has shifted as well.

A device is itself responsible to notice resizing, and to implement it. To make the screen handle the changed screen size, first the device must update the screenSize properties accordingly. Then, the screen’s resize function, self.Screen._screen_resized() has to be called. This function is an async function, so it must be awaited. Calling it allows the screen to take care of resizing all the elements. However the device function must subsequently call self.Screen.print_stack(), in order to print the dashboard again in the new size.

For rotation, the device must implement a _rotate() function. The screen calls this function once the user requests a rotation. Rotations are passed as a string value, as a rotation value. From there, the _rotate() function should follow the same procedure as resizing.