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.

313 lines
9.7 KiB

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