This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/OTA/1.0.2/flash/config.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
WIFI_SSID = "ENTER_ME"
WIFI_PW = "ENTER_ME"
SERVER_HOST = "ENTER_ME"
4 changes: 4 additions & 0 deletions examples/OTA/1.0.2/flash/get_id.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
from network import LoRa
import binascii
lora = LoRa(mode=LoRa.LORAWAN)
print(binascii.hexlify(lora.mac()).upper().decode('utf-8'))
256 changes: 256 additions & 0 deletions examples/OTA/1.0.2/flash/lib/OTA.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
import network
import socket
import ssl
import machine
import ujson
import uhashlib
import ubinascii
import gc
import pycom
import os
import machine

# Try to get version number
try:
from OTA_VERSION import VERSION
except ImportError:
VERSION = '1.0.0'


class OTA():
# The following two methods need to be implemented in a subclass for the
# specific transport mechanism e.g. WiFi

def connect(self):
raise NotImplementedError()

def get_data(self, req, dest_path=None, hash=False):
raise NotImplementedError()

# OTA methods

def get_current_version(self):
return VERSION

def get_update_manifest(self):
req = "manifest.json?current_ver={}".format(self.get_current_version())
manifest_data = self.get_data(req).decode()
manifest = ujson.loads(manifest_data)
gc.collect()
return manifest

def update(self):
manifest = self.get_update_manifest()
if manifest is None:
print("Already on the latest version")
return

# Download new files and verify hashes
for f in manifest['new'] + manifest['update']:
# Upto 5 retries
for _ in range(5):
try:
self.get_file(f)
break
except Exception as e:
print(e)
print("Error downloading `{}` retrying...".format(f['URL']))
else:
raise Exception("Failed to download `{}`".format(f['URL']))

# Backup old files
# only once all files have been successfully downloaded
for f in manifest['update']:
self.backup_file(f)

# Rename new files to proper name
for f in manifest['new'] + manifest['update']:
new_path = "{}.new".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

os.rename(new_path, dest_path)

# `Delete` files no longer required
# This actually makes a backup of the files incase we need to roll back
for f in manifest['delete']:
self.delete_file(f)

# Flash firmware
if "firmware" in manifest:
self.write_firmware(manifest['firmware'])

# Save version number
try:
self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
except OSError:
pass # There isnt a previous file to backup
with open("/flash/OTA_VERSION.py", 'w') as fp:
fp.write("VERSION = '{}'".format(manifest['version']))
from OTA_VERSION import VERSION

# Reboot the device to run the new decode
machine.reset()

def get_file(self, f):
new_path = "{}.new".format(f['dst_path'])

# If a .new file exists from a previously failed update delete it
try:
os.remove(new_path)
except OSError:
pass # The file didnt exist

# Download new file with a .new extension to not overwrite the existing
# file until the hash is verified.
hash = self.get_data(f['URL'].split("/", 3)[-1],
dest_path=new_path,
hash=True)

# Hash mismatch
if hash != f['hash']:
print(hash, f['hash'])
msg = "Downloaded file's hash does not match expected hash"
raise Exception(msg)

def backup_file(self, f):
bak_path = "{}.bak".format(f['dst_path'])
dest_path = "{}".format(f['dst_path'])

# Delete previous backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous backup

# Backup current file
os.rename(dest_path, bak_path)

def delete_file(self, f):
bak_path = "/{}.bak_del".format(f)
dest_path = "/{}".format(f)

# Delete previous delete backup if it exists
try:
os.remove(bak_path)
except OSError:
pass # There isnt a previous delete backup

# Backup current file
os.rename(dest_path, bak_path)

def write_firmware(self, f):
hash = self.get_data(f['URL'].split("/", 3)[-1],
hash=True,
firmware=True)
# TODO: Add verification when released in future firmware


class WiFiOTA(OTA):
def __init__(self, ssid_name, ssid_password, host, port=443):
self.ssid_name = ssid_name
self.ssid_password = ssid_password
self.host = host
self.port = port

def connect(self):
self.wlan = network.WLAN(mode=network.WLAN.STA)
if not self.wlan.isconnected() or self.wlan.ssid() != self.ssid_name:
for net in self.wlan.scan():
if net.ssid == self.ssid_name:
self.wlan.connect(self.ssid_name, auth=(network.WLAN.WPA2,
self.ssid_password))
while not self.wlan.isconnected():
machine.idle() # save power while waiting
break
else:
raise Exception("Cannot find network '{}'".format(SSID))
else:
# Already connected to the correct WiFi
pass

def _http_get(self, path, host):
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
req = bytes(req_fmt.format(path, host), 'utf8')
return req

def get_socket(self, host, port=80):
ai = socket.getaddrinfo(host, port)
addr = ai[0][4]
s = socket.socket()
s.connect(addr)

if port in (443, 8443):
s = ssl.wrap_socket(s)

return s

def get_data(self, req, dest_path=None, hash=False, firmware=False):
# Connect to server
print("Requesting: {}".format(req))

# open a new socket
s = self.get_socket(self.host, self.port)

# Request File
s.sendall(self._http_get(req, "{}:{}".format(self.host, self.port)))

try:
content = bytearray()
fp = None
if dest_path is not None:
if firmware:
raise Exception("Cannot write firmware to a file")
fp = open(dest_path, 'wb')

if firmware:
pycom.ota_start()

h = uhashlib.sha1()

# Get data from server
result = s.recv(100)

start_writing = False
while (len(result) > 0):
# Ignore the HTTP headers
if not start_writing:
if "\r\n\r\n" in result:
start_writing = True
result = result.decode().split("\r\n\r\n")[1].encode()

if start_writing:
if firmware:
pycom.ota_write(result)
elif fp is None:
content.extend(result)
else:
fp.write(result)

if hash:
h.update(result)

result = s.recv(100)

s.close()

if fp is not None:
fp.close()
if firmware:
pycom.ota_finish()

except Exception as e:
# Since only one hash operation is allowed at Once
# ensure we close it if there is an error
if h is not None:
h.digest()
raise e

hash_val = ubinascii.hexlify(h.digest()).decode()

if dest_path is None:
if hash:
return (bytes(content), hash_val)
else:
return bytes(content)
elif hash:
return hash_val
67 changes: 67 additions & 0 deletions examples/OTA/1.0.2/flash/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
from network import LoRa, WLAN
import socket
import time
from OTA import WiFiOTA
from time import sleep
import pycom
import binascii

from config import WIFI_SSID, WIFI_PW, SERVER_HOST

# Turn on GREEN LED
pycom.heartbeat(False)
pycom.rgbled(0xff)

# Setup OTA
ota = WiFiOTA(WIFI_SSID,
WIFI_PW,
SERVER_HOST, # Update server address
8000) # Update server port

# Turn off WiFi to save power
w = WLAN()
w.deinit()

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)

app_eui = binascii.unhexlify('ENTER_ME')
app_key = binascii.unhexlify('ENTER_ME')

# join a network using OTAA (Over the Air Activation)
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
time.sleep(2.5)
print('Not yet joined...')

# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

while True:
# send some data
s.send(bytes([0x04, 0x05, 0x06]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)

# Some sort of OTA trigger
if data == bytes([0x01, 0x02, 0x03]):
print("Performing OTA")
# Perform OTA
ota.connect()
ota.update()

sleep(5)