๐ ๏ธ Developer Guide¶
This document explains how to build custom modules for Inkycal, how modules interact with the core system, how to structure clean and reliable image generation code, and how to register your module with the Inkycal runtime.
If you want a minimal starting point, scroll to Module Template below.
๐ฆ What Is an Inkycal Module?¶
A module in Inkycal is a self-contained unit that:
- receives configuration from the
settings.jsonfile, - generates an image (black + colour layer),
- fits into a layout region managed by the Inkycal core,
- can specify required & optional Web-UI configuration fields.
Modules must subclass InkycalModule, which defines the interface and base behavior.
๐งฌ Module Lifecycle¶
Every module follows the same three steps:
1. Initialization¶
Receives its config. Sets width, height, padding, font, fontsize.
2. (Optional) Validation¶
Ensures its parameters are meaningful.
3. Image generation¶
Builds the actual e-paper image content via generate_image().
The image is finally combined with others by Inkycal and rendered on the display.
๐งฉ Anatomy of a Module¶
All modules inherit from:
class InkycalModule(metaclass=abc.ABCMeta):
...
Key inherited attributes¶
| Attribute | Description |
|---|---|
self.width, self.height |
Module region size in pixels |
self.padding_left, self.padding_top |
Padding around your module |
self.fontsize, self.font |
Default font settings |
self.config |
Dict of module config parameters |
Required method¶
Each module must implement:
def generate_image(self):
"""Return (image_black, image_colour)"""
This is where all drawing happens.
โ๏ธ Defining Module Parameters (Web-UI Integration)¶
Modules declare configuration fields using two dictionaries:
requires (mandatory fields)¶
These fields must be provided in the Web-UI or an error is raised.
requires = {
"api_key": {"label": "Your API key"},
"username": {"label": "Enter your username"},
}
optional (UI-exposed but not required)¶
optional = {
"hobbies": {"label": "Hobbies (comma-separated)"},
"age": {"label": "Your age", "default": 18},
"likes_inkycal": {
"label": "Do you like Inkycal?",
"options": [True, False],
},
}
options turns the field into a dropdown menu in the Web-UI.
๐ง How Web-UI Input Works¶
The browser can only send:
- strings,
- booleans,
- empty values (
None).
Convert types manually inside __init__():
if config["age"] and isinstance(config["age"], str):
self.age = int(config["age"])
else:
self.age = 18
List conversion:
self.hobbies = config["hobbies"].split(",") if config["hobbies"] else []
๐จ Rendering Images with Canvas¶
The Canvas class is the recommended way to draw on images.
Create a canvas:¶
from inkycal.utils.canvas import Canvas
canvas = Canvas(im_size=(width, height), font=self.font, font_size=self.fontsize)
Write text:¶
canvas.write(
xy=(0, 0),
box_size=(200, 40),
text="Hello World!",
alignment="center"
)
Draw coloured text:¶
canvas.write(
xy=(0,0),
box_size=(200,40),
text="Hello!",
colour="colour"
)
Return the final images:¶
return canvas.image_black, canvas.image_colour
๐งช Optional Validation Step¶
Modules may implement _validate() to check logic:
def _validate(self):
if not isinstance(self.age, int):
raise ValueError("Age must be an integer")
This runs automatically when calling set(help=False, ...) from the API.
๐ผ๏ธ Full Minimal Example Module¶
import logging
from inkycal.modules.template import InkycalModule
from inkycal.utils.canvas import Canvas
logger = logging.getLogger(__name__)
class Simple(InkycalModule):
name = "Simple - Hello World"
requires = {
"username": {"label": "Your name"}
}
optional = {
"show_smiley": {
"label": "Show a smiley?",
"options": [True, False],
"default": True,
}
}
def __init__(self, config):
super().__init__(config)
cfg = config["config"]
self.username = cfg["username"]
self.show_smiley = bool(cfg.get("show_smiley", True))
def generate_image(self):
w = self.width - 2 * self.padding_left
h = self.height - 2 * self.padding_top
canvas = Canvas(im_size=(w, h), font=self.font, font_size=self.fontsize)
text = f"Hello {self.username}"
if self.show_smiley:
text += " ๐"
canvas.write(xy=(0, 0), box_size=(w, h), text=text)
return canvas.image_black, canvas.image_colour
โ Registering Your Module¶
You must tell Inkycal where to find your new module.
1. Update inkycal/modules/__init__.py¶
from .simple import Simple
2. Update inkycal/__init__.py¶
import inkycal.modules.simple
Your module is now available inside the Web-UI.
๐งญ Debugging Techniques¶
Preview without hardware:¶
black, colour = module.generate_image()
black.show()
colour.show()
Save previews:¶
black.save("preview_black.png")
colour.save("preview_colour.png")
Logging:¶
logger.debug("Value is %s", my_value)
Logs on the Pi are stored at:
/var/log/inkycal.log
๐ Advanced Development Tips¶
1. Multi-section layouts¶
You can subdivide your module region and use multiple canvas objects.
2. Use external APIs¶
Modules can fetch weather, calendar, news, etc.
3. Icon rendering¶
The Canvas provides draw_icon() to render SVG-style icons cleanly and centered.
4. Enhanced performance¶
Cache API responses to speed up repeated renders.
๐ You're Ready to Build Modules!¶
You now know how to:
- Define required/optional fields
- Parse Web-UI configuration correctly
- Use Canvas to render clean layouts
- Debug and preview without e-paper hardware
- Register your module with Inkycal
If you want to add:
- A Module API Reference
- A Layout Design Guide
- A Testing Guide (pytest/unittest)
- A Module Generator Tool
Just tell me โ I can create those too!