Fork of the espurna firmware for `mhsw` switches
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.

295 lines
9.2 KiB

6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. # -------------------------------------------------------------------------------
  4. # ESPurna OTA manager
  5. # xose.perez@gmail.com
  6. #
  7. # Requires PlatformIO Core
  8. # -------------------------------------------------------------------------------
  9. from __future__ import print_function
  10. import argparse
  11. import re
  12. import socket
  13. import subprocess
  14. import sys
  15. from time import sleep
  16. from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
  17. try:
  18. # noinspection PyUnresolvedReferences
  19. input = raw_input # Python2 *!! redefining build-in input.
  20. except NameError:
  21. pass # Python3
  22. # -------------------------------------------------------------------------------
  23. devices = []
  24. description = "ESPurna OTA Manager v0.1"
  25. # -------------------------------------------------------------------------------
  26. def on_service_state_change(zeroconf, service_type, name, state_change):
  27. """
  28. Callback that adds discovered devices to "devices" list
  29. """
  30. if state_change is ServiceStateChange.Added:
  31. info = zeroconf.get_service_info(service_type, name)
  32. if info:
  33. hostname = info.server.split(".")[0]
  34. device = {
  35. 'hostname': hostname.upper(),
  36. 'ip': socket.inet_ntoa(info.address)
  37. }
  38. device['mac'] = info.properties.get('mac', '')
  39. device['app'] = info.properties.get('app_name', '')
  40. device['version'] = info.properties.get('app_version', '')
  41. device['device'] = info.properties.get('target_board', '')
  42. if 'mem_size' in info.properties:
  43. device['mem_size'] = info.properties.get('mem_size')
  44. if 'sdk_size' in info.properties:
  45. device['sdk_size'] = info.properties.get('sdk_size')
  46. if 'free_space' in info.properties:
  47. device['free_space'] = info.properties.get('free_space')
  48. devices.append(device)
  49. def list_devices():
  50. """
  51. Shows the list of discovered devices
  52. """
  53. output_format="{:>3} {:<14} {:<15} {:<17} {:<12} {:<8} {:<25} {:<8} {:<8} {:<10}"
  54. print(output_format.format(
  55. "#",
  56. "HOSTNAME",
  57. "IP",
  58. "MAC",
  59. "APP",
  60. "VERSION",
  61. "DEVICE",
  62. "MEM_SIZE",
  63. "SDK_SIZE",
  64. "FREE_SPACE"
  65. ))
  66. print("-" * 139)
  67. index = 0
  68. for device in devices:
  69. index = index + 1
  70. print(output_format.format(
  71. index,
  72. device.get('hostname', ''),
  73. device.get('ip', ''),
  74. device.get('mac', ''),
  75. device.get('app', ''),
  76. device.get('version', ''),
  77. device.get('device', ''),
  78. device.get('mem_size', ''),
  79. device.get('sdk_size', ''),
  80. device.get('free_space', ''),
  81. ))
  82. print()
  83. def get_boards():
  84. """
  85. Grabs board types fro hardware.h file
  86. """
  87. boards = []
  88. for line in open("espurna/config/hardware.h"):
  89. m = re.search(r'defined\((\w*)\)', line)
  90. if m:
  91. boards.append(m.group(1))
  92. return sorted(boards)
  93. def get_empty_board():
  94. """
  95. Returns the empty structure of a board to flash
  96. """
  97. board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
  98. return board
  99. def get_board_by_index(index):
  100. """
  101. Returns the required data to flash a given board
  102. """
  103. board = {}
  104. if 1 <= index and index <= len(devices):
  105. device = devices[index - 1]
  106. board['hostname'] = device.get('hostname')
  107. board['board'] = device.get('device', '')
  108. board['ip'] = device.get('ip', '')
  109. board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024
  110. return board
  111. def get_board_by_hostname(hostname):
  112. """
  113. Returns the required data to flash a given board
  114. """
  115. hostname = hostname.lower()
  116. for device in devices:
  117. if device.get('hostname', '').lower() == hostname:
  118. board = {}
  119. board['hostname'] = device.get('hostname')
  120. board['board'] = device.get('device')
  121. if not board['board']:
  122. return None
  123. board['ip'] = device.get('ip')
  124. if not board['ip']:
  125. return None
  126. board['size'] = int(device.get('sdk_size', 0)) / 1024
  127. if board['size'] == 0:
  128. return None
  129. return board
  130. return None
  131. def input_board():
  132. """
  133. Grabs info from the user about what device to flash
  134. """
  135. # Choose the board
  136. try:
  137. index = int(input("Choose the board you want to flash (empty if none of these): "))
  138. except ValueError:
  139. index = 0
  140. if index < 0 or len(devices) < index:
  141. print("Board number must be between 1 and %s\n" % str(len(devices)))
  142. return None
  143. board = get_board_by_index(index);
  144. # Choose board type if none before
  145. if len(board.get('board', '')) == 0:
  146. print()
  147. count = 1
  148. boards = get_boards()
  149. for name in boards:
  150. print("%3d\t%s" % (count, name))
  151. count = count + 1
  152. print()
  153. try:
  154. index = int(input("Choose the board type you want to flash: "))
  155. except ValueError:
  156. index = 0
  157. if index < 1 or len(boards) < index:
  158. print("Board number must be between 1 and %s\n" % str(len(boards)))
  159. return None
  160. board['board'] = boards[index - 1]
  161. # Choose board size of none before
  162. if board.get('size', 0) == 0:
  163. try:
  164. board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): "))
  165. except ValueError:
  166. print("Wrong memory size")
  167. return None
  168. # Choose IP of none before
  169. if len(board.get('ip', '')) == 0:
  170. board['ip'] = input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1"
  171. return board
  172. def run(device, env):
  173. print("Building and flashing image over-the-air...")
  174. command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload"
  175. command = command % (device['ip'], device['board'], device['auth'], device['flags'], env)
  176. subprocess.check_call(command, shell=True)
  177. # -------------------------------------------------------------------------------
  178. if __name__ == '__main__':
  179. # Parse command line options
  180. parser = argparse.ArgumentParser(description=description)
  181. parser.add_argument("-c", "--core", help="flash ESPurna core", default=0, action='count')
  182. parser.add_argument("-f", "--flash", help="flash device", default=0, action='count')
  183. parser.add_argument("-o", "--flags", help="extra flags", default='')
  184. parser.add_argument("-p", "--password", help="auth password", default='')
  185. parser.add_argument("-s", "--sort", help="sort devices list by field", default='hostname')
  186. parser.add_argument("-y", "--yes", help="do not ask for confirmation", default=0, action='count')
  187. parser.add_argument("hostnames", nargs='*', help="Hostnames to update")
  188. args = parser.parse_args()
  189. print()
  190. print(description)
  191. print()
  192. # Look for sevices
  193. zeroconf = Zeroconf()
  194. browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
  195. sleep(5)
  196. zeroconf.close()
  197. if len(devices) == 0:
  198. print("Nothing found!\n")
  199. sys.exit(0)
  200. # Sort list
  201. field = args.sort.lower()
  202. if field not in devices[0]:
  203. print("Unknown field '%s'\n" % field)
  204. sys.exit(1)
  205. devices = sorted(devices, key=lambda device: device.get(field, ''))
  206. # List devices
  207. list_devices()
  208. # Flash device
  209. if args.flash > 0:
  210. # Board(s) to flash
  211. queue = []
  212. # Check if hostnames
  213. for hostname in args.hostnames:
  214. board = get_board_by_hostname(hostname)
  215. if board:
  216. board['auth'] = args.password
  217. board['flags'] = args.flags
  218. queue.append(board)
  219. # If no boards ask the user
  220. if len(queue) == 0:
  221. board = input_board()
  222. if board:
  223. board['auth'] = args.password or input("Authorization key of the device to flash: ")
  224. board['flags'] = args.flags or input("Extra flags for the build: ")
  225. queue.append(board)
  226. # If still no boards quit
  227. if len(queue) == 0:
  228. sys.exit(0)
  229. # Flash eash board
  230. for board in queue:
  231. # Flash core version?
  232. if args.core > 0:
  233. board['flags'] = "-DESPURNA_CORE " + board['flags']
  234. env = "esp8266-%sm-ota" % board['size']
  235. # Summary
  236. print()
  237. print("HOST = %s" % board.get('hostname', board['ip']))
  238. print("IP = %s" % board['ip'])
  239. print("BOARD = %s" % board['board'])
  240. print("AUTH = %s" % board['auth'])
  241. print("FLAGS = %s" % board['flags'])
  242. print("ENV = %s" % env)
  243. response = True
  244. if args.yes == 0:
  245. response = (input("\nAre these values right [y/N]: ") == "y")
  246. if response:
  247. print()
  248. run(board, env)