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

Remote config and ota #4

Merged
merged 2 commits into from
Jul 9, 2022
Merged
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
51 changes: 51 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
kind: pipeline
type: docker
name: arm64-build

platform:
arch: arm64
os: linux

steps:
- name: build-pio
image: uxian/pio-esp32
commands:
- pio run
when:
event:
- pull_request
- tag

- name: docker-pr-build
image: plugins/docker
settings:
dry_run: true
repo: test
tags:
- ${DRONE_COMMIT_SHA}
when:
event:
- pull_request

- name: docker-tag-build
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo:
from_secret: docker_repo
tags:
- latest
- "${DRONE_TAG}"
when:
event:
- tag

trigger:
ref:
include:
- refs/pull/**
- refs/tags/**
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3-slim AS stage

COPY server/* /app/
WORKDIR /app

RUN python3 -m venv /opt/venv
RUN /opt/venv/bin/pip install -r requirements.txt
RUN /opt/venv/bin/pip list -v

FROM python:3-slim

ARG VERSION_NUMBER

COPY --from=stage /app /app
COPY --from=stage /opt/venv /opt/venv
COPY .pio/build/lolin_d32/firmware.bin /assets/firmware-${VERSION_NUMBER}.bin
RUN echo "${VERSION_NUMBER}" > /.version

ENV PYTHONPATH /piplib

WORKDIR /app

ENTRYPOINT ["/opt/venv/bin/python"]
CMD ["main.py"]
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Environment Monitor with Logging to MQTT

This device measures environmental conditions such as temperature, humidity, relative pressure and rainfall and reports that data to InfluxDB via the Telegraf endpoint when deployed as part of the TICK stack (https://www.influxdata.com/time-series-platform/). It also includes a battery voltage monitor to enable alerting when the device needs to be recharged. The current timestamp is retrieved via NTP each time the board wakes from deep sleep mode.
This device measures environmental conditions such as temperature, humidity, relative pressure and rainfall and reports that data to MQTT topics based on the location. The data pushed only includes the reading (no timestamp) but should be suitable for consumption
by systems such as Home Assistant.

## MQTT deployment

Expand Down Expand Up @@ -35,4 +36,19 @@ I'll leave that as an exercise for the user since I'm not 100% happy with the as

![Assembled Outdoor Sensor](docs/outdoor-version.jpg)

Since the rain sensor needs to be outside the box I had to drill a few holes for the standoffs and cabling which I sealed (hopefully) using Sugru.
Since the rain sensor needs to be outside the box I had to drill a few holes for the standoffs and cabling which I sealed (hopefully) using Sugru.

## Setup

On first launch the device will create a WiFi hotspot called sensor-XXXXXX (where XXXXXX is the MAC address of the device). Connect
a laptop or phone to this hotspot to use the captive portal to configure your WiFi, MQTT sever and (optionally) syslog server and/or firmware server.

If the system detects a new firmware has been installed that requires additional configuration it will automatically switch back into configuration mode with an AP named sensor-LOCATION instead.

To force the device back into configuration mode, press the reboot button and the press the BOOT button twice in quick succession.

## Firmware Server

This software uses the esp32FOTA library to detect firmware updates. This library expects a JSON descriptor that identifies the current firmware version and provides a link to the firmware's binary image. I have provided a Dockerfile and Python script that will provide such a server. To run it needs an environment BASE_URL to indicate where it is being server from. This is because the esp32FOTA library does not support relative URLs.

I've configure esp32FOTA to support insecure HTTPS without validating certificates since I'll be serving this from a closed environment on a LAN. You may want to add your own code for certificate validation if you want to improve security.
7 changes: 7 additions & 0 deletions assets/bootstrap.min.css

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions assets/bootstrap.min.js

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

<link rel="stylesheet" href="bootstrap.min.css" />
<title>Configure Environment Sensor</title>
</head>
<body>
<div class="container">
<div class="alert alert-%STATUS%" role="alert">
%MESSAGE%
</div>

<h2>Configure Environment Sensor</h2>

<hr />

<h3>General Configuration</h3>

<form action="/configure" method="POST">
<div class="form-group row">
<label for="name" class="col-sm-2">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name" aria-describedby="nameHelp" value="%NAME%" />
<small id="nameHelp" class="form-text text-muted">This should be a single word with no spaces or punctuation. It will be used to define the MQTT topic as environment/<i>name</i>/<i>gauge</i></small>
</div>
</div>

<hr />

<h3>Network</h3>

<div class="form-group row">
<label for="ssid" class="col-sm-2">SSID</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="ssid" name="ssid" aria-describedby="ssidHelp" value="%SSID%" />
<small id="ssidHelp" class="form-text text-muted">SSID of your WiFi (must support 2.4GHz)</small>
</div>
</div>

<div class="form-group row">
<label for="passphrase" class="col-sm-2">Passphrase</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="passphrase" name="passphrase" aria-describedby="passphraseHelp" />
<small id="passphraseHelp" class="form-text text-muted">Passphrase of your WiFi access point</small>
</div>
</div>

<div class="form-group row">
<label for="mqttHost" class="col-sm-2">MQTT Hostname</label>
<div class="col-sm-7">
<input type="text" class="form-control" id="mqttHost" name="mqttHost" value="%MQTT_HOST%" />
</div>
<label for="mqttPort" class="col-sm-1">Port</label>
<div class="col-sm-2">
<input type="number" class="form-control" id="mqttPort" name="mqttPort" value="%MQTT_PORT%" />
</div>
</div>

<div class="form-group row">
<label for="updateUrl" class="col-sm-2">Firmware Version URL</label>
<div class="col-sm-10">
<input type="url" class="form-control" id="updateUrl" name="updateUrl" aria-describedby="updateUrlHelp" value="%UPDATE_URL%" />
<small id="updateUrlHelp" class="form-text text-muted">This should point to version.json</small>
</div>
</div>

<div class="form-group row">
<label for="syslogHost" class="col-sm-2">syslog Hostname</label>
<div class="col-sm-7">
<input type="text" class="form-control" id="syslogHost" name="syslogHost" value="%SYSLOG_HOST%" />
</div>
<label for="syslogPort" class="col-sm-1">Port</label>
<div class="col-sm-2">
<input type="number" class="form-control" id="syslogPort" name="syslogPort" value="%SYSLOG_PORT%" />
</div>
</div>

<hr />

<h3>Features</h3>

<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="rainSensor" name="rainSensor" %RAIN_SENSOR%>
<label class="form-check-label" for="rainSensor">
Rain Sensor
</label>
</div>

<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="batterySensor" name="batterySensor" %BATTERY_SENSOR%>
<label class="form-check-label" for="batterySensor">
Battery Sensor
</label>
</div>

<hr />

<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>

<script src="bootstrap.min.js"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions assets/success.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Success</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h3>Success</h3>
<p>The configuration process is complete. The device will now reboot and you may close this window.</p>
</body>
</html>
13 changes: 13 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REPO=${1:-}
TAG=${2:-}

export PLATFORMIO_BUILD_FLAGS="'-DVERSION_NUMBER=\"${TAG}\"'"
pio run

docker build \
--progress=plain \
--build-arg VERSION_NUMBER="${TAG}" \
-t "${REPO}:${TAG}" \
.
8 changes: 8 additions & 0 deletions partitions.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x009000, 0x4000,
otadata, data, ota, 0x00d000, 0x2000,
phy_init, data, phy, 0x00f000, 0x1000,
ota_0, app, ota_0, 0x010000, 0x180000,
ota_1, app, ota_1, 0x190000, 0x180000,
spiffs, data, spiffs, 0x310000, 0xf0000,
30 changes: 22 additions & 8 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,33 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[platformio]
extra_configs = secret_config.ini

[env:lolin_d32]
platform = espressif32
board = lolin32
framework = arduino
board_build.partitions = partitions.csv

lib_deps =
adafruit/Adafruit BME280 Library @ ^2.2.2
ottowinter/AsyncMqttClient-esphome @ ^0.8.6
rpolitex/ArduinoNvs @ ^2.5
ottowinter/ESPAsyncWebServer-esphome @ ^2.1.0
arcao/Syslog @ ^2.0.0
chrisjoyce911/esp32FOTA @ ^0.1.6

board_build.embed_txtfiles =
assets/bootstrap.min.css
assets/bootstrap.min.js
assets/index.html
assets/success.html

build_flags =
-std=gnu++17

build_unflags =
-std=gnu++11

upload_port = /dev/cu.usbserial-0001
upload_speed = 921600
monitor_port = /dev/cu.usbserial-0001
monitor_speed = 115200
lib_deps =
adafruit/Adafruit BME280 Library @ ^2.2.2
arduino-libraries/NTPClient @ ^3.2.1
ottowinter/AsyncMqttClient-esphome @ ^0.8.6
arcao/Syslog @ ^2.0.0
13 changes: 0 additions & 13 deletions secret_config.ini.template

This file was deleted.

35 changes: 35 additions & 0 deletions server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from flask import Flask
from flask import send_from_directory
from flask import request
import os

url_override = os.environ.get('BASE_URL')
if (url_override is not None and not url_override.endswith('/')):
url_override = url_override + '/'

app = Flask(__name__)
global version
with open("/.version", "r") as f:
version = f.readline().rstrip()

@app.route("/version.json")
def version_json():
global version
base_url = ""

if url_override is None:
base_url = request.host_url
else:
base_url = url_override

return {
"type": "home-sensor",
"version": version,
"url": base_url + "fw/firmware-" + version + ".bin"
}

@app.route('/fw/<path:path>')
def firmware(path):
return send_from_directory('/assets', path)

app.run(host='0.0.0.0', port=80)
1 change: 1 addition & 0 deletions server/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flask >= 2.1.2
Loading