From 77045d9bf9090953e456690a691369dedc3e00e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Fri, 5 Jan 2018 21:33:00 +0100 Subject: [PATCH] ESPurna OTA Manager implemented in python --- .gitignore | 2 + code/ota.py | 210 +++++++++++++++++++++++++++++++++++++++ code/ota_flash.sh | 225 ------------------------------------------ code/ota_list.sh | 74 -------------- code/requirements.txt | 7 ++ 5 files changed, 219 insertions(+), 299 deletions(-) create mode 100644 code/ota.py delete mode 100755 code/ota_flash.sh delete mode 100755 code/ota_list.sh create mode 100644 code/requirements.txt diff --git a/.gitignore b/.gitignore index 4a2aa300..3cedb39b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ credentials.h node_modules code/utils custom.h +.python +.env diff --git a/code/ota.py b/code/ota.py new file mode 100644 index 00000000..8e5a4bc6 --- /dev/null +++ b/code/ota.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +#------------------------------------------------------------------------------- +# ESPurna OTA manager +# xose.perez@gmail.com +# +# Requires PlatformIO Core +#------------------------------------------------------------------------------- + +import sys +import re +import logging +import socket +import argparse +import subprocess +from time import sleep +from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf + +#------------------------------------------------------------------------------- + +devices = [] +description = "ESPurna OTA Manager v0.1" + +#------------------------------------------------------------------------------- + +def on_service_state_change(zeroconf, service_type, name, state_change): + ''' + Callback that adds discovered devices to "devices" list + ''' + + if state_change is ServiceStateChange.Added: + info = zeroconf.get_service_info(service_type, name) + if info: + hostname = info.server.split(".")[0] + device = { + 'hostname': hostname.upper(), + 'ip': socket.inet_ntoa(info.address) + } + device['app'] = info.properties.get('app_name', '') + device['version'] = info.properties.get('app_version', '') + device['device'] = info.properties.get('target_board', '') + device['mem_size'] = info.properties.get('mem_size', '') + device['sdk_size'] = info.properties.get('sdk_size', '') + devices.append(device) + +def list(): + ''' + Shows the list of discovered devices + ''' + output_format="{:>3} {:<25}{:<25}{:<15}{:<15}{:<30}{:<10}{:<10}" + print(output_format.format( + "#", + "HOSTNAME", + "IP", + "APP", + "VERSION", + "DEVICE", + "MEM_SIZE", + "SDK_SIZE", + )) + print "-" * 135 + + index = 0 + for device in devices: + index = index + 1 + print(output_format.format( + index, + device.get('hostname', ''), + device.get('ip', ''), + device.get('app', ''), + device.get('version', ''), + device.get('device', ''), + device.get('mem_size', ''), + device.get('sdk_size', ''), + )) + + print + +def get_boards(): + ''' + Grabs board types fro hardware.h file + ''' + boards = [] + for line in open("espurna/config/hardware.h"): + m = re.search(r'defined\((\w*)\)', line) + if m: + boards.append(m.group(1)) + return sorted(boards) + +def flash(): + ''' + Grabs info from the user about what device to flash + ''' + + # Choose the board + try: + index = int(input("Choose the board you want to flash (empty if none of these): ")) + except: + index = 0 + if index < 0 or len(devices) < index: + print "Board number must be between 1 and %s\n" % str(len(devices)) + return None + + board = {'board': '', 'ip': '', 'size': 0 , 'auth': '', 'flags': ''} + + if index > 0: + device = devices[index-1] + board['board'] = device.get('device', '') + board['ip'] = device.get('ip', '') + board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024 + + # Choose board type if none before + if len(board['board']) == 0: + + print + count = 1 + boards = get_boards() + for name in boards: + print "%3d\t%s" % (count, name) + count = count + 1 + print + + try: + index = int(input("Choose the board type you want to flash: ")) + except: + index = 0 + if index < 1 or len(boards) < index: + print "Board number must be between 1 and %s\n" % str(len(boards)) + return None + board['board'] = boards[index-1] + + # Choose board size of none before + if board['size'] == 0: + try: + board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): ")) + except: + print "Wrong memory size" + return None + + # Choose IP of none before + if len(board['ip']) == 0: + try: + board['ip'] = raw_input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1" + except: + print "Wrong IP" + return None + + board['auth'] = raw_input("Authorization key of the device to flash: ") + board['flags'] = raw_input("Extra flags for the build: ") + + return board + +def run(device, env): + command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload" + command = command % (device['ip'], device['board'], device['auth'], device['flags'], env) + subprocess.check_call(command, shell=True) + +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + + # Parse command line options + parser = argparse.ArgumentParser(description=description) + #parser.add_argument("-v", "--verbose", help="show verbose output", default=0, action='count') + parser.add_argument("-f", "--flash", help="flash device", default=0, action='count') + parser.add_argument("-s", "--sort", help="sort devices list by field", default='hostname') + args = parser.parse_args() + + print + print description + print + + # Enable logging if verbose + #logging.basicConfig(level=logging.DEBUG) + #logging.getLogger('zeroconf').setLevel(logging.DEBUG) + + # Look for sevices + zeroconf = Zeroconf() + browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change]) + sleep(1) + zeroconf.close() + + # Sort list + field = args.sort.lower() + if field not in devices[0]: + print "Unknown field '%s'\n" % field + sys.exit(1) + devices = sorted(devices, key=lambda device: device.get(field, '')) + + # List devices + list() + + # Flash device + if args.flash > 0: + device = flash() + if device: + + env = "esp8266-%sm-ota" % device['size'] + + # Summary + print + print "ESPURNA_IP = %s" % device['ip'] + print "ESPURNA_BOARD = %s" % device['board'] + print "ESPURNA_AUTH = %s" % device['auth'] + print "ESPURNA_FLAGS = %s" % device['flags'] + print "ESPURNA_ENV = %s" % env + + response = raw_input("\nAre these values right [y/N]: ") + print + if response == "y": + run(device, env) diff --git a/code/ota_flash.sh b/code/ota_flash.sh deleted file mode 100755 index b217f0a4..00000000 --- a/code/ota_flash.sh +++ /dev/null @@ -1,225 +0,0 @@ -#!/bin/bash - -ip= -board= -size= -auth= -flags= - -export boards=() -ips="" - -exists() { - command -v "$1" >/dev/null 2>&1 -} - -echo_pad() { - string=$1 - pad=$2 - printf '%s' "$string" - printf '%*s' $(( $pad - ${#string} )) -} - -useAvahi() { - - echo_pad "#" 4 - echo_pad "HOSTNAME" 25 - echo_pad "IP" 25 - echo_pad "APP" 15 - echo_pad "VERSION" 15 - echo_pad "DEVICE" 30 - echo_pad "MEM_SIZE" 10 - echo_pad "SDK_SIZE" 10 - echo - - printf -v line '%*s\n' 134 - echo ${line// /-} - - counter=0 - - ip_file="/tmp/espurna.flash.ips" - board_file="/tmp/espurna.flash.boards" - count_file="/tmp/espurna.flash.count" - size_file="/tmp/espurna.flash.size" - echo -n "" > $ip_file - echo -n "" > $board_file - echo -n "" > $size_file - echo -n "$counter" > $count_file - - avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | sort -t ';' -k 3 | while read line; do - - (( counter++ )) - echo "$counter" > $count_file - - hostname=`echo $line | cut -d ';' -f4` - ip=`echo $line | cut -d ';' -f8` - txt=`echo $line | cut -d ';' -f10` - app_name=`echo $txt | sed -n "s/.*app_name=\([^\"]*\).*/\1/p"` - app_version=`echo $txt | sed -n "s/.*app_version=\([^\"]*\).*/\1/p"` - board=`echo $txt | sed -n "s/.*target_board=\([^\"]*\).*/\1/p"` - mem_size=`echo $txt | sed -n "s/.*mem_size=\([^\"]*\).*/\1/p"` - sdk_size=`echo $txt | sed -n "s/.*sdk_size=\([^\"]*\).*/\1/p"` - - echo_pad "$counter" 4 - echo_pad "$hostname" 25 - echo_pad "http://$ip" 25 - echo_pad "$app_name" 15 - echo_pad "$app_version" 15 - echo_pad "$board" 30 - echo_pad "$mem_size" 10 - echo_pad "$sdk_size" 10 - echo - - echo -n "$ip;" >> $ip_file - echo -n "$board;" >> $board_file - if [ "$mem_size" == "$sdk_size" ]; then - mem_size=`echo $mem_size | head -c 1` - echo -n "$mem_size;" >> $size_file - else - echo -n ";" >> $size_file - fi - - done - - echo - read -p "Choose the board you want to flash (empty if none of these): " num - - # None of these - if [ "$num" == "" ]; then - return - fi - - # Check boundaries - counter=`cat $count_file` - if [ $num -lt 1 ] || [ $num -gt $counter ]; then - echo "Board number must be between 1 and $counter" - exit 1 - fi - - # Fill the fields - ip=`cat $ip_file | cut -d ';' -f$num` - board=`cat $board_file | cut -d ';' -f$num` - size=`cat $size_file | cut -d ';' -f$num` - -} - -getBoard() { - - boards=(`cat espurna/config/hardware.h | grep "defined" | sed "s/.*(\(.*\)).*/\1/" | sort`) - - echo_pad "#" 4 - echo_pad "DEVICE" 30 - echo - - printf -v line '%*s\n' 34 - echo ${line// /-} - - counter=0 - for board in "${boards[@]}"; do - (( counter++ )) - echo_pad "$counter" 4 - echo_pad "$board" 30 - echo - done - - echo - read -p "Choose the board you want to flash (empty if none of these): " num - - # None of these - if [ "$num" == "" ]; then - return - fi - - # Check boundaries - counter=${#boards[*]} - if [ $num -lt 1 ] || [ $num -gt $counter ]; then - echo "Board code must be between 1 and $counter" - exit 1 - fi - - # Fill the fields - (( num -- )) - board=${boards[$num]} - -} - -# ------------------------------------------------------------------------------ - -# Welcome -echo -echo "--------------------------------------------------------------" -echo "ESPURNA FIRMWARE OTA FLASHER" - -# Get current version -version=`cat espurna/config/version.h | grep APP_VERSION | awk '{print $3}' | sed 's/"//g'` -echo "Building for version $version" - -echo "--------------------------------------------------------------" -echo - -if exists avahi-browse; then - useAvahi -fi - -if [ "$board" == "" ]; then - getBoard -fi - -if [ "$board" == "" ]; then - read -p "Board type of the device to flash: " -e -i "NODEMCU_LOLIN" board -fi - -if [ "$board" == "" ]; then - echo "You must define the board type" - exit 2 -fi - -if [ "$size" == "" ]; then - read -p "Board memory size (1 for 1M, 4 for 4M): " -e size -fi - -if [ "$size" == "" ]; then - echo "You must define the board memory size" - exit 2 -fi - -if [ "$ip" == "" ]; then - read -p "IP of the device to flash: " -e -i 192.168.4.1 ip -fi - -if [ "$ip" == "" ]; then - echo "You must define the IP of the device" - exit 2 -fi - -if [ "$auth" == "" ]; then - read -p "Authorization key of the device to flash: " auth -fi - -if [ "$flags" == "" ]; then - read -p "Extra flags for the build: " -e -i "" flags -fi - -env="esp8266-${size}m-ota" - -echo -echo "ESPURNA_IP = $ip" -echo "ESPURNA_BOARD = $board" -echo "ESPURNA_AUTH = $auth" -echo "ESPURNA_FLAGS = $flags" -echo "ESPURNA_ENV = $env" - -echo -echo -n "Are these values corrent [y/N]: " -read response - -if [ "$response" != "y" ]; then - exit -fi - -export ESPURNA_IP=$ip -export ESPURNA_BOARD=$board -export ESPURNA_AUTH=$auth -export ESPURNA_FLAGS=$flags - -platformio run --silent --environment $env -t upload diff --git a/code/ota_list.sh b/code/ota_list.sh deleted file mode 100755 index 6a988db9..00000000 --- a/code/ota_list.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash - -exists() { - command -v "$1" >/dev/null 2>&1 -} - -echo_pad() { - string=$1 - pad=$2 - printf '%s' "$string" - printf '%*s' $(( $pad - ${#string} )) -} - -useAvahi() { - - echo_pad "#" 4 - echo_pad "HOSTNAME" 25 - echo_pad "IP" 25 - echo_pad "APP" 15 - echo_pad "VERSION" 15 - echo_pad "DEVICE" 30 - echo_pad "MEM_SIZE" 10 - echo_pad "SDK_SIZE" 10 - echo - - printf -v line '%*s\n' 134 - echo ${line// /-} - - counter=0 - - avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | sort -t ';' -k 3 | while read line; do - - (( counter++ )) - - hostname=`echo $line | cut -d ';' -f4` - ip=`echo $line | cut -d ';' -f8` - txt=`echo $line | cut -d ';' -f10` - app_name=`echo $txt | sed -n "s/.*app_name=\([^\"]*\).*/\1/p"` - app_version=`echo $txt | sed -n "s/.*app_version=\([^\"]*\).*/\1/p"` - board=`echo $txt | sed -n "s/.*target_board=\([^\"]*\).*/\1/p"` - mem_size=`echo $txt | sed -n "s/.*mem_size=\([^\"]*\).*/\1/p"` - sdk_size=`echo $txt | sed -n "s/.*sdk_size=\([^\"]*\).*/\1/p"` - - echo_pad "$counter" 4 - echo_pad "$hostname" 25 - echo_pad "http://$ip" 25 - echo_pad "$app_name" 15 - echo_pad "$app_version" 15 - echo_pad "$board" 30 - echo_pad "$mem_size" 10 - echo_pad "$sdk_size" 10 - echo - - done - - echo - -} - -# ------------------------------------------------------------------------------ - -# Welcome -echo -echo "--------------------------------------------------------------" -echo "OTA-UPDATABLE DEVICES" -echo "--------------------------------------------------------------" -echo - -if exists avahi-browse; then - useAvahi -else - echo "Avahi not installed" - exit 1 -fi diff --git a/code/requirements.txt b/code/requirements.txt new file mode 100644 index 00000000..f8acf8d1 --- /dev/null +++ b/code/requirements.txt @@ -0,0 +1,7 @@ +enum-compat==0.0.2 +enum34==1.1.6 +netifaces==0.10.6 +ordereddict==1.1 +six==1.11.0 +sortedcontainers==1.5.9 +zeroconf==0.19.1