mirror of
https://github.com/revspace/operame
synced 2024-10-31 21:47:30 +00:00
OTA docs
This commit is contained in:
parent
8084a7b6b4
commit
63a6b2d354
BIN
OTA/Discovery_tool.png
Normal file
BIN
OTA/Discovery_tool.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
57
OTA/README.md
Normal file
57
OTA/README.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Aanpassing firmware voor mqtt server met authenticatie
|
||||||
|
|
||||||
|
## 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 krijgen en zichzelf bekend maken op het lokale netwerk via mDNS. Op de Mac kan je met de Discovery tool (te downloaden vanuit 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). xxxxxx moet je vervangen door het in stap 2 ingestelde wachtwoord.
|
||||||
|
|
||||||
|
Je zult op de ```operame``` het uploaden zien (met percentage oplopend van 0..100%). Output op de terminal:
|
||||||
|
````
|
||||||
|
$ python espota.py -i 192.168.22.228 -p 3232 --auth=xxxxxx -f ./firmware_mqtt.bin
|
||||||
|
Sending invitation to 192.168.22.228
|
||||||
|
Authenticating...OK
|
||||||
|
Uploading.........................................................................................................
|
||||||
|
..................................................................................................................
|
||||||
|
..................................................................................................................
|
||||||
|
..................................................................................................................
|
||||||
|
.....................................................................................................
|
||||||
|
$
|
||||||
|
````
|
||||||
|
|
||||||
|
## Gebruik maken van Adafruit IoT server voor het weergeven van CO2 waardes
|
||||||
|
|
||||||
|
Via https://accounts.adafruit.com/ kun je een gratis account aanmaken om vervolgens via https://io.adafruit.com een dashboard van maximaal 5 operame sensors te tonen (als je er meer hebt heb je een betaald account nodig).
|
||||||
|
|
||||||
|
Voorbeeld van mijn data: https://io.adafruit.com/bart59/dashboards/co2
|
||||||
|
Binnen adafruit definieer je een dashboard en een feed voor elke operame sensor.
|
||||||
|
|
||||||
|
Instellingen die je moet doen via de ```operame``` web interface:
|
||||||
|
|
||||||
|
````
|
||||||
|
Enable Mqtt: check checkbox
|
||||||
|
Mqtt Host: io.adafruit.com
|
||||||
|
Mqtt Port: 1883
|
||||||
|
Mqtt Username: Your Adafruit IO Username
|
||||||
|
Mqtt Password: Your Adafruit IO Key
|
||||||
|
Mqtt topic: <adafruit username>/feeds/<feed name> (in mijn geval: bart59/feeds/CO2_home)
|
||||||
|
````
|
||||||
|
|
353
OTA/espota.py
Normal file
353
OTA/espota.py
Normal 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
BIN
OTA/firmware_mqtt.bin
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user