dsmrreader / dsmr-reader

DSMR-telegram reader & data visualizer for hobbyists. Free for non-commercial use.
https://dsmr-reader.readthedocs.io
Other
463 stars 95 forks source link

Read data from ESP8266 network socket? #187

Closed ThinkPadNL closed 7 years ago

ThinkPadNL commented 7 years ago

Software looks good!

However, i would like to run it inside a VM on my homeserver, which isn't near my smartmete and thus no serial tty port. Would it be possible to include support for reading P1-data from a network socket? I already found this ESP8266 project: http://romix.macuser.nl/software.html and fiddled around with it. With Putty i can receive telegrams, so that part works.

dennissiemensma commented 7 years ago

Hi, thank you for your question.

As a matter of fact, there is API support for posting the readings remotely to the app. By coincidence, I just created #185 with an example script. The only thing is, I do not have any experience with ESP devices, but it should be possible I guess. Any program able to read the network socket, and create a HTTP POST request, should do the trick.

Can you provide me with an example of how you read the network socket with Putty? I might be able to replace the serial input, of the script in #185, with reading data from a network socket.

ThinkPadNL commented 7 years ago

I already fiddled around with some code and with this piece of Python-code, i can read one telegram and then it returns back to the command prompt:

import socket

# creates socket object
s = socket.socket(socket.AF_INET,
                  socket.SOCK_STREAM)

host = '192.168.4.17'
port = 8088 

s.connect((host, port))

telegram = s.recv(1024) # msg can only be 1024 bytes long

#s.close()
print(telegram)

But my Python skills are reeeally basic, so it is getting too complex for me to integrate this into your project.

By the way, i couldn't find it quickly, but does your code verify if the CRC that is sent along with the telegram matches with a checksum that is calculated over the telegram?

dennissiemensma commented 7 years ago

Hi, the CRC verification is lacking, but I think that's a good idea anyway, so I'll create an separate issue for it. I am planning to use another parser in #154, but CRC lacks there as well (for the moment).

dennissiemensma commented 7 years ago

As for the sockets, it's a bit hard scripting remotely, but can you try whether this works?

There are some debugging print, but when succesful, it should print ----- Read telegram ----- followed by the telegram.

from time import sleep
import socket

HOST = '192.168.4.17'
PORT = 8088

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        telegram = read_telegram(s)
        print('----- Read telegram -----')
        print(telegram)

def read_telegram(s):
    """ Reads the socket until we can create a reading point. """

    telegram_start_seen = False
    telegram = ''

    # Just keep fetching data until we got what we were looking for.
    while True:
        print('Listening at socket...')
        socket_data = s.recv(1024)
        print('RECV:', socket_data)
        sleep(1)

        for data in socket_data.split("\n"):
            print("DATA LINE:", data)
            try:
                # Make sure weird characters are converted properly.
                data = str(data, 'utf-8')
            except TypeError:
                pass

            # This guarantees we will only parse complete telegrams. (issue #74)
            if data.startswith('/'):
                telegram_start_seen = True

            # Delay any logging until we've seen the start of a telegram.
            if telegram_start_seen:
                telegram += data

            # Telegrams ends with '!' AND we saw the start. We should have a complete telegram now.
            if data.startswith('!') and telegram_start_seen:
                return telegram

if __name__ == '__main__':
    main()
ThinkPadNL commented 7 years ago

Thank you, i will try that somewhere in the following days :)

dennissiemensma commented 7 years ago

Heb je dit nog kunnen testen of is dit niet meer van toepassing sinds de CRC-check?

ThinkPadNL commented 7 years ago

Nee, helaas nog niet aan toe gekomen. Het staat nog wel op de planning, moet komende week vast wel ergens lukken.

Het is zeker nog van toepassing. Er ligt nu een 5m. lange kabel van mijn meterkast naar m'n server om de P1-poort uit te lezen. Als ik die kabel kan vervangen door een draadloze oplossing dan heeft dat zeker mijn voorkeur. Dit issue heeft overigens geen haast hoor.

ThinkPadNL commented 7 years ago

Ik had eindelijk wat tijd om hier weer naar te kijken. Een RJ11-splitter op de P1-poort gezet zodat dsmr-reader gewoon door kan blijven gaan met loggen en op de tweede uitgang de ESP8266 geknupt. Script wat je hierboven plaatste even geprobeerd, dit is de output:

