Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

possible to use OTA via PlattformAIO? #183

Closed
pcace opened this issue May 2, 2024 · 2 comments
Closed

possible to use OTA via PlattformAIO? #183

pcace opened this issue May 2, 2024 · 2 comments

Comments

@pcace
Copy link

pcace commented May 2, 2024

Hi,

thanks for that wonderful Library! Is it for convenience possible to use PlattformAIO directly to do OTA updates?
https://docs.platformio.org/en/latest/platforms/espressif32.html#over-the-air-ota-update

It basically uses espota.py

espota.py --help

Usage: espota.py [options]

Transmit image over the air to the esp32 module with OTA support.

Options:
  -h, --help            show this help message and exit

  Destination:
    -i ESP_IP, --ip=ESP_IP
                        ESP32 IP Address.
    -I HOST_IP, --host_ip=HOST_IP
                        Host IP Address.
    -p ESP_PORT, --port=ESP_PORT
                        ESP32 ota Port. Default 3232
    -P HOST_PORT, --host_port=HOST_PORT
                        Host server ota Port. Default random 10000-60000

  Authentication:
    -a AUTH, --auth=AUTH
                        Set authentication password.

  Image:
    -f FILE, --file=FILE
                        Image file.
    -s, --spiffs        Use this option to transmit a SPIFFS image and do not
                        flash the module.

  Output:
    -d, --debug         Show debug output. And override loglevel with debug.
    -r, --progress      Show progress output. Does not work for ArduinoIDE
    -t TIMEOUT, --timeout=TIMEOUT
                        Timeout to wait for the ESP32 to accept invitation

thanks a lot for ideas!!

@mathieucarbou
Copy link

espota.py is for the ArduinoOTA protocol through UDP port 3232.
ElegantOTA is HTTP based.
But there is a way to do it with the python script at the root of the project

[env]
upload_protocol = custom
upload_url = http://192.168.4.1
extra_scripts =
    pio/upload.py
# Allows PlatformIO to upload directly to ElegantOTA
#
# To use:
# - copy this script into the same folder as your platformio.ini
# - set the following for your project in platformio.ini:
#
# extra_scripts = platformio_upload.py
# upload_protocol = custom
# upload_url = <your upload URL>
# 
# An example of an upload URL:
#                upload_url = http://192.168.1.123/update 
# also possible: upload_url = http://domainname/update

import requests
import hashlib
from urllib.parse import urlparse
import time
from requests.auth import HTTPDigestAuth
Import("env")

try:
    from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
    from tqdm import tqdm
except ImportError:
    env.Execute("$PYTHONEXE -m pip install requests_toolbelt")
    env.Execute("$PYTHONEXE -m pip install tqdm")
    from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
    from tqdm import tqdm

def on_upload(source, target, env):
    firmware_path = str(source[0])

    auth = None
    upload_url = env.GetProjectOption('upload_url')

    with open(firmware_path, 'rb') as firmware:
        md5 = hashlib.md5(firmware.read()).hexdigest()

        parsed_url = urlparse(upload_url)
        host_ip = parsed_url.netloc

        # Führe die GET-Anfrage aus
        start_url = f"{upload_url}/ota/start?mode=fr&hash={md5}"

        start_headers = {
            'Host': host_ip,
            'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0',
            'Accept': '*/*',
            'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
            'Accept-Encoding': 'gzip, deflate',
            'Referer': f'{upload_url}/update',
            'Connection': 'keep-alive'
            }
        
        checkAuthResponse = requests.get(f"{upload_url}/update")
        
        if checkAuthResponse.status_code == 401:
            try:
                username = env.GetProjectOption('custom_username')
                password = env.GetProjectOption('custom_password')
            except:
                username = None
                password = None
                print("No authentication values specified.")
                print('Please, add some Options in your .ini file like: \n\ncustom_username=username\ncustom_password=password\n')
            if username is None or password is None:
                print("Authentication required, but no credentials provided.")
                return
            print("Serverconfiguration: authentication needed.")
            auth = HTTPDigestAuth(username, password)
            doUpdateAuth = requests.get(start_url, headers=start_headers, auth=auth)

            if doUpdateAuth.status_code != 200:
                print("authentication faild " + str(doUpdateAuth.status_code))
                return
            print("Authentication successfull")
        else:
            auth = None
            print("Serverconfiguration: autentication not needed.")
            doUpdate = requests.get(start_url, headers=start_headers)

            if doUpdate.status_code != 200:
                print("start-request faild " + str(doUpdate.status_code))
                return

        firmware.seek(0)
        encoder = MultipartEncoder(fields={
            'MD5': md5,
            'firmware': ('firmware', firmware, 'application/octet-stream')}
        )

        bar = tqdm(desc='Upload Progress',
                   total=encoder.len,
                   dynamic_ncols=True,
                   unit='B',
                   unit_scale=True,
                   unit_divisor=1024
                   )

        monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update(monitor.bytes_read - bar.n))

        post_headers = {
            'Host': host_ip,
            'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0',
            'Accept': '*/*',
            'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
            'Accept-Encoding': 'gzip, deflate',
            'Referer': f'{upload_url}/update',
            'Connection': 'keep-alive',
            'Content-Type': monitor.content_type,
            'Content-Length': str(monitor.len),
            'Origin': f'{upload_url}'
        }


        response = requests.post(f"{upload_url}/ota/upload", data=monitor, headers=post_headers, auth=auth)
        
        bar.close()
        time.sleep(0.1)
        
        if response.status_code != 200:
            message = "\nUpload faild.\nServer response: " + response.text
            tqdm.write(message)
        else:
            message = "\nUpload successful.\nServer response: " + response.text
            tqdm.write(message)

            
env.Replace(UPLOADCMD=on_upload)

@pcace
Copy link
Author

pcace commented May 12, 2024

Thanks a lot!!!

@pcace pcace closed this as completed May 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants