Repository navigation
Releases
1.0.0
The first stable release: the protocol work, uploads, slots and conversion have been in use in ha-idotmatrix and other projects for a while, and the public API is what we intend to keep.
Behavior changes
- GIFs keep all their frames and the delay of every frame. The conversion used to cut a GIF to 64 frames and 2 s per loop by dropping frames, and gave every frame the delay of the first one, so long animations played up to 8 times too fast and a long last frame (e.g. to hold a picture) was lost. The device plays long GIFs at their original speed (tried with 336 frames, an 11 s loop). Use
max_size_kbto keep uploads short. max_size_kbgives up frames before colors (every 2nd, 3rd, ... frame, the animation takes as long as before): fewer colors looked clearly worse than fewer frames.DigitalPictureFrame.start_slideshow()no longer installs SIGINT/SIGTERM handlers. They stopped the whole event loop and replaced the handlers of the client and of the application. The application handles signals and callsstop_slideshow(), seeexamples/example_digital_picture_frame.py.
New
- Upload progress:
progress=onGifModule.upload_gif_file, the image uploads,MaterialsModule.store(per file) andConnectionManager.send_packets, called as chunks are acknowledged. - Terminal UI (
pip install idotmatrix-api-client[tui], thenidotmatrix-tui): find and remember displays, browse files with a pixel-exact preview and show them, control pages (clock, effects, text, countdown, scoreboard, brightness, power, ...) and a pixel editor. image_utils.is_animated(): JPEGs with several pictures (MPO from phones) are no longer treated as animations.
Fixes and cleanup
- Races in the tests found by running them in parallel are fixed.
- Old TODO comments are resolved, the open device questions are in
TODO.md. - Documentation: README rework,
docs/DEVICE-PROTOCOL.md,docs/INTEGRATION.md,docs/DEVELOPMENT.md,docs/TUI.md,docs/FONTS.md.
0.3.0
Behavior change
ImageModule.show_image_fileandshow_image_pixeldatasend a PNG by default now (png=True). A raw RGB upload leaves a stray dark red pixel at the second LED of the first row of a 64x64 display, a PNG does not, and it is lossless for RGB and smaller.png=Falsesends raw RGB like the official app. Tested on a 64x64 only.
New
colorsfor GIFs and images (2 to 256): fewer colors make a GIF smaller, which means a faster upload.colors="auto"for GIFs: tries palettes of 256 to 16 colors, each per frame and shared, scores them against the 256 color version (color difference plus flicker,idotmatrix.util.quality) and takes the smallest file that still looks fine.max_color_errorsets the limit (default 5),shared_palette=Trueuses one palette for all frames.max_size_kbfor GIFs: reduces colors, then frames, until the GIF fits.overlay=TextOverlay(...)draws a text on every frame of a GIF or on an image (a label, the time, a title). Positions, colors, a background and the font size can be set, andMaterialsModule.storetakes a function to label each file.MaterialsModule.storeremembers what is in each slot and only transfers what is new, so storing the same list again takes a second instead of a minute.keep_order,clear_unusedandverifycontrol the details.
Fixes
- Slots are no longer deleted before they are filled, so the display does not go dark in between.
ResizeMode.FITno longer crashes on tiny canvases with a wide or tall picture,FILLis no longer a pixel short, andbackground_coloris respected for images.
The conversion is tested with a broad set of generated media (noise, flat colors, 200+ frames, tiny and huge sizes, transparency, odd durations).
0.2.3
Fixes
- Uploads of images and GIFs write every BLE packet with response, like the official app, instead of a burst without response. Behind an ESPHome Bluetooth proxy the burst lost packets: the display acknowledged a chunk seconds late or never, and rebooted. The pause between packets of 0.2.2 is only used for writes without response now.
- The display time in the upload header is written little endian, like the app (it was read as 1280 seconds instead of 5).
New
- Slots:
GifModule.upload_gif_fileandImageModule.show_image_filetakeslotanddisplay_timeand store the item on the device instead of showing it right away. The device keeps up to 12 items and cycles through them by itself. client.materials(MaterialsModule):clear(slots)deletes slots,store(files, slots, display_time, ...)stores a list of files into a list of slots (all 12 by default, other slots are never touched).
Uploads are a bit slower than in 0.2.2 (about 18% on BlueZ).
0.2.2
Fixes
- Uploads of images and GIFs write the BLE packets of a 4096 byte chunk 20 ms apart, like the official app, instead of in one burst. Sporadic
the device did not acknowledge the upload within 5 secondserrors are suspected to come from the burst overrunning the device (not proven; tested with 264 uploads on a 64x64 without failures, with and without pacing). - The 5 s acknowledgement timer starts with the first write of a chunk, like in the app.
Costs about 80 ms per upload.
0.2.1
Fixes
- A connection could end up without notifications, which left it without acknowledgements for uploads and commands and without the heartbeat. The log message was
could not subscribe to device notifications, uploads are not flow controlled: 'NoneType' object has no attribute 'start_notify'.- Cause:
ConnectionManager.set_deviceis called whenever a scanner (f.e. the one of Home Assistant) sees the display again, which also happens while a connection attempt is running. It replaced the transport then, so the connect finished on the old transport and the subscription ran on the new one, which was never connected. set_devicenow only remembers a newerBLEDevicefor the same display, and the next connection uses it. Setting the device of a different display still replaces the transport right away.connect()subscribes on the transport it connected.
- Cause:
Everybody who uses set_device or IDotMatrixClient.set_ble_device (Home Assistant integrations) should update.
0.2.0
This is the first release on PyPI (pip install idotmatrix-api-client). It collects the reliability work, the protocol research and the groundwork for the Home Assistant integration ha-idotmatrix.
Breaking changes
idotmatrix.digital_picture_framemoved toidotmatrix.apps.digital_picture_frame. The old path is gone.ConnectionManager.client(the rawBleakClient) is gone. The connection goes through aTransportnow, see below.IDotMatrixClient(screen_size=...)is optional now, which is not breaking, but thescreen_sizeproperty raises aRuntimeErroruntil the size is known (see below).
Reliable uploads
- Images and GIFs wait for the acknowledgement of every 4096 byte chunk, like the official app. Before, the device dropped or garbled data, and animations stuttered or lost frames.
- All frames of a GIF get color tables of the same size, which the device needs to play them correctly.
- Photos are shown with
ImageModule.show_image_file(the "static image" upload of the app), which needs no mode switching. The picture frame uses it and can mix photos and GIFs freely. - The BLE packet size follows the negotiated MTU, which fixes uploads on macOS and other stacks with a small MTU.
show_image_file(png=True)is an experimental opt-in that sends a PNG instead of raw pixels, which is a little faster. Tested on a 64x64 only.
Talking to the device
- Commands are acknowledged and the library waits for the acknowledgement of the commands that are known to be acknowledged. A rejected command raises
CommandRejectedError. - A heartbeat (
set_heartbeat) notices a link that died without Bluetooth noticing, f.e. an unplugged display, and can reconnect. CommonModule.get_device_info()returns the firmware version, the screen type and the password flag, which the device sends as the reply to the set time command.IDotMatrixClient(screen_size=None)asks the device for its screen size when connecting (detect_screen_size()).MusicSyncworks with the microphone of the display (set_mic_type).
Internals
idotmatrix/transport.py: aTransportprotocol andBleakTransport.ConnectionManager(transport_factory=...)andIDotMatrixClient(transport_factory=...)allow another way of connecting, andtests/fake_transport.pysimulates a display for tests.idotmatrix/protocol.py: a table of the known commands andbuild_command, which writes the length bytes.- Images, GIFs and fonts are loaded in a thread, so the event loop is not blocked.
docs/DEVICE-PROTOCOL.mdcollects everything known about the protocol, marked as verified, from the app, claimed or guessed.- 339 tests, 94 % line coverage, CI for Python 3.12 to 3.14.
For Home Assistant and other Bluetooth stacks
BleakTransportandIDotMatrixClient(ble_device=...)take aBLEDeviceinstead of an address,BleakTransport(connector=...)connects withbleak-retry-connector, andinstall_signal_handlers=Falseleaves SIGINT and SIGTERM alone. See the README.
Fixes
connect()without an address no longer deadlocks, and it no longer notifies the listeners when it is already connected.- The folder watching of the picture frame works, and removing a picture no longer skips the next one.
- Color parsing:
#fffshorthand, length checks, named colors without matplotlib. - Dropped the
asyncioandmatplotlibdependencies.
Thanks to everybody who contributed to the fork: compact fonts and font selection, MTU aware fragmentation, multi-pixel graffiti, light effects, reset and chunked GIF uploads.