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.

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