Verbesserung der Telemetrie für den Freifunk-OpenMPPT-Solarladeregler

Einleitung

Bei DIY MPPT, FOSS und OSHW Solar-Bleiakku-Lader haben @weef und @elektra einen OSHW MPPT charger vorgestellt. Anfangs noch mit einem ATMega8 als Steuercomputer ausgestattet (2017), ist auf der Neuauflage nun ein ESP32 zu Gast (2019), der mit NodeMCU/Lua programmiert wird.

Wir entwickeln hier ein paar Erweiterungen, damit Telemetriedaten sowohl an den GitHub - ISEMS/isems-data-collector als auch an InfluxDB/Grafana übermittelt werden können.

Einleitung

Auf Basis des AVR freifunk-open-mppt/uC-Sourcecode at master · elektra42/freifunk-open-mppt · GitHub haben wir es leider nie geschafft, obige Sache zu realisieren. Die Appliance hatte damals schließlich auch noch keine direkte IP-Konnektivität.

Implementierung

Die Realisierung der Idee ist mit der neuen Hardware-Revision auf Basis des ESP32 und der entsprechenden, auf NodeMCU/Lua basierenden Firmware

nun deutlich niedrigschwelliger geworden. Danke, @elektra.

Der Beitrag

ist zwar noch nicht vollständig getestet, könnte aber grob tun.

Darstellung

Damit die Voreinstellung in der Firmware auf Anhieb funktioniert, haben wir kurzerhand https://isems.mqtthub.net/ in Betrieb genommen.

Beispiel

Dashboard isems-testdrive-99 [1].

image

Ausblick

Auf Basis dieser Implementierung ließen sich nun auch

  • komfortabel bestimmte Ereignisse innerhalb der Appliance automatisch fernmelden und als Annotationen im Graphen darstellen, so wie es @wtf, @weef und @roh bei ihrer Autonomen Zelle realisiert haben.
  • Wetterdaten aus dem Datenmischwerk in die Darstellung dazuholen [2].

  1. Realisiert über die Bash-Implementierung bei MQTTKit — Kotori 0.23.0 documentation. Die o.g. Implementierung wurde also noch nicht auf einem echten Gerät getestet. ↩︎

  2. Beispielsweise wie im Dashboard BeeKloppte: Stockübersicht & Bienenwetter. ↩︎

Hi Andreas –

ich habe eine Kleinigkeit in telemetry.lua gefixt. Sieht gut aus. Ich verwende ein ESP32-Dev-Board ohne Peripherie. Ich musste nur den Tiefentladeschutz in mp2.lua auskommentieren, da der Node sonst automatisch nach jedem Start in den Tiefschlaf geht. Die Telemetrie wird über is.lua nach einer Minute zum ersten Mal vom Timer aufgerufen.

LG Elektra


Log-Ausgabe

Preparing mqtt data.
Submitting telemetry data to MQTT broker.
Creating JSON payload.
JSON payload:   {"batteryVoltage":1.591,"ratedSolarModuleCapacity":20,"timeToShutdown":999,"batteryChargeEstimate":0,"status":"886","mppVoltage":2.299,"rateBatteryCapacity":7.2,"isemsRevision":"1","batteryHealthEstimate":100,"openCircuitVoltage":2.299,"latitude":52.520008,"nodeId":"ESP32-Meshnode-Elektra-test","temperatureCorrectedVoltage":19.065,"lowVoltageDisconnectVoltage":11.9,"longitude":13.404954,"timestamp":1579142675,"isPowerSaveMode":0,"batteryTemperature":-137.19}

Http post data.
Submitting telemetry data to HTTP endpoint.
Creating JSON payload.
I (3714617) MQTT_CLIENT: Sending MQTT CONNECT message, type: 1, id: 0000
JSON payload:   {"batteryVoltage":1.591,"ratedSolarModuleCapacity":20,"timeToShutdown":999,"batteryChargeEstimate":0,"status":"886","mppVoltage":2.299,"rateBatteryCapacity":7.2,"isemsRevision":"1","batteryHealthEstimate":100,"openCircuitVoltage":2.299,"latitude":52.520008,"nodeId":"ESP32-Meshnode-Elektra-test","temperatureCorrectedVoltage":19.065,"lowVoltageDisconnectVoltage":11.9,"longitude":13.404954,"timestamp":1579142675,"isPowerSaveMode":0,"batteryTemperature":-137.19}

