python-usage.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. Python Usage
  2. ------------
  3. 8x8 LED Matrices
  4. ^^^^^^^^^^^^^^^^
  5. For the matrix device, initialize the :py:class:`luma.led_matrix.device.max7219`
  6. class, as follows:
  7. .. code:: python
  8. from luma.core.interface.serial import spi, noop
  9. from luma.core.render import canvas
  10. from luma.led_matrix.device import max7219
  11. serial = spi(port=0, device=0, gpio=noop())
  12. device = max7219(serial)
  13. The display device should now be configured for use. The specific
  14. :py:class:`~luma.led_matrix.device.max7219` class exposes a
  15. :py:func:`~luma.led_matrix.device.max7219.display` method which takes an image
  16. with attributes consistent with the capabilities of the configured device's
  17. capabilities. However, for most cases, for drawing text and graphics primitives,
  18. the canvas class should be used as follows:
  19. .. code:: python
  20. from PIL import ImageFont
  21. font = ImageFont.truetype("examples/pixelmix.ttf", 8)
  22. with canvas(device) as draw:
  23. draw.rectangle(device.bounding_box, outline="white", fill="black")
  24. The :py:class:`luma.core.render.canvas` class automatically creates an
  25. :py:mod:`PIL.ImageDraw` object of the correct dimensions and bit depth suitable
  26. for the device, so you may then call the usual Pillow methods to draw onto the
  27. canvas.
  28. As soon as the with scope is ended, the resultant image is automatically
  29. flushed to the device's display memory and the :mod:`PIL.ImageDraw` object is
  30. garbage collected.
  31. .. note::
  32. The default Pillow font is too big for 8px high devices like the LED matrices
  33. here, so the `luma.examples <https://github.com/rm-hull/luma.examples>`_ repo
  34. inclues a small TTF pixel font called **pixelmix.ttf** (attribution:
  35. http://www.dafont.com/) which just fits.
  36. Alternatively, a set of "legacy" fixed-width bitmap fonts are included in
  37. the `luma.core <https://github.com/rm-hull/luma.core>`__ codebase and may be
  38. used as follows:
  39. .. code:: python
  40. from luma.core.legacy import text
  41. from luma.core.legacy.font import proportional, CP437_FONT, LCD_FONT
  42. with canvas(device) as draw:
  43. text(draw, (0, 0), "A", fill="white", font=proportional(CP437_FONT))
  44. The fixed-width fonts can be "converted" on-the-fly to proportionally
  45. spaced by wrapping them with the :py:class:`luma.core.legacy.font.proportional`
  46. class.
  47. Scrolling / Virtual viewports
  48. """""""""""""""""""""""""""""
  49. A single 8x8 LED matrix clearly hasn't got a lot of area for displaying useful
  50. information. Obviously they can be daisy-chained together to provide a longer
  51. line of text, but as this library extends `luma.core <https://github.com/rm-hull/luma.core>`_,
  52. then we can use the :py:class:`luma.core.virtual.viewport` class to allow
  53. scrolling support:
  54. .. code:: python
  55. import time
  56. from luma.core.interface.serial import spi, noop
  57. from luma.core.render import canvas
  58. from luma.core.virtual import viewport
  59. from luma.led_matrix.device import max7219
  60. serial = spi(port=0, device=0, gpio=noop())
  61. device = max7219(serial)
  62. virtual = viewport(device, width=200, height=100)
  63. with canvas(virtual) as draw:
  64. draw.rectangle(device.bounding_box, outline="white", fill="black")
  65. draw.text((3, 3), "Hello world", fill="white")
  66. for offset in range(8):
  67. virtual.set_position((offset, offset))
  68. time.sleep(0.1)
  69. Calling :py:meth:`~luma.core.virtual.viewport.set_position` on a virtual
  70. viewport, causes the device to render what is visible at that specific
  71. position; altering the position in a loop refreshes every time it is called,
  72. and gives an animated scrolling effect.
  73. By altering both the X and Y co-ordinates allows scrolling in any direction,
  74. not just horizontally.
  75. Color Model
  76. """""""""""
  77. Any of the standard :mod:`PIL.ImageColor` color formats may be used, but since
  78. the 8x8 LED Matrices are monochrome, only the HTML color names :py:const:`"black"` and
  79. :py:const:`"white"` values should really be used; in fact, by default, any value
  80. *other* than black is treated as white. The :py:class:`luma.core.render.canvas`
  81. constructor does have a :py:attr:`dither` flag which if set to
  82. :py:const:`True`, will convert color drawings to a dithered monochrome effect.
  83. .. code:: python
  84. with canvas(device, dither=True) as draw:
  85. draw.rectangle(device.bounding_box, outline="white", fill="red")
  86. Landscape / Portrait Orientation
  87. """"""""""""""""""""""""""""""""
  88. By default, cascaded matrices will be oriented in landscape mode. Should you
  89. have an application that requires the display to be mounted in a portrait
  90. aspect, then add a :py:attr:`rotate=N` parameter when creating the device:
  91. .. code:: python
  92. from luma.core.interface.serial import spi, noop
  93. from luma.core.render import canvas
  94. from luma.led_matrix.device import max7219
  95. serial = spi(port=0, device=0, gpio=noop())
  96. device = max7219(serial, rotate=1)
  97. # Box and text rendered in portrait mode
  98. with canvas(device) as draw:
  99. draw.rectangle(device.bounding_box, outline="white", fill="black")
  100. *N* should be a value of 0, 1, 2 or 3 only, where 0 is no rotation, 1 is
  101. rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation.
  102. The :py:attr:`device.size`, :py:attr:`device.width` and :py:attr:`device.height`
  103. properties reflect the rotated dimensions rather than the physical dimensions.
  104. Daisy-chaining
  105. """"""""""""""
  106. The MAX7219 chipset supports a serial 16-bit register/data buffer which is
  107. clocked in on pin DIN every time the clock edge falls, and clocked out on DOUT
  108. 16.5 clock cycles later. This allows multiple devices to be chained together.
  109. If you have more than one device and they are daisy-chained together, you can
  110. initialize the library in one of two ways, either using :py:attr:`cascaded=N`
  111. to indicate the number of daisychained devices:
  112. .. code:: python
  113. from luma.core.interface.serial import spi, noop
  114. from luma.core.render import canvas
  115. from luma.led_matrix.device import max7219
  116. serial = spi(port=0, device=0, gpio=noop())
  117. device = max7219(serial, cascaded=3)
  118. with canvas(device) as draw:
  119. draw.rectangle(device.bounding_box, outline="white", fill="black")
  120. Using :py:attr:`cascaded=N` implies there are N devices arranged linearly and
  121. horizontally, running left to right.
  122. Alternatively, the device configuration may configured with :py:attr:`width=W`
  123. and :py:attr:`height=H`. These dimensions denote the number of LEDs in the all
  124. the daisychained devices. The width and height *must* both be multiples of 8:
  125. this has scope for arranging in blocks in, say 3x3 or 5x2 matrices (24x24 or
  126. 40x16 pixels, respectively).
  127. Given 12 daisychained MAX7219's arranged in a 4x3 layout, the simple example
  128. below,
  129. .. code:: python
  130. from luma.core.interface.serial import spi, noop
  131. from luma.core.render import canvas
  132. from luma.core.legacy import text
  133. from luma.core.legacy.font import proportional, LCD_FONT
  134. from luma.led_matrix.device import max7219
  135. serial = spi(port=0, device=0, gpio=noop(), block_orientation=-90)
  136. device = max7219(serial, width=32, height=24)
  137. with canvas(device) as draw:
  138. draw.rectangle(device.bounding_box, outline="white")
  139. text(draw, (2, 2), "Hello", fill="white", font=proportional(LCD_FONT))
  140. text(draw, (2, 10), "World", fill="white", font=proportional(LCD_FONT))
  141. displays as:
  142. .. image:: images/box_helloworld.jpg
  143. :alt: box helloworld
  144. Trouble-shooting / common problems
  145. """"""""""""""""""""""""""""""""""
  146. Some online retailers are selling pre-assembled `'4-in-1' LED matrix displays
  147. <http://www.ebay.co.uk/itm/371306583204>`_, but they appear to be wired 90°
  148. out-of-phase such that horizontal scrolling appears as below:
  149. .. image:: images/block_reorientation.gif
  150. :alt: block alignment
  151. This can be rectified by initializing the :py:class:`~luma.led_matrix.device.max7219`
  152. device with a parameter of :py:attr:`block_orientation=-90` (or +90, if your device is
  153. aligned the other way):
  154. .. code:: python
  155. from luma.core.interface.serial import spi, noop
  156. from luma.core.render import canvas
  157. from luma.led_matrix.device import max7219
  158. serial = spi(port=0, device=0, gpio=noop())
  159. device = max7219(serial, cascaded=4, block_orientation=-90)
  160. Every time a display render is subsequenly requested, the underlying image
  161. representation is corrected to reverse the 90° phase shift.
  162. Similarly, in other pre-assembled configurations, the 4-in-1 blocks
  163. arrange the 8x8 blocks in reverse order. In that case, you need to pass
  164. a True value to parameter `blocks_arranged_in_reverse_order`, requesting
  165. an additional pre-processing step that fixes this:
  166. .. code:: python
  167. ...
  168. device = max7219(serial, cascaded=4, block_orientation=-90,
  169. blocks_arranged_in_reverse_order=True)
  170. 7-Segment LED Displays
  171. ^^^^^^^^^^^^^^^^^^^^^^
  172. For the 7-segment device, initialize the :py:class:`luma.core.virtual.sevensegment`
  173. class, and wrap it around a previously created :py:class:`~luma.led_matrix.device.max7219`
  174. device:
  175. .. code:: python
  176. from luma.core.interface.serial import spi, noop
  177. from luma.core.render import canvas
  178. from luma.core.virtual import sevensegment
  179. from luma.led_matrix.device import max7219
  180. serial = spi(port=0, device=0, gpio=noop())
  181. device = max7219(serial, cascaded=2)
  182. seg = sevensegment(device)
  183. The **seg** instance now has a :py:attr:`~luma.core.virtual.sevensegment.text`
  184. property which may be assigned, and when it does will update all digits
  185. according to the limited alphabet the 7-segment displays support. For example,
  186. assuming there are 2 cascaded modules, we have 16 character available, and so
  187. can write:
  188. .. code:: python
  189. seg.text = "Hello world"
  190. Rather than updating the whole display buffer, it is possible to update
  191. 'slices', as per the below example:
  192. .. code:: python
  193. seg.text[0:5] = "Goodbye"
  194. This replaces ``Hello`` in the previous example, replacing it with ``Gooobye``.
  195. The usual python idioms for slicing (inserting / replacing / deleteing) can be
  196. used here, but note if inserted text exceeds the underlying buffer size, a
  197. :py:exc:`ValueError` is raised.
  198. Floating point numbers (or text with '.') are handled slightly differently - the
  199. decimal-place is fused in place on the character immediately preceding it. This
  200. means that it is technically possible to get more characters displayed than the
  201. buffer allows, but only because dots are folded into their host character
  202. .. image:: images/IMG_2810.JPG
  203. :alt: max7219 sevensegment
  204. WS2812 NeoPixels
  205. ^^^^^^^^^^^^^^^^
  206. For a strip of neopixels, initialize the :py:class:`luma.led_matrix.device.ws2812`
  207. class (also aliased to :py:class:`luma.led_matrix.device.neopixel`), supplying a
  208. parameter :py:attr:`cascaded=N` where *N* is the number of daisy-chained LEDs.
  209. This script creates a drawing surface 100 pixels long, and lights up three specific
  210. pixels, and a contiguous block:
  211. .. code:: python
  212. from luma.core.render import canvas
  213. from luma.led_matrix.device import ws2812
  214. device = ws2812(cascaded=100)
  215. with canvas(device) as draw:
  216. draw.point((0,0), fill="white")
  217. draw.point((4,0), fill="blue")
  218. draw.point((11,0), fill="orange")
  219. draw.rectange((20, 0, 40, 0), fill="red")
  220. If you have a device like Pimoroni's `Unicorn pHat <https://shop.pimoroni.com/products/unicorn-phat>`_,
  221. initialize the device with :py:attr:`width=N` and :py:attr:`height=N` attributes instead:
  222. .. code:: python
  223. from luma.core.render import canvas
  224. from luma.led_matrix.device import ws2812
  225. # Pimoroni's Unicorn pHat is 8x4 neopixels
  226. device = ws2812(width=8, height=4)
  227. with canvas(device) as draw:
  228. draw.line((0, 0, 0, device.height), fill="red")
  229. draw.line((1, 0, 1, device.height), fill="orange")
  230. draw.line((2, 0, 2, device.height), fill="yellow")
  231. draw.line((3, 0, 3, device.height), fill="green")
  232. draw.line((4, 0, 4, device.height), fill="blue")
  233. draw.line((5, 0, 5, device.height), fill="indigo")
  234. draw.line((6, 0, 6, device.height), fill="violet")
  235. draw.line((7, 0, 7, device.height), fill="white")
  236. .. note::
  237. The ws2812 driver uses the `ws2812 <https://pypi.python.org/pypi/ws2812>`_
  238. PyPi package to interface to the daisychained LEDs. It uses DMA (direct memory
  239. access) via ``/dev/mem`` which means that it has to run in privileged mode
  240. (via ``sudo`` root access).
  241. The same viewport, scroll support, portrait/landscape orientation and color model
  242. idioms provided in luma.core are equally applicable to the ws2812 implementation.
  243. Pimoroni Unicorn HAT
  244. """"""""""""""""""""
  245. Pimoroni sells the `Unicorn HAT <https://shop.pimoroni.com/products/unicorn-hat>`_,
  246. comprising 64 WS2812b NeoPixels in an 8x8 arrangement. The pixels are cascaded, but
  247. arranged in a 'snake' layout, rather than a 'scan' layout. In order to accomodate this,
  248. a translation mapping is required, as follows:
  249. .. code:: python
  250. import time
  251. from luma.led_matrix.device import ws2812, UNICORN_HAT
  252. from luma.core.render import canvas
  253. device = ws2812(width=8, height=8, mapping=UNICORN_HAT)
  254. for y in range(device.height):
  255. for x in range(device.width):
  256. with canvas(device) as draw:
  257. draw.point((x, y), fill="green")
  258. time.sleep(0.5)
  259. This should animate a green dot moving left-to-right down each line.
  260. Pimoroni Unicorn HAT HD
  261. """""""""""""""""""""""
  262. Pimoroni sells the `Unicorn HAT HD <https://shop.pimoroni.com/products/unicorn-hat-hd>`_,
  263. comprising 256 high-intensity RGB LEDs in a 16x16 arrangement. The pixels are driven by an
  264. ARM STM32F making the display appear as an SPI device:
  265. .. code:: python
  266. import time
  267. from luma.led_matrix.device import unicornhathd
  268. from luma.core.interface.serial import spi, noop
  269. from luma.core.render import canvas
  270. serial = spi(port=0, device=0, gpio=noop())
  271. device = unicornhathd(serial)
  272. for y in range(device.height):
  273. for x in range(device.width):
  274. with canvas(device) as draw:
  275. draw.point((x, y), fill="green")
  276. time.sleep(0.5)
  277. This should animate a green dot moving left-to-right down each line.
  278. NeoSegments (WS2812)
  279. """"""""""""""""""""
  280. `@msurguy <https://twitter.com/msurguy?lang=en>`_ has `crowdsourced some WS2812 neopixels <https://www.crowdsupply.com/maksmakes/neosegment>`_
  281. into a modular 3D-printed seven-segment unit. To program these devices:
  282. .. code:: python
  283. import time
  284. from luma.led_matrix_device import neosegment
  285. neoseg = neosegment(width=6)
  286. # Defaults to "white" color initially
  287. neoseg.text = "NEOSEG"
  288. time.sleep(1)
  289. # Set the first char ('N') to red
  290. neoseg.color[0] = "red"
  291. time.sleep(1)
  292. # Set fourth and fifth chars ('S','E') accordingly
  293. neoseg.color[3:5] = ["cyan", "blue"]
  294. time.sleep(1)
  295. # Set the entire string to green
  296. neoseg.color = "green"
  297. The :py:class:`~luma.led_matrix.device.neosegment` class extends :py:class:`~luma.core.virtual.sevensegment`,
  298. so the same text assignment (Python slicing paradigms) can be used here as well -
  299. see the earlier section for further details.
  300. The underlying device is exposed as attribute :py:attr:`device`, so methods
  301. such as :py:attr:`show`, :py:attr:`hide` and :py:attr:`contrast` are available.
  302. Next-generation APA102 NeoPixels
  303. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  304. APA102 RGB neopixels are easier to control that WS2812 devices - they are driven
  305. using SPI rather than precise timings that the WS2812 devices need. Initialize the
  306. :py:class:`luma.led_matrix.device.apa102` class, supplying a parameter
  307. :py:attr:`cascaded=N` where *N* is the number of daisy-chained LEDs.
  308. The following script creates a drawing surface 8 pixels long, and lights up three
  309. specific pixels:
  310. .. code:: python
  311. from luma.core.render import canvas
  312. from luma.led_matrix.device import apa102
  313. device = apa102(cascaded=8)
  314. with canvas(device) as draw:
  315. draw.point((0,0), fill="white")
  316. draw.point((0,1), fill="blue")
  317. draw.point((0,2), fill=(0xFF, 0x00, 0x00, 0x80)) # RGBA tuple, alpha controls brightness
  318. APA102 RGB pixels can have their brightness individually controlled: by setting
  319. the alpha chanel to a translucent value (as per the above example) will set the
  320. brightness accordingly.
  321. Emulators
  322. ^^^^^^^^^
  323. There are various `display emulators <http://github.com/rm-hull/luma.emulator>`_
  324. available for running code against, for debugging and screen capture functionality:
  325. * The :py:class:`luma.emulator.device.capture` device will persist a numbered
  326. PNG file to disk every time its :py:meth:`~luma.emulator.device.capture.display`
  327. method is called.
  328. * The :py:class:`luma.emulator.device.gifanim` device will record every image
  329. when its :py:meth:`~luma.emulator.device.gifanim.display` method is called,
  330. and on program exit (or Ctrl-C), will assemble the images into an animated
  331. GIF.
  332. * The :py:class:`luma.emulator.device.pygame` device uses the :py:mod:`pygame`
  333. library to render the displayed image to a pygame display surface.
  334. Invoke the demos with::
  335. $ python examples/clock.py -d capture --transform=led_matrix
  336. or::
  337. $ python examples/clock.py -d pygame --transform=led_matrix
  338. .. note::
  339. *Pygame* is required to use any of the emulated devices, but it is **NOT**
  340. installed as a dependency by default, and so must be manually installed
  341. before using any of these emulation devices (e.g. ``pip install pygame``).
  342. See the install instructions in `luma.emulator <http://github.com/rm-hull/luma.emulator>`_
  343. for further details.
  344. .. image:: images/emulator.gif
  345. :alt: max7219 emulator