1
0
mirror of https://github.com/revspace/operame synced 2024-10-31 21:47:30 +00:00

OTA documentation

This commit is contained in:
bmellink 2021-01-06 21:46:55 +01:00
parent 44f018e3b0
commit 9eeff596ae
4 changed files with 384 additions and 0 deletions

BIN
OTA/Discovery_tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

31
OTA/README.md Normal file
View File

@ -0,0 +1,31 @@
# Uitleg hoe je via de commandline de firmware kunt uploaden via OTA
OTA = Over The Air - ofwel: via WiFi de firmware updaten
De onderstaande procedure werkt voor MacOS. Voor Windows zou het ook kunnen werken met kleine aanpassingen.
Stappen:
1. Stel via de web interface van operame OTA in (checkbox "Draadloos herprogrammeren inschakelen.").
2. Kies een portaal wachtwoord (xxxxxx in het voorbeeld hieronder).
3. Na een reboot zal operame een IP adres toegewezen kregen en zichzelf bekend maken op het lokale netwerk via mDNS. Op de Mac kan je met de Discovery tool (te downloaden van de App store via https://apps.apple.com/us/app/discovery-dns-sd-browser/id1381004916) het IP adres en bijbehorende port nummer vinden. Zie hieronder voor een screen shot (onder de tag ```_arduino._tcp```). Noteer het IP adres en port nummer (in dit geval ```192.168.22.228``` en ```3232```).
![Discovery Tool](Discovery_tool.png?raw=true "Discovery tool output")
4. Download de bestanden ```espota.py``` en ```firmware_mqtt.bin``` naar een folder.
5. Start terminal (command line tool) en navigeer naar de bovenstaande folder.
6. Start upload met het commando
````
python espota.py -i 192.168.22.228 -p 3232 --auth=xxxxxx -f ./firmware_mqtt.bin
````
Waarbij 192.168.22.228 vervangen wordt door het IP adres van jouw operame device en 3232 het port nummer (3232 is de default en is waarschijnlijk altijd hetzelfde).
````
Sending invitation to 192.168.22.228
Authenticating...OK
Uploading.........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
````

353
OTA/espota.py Normal file
View File

@ -0,0 +1,353 @@
#!/usr/bin/env python
#
# Original espota.py by Ivan Grokhotkov:
# https://gist.github.com/igrr/d35ab8446922179dc58c
#
# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor)
# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev)
# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman)
#
# This script will push an OTA update to the ESP
# use it like: python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <Host_port> [-a password] -f <sketch.bin>
# Or to upload SPIFFS image:
# python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <HOST_port> [-a password] -s -f <spiffs.bin>
#
# Changes
# 2015-09-18:
# - Add option parser.
# - Add logging.
# - Send command to controller to differ between flashing and transmitting SPIFFS image.
#
# Changes
# 2015-11-09:
# - Added digest authentication
# - Enhanced error tracking and reporting
#
# Changes
# 2016-01-03:
# - Added more options to parser.
#
from __future__ import print_function
import socket
import sys
import os
import optparse
import logging
import hashlib
import random
# Commands
FLASH = 0
SPIFFS = 100
AUTH = 200
PROGRESS = False
# update_progress() : Displays or updates a console progress bar
## Accepts a float between 0 and 1. Any int will be converted to a float.
## A value under 0 represents a 'halt'.
## A value at 1 or bigger represents 100%
def update_progress(progress):
if (PROGRESS):
barLength = 60 # Modify this to change the length of the progress bar
status = ""
if isinstance(progress, int):
progress = float(progress)
if not isinstance(progress, float):
progress = 0
status = "error: progress var must be float\r\n"
if progress < 0:
progress = 0
status = "Halt...\r\n"
if progress >= 1:
progress = 1
status = "Done...\r\n"
block = int(round(barLength*progress))
text = "\rUploading: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), int(progress*100), status)
sys.stderr.write(text)
sys.stderr.flush()
else:
sys.stderr.write('.')
sys.stderr.flush()
def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH):
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (localAddr, localPort)
logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1]))
try:
sock.bind(server_address)
sock.listen(1)
except:
logging.error("Listen Failed")
return 1
content_size = os.path.getsize(filename)
f = open(filename,'rb')
file_md5 = hashlib.md5(f.read()).hexdigest()
f.close()
logging.info('Upload size: %d', content_size)
message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5)
# Wait for a connection
inv_trys = 0
data = ''
msg = 'Sending invitation to %s ' % (remoteAddr)
sys.stderr.write(msg)
sys.stderr.flush()
while (inv_trys < 10):
inv_trys += 1
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
remote_address = (remoteAddr, int(remotePort))
try:
sent = sock2.sendto(message.encode(), remote_address)
except:
sys.stderr.write('failed\n')
sys.stderr.flush()
sock2.close()
logging.error('Host %s Not Found', remoteAddr)
return 1
sock2.settimeout(TIMEOUT)
try:
data = sock2.recv(37).decode()
break;
except:
sys.stderr.write('.')
sys.stderr.flush()
sock2.close()
sys.stderr.write('\n')
sys.stderr.flush()
if (inv_trys == 10):
logging.error('No response from the ESP')
return 1
if (data != "OK"):
if(data.startswith('AUTH')):
nonce = data.split()[1]
cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr)
cnonce = hashlib.md5(cnonce_text.encode()).hexdigest()
passmd5 = hashlib.md5(password.encode()).hexdigest()
result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce)
result = hashlib.md5(result_text.encode()).hexdigest()
sys.stderr.write('Authenticating...')
sys.stderr.flush()
message = '%d %s %s\n' % (AUTH, cnonce, result)
sock2.sendto(message.encode(), remote_address)
sock2.settimeout(10)
try:
data = sock2.recv(32).decode()
except:
sys.stderr.write('FAIL\n')
logging.error('No Answer to our Authentication')
sock2.close()
return 1
if (data != "OK"):
sys.stderr.write('FAIL\n')
logging.error('%s', data)
sock2.close()
sys.exit(1);
return 1
sys.stderr.write('OK\n')
else:
logging.error('Bad Answer: %s', data)
sock2.close()
return 1
sock2.close()
logging.info('Waiting for device...')
try:
sock.settimeout(10)
connection, client_address = sock.accept()
sock.settimeout(None)
connection.settimeout(None)
except:
logging.error('No response from device')
sock.close()
return 1
try:
f = open(filename, "rb")
if (PROGRESS):
update_progress(0)
else:
sys.stderr.write('Uploading')
sys.stderr.flush()
offset = 0
while True:
chunk = f.read(1024)
if not chunk: break
offset += len(chunk)
update_progress(offset/float(content_size))
connection.settimeout(10)
try:
connection.sendall(chunk)
res = connection.recv(10)
lastResponseContainedOK = 'OK' in res.decode()
except:
sys.stderr.write('\n')
logging.error('Error Uploading')
connection.close()
f.close()
sock.close()
return 1
if lastResponseContainedOK:
logging.info('Success')
connection.close()
f.close()
sock.close()
return 0
sys.stderr.write('\n')
logging.info('Waiting for result...')
try:
count = 0
while True:
count=count+1
connection.settimeout(60)
data = connection.recv(32).decode()
logging.info('Result: %s' ,data)
if "OK" in data:
logging.info('Success')
connection.close()
f.close()
sock.close()
return 0;
if count == 5:
logging.error('Error response from device')
connection.close()
f.close()
sock.close()
return 1
except e:
logging.error('No Result!')
connection.close()
f.close()
sock.close()
return 1
finally:
connection.close()
f.close()
sock.close()
return 1
# end serve
def parser(unparsed_args):
parser = optparse.OptionParser(
usage = "%prog [options]",
description = "Transmit image over the air to the esp32 module with OTA support."
)
# destination ip and port
group = optparse.OptionGroup(parser, "Destination")
group.add_option("-i", "--ip",
dest = "esp_ip",
action = "store",
help = "ESP32 IP Address.",
default = False
)
group.add_option("-I", "--host_ip",
dest = "host_ip",
action = "store",
help = "Host IP Address.",
default = "0.0.0.0"
)
group.add_option("-p", "--port",
dest = "esp_port",
type = "int",
help = "ESP32 ota Port. Default 3232",
default = 3232
)
group.add_option("-P", "--host_port",
dest = "host_port",
type = "int",
help = "Host server ota Port. Default random 10000-60000",
default = random.randint(10000,60000)
)
parser.add_option_group(group)
# auth
group = optparse.OptionGroup(parser, "Authentication")
group.add_option("-a", "--auth",
dest = "auth",
help = "Set authentication password.",
action = "store",
default = ""
)
parser.add_option_group(group)
# image
group = optparse.OptionGroup(parser, "Image")
group.add_option("-f", "--file",
dest = "image",
help = "Image file.",
metavar="FILE",
default = None
)
group.add_option("-s", "--spiffs",
dest = "spiffs",
action = "store_true",
help = "Use this option to transmit a SPIFFS image and do not flash the module.",
default = False
)
parser.add_option_group(group)
# output group
group = optparse.OptionGroup(parser, "Output")
group.add_option("-d", "--debug",
dest = "debug",
help = "Show debug output. And override loglevel with debug.",
action = "store_true",
default = False
)
group.add_option("-r", "--progress",
dest = "progress",
help = "Show progress output. Does not work for ArduinoIDE",
action = "store_true",
default = False
)
group.add_option("-t", "--timeout",
dest = "timeout",
type = "int",
help = "Timeout to wait for the ESP32 to accept invitation",
default = 10
)
parser.add_option_group(group)
(options, args) = parser.parse_args(unparsed_args)
return options
# end parser
def main(args):
options = parser(args)
loglevel = logging.WARNING
if (options.debug):
loglevel = logging.DEBUG
logging.basicConfig(level = loglevel, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S')
logging.debug("Options: %s", str(options))
# check options
global PROGRESS
PROGRESS = options.progress
global TIMEOUT
TIMEOUT = options.timeout
if (not options.esp_ip or not options.image):
logging.critical("Not enough arguments.")
return 1
command = FLASH
if (options.spiffs):
command = SPIFFS
return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command)
# end main
if __name__ == '__main__':
sys.exit(main(sys.argv))

BIN
OTA/firmware_mqtt.bin Normal file

Binary file not shown.