Help getting idotmatrix working in HA

idotmatrix is an Android/iOS app using to configure LED displays (e.g. 16x16, 32x32) with images and GIFs as well as a number of other things. Work has been done to create a Python shell client as well as a library to directly interact with the device via Bluetooth (see GitHub - derkalle4/python3-idotmatrix-client: reverse engineered python3 client to control all your 16x16 or 32x32 pixel displays (experimental)). I’ve been able to install the client successfully in Home Assistant and also execute the commands via terminal. Based on this, I created shell_commands to trigger the client but have hit a roadblock where the action fails with the following error:

stdout: ""
stderr: |-
  Traceback (most recent call last):
    File "/config/./app.py", line 7, in <module>
      from core.cmd import CMD
    File "/config/core/cmd.py", line 9, in <module>
      from idotmatrix import ConnectionManager
  ModuleNotFoundError: No module named 'idotmatrix'
returncode: 1

My configuration.yaml snippet:

shell_command:

  set_pixel_display_time: 'python3 ./app.py --address 72:A8:19:ED:04:DE --sync-time'
  set_pixel_display_clock_with_date: 'python3 ./app.py --address 72:A8:19:ED:04:DE --clock 0 --clock-with-date'
  set_pixel_display_image: 'python3 ./app.py --address 72:A8:19:ED:04:DE --image true --set-image {{ image }} --process-image 16'
  set_pixel_display_gif: 'python3 ./app.py --address 72:A8:19:ED:04:DE --set-gif {{ image }} --process-gif 16'
  set_pixel_display_text: 'python3 ./app.py --address 72:A8:19:ED:04:DE --set-text "{{ text }}" --text-size {{ size }} --text-mode {{ mode }} --text-speed {{ speed }} --text-color-mode {{ colormode }}'
  set_pixel_display_pixels: 'python3 ./app.py --address 72:A8:19:ED:04:DE --pixel-color {{ xyrgb }}'
  set_pixel_display_colour: 'python3 ./app.py --address 72:A8:19:ED:04:DE --fullscreen-color {{ rgb }}'
  set_pixel_display_off: 'python3 ./app.py --address 72:A8:19:ED:04:DE --screen off'
  set_pixel_display_on: 'python3 ./app.py --address 72:A8:19:ED:04:DE --screen on'
  

Any help or pointers on how to fix this appreciated!

You have to install necessary runtime libraries (such asi idotmatrix) into home assistant environment to do so.

Instead of doing so, i suggest you to use pyscript or appdaemon.

It is not possible to use Python imports with this integration. 
If you want to do more advanced scripts, you can take a look at AppDaemon or pyscript

Thanks for the suggestion. I’ve installed Pyscript but I’m getting ‘Module not found’ errors for idotmatrix. I’ve installed idotmatrix via Terminal. Any ideas how I get it to point to the right location?

Okay. So I appear to have this working in HA with my display.
What I did was install pyscript and create a requirements.txt file in the pyscript folder that simply has"

idotmatrix

Then I have a test.py file (still in testing), that looks like this:

import asyncio
import time
from idotmatrix import ConnectionManager
from idotmatrix import Chronograph
from idotmatrix import Clock
from idotmatrix import Gif
from idotmatrix import Image
from idotmatrix import Scoreboard
from idotmatrix import Text

@service
async def clock(address=None, style=5, r=255, g=255, b=255):
    # connect F7:37:34:43:02:93
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    # Chronograph().setMode(mode = 1)
    Clock().setMode(style=style, visibleDate=False, hour24=True, r=r, g=g, b=b)
@service
async def chrono(address=None, mode=[0,1,2,3]):
    # connect F7:37:34:43:02:93
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Chronograph().setMode(mode = mode)

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        quit()

This then allows my to fire pyscript services (clock and chrono) to a given MAC address with the various parameters.
e.g. this one shows a simple 24hr clock with white text:

action: pyscript.clock
data: 
  address: "insert your device address here"
  style: 6
1 Like

