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:
An
__init__.pyfileA
device.pyfileIn
device.pyaDeviceclass has to be defined, which should be based oninkBoard.platforms.BaseDevice
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_onorturn_on_asyncshould 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 thenotify_conditionfunction 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 devicereboot, 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.