Connected to MQTT broker.
I (3714847) HTTP_CLIENT: Redirect to https://isems.mqtthub.net/api-notls/isems/testdrive/foobar/ESP32-Meshnode-Elektra-test/data.json

Antwort: 405 Method Not Allowed

<html>
  <head><title>405 - Method Not Allowed</title></head>
  <body>
    <h1>Method Not Allowed</h1>
    <p>Your browser approached me (at /api-notls/isems/testdrive/foobar/ESP32-Meshnode-Elektra-test/data.json) with the method "POST".  I only allow the method HEAD here.</p>
  </body>
</html>
1 Like

Hi Elektra,

schön dass Dir das gefällt. Danke für Deine Antwort.

Exzellent!

Danke! Sorry für den Instrumentenflug bei mir und gut, dass Du Dir helfen konntest.

Anbei ein paar weitere Punkte, die mir an der Log-Ausgabe auffallen.

MQTT Nachricht kommt an?

Frage: Die Log-Meldung

MQTT message sent.

kommt nicht? Falls nein, müssen wir hier nochmal nachlegen.

Per

mosquitto_sub -h isems.mqtthub.net -p 1883 -t 'isems/#' -v

kannst Du dies nachverfolgen.

Programmierfehler bei MQTT

Da wo hier noch "hello" steht, muss natürlich json rein.

HTTP-Telemetrie funkt nicht

Da macht noch irgendwas Quatsch. Wahrscheinlich der Server. Der /api-notls Suffix ist für nicht-TLS Kommunikation gedacht. In diesem Fall sollte natürlich gerade nicht zu https://... rübergeschwenkt werden.

Falls TLS auf dem Eumel funktioniert, hieße die richtige Endpunkt-URL in der Konfiguration so:

http_endpoint = "https://isems.mqtthub.net/api/" .. telemetry_channel .. "/data.json"

Also ohne das "-notls"-Infix, dafür aber dann wirklich mit "https://"-Präfix. Gerade heraus:

-- HTTPS communication (preferred)
http_endpoint = "https://isems.mqtthub.net/api/" .. telemetry_channel .. "/data.json"

-- HTTP communication (fallback)
-- http_endpoint = "http://isems.mqtthub.net/api-notls/" .. telemetry_channel .. "/data.json"

Viele Grüße,
Andreas.

Das ist jetzt behoben. Für die Konfiguration des isems.mqtthub.net virtual hosts im Nginx fehlte noch der dedizierte Port-80 Schnipsel, der Anfragen an das /api-notls-Präfix exklusiv unverschlüsselt durchlässt. Alle anderen Anfragen werden weiterhin nach https:// umgeleitet.


P.S. @wtf: Ich hab das derweil mal auf eltiempo aufgelegt – gute Nachbarschaft und so. Ich hoffe das passt für Dich.

Hallo @elektra,

https://github.com/ISEMS/ISEMS-ESP32/pull/3 verbessert noch ein paar der oben angesprochenen Dinge.

Viele Grüße,
Andreas.

P.S.

Hiermit ist alles in Ordnung. Diese Nachricht kommt nur, wenn mit QoS == 1 übermittelt werden würde, siehe mqtt - NodeMCU Documentation.

function(client) optional callback fired when PUBACK received.

Hi Andreas -

das Konkatenieren habe ich aus config.lua entfernt und in telemetry.lua verschoben, der MQTT Code gibt mehr Feedback auf die Konsole aus und mit QoS Level 1 bekomme ich auch die Bestätigung, dass die Daten ankommen. HTTP POST beantwortet der Server immer noch mit einem 405.

LG Elektra

