update.py 4.1 KB

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