Pond[CTRL] Nieuwe User Story

Omdat ik erg weinigtijd voor de ontwikkeling van de App heb, wil ik een simpel alternatief om Pond[CTRL] toch via m’n iPhone te kunnen bedienen. Zo ontstond de volgende user story:

Als iPhone-gebruiker wil ik Pond[CTRL] kunnen bedienen via de Woning-app, zodat dit niet langer omslachtig via een browser met login hoeft.

Een simpele oplossing lijkt me via HomeBridge en de Woning-app. Komende dagen zal ik inventariseren welke aanpassingen aan de firmware nodig zijn (bijvoorbeeld authenticatie tussen HomeBrigde en Pond[CTRL]) en hoe complex het schrijven van een Plug-in voor HomeBridge is.

Pond[CTRL] Update

Er zat een bug in de firmware waarbij stekkers met functie ‘altijd aan’ of ‘altijd uit’ niet reageerden op voederpauze. De functie ‘altijd aan’ en ‘altijd uit’ deden dus hun naam wel eer aan, echter bleek dit niet handig.

Na aansluiten van schrikdraad (moet altijd aan staan i.v.m. een terugkerende reiger) op een stekker wilde ik dat deze tijdens voeren uit ging. Wel zo veilig voor de kids (en mijzelf).

De bug is nu uiteraard opgelost.

De enige (nu bekende) fout is nog steeds dat de pH-sensor extreem afwijkende waarden geeft bij een omgevingstemperatuur van minder dan ongeveer 7,5˚C. Dit lijkt niet afhankelijk van de watertemperatuur. Nu de temperatuur in de nacht niet zo ver meer zakt is het moeilijk deze fout te reproduceren. Voor de koude nachten zich weer laten zien, wil ik een tweede pH sensor installeren om te kijken of het te maken heeft met de sensor of wellicht het hardware-ontwerp. Maar: eerst maar eens genieten van de warme dagen!

Huidige firmwareversie: 1135

Pond[CTRL] Update

Vandaag heb ik zeven prachtige Euro Koi mogen verwelkomen in de vijver. Ik hoop ze ooit zo vertrouwd met mensen te kunnen maken dat ze uit je handen eten. Om daarmee te beginnen heb ik de voederpauze-functie ingebouwd. Ik wil de Koi alleen voeren tijdens een voederpauze, waarbij alle pompen uitgeschakeld zijn. Ik hoop dat de vissen zo slim worden dat als er geen stroming is, ze het besef hebben dat er gevoerd wordt. Ik voer alleen in een voederring, zodat ik na het voeren het overtollige voer gemakkelijk kan verwijderen.

In de firmware moet de voederpauze automatisch uitgeschakeld worden na een half uur. Deze functie bleek verwijderd te zijn bij het verplaatsen van de hoofdfuncties naar de WiFi-module. De firmware is nu aangepast en werkt weer naar behoren.

Daarnaast heb ik vandaag een eerste begin gemaakt aan de iOS-app. Omdat ik nog helemaal niet bekend was met XCode en SwiftUI heb ik eerst een aantal online tutorials moeten doorlopen.

Huidige firmwareversie: 1133

Pond[CTRL] Update

Er zat een bug in de firmware waarbij uitgeschakelde sensoren een alarm konden verzoorzaken. Dit is opgelost vanaf firmwareversie 1130.

Huidige firmwareversie: 1130

Pond[CTRL] Update

De vernieuwde taakverdeling tussen microprocessor en WiFi-module blijkt een goede zet te zijn geweest. Pond[CTRL] draait vele malen stabieler en heeft de afgelopen 24 uur nog geen enkele ongeplande reset gehad (een ongeplande reset is een reset door bijvoorbeeld een hard- of softwarefout).

Vandaag heb ik de API en de firmware van de WiFi-module aangepast. De API geeft nu een json-resultaat na uploaden van data waarin de meest firmware-versie en de status vermeld staan. Indien de API-call succesvol was, dan zal de programmeerbare LED gaan branden wat vanaf nu aangeeft of er een succesvolle link is met de API. De LED brandt net zo lang tot er een negatieve of geen status ontvangen wordt. Door de versienummers van de huidige firmware en de nieuwste firmware te vergelijken, wordt er nu een automatische update in gang gezet.