domotica@domotica-vm:~$ python /home/domotica/p1smartmeter/esp.py
Listening at socket...
('RECV:', '/KFM5KAIFA-METER\r\n\r\n1-3:0.2.8(42)\r\n0-0:1.0.0(170104195652W)\r\n0-0:96.1.1(SERIAL_OF_MY_METER_HERE)\r\n1-0:1.8.1(002015.856*kWh)\r\n1-0:1.8.2(002048.232*kWh)\r\n1-0:2.8.1(000000.000*kWh)\r\n1-0:2.8.2(000000.000*kWh)\r\n0-0:96.14.0(0002)\r\n1-0:1.7.0(00.379*kW)\r\n1-0:2.7.0(00.000*kW)\r\n0-0:96.7.21(00005)\r\n0-0:96.7.9(00003)\r\n1-0:99.97.0(1)(0-0:96.7.19)(000101000001W)(2147483647*s)\r\n1-0:32.32.0(00000)\r\n1-0:32.36.0(00000)\r\n0-0:96.13.1()\r\n0-0:96.13.0()\r\n1-0:31.7.0(001*A)\r\n1-0:21.7.0(00.379*kW)\r\n1-0:22.7.0(00.000*kW)\r\n0-1:24.1.0(003)\r\n0-1:96.1.0(SERIAL_OF_MY_METER_HERE)\r\n0-1:24.2.1(170104190000W)(01539.233*m3)\r\n!DA5E\x00\r\n')
('DATA LINE:', '/KFM5KAIFA-METER\r')
('DATA LINE:', '\r')
('DATA LINE:', '1-3:0.2.8(42)\r')
('DATA LINE:', '0-0:1.0.0(170104195652W)\r')
('DATA LINE:', '0-0:96.1.1(SERIAL_OF_MY_METER_HERE)\r')
('DATA LINE:', '1-0:1.8.1(002015.856*kWh)\r')
('DATA LINE:', '1-0:1.8.2(002048.232*kWh)\r')
('DATA LINE:', '1-0:2.8.1(000000.000*kWh)\r')
('DATA LINE:', '1-0:2.8.2(000000.000*kWh)\r')
('DATA LINE:', '0-0:96.14.0(0002)\r')
('DATA LINE:', '1-0:1.7.0(00.379*kW)\r')
('DATA LINE:', '1-0:2.7.0(00.000*kW)\r')
('DATA LINE:', '0-0:96.7.21(00005)\r')
('DATA LINE:', '0-0:96.7.9(00003)\r')
('DATA LINE:', '1-0:99.97.0(1)(0-0:96.7.19)(000101000001W)(2147483647*s)\r')
('DATA LINE:', '1-0:32.32.0(00000)\r')
('DATA LINE:', '1-0:32.36.0(00000)\r')
('DATA LINE:', '0-0:96.13.1()\r')
('DATA LINE:', '0-0:96.13.0()\r')
('DATA LINE:', '1-0:31.7.0(001*A)\r')
('DATA LINE:', '1-0:21.7.0(00.379*kW)\r')
('DATA LINE:', '1-0:22.7.0(00.000*kW)\r')
('DATA LINE:', '0-1:24.1.0(003)\r')
('DATA LINE:', '0-1:96.1.0(SERIAL_OF_MY_METER_HERE)\r')
('DATA LINE:', '0-1:24.2.1(170104190000W)(01539.233*m3)\r')
('DATA LINE:', '!DA5E\x00\r')
----- Read telegram -----
!DA5E4.2.1(170104190000W)(01539.233*m3)383134)7483647*s)
Listening at socket...

En dan na Listening at socket begint bovenstaande weer opnieuw. Volgens mij zijn we al een heel eind dan, of niet?

Ik hoop dat je hier iets mee kunt. Ik laat de ESP8266 eraan hangen, mocht je een ander script hebben dan kan ik dat eenvoudig opnieuw testen dan.

dennissiemensma commented 7 years ago

Mooi, ik had niet verwacht dat die meteen zo ver zou komen.

Hopelijk gaat deze dan ook in 1 x goed, let even goed op de stappen onderaan het codeblok, een variant op deze:

from time import sleep
import socket

import requests

HOST = '192.168.4.17'
PORT = 8088

API_SERVERS = (
    ('http://HOST-OR-IP/api/v1/datalogger/dsmrreading', 'api-key'),
)

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        telegram = read_telegram(s)
        print('Read telegram:')
        print(telegram)

        for current_server in API_SERVERS:
            api_url, api_key = current_server
            send_telegram(telegram, api_url, api_key)
            print('Sent telegram to:', api_url)

        sleep(1)

def read_telegram(s):
    """ Reads the socket until we can create a reading point. """

    telegram_start_seen = False
    telegram = ''

    # Just keep fetching data until we got what we were looking for.
    while True:
        print('Listening at socket...')
        socket_data = s.recv(1024)

        for data in socket_data.split("\n"):
            try:
                # Make sure weird characters are converted properly.
                data = str(data, 'utf-8')
            except TypeError:
                pass

            # This guarantees we will only parse complete telegrams. (issue #74)
            if data.startswith('/'):
                telegram_start_seen = True

            # Delay any logging until we've seen the start of a telegram.
            if telegram_start_seen:
                telegram += data

            # Telegrams ends with '!' AND we saw the start. We should have a complete telegram now.
            if data.startswith('!') and telegram_start_seen:
                return telegram