I have countdown, full screen colour and scoreboard working too. For some reason I can’t get text working. But I’m muddling through without much knowledge, so I’m sure it’s something simple I’m missing.

I believe we have solved the initial problem of missing dependency issue.

you mentioned that text functionality is not working, but you should share the respective function over here so we can comment on it

You are correct. I should have. I was just excited to get something working :slight_smile:
This is the snippet that I’ve tried that’s not sending anything:

import asyncio
import time
from idotmatrix import ConnectionManager
from idotmatrix import Chronograph
from idotmatrix import Clock
from idotmatrix import Common
from idotmatrix import Countdown
from idotmatrix import Effect

# from idotmatrix import Eco
from idotmatrix import FullscreenColor
from idotmatrix import Gif
from idotmatrix import Graffiti
from idotmatrix import Image

# from idotmatrix import MusicSync
from idotmatrix import Scoreboard

# from idotmatrix import System
from idotmatrix import Text

[Other working services]

@service
async def text():
    conn = ConnectionManager()
    await conn.connectByAddress("[device MAC]")
    Text().setMode("HELLO WORLD")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        quit()

I’m not sure if there are other arguments that are required to be sent as part of the Text module?
The test.py example only appears to send a text string and a font_path, but my reading of the text.py is that that bit is optional.

you might need to install the default font over here, but finding the right path would be hard now.

Hmm… I don’t think that’s it. I managed to get the demo gif to load by downloading it at putting it in /config/pyscript/images/ and referencing that, but the same filepath doesn’t work with the text module.

I’m wondering if it’s this part of the text.py which is causing issues because I’m using a 16x16 device, not 32x32?

    logging = logging.getLogger(__name__)
    # must be 16x32 or 8x16
    image_width = 16
    image_height = 32
    # must be x05 for 16x32 or x02 for 8x16
    separator = b"\x05\xff\xff\xff"

But I don’t know how to change that to 8x16 and x02 in the function…
I assume it’s something like

Text(image_width=8,  image_height=16, separator=b"\x02\xff\xff\xff").setMode("HELLO WORLD")

But that does nothing too…

Thanks mate you’re a star! At least this basic setup worked for me.
I couldn’t get text working when using the client version of idotmatrix (allows you to run via command line). I followed the examples and it errored out for me. What happens is:

  1. First attempt times out
  2. Second attempt caused the device to restart
  3. Device is no longer found. Needs a power cycle OR it seems to come back after waiting a little bit

I also messed around a little bit and found that sending one character doesn’t crash/restart the device but just makes the screen blank. Even changing the values in the text.py didn’t help for me. Presume you had a look at GitHub - 8none1/idotmatrix: iDotMatrix pixel matrix ?