Huidige firmwareversie: 1129

Pond[CTRL] Update

Omdat Pond[CTRL] nu naast de vijver hangt in een waterdichte behuizing, is het lastig om software van de microprocessor te updaten. Hierdoor heb ik de beslissing genomen om alle logica en berekeningen te verplaatsen van de software van de microprocessor naar die van de WiFi-module welke op afstand bij te werken is.

De microprocessor doet nu niets anders van sensorgegevens ophalen en luisteren naar opdrachten van de WiFi-module. De WiFi-module kan vanaf nu ook stekkers schakelen, de status-LED besturen en buzzer (de)activeren.

Daarnaast was het geven van opdrachten vanaf de online app te complex. Er moest gewerkt worden met byte-arrays die direct doorgespeeld werden naar de microprocessor. Ik heb opdracht-URLs toegevoegd aan de webserver van de WiFi-module, waarmee aan de hand van simpele opdrachten gecombineerd met POST-data alles op te halen en in te stellen is. Voor test-doeleinden is de ‘query?data=’-URL nog steeds aanwezig maar werkt alleen zodra ingelogd op de module.

De microprocessor crashte, ondanks het toevoegen van varistors, bij het schakelen van meerde apparaten gelijktijdig. Om dit te voorkomen heb ik een kleine vertraging van het schakelen van elk relais toegevoegd waarmee het probleem verholpen lijkt.

Enige probleem op dit moment is nog steeds de foute meetwaarden van de pH-sensor bij een buitentemperatuur van onder de 10 graden Celcius. Het vreemde is dat de meting niet afhankelijk is van de watertemperatuur, terwijl de sensor in het water hangt.

Pond[CTRL] Update

Pond[CTRL] is nu enige tijd actief en door uitgebreid te testen heb ik een en ander aan de hard- en software aangepast.

Software

De belangrijkste wijziging in de software is het afvlakken van de meetwaarden. Er wordt nu bij temperatuur- en pH-sensoren een gemiddelde van de laatste 120 juiste meetwaarden weergegeven, waarbij een juiste meetwaarde een waarde is die maximaal 5% afwijkt van de voorgaande. Bij schakelen van relais bleek er soms een foute meetwaarde tussen te zitten (door een dip in de stroomvoorziening) waardoor meetwaarden een alarm veroorzaakten. De 5% regel zit geprogrammeerd in de microprocessor zelf en de WiFi-module om ook storingen op de I2C-bus op te vangen.

De ingebouwde interface van de WiFi-module werkte alleen juist in standalone-modus (wanneer Pond[CTRL] als access point fungeerde). De interface werkt nu in beide modi.

Hardware

De hardware bleek helaas ook niet helemaal te functioneren zoals gewenst. Veel apparaten kunnen geschakeld worden zonder problemen, echter bij het uitschakelen van een zwaardere pomp ontstond er storing in de stroomvoorziening waardoor de microprocessor zichzelf resette. Dit probleem is gemakkelijk op te lossen door een voldoende zware varistor op de fase en nul van elk relais te zetten. De bedoeling is om deze varistor in het standaardontwerp van de print op te nemen.

Online Dashboard

Het online Dashboard toont nu alleen het bereik van de meetwaarden en niet langer het gehele bereik tussen laag en hoog alarm.

Problemen

Het enige probleem wat ik nog niet opgelost is, is de instabiliteit van de pH sensor bij temperaturen onder de 10 graden Celsius. Omdat ik niet weet of dit aan de sensor of het hardware-ontwerp van Pond[CTRL] ligt, wil ik eerst een tweede sensor testen.

What’s next

De komende tijd zal vooral bestaan uit een duurtest en (tromgeroffel!) het ontwerpen van een iOS-app. De app zal het online dashboard en app combineren in één.

Afwezigheidsdetectie: Eureka!