def send_telegram(telegram, api_url, api_key):
    # Register telegram by simply sending it to the application with a POST request.
    response = requests.post(
        api_url,
        headers={'X-AUTHKEY': api_key},
        data={'telegram': telegram},
    )

    # You will receive a status 200/201 when successful.
    if response.status_code not in (200, 201):
        # Or you will find the error (hint) in the response body on failure.
        print('[!] Error: {}'.format(response.text))

if __name__ == '__main__':
    main()

Stappen vóór uitvoer:

ThinkPadNL commented 7 years ago

Gaat nog niet helemaal goed:

domotica@domotica-vm:~$ python /home/domotica/p1smartmeter/esp.py
Listening at socket...
Read telegram:
!4C194.2.1(170104210000W)(01540.161*m3)383134)7483647*s)
[!] Error: Failed to parse telegram
('Sent telegram to:', 'http://192.168.4.21/api/v1/datalogger/dsmrreading')
Listening at socket...
Read telegram:
!C9634.2.1(170104210000W)(01540.161*m3)383134)7483647*s)
[!] Error: Failed to parse telegram

Dit gaat de hele tijd zo door, er wordt niet één keer een telegram succesvol verzonden naar dsmr-reader.

In het script heb ik de connectie als volgt ingevuld (api-key wat ingekort):

API_SERVERS = (
    ('http://192.168.4.21/api/v1/datalogger/dsmrreading', '5S7SVP.......PO6ZJB'),
)

Dat klopt? De foutmelding Failed to parse telegram lijkt mij afkomstig van de API, omdat ik hem in het script zelf nergens terugvind.

Komt dit door dat er wellicht een carriage return / linefeed wordt meegestuurd (of juist niet)?

dennissiemensma commented 7 years ago

Ja, volgens mij zie ik het al en klopt het wat je al vermoedt.

Ik splits het bericht per linefeed, want die krijg ik normaal terug via de seriele poort, maar ik zet die niet terug bij elke regel.

Dus wellicht kun je van de regel:

telegram += data

Dit maken:

telegram += data + "\n"
ThinkPadNL commented 7 years ago

Jij bent echt de held van de dag! Aanpassing gedaan en het lijkt nu perfect te werken! 👍 👍 👍

domotica@domotica-vm:~$ python /home/domotica/p1smartmeter/esp.py
Listening at socket...
Read telegram:
/KFM5KAIFA-METER

1-3:0.2.8(42)
0-0:1.0.0(170104223828W)
0-0:96.1.1(SERIALNUMBERHERE)
1-0:1.8.1(002015.856*kWh)
1-0:1.8.2(002049.345*kWh)
1-0:2.8.1(000000.000*kWh)
1-0:2.8.2(000000.000*kWh)
0-0:96.14.0(0002)
1-0:1.7.0(00.258*kW)
1-0:2.7.0(00.000*kW)
0-0:96.7.21(00005)
0-0:96.7.9(00003)
1-0:99.97.0(1)(0-0:96.7.19)(000101000001W)(2147483647*s)
1-0:32.32.0(00000)
1-0:32.36.0(00000)
0-0:96.13.1()
0-0:96.13.0()
1-0:31.7.0(001*A)
1-0:21.7.0(00.257*kW)
1-0:22.7.0(00.000*kW)
0-1:24.1.0(003)
0-1:96.1.0(....)
0-1:24.2.1(170104220000W)(01540.272*m3)
!671D

('Sent telegram to:', 'http://192.168.4.21/api/v1/datalogger/dsmrreading')
Listening at socket...

Het proces dsmr_datalogger in supervisor zorgt normaal voor het uitlezen van de ttyUSB0 toch? Als ik die stopzet en snel even een nieuwe supervisor config maak voor dit Python dingetje, dan zou alles net zo moeten werken als met een fysieke kabel toch? Dan laat ik hem vannacht namelijk even draaien zodat ik morgenochtend een update kan geven. Hoe kan ik rechtstreeks de database querien? Dat gaat wat sneller dan dat ik 100x moet klikken om te kijken of hij ook waardes heeft gemist.

Edit: Ik probeer het op de VM waarin ik dsmr-reader heb draaien, uit te voeren maar dan krijg ik telkens deze foutmelding:

(dsmrreader) dsmr@dsmrreader-vm:~$ python /home/dsmr/dsmr-reader/esp_sockets.py
Listening at socket...
Traceback (most recent call last):
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 78, in <module>
    main()
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 20, in main
    telegram = read_telegram(s)
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 43, in read_telegram
    for data in socket_data.split("\n"):
TypeError: a bytes-like object is required, not 'str'
(dsmrreader) dsmr@dsmrreader-vm:~$

