This repository implements an IoT system for controlling relay modules from an Android app via MQTT.
The system has two MQTT access paths:
- Private (current project path for devices): MQTT over WireGuard on
1883(no TLS, tunnel provides transport security). - Public (for Android without WireGuard): MQTT over TLS on
8883(encrypted, certificate-based server identity, optional client certs).
The system consists of three main components:
- Android App – UI to discover devices and toggle relays (MQTT over TLS
8883). - Raspberry Pi Pico W – relay controller node (Wi-Fi uplink + WireGuard client + MQTT to
1883). - Raspberry Pi 5 (Provider) – WireGuard server, Mosquitto MQTT broker (both
1883and8883), and discovery/provider daemon.
- Pico W devices connect to the local network via Wi‑Fi.
- The Raspberry Pi 5 must be in the same Wi‑Fi/LAN segment because device discovery uses UDP broadcast (local network only).
- Over that uplink, Pico W devices establish a WireGuard tunnel to the Pi 5 (
10.8.0.0/24). - Pico W devices publish/subscribe MQTT via
10.8.0.1:1883(inside WireGuard).
Design note: JaszczurHAL supports MQTTS through its HAL MQTT and BearSSL transport, so a direct TLS device path is technically available. TimerNTP intentionally uses WireGuard because the deployment infrastructure is WireGuard-based and the firmware serves as a practical testbed for the JaszczurHAL tunnel implementation. Moving the device path to MQTTS would also require corresponding credential, broker-routing, and firmware configuration changes.
- The Android app connects to Mosquitto via MQTT over TLS on
8883. - This allows Android to work from outside the home network without requiring WireGuard.
1883is used by Pico W nodes via WireGuard (and optionally by other trusted hosts on LAN).1883must NOT be exposed to the public Internet.- Restrict it to:
- WireGuard subnet (
10.8.0.0/24) and/or - local LAN subnet(s)
- WireGuard subnet (
8883is exposed for Android clients that do not run WireGuard.- Traffic is encrypted with TLS.
- Authentication should include:
- MQTT username/password, and optionally
- client certificates (mTLS) if you want per-device certificate control.
- Written in Java (with some Kotlin UI utilities).
- Uses Eclipse Paho MQTT client.
- Dynamically builds device list based on topic
discovered/devices. - Subscribes to per-device topics for state updates (
status-{hostname}). - Supports relay toggling and time-based schedules.
- Firmware: C++ using the portable JaszczurHAL
app_start()/app_task0()contract. - Current firmware dependency (required): JaszczurHAL
- Native build/runtime: official Pico SDK with CYW43/lwIP, selected through the
JaszczurHAL
rp2040target andpicowboard profile. - In this project, JaszczurHAL covers:
- WireGuard client/tunnel
- MQTT transport over the HAL TCP stack
- NTP/time synchronization
- SSD1306 OLED display over HAL I2C
- EEPROM/KV persistence (
hal_eeprom/hal_kv) - authenticated native OTA with trial confirmation and rollback
- UDP discovery primitives and watchdog/common hardware APIs
- DS18B20 temperature acquisition
- Key features:
- Wi-Fi uplink through the native CYW43/lwIP stack
cJSON-based status/config payloads- state machine
- relay control + schedule window logic
- discovery responder (UDP)
- hardware watchdog
JaszczurHAL API reference: JaszczurHAL_API.md
- Written in C and runs as a
systemdservice. - Periodically:
- broadcasts UDP discovery
- receives UDP responses from Pico W
- verifies availability of Pico W devices
- publishes device list to
discovered/devices
| Topic | Direction | Payload Example | Purpose |
|---|---|---|---|
discovered/devices |
Provider -> App | {"devices":[...]} |
Broadcasts device list |
switch-{hostname} |
App -> Pico W | {"isOn1": true} |
Relay toggle |
time-{hostname} |
App -> Pico W | {"dateHourStart": 300, "dateHourEnd": 600} |
Schedule relays |
status-{hostname} |
Pico W -> App | {"status":"ok",...} |
Reports full device state |
Note: topic names above are the literal values used by current code.
Pico W nodes must be on the same local LAN/Wi‑Fi segment as the Pi 5 because discovery uses UDP broadcast.
| Port | Protocol | Purpose | Required |
|---|---|---|---|
| 51820 | UDP | WireGuard server endpoint (Pico W -> Pi 5) | ✅ yes |
| 1883 | TCP | MQTT for Pico W / internal clients (WireGuard/LAN only) | ✅ yes* |
| 8883 | TCP | MQTT over TLS for Android clients (public) | ✅ yes |
| 8266 | TCP | OTA firmware updates (optional) | |
| 12345 | UDP | Device discovery via broadcast (LAN only) | ✅ yes |
* Security requirement: 1883 must be reachable only from WireGuard and/or LAN. Do not expose it publicly.
Replace LAN_SUBNET with your local network CIDR (e.g. 192.168.2.0/24).
# WireGuard server
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# MQTT over TLS (public for Android)
sudo iptables -A INPUT -p tcp --dport 8883 -j ACCEPT
# MQTT plaintext (internal only)
sudo iptables -A INPUT -p tcp --dport 1883 -s 10.8.0.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 1883 -s LAN_SUBNET -j ACCEPT
# (Optional) OTA firmware updates
sudo iptables -A INPUT -p tcp --dport 8266 -j ACCEPT
# UDP discovery (LAN only)
sudo iptables -A INPUT -p udp --dport 12345 -s LAN_SUBNET -j ACCEPT
# Save firewall rules
sudo netfilter-persistent savesudo apt update
sudo apt install -y mosquitto mosquitto-clients libmosquitto-dev libcjson-dev
sudo systemctl enable mosquitto
sudo systemctl start mosquittosudo mosquitto_passwd -c /etc/mosquitto/passwd your_usernameBind 1883 to WireGuard (and optionally LAN), and keep it off the public Internet.
Example /etc/mosquitto/conf.d/internal.conf:
# WireGuard-only listener (recommended)
listener 1883 10.8.0.1
allow_anonymous false
password_file /etc/mosquitto/passwd
# (Optional) LAN listener if you have trusted LAN clients:
# listener 1883 192.168.X.Y
# allow_anonymous false
# password_file /etc/mosquitto/passwdExample /etc/mosquitto/conf.d/tls.conf:
listener 8883
protocol mqtt
allow_anonymous false
password_file /etc/mosquitto/passwd
# Server TLS (required)
cafile /etc/ssl/certs/ca-certificates.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
# Optional: require client certificates (mTLS)
# require_certificate true
# use_identity_as_username trueRestart Mosquitto:
sudo systemctl restart mosquitto
sudo systemctl status mosquittoQuick test (from a WireGuard peer) on 1883:
mosquitto_pub -h 10.8.0.1 -p 1883 -u your_username -P your_password -t test -m "Hello over WireGuard"
mosquitto_sub -h 10.8.0.1 -p 1883 -u your_username -P your_password -t testQuick test (TLS) on 8883:
mosquitto_pub -h YOUR_PUBLIC_HOST -p 8883 --cafile /etc/mosquitto/certs/ca.crt \
-u your_username -P your_password -t test -m "Hello over TLS"For Android, use a certificate chain that the phone trusts (public CA) or bundle/ship your CA certificate if you use a private CA.
cd lights-timer/RaspberryPi/aqua_topic_provider/
make
# Edit service file (paths, user, etc.)
sudo nano mqtt-devices-provider.service
sudo cp mqtt-devices-provider.service /etc/systemd/system/
sudo systemctl enable mqtt-devices-provider
sudo systemctl start mqtt-devices-providerExample mqtt-devices-provider.service:
[Unit]
Description=MQTT devices provider
After=network-online.target wg-quick@wg0.service
Requires=network-online.target wg-quick@wg0.service
[Service]
ExecStart=/home/pi/Documents/lights-timer/RaspberryPi/aqua_topic_provider/provider
WorkingDirectory=/home/pi/Documents/lights-timer/RaspberryPi/aqua_topic_provider
Restart=always
RestartSec=5
User=pi
[Install]
WantedBy=multi-user.target-
The firmware workflow is VS Code-first. Build and upload are driven by JaszczurHAL's shared
jh-vscodeentrypoint through project tasks inTimerNTP/.vscode/. -
Open
TimerNTPas your workspace folder in VS Code. -
Prerequisites:
- JaszczurHAL checked out at
../libraries/JaszczurHALrelative to this repository - JaszczurHAL setup completed with
./runmefirst.sh; it prepares the pinned Pico SDK, RP toolchain, picotool, and remaining managed dependencies - Python 3 for the shared monitor and upload tooling
- JaszczurHAL checked out at
-
The tracked manifest selects target
rp2040and boardpicow. Target/board overrides are stored locally inTimerNTP/.vscode/jaszczurhal.local.json. -
Use provided VS Code tasks (Command Palette -> Tasks: Run Task):
Project: BuildProject: Build (Debug)Project: Upload(serial)Project: Upload (UF2 / BOOTSEL)Project: Upload (OTA)Project: Discover OTA devicesProject: Serial MonitorProject: Refresh IntelliSenseProject: Clear USB Identity
-
From the
TimerNTPdirectory, the equivalent command-line build is:../../libraries/JaszczurHAL/vscode/entry/jh-vscode build --project . -
Main artifacts are copied to
TimerNTP/.build/:firmware.elf,firmware.bin,firmware.uf2, andfirmware.ota. The signed OTA container is generated when the OTA upload workflow receivesTIMER_NTP_OTA_PASSWORD.
Firmware links the external precompiled library at
../libraries/Credentials/src/cortex-m0plus/libCredentials.a. The author's
library is private and is not distributed in this repository. The tracked
libraries/Credentials directory is a complete replacement template with the
same API, but without author secrets or private encoding code.
If ../libraries/Credentials already exists, it is used unchanged. For a fresh
checkout, run from the lights-timer directory:
./scripts/setup-credentials.shThe script refuses to overwrite an existing library. It creates private files:
../libraries/Credentials/config/CredentialsData.local.h
../libraries/Credentials/config/MacHostMapping.local.cpp
Replace all placeholders, set CREDENTIALS_LOCAL_CONFIGURED to 1, then run:
../libraries/Credentials/scripts/build.sh rp2040The *.local.* files and generated archives are ignored by Git. Detailed
instructions are in libraries/Credentials/README.md.
Each device entry in
../libraries/Credentials/config/MacHostMapping.local.cpp needs the Pico W
factory MAC, hostname, relay count, WireGuard address, and private key.
Obtain the MAC from the Wi-Fi access point's client/DHCP table after the board starts its station connection, or use a small native JaszczurHAL diagnostic that calls:
char mac[18] = {};
hal_wifi_get_mac(mac, sizeof(mac));The JaszczurHAL 15_wifi example provides a ready native Wi-Fi diagnostic.
MAC matching in the Credentials template ignores separators and letter case.
After adding the entry, rebuild the Credentials archive and TimerNTP firmware.
- Open in Android Studio (Gradle sync).
- Supports Android 7.0+ (minSdk 24).
- Configure broker host:
YOUR_PUBLIC_HOST(or IP address, without port)- app always connects using
ssl://<broker>:8883
- Provide MQTT username/password.
- Add CA certificate file for TLS pinning:
- place certificate at
Android/app/src/main/res/raw/ca.crt(loaded asR.raw.ca) - this file is intentionally gitignored; provide it locally per environment
- place certificate at
- Firmware uses cJSON API through JaszczurHAL.
- Raspberry Pi provider links against system
libcjson(libcjson-devon Debian/Ubuntu).
MIT License – see LICENSE file.