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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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. # to check if we're actually running on MicroPython
  17. on_pico = False
  18. try:
  19. import machine
  20. on_pico = True
  21. except Exception as e:
  22. print()
  23. if hasattr(sys, "print_exception"):
  24. sys.print_exception(e)
  25. else:
  26. print(e)
  27. print()
  28. class PicoOTA:
  29. def __init__(self, host, repo, branch = None):
  30. self.host = host
  31. self.repo = repo
  32. self.branch = branch
  33. self.get = None
  34. self.update_path = "."
  35. self.exe_path = ""
  36. self.version_file = "ota_version"
  37. self.blacklist = []
  38. def path(self, p):
  39. self.update_path = p
  40. def exe(self, e):
  41. self.exe_path = e
  42. def ignore(self, path):
  43. if not path in self.blacklist:
  44. self.blacklist.append(path)
  45. def fetch(self, url):
  46. # lazily initialize WiFi
  47. if self.get == None:
  48. self.get = util.getRequests()
  49. if self.get == None:
  50. return None
  51. try:
  52. #print("GET " + url)
  53. r = self.get(url)
  54. # explitic close on Response object not needed,
  55. # handled internally by r.content / r.text / r.json()
  56. # to avoid this automatic behaviour, first access r.content
  57. # to trigger caching it in response object, then close
  58. # socket.
  59. tmp = r.content
  60. if hasattr(r, "raw"):
  61. if r.raw != None:
  62. r.raw.close()
  63. r.raw = None
  64. return r
  65. except Exception as e:
  66. print()
  67. if hasattr(sys, "print_exception"):
  68. sys.print_exception(e)
  69. else:
  70. print(e)
  71. print()
  72. return None
  73. def get_stored_commit(self):
  74. current = "unknown"
  75. try:
  76. f = open(self.update_path + "/" + self.version_file, "r")
  77. current = f.readline().strip()
  78. f.close()
  79. except Exception as e:
  80. print()
  81. if hasattr(sys, "print_exception"):
  82. sys.print_exception(e)
  83. else:
  84. print(e)
  85. print()
  86. return current
  87. def get_previous_commit(self, commit):
  88. r = self.fetch(self.host + "/" + self.repo + "/commit/" + commit).text
  89. for line in r.splitlines():
  90. if not (self.repo + "/commit/") in line:
  91. continue
  92. line = line[line.find("/commit/") : ][8 : ][ : 40]
  93. if line != commit:
  94. return line
  95. return "unknown"
  96. def check(self, verbose = False):
  97. if self.branch == None:
  98. # get default branch
  99. r = self.fetch(self.host + "/api/v1/repos/" + self.repo).json()
  100. self.branch = r["default_branch"]
  101. if verbose:
  102. print("Selected default branch " + self.branch)
  103. # check for latest commit in branch
  104. r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/branches/" + self.branch).json()
  105. commit = r["commit"]["id"]
  106. if verbose:
  107. print("Latest commit is " + commit)
  108. current = self.get_stored_commit()
  109. if verbose:
  110. if current != commit:
  111. print("Current commit " + current + " is different!")
  112. else:
  113. print("No update required")
  114. return (current != commit, commit)
  115. def update_to_commit(self, commit, verbose = False):
  116. # list all files for a commit
  117. r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/git/trees/" + commit).json()
  118. # TODO does not support sub-folders
  119. if verbose:
  120. if len(r["tree"]) > 0:
  121. print(str(len(r["tree"])) + " files in repo:")
  122. for f in r["tree"]:
  123. if f["path"] in self.blacklist:
  124. print(" - (IGNORED) " + f["path"])
  125. else:
  126. print(" - " + f["path"])
  127. else:
  128. print("No files in repo?!")
  129. for f in r["tree"]:
  130. if f["path"] in self.blacklist:
  131. continue
  132. # get a file from a commit
  133. r = self.fetch(self.host + "/" + self.repo + "/raw/commit/" + commit + "/" + f["path"]).text
  134. if verbose:
  135. print("Writing " + f["path"] + " to " + self.update_path)
  136. # overwrite existing file
  137. fo = open(self.update_path + "/" + f["path"], "w")
  138. fo.write(r)
  139. fo.close()
  140. if f["path"] == self.exe_path:
  141. if verbose:
  142. print("Writing " + f["path"] + " to main.py")
  143. fo = open(self.update_path + "/" + "main.py", "w")
  144. fo.write(r)
  145. fo.close()
  146. # Write new commit id to local file
  147. f = open(self.update_path + "/" + self.version_file, "w")
  148. f.write(commit + "\n")
  149. f.close()
  150. def non_pico_ota_test(ota):
  151. if not os.path.exists("tmp"):
  152. os.makedirs("tmp")
  153. ota.path("tmp")
  154. print("Checking for updates")
  155. newer, commit = ota.check(True)
  156. print()
  157. # Just for testing
  158. previous = ota.get_previous_commit(commit)
  159. print("Previous commit (-1):", previous)
  160. previous = ota.get_previous_commit(previous)
  161. print("Previous commit (-2):", previous)
  162. print()
  163. if newer:
  164. print("Updating")
  165. ota.update_to_commit(commit, True)
  166. else:
  167. print("No update required")
  168. def pico_ota_run(ota):
  169. import gc
  170. #gc.collect()
  171. #print(gc.mem_free())
  172. i = util.getInput()
  173. t = util.getTarget(i)
  174. #gc.collect()
  175. #print(gc.mem_free())
  176. # Loading fonts and graphics takes a while.
  177. # So show a splash screen while the user waits.
  178. from splash import SplashScreen
  179. splash = SplashScreen(t)
  180. t.loop_start()
  181. splash.draw()
  182. t.loop_end()
  183. #gc.collect()
  184. #print(gc.mem_free())
  185. print("Checking for updates")
  186. newer, commit = ota.check(True)
  187. #gc.collect()
  188. #print(gc.mem_free())
  189. if newer:
  190. from pico import PicoText
  191. s = PicoText(t)
  192. s.setText("Update", "bitmap6")
  193. s.draw(0, 0, False)
  194. s.setText(commit, "bitmap6")
  195. s.draw(0, 8, False)
  196. print("Updating to:", commit)
  197. ota.update_to_commit(commit, True)
  198. print("Resetting")
  199. machine.soft_reset()
  200. fallback = False
  201. try:
  202. gc.collect()
  203. print("Collected Garbage:", gc.mem_free())
  204. print("Starting Application")
  205. import camp_pico
  206. except Exception as e:
  207. print()
  208. if hasattr(sys, "print_exception"):
  209. sys.print_exception(e)
  210. else:
  211. print(e)
  212. print()
  213. print("Falling back to previous")
  214. fallback = True
  215. # TODO this would immediately cause another update on reboot
  216. # TODO set a flag to prevent updates after fallbacks?
  217. # TODO or better, blacklist failed commit_id!
  218. #if fallback:
  219. # previous = ota.get_previous_commit(commit, True)
  220. # ota.update_to_commit(previous, True)
  221. # machine.soft_reset()
  222. if True: #__name__ == "__main__":
  223. ota = PicoOTA("https://git.xythobuz.de", "thomas/rgb-matrix-visualizer")
  224. # stuff not needed on Pico
  225. ota.ignore(".gitignore")
  226. ota.ignore("README.md")
  227. ota.ignore("copy.sh")
  228. ota.ignore("config.py")
  229. ota.ignore("fonts")
  230. ota.ignore("hardware")
  231. ota.ignore("images")
  232. ota.ignore("bdf.py")
  233. ota.ignore("camp_small.py")
  234. ota.ignore("gamepad.py")
  235. ota.ignore("pi.py")
  236. ota.ignore("test.py")
  237. if not on_pico:
  238. non_pico_ota_test(ota)
  239. else:
  240. ota.exe("pico_ota.py")
  241. pico_ota_run(ota)