In die andere VM waar ik het net mee probeerde werkte het wel, dus er zal iets verschillend zijn, maar wat?

dennissiemensma commented 7 years ago
ThinkPadNL commented 7 years ago

Helaas, heb het aangepast met die b erin (jouw code gekopieerd) maar krijg nog steeds een foutmelding:


(dsmrreader) dsmr@dsmrreader-vm:~$ python /home/dsmr/dsmr-reader/esp_sockets.py
Listening at socket...
Traceback (most recent call last):
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 78, in <module>
    main()
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 20, in main
    telegram = read_telegram(s)
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 43, in read_telegram
    for data in socket_data.split("\n"):
TypeError: a bytes-like object is required, not 'str'

Ik heb het op die andere VM in een supervisor gegooid en laat het vannacht draaien. Het in deze VM werkend krijgen komt wel. Ik ben veel meer benieuwd naar hoe stabiel zich dit verhoud als het een aantal uren draait. Het script doet geen check op CRC zie ik, doet de API dat wel? Het voordeel in dit geval is dat de code op de ESP8266 ook al een check op de CRC doet voordat hij het het netwerk op duwt. Maar tussen ESP en Python-script kan het dus nog misgaan en wellicht zijn er ook mensen die de meter gaan uitlezen met iets wat geen CRC check doet.

Ik ga nu slapen, ik laat het morgen weten hoe het vannacht heeft gedraaid. We komen er wel samen!😎

dennissiemensma commented 7 years ago

Ik ben niet helemaal wakker zie ik. Je foutmeldingen geven een compleet ander stuk code aan als oorzaak, dòh. Alleen wellicht dat de b op die plek uiteindelijk ook nodig was.

Kun je hetzelfde truukje halen bij for data in socket_data.split("\n"):? Dus dat wordt for data in socket_data.split(b"\n"): Het is jammer dat ik niet exact dezelfde setup heb hier, dan kan ik je direct werkende code geven mja.

Wat betreft de CRC-check, die wordt op het laatst moment gedaan, vlak voordat de meting wordt weggeschreven in de database. Feitelijk doet de datalogger nu ook al geen CRC-check, maar gebeurt dat indirect in de achterliggende code.

dsmr_datalogger.services.telegram_to_reading(...) (CRC):

ThinkPadNL commented 7 years ago

Helaas, ook dan weer een foutmelding:

(dsmrreader) dsmr@dsmrreader-vm:~$ python /home/dsmr/dsmr-reader/esp_sockets.py                                         Listening at socket...
Traceback (most recent call last):
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 78, in <module>
    main()
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 20, in main
    telegram = read_telegram(s)
  File "/home/dsmr/dsmr-reader/esp_sockets.py", line 56, in read_telegram
    telegram += data + b"\n"
TypeError: Can't convert 'bytes' object to str implicitly
(dsmrreader) dsmr@dsmrreader-vm:~$

Voor de volledigheid hier nog even het gehele script waarmee ik bovenstaande error krijg:

from time import sleep
import socket

import requests

HOST = '192.168.4.17'
PORT = 8088

API_SERVERS = (
    ('http://192.168.4.21/api/v1/datalogger/dsmrreading', '5S7SV....JB'),
)

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        telegram = read_telegram(s)
        print('Read telegram:')
        print(telegram)

        for current_server in API_SERVERS:
            api_url, api_key = current_server
            send_telegram(telegram, api_url, api_key)
            print('Sent telegram to:', api_url)

        sleep(1)

def read_telegram(s):
    """ Reads the socket until we can create a reading point. """

    telegram_start_seen = False
    telegram = ''

    # Just keep fetching data until we got what we were looking for.
    while True:
        print('Listening at socket...')
        socket_data = s.recv(1024)

        for data in socket_data.split(b"\n"):
            try:
                # Make sure weird characters are converted properly.
                data = str(data, 'utf-8')
            except TypeError:
                pass

            # This guarantees we will only parse complete telegrams. (issue #74)
            if data.startswith('/'):
                telegram_start_seen = True

            # Delay any logging until we've seen the start of a telegram.
            if telegram_start_seen:
                telegram += data + b"\n"

            # Telegrams ends with '!' AND we saw the start. We should have a complete telegram now.
            if data.startswith('!') and telegram_start_seen:
                return telegram

def send_telegram(telegram, api_url, api_key):
    # Register telegram by simply sending it to the application with a POST request.
    response = requests.post(
        api_url,
        headers={'X-AUTHKEY': api_key},
        data={'telegram': telegram},
    )

    # You will receive a status 200/201 when successful.
    if response.status_code not in (200, 201):
        # Or you will find the error (hint) in the response body on failure.
        print('[!] Error: {}'.format(response.text))

if __name__ == '__main__':
    main()

Zoals gezegd heb ik het in die andere VM wel draaiend, kan ik daar mooi kijken of het blijft draaien en kun jij rustig kijken of je nog een idee hebt. Nu echt slapen 😉

