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.

293 lines
8.9 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("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 = input("\nAre these values right [y/N]: ")
  244. print()
  245. if response == "y":
  246. run(board, env)