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.

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