dennissiemensma commented 7 years ago

Wellicht is dan de oplossing om de b in de laatste telegram += data + b"\n" weer weg te halen. En dan alleen degene te behouden die als tweede is toegevoegd in de split().


Ik kan het toch soort van lokaal reproduceren op de shell en ik krijg voor de drie varianten ook exact dezelfde foutmeldingen:

>>> [str(x, 'utf-8') + "\n" for x in b'abcde\nfghij\nklnmop'.split("\n")]

TypeError: a bytes-like object is required, not 'str'

Daarna de b voor de + "\n":

>>> [str(x, 'utf-8') + b"\n" for x in b'abcde\nfghij\nklnmop'.split("\n")]

TypeError: a bytes-like object is required, not 'str'

Daarna de b voor de split("\n"):

>>> [str(x, 'utf-8') + b"\n" for x in b'abcde\nfghij\nklnmop'.split(b"\n")]

TypeError: Can't convert 'bytes' object to str implicitly

Uiteindelijk de eerst toegevoegde b weghalen (gaat goed):

>>> [str(x, 'utf-8') + "\n" for x in b'abcde\nfghij\nklnmop'.split(b"\n")]

['abcde\n', 'fghij\n', 'klnmop\n']

Ben benieuwd :]

ThinkPadNL commented 7 years ago

Het script is netjes blijven draaien vannacht:

supervisor> status
p1esp8266                        RUNNING   pid 19614, uptime 8:34:55

Daarna in de database gekeken op de dsmr-reader VM en hij lijkt continu te hebben gelogd, nice!

Aanpassing gedaan in het script op de dsmr-reader VM zoals jij zegt en dan knalt het script er niet meer uit, maar krijg ik wel foutmeldingen:

(dsmrreader) dsmr@dsmrreader-vm:~/dsmr-reader$ python /home/dsmr/dsmr-reader/esp_sockets.py
Listening at socket...
Read telegram:
!ECBD4.2.1(170105080000W)(01540.862*m3)383134)7483647*s)
[!] Error: Failed to parse telegram
Sent telegram to: http://192.168.4.21/api/v1/datalogger/dsmrreading
Listening at socket...
Read telegram:
!F0144.2.1(170105080000W)(01540.862*m3)383134)7483647*s)
[!] Error: Failed to parse telegram
Sent telegram to: http://192.168.4.21/api/v1/datalogger/dsmrreading
dennissiemensma commented 7 years ago

Zie je onder Read telegram: wel een compleet telegram staan? Of slechts een deel zoals je letterlijk hierboven post?

ThinkPadNL commented 7 years ago

De output hierboven is wat ik zie, heb niks eruit geknipt.

dennissiemensma commented 7 years ago

Okee, dan gaat er nu weer iets mis met het samenvoegen. Dit zie ik al terug in een eerder bericht. Ik denk dat je dan even moet kijken of het splitsen op de linefeed goed gaat.

Maar wellicht eerst even iets anders proberen. Ik zie namelijk dat hier recv() een compleet telegram teruggeeft. Je zou kunnen proberen om die data compleet op te sturen en te hopen dat de recv() alles in 1x opgehaald heeft.

Script is ook even ingekort zonder comments enzo. Je kunt alles onder API_SERVERS vervangen met:

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        socket_data = s.recv(1024)
        telegram = str(socket_data, 'utf-8')

        print('Read telegram:')
        print(telegram)

        for current_server in API_SERVERS:
            api_url, api_key = current_server
            print('Sending telegram to:', api_url)
            send_telegram(telegram, api_url, api_key)

        sleep(1)

def send_telegram(telegram, api_url, api_key):
    response = requests.post(
        api_url,
        headers={'X-AUTHKEY': api_key},
        data={'telegram': telegram},
    )

    if response.status_code not in (200, 201):
        print('[!] API error: {}'.format(response.text))

if __name__ == '__main__':
    main()

Wellicht werkt dit meteen? read_telegram() is dus flink ingekort.

ThinkPadNL commented 7 years ago

Dat werkt meteen idd!

(dsmrreader) dsmr@dsmrreader-vm:~/dsmr-reader$ python /home/dsmr/dsmr-reader/esp_sockets.py
Listening at socket...
Read telegram:
/KFM5KAIFA-METER

1-3:0.2.8(42)
0-0:1.0.0(170105202122W)
...knip, rest van telegram...
0-1:96.1.0(47......34)
0-1:24.2.1(170105200000W)(01544.140*m3)
!0720

Sending telegram to: http://192.168.4.21/api/v1/datalogger/dsmrreading
Listening at socket...

👍

Trouwens, volgens mij doet deze code geen foutafhandeling toch? Stel dat m'n wifi even hikt, dan gaat het script onderuit denk ik?

dennissiemensma commented 7 years ago

