rm-hull/luma.led_matrix

basic led manipulation

Closed this issue ยท 14 comments

Probably a stupid question but does the updated package still provide the capability to manipulate individual leds - it's all I need to do ! [ for instance - set_pixel(0, 4, 1, redraw=False) ]

If so some info and examples in the documentation would be great

Type of Raspberry Pi

I am working across most variants but in this case the specific issue is with a pi zero application, that I would like to migrate from the old library

Linux Kernel version

Linux raspberrypi 4.4.34+ #930 Wed Nov 23 15:12:30 GMT 2016 armv6l GNU/Linux

No, no - good question :-) ... you can still update individual pixels - Basically, any of the methods on ImageDraw are valid inside the canvas context, so you can do stuff like:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2014-17 Richard Hull and contributors
# See LICENSE.rst for details.
# PYTHON_ARGCOMPLETE_OK

"""
Draw random dots
"""

import time
import random
from demo_opts import device
from luma.core.render import canvas


def main():
    while True:
        with canvas(device) as draw:
            for i in range(4):
                x = random.randint(0, device.width)
                y = random.randint(0, device.height)

                # 'draw' is an ImageDraw object.
                draw.point((x, y), fill="white")
                time.sleep(0.05)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass

For the LED matrix, any fill color (except "black") will switch the led on.

teqie commented

Hi
I've been working with this today, having previously developed a python script to display various graphics. In the previous library we were setting the pixels and then flushing this to the device, making the graphic appear almost instantly and all at once.

With code similar to yours above I'm seeing a more distinct 'sweeping' as the leds light one at a time.

Does this make sense? Is there a way around this?

What was the reason for abandoning the older method? It seems the features improved re: text and shapes but then the flexibility with individual pixel programming has been lost somewhat. Did I miss something?

Thanks

@teqie: the motivation for changing the library was mainly to leverage using Pillow to provide the drawing primitives, but the previous scrolling mechanism before was not very flexible - with the virtual viewport idea scrolling any direction is possible. It also means that any code written for this can work on an OLED or LCD display as well.

The rendering mechanism should be very fast - much faster than the previous version as this now uses hardware SPI to send to the MAX7219 - previously it was bitbanging in software. I benchmarked the library using an 8x8 LED matrix a few weeks back on a Pi Zero and it clocked about 260 FPS which is insanely fast.

Just by the nature of how the MAX7219 chips are daisy-chained, any single pixel change will require the whole image representation to be flushed out to the device. That was true for the old library as it is for the new library.

If you are seeing sweeping pixels, this implies you are force rendering after each pixel is updated, rather than rendering a full frame at a time. If that's just how your program works, there are ways around this - can you share your code? - I help can suggest ways to restructure it to make it perform better with the new library.

p.s. What device are you using?

teqie commented

Sure I'll post it here when I get chance. Currently it's a pi2 with a max7219cng chip. We've removed the original led matrix it came with and re-wired it to a grid of LEDs which are due to be attached to a robot. We want to use the LEDs to display an arrow or chevron or similar, indicating direction of movement.

Initially we had LEDs not staying lit, until I changed (from top of my head):
canvas(device) as draw
For :
C = canvas(device)
with c as draw
As I read that's the state is flushed at the end of each with loop, and we are using a nested for loop to parse a 2d array and set pixels on according to the previously defined array.

Eventually we're planning for a pi zero to drive the matrix, and a pi2 to drive the robot, with an rs232-over-USB link between the two for Comms. The pi zero may also end up doing some other led sequences under an idle condition but that's a 'like to have' as the LED team are largely amateur programmers (including myself), lightly assisted by some better programmers as time allows.

Sounds like you'd be better off rendering a series of frames for the arrows/chevrons to a virtual device once up-front, and then use the viewport to simulate the animation sequences.

@teqie I've done some testing with a larger number of cascaded devices, and identified and squashed some performance issues - if you could update to the latest version (0.5.3) and let me know if that works any better for you.