➜  homeassistant git:(main) ✗ ./run_in_venv.sh --address 72:A8:19:ED:04:DE --set-text "Hello"
22.01.2025 11:54:33 :: INFO :: idotmatrix :: initialize app
22.01.2025 11:54:33 :: INFO :: idotmatrix.core.cmd :: initializing command line
22.01.2025 11:54:33 :: DEBUG :: idotmatrix.core.cmd :: using --address
Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/bleak/backends/bluezdbus/client.py", line 214, in connect
    reply = await self._bus.call(
            ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/dbus_fast/aio/message_bus.py", line 392, in call
    await future
asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/homeassistant/./app.py", line 48, in <module>
    main()
  File "/homeassistant/./app.py", line 40, in main
    asyncio.run(cmd.run(args))
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 686, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/homeassistant/core/cmd.py", line 252, in run
    await self.conn.connectByAddress(address)
  File "/usr/lib/python3.12/site-packages/idotmatrix/connectionManager.py", line 47, in connectByAddress
    await self.connect()
  File "/usr/lib/python3.12/site-packages/idotmatrix/connectionManager.py", line 63, in connect
    await self.client.connect()
  File "/usr/lib/python3.12/site-packages/bleak/__init__.py", line 615, in connect
    return await self._backend.connect(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/bleak/backends/bluezdbus/client.py", line 151, in connect
    async with async_timeout(timeout):
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/timeouts.py", line 115, in __aexit__
    raise TimeoutError from exc_val
TimeoutError
➜  homeassistant git:(main) ✗ ./run_in_venv.sh --address 72:A8:19:ED:04:DE --set-text "Hello"
22.01.2025 11:54:56 :: INFO :: idotmatrix :: initialize app
22.01.2025 11:54:56 :: INFO :: idotmatrix.core.cmd :: initializing command line
22.01.2025 11:54:56 :: DEBUG :: idotmatrix.core.cmd :: using --address
22.01.2025 11:54:59 :: INFO :: idotmatrix.connectionManager :: connected to 72:A8:19:ED:04:DE
22.01.2025 11:54:59 :: INFO :: idotmatrix.core.cmd :: setting text
22.01.2025 11:54:59 :: DEBUG :: idotmatrix.connectionManager :: sending message(s) to device
➜  homeassistant git:(main) ✗ ./run_in_venv.sh --address 72:A8:19:ED:04:DE --set-text "Hello"
22.01.2025 11:55:13 :: INFO :: idotmatrix :: initialize app
22.01.2025 11:55:13 :: INFO :: idotmatrix.core.cmd :: initializing command line
22.01.2025 11:55:13 :: DEBUG :: idotmatrix.core.cmd :: using --address
Traceback (most recent call last):
  File "/homeassistant/./app.py", line 48, in <module>
    main()
  File "/homeassistant/./app.py", line 40, in main
    asyncio.run(cmd.run(args))
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 686, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/homeassistant/core/cmd.py", line 252, in run
    await self.conn.connectByAddress(address)
  File "/usr/lib/python3.12/site-packages/idotmatrix/connectionManager.py", line 47, in connectByAddress
    await self.connect()
  File "/usr/lib/python3.12/site-packages/idotmatrix/connectionManager.py", line 63, in connect
    await self.client.connect()
  File "/usr/lib/python3.12/site-packages/bleak/__init__.py", line 615, in connect
    return await self._backend.connect(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/bleak/backends/bluezdbus/client.py", line 145, in connect
    raise BleakDeviceNotFoundError(
bleak.exc.BleakDeviceNotFoundError: Device with address 72:A8:19:ED:04:DE was not found.

Yeah, it’s mostly working. But I’m having issues with text as well as image. For some reason when I run my image function it just flashes on the screen and then goes back to whatever was previously there.
I get this in my raw logs

2025-01-22 12:30:43.182 ERROR (MainThread) [idotmatrix.modules.image] could not upload processed image: Caught blocking call to sleep with args (0.01,) inside the event loop by custom integration 'pyscript' at custom_components/pyscript/eval.py, line 1973: return await func(*args, **kwargs). (offender: /usr/local/lib/python3.13/site-packages/idotmatrix/connectionManager.py, line 81: time.sleep(0.01))

And for some reason the condensed logs say

* could not upload processed image: [Errno 2] No such file or directory: '/config/pyscript/images/Demo_16.png'

even though it found the image and flashed it. And I’ve used the same file path syntax for the gif that works fine…

@service
async def image(address="F7:37:34:43:02:93"):
    # connect F7:37:34:43:02:93
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Image().uploadProcessed(file_path="/config/pyscript/images/demo_16.png", pixel_size=16)

Ok here’s my py file with more commands. I’ve made the clock set the time at the same time as displaying the clock. This way it always has an accurate time displayed. Strangely this doesn’t seem to work all the time for some reason. If I call the script again then it works. Maybe slow script processing or something else?
Thanks @jonaseymour for the starting tips.
For some reason though I can’t get the following working:

  • Brightness
  • Gif
import asyncio
import time
from datetime import datetime
from idotmatrix import ConnectionManager
from idotmatrix import Chronograph
from idotmatrix import Clock
from idotmatrix import Common
from idotmatrix import Countdown
from idotmatrix import Effect

# from idotmatrix import Eco
from idotmatrix import FullscreenColor
from idotmatrix import Gif
from idotmatrix import Graffiti
from idotmatrix import Image

# from idotmatrix import MusicSync
from idotmatrix import Scoreboard

# from idotmatrix import System
from idotmatrix import Text

# My devices
# First - 72:A8:19:ED:04:DE


@service
async def clock(address=None, style=5, r=255, g=255, b=255):
    
    dt = datetime.now()
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    # Chronograph().setMode(mode = 1)
    
    Common().setTime(year=dt.year, month=dt.month, day=dt.day, hour=dt.hour, minute=dt.minute, second=dt.second)
    
    Clock().setMode(style=style, visibleDate=False, hour24=False, r=r, g=g, b=b)

@service
async def screenon(address=None):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Common().screenOn()

@service
async def screenoff(address=None):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Common().screenOff()

# Not working for some reason
@service
async def setbrightness( address=None, brightness=50 ):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Common().setBrightness( brightness=brightness )




@service
async def chrono(address=None, mode=[0,1,2,3]):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Chronograph().setMode(mode = mode)

@service
async def fullscreencolour(address=None, r=50, g=50, b=50):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    FullscreenColor().setMode( r=r, g=g, b=b)
    


@service
async def graffiti(address=None, r=50, g=50, b=50, x=10, y=10):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    Graffiti().setPixel( r=r, g=g, b=b, x=x, y=y)


#Not working for me?
@service
async def gif(address=None, gif=None):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    #Gif().uploadProcessed(file_path="/config/pyscript/images/earth.gif", pixel_size=16)
    Gif().uploadProcessed(file_path=gif, pixel_size=16)

@service
async def image(address=None, image=None):
    
    conn = ConnectionManager()
    await conn.connectByAddress(address)
    #Image().uploadProcessed(file_path="/config/pyscript/images/demo_16.png", pixel_size=16)
    Image().uploadProcessed(file_path=image, pixel_size=16)
    


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        quit()
offender: /usr/local/lib/python3.13/site-packages/idotmatrix/connectionManager.py, line 81: time.sleep(0.01))

The time.sleep in connectionManager.py seems to be causing stability issues with pyScript. Does anyone know how to update it to use asyncio.sleep instead of time.sleep to see if it will make it more stable?

I have been testing with the 16x16 Pixel Frame in a different room to my HA server with ESP32 Bluetooth proxies and it seems to be working but I get lots of the following errors with the sleep issue and many of the commands don’t always work.

2025-01-23 21:54:20.145 ERROR (MainThread) [custom_components.pyscript.file.idotmatrix.screenon] Exception in <file.idotmatrix.screenon> line 50:
        Common().screenOn()
        ^
RuntimeError: Caught blocking call to sleep with args (0.01,) inside the event loop by custom integration 'pyscript' at custom_components/pyscript/eval.py, line 1973: return await func(*args, **kwargs). (offender: /usr/local/lib/python3.13/site-packages/idotmatrix/connectionManager.py, line 81: time.sleep(0.01)), please create a bug report at https://github.com/custom-components/pyscript/issues
For developers, please see https://developers.home-assistant.io/docs/asyncio_blocking_operations/#sleep

@athua - Isn’t it as simple as importing asyncio and then changing the function call?

import asyncio

async def hello():
await asyncio.sleep(3)
print("Welcome to Sling Academy!")

asyncio.run(hello())

Edit: I’m trying to make the change but I think I’m changing the wrong file. My logs say it’s here:
offender: /usr/local/lib/python3.13/site-packages/idotmatrix/connectionManager.py, line 81

But using file browser/file editor I can’t find that location? Sorry I don’t quite understand containers / virtual environments in HA / Python so I suspect I’ve been making changes to the wrong source trying to get stuff to work :frowning:

I don’t know Python very well and also struggling with how Pyscript works. It looks like connectionManager.py is loaded in from the line in the test.py script:

from idotmatrix import ConnectionManager

I believe to make the change we would need to have a local copy of connectionManager.py and change the import to use the local version but I’m having difficulty getting it to work.

According to the Pyscripts documentation, any modules to import should be placed in:

<config>/pyscript/modules

But it doesn’t seem to be working for me.