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.

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