Python API
Every name PyEilik exports from import eilik, grouped by what it belongs to.
import eilikModule functions
Section titled “Module functions”eilik.connect
Section titled “eilik.connect”eilik.connect(device: str | None = None) -> RobotOpens a robot, discovering the serial port when no device is given. Use it as a context manager so the port is released even if something raises — a port left open stops the next run from connecting at all.
with eilik.connect() as robot: robot.arm_left.to(1800) robot.commit()eilik.load
Section titled “eilik.load”eilik.load( source: str | Path | Sequence[bytes], *, fps: float = 30.0, dance: str | None = None, audio: bool = True, fill: bool = False, threshold: int = 128, strum_arm: int = 2,) -> ClipPrepares a video for playing. source is a path to a video, or a sequence of
ready framebuffers. dance is "simple", "guitar" or None — "guitar"
needs music with an audible pulse; on music without one it strums to a grid
that means nothing, and "simple" will look better. Decoding needs ffmpeg;
playing an already-loaded clip does not. Returns a Clip.
A connected robot. Prefer eilik.connect over constructing this directly. A
Robot holds four Joint attributes — arm_left, arm_right, body,
head — and a screen attribute, a Screen.
Robot.commit
Section titled “Robot.commit”Robot.commit() -> NoneSends every staged joint in a single packet. Separate packets per joint would move the joints visibly out of step.
Robot.rest
Section titled “Robot.rest”Robot.rest() -> NoneReturns every joint to the centre.
Robot.positions
Section titled “Robot.positions”Robot.positions() -> tuple[int, int, int, int]Reads all four joint positions from the robot.
Robot.play
Section titled “Robot.play”Robot.play(source, *, on_frame=None, **options) -> intPlays a video on the screen, optionally dancing to its soundtrack:
robot.play("clip.mp4")robot.play("clip.mp4", dance="simple")robot.play("clip.mp4", dance="guitar")source may be a path (in which case **options are forwarded to
eilik.load) or an already-prepared Clip, in which case passing **options
raises TypeError — they would do nothing, since conversion already happened
when the clip was prepared. Blocks until the clip ends or Ctrl-C, and returns
how many frames were shown. Decoding a video needs ffmpeg; a prepared clip does
not. Sound comes out of this computer: the robot has a speaker, but no command
exists to send audio to it.
Robot.close
Section titled “Robot.close”Robot.close() -> NoneReleases the serial port. Safe to call more than once.
One servo joint, reached as robot.arm_left, robot.arm_right, robot.body
or robot.head. Sides are the robot’s own: arm_left is the robot’s left
arm, which is the one you see on your right when facing it.
Joint.to
Section titled “Joint.to”Joint.to(position: int) -> NoneStages a target. Nothing moves until Robot.commit.
Joint.position
Section titled “Joint.position”Joint.position -> intThe joint’s real position, read back from the robot.
Screen
Section titled “Screen”The robot’s screen, reached as robot.screen. The panel is 128×64,
monochrome, one bit per pixel.
Screen.WIDTH
Section titled “Screen.WIDTH”Screen.WIDTH: int # 128Screen.HEIGHT
Section titled “Screen.HEIGHT”Screen.HEIGHT: int # 64Screen.BYTES
Section titled “Screen.BYTES”Screen.BYTES: int # 1024The size of a framebuffer: WIDTH * HEIGHT // 8.
Screen.show
Section titled “Screen.show”Screen.show(source, *, threshold: int = 128, fill: bool = False) -> NonePuts something on the screen. source is either a ready 1024-byte
framebuffer, sent unchanged, or anything Pillow can open — a path, a file
object, or an Image — which is scaled to 128×64, reduced to one bit per
pixel at threshold, and rotated to match the panel. fill crops to fill the
screen instead of letterboxing. Opening an image needs Pillow
(pip install 'pyeilik[image]'); the framebuffer path has no dependencies at
all.
Screen.read
Section titled “Screen.read”Screen.read() -> bytesReads back what is on the screen, as a 1024-byte framebuffer in the panel’s
own order — feeding it straight back to show reproduces the picture. Use
eilik.screen.unpack to get pixels the right way up for a human to look at.
Screen.clear
Section titled “Screen.clear”Screen.clear() -> NoneBlanks the screen.
Framebuffers
Section titled “Framebuffers”Two helpers in the eilik.screen submodule, for looking at a framebuffer as
pixels rather than as 1024 opaque bytes. They are not re-exported by import eilik, so reach them through the submodule:
from eilik.screen import pack, unpackeilik.screen.unpack
Section titled “eilik.screen.unpack”eilik.screen.unpack(framebuffer: bytes, *, rotate: bool = True) -> list[list[int]]Turns the panel’s 1024-byte page order back into a 64 × 128 grid of 0 and 1,
row by row, the way a human looks at it. The panel is mounted upside down, so
by default the grid is turned back the right way up; rotate=False returns it
in the panel’s own orientation.
eilik.screen.pack
Section titled “eilik.screen.pack”eilik.screen.pack(rows: Sequence[Sequence[int]], *, rotate: bool = True) -> bytesThe inverse: a 64 × 128 grid of 0 and 1 becomes the 1024 bytes Screen.show
sends, with the same rotate. Drawing a frame pixel by pixel and packing it
needs no Pillow at all.
Frames, and optionally the motion and sound that go with them. Returned by
eilik.load and accepted by Robot.play.
Clip.frames
Section titled “Clip.frames”Clip.frames: list[bytes]The framebuffers that make up the clip.
Clip.fps
Section titled “Clip.fps”Clip.fps: floatThe frame rate the clip plays at.
Clip.motion
Section titled “Clip.motion”Clip.motion: list[tuple[int, int, int, int]] | NoneJoint targets per motion frame, or None when the clip was loaded without
dance=.
Clip.audio
Section titled “Clip.audio”Clip.audio: Path | NoneThe path to the extracted soundtrack, or None when there is none to play.
Clip.style
Section titled “Clip.style”Clip.style: str | NoneThe dance= style the clip was prepared with, or None.
Clip.duration
Section titled “Clip.duration”Clip.duration -> floatThe clip’s length in seconds: len(clip.frames) / clip.fps.
Clip.close
Section titled “Clip.close”Clip.close() -> NoneDeletes anything temporary this clip created, such as an extracted soundtrack.
Status
Section titled “Status”class Status(IntEnum): ...Mirrors eilik_status_t in include/eilik.h, in the same order. Every member
is the status code eilik.errors.check maps to the matching exception below.
Status.OK
Section titled “Status.OK”Success.
Status.ERR_ARG
Section titled “Status.ERR_ARG”An invalid argument was passed to the underlying C call.
Status.ERR_PORT
Section titled “Status.ERR_PORT”The serial port could not be opened or configured.
Status.ERR_BUSY
Section titled “Status.ERR_BUSY”The serial port is already open in another process.
Status.ERR_IO
Section titled “Status.ERR_IO”An input/output error occurred talking to the robot.
Status.ERR_TIMEOUT
Section titled “Status.ERR_TIMEOUT”The robot did not acknowledge in time.
Status.ERR_PROTOCOL
Section titled “Status.ERR_PROTOCOL”The robot sent a reply that could not be parsed.
Status.ERR_RANGE
Section titled “Status.ERR_RANGE”A position fell outside the joint’s limits, in strict mode.
Status.ERR_SERVO_FAULT
Section titled “Status.ERR_SERVO_FAULT”The servo controller has stopped running: every joint reads 0. Turn the robot off with its power switch and on again; unplugging USB is not enough, because it has an internal battery.
Exceptions
Section titled “Exceptions”Every failure this library raises is one of these, and every one inherits
from EilikError.
EilikError
Section titled “EilikError”Base class for every failure this library reports.
PortError
Section titled “PortError”The robot could not be reached. Named PortError rather than
ConnectionError so it does not shadow the builtin of that name in callers’
code.
PortBusyError
Section titled “PortBusyError”A subclass of PortError: another process already holds the serial port.
ProtocolError
Section titled “ProtocolError”The robot sent something that could not be parsed.
TimeoutError
Section titled “TimeoutError”The robot did not acknowledge in time. Shadows the builtin TimeoutError
deliberately, since it means the same thing here.
ServoFaultError
Section titled “ServoFaultError”The servo controller has stopped running.
RangeError
Section titled “RangeError”A subclass of both EilikError and ValueError: a position fell outside the
joint’s limits in strict mode.