Na wat finetunen werkt alles zoals in eerste instantie bedoeld: er wordt een reeks IP-adressen gescand en indien online wordt het MAC-adres vergeleken met bekende apparaten. Is er een bekend apparaat online, dan zal de thermostaat (geroote Toon) zijn programma vervolgen. Zijn er geen apparaten online, dan wordt de thermostaat ingesteld op programma ‘Weg’ en gaan de opgegeven lampen (Philips Hue) automatisch uit! Naast lampen kan door middel van slimme stekkers natuurlijk ook sluipverbruik (standby apparaten) voorkomen worden.

Toekomstwensen? Mogelijkheid tot opnieuw inschakelen van bijvoorbeeld nachtlampjes op de kinderkamers. Zijn we weg, dan blijft alles uit. Komen we weer thuis, dan zou er bijvoorbeeld ingeschakeld kunnen worden als de zon onder is. De voorbereidingen voor zoninformatie is al in de laatste versie van mijn script verwerkt.

Het script

Je dient variabelen ‘toonIP’, ‘hueIP’, ‘locationLat’, ‘locationLon’, ‘ipRange’, ‘ipStart’, ‘ipEnd’, ‘hueAPIKey’ en ‘path’ in te stellen.

#!/bin/bash
# Script door Dennis Bor

# IP van Toon
toonIP="192.168.0.X"

# IP van Hue
hueIP="192.168.0.X"

# jouw locatie
locationLat="XX.XXXXXX"
locationLon="X.XXXXXX"

# te scannen IP range (opgegeven IP .1 - .255
ipRange="192.168.0."
ipStart=130
ipEnd=140

# API-key van Hue
hueAPIKey="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# pad naar het script
path="/home/dennis/home_automation/"

# pad naar arp
arpPath="/usr/sbin/"

# toon informatie
date

# zonsopkomst en ondergang ophalen aan de hand van de opgegeven longitude en latitude
sunrise=`curl -s "https://api.sunrise-sunset.org/json?lat=$locationLat&lng=$locationLon"  | jq -r '.results.sunrise'`
sunset=`curl -s "https://api.sunrise-sunset.org/json?lat=$locationLat&lng=$locationLon"  | jq -r '.results.sunset'`

# tijdverschil ten opzichte van UTC ophalen
timeDifferenceUTC=`date +%z`

# substring vanaf index 2 (+0100 wordt bijvoorbeeld 100; 1 uur)
timeOffset=${timeDifferenceUTC:2}

# offset delen door 100 voor het aantal hele uren
timeOffset="$(($timeOffset / 100))"

# timestamps genereren voor zonsopkomst, zonsondergang en huidige tijd, inclusief UTC offset
# voor gegevens van api.sunrise-sunset.org
sunriseTimestamp=`date -d "$sunrise + $timeOffset hours" +%s`
sunsetTimestamp=`date -d "$sunset + $timeOffset hours" +%s`
currentTimestamp=`date +%s`

# huidige tijd tussen zonsopkomst en ondergang
if [ $sunriseTimestamp -lt $currentTimestamp ] && [ $sunsetTimestamp -gt $currentTimestamp ]; then
	
	# ja
	
	# toon informatie
	echo "De zon is momenteel op."
	
	# stel de boolean in op true
	sunUp=true

# huidige tijd voor zonsopkomst of na zonsondergang	
else

	# ja
	
	# toon informatie
	echo "De zon is momenteel onder."
	
	# stel de boolean in op true
	sunUp=false
fi

# Array met mac adressen
# Als geen van deze apparaten gevonden wordt in het lokale
# netwerk, dan zal er geschakeld worden.
deviceArray=()

# itereer door bestand 'devices' waarin
# per regel een mac-adres van een apparaat opgegeven
# kan worden
while read line; do

	# geen lege regel?
	if [[ $line != "" ]]; then

		# voeg het apparaat toe aan de array
		deviceArray+=("$line")
	fi

done < "$path"devices

# Boolean waarin opgeslagen wordt of er tenminste een
# van de gespecificeerde apparaten online is. Standaard false
deviceFound=false

# Toon informatie
echo "Zoeken naar apparaten, dit duurt een paar minuutjes..."

