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_ota.py 8.8KB

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