Top, mooi man. Waarschijnlijk kan die while True: er dan ook uit, want hij doet toch al meteen een return na de eerste recv().

Ik heb overigens in al je voorbeelden ook nog even het serienummer van je gasmeter weggehaald (47......34), de regel met 0-1:96.1.0. Er staan dus twee serienummers in DSMR v4 telegrammen.

ThinkPadNL commented 7 years ago

Verkorte variant werkt ook! En bedankt voor het aanpassen. Zal er op letten, hoef idd niet m'n serienummers de wereld in te gooien.

Voor de volledigheid nog even weer het complete script:

from time import sleep
import socket

import requests

HOST = '192.168.4.17'
PORT = 8088

API_SERVERS = (
    ('http://192.168.4.21/api/v1/datalogger/dsmrreading', '5S7SVP......O6ZJB'),
)

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        socket_data = s.recv(1024)
        telegram = str(socket_data, 'utf-8')

        print('Read telegram:')
        print(telegram)

        for current_server in API_SERVERS:
            api_url, api_key = current_server
            print('Sending telegram to:', api_url)
            send_telegram(telegram, api_url, api_key)

        sleep(1)

def send_telegram(telegram, api_url, api_key):
    response = requests.post(
        api_url,
        headers={'X-AUTHKEY': api_key},
        data={'telegram': telegram},
    )

    if response.status_code not in (200, 201):
        print('[!] API error: {}'.format(response.text))

if __name__ == '__main__':
    main()

Maar wat ik in m'n vorige comment zei, hoe zit het met foutafhandeling? De kans is natuurlijk aanwezig dat de ESP een keer vastloopt/netwerk een hikje geeft. Moet daar nog iets voor ingebouwd?

dennissiemensma commented 7 years ago

Top, bedankt voor je geduld en tests.

Ik sluit deze, mocht je toch nog nazorg hebben hierover dan hoor ik het hier wel.

ThinkPadNL commented 7 years ago

De P1-kabel kan ik afkoppelen, mooi :) Maar hoe integreer ik dit in dsmr-reader ? Kan ik in supervisor het dsmr_datalogger component uitschakelen? Dat zorgt voor het uitlezen van de ttyUSB0 toch? Of moet dat ook blijven draaien als ik het via de API erin duw?

dennissiemensma commented 7 years ago

Ter referentie. Ik heb een Pi2 aan de meter hangen met kabel. De datalogger is daar uitgeschakeld en vervangen door de variant in bovenstaande docs link. In Supervisor ziet dat er zo uit:

dsmr_backend                     RUNNING
dsmr_client_datalogger           RUNNING
dsmr_webinterface                RUNNING

dsmr_client_datalogger (of hoe je hem ook wilt noemen) stuurt dus de metingen via de API door (ondanks dat de applicatie op hetzelfde apparaat staat). Daarnaast stuurt die ook de metingen door naar een andere, Pi3, die heel ergens anders staat. (als semi-productie-testomgeving).

dennissiemensma commented 7 years ago

Bij mij doet de dsmr_client_datalogger dus de uitlezingen via de P1-kabel, bij jou zou dat via die ESP gaan (beiden slechts transportmiddel).

ThinkPadNL commented 7 years ago

Ik heb de dsmr_datalogger uitgeschakeld in supervisor en een eigen supervisor config gemaakt voor het Python-script wat je hebt gemaakt om de ESP8266 uit te lezen. Voor iemand die dit in de toekomst leest en ook geïnteresseerd is om de P1-poort van de slimme meter draadloos uit te lezen, ik heb het hele spulletje (incl. firmware voor ESP8266) hier online gezet.

Dennis, bedankt voor je hulp om dit werkend te krijgen!

Calimerorulez commented 7 years ago

Ik was hier toevallig net naar op zoek; eens kijken of het ook bij mij lukt :-D

Calimerorulez commented 7 years ago

Als ik handmatig het script van ThinkPadNL zijn site draai, krijg ik na enkele succesvolle telegrams:

Sending telegram to: http://-ip-/api/v1/datalogger/dsmrreading [!] API error: Invalid data Read telegram:

en dit blijft dan door'loop'en in de invalid data...

Op de 'automaat' via supervisor stopt hij ook met het verzenden van data na een paar succesvolle telegrams.

dennissiemensma commented 7 years ago

@Calimerorulez welke versie van DSMR heeft je meter?

Zie je dit als een van de eerste regels:

1-3:0.2.8(50) (v5.0)

of dit:

1-3:0.2.8(42) (v4.2)

Calimerorulez commented 7 years ago

Hey Dennis,

Dat is 1-3:0.2.8(42)

Ik dacht eerst dat het aan de combinatie met domoticz lag, omdat domoticz de netwerkpoort van de esp ook uitleest, maar als ik domoticz stop, wijzigt er niks aan het foutgaan.

