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.

334 lines
10 KiB

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