update.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env python3
  2. from flipper.app import App
  3. from flipper.utils.fff import FlipperFormatFile
  4. from os.path import basename, join, exists
  5. import os
  6. import shutil
  7. import zlib
  8. import tarfile
  9. class Main(App):
  10. # No compression, plain tar
  11. ASSET_TAR_MODE = "w:"
  12. def init(self):
  13. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  14. # generate
  15. self.parser_generate = self.subparsers.add_parser(
  16. "generate", help="Generate update description file"
  17. )
  18. self.parser_generate.add_argument("-d", dest="directory", required=True)
  19. self.parser_generate.add_argument("-v", dest="version", required=True)
  20. self.parser_generate.add_argument("-t", dest="target", required=True)
  21. self.parser_generate.add_argument("-dfu", dest="dfu", required=False)
  22. self.parser_generate.add_argument("-a", dest="assets", required=False)
  23. self.parser_generate.add_argument("-stage", dest="stage", required=True)
  24. self.parser_generate.add_argument(
  25. "-radio", dest="radiobin", default="", required=False
  26. )
  27. self.parser_generate.add_argument(
  28. "-radioaddr", dest="radioaddr", required=False
  29. )
  30. self.parser_generate.add_argument(
  31. "-radiover", dest="radioversion", required=False
  32. )
  33. self.parser_generate.set_defaults(func=self.generate)
  34. def generate(self):
  35. stage_basename = basename(self.args.stage)
  36. dfu_basename = basename(self.args.dfu)
  37. radiobin_basename = basename(self.args.radiobin)
  38. assets_basename = ""
  39. if not exists(self.args.directory):
  40. os.makedirs(self.args.directory)
  41. shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename))
  42. shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename))
  43. if radiobin_basename:
  44. shutil.copyfile(
  45. self.args.radiobin, join(self.args.directory, radiobin_basename)
  46. )
  47. if self.args.assets:
  48. assets_basename = "assets.tar"
  49. self.package_assets(
  50. self.args.assets, join(self.args.directory, assets_basename)
  51. )
  52. file = FlipperFormatFile()
  53. file.setHeader("Flipper firmware upgrade configuration", 1)
  54. file.writeKey("Info", self.args.version)
  55. file.writeKey("Target", self.args.target[1:]) # dirty 'f' strip
  56. file.writeKey("Loader", stage_basename)
  57. file.writeComment("little-endian hex!")
  58. file.writeKey("Loader CRC", self.int2ffhex(self.crc(self.args.stage)))
  59. file.writeKey("Firmware", dfu_basename)
  60. file.writeKey("Radio", radiobin_basename or "")
  61. file.writeKey("Radio address", self.int2ffhex(self.args.radioaddr or 0))
  62. file.writeKey("Radio version", self.int2ffhex(self.args.radioversion or 0))
  63. if radiobin_basename:
  64. file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin)))
  65. else:
  66. file.writeKey("Radio CRC", self.int2ffhex(0))
  67. file.writeKey("Assets", assets_basename)
  68. file.save(join(self.args.directory, "update.fuf"))
  69. return 0
  70. def package_assets(self, srcdir: str, dst_name: str):
  71. with tarfile.open(
  72. dst_name, self.ASSET_TAR_MODE, format=tarfile.USTAR_FORMAT
  73. ) as tarball:
  74. tarball.add(srcdir, arcname="")
  75. @staticmethod
  76. def int2ffhex(value: int):
  77. hexstr = "%08X" % value
  78. return " ".join(list(Main.batch(hexstr, 2))[::-1])
  79. @staticmethod
  80. def crc(fileName):
  81. prev = 0
  82. with open(fileName, "rb") as file:
  83. for eachLine in file:
  84. prev = zlib.crc32(eachLine, prev)
  85. return prev & 0xFFFFFFFF
  86. @staticmethod
  87. def batch(iterable, n=1):
  88. l = len(iterable)
  89. for ndx in range(0, l, n):
  90. yield iterable[ndx : min(ndx + n, l)]
  91. if __name__ == "__main__":
  92. Main()()