# itereer door het opgegeven ip bereik
for iterator in $(seq $ipStart $ipEnd)
do

	# apparaat gevonden (boolean true)?
	if [[ $deviceFound == true ]]; then
	
		# ja, escape deze for-loop
		break
	fi

	# toon informatie
	printf "Scannen van $ipRange$iterator"

	# itereer van 1 t/m 50 (maximaal aantal pogingen). Het grote aantal iteraties bleek
	# nodig omdat de enige open WiFi poort op de iphone tijdens standy (62078) maar
	# op bepaalde momenten reageert. 99% van de keren is dit binnen 25 pogingen, maar
	# enkele uitschieters tussen de 30 en 40 kwamen voor. 50 is dus echt met een 
	# ingebouwd veilig marge
	for subIterator in {1..50}}
	do

		# apparaat nog (steeds) niet gevonden?
		if [[ $deviceFound == false ]]; then

			# toon informatie (puntje, soort progress indicator)
			printf "."

			# scan poort 62078 met een delay van 250ms op het huidige ip adres
			nmapResult=`nmap --scan-delay 250ms -p 62078 "$ipRange$iterator"`

			# komt de tekst '1 host up' voor in de uitvoer van nmap? dat
			# betekent dat het apparaat gevonden is
			if [[ $nmapResult == *"1 host up"* ]]; then

				# itereer door alle apparaten in de array
				for macIterator in "${deviceArray[@]}"
				do
					# komt het huidige mac adres voor in de uitvoer van nmap?
					if [[ $nmapResult == *"$macIterator"* ]]; then

						# ja
						
						# toon informatie (eerste echo zorgt voor een linebreak na de progress
						# indicator puntjes)
						echo ""
						printf "Apparaat met adres $macIterator gevonden"
						
						# stel de boolean in op true (apparaat gevonden!)
						deviceFound=true
						
						# verlaat de for-loop
						break
					fi
				done
			fi
		fi
	done
	
	# apparaat nog (steeds) niet gevonden
	if [[ $deviceFound == false ]]; then

		# nee
		
		# toon progress indicator
		printf "."
	
		# als laatste poging pingen we, waarop Android apparaten wel altijd reageren.
		# lukt de ping ook niet, dan kunnen we er wel van uit gaan dat het apparaat echot
		# niet binnen het bereik van het WiFi-netwerk is.
		pingResult=`ping -c 4 "$ipRange$iterator"`

		# komt de tekst '100% packet loss' niet voor in de uitvoer van ping?
		if [[ $pingResult != *"100% packet loss"* ]]; then

			# nee! dat betekent dat het apparaat tenmiste eenmaal gereageerd
			# heeft op een ping
			
			# arp informatie ophalen van het apparaat, zodat we het mac adres	
			# kunnen vergelijken met onze bekende apparaten
			arpResult=`"$arpPath"arp -a "$ipRange$iterator"`

			# itereer door alle mac adressen in de array
			for macIterator in "${deviceArray[@]}"
            do
				# komt het huidige mac adres voor in de uitvoer van arp?	
				if [[ ${arpResult^^} == *"$macIterator"* ]]; then

					# yep! bekend apparaat gevonden!
			
					# toon informatie (eerste echo is een linebreak na de progress
					# indicator
					echo ""
					printf "Apparaat met adres $macIterator gevonden"
					
					# stel de boolean in op true (apparaat gevonden)
					deviceFound=true
					
					# verlaat de for loop
					break
				fi
			done
		fi
	fi	
	
	# toon informatie (linebreak)
	echo ""

done

