The wifi network is created by the hems-supervisor
service, using a python script that looks like this:
#!/usr/bin/env python
import logging
import os
import time
import typing
import uuid
import dbus
import requests
import sentry_sdk
logging.basicConfig(level=logging.DEBUG)
DEBUG = False
def get_device_name_and_uuid() -> typing.Tuple[str, str]:
"""
Get the Balena device name and UUID from environment variables.
"""
BALENA_DEVICE_NAME_AT_INIT = ""
BALENA_DEVICE_UUID = ""
if "BALENA_DEVICE_UUID" in os.environ.keys():
BALENA_DEVICE_UUID = os.environ["BALENA_DEVICE_UUID"]
else:
logging.warning("$BALENA_DEVICE_UUID not set in environment!")
if "BALENA_DEVICE_NAME_AT_INIT" in os.environ.keys():
BALENA_DEVICE_NAME_AT_INIT = os.environ["BALENA_DEVICE_NAME_AT_INIT"]
else:
logging.warning("$BALENA_DEVICE_NAME_AT_INIT not set in environment!")
logging.debug(
"BALENA_DEVICE_NAME_AT_INIT={},BALENA_DEVICE_UUID={}".format(
BALENA_DEVICE_NAME_AT_INIT, BALENA_DEVICE_UUID
)
)
return BALENA_DEVICE_NAME_AT_INIT, BALENA_DEVICE_UUID
def setup_network(device_name: str, device_uuid: str):
"""
This function configures the network on the host by sending a message to the host DBUS based on:
https://www.balena.io/docs/reference/OS/network/2.x/#changing-the-network-at-runtime
It broadly maps onto nm-settings conf (ref: https://developer.gnome.org/NetworkManager/stable/nm-settings.html).
If changes are required: to infer DBUS signatures used for marshalling data you need to cross-reference nm-settings with DBUS type signatures defined in https://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html .
"""
s_con = dbus.Dictionary(
{
"id": "balena-hotspot",
"type": "802-11-wireless",
"autoconnect": dbus.Boolean(True),
"autoconnect-retries": dbus.types.Int32(0),
"interface-name": "wlan0",
"mdns": dbus.types.Int32(1),
}
)
s_wifi = dbus.Dictionary(
{
"band": "bg",
"mode": "ap",
"ssid": dbus.ByteArray(
str("ccoop-hems-{}".format(device_uuid[:4])).encode("utf8")
),
"powersave": dbus.types.UInt32(2),
}
)
s_wsec = dbus.Dictionary(
{
"key-mgmt": "wpa-psk",
"proto": dbus.types.Array([b"rsn"], "s"),
"psk": device_name,
}
)
s_ip4 = dbus.Dictionary({"method": "shared"})
s_ip6 = dbus.Dictionary(
{
"method": "ignore",
}
)
con = dbus.Dictionary(
{
"connection": s_con,
"802-11-wireless": s_wifi,
"802-11-wireless-security": s_wsec,
"ipv4": s_ip4,
"ipv6": s_ip6,
}
)
bus = dbus.SystemBus()
proxy = bus.get_object(
"org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/Settings"
)
settings = dbus.Interface(proxy, "org.freedesktop.NetworkManager.Settings")
##
logging.debug(
"Network initialisation successful. The following network config was sent to host dbus:"
+ str(con)
)
settings.AddConnection(con)
def restart_supervisor_service():
"""
Use balena supervisor API to restart the container in hope of BALENA_DEVICE_NAME_AT_INIT and BALENA_DEVICE_UUID being initialised next time!
"""
logging.warning(
"BALENA_DEVICE_UUID and/or BALENA_NAME_AT_INIT is not set in environment!!! Attempting to restart hems-supervisor container..."
)
BALENA_SUPERVISOR_ADDRESS = os.environ["BALENA_SUPERVISOR_ADDRESS"]
BALENA_APP_ID = os.environ["BALENA_APP_ID"]
BALENA_SUPERVISOR_API_KEY = os.environ["BALENA_SUPERVISOR_API_KEY"]
request_url = "{}/v2/applications/{}/restart-service?apikey={}".format(
BALENA_SUPERVISOR_ADDRESS, BALENA_APP_ID, BALENA_SUPERVISOR_API_KEY
)
request_data = {"serviceName": "hems-supervisor"}
logging.debug(
"Attempting to restart supervisor ... url: {} data: {}".format(
request_url, request_data
)
)
while True:
try:
response = requests.post(request_url, data=request_data, timeout=10)
except:
logging.error(
"Whilst attempting to connect to supervisor API to restart an exception occurred! Retrying... "
)
# Should never get to here as the service should restart
return
if __name__ == "__main__":
# Get device name and UUID from env variables
device_name, device_uuid = get_device_name_and_uuid()
if DEBUG:
device_name = ""
device_uuid = ""
logging.debug(f"device_name={device_name},device_uuid={device_uuid}")
# Check to see if device name and UUID initialised properly, if not restart container and hope for the best!
if device_name == "" or device_uuid == "":
time.sleep(3) # delay restart
restart_supervisor_service()
# If name/UUID set start wifi
setup_network(device_name, device_uuid)
Full docker compose file:
version: "2"
volumes:
carboncoop-hems-shared-data:
carboncoop-hems-homeassistant-data:
carboncoop-hems-homeassistant-config-storage:
carboncoop-hems-mosquitto-data:
carboncoop-hems-supervisor-data:
services:
homeassistant:
build:
dockerfile: ./docker/Dockerfile
context: ./homeassistant
args:
ha_machine_name: raspberrypi3
ha_version: 2021.3.3
ven_version: 0.4.8
ports:
- "80:8123"
restart: always
volumes:
- carboncoop-hems-shared-data:/carboncoop-hems-shared-data
- carboncoop-hems-homeassistant-data:/carboncoop-hems-homeassistant-data
- carboncoop-hems-homeassistant-config-storage:/config/.storage
environment:
CCOOP_HEMS_CA_CERT_URL:
CCOOP_HEMS_HUB_REG_URL:
CCOOP_HEMS_VTN_ADDRESS:
mosquitto:
image: arm32v6/eclipse-mosquitto:1.6
ports:
- "1883:1883"
restart: always
volumes:
- carboncoop-hems-shared-data:/carboncoop-hems-shared-data
- carboncoop-hems-mosquitto-data:/carboncoop-hems-mosquitto-data
hems-supervisor:
build:
dockerfile: ./docker/Dockerfile
context: ./supervisor
restart: always
volumes:
- carboncoop-hems-shared-data:/carboncoop-hems-shared-data
- carboncoop-hems-supervisor-data:/carboncoop-hems-supervisor-data
command: "/bin/sh -c 'while :; do python /usr/src/app/network.py; sleep 24h;done;'"
environment:
- DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
labels:
io.balena.features.dbus: "true"
io.balena.features.supervisor-api: "true"
telegraf:
build:
dockerfile: ./docker/Dockerfile
context: ./telegraf
restart: always
volumes:
- carboncoop-hems-shared-data:/carboncoop-hems-shared-data
environment:
TELEMETRY_SERVER_URL:
The IoT devices are communicating with the mosquitto
service, and that is communicating with HA.