Skip to content

Commit

Permalink
Merge pull request platformio#13 from maxgerhardt/ota
Browse files Browse the repository at this point in the history
OTA v1
  • Loading branch information
maxgerhardt authored Aug 21, 2022
2 parents 5bb8311 + 47726da commit 2ace1c2
Show file tree
Hide file tree
Showing 19 changed files with 637 additions and 6 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
example:
- "examples/arduino-blink"
- "examples/arduino-wifi-scan"
- "examples/arduino-ota"
- "examples/arduino-signed-ota"
- "examples/arduino-external-libs"
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -25,6 +27,20 @@ jobs:
run: |
pip install -U https://github.com/platformio/platformio/archive/develop.zip
pio pkg install --global --platform symlink://.
# OpenSSL needed for signed OTA update example
- name: Install OpenSSL
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt-get install -y openssl
elif [ "$RUNNER_OS" == "Windows" ]; then
choco install openssl
elif [ "$RUNNER_OS" == "macOS" ]; then
brew install openssl
else
echo "$RUNNER_OS not supported"
exit 1
fi
shell: bash
- name: Build examples
run: |
pio run -d ${{ matrix.example }}
123 changes: 118 additions & 5 deletions builder/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import sys
from platform import system
from os import makedirs
from os import makedirs, remove
from os.path import isdir, join, isfile
import re
import time
Expand Down Expand Up @@ -184,6 +184,22 @@ def generate_uf2(target, source, env):
"$TARGET"
]), "Building $TARGET"),
suffix=".hex"
),
BinToSignedBin=Builder(
action=env.VerboseAction(" ".join([
'"$PYTHONEXE" "%s"' % join(
platform.get_package_dir("framework-arduinopico") or "",
"tools", "signing.py"),
"--mode",
"sign",
"--privatekey",
'"%s"' % join("$PROJECT_SRC_DIR", "private.key"),
"--bin",
"$SOURCES",
"--out",
"$TARGET"
]), "Building $TARGET"),
suffix=".bin.signed"
)
)
)
Expand All @@ -209,14 +225,30 @@ def generate_uf2(target, source, env):
)
)

is_arduino_pico_build = env.BoardConfig().get("build.core", "arduino") == "earlephilhower" and "arduino" in env.get("PIOFRAMEWORK")
if is_arduino_pico_build:
pubkey = join(env.subst("$PROJECT_SRC_DIR"), "public.key")
if isfile(pubkey):
header_file = join(env.subst("$BUILD_DIR"), "core", "Updater_Signing.h")
env.Prepend(CCFLAGS=['-I"%s"' % join("$BUILD_DIR", "core")])
env.Execute(" ".join([
'"$PYTHONEXE" "%s"' % join(
platform.get_package_dir("framework-arduinopico"), "tools", "signing.py"),
"--mode", "header",
"--publickey", '"%s"' % join("$PROJECT_SRC_DIR", "public.key"),
"--out", '"%s"' % join("$BUILD_DIR", "core", "Updater_Signing.h")
]))

#
# Target: Build executable and linkable firmware
#

target_elf = None
target_signed_bin = None
if "nobuild" in COMMAND_LINE_TARGETS:
target_elf = join("$BUILD_DIR", "${PROGNAME}.elf")
target_firm = join("$BUILD_DIR", "${PROGNAME}.bin")
target_firm = join("$BUILD_DIR", "${PROGNAME}.bin.signed")
else:
target_elf = env.BuildProgram()
if set(["buildfs", "uploadfs"]) & set(COMMAND_LINE_TARGETS):
Expand All @@ -225,11 +257,14 @@ def generate_uf2(target, source, env):
AlwaysBuild(target_firm)
else:
target_firm = env.ElfToBin(join("$BUILD_DIR", "${PROGNAME}"), target_elf)
if is_arduino_pico_build:
target_signed_bin = env.BinToSignedBin(join("$BUILD_DIR", "${PROGNAME}"), target_firm)
env.Depends(target_signed_bin, "checkprogsize")
env.Depends(target_firm, "checkprogsize")

env.AddPlatformTarget("buildfs", target_firm, target_firm, "Build Filesystem Image")
AlwaysBuild(env.Alias("nobuild", target_firm))
target_buildprog = env.Alias("buildprog", target_firm, target_firm)
target_buildprog = env.Alias("buildprog", [target_firm, target_signed_bin], target_firm)

env.AddPostAction(
target_elf, env.VerboseAction(generate_uf2, "Generating UF2 image")
Expand Down Expand Up @@ -284,14 +319,93 @@ def UploadUF2ToDisk(target, source, env):
copyfile(fpath, join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext)))
print(
"Firmware has been successfully uploaded.\n"
"(Some boards may require manual hard reset)"
)

def TryResetPico(target, source, env):
upload_options = {}
if "BOARD" in env:
upload_options = env.BoardConfig().get("upload", {})
ports = list_serial_ports()
if len(ports) != 0:
last_port = ports[-1]["port"]
if upload_options.get("use_1200bps_touch", False):
env.TouchSerialPort(last_port, 1200)
time.sleep(2.0)

from platformio.device.list.util import list_logical_devices
from platformio.device.finder import is_pattern_port
from fnmatch import fnmatch

