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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. #!/usr/bin/env python3
  2. # Uses the Gitea API to fetch the latest revision of the project from a repo.
  3. #
  4. # Inspired by:
  5. # https://github.com/olivergregorius/micropython_ota
  6. #
  7. # ----------------------------------------------------------------------------
  8. # "THE BEER-WARE LICENSE" (Revision 42):
  9. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  10. # you can do whatever you want with this stuff. If we meet some day, and you
  11. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  12. # ----------------------------------------------------------------------------
  13. import util
  14. import sys
  15. import os
  16. import gc
  17. # to check if we're actually running on MicroPython
  18. on_pico = False
  19. try:
  20. import machine
  21. on_pico = True
  22. except Exception as e:
  23. print()
  24. if hasattr(sys, "print_exception"):
  25. sys.print_exception(e)
  26. else:
  27. print(e)
  28. print()
  29. class PicoOTA:
  30. def __init__(self, host, repo, branch = None):
  31. self.host = host
  32. self.repo = repo
  33. self.branch = branch
  34. self.get = None
  35. self.update_path = "."
  36. self.exe_path = ""
  37. self.version_file = "ota_version"
  38. self.blacklist = []
  39. self.gui = None
  40. self.text = None
  41. def ui(self, g, t):
  42. self.gui = g
  43. self.text = t
  44. def path(self, p):
  45. self.update_path = p
  46. def exe(self, e):
  47. self.exe_path = e
  48. def ignore(self, path):
  49. if not path in self.blacklist:
  50. self.blacklist.append(path)
  51. def fetch(self, url):
  52. # lazily initialize WiFi
  53. if self.get == None:
  54. self.get = util.getRequests()
  55. if self.get == None:
  56. return None
  57. try:
  58. #print("GET " + url)
  59. r = self.get(url)
  60. # explitic close on Response object not needed,
  61. # handled internally by r.content / r.text / r.json()
  62. # to avoid this automatic behaviour, first access r.content
  63. # to trigger caching it in response object, then close
  64. # socket.
  65. tmp = r.content
  66. if hasattr(r, "raw"):
  67. if r.raw != None:
  68. r.raw.close()
  69. r.raw = None
  70. return r
  71. except Exception as e:
  72. print()
  73. if hasattr(sys, "print_exception"):
  74. sys.print_exception(e)
  75. else:
  76. print(e)
  77. print()
  78. return None
  79. def get_stored_commit(self):
  80. current = "unknown"
  81. try:
  82. f = open(self.update_path + "/" + self.version_file, "r")
  83. current = f.readline().strip()
  84. f.close()
  85. except Exception as e:
  86. print()
  87. if hasattr(sys, "print_exception"):
  88. sys.print_exception(e)
  89. else:
  90. print(e)
  91. print()
  92. return current
  93. def get_previous_commit(self, commit):
  94. r = self.fetch(self.host + "/" + self.repo + "/commit/" + commit).text
  95. for line in r.splitlines():
  96. if not (self.repo + "/commit/") in line:
  97. continue
  98. line = line[line.find("/commit/") : ][8 : ][ : 40]
  99. if line != commit:
  100. return line
  101. return "unknown"
  102. def check(self, verbose = False):
  103. if self.branch == None:
  104. # get default branch
  105. r = self.fetch(self.host + "/api/v1/repos/" + self.repo).json()
  106. self.branch = r["default_branch"]
  107. if verbose:
  108. print("Selected default branch " + self.branch)
  109. # check for latest commit in branch
  110. r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/branches/" + self.branch).json()
  111. commit = r["commit"]["id"]
  112. if verbose:
  113. print("Latest commit is " + commit)
  114. current = self.get_stored_commit()
  115. if verbose:
  116. if current != commit:
  117. print("Current commit " + current + " is different!")
  118. else:
  119. print("No update required")
  120. return (current != commit, commit)
  121. def update_to_commit(self, commit, verbose = False):
  122. # list all files for a commit
  123. r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/git/trees/" + commit).json()
  124. # TODO does not support sub-folders
  125. if verbose:
  126. if len(r["tree"]) > 0:
  127. print(str(len(r["tree"])) + " files in repo:")
  128. for f in r["tree"]:
  129. if f["path"] in self.blacklist:
  130. print(" - (IGNORED) " + f["path"])
  131. else:
  132. print(" - " + f["path"])
  133. else:
  134. print("No files in repo?!")
  135. for f in r["tree"]:
  136. if f["path"] in self.blacklist:
  137. continue
  138. if (self.gui != None) and (self.text != None):
  139. self.gui.loop_start()
  140. self.text.fg = (255, 255, 0)
  141. self.text.setText("OTA", "bitmap6")
  142. self.text.draw(0, 6 * 0, False)
  143. self.text.fg = (255, 0, 255)
  144. self.text.setText(commit[ 0 : 5], "bitmap6")
  145. self.text.draw(0, 6 * 1, False)
  146. self.text.fg = (0, 255, 255)
  147. self.text.setText("Get", "bitmap6")
  148. self.text.draw(0, 6 * 2, False)
  149. self.text.fg = (0, 255, 0)
  150. self.text.setText(f["path"][0 : 5], "bitmap6")
  151. self.text.draw(0, 6 * 3, False)
  152. self.text.setText(f["path"][5 : ], "bitmap6")
  153. self.text.draw(0, 6 * 4, False)
  154. self.gui.loop_end()
  155. gc.collect()
  156. if hasattr(gc, "mem_free"):
  157. print("Collected Garbage:", gc.mem_free())
  158. # get a file from a commit
  159. r = self.fetch(self.host + "/" + self.repo + "/raw/commit/" + commit + "/" + f["path"]).text
  160. if verbose:
  161. print("Writing " + f["path"] + " to " + self.update_path)
  162. if (self.gui != None) and (self.text != None):
  163. self.gui.loop_start()
  164. self.text.fg = (255, 255, 0)
  165. self.text.setText("OTA", "bitmap6")
  166. self.text.draw(0, 6 * 0, False)
  167. self.text.fg = (255, 0, 255)
  168. self.text.setText(commit[ 0 : 5], "bitmap6")
  169. self.text.draw(0, 6 * 1, False)
  170. self.text.fg = (255, 0, 0)
  171. self.text.setText("Write", "bitmap6")
  172. self.text.draw(0, 6 * 2, False)
  173. self.text.fg = (0, 255, 0)
  174. self.text.setText(f["path"][0 : 5], "bitmap6")
  175. self.text.draw(0, 6 * 3, False)
  176. self.text.setText(f["path"][5 : ], "bitmap6")
  177. self.text.draw(0, 6 * 4, False)
  178. self.gui.loop_end()
  179. # overwrite existing file
  180. fo = open(self.update_path + "/" + f["path"], "w")
  181. fo.write(r)
  182. fo.close()
  183. if f["path"] == self.exe_path:
  184. if verbose:
  185. print("Writing " + f["path"] + " to main.py")
  186. fo = open(self.update_path + "/main.py", "w")
  187. fo.write(r)
  188. fo.close()
  189. # Write new commit id to local file
  190. f = open(self.update_path + "/" + self.version_file, "w")
  191. f.write(commit + "\n")
  192. f.close()
  193. def non_pico_ota_test(ota):
  194. if not os.path.exists("tmp"):
  195. os.makedirs("tmp")
  196. ota.path("tmp")
  197. print("Checking for updates")
  198. newer, commit = ota.check(True)
  199. print()
  200. # Just for testing
  201. previous = ota.get_previous_commit(commit)
  202. print("Previous commit (-1):", previous)
  203. previous = ota.get_previous_commit(previous)
  204. print("Previous commit (-2):", previous)
  205. print()
  206. if newer:
  207. print("Updating")
  208. ota.update_to_commit(commit, True)
  209. else:
  210. print("No update required")
  211. if on_pico:
  212. from pico import PicoText
  213. i = util.getInput()
  214. t = util.getTarget(i)
  215. s = PicoText(t)
  216. def pico_ota_run(ota):
  217. gc.collect()
  218. print("Collected Garbage:", gc.mem_free())
  219. t.loop_start()
  220. s.fg = (255, 255, 0)
  221. s.setText("OTA", "bitmap6")
  222. s.draw(0, 6 * 0, False)
  223. s.fg = (0, 255, 255)
  224. s.setText("Check", "bitmap6")
  225. s.draw(0, 6 * 2, False)
  226. t.loop_end()
  227. gc.collect()
  228. print("Collected Garbage:", gc.mem_free())
  229. print("Checking for updates")
  230. newer, commit = ota.check(True)
  231. gc.collect()
  232. print("Collected Garbage:", gc.mem_free())
  233. if newer:
  234. t.loop_start()
  235. s.fg = (255, 255, 0)
  236. s.setText("OTA", "bitmap6")
  237. s.draw(0, 6 * 0, False)
  238. s.fg = (255, 0, 255)
  239. s.setText(commit[ 0 : 5], "bitmap6")
  240. s.draw(0, 6 * 1, False)
  241. s.setText(commit[ 5 : 10], "bitmap6")
  242. s.draw(0, 6 * 2, False)
  243. s.setText(commit[10 : 15], "bitmap6")
  244. s.draw(0, 6 * 3, False)
  245. s.setText(commit[15 : 20], "bitmap6")
  246. s.draw(0, 6 * 4, False)
  247. t.loop_end()
  248. print("Updating to:", commit)
  249. ota.update_to_commit(commit, True)
  250. print("Resetting")
  251. machine.soft_reset()
  252. else:
  253. t.loop_start()
  254. s.fg = (255, 255, 0)
  255. s.setText("OTA", "bitmap6")
  256. s.draw(0, 6 * 0, False)
  257. s.fg = (0, 255, 0)
  258. s.setText("Done!", "bitmap6")
  259. s.draw(0, 6 * 1, False)
  260. s.fg = (255, 0, 255)
  261. s.setText("Boot", "bitmap6")
  262. s.draw(0, 6 * 2, False)
  263. s.fg = (0, 255, 255)
  264. s.setText("camp_pico.py"[0 : 5], "bitmap6")
  265. s.draw(0, 6 * 3, False)
  266. s.setText("camp_pico.py"[5 : ], "bitmap6")
  267. s.draw(0, 6 * 4, False)
  268. t.loop_end()
  269. fallback = False
  270. try:
  271. gc.collect()
  272. print("Collected Garbage:", gc.mem_free())
  273. print("Starting Application")
  274. import camp_pico
  275. except Exception as e:
  276. print()
  277. if hasattr(sys, "print_exception"):
  278. sys.print_exception(e)
  279. else:
  280. print(e)
  281. print()
  282. print("Falling back to previous")
  283. fallback = True
  284. # TODO this would immediately cause another update on reboot
  285. # TODO set a flag to prevent updates after fallbacks?
  286. # TODO or better, blacklist failed commit_id!
  287. #if fallback:
  288. # previous = ota.get_previous_commit(commit, True)
  289. # ota.update_to_commit(previous, True)
  290. # machine.soft_reset()
  291. ota = PicoOTA("https://git.xythobuz.de", "thomas/rgb-matrix-visualizer")
  292. # stuff not needed on Pico
  293. ota.ignore(".gitignore")
  294. ota.ignore("README.md")
  295. ota.ignore("copy.sh")
  296. ota.ignore("config.py")
  297. ota.ignore("fonts")
  298. ota.ignore("hardware")
  299. ota.ignore("images")
  300. ota.ignore("bdf.py")
  301. ota.ignore("camp_small.py")
  302. ota.ignore("gamepad.py")
  303. ota.ignore("pi.py")
  304. ota.ignore("test.py")
  305. if not on_pico:
  306. non_pico_ota_test(ota)
  307. else:
  308. ota.exe("pico_ota.py")
  309. ota.ui(t, s)
  310. pico_ota_run(ota)