Skip to content

๐Ÿ› ๏ธ 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.json file,
  • 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!