Elixir library that initializes the e-ink display on Kobo e-readers running Nerves, so that fbink_nif can drive the screen.
Kobo devices (tested on the Clara Colour, MT8113 SoC) ship with proprietary userspace daemons that must be running before the kernel's HWTCON framebuffer is fully operational. Because these binaries live on the stock Kobo root partition and cannot be redistributed, this library extracts them at first boot (via BlobCopy) and starts the required services under OTP supervision.
-
Extracts proprietary binaries from the stock Kobo root partition (
/dev/mmcblk0p10) on first boot:Binary Role mdpdMediaTek Display Processing Daemon nvram_daemonNVRAM calibration/config service libnvram.so,libnvram_custom.soNVRAM access libraries A marker file prevents re-extraction on subsequent boots.
-
Starts display services in order:
- Mounts the init_bin partition (
/dev/mmcblk0p8->/data/init_bin) which contains the waveform LUT and CFA LUT files needed by the hwtcon kernel driver - Creates REGAL waveform device nodes in
/dev - Starts
mdpd(supervised viaMuonTrap.Daemon) - Starts
nvram_daemon(supervised viaMuonTrap.Daemon)
- Mounts the init_bin partition (
-
Broadcasts events so your application knows exactly when
/dev/fb0is ready for FBInk.
Add kobo_eink to your dependencies in mix.exs:
def deps do
[
{:kobo_eink, github: "Spin42/ex_kobo_eink"}
]
endkobo_eink pulls in blob_copy and
muontrap automatically.
The application starts automatically. Subscribe to get notified when the display is ready:
defmodule MyApp.Display do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
@impl true
def init(nil) do
KoboEink.subscribe()
{:ok, :waiting}
end
@impl true
def handle_info({:kobo_eink, :ready}, _state) do
{:ok, fd} = FBInk.open()
FBInk.init(fd, %FBInk.Config{})
FBInk.print(fd, "Hello from Nerves!", %FBInk.Config{})
{:noreply, {:ready, fd}}
end
def handle_info({:kobo_eink, {:error, reason}}, _state) do
require Logger
Logger.error("E-ink init failed: #{inspect(reason)}")
{:noreply, {:error, reason}}
end
def handle_info({:kobo_eink, phase}, state) do
require Logger
Logger.info("E-ink init phase: #{phase}")
{:noreply, state}
end
endSubscribers receive {:kobo_eink, event} messages:
| Event | Meaning |
|---|---|
:copying_firmware |
Extracting binaries from stock partition |
:firmware_copied |
Extraction complete |
:starting_services |
Starting mdpd, nvram_daemon |
:services_started |
All services running |
:ready |
/dev/fb0 is ready for fbink_nif |
{:error, reason} |
Initialization failed |
If you subscribe after initialization has already completed (or failed), you immediately receive the current terminal state.
KoboEink.status()
# :ready | :initializing | :copying_firmware | :starting_services | {:error, reason}KoboEink.stop_services()All values have sensible defaults for the Kobo Clara Colour. Override in your
config.exs if your device differs:
config :kobo_eink,
# Partition containing waveform/CFA LUT files for the hwtcon driver
init_bin_partition: "/dev/mmcblk0p8",
init_bin_mount: "/data/init_bin",
# BlobCopy configuration — first-boot extraction of proprietary binaries
blob_copy: [
partition: "/dev/mmcblk0p10",
mount_point: "/tmp/kobo-eink-mount",
marker_file: "/var/lib/kobo-eink-copied",
log_prefix: "KoboEink",
partition_wait_timeout: 30_000,
mount_retries: 10,
mount_retry_delay: 1_000,
manifest: [
%{source: "usr/bin/mdpd", dest: "/usr/bin/mdpd", permissions: 0o755, critical: true},
%{source: "sbin/nvram_daemon", dest: "/sbin/nvram_daemon", permissions: 0o755},
%{source: "lib/libnvram.so", dest: "/lib/libnvram.so", permissions: 0o755},
%{source: "lib/libnvram_custom.so", dest: "/lib/libnvram_custom.so", permissions: 0o755}
]
]See the BlobCopy.Config module for a full description of each option.
This library is the Elixir/Nerves equivalent of the S20kobo-eink init script
from buildroot-kobo. The Kobo Clara
Colour uses a MediaTek MT8113 SoC with a kernel-level HWTCON driver that
provides /dev/fb0. However, the framebuffer isn't usable until several
proprietary userspace daemons are running.
Because the device uses Secure Boot, the proprietary daemons can't be freely
redistributed. Instead, they're extracted at first boot from the stock Kobo root
filesystem that still lives on the device's eMMC. This extraction is handled by
the BlobCopy library.
Daemons are started as MuonTrap.Daemon processes under
KoboEink.DaemonSupervisor (a DynamicSupervisor), giving proper OTP
supervision, automatic restart on crash, and clean shutdown without manual PID
tracking.
The initialization sequence:
Kernel (HWTCON + PMIC drivers built-in)
|
+-- /dev/fb0 exists (waveform loaded lazily on first update)
|
KoboEink.Init GenServer starts
|
+-- [first boot] BlobCopy: mount /dev/mmcblk0p10, extract binaries
|
+-- Mount /dev/mmcblk0p8 -> /data/init_bin (waveform + CFA LUTs)
+-- REGAL waveform device node setup
+-- mdpd -f (MuonTrap.Daemon)
+-- nvram_daemon (MuonTrap.Daemon)
|
+-- :ready -- /dev/fb0 is now usable by FBInk/fbink_nif
MIT License. See LICENSE for details.