Gateway for listening for ESPNOW and proxying to mqtt over wifi

This commit is contained in:
2023-07-31 13:39:39 +02:00
parent 4458c3729f
commit 638ffbfda4
2 changed files with 308 additions and 0 deletions

203
src/gateway.py Normal file
View File

@@ -0,0 +1,203 @@
import network, time, utime, json, machine, esp32, ntptime
import espnow
from machine import Pin, ADC
from umqtt.simple import MQTTClient
from ubinascii import b2a_base64
from configuration_server import serveSetupServer, getStorageVar
REBOOT_DELAY = 2
wifiChannel = -1
# BASE_TOPIC = getStorageVar('mqtt_topic')
class Wifi():
def __init__(self):
self.sta = network.WLAN(network.STA_IF)
self.channel = -1
# The power saving mode causes the device to turn off the radio
# periodically (typically for hundreds of milliseconds), making
# it unreliable in receiving
def disablePowerSavings(self):
self.sta.config(pm=self.sta.PM_NONE)
def setChannel(self):
global wifiChannel
self.channel = self.sta.config("channel")
wifiChannel = self.channel
print("Proxy running on channel:", self.channel)
def connect(self, ssid, password):
self.sta.active(True)
self.sta.connect(ssid, password)
while not self.sta.isconnected(): # Wait until connected...
time.sleep(0.5)
print('waiting to connect to wifi...')
self.sta.config(pm=self.sta.PM_NONE)
print('Connected to wifi "%s"' % ssid)
self.disablePowerSavings()
self.setChannel()
class ESPNowServer():
def __init__(self):
self.ap = network.WLAN(network.AP_IF)
self.server = None
self.mqttClient = None
def setup(self):
self.server = espnow.ESPNow()
self.server.active(True)
print("Successtully activate ESP now on AP antenna")
def receive(self, mqttClient):
count = 0
mqttClient.publishGatewayStatus()
while True:
# TODO change to irecv, bytearray instead of bytestring
peer, msg = self.server.recv()
if msg is not None and msg not in [b'end'] and 'ack' not in msg:
payload = parse_json(msg.decode('utf-8'))
hiveName = payload['hive_name']
mqttClient.relayTelemetry(hiveName, payload)
count = count + 1
elif count > 20:
mqttClient.publishGatewayStatus()
count = 0
class TelemetryMQTT():
def __init__(self, clientId, brokerUrl):
self.clientId = clientId
self.mqttUrl = brokerUrl
self.client = None
self.keepalive = 60
def connect(self):
self.client = MQTTClient(self.clientId, self.mqttUrl, port=31883, keepalive=self.keepalive)
try:
self.client.connect(clean_session=True)
print("Connected to MQTT broker")
except:
print('Unable to connect to MQTT broker')
reboot()
def publishGatewayStatus(self, persist=True):
topic = 'telemetry/gateway/{}'.format(self.clientId)
payload = str({
'gateway_name': self.clientId,
't': getTime(),
'temperature': readTemperature(),
'channel': wifiChannel
}).replace("'", '"').encode()
self.client.publish(topic, payload, persist)
def relayTelemetry(self, topic, msg, persist=True):
topic = 'telemetry/hive/{}'.format(topic)
msg['t'] = getTime()
payload = str(msg).replace("'", '"').encode()
self.client.publish(topic, payload, persist)
def resetAntennas(): # Reset wifi to AP_IF off, STA_IF on and disconnected
sta = network.WLAN(network.STA_IF)
ap = network.WLAN(network.AP_IF)
sta.active(False)
ap.active(False)
sta.active(True)
while not sta.active():
time.sleep(0.1)
print('sta.active():')
while sta.isconnected():
time.sleep(0.1)
print('sta.isconnected():')
def reboot(delay = REBOOT_DELAY):
print (f'Rebooting device in {delay} seconds (Ctrl-C to escape).')
utime.sleep(delay)
machine.reset()
def parse_json(json_str):
try:
# Evaluate the JSON string to convert it into a Python dictionary
result_dict = eval(json_str)
if not isinstance(result_dict, dict):
raise ValueError("JSON does not represent a valid object.")
return result_dict
except Exception as e:
raise ValueError("Invalid JSON format:", str(e))
def syncTime():
ntptime.settime()
def getTime():
[ y, M, d, h, m, s, dow, doy] = time.localtime()
return '{}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}Z'.format(y, M, d, h, m, s)
def readTemperature():
fahrenheit = esp32.raw_temperature()
celcius = (fahrenheit - 32) / 1.8
return "{0:.2f}".format(celcius)
# def readBatteryLevel():
# adc = ADC(Pin(13, Pin.IN))
# voltage = (adc.read() / 4095) * 2 * 3.3 * 1.1
# # return 22
# return "{0:.2f}".format(voltage)
def setupAndProxy():
resetAntennas()
espNowServer = ESPNowServer()
espNowServer.setup()
ssid = getStorageVar('ssid')
password = getStorageVar('pass')
wifi = Wifi()
wifi.connect(ssid, password)
brokerUrl = getStorageVar('mqtt_broker')
mqttClient = TelemetryMQTT('House', brokerUrl)
mqttClient.connect()
syncTime()
return espNowServer, mqttClient
if __name__ == '__main__':
modeSwitch = Pin(26, Pin.IN, Pin.PULL_UP)
mode = modeSwitch.value()
try:
if mode == 1:
[espNowServer, mqttClient] = setupAndProxy()
print('activated and waiting for messages')
espNowServer.receive(mqttClient)
else:
print('serving setup server')
serveSetupServer()
except KeyboardInterrupt as err:
raise err # use Ctrl-C to exit to micropython repl
except Exception as err:
print ('Error during execution:', err)
# raise err
reboot()