Update with:

$ sudo -H pip install --upgrade luma.led_matrix

@teqie any news?

teqie commented

Hi sorry it's been a bit crazy and trying to get the code machine close to GitHub has been awkward.

We've sorted the 'sweeping' - it was caused by the code printing the output to the console, and the time that took meant that instead of the LEDs all coming on together, they were lit sequentially and hence the sweeping effect.

I haven't updated yet either but we've only got one controller so not sure if it will make much difference.

I'll still post the code when I get a chance, see if we can streamline it.
Thanks

Hi sorry it's been a bit crazy and trying to get the code machine close to GitHub has been awkward.

awkward, how?

teqie commented

Trying to get the pi, myself and an internet connection together with 5 minutes to spare has been - challenging.

Here's some code snippets of what we're doing:
the drawoutput variable allows me to see on the command line what the function is doing, if set to 1. This as I said before was part of the cause of our sweeping rather than all-at-once issue.

The array passed in is a 2d array, initially zeroed and then setting certain pixels on eg:

forward_2=[[0 for x in range(w)] for y in range(h)]
forward_2[5][6]=1
forward_2[5][4]=1
forward_2[4][5]=1

then the function to output it to the matrix controller:

def drawstuff(array):
    c=canvas(device)
    device.contrast(255)
    for y in range(0,8):
        for x in range(0,8):
            if drawoutput == 1 : print array[y][x],
            with c as draw:
                draw.point((y,x),fill=array[y][x])
            
        if drawoutput == 1 : print
    if drawoutput == 1 : print

I hope that makes sense? If you think we can do something better please let me know.

We are currently looking to have a serial connection via the UARTs between 2 PIs - one of which will be doing the LED matrix. The Serial will send a single digit indicating which function to call e.g. 1 may be 'goforward' which will in turn run a series of 'drawstuff' routines to animate the LEDs, so forward_1 array represents the 1st frame (2nd actually) of an animation. The goforward function iterates the frames and draws them. We can see an issue that the LED pi may not have finished it's animation before the control pi sends another digit via serial. Our plan then rather than to try to run whats in the buffer is just to drop it at the end of each animation, and wait for the next input. We realise using interrupts would potentially give a better result but for our purpose it is too complex, we don't have time.

Thanks

Hi,

I also used individual pixels. At the end: device.flush().

This system is perfect for my because I need the drawing to stay lit until I do another device.flush().

Or device.clear().

However, when using the new library and

draw.point((x, y), fill="white")
time.sleep(0.05)

the pixels are switched off when "time.sleep(0.05)" finished.

Is there some method so that the pixels remain permanently lit without using a loop?

I run it from php and I finish the script and the pixels must be lit permanently

Thanks

(and good job)

@teqie - you need to move the with c as draw: to be outside the two for loops. This is because every time the code inside with context comes out of scope, it forces a refresh to the display. The moving it to the top, there is no point in setting the canvas separately, e.g.:

def drawstuff(array):
    device.contrast(255)
    with canvas(device) as draw:
        for y in range(0,8):
            for x in range(0,8):
                if drawoutput == 1 : print array[y][x],
                draw.point((y,x),fill=array[y][x])
            
        if drawoutput == 1 : print
    if drawoutput == 1 : print

p.s. I would also swap the x and y variables round

rm-hull, perfect! Works!!! You saved my project!

I keep image on display after script finished with this code:

from luma.led_matrix.device import max7219
from luma.core.serial import spi, noop
from luma.core.render import canvas

def do_nothing(obj):
    pass

# create matrix device
serial = spi(port=0, device=0, gpio=noop())
device = max7219(serial, cascaded=2)
print("Created device")

# override the cleanup method
device.cleanup = do_nothing

with canvas(device) as draw:
    draw.point((1,1), fill="white")
    draw.point((4,1), fill="blue")
    draw.point((7,1), fill="orange")