Python RGB Matrix games and animations https://www.xythobuz.de/ledmatrix_v2.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pico.py 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. #!/usr/bin/env python3
  2. # For the Pimoroni Interstate75 Raspberry Pi Pico RGB LED Matrix interface:
  3. # https://github.com/pimoroni/pimoroni-pico
  4. #
  5. # ----------------------------------------------------------------------------
  6. # "THE BEER-WARE LICENSE" (Revision 42):
  7. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  8. # you can do whatever you want with this stuff. If we meet some day, and you
  9. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  10. # ----------------------------------------------------------------------------
  11. import interstate75
  12. import hub75
  13. from mapper import MapperReduceBrightness
  14. import time
  15. from machine import Pin, ADC
  16. import math
  17. # https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/interstate75/75W/clock.py
  18. @micropython.native # noqa: F821
  19. def from_hsv(h, s, v):
  20. i = math.floor(h * 6.0)
  21. f = h * 6.0 - i
  22. v *= 255.0
  23. p = v * (1.0 - s)
  24. q = v * (1.0 - f * s)
  25. t = v * (1.0 - (1.0 - f) * s)
  26. i = int(i) % 6
  27. if i == 0:
  28. return int(v), int(t), int(p)
  29. if i == 1:
  30. return int(q), int(v), int(p)
  31. if i == 2:
  32. return int(p), int(v), int(t)
  33. if i == 3:
  34. return int(p), int(q), int(v)
  35. if i == 4:
  36. return int(t), int(p), int(v)
  37. if i == 5:
  38. return int(v), int(p), int(q)
  39. def batt_to_color(batt):
  40. h = batt[0] / 100.0 * 0.3333
  41. r, g, b = from_hsv(h, 1.0, 1.0)
  42. return r, g, b
  43. class PicoMatrix:
  44. def __init__(self, input = None, w = 32, h = 32):
  45. self.width = w # x-axis
  46. self.height = h # y-axis
  47. self.panelW = w # x-axis
  48. self.panelH = h # y-axis
  49. # compatibility to TestGUI
  50. self.multiplier = 1.0
  51. if (w != 32) or (h != 32):
  52. raise RuntimeError("TODO not yet supported")
  53. self.input = input
  54. if self.input != None:
  55. self.input.gui = self
  56. mode = interstate75.DISPLAY_INTERSTATE75_32X32
  57. self.matrix = interstate75.Interstate75(display = mode, panel_type = hub75.PANEL_FM6126A)
  58. self.black = self.matrix.display.create_pen(0, 0, 0)
  59. self.white = self.matrix.display.create_pen(255, 255, 255)
  60. self.ledTime = time.time()
  61. self.led = Pin("LED", Pin.OUT)
  62. self.ledRefresh = 0.5
  63. self.adc = ADC(26)
  64. self.battState = None
  65. self.battTime = time.time()
  66. self.battRefresh = 10
  67. self.loop_start() # initialize with blank image for ScrollText constructor
  68. def exit(self):
  69. self.matrix.stop()
  70. def battery(self):
  71. n = const(10)
  72. bits = const(10)
  73. raw = 0
  74. for i in range(0, n):
  75. raw += self.adc.read_u16() >> (16 - bits)
  76. time.sleep(0.1 / n)
  77. raw /= n
  78. v_adc_ref = const(3.3) # V
  79. v_adc = (raw / ((1 << bits) - 1)) * v_adc_ref
  80. r1 = const(27.0) # kOhm
  81. r2 = const(5.6) # kOhm
  82. v_bat_uncal = (v_adc * (r1 + r2)) / r2
  83. # Calibration
  84. # TODO supports only 2 points
  85. cal_pts = [
  86. # adc, bat
  87. #(13.65, 15.46),
  88. #(13.60, 15.34),
  89. (13.625, 15.4), # avg of above
  90. (14.30, 16.13),
  91. ]
  92. # https://www.ti.com/europe/downloads/f2810_12_calibration_10.pdf
  93. gain = (cal_pts[0][1] - cal_pts[1][1]) / (cal_pts[0][0] - cal_pts[1][0])
  94. offset = cal_pts[1][1] - cal_pts[1][0] * gain
  95. v_bat = v_bat_uncal * gain + offset
  96. # Use this to gather calibration data
  97. #v_bat = v_bat_uncal
  98. #print(v_bat)
  99. # TODO auto-detect cell count
  100. n_lipo = const(4) # 4S
  101. v_cell = v_bat / n_lipo
  102. v_cell_min = const(3.25)
  103. v_cell_max = const(4.2)
  104. p_cell = (v_cell - v_cell_min) / (v_cell_max - v_cell_min) * 100.0
  105. p_cell = max(min(p_cell, 100.0), 0.0)
  106. ret = (p_cell, v_cell, v_bat)
  107. r, g, b = batt_to_color(ret)
  108. self.matrix.set_led(r, g, b)
  109. return ret
  110. def batteryCache(self, refresh = False):
  111. now = time.time()
  112. if (self.battState == None) or ((now - self.battTime) >= self.battRefresh) or (now < self.battTime) or refresh:
  113. self.battTime = now
  114. self.battState = self.battery()
  115. return self.battState
  116. def heartbeat(self):
  117. now = time.time()
  118. if ((now - self.ledTime) >= self.ledRefresh) or (now < self.ledTime):
  119. self.ledTime = now
  120. self.led.toggle()
  121. def loop_start(self):
  122. self.matrix.display.set_pen(self.black)
  123. self.matrix.display.clear()
  124. self.matrix.display.set_pen(self.white)
  125. return False # no input, never quit on our own
  126. def loop_end(self):
  127. self.matrix.update()
  128. # LED heartbeat blink
  129. self.heartbeat()
  130. # update battery if necessary
  131. self.batteryCache(False)
  132. def set_pixel(self, x, y, color):
  133. if (x < 0) or (y < 0) or (x >= self.width) or (y >= self.height):
  134. return
  135. pen = self.matrix.display.create_pen(color[0], color[1], color[2])
  136. self.matrix.display.set_pen(pen)
  137. self.matrix.display.pixel(int(x), int(y))
  138. class PicoText:
  139. def __init__(self, g, fg = (255, 255, 255), bg = (0, 0, 0), c = (0, 255, 0)):
  140. self.gui = g
  141. self.fg = fg
  142. self.bg = bg
  143. self.color = c
  144. # text drawing API
  145. def setText(self, s, f):
  146. self.text = s
  147. self.font = f
  148. # text drawing API
  149. def getDimensions(self):
  150. self.gui.matrix.display.set_font(self.font)
  151. w = self.gui.matrix.display.measure_text(self.text, scale=1)
  152. return (w, 42) # TODO wrong height
  153. # text drawing API
  154. def draw(self, xOff = 0, yOff = 0, compat = True):
  155. color = self.fg
  156. if isinstance(self.gui, MapperReduceBrightness):
  157. color = self.gui.adjust(color)
  158. pen = self.gui.matrix.display.create_pen(color[0], color[1], color[2])
  159. self.gui.matrix.display.set_pen(pen)
  160. self.gui.matrix.display.set_font(self.font)
  161. if not compat:
  162. # absolute positioning
  163. x = xOff
  164. y = yOff
  165. else:
  166. # centered, like BDF DrawText implementation
  167. fontOff = 0
  168. if self.font == "bitmap6":
  169. fontOff = 3
  170. elif self.font == "bitmap8":
  171. fontOff = 4
  172. elif self.font == "bitmap14_outline":
  173. fontOff = 7
  174. x = -xOff
  175. y = int(self.gui.height / 2 - fontOff + yOff)
  176. self.gui.matrix.display.text(self.text, x, y, scale=1)
  177. class PicoBatt:
  178. def __init__(self, g, ti = 5.0, tt = 5.0):
  179. self.gui = g
  180. self.timeImage = ti
  181. self.timeText = tt
  182. self.text = PicoText(self.gui)
  183. self.restart()
  184. def restart(self):
  185. self.start = time.time()
  186. def finished(self):
  187. now = time.time()
  188. return ((now - self.start) >= (self.timeText + self.timeImage)) or (now < self.start)
  189. def drawText(self, refresh = False):
  190. batt = self.gui.batteryCache(refresh)
  191. c = batt_to_color(batt)
  192. self.text.fg = (255, 255, 255)
  193. self.text.setText( "Batt:", "bitmap8")
  194. self.text.draw(0, 8 * 0, False)
  195. self.text.fg = c
  196. self.text.setText("{:.2f}%".format(batt[0]), "bitmap8")
  197. self.text.draw(0, 8 * 1, False)
  198. self.text.setText("{:.2f}V".format(batt[1]), "bitmap8")
  199. self.text.draw(0, 8 * 2, False)
  200. self.text.setText("{:.2f}V".format(batt[2]), "bitmap8")
  201. self.text.draw(0, 8 * 3, False)
  202. def drawImage(self, refresh = False):
  203. x_off = const(1)
  204. y_off = const(16)
  205. nub_w = const(3)
  206. nub_h = const(6)
  207. w = self.gui.width - x_off * 2
  208. h = self.gui.height - y_off
  209. batt = self.gui.batteryCache(refresh)
  210. c = batt_to_color(batt)
  211. fill_w = int(batt[0] / 100.0 * (w - nub_w))
  212. s = "{:.0f}%".format(batt[0])
  213. self.text.setText(s, "bitmap14_outline")
  214. s_w = self.text.getDimensions()[0]
  215. self.text.fg = (255, 255, 255)
  216. self.text.draw(int((self.gui.width - s_w) / 2), 1, False)
  217. for x in range(0, w - nub_w):
  218. for y in range(0, h):
  219. if (x == 0) or (x == (w - nub_w - 1)) or (y == 0) or (y == (h - 1)) or (x < fill_w):
  220. self.gui.set_pixel(x + x_off, y + y_off, c)
  221. for x in range(0, nub_w):
  222. for y in range(0, nub_h):
  223. self.gui.set_pixel(x + x_off + w - nub_w, y + y_off + int((h - nub_h) / 2), c)
  224. def draw(self, refresh = False):
  225. now = time.time()
  226. if (now - self.start) < self.timeImage:
  227. self.drawImage(refresh)
  228. else:
  229. self.drawText(refresh)
  230. class PicoInput:
  231. def __init__(self):
  232. self.gui = None
  233. self.keys = self.input.empty() # TODO support missing input
  234. def get(self):
  235. if self.gui != None:
  236. self.keys["l"] = self.gui.matrix.switch_pressed(interstate75.SWITCH_A)
  237. self.keys["r"] = self.gui.matrix.switch_pressed(interstate75.SWITCH_B)
  238. return self.keys
  239. if __name__ == "__main__":
  240. import time
  241. import util
  242. t = PicoMatrix(32, 32)
  243. s = PicoText(t)
  244. b = PicoBatt(t, 10.0, 10.0)
  245. start = time.time()
  246. i = 0
  247. def helper():
  248. global s, start, i, b
  249. now = time.time()
  250. if ((now - start) > 5.0) or (now < start):
  251. start = now
  252. i = (i + 1) % 6
  253. if i == 0:
  254. b.restart()
  255. if i < 4:
  256. b.draw(True)
  257. time.sleep(1.0)
  258. elif i == 4:
  259. s.setText("Abgj6", "bitmap6")
  260. s.draw(0, 0, False)
  261. s.setText("Abdgj8", "bitmap8")
  262. s.draw(0, 6 + 2, False)
  263. s.setText("Ag14", "bitmap14_outline")
  264. s.draw(0, 6 + 2 + 8 + 1, False)
  265. else:
  266. s.setText("Drinks:", "bitmap8")
  267. s.draw()
  268. util.loop(t, helper)