Wetterstation III

Ein kleines Update: nachdem ich den ersten esp32 verbrannt habe (und das meine ich fast wörtlich: es gab ordentlich Funken), musste ich auf die Neulieferung warten. Die Zeit habe ich genutzt, um tatsächlich ein ordentliches Gehäuse zu bauen. Man muss dazu wissen, dass ich wirklich gerne bastele, allerdings nicht wirklich talentiert bin. Ich folge hier der Tradition von Tim Taylor (die älteren unter uns können mit den Namen vielleicht noch etwas anfangen). Am Ende ist es nicht so ganz exakt gerade geworden, aber doch ganz ordentlich:

Letzte Woche kam dann auch der neue esp32, auch den konnte ich in Betrieb nehmen (wohlgemerkt: man muss dabei löten – und es funktioniert trotzdem). Unglücklicherweise habe ich bei der Gelegenheit auch mal die libraries aktualisiert: Merlin war auch fleissig und es hat eine ganze Weile gedauert, die Konflikte aus dem merge aufzulösen. Leider hat der Code zwar an Umfang, aber nicht an Qualität zugelegt. Ich lästere an dieser Stelle mit viel Begeisterung, alldieweil ich vor geraumer Zeit mal als Programmierer in den embedded Bereich wechseln wollte. Leider hat sich da nichts ergeben, die meisten Absagen kamen mit der Begründung, ich hätte ja keine Erfahrung auf diesem Gebiet. Nun verstehe ich, warum die Leute keine echten Softwareentwickler haben wollen 😉 Nur um Beschwerden vorzubeugen: alle embedded developer die ich persönlich kenne, halte ich durchaus für kompetent.

Und es gibt auch schon ein neues Projekt: unerwarteterweise hat meine Frau Gefallen gefunden an dem Gerät und wünscht sich mehrere davon, nach Möglichkeit auch mit mehr bunt…

Wifi fuer IoT

Wenn man, so wie ich, eine “gebrauchte” Wohnung modernisieren will, landet man beim Thema SmartHome zwangslaeufig bei irgendwelchen Funk-Technologien. Ganz beliebt ist hier Wifi, proprietaere Geraete haengen sich in das heimische Wifi und unterhalten sich froehlich mit den Server ihres Herstellers, ohne dass wir Anwender auch nur ansatzweise mitbekommen, was da besprochen wird.

Wenngleich mir so etwas Job und Einkommen sichern, moechte ich das nicht in meiner Wohnung haben. Also gibt es bei mir ein Wifi nur fuers IoT. Mit einem raspberry ist das schon lange kein Hexenwerk mehr, der Kleine ist schnell zu einem Access Point auf- bzw. umgeruestet. Und das geht so:

Erstmal sollte wifi generell moeglich sein, also per ifconfig schauen, ob es ein interface wlan0 gibt, sonst unter /boot/ in der config.txt oder cmdline.txt nachschauen, ob das irgendwie abgeschaltet ist. Ausserdem sollte der rapsberry per LAN (also Kabel) im heimischen Netz haengen (das wifi bekommt ja nun neue Aufgaben).

Dann installieren wir den hostapd, dieser macht einen Rapsberry zum Access Point.

sudo apt-get install hostapd

Damit der dhcp sich hier nicht einmischt, verbieten wir ihm Eingriffe in das Wifi und schreiben in die /etc/dhcpcd.conf:

denyinterfaces wlan0

Wie jeder AP bekommt unser wifi eine statische ip, das konfigurieren wir in der /etc/network/interfaces:

allow-hotplug wlan0
iface wlan0 inet static
address 192.168.220.1
netmask 255.255.255.0
network 192.168.220.0
broadcast 192.168.220.255

Das sollte ein IP-Bereich sein, den man sonst nicht im eigenen Netz hat. Nun kann man sich um den hostapd selber kuemmern, das passiert in der /etc/hostapd/hostapd.conf:

# WifI interface and driver to be used
interface=wlan0
driver=nl80211

# WiFi settings
hw_mode=g
channel=8
ieee80211n=1
wmm_enabled=1
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]
macaddr_acl=0
ignore_broadcast_ssid=0

# Use WPA authentication and a pre-shared key
auth_algs=1
wpa=2
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP

# Network Name
ssid=fuerdasIOT
# Network password
wpa_passphrase=geheim

ssid und wpa_passphrase darf man natuerlich gerne anpassen, ebenso den channel. Bei letzteren schaut man einfach, wo am wenigsten Betrieb ist.

Jetzt haben wir schon einen AP, mit dem man sich auch verbinden kann. Nun sorgen wir noch dafuer, dass jeder, der das tut, auch eine IP-Adresse bekommt, dafuer nehmen wir den dnsmasq:

sudo apt-get install dnsmasq

Der dnsmasq ist ein erstaunlich vielseitiges Tool, und genauso umfangreich ist auch seine default config in /etc/dnsmasq.conf. Das brauchen wir alles nicht, daher benennen wir die Datei um oder loeschen sie einfach (je nach Mut-Level) und erstellen sie mit folgenden Inhalt neu:

interface=wlan0
listen-address=192.168.220.1
bind-interfaces
server=192.168.220.1
domain-needed
bogus-priv
dhcp-range=192.168.220.50,192.168.220.150,12h
no-resolv
address=/#/192.168.220.1

Die IP-Adressen sollten denen aus der config vom hostapd entsprechen. Nettes Gimmick ist die letzte Zeile, damit sagen wir dem dnsmasq, dass er jede DNS-Anfrage mit 192.168.220.1 beantworten soll.

Nun koennen wir das alles in Betrieb nehmen:

systemctl enable dnsmasq
systemctl start dnsmasq
systemctl enable hostapd
systemctl start hostapd

Jetzt ist der Zeitpunkt gekommen, sich ein Notebook zu nehmen und dieses mal mit dem neuen AP zu verbinden. Wenn man die SID sieht, sich mit dem AP verbinden kann und dann auch noch eine IP aus dem dhcp-range bekommt – dann sind wir auf der Siegerstrasse. Spasseshalber kann sich noch davon ueberzeugen, dass alle IP-Abfragen immer auf unseren AP landen.

Wir sind aber noch nicht fertig, nun schauen wir uns den AP selber mal an. Auf dem mit unseren neuen AP verbunden Notebook starten wir jetzt mal ein

nmap 192.168.220.1

Bei mir hat nmap einen nginx, den ssh und auch das openhab gefunden. Je nachdem, was auf dem AP-raspberry installiert ist, kann das entsprechend anders aussehen. Da wir die Kontrolle behalten wollen, haben wir nun 2 Moeglichkeiten:

  1. wir blocken auf dem AP einfach alle “ungewollten” Zugriff per interface wlan0 via Firewall
  2. wir sorgen dafuer, dass nichts “unnoetiges” an wlan0 lauscht

Ich habe mich fuer letzteres entschieden. Was man nun tut, haengt von den gefunden Einfallstoren ab, fuer ssh, nginx und openhab ist das ganz einfach:

In der /etc/ssh/sshd_config tragen wir unter ListenAddress die IP-Adresse des LAN-interfaces ein und starten sshd neu. Mit

netstat -alpn |grep ssh

schauen wir dann, dass der sshd sich tatsaechlich nicht mehr an das wlan0-interface haengt.

Beim nginx gibt man via listen-directive vor, an welchem interface dieser lauschen soll, nginx neu starten und pruefen wieder mit netstat.

Fuer openhab2 gibt es die Datei org.ops4j.pax.web.cfg (bei mir unter /srv/openhab2-userdata/etc/), dort traegt man die Zeile

org.ops4j.pax.web.listening.addresses = 127.0.0.1