dennissiemensma commented 7 years ago

Er komt een nieuwe patch de komende dagen, waarin beter wordt gelogd wat alle telegram-input is en welke falen/goed zijn (#224). Ik denk dat het even daarop wachten is en dat debuggen makkelijker maakt.

Calimerorulez commented 7 years ago

Ok, ik wacht af. Dank voor het mooie werk.

Ik merk overigens dat als ik een putty sessie open naar mijn esp, dat putty vanzelf afsluit na enkele geldige telegrammen, dus ik denk dat er in de esp-code/een telegram een raar (of een escape) character zit of iets dergelijks... Ik zal eens proberen te debuggen.

edit: een 'nc' sessie naar mijn esp stopt er ook na enkele telegrammen mee; het lijkt erop dat de verbinding door de esp gesloten wordt o.i.d.

Ik gebruik domoticz ook. Als domoticz draait, dan stoort de boel elkaar lijkt het wel. Start ik domoticz, dan maakt domoticz een connectie en pollt goed. Zodra ik het script van ThinkPadNL ernaast start, dan logt domoticz een error, over een verloren connectie. Het script van ThankPadNL pollt wel goed, totdat domoticz een nieuwe poging waagt, na 30 seconden, om te connecten. Dan geeft dsmr-reader een 400 error (print(response.status_code)), ipv de correcte 200 status.

Stop ik domoticz helemaal, dan gaat het script van ThinkPadNL een tijdje langer goed, maar ook daarna komen er weer http 400-errors :-)

dennissiemensma commented 7 years ago

Ik heb nog even gekeken. Die Invalid data melding komt wanneer het POST-request op de API geen 'telegram' veld bevat.

Ik heb nu #224 klaar en daar zit ook betere logging in voor de API. Zodat je straks, als het goed is, kunt zien wat er binnenkomt.

Dit draait nu een dagje bij mij in productie en wanneer dat goed gaat komt de patch uit (waarschijnlijk morgen dus), samen met wat andere kleine dingetjes.

dennissiemensma commented 7 years ago

@Calimerorulez zojuist is de v1.5.2 patch uit met verbeterde logging. Aan de hand voor je input heb ik ook betere logging toegevoegd aan de API.

Wil je updaten naar de laatste versie?

Als dat is gelukt staan er als het goed is logfiles hier:

sudo su - dsmr

tail -f logs/dsmrreader.log 

(CTRL + C om het tailen af te breken)

Wil je aangeven of die nu een betere foutmelding geven? De 'Invalid data` zou in de logs nu zoiets moeten tonen:

API validation failed with POST data: X

Waarbij X de data is die meegestuurd wordt (dat zou een compleet telegram moeten zijn).

Let erop dat je hier (of uberhaupt op internet) niet je serienummers van je (2?) meters plaatst wanneer je een voorbeeld plakt. Dat zijn lange cijferreeksen van zo rond de 30 tekens aaneensluitend.

Ook de base64 data graag hier niet plaatsen. Mocht dat nodig zijn dan mag dat per e-mail.

ThinkPadNL commented 7 years ago

Het uitlezen van de ESP8266 gaat hier nog steeds uitstekend: p1esp8266 RUNNING pid 14642, uptime 4 days, 0:22:53 Nog steeds erg blij mee dat het nu werkt, geen lelijk USB-serial kabel meer in het zicht naar m'n server toe.

Als bij @Calimerorulez de boel er dus uit klapt dan ligt het waarschijnlijk aan de code op je ESP/de ESP zelf. Welke code gebruik je? Die van mijn Bitbucket ?

Calimerorulez commented 7 years ago

@dennissiemensma in de log vind ik: [2017-01-10 18:00:35,765] WARNING @ views | API validation failed with POST data: <QueryDict: {'telegram': ['']}>

iedere seconde. Draai ik het schript van ThinkPadNL, dan zie ik 3 base64 encoded messages komen (tussendoor bovenstaande fout) en daarna weer continu om de seconde de bovenstaande fout.

@ThinkPadNL Ik gebruik de code van Romix, vanaf https://github.com/ESP8266nu/ESPEasyPluginPlayground. Ik heb espeasy ingesteld zoals vermeld in de documentatie van Romix.

Als ik de komende dagen meer tijd heb, dan zal ik mijn hele esp upgraden. Ik draai nu op build 138.

Ik zal jouw code eens flashen. Ik snap alleen het nut niet van de specificatie van het te gebruiken protocol op de Config pagina van espeasy. Daar heb ik nu Domoticz HTTP staan.

edit: _P110_P1WifiGateway.ino van jouw bitbucket is aan de code te zien een html-pagina.. ;-)

ThinkPadNL commented 7 years ago

@Calimerorulez Je zegt 'Iedere seconde draai ik het script van ThinkPad'. Maar hoe bedoel je dat precies? Je start het script en die blijft luisteren naar de ESP8266. Eén keer starten is dus genoeg.