mqtt_topic:     isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42
Http telemetry url:     https://isems.mqtthub.net/api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json
Preparing mqtt data.
Submitting telemetry data to MQTT broker.
Creating JSON payload.
JSON payload:   {"batteryVoltage":1.479,"status":"886","batteryHealthEstimate":100,"latitude":52.52,"ratedSolarModuleCapacity":10,"rateBatteryCapacity":8,"timeToShutdown":3553,"batteryChargeEstimate":0,"temperatureCorrectedVoltage":19.077,"lowVoltageDisconnectVoltage":11.9,"openCircuitVoltage":2.275,"longitude":13.4,"mppVoltage":2.275,"isPowerSaveMode":0,"batteryTemperature":-137.6}
########## MQTT broker host:    isems.mqtthub.net
Http post mqtt data.
Submitting telemetry data to HTTP endpoint.
Creating JSON payload.
JSON payload:   {"batteryVoltage":1.479,"status":"886","batteryHealthEstimate":100,"latitude":52.52,"ratedSolarModuleCapacity":10,"rateBatteryCapacity":8,"timeToShutdown":3553,"batteryChargeEstimate":0,"temperatureCorrectedVoltage":19.077,"lowVoltageDisconnectVoltage":11.9,"openCircuitVoltage":2.275,"longitude":13.4,"mppVoltage":2.275,"isPowerSaveMode":0,"batteryTemperature":-137.6}
> ########## Connected to MQTT broker
########## Success: MQTT message sent.
405
<html>
  <head><title>405 - Method Not Allowed</title></head>
  <body>
    <h1>Method Not Allowed</h1>
    <p>Your browser approached me (at /api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json) with the method "POST".  I only allow the method HEAD here.</p>
  </body>
</html>
1 Like

Hi Elektra,

Mixed up endpoint suffixes of MQTT vs. HTTP. This fixes that glitch. by amotl · Pull Request #4 · ISEMS/ISEMS-ESP32 · GitHub müsste hoffentlich einen weiteren Fehler meinerseits beheben. Das kommt davon, wenn man den Code nicht doch selber laufen lässt – sorry.

Viele Grüße,
Andreas.

Danke. Das scheint zu passen:

200 OK
[
    {
        "message": "Received #1 readings",
        "type": "info"
    }
]

Na, läuft noch nicht ganz rund:

isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json {"batteryVoltage":1.827,"status":"886","batteryHealthEstimate":100,"latitude":52.52,"ratedSolarModuleCapacity":10,"rateBatteryCapacity":8,"timeToShutdown":3600,"batteryChargeEstimate":0,"temperatureCorrectedVoltage":19.004,"lowVoltageDisconnectVoltage":11.9,"openCircuitVoltage":2.378,"longitude":13.4,"mppVoltage":2.378,"isPowerSaveMode":0,"batteryTemperature":-135.15}
isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json {}
isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/error.json {
    "timestamp": "2020-01-21T21:47:55+00:00", 
    "message": "Data payload is empty", 
    "type": "", 
    "description": "Error processing MQTT message \"{}\" from topic \"isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json\"."
}
1 Like

Et voilà.

https://weather.hiveeyes.org/grafana/d/UsLo8SsZk/isems-testdrive

@elektra: Eine von beiden Telemetrievarianten funkt scheinbar noch nicht. Man müsste nun schauen, welche von beiden das ist. Ich habe gerade eben mal das Logging am Server höhergestellt, vielleicht kommt wir damit dem Problem auf die Schliche, wenn das Testgerät nochmals Daten übermittelt (bis 11:43 Uhr tat es das).

9 posts were merged into an existing topic: Telemetriedaten des Freifunk-OpenMPPT-Solarladeregler darstellen

Bericht

Korrekt.

2020-01-23T04:07:31+0100 [kotori.daq.services.mig            ] DEBUG   : Processing message on topic 'isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json' with payload '{"batteryVoltage":3.104,"status":"486","batteryHealthEstimate":100,"latitude":52.52,"ratedSolarModuleCapacity":10,"rateBatteryCapacity":8,"timeToShutdown":3121,"batteryChargeEstimate":0,"temperatureCorrectedVoltage":18.717,"lowVoltageDisconnectVoltage":11.9,"openCircuitVoltage":0,"longitude":13.4,"mppVoltage":0,"isPowerSaveMode":0,"batteryTemperature":-125.6}'
2020-01-23T04:07:31+0100 [kotori.daq.services.mig            ] DEBUG   : Topology address: {'node': 'ESP32-Meshnode-1-Testing-Elektra42', 'slot': 'data.json', 'realm': 'isems', 'network': 'testdrive', 'gateway': 'foobar'}
2020-01-23T04:07:31+0100 [kotori.daq.services.mig            ] DEBUG   : Storage location: {'node': 'ESP32-Meshnode-1-Testing-Elektra42', 'slot': 'data.json', 'realm': 'isems', 'network': 'testdrive', 'database': 'isems_testdrive', 'measurement_events': 'foobar_esp32_meshnode_1_testing_elektra42_events', 'label': 'foobar_esp32_meshnode_1_testing_elektra42', 'measurement': 'foobar_esp32_meshnode_1_testing_elektra42_sensors', 'gateway': 'foobar'}
2020-01-23T04:07:32+0100 [kotori.daq.storage.influx          ] DEBUG   : Storage success: {'fields': {u'status': 486.0, u'batteryVoltage': 3.104, u'openCircuitVoltage': 0.0, u'batteryChargeEstimate': 0.0, u'temperatureCorrectedVoltage': 18.717, u'batteryHealthEstimate': 100.0, u'batteryTemperature': -125.6, u'mppVoltage': 0.0, u'timeToShutdown': 3121.0, u'isPowerSaveMode': 0.0, u'lowVoltageDisconnectVoltage': 11.9, u'rateBatteryCapacity': 8.0, u'ratedSolarModuleCapacity': 10.0}, 'tags': {'latitude': 52.52, 'longitude': 13.4}, 'time_precision': 'n', 'measurement': 'foobar_esp32_meshnode_1_testing_elektra42_sensors'}