def find_rpi_disk(initial_port):
msdlabels = ("RPI-RP2")
item:str
for item in list_logical_devices():
if item["path"].startswith("/net"):
continue
if (
initial_port
and is_pattern_port(initial_port)
and not fnmatch(item["path"], initial_port)
):
continue
mbed_pages = [join(item["path"], n) for n in ("INDEX.HTM", "INFO_UF2.TXT")]
if any(isfile(p) for p in mbed_pages):
return item["path"]
if item["name"] and any(l in item["name"].lower() for l in msdlabels):
return item["path"]
return None

def AutodetectPicoDisk(target, source, env):
initial_port = env.subst("$UPLOAD_PORT")
if initial_port and not is_pattern_port(initial_port):
print(env.subst("Using manually specified: $UPLOAD_PORT"))
return

if upload_protocol == "mbed":
env.Replace(UPLOAD_PORT=find_rpi_disk(initial_port))

if env.subst("$UPLOAD_PORT"):
print(env.subst("Auto-detected: $UPLOAD_PORT"))
else:
sys.stderr.write(
"Error: Please specify `upload_port` for environment or use "
"global `--upload-port` option.\n"
"For some development platforms it can be a USB flash "
"drive (i.e. /media/<user>/<device name>)\n"
)
env.Exit(1)

if upload_protocol == "mbed":
upload_actions = [
env.VerboseAction(env.AutodetectUploadPort, "Looking for upload disk..."),
env.VerboseAction(TryResetPico, "Trying to reset Pico into bootloader mode..."),
env.VerboseAction(AutodetectPicoDisk, "Looking for upload disk..."),
env.VerboseAction(UploadUF2ToDisk, "Uploading $SOURCE")
]
elif upload_protocol == "espota":
if not env.subst("$UPLOAD_PORT"):
sys.stderr.write(
"Error: Please specify IP address or host name of ESP device "
"using `upload_port` for build environment or use "
"global `--upload-port` option.\n"
"See https://docs.platformio.org/page/platforms/"
"espressif8266.html#over-the-air-ota-update\n")
env.Replace(
UPLOADER=join(
platform.get_package_dir("framework-arduinopico") or "",
"tools", "espota.py"),
UPLOADERFLAGS=["--debug", "--progress", "-i", "$UPLOAD_PORT", "-p", "2040"],
UPLOADCMD='"$PYTHONEXE" "$UPLOADER" $UPLOADERFLAGS -f $SOURCE'
)
if "uploadfs" in COMMAND_LINE_TARGETS:
env.Append(UPLOADERFLAGS=["-s"])
else:
# check if we have a .bin.signed file available.
# since the file may not be build yet, we try to predict that we will
# have that file if they private signing key exists.
if isfile(join(env.subst("$PROJECT_SRC_DIR"), "private.key")):
sys.stdout.write("Using signed OTA update file.")
upload_source = target_signed_bin
upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")]
elif upload_protocol == "picotool":
env.Replace(
UPLOADER=join(platform.get_package_dir("tool-rp2040tools") or "", "rp2040load"),
Expand Down Expand Up @@ -405,5 +519,4 @@ def _jlink_cmd_script(env, source):
#
# Default targets
#

Default([target_buildprog, target_size])
1 change: 1 addition & 0 deletions examples/arduino-ota/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.pio
33 changes: 33 additions & 0 deletions examples/arduino-ota/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
How to build PlatformIO based project
=====================================

1. [Install PlatformIO Core](https://docs.platformio.org/page/core.html)
2. Download [development platform with examples](https://github.com/platformio/platform-raspberrypi/archive/develop.zip)
3. Extract ZIP archive
4. Run these commands:

```shell
# Change directory to example
$ cd platform-raspberrypi/examples/arduino-ota

# Build project
$ pio run

# Upload firmware
$ pio run --target upload

# Clean build files
$ pio run --target clean
```

## Notes

This examples showcases the usage of Over-The-Air (OTA) updates with the Raspberry Pi Pico W.

For more details, see the [documentation](https://arduino-pico.readthedocs.io/en/latest/ota.html).

For the initial firmware update, use the `rpipicow_via_usb` environment.

Then, open the serial monitor and note down the IP of the Pico that it outputs.

Use this IP as the `upload_port` in the `rpipicow_via_ota` environment and use the "Upload" project task there.
39 changes: 39 additions & 0 deletions examples/arduino-ota/include/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

This directory is intended for project header files.

A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.

```src/main.c

#include "header.h"

int main (void)
{
...
}
```

Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.

In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.

Read more about using header files in official GCC documentation:

* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes

https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
46 changes: 46 additions & 0 deletions examples/arduino-ota/lib/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.

The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").

For example, see a structure of the following two libraries `Foo` and `Bar`:

|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c

and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>

int main (void)
{
...
}

```

PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.

More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
23 changes: 23 additions & 0 deletions examples/arduino-ota/platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter, extra scripting
; Upload options: custom port, speed and extra flags
; Library options: dependencies, extra library storages
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env]
platform = raspberrypi
framework = arduino

; upload via USB
[env:rpipicow_via_usb]
board = rpipicow
upload_protocol = mbed

; upload via OTA (change IP)
[env:rpipicow_via_ota]
board = rpipicow
upload_protocol = espota
upload_port = 192.168.0.206
Loading

0 comments on commit 2ace1c2

Please sign in to comment.