Mirror of espurna firmware for wireless switches and more
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.

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