Skip to content

Releases

1.0.0

Choose a tag to compare

@markusressel markusressel released this 07 Nov 18:53
37b8b61

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_kb to keep uploads short.
  • max_size_kb gives 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 calls stop_slideshow(), see examples/example_digital_picture_frame.py.

New

  • Upload progress: progress= on GifModule.upload_gif_file, the image uploads, MaterialsModule.store (per file) and ConnectionManager.send_packets, called as chunks are acknowledged.
  • Terminal UI (pip install idotmatrix-api-client[tui], then idotmatrix-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

Choose a tag to compare

@markusressel markusressel released this 23 Oct 19:20
589aace

Behavior change

  • ImageModule.show_image_file and show_image_pixeldata send 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=False sends raw RGB like the official app. Tested on a 64x64 only.

New

  • colors for 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_error sets the limit (default 5), shared_palette=True uses one palette for all frames.
  • max_size_kb for 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, and MaterialsModule.store takes a function to label each file.
  • MaterialsModule.store remembers 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_unused and verify control the details.

Fixes

  • Slots are no longer deleted before they are filled, so the display does not go dark in between.
  • ResizeMode.FIT no longer crashes on tiny canvases with a wide or tall picture, FILL is no longer a pixel short, and background_color is 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

Choose a tag to compare

@markusressel markusressel released this 23 Oct 14:34
c78972a

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_file and ImageModule.show_image_file take slot and display_time and 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

Choose a tag to compare

@markusressel markusressel released this 23 Oct 11:24
39c0add

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 seconds errors 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

Choose a tag to compare

@markusressel markusressel released this 23 Oct 01:14
3dd9385

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_device is 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_device now only remembers a newer BLEDevice for 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.

Everybody who uses set_device or IDotMatrixClient.set_ble_device (Home Assistant integrations) should update.

0.2.0

Choose a tag to compare

@markusressel markusressel released this 23 Oct 00:51
14002e1

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_frame moved to idotmatrix.apps.digital_picture_frame. The old path is gone.
  • ConnectionManager.client (the raw BleakClient) is gone. The connection goes through a Transport now, see below.
  • IDotMatrixClient(screen_size=...) is optional now, which is not breaking, but the screen_size property raises a RuntimeError until 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()).
  • MusicSync works with the microphone of the display (set_mic_type).

Internals

  • idotmatrix/transport.py: a Transport protocol and BleakTransport. ConnectionManager(transport_factory=...) and IDotMatrixClient(transport_factory=...) allow another way of connecting, and tests/fake_transport.py simulates a display for tests.
  • idotmatrix/protocol.py: a table of the known commands and build_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.md collects 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

  • BleakTransport and IDotMatrixClient(ble_device=...) take a BLEDevice instead of an address, BleakTransport(connector=...) connects with bleak-retry-connector, and install_signal_handlers=False leaves 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: #fff shorthand, length checks, named colors without matplotlib.
  • Dropped the asyncio and matplotlib dependencies.

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.