ein, alternativ kann man hier auch die IP des LAN-interfaces nehmen (ich habe den nginx als proxy vor dem openhab, daher reicht mir localhost). Wieder openhab neu starten und mit netstat pruefen.

Wenn man alle services durch hat, sollte nmap auf dem Notebook einen mehr oder minder “toten” Host anzeigen, sprich keine verfuegbaren services auf dem AP.

Und nun kann man anfangen sich zu ueberlegen, welche services man fuer sein IoT-Wifi freigeben moechte und diese ganz explizit entsprechen konfigurieren.

Es ist irgendwie ein gutes Gefuehl, die Kontrolle zu behalten.

Wetterstation II

Die Idee, eine sitemap aus openhab einfach als Bild auf das E-Paper Display zu schieben, hat sich als wenig praktikabel herausgestellt. Inzwischen generiert ein kleines Python-Script den Inhalt meiner Wetterstation. Im Prinzip war das einfacher als gedacht (Python ebend): wir brauchen pil und requests.

Mit requests holen wir uns die Daten aus openhab. Letzteres hat ein nettes Rest-API. Die Doku dazu kann man sich per PaperUI via Add-ons->misc installieren. Danach hat man unter seiner Root-url einen Link zum Rest API, ueber dieses kann man sich durch das API durchklicken. Ein Item kann man sich damit recht einfach holen:

curl http://openhab:8080/rest/items/Temperature_Balkon |python -m json.tool

holt den aktuellen Zustand des Temperatursensors vom Balkon bei mir.
In python nutze ich requests dafuer, das sieht dann so aus:

import requests
def getItem(item):
    url = baseURL + "/items/" + item
    r = requests.get(url, headers = {"accept" : "application/json"})
    return = r.json()

Um die damit gewonnenen Rohdaten in eine halbwegs ansprechende Form zu giessen, nutze ich pil bzw. dessen fork pillow:

from PIL import Image, ImageDraw, ImageFont
DISPLAY_SIZE = (640,384)
im = Image.new("1",DISPLAY_SIZE)
draw = ImageDraw.Draw(im)
draw.rectangle(((0,0),DISPLAY_SIZE),fill=1,outline=1)

In das so erzeugte draw-Objekt kann ich jetzt malen:

draw.text((x,y),line, font = infoFnt)
draw.line((maxW,upperLineY,maxW,maxY))

Die erste Zeile schreibt den Text text an die Position (x,y) und nutzt dafuer den Font infoFnt. Wenn man sich den Text als Rechteck vorstellt, bezeichnet (x,y) dessen linke obere Ecke. Um mehrere Zeilen unter- oder nebeneinander zu schreiben, ist es sinnvoll, die Ecke rechts unten dieses Recktecks zu kennen. Deswegen habe ich Textausgaben in eine Funktion gepackt:

def drawText(draw,x,y,text,font):
    p = font.getsize(text)
    draw.text((x,y),text, font = font)
    return x+p[0],y+p[1]

Das spart viel Schreibarbeit.

Als Fonts kann pil TrueType Fonts nutzen. Da muss man mal schauen, was so installiert ist. Auf meinem debian liegen diverse davon unter /usr/share/fonts/truetype, erzeugen kann man sich ein Font-Objekt dann einfach per:

infoFnt = ImageFont.truetype("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",15)

Am Ende schreibt man das muehevoll erzeugte Bild in eine Datei:

del draw
im.save("pyimage.pbm")

Von dem pbm braucht man dann nur den Header abschneiden und schon haben wir wieder genau die Binaerdatei, welche sich der esp32 dann abholen kann.

Das Ergebnis sieht bei mir dann so aus:

Den Wetterbericht unten hole ich mir aus dem Internet. Einen Design-Preise gewinne ich damit nicht, aber immerhin durfte ich die Anzeige jetzt schon im Wohnzimmer aufstellen. Was will Mann mehr?