105
src/setup/gateway.html Normal file
View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1"
/>
<title></title>
<link rel="stylesheet" type="text/css" href="./styles.css" />
</head>
<body>
<h1>Hive setup page</h1>
<section>
<p>Configure your microcontroller with the input fields below.</p>
<button class="light" id="device-info">View device info</button>
<button class="light" id="identify">Identify</button>
<form action="/save" method="POST">
<h3>General</h3>
<div>
<label for="name">Hive name</label>
<input id="name" name="name" enterkeyhint="send" value="{{ name }}" />
</div>
<h3>MQTT</h3>
<div>
<label for="mqtt_broker">MQTT broker</label>
<input
id="mqtt_broker"
name="mqtt_broker"
enterkeyhint="send"
value="{{ mqtt_broker }}"
/>
</div>
<div>
<label for="mqtt_topic">MQTT topic</label>
<input
id="mqtt_topic"
name="mqtt_topic"
enterkeyhint="send"
value="{{ mqtt_topic }}"
/>
</div>
<h3>Wifi</h3>
<div>
<label for="ssid">SSID</label>
<input id="ssid" name="ssid" enterkeyhint="send" value="{{ ssid }}" />
</div>
<div>
<label for="pass">Password</label>
<input
id="pass"
name="pass"
enterkeyhint="send"
type="password"
value="{{ pass }}"
/>
<i id="password">🙈</i>
</div>
<button type="submit">Save</button>
</form>
</section>
<dialog id="dialog">
<form>
<p><b>MAC address: </b><span>{{ mac }}</span></p>
<p><b>CPU freq: </b><span>{{ freq }} Mhz</span></p>
<button value="cancel" formmethod="dialog">Close</button>
</form>
</dialog>
</body>
<script>
function togglePasswordVisiblity(event) {
const label = event.target;
const input = label.parentElement.getElementsByTagName('input')[0];
label.innerText = input.type === 'password' ? '🐵' : '🙈';
input.type = input.type === 'password' ? 'text' : 'password';
}
function identifyApiCall() {
fetch('identify', { method: 'POST' });
}
const deviceBtn = document.getElementById('device-info');
const identifyBtn = document.getElementById('identify');
const passwordBtn = document.getElementById('password');
const dialog = document.getElementById("dialog");
deviceBtn.addEventListener('click', () => dialog.showModal());
passwordBtn.addEventListener('click', togglePasswordVisiblity);
identifyBtn.addEventListener('click', identifyApiCall);
</script>
</html>