Ik heb de firmware van http://www.esp8266thingies.nl/wp/software/ gepakt, R124b. Die werkt dus, zoals hierboven in de output van supervisor te zien dus prima.

Bedankt voor de tip m.b.t. het .ino bestand. Ik heb het .ino bestand er nu op gezet ipv HTML-code.

Calimerorulez commented 7 years ago

Dank je voor je reactie. De tekst "iedere seconde", dat hoorde bij de regel ervoor. Ik start jouw script gewoon eenmalig op, en laat dat draaien.

Ik heb net de code van espeasy gepulled, P110 erin gehangen, want P1 smart meter zit er standaard niet in als device, dus heb ik de firmware via Arduino IDE zelf gecompileerd en geupload.

Ik heb een nieuwe Wemos D1 R2 genomen, geflasht, maar geen verschil. Als ik putty 'raw' connect op de esp poort, dan vliegt ie er ook na 3 telegrams uit. Ik zal R124b proberen. Draai jij ook Domoticz?

ThinkPadNL commented 7 years ago

Nee, geen Domoticz. Ik lees de P1 alleen uit naar dsmr-reader. Ik gebruik Home Assistant voor m'n domotica, maar doe daarin niks met de P1-poort.

Stappenplan:

Als je dat allemaal hebt gedaan zou je die R124b firmware op de Wemos D1 moeten hebben. In de webinterface kun je dan de baudrate instellen en de poort (8088 bijv., die staat in mijn script ook ingevuld).

Die hele combinatie werkt dus erg betrouwbaar en zonder nukken bij mij. Met bovenstaande stappen hoef je dus ook niks te compilen of extra plugins toe te voegen. Die R124b.bin is voldoende aangezien daar alles al in zit.

Calimerorulez commented 7 years ago

Dank je, het werkt nu een stuk stabieler denk ik, hij blijft doorlopen. Ik heb de esp maar uit Domoticz verwijderd, want die blijft de connectie van jouw script om zeep helpen. Zodra Domoticz na 30 seconden opnieuw probeert te connecten naar de ESP (want Domoticz begint te klagen over een verloren connectie, als ik jouw script opstart), dan krijg ik weer de betreffende fout. Het lijkt erop dat twee processen/connecties tegelijk naar de esp de boel ophangt.

Ik experimenteer weer verder :-)

Dank!

ThinkPadNL commented 7 years ago

Goedzo. Waarom heb je Domoticz nog nodig als je dsmr-reader hebt draaien? Die kan hetzelfde en meer toch? ;)

Calimerorulez commented 7 years ago

Koudwatervrees ;-)

dennissiemensma commented 7 years ago

Wellicht dan een splitter ertussen zetten, dan kun je beide gebruiken ;-)

Wat betreft je foutmelding: API validation failed with POST data: <QueryDict: {'telegram': ['']}>

Dit betekent dat er geen data binnenkomt, de telegram string is leeg. Dus de API call wordt gedaan, maar de data mist.

ThinkPadNL commented 7 years ago

Hoe draait het nu @Calimerorulez ? Bij mij nog steeds als een trein, goede combi op deze manier. Ga binnenkort verhuizen en ben erg blij dat ik het op deze manier werkend heb kunnen krijgn.

Een ESP8266 in de meterkast is genoeg. Voeding kan dan via USB-poort van m'n router en m'n server waar VM met dsmr-reader kan bijv. op zolder komen te staan.

Calimerorulez commented 7 years ago

Hier ook als een trein, sinds ik hem uit domoticz gemikt heb 5 dagen geleden. Nogmaals dank voor de code en hulp :-)

ThinkPadNL commented 7 years ago

Vandaag was de boel even stuk. Ik opende de webinterface van dsmr-reader toevallig even en zag dat er al 2 uur geen data meer was binnengekomen ( #208 is best wel gewenst 😉 ). De ESP herstart maar toen leek er nog steeds geen data binnen te komen. In supervisor het uitleesscript herstart en toen leek er weer data binnen te komen. Kan in de logging niet echt een duidelijke oorzaak vinden. Als #208 is geïmplementeerd dan kan ik ook sneller de logging controleren mocht het uitlezen weer stagneren.

Nog wel een verzoek, ik heb de logging in /var/log/supervisor bekeken en zag daar veel entries staan:

Sending telegram to: http://192.168.4.21/api/v1/datalogger/dsmrreading
[!] API error: Invalid data
Read telegram:

Wat ik eigenlijk mis is een datum en timestamp in de supervisor logging. Is dat eenvoudig toe te voegen in het ESP8266 uitleesscript wat ik gebruik?

dennissiemensma commented 7 years ago

Bovenin het script:

from datetime import datetime

Op de plek waar je de logging wil hebben:

print(datetime.now())