==== Vorwort ==== Die Portierung des vzloggers auf den [[https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html|Raspberry Pico W]] war/ist ein "Bastelprojekt" insb mit der Motivation "das müsste doch gehen" und der aktuelle Zustand ist "funktioniert mit ein paar kleinen, noch zu behebenden Schönheitsfehlern" (siehe ganz unten TODO), wozu jede Hilfe willkommen ist. Weitere Motivation war die Verfügbarkeit von analogen GPIO Ports (on-board ADC), also analogen Sensoren (s.u.), sowie geringerer Stromverbrauch (batterietauglich?) und Anschaffungskosten. Der RPi Pico W ("W" für on-board WiFi - alles andere macht es noch komplizierter und lohnt vermutlich nicht) ist im Gegensatz zum "normalen" Raspberry Pi ein "Microcontroller" mit einem RP2040 Chip. Dies bedeutet insb: * kein Betriebssystem (es gibt quasi nur ein einziges Executable) * damit kein Filesystem (damit kein logging, kein config, ...) * rudimentärer Satz von POSIX System calls, insb keine pthreads * sehr eingeschränkter RAM sowie Flash-Memory Es gibt inzwischen RPi Pico 2 - damit noch keine Erfahrungen, offenbar aber nicht als "W"-Version. Den RPi Pico 1 gibt es auch als "WH" mit bereits angelöteten Pins - macht es nochmal einfacher. Versuche, libcurl zu portieren, hatten sich als sehr aufwändig herausgestellt und wurden abgebrochen. Statt dessen wird eine IP Implementierung [[https://www.nongnu.org/lwip|lwIP]] genutzt (sowie bei Bedarf sogar "mbedtls" als TLS Implementierung, bisher nicht genutzt). In der Konsequenz heisst das (nur, wenn entspr compiliert): * Embedded config als JSON-String * Logging nach stdout, über USB sichtbar, solange der Pico an einem "richtigen" Computer am USB-Anschluss hängt (gleichzeitig Stromversorgung) * Ersetzen von pthreads durch main-loop Konzept * Minimale Implemetierung (kein embedded httpd, kein Push-Server, kein MQTT, keine SML/S2/D2/mbus/... Protokolle - diese könnten aber vermutlich noch hinzugefügt werden, zumindestens teilweise) * Ersetzen von Curl durch lwip-API Dies wiederum erforderte ein paar Umstrukturierungen - der resultierende Code ist aber sowohl auf dem RPi Pico als auch einem normalen RPi lauffähig (muss natürlich separat gebaut werden). === Install PICO Dev-Kit === Siehe Doc hier: https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf $ mkdir ~/projects/pico $ cd ~/projects/pico $ git clone https://github.com/raspberrypi/pico-sdk.git --branch master $ cd pico-sdk $ git submodule update --init $ cd .. $ git clone https://github.com/raspberrypi/pico-examples.git --branch master === Clone GIT repo === $ mkdir ~/projects/vzlogger/pico $ cd ~/projects/vzlogger/pico $ git clone https://github.com/tge12/vzlogger Es gibt 2 CMakeLists.txt Varianten - linke die Passende (ja, das ist hässlich, noch TODO): $ ln -sf CMakeLists-pico.txt CMakeLists.txt === Clone json-c lib sources === Diese müssen selbst compiliert werden, da eine "fertige" lib für den RPi Pico nicht zu finden war/ist. $ cd ~/projects/vzlogger/pico/libs/json-c $ git clone https://github.com/json-c/json-c . Auch hier sind ein paar Anpassungen nötig, um die lib json-c kompilierbar zu machen für den RPi Pico (TODO auch hässlich und zu verbessern, evtl sind Erweiterungen im Bauprozess in der lib json-c nötig): $ cp ../json-c-vz/* . === Clone EmonLib lib sources === Ähnliche Situation. EmonLib ist für den eigentlichen RPi Pico-Port nicht notwendig, wohl aber für das neue "protocol" zum Lesen von Strommesswerten mittels SCT013 (s.u.). Es gibt auch hier einen git fork, welcher 2 Erweiterungen enthält, die z.T. unbedingt notwendig sind (s.u.). $ cd ~/projects/vzlogger/pico/libs/EmonLib $ git clone https://github.com/tge12/EmonLib . Auch hier sind ein paar Anpassungen nötig, um die lib kompilierbar zu machen für den RPi Pico (TODO auch hässlich und zu verbessern, evtl sind Erweiterungen im Bauprozess in der lib json-c nötig): $ cp ../EmonLib-vz/* . === Bauen === Zuerst muss die "embedded config" in src/vzlogger_pico.cpp angepasst werden, insb die VZ Server URL, WiFi Zugangsdaten, meters und channels - wie üblich, nur eben embedded. $ cd ~/projects/vzlogger/pico $ mkdir build $ cd build $ cmake -DPICO_BOARD=pico_w .. ... $ make ... Wenn alles klappt, kommt ein File namens .../build/src/vzlogger.uf2 dabei raus, welches auf den RPi Pico kopiert werden kann wie in der Pico SDK Doc beschrieben. === TODOs === * Die cmake Infrastruktur ist noch sehr unschön ==== Neues EmonLib Protokoll ==== Es gibt allerlei andere Projekte, die mit dem nicht-invasiven (einfach um stromführenden Leiter drum herum clippen) Stromsensor SCT013 den heimischen Energieverbrauch messen. Idee war, dies in VZ zu integrieren. Die mit Abstand beste Seite hierzu mit viel Theorie usw usf: https://docs.openenergymonitor.org/electricity-monitoring/index.html Dazu wird benötigt: * mindestens ein SCT013 (es gibt verschiedene Varianten, in meinem Fall wurde ein SCT013-030 verwendet, also liefert AC mit max 1V bei 30A, integrierter Lastwiderstand), bei mehreren Phasen entsprechende Anzahl. * mindestens ein kl Trafo (z.B. altes Netzteil), muss AC liefern, typischerweise 9V * kleine selbstgebaute Adapterschaltung, die den output o.g. Quellen für den RPi Pico ADC lesbar macht (s.u.). Wichtig dabei: der ADC verträgt max 0..3V AC, d.h. die AC Sinuskurven müssen um 1.5V pendeln im Bereich von eben max 0..3V Sieht dann so aus - die beiden SCT013: {{:software:controller:vzlogger:rpi_pico:signal-2024-08-31-203850_005-1.jpeg?nolink&400|}} Das Befestigen der SCT013 um Netzspannung führende Leiter ist an sich harmlos - trotzdem muss man evtl. im Sicherungskasten herumhantieren und man sollte wissen, was man da tut!!! Im Zweifel lieber Finger weg oder einen Elektriker bitten! Die Adapterschaltung nebst RPi Pico (die Stecker: 2x I (SCT013), 1x U (kl 9V AC Netzteil)): {{:software:controller:vzlogger:rpi_pico:signal-2024-09-02-181804_002-1.jpeg?nolink&400|}} Die Grundidee ist nun, dass vzlogger kontinuierlich beide Sensoren (U und I) via EmonLib abfragt und daraus sowohl Wirk- als auch Scheinleistung berechnet (bei PV Einspeisung sogar negativ). Sieht dann so aus (bereits mit 2 Phasen, braun L1 und schwarz L2): {{:software:controller:vzlogger:rpi_pico:screenshot_2024-09-01_12-23-45.jpg?nolink&800|}} Ein Messzyklus mit EmonLib calcVI() misst 20 volle Sinuswellen (dauert bei 50Hz immer reichlich 200ms), dabei werden vom RPi Pico bis zu ~6000 Messwerte gewonnen. D.h. pro einzelner Sinuswelle gibt es Hunderte von einzelnen Messwerten, womit sich die Kurve gut rekonstruieren (selbst wenn in der Realität verzerrt) und auch die "Richtung" des Stroms (Bezug oder Einspeisung) ermitteln lässt. All dies ist sehr gut erklärt auf o.g. Seite, dort auch die Adapterschaltung: https://docs.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino.html Die EmonLib Erweiterung hat sich als notwendig entpuppt, da der o.g. kl Trafo eine hohe Phasenverschiebung (eingebaute Komponenten?) zwischen U und I erzeugte, d.h. U passte nicht mehr zu I, selbst und inbs. mit rein ohmschen Lasten (Toaster), wo genau dies eigtl nicht passiert. Zum anderen ist in der Erweiterung die Möglichkeit enthalten, die tatsächlichen ADC Messwerte auszugeben, um diese dann in ein Spreadsheet zu laden und damit die echten Sinuskurven anzuzeigen. Beispiel (U und I verschoben, P aber korrigiert): {{:software:controller:vzlogger:screenshot_2024-08-17_11-03-02.jpg?nolink&800|}} Um Messfehler, die durch die eigene Messinfrastruktur erzeugt wurden, zu kompensieren, gibt es verschiedene EmonLib-Parameter (die in der vzlogger Config enthalten sein müssen). Diese "Kalibrierung" ist ebenfalls auf der openenergymonitor Seite beschrieben, außer o.g. neue Phasenverschiebungsfehler-Kompensation. Idee hierbei ist es, im {{ :software:controller:vzlogger:rpi_pico:phasecal-generic.ods |Simulations-Spreadsheet}} die Stichproben-Reihen zu ermitteln die für U und I jeweils am nächsten bei Null sind (ohne Phasenverschiebung und mit rein ohmscher Last (!!) wäre dies exakt die gleiche Stichprobe) und quasi softwaremäßig wieder zurück zu verschieben, d.h. passend zu U Reihe 44 und für I Reihe 18, macht 44-18=26. Heisst, I ist U etwa 26 Stichproben voraus und ein korrekter Wert für P wird ermittelt, indem für jedes U[i] ein I[i - 26] verwendet wird. All dies passiert innerhalb EmonLib - es muss nur der "phaseCalibration" Config-Wert eingestellt werden. Die resultierende config sieht dann so aus (bereits mit 2 gemessenen Phasen s.u. - dies hatte übrigens den Effekt, dass die L1 Phasenverschiebung nochmal etwas anders war: 35 statt 26. Möglicherweise Interferenz der beiden SCT013?): static const char * inlineConfig = "{ 'verbosity': 5, \ 'retry': 30, \ 'meters': \ [ \ { \ 'enabled': true, \ 'skip': false, \ 'interval': 10, \ 'protocol': 'emonlib', \ 'adcCurrent': 0, \ 'adcVoltage': 1, \ 'currentCalibration' : 30, \ 'voltageCalibration' : 247.0, \ 'phaseCalibration' : 35.0, \ 'delay': 100, \ 'numSamples': 20, \ 'channels': \ [ \ { \ 'uuid': 'f3ef9b70-de3b-11ee-83b5-73042e2a7e09', \ 'api': 'volkszaehler', \ 'middleware': '" VZ_SERVER_URL "', \ 'identifier': 'RealPower'\ } \ ,{ \ 'uuid': '560ff4e0-2d94-11ef-9a04-7f5c06e34262', \ 'api': 'volkszaehler', \ 'middleware': '" VZ_SERVER_URL "', \ 'identifier': 'Voltage'\ } \ ,{ \ 'uuid': '2e2a8c90-dd66-11ee-9621-0d0747854c29', \ 'api': 'volkszaehler', \ 'middleware': '" VZ_SERVER_URL "', \ 'identifier': 'Current' \ } \ ] \ } \ ,{ \ 'enabled': true, \ 'skip': false, \ 'interval': 10, \ 'protocol': 'emonlib', \ 'adcCurrent': 2, \ 'adcVoltage': 1, \ 'currentCalibration' : 30, \ 'voltageCalibration' : 247.0, \ 'phaseCalibration' : 132.0, \ 'delay': 100, \ 'numSamples': 20, \ 'channels': \ [ \ { \ 'uuid': 'a8d6bba0-6462-11ef-aea1-3b1a1ab3364e', \ 'api': 'volkszaehler', \ 'middleware': '" VZ_SERVER_URL "', \ 'identifier': 'RealPower'\ } \ ,{ \ 'uuid': 'd9c7cd20-67bf-11ef-938c-3b62ce66ce08', \ 'api': 'volkszaehler', \ 'middleware': '" VZ_SERVER_URL "', \ 'identifier': 'Current' \ } \ ] \ } \ ] }"; Die einzelnen Meter-Attribute: * adcCurrent - welcher ADC Pin * adcVoltage - welcher ADC Pin * currentCalibration - ggf Kalibrierungswert (wenn SCT013-30 dann einfach 30) * voltageCalibration - Kalibrierungswert, ausprobieren, siehe openenergiemon.org Doc * phaseCalibration - Ausgleich Phasenverschiebung zw U und I (s.o.) * delay - in Microsecs, Verlangsamung der Messschleife * numSamples - wie viele Sinuswellen messen Der "delay" entspricht einem "sleep(100us)", damit kommen "nur" ~1500 Stichproben pro Messzyklus heraus, hat aber den Vorteil, dass der L2 Phasecal-Wert mit 132 bedeutet, dass in EmonLib nicht mehr als 256 Stichproben aufgehoben werden müssen - diese Anzahl Stichproben ist immer noch viel mehr als genug um die Sinuskurve rekonstruieren zu können). === 3-Phasen === Um mehrere Phasen zu messen, gibt es die folgenden Möglichkeiten: * pro Phase ein SCT013 + je ein Trafo an der jeweils gleichen Phase, damit P pro Phase genau ermittelbar wie oben. Erfordert aber eben 3 Trafos und entspr Verkabelung (wo sind die dazu passenden Steckdosen?) * pro Phase ein SCT013 + nur ein Trafo an einer Phase. Variante 2 scheint ausreichend genau zu sein, d.h. U scheint für die verschiedenen Phasen ausreichend gleich. Es muss aber die gleiche Phasenkorrektur zwischen U und I wie oben stattfinden - wenn U und I an verschiedenen Phasen gemessen werden (dabei dann aber mit einer erheblich grösseren Differenz, in der realen Beispiel-Config 132). === Simulation === Wenn libs/EmonLib mit -DCALIBRATION compiliert wird, gibt der vzlogger nach jedem Messzyklus genau 1000 Stichproben als CSV aus (Zeitstempel in Microsecs;ADC I;ADC U) sowie die initialen OffsetI und Offset U. Diese Werte können in das Spreadsheet kopiert werden, damit sind dann im Idealfall schöne Sinuskurven sichtbar (*immer* für U, je nach Last für I -> Tip: große, rein ohmsche Lasten verwenden z.B. Wasserkocher, Toaster, ...). Damit wiederum den phaseCal Wert wie oben beschrieben ermitteln, in config eintragen, neu compilieren. Dies muss für alle Phasen separat gemacht werden. === Tips und Tricks === Ein paar Erfahrungen, die man nicht alle nochmal selbst machen muss: * Die SCT013 in Spannungsvariante liefern AC max 1V, kann man also einfach mit einem Multimeter testen - um *eine* Kabelader clippen, Strom durchfliessen lassen, messen. 30A macht 1V, also 3A == 100mV usw. Dabei gibt es *keine* Richtung. SCT013 in Stromvariante geht genauso, erfordert aber einen Lastwiderstand. * Der RPi Pico hat 3 (evtl 4) ADC Eingänge und genau einen ADC_GND (andere GND führen zu dubiosen Messwerten), d.h. alle Sensoren teilen sich den gleichen ADC_GND * Die ADC Rohwerte lassen sich auch interpretieren. Der Pico ADC mit 12bits liefert also Messwerte zwischen 0..4094 (weil 2^12), d.h. mit einem 9V AC Netzteil (Leerlauf gern 11-12V, die dann mit der Adapterschaltung auf max 3V AC gebracht werden, mit 0 bei 1.5V) sind Rohwerte zwischen 500 und 3500 zu erwarten. Für I gilt prinzipiell das Gleiche, hier ist aber die Amplitude meist viel kleiner, die ADC Werte bewegen sich im 2000er Bereich. * Der vzlogger output geht über den USB Anschluss und lässt sich anzeigen, indem man den RPi Pico gleichzeitig per USB von Laptop mit Strom versorgt und dann, sobald der RPi Pico bootet: $ sudo screen -L /dev/ttyACM0 Das gibt alles nach stdout aus, sowie gleichzeitig in ein File screenlog.0, was man dann später analysieren kann (enthält auch den CALIBRATION output). Alternativ zum Laptop tut es auch ein "normaler RPi". Vorteil: Im Dauerbetrieb kann der Laptop wieder ab. Das Logdatenvolumen kann per log-level in der embedded config gesteuert werden. Wenn alles klappt, als Stromversorgung ein einfaches USB Netzteil.