# Geen apparaten online?
if [[ $deviceFound == false ]]; then

	# nee

	# toon informatie
	echo "Geen apparaten online, actie ondernemen..."

	# schakel programma Toon in op Weg
	# gebruik curl, parse de uitvoer als JSON en sla de waarde van 'result' op
	# de waarde wordt bij de tweede call overschreven; als de laatste faalt,
	# dan moet de eerste ook gefaald zijn en andersom
	result=`curl -s "http://$toonIP/happ_thermstat?action=changeSchemeState&state=0"`
	result=`curl -s "http://$toonIP/happ_thermstat?action=changeSchemeState&state=2&temperatureState=3" | jq -r '.result'`

	# waarde van result 'ok'?
	# dat betekent dat Toon het gekozen programma geacvtiveerd heeft
	if [[ $result == "ok" ]]; then

		# toon informatie
		echo "Toon ingesteld op 'Weg'"

	# waarde van result ongelijk aan 'ok'? dit kan alles zijn, van een ander resultaat tot
	# verbindingsproblemen, etc.
	else

		# toon informatie
		echo "Kan toon niet instellen op 'Weg'"
	fi

	# toon informatie
	echo "Hue instellen..."

	# haal alle lampen op
	result=`curl -s "http://$hueIP/api/$hueAPIKey/lights"`

	# array keys uit de json array van Hue halen. helaas gebruikt Hue geen
	# array met indices, maar met keys. van lampen die verwijderd worden
	# vervalt het id.
	arrayKeys=`echo "$result" | jq -r 'keys | .[]'`
	
	# nieuwe array om alle keys in op te slaan
	arrayIDs=()

	# itereer door alle array keys
	for key in ${arrayKeys//\\n/ }
	do
		# voeg de key toe aan de array
		arrayIDs+=("$key")

	done

	# inhoud van bestand 'lights' opslaan in de variabele. Bij alle lampen van Hue
	# wordt gecontroleerd of ze in deze lijst voorkomen, voordat ze uitgeschakeld worden.
	# hierdoor blijft het mogelijk om bepaalde verlichting te laten branden (bijvoorbeeld
	# tuinverlichting)
	lightsToSwitch=`cat "$path"lights`

	# itereer door alle IDs
	for lightIterator in "${arrayIDs[@]}"
	do

		# unieke id van de lamp ophalen. in het betand 'lights' kun je een lijst aanleggen
		# van unieke ids van lampen die uitgeschakeld moeten worden
		currentUniqueId=`echo "$result" | jq --arg value $lightIterator -r '.[$value].uniqueid'`
		
		# komt het huidige unieke id voor in de lijst met ids?
		if [[ $lightsToSwitch == *"$currentUniqueId"* ]]; then

			# ja
			
			# lamp naam ophalen zodat deze getoond kan worden
			currentName=`echo "$result" | jq --arg value $lightIterator -r  '.[$value].name'`
			
			# toon informatie
			echo "Uitschakelen: $currentName ($currentUniqueId)"
			
			# schakel de lamp uit
			toonResult=`curl -s -X PUT -H "Content-Type: application/json" -d '{"on":false}' "http://$hueIP/api/$hueAPIKey/lights/$lightIterator/state"`

		fi
	done

# Tenminste een apparaat online?
else

	# ja

	 # toon informatie
     echo "Een of meer apparaten online, thermostaat herstellen..."

	# hervat het programma van toon
	# gebruik curl, parse de uitvoer als JSON en sla de waarde van 'result' op
	result=`curl -s "http://$toonIP/happ_thermstat?action=changeSchemeState&state=1" | jq -r '.result'`

	# waarde van result 'ok'?
        # dat betekent dat Toon het programma hersteld heeft
        if [[ $result == "ok" ]]; then

                # toon informatie
                echo "Toon hervat programma"

        # waarde van result ongelijk aan 'ok'? dit kan alles zijn, van een ander resultaat tot
        # verbindingsproblemen, etc.
        else

                # toon informatie
                echo "Kan het programma op Toon niet hervatten"
        fi

	# toon informatie
	echo "Klaar..."

fi

Benodigde bestanden

Bestandsnaam ‘devices’

# lijst met MAC adressen van bekende apparaten. 
# zet elk apparaat op een aparte regel 
XX:XX:XX:XX:XX:XX
XX:XX:XX:XX:XX:XX

Bestandsnaam ‘lights’

# lijst met uit te schakelen Hue lampen bij afwezigheid
# zet elke lamp (of slimme stekker) op een aparte regel
# gebruik de 'uniqueid' parameter
XX:XX:XX:XX:XX:XX-XX:XX
XX:XX:XX:XX:XX:XX-XX:XX