Stimmt.

2020-01-23T04:07:33+0100 [kotori.io.protocol.http            ] DEBUG   : HTTP access: "127.0.0.1" - - [23/Jan/2020:03:07:32 +0000] "POST /api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data HTTP/1.0" 200 82 "-" "NodeMCU (ESP32)"
2020-01-23T04:07:33+0100 [kotori.io.protocol.http            ] DEBUG   : Received HTTP request on uri /api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data, content type is "application/x-www-form-urlencoded"
2020-01-23T04:07:33+0100 [kotori.io.protocol.target          ] DEBUG   : Emitting to target scheme mqtt
2020-01-23T04:07:33+0100 [kotori.io.protocol.forwarder       ] DEBUG   : Forwarding bucket to mqtt:/isems/{address}/{slot}.json with bucket={'body': '{"batteryVoltage":3.104,"status":"486","batteryHealthEstimate":100,"latitude":52.52,"ratedSolarModul [...]', 'json': '{}', 'address': Bunch(source=http:///api/isems/{address:.*}/{slot:(data|event)} [u'POST'], target=mqtt:/isems/{address}/{slot}.json []), 'tdata': Bunch(address=u'testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42', slot=u'data'), 'path': '/api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data', 'request': <Request at 0x7f31284367e8 method=POST uri=/api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data clientproto=HTTP/1.0>, 'data': {}}. Effective target uri is isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json
2020-01-23T04:07:33+0100 [kotori.daq.services.mig            ] DEBUG   : Channel base topic is isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42
2020-01-23T04:07:33+0100 [kotori.daq.services.mig            ] DEBUG   : Processing message on topic 'isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json' with payload '{}'
2020-01-23T04:07:33+0100 [kotori.daq.services.mig            ] DEBUG   : Topology address: {'node': 'ESP32-Meshnode-1-Testing-Elektra42', 'slot': 'data.json', 'realm': 'isems', 'network': 'testdrive', 'gateway': 'foobar'}
2020-01-23T04:07:33+0100 [kotori.daq.services.mig            ] DEBUG   : Storage location: {'node': 'ESP32-Meshnode-1-Testing-Elektra42', 'slot': 'data.json', 'realm': 'isems', 'network': 'testdrive', 'database': 'isems_testdrive', 'measurement_events': 'foobar_esp32_meshnode_1_testing_elektra42_events', 'label': 'foobar_esp32_meshnode_1_testing_elektra42', 'measurement': 'foobar_esp32_meshnode_1_testing_elektra42_sensors', 'gateway': 'foobar'}
2020-01-23T04:07:33+0100 [kotori.daq.storage.influx          ] CRITICAL: Could not format chunk (ex=AssertionError: Data payload is empty): data={}, meta={'node': 'ESP32-Meshnode-1-Testing-Elektra42', 'slot': 'data.json', 'realm': 'isems', 'network': 'testdrive', 'database': 'isems_testdrive', 'measurement_events': 'foobar_esp32_meshnode_1_testing_elektra42_events', 'label': 'foobar_esp32_meshnode_1_testing_elektra42', 'measurement': 'foobar_esp32_meshnode_1_testing_elektra42_sensors', 'gateway': 'foobar'}
2020-01-23T04:07:33+0100 [kotori.daq.services.mig            ] ERROR   : Processing MQTT message failed from topic "isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data.json":
	[Failure instance: Traceback: <type 'exceptions.AssertionError'>: Data payload is empty
	/usr/lib/python2.7/threading.py:801:__bootstrap_inner
	/usr/lib/python2.7/threading.py:754:run
	/opt/kotori/lib/python2.7/site-packages/twisted/_threads/_threadworker.py:46:work
	/opt/kotori/lib/python2.7/site-packages/twisted/_threads/_team.py:190:doWork
	--- <exception caught here> ---
	/opt/kotori/lib/python2.7/site-packages/twisted/python/threadpool.py:250:inContext
	/opt/kotori/lib/python2.7/site-packages/twisted/python/threadpool.py:266:<lambda>
	/opt/kotori/lib/python2.7/site-packages/twisted/python/context.py:122:callWithContext
	/opt/kotori/lib/python2.7/site-packages/twisted/python/context.py:85:callWithContext
	/opt/kotori/lib/python2.7/site-packages/kotori/daq/services/mig.py:252:process_message
	/opt/kotori/lib/python2.7/site-packages/kotori/daq/services/mig.py:289:store_message
	/opt/kotori/lib/python2.7/site-packages/kotori/daq/storage/influx.py:74:write
	/opt/kotori/lib/python2.7/site-packages/kotori/daq/storage/influx.py:233:format_chunk
	]

Analyse

Scheinbar wird als Content-Type noch "application/x-www-form-urlencoded" übermittelt…

daher sehen wir später

Processing message on topic [...] with payload '{}'

obwohl ja eigentlich

Mögliche Ursache

Hm. Hier könnte der Doppelpunkt zuviel sein?

Rückfrage bzgl. Funktionssignatur

@elektra: Du hattest diese Stelle vor ein paar Tagen per Fixed a bug in http post routine. · ISEMS/ISEMS-ESP32@4ae03c0 · GitHub geändert.

Stimmt denn die Dokumentation bei http - NodeMCU Documentation nicht mehr? Dort ist der zweite Parameter in der Signatur noch nicht { headers = headers }, sondern einfach nur ein String. Ich vermute die Signatur hat sich geändert, ergo ist die Dokumentation veraltet und Du hast den Code entsprechend angepasst. Wollte nur nocheinmal sicher gehen.

http.post('http://httpbin.org/post',
  'Content-Type: application/json\r\n',
  '{"hello":"world"}',
  function(code, data)
    if (code < 0) then
      print("HTTP request failed")
    else
      print(code, data)
    end
  end)

Gedanken

Worauf ich hier hinauswill ist, wenn sich die Signatur tatsächlich geändert hat und man dort nun eine Map/Dictionary/Table übergibt, es u.U. auch nicht mehr angebracht ist, den Wert des Header-Feldes mit "\r\n" zu suffixen.

Dann müsste/könnte es korrekt folgendermaßen heißen.

headers = {
  ["Content-Type"] = "application/json",
}

Das wäre ja in der Tat auch deutlich besser aus der Perspektive einer saubereren HTTP-Schnittstelle.

1 Like

Hallo Andreas -

vielen Dank für das Feedback!

Die URL zur Doku wurde im Patch auch korrigiert. Siehe Zeile 94:

Die Doku für NodeMCU auf ESP32 (nicht ESP 8266!) ist hier: Overview - NodeMCU Documentation

Bei der Suche nach der Doku auf den String dev-esp32 in der URL achten :)

Ich werde \r\n jetzt aus dem Code rausnehmen und testen.

I see!

Und den Doppelpunkt nicht vergessen im Header-Namen!

1 Like

Upps. Ich sende die Daten gerade noch mit Doppelpunkt. Sekunde…

Scheint zu klappen!

2020-01-23T17:38:43+0100 [kotori.io.protocol.http            ]
  DEBUG: Received HTTP request on uri /api/isems/testdrive/foobar/ESP32-Meshnode-1-Testing-Elektra42/data,
  content type is "application/json"
1 Like

Da sieht man wieder: Kaum macht man alles richtig, schon funktioniert es! ;)

2 Likes

Wunderbar! Ich habe das Logging nun wieder runtergestellt und Du kannst nun, nachdem beide Varianten funktionieren, gerne im (Test-)Betrieb wieder nur eine Variante (bevorzugt MQTT) anschalten.

Beide Varianten zu haben war nur / vor allem für die Flexibilität gedacht. Es macht praktisch keinen Sinn, die Daten doppelt zu übertragen.

Klar! Ich schalte jetzt HTTP ab.