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.

359 lines
10 KiB

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