Inhaltsverzeichnis

Iskraemeco MT 372

Genauer Typ: MT372-D1A51-B11L11-M2K0agZ Das „K0“ bedeutet, dass er über eine bidirektionale Kundenschnittstelle nach DIN EN 62056-21 verfügt. Zusätzlich trägt der Zähler das DLMS-Logo.

Hardware

Der Zähler kann mittels IR-Schreib-Lesekopf bei 300bd, 7E1 nach DIN EN 62056-21 ausgelesen werden.

Kommunikation

Da der Zähler mit:

/ISK5\2MT372-0002<\r><\n>

antwortet, kann man den Zähler auf 9600bd umschalten.

Beispiel vzlogger.conf

Im Frontend wird ein Kanal 'El.Energie (Zählerstände)' angelegt. Als Auflösung wird '1' eingetragen. Der Style sollte 'steps' sein. Ein Haken bei 'Öffentlich' und bei 'Cookie' machen. Weiter sollte der 'Initialverbrauch' (aktueller Zählerstand) eingetragen werden. Die UUIDs werden automatisch erzeugt und können durch klicken auf das (i) rechts neben dem Kanal sichtbar gemacht werden. Wenn alles funktioniert, kann 'verbosity' in der vzlogger.conf auf 0 gesetzt werden.

vzlogger.conf
{
  "retry": 0,
  "verbosity": 15,
  "log": "/var/log/vzlogger/vzlogger.log",
  "local": {
    "enabled": false,
    "port": 8081,
    "index": true,
    "timeout": 0,
    "buffer": 0
  },
  "meters": [
    {
      "enabled": true,
      "allowskip": false,
      "interval": -1,
      "aggtime": -1,
      "aggfixedinterval": false,
      "channels": [
        {
          "uuid": "hier die UUID des im Frontend angelegten Kanals (El. Energie (Zählerstand) ) einfügen",
          "identifier": "1.8.0",
          "api": "volkszaehler",
          "middleware": "http://localhost/middleware.php",
          "aggmode": "max",
          "duplicates": 0
        }
      ],
      "protocol": "d0",
      "device": "/dev/ttyUSB0",
      "pullseq": "2F3F210D0A",
      "ackseq": "063035300d0a",
      "baudrate": 300,
      "baudrate_read": 9600,
      "parity": "7e1",
      "wait_sync": "off",
      "read_timeout": 10,
      "baudrate_change_delay": 0
    }
  ]
}

Test mit HTerm

Zum Testen wurde HTerm verwendet, da test1107 nach dem der Zähler mit seiner Typenbezeichnung geantwortet hatte, in ein Timeout lief (Tout).
Einzustellen sind:
Port: bei Windows:verwendeter COM-Anschluss des USB-Adapters, bei Linux z.B. /dev/ttyUSB0
Baud: 300
Data: 7
Stop: 1
Parity: Even
Bei „input control“: Send on Enter auf „CR-LF“
Auf „Connect“ klicken, dann im Eingabefeld “/?!“ (ohne Anführungszeichen) eingeben und Enter drücken. Wenn im Feld „received data“ was erscheint, funktioniert die Kommunikation prinzipiell.

Möchte man dem Zähler seinen Standarddatensatz entlocken muss mit hterm nach „/?!“ innerhalb von 1,5s

<ACK>000<CR><LF> (hex 06 30 30 30 0D 0A)

gesendet werden. Das ist schwierig zu realisieren.
Stattdessen einfach ein paar (hex 00) zwischen Inistialisierung und <ACK> stecken und alles in einer Zeichefolge aus hterm senden. Ab sechs NUL aufwärts klappt es.

Beispieldaten

Gesendet:

/?! CR LF NUL NUL NUL NUL NUL NUL ACK 0 0 0 CR LF (hex 2F 3F 21 0D 0A 00 00 00 00 00 00 00 06 30 30 30 0D 0A)

Antwort des Zählers im hterm:

/ISK5\2MT372-0002<\r><\n>
/?!<\r><\n><6>000<\r><\n>
<2>F.97.0(00000000)<\r><\n>
0.0.0(58231541)<\r><\n>
C.1.1(00000000)<\r><\n>
C.1.0(58231541)<\r><\n>
0.9.1(175102)<\r><\n>
0.9.2(120619)<\r><\n>
1.8.0(00132.149*kWh)<\r><\n>
1.8.1(00132.149*kWh)<\r><\n>
1.8.2(00000.000*kWh)<\r><\n>
1.8.3(00000.000*kWh)<\r><\n>
1.8.4(00000.000*kWh)<\r><\n>
!<\r><\n>
<3><9>

Auslesen per Python-Script

from __future__ import print_function 
import serial
import time
 
def send(port, bytes, tr):
  """ sends an command to serial and reads and checks the echo
      port  - the open serial port
      bytes - the string to be send
      tr    - the responce time
  """
  #print("start send")  
  port.write(bytes)
  time.sleep(tr)
  echo = port.read(len(bytes))
  if (echo != bytes):
    print("echo is not same as send:", bytes, " vs ", echo)
  #print("end send")
 
def read_datablock():
  ACK = '\x06'
  STX = '\x02'
  ETX = '\x03'
  tr = 0.3
  """ does all that's needed to get meter data from the meter device """ 
  try:
    IskraMT171=serial.Serial(port='/dev/ttyUSB0', baudrate=300, bytesize=7, parity='E', stopbits=1, timeout=1.5); # open port at specified speed
    # 1 ->
    time.sleep(tr)
    Request_message='/?!\r\n' + ACK + '000\r\n' # IEC 62056-21:2002(E) 6.3.1
    send(IskraMT171, Request_message, tr)
    # 2 <-
    time.sleep(tr)
    Identification_message=IskraMT171.readline() # IEC 62056-21:2002(E) 6.3.2
    if (Identification_message[0] != '/'):
      print("no Identification message")
      IskraMT171.close()
      return ""
    if (len(Identification_message) < 7):
      print("Identification message to short")
      IskraMT171.close()
      return ""
    if (Identification_message[4].islower()):
      tr = 0.02
    manufacturers_ID = Identification_message[1:4]
    if (Identification_message[5] == '\\'):
      identification = Identification_message[7:-2]
    else:
      identification = Identification_message[5:-2]
    speed = Identification_message[4]
    #print("speed = ", speed)
    if (speed == "1"): new_baud_rate = 600
    elif (speed == "2"): new_baud_rate = 1200
    elif (speed == "3"): new_baud_rate = 2400
    elif (speed == "4"): new_baud_rate = 4800
    elif (speed == "5"): new_baud_rate = 9600
    elif (speed == "6"): new_baud_rate = 19200
    else:
      new_baud_rate = 300
      speed = "0"
    #print(manufacturers_ID, " ", identification, " speed=", speed, )
    # 3 ->
    Acknowledgement_message=ACK + '0' + speed + '0\r\n' # IEC 62056-21:2002(E) 6.3.3
    send(IskraMT171, Acknowledgement_message, tr)
    IskraMT171.baudrate=new_baud_rate
    time.sleep(tr)
    # 4 <-
    datablock = ""
    if (IskraMT171.read() == STX):
      x = IskraMT171.read()
      BCC = 0
      while (x  != '!'):
        BCC = BCC ^ ord(x)
        datablock = datablock + x
        x = IskraMT171.read()
      while (x  != ETX):
        BCC = BCC ^ ord(x) # ETX itself is part of block check
        x = IskraMT171.read()
      BCC = BCC ^ ord(x)
      x = IskraMT171.read() # x is now the Block Check Character
      # last character is read, could close connection here
      if (BCC != ord(x)): # received correctly?
        datablock = ""
        print("Result not OK, try again")
    else:
      print("No STX found, not handled.")
    IskraMT171.close()
    return datablock
  except:
    print("Some error reading data")
    if (IskraMT171.isOpen()):
      IskraMT171.close()
    return ""
 
def meter_data(datablock, map, header):
  """ takes a datablock as received from the meter and returns a list with requested meter data as set in map
      if header != 0 a list with data type and units is returned """
  line = []
  ## initialise line
  for l in range(len(map)):
    if (header == 1):
      line.append(map[l][1])
    elif (map[l][0] == "time"):
      line.append(time.strftime("%Y-%m-%d %H:%M:%S"))
    else:
      line.append("")
  datasets = datablock.split('\n')
  for dataset in datasets:
    if (dataset != ""):
      x = dataset.split('(')
      address = x[0]
      x = x[1][:-2].split(' ') # the standard seems to have a '*' instead of ' ' here
      value = x[0]
      try:
        unit = '['+x[1]+']'
      except:
        unit = ""
      for l in range(len(map)):
        if (map[l][0] == address):
          if (header == 0):
            line[l] = value
          else:
            line[l] = map[l][1] + unit
          break;
  return line
 
def output(filename, line):
  f = open(filename, "a")
  print(line, file=f)
  f.close()
 
map = [
  # The structure of the meter_data() output can be set with this variable 
  # first string on each line is the cosim adress of the data you want to safe or "time" to insert the time
  # the second string on each line is a description of the type of data belonging to the cosim address
  # the order of the lines sets the order of the meter_data() output
  # example
  # header: ['meter ID', 'datum & tijd', 'verbruik totaal[kWh]', 'verbruik tarief1[kWh]', 'verbruik tarief2[kWh]', 'terug totaal[kWh]', 'terug tarief1[kWh]', 'terug tarief2[kWh]']
  # data: ['12345678', '2013-02-08 10:08:41', '0054321', '0000000', '0054321', '0000000', '0000000', '0000000']
["0.0.0", "meter ID"],
  ["time", "datum & tijd"],
  ["C.1.0", "EigentumNr9-16"],
  ["C.1.1", "EigentumNr1-8"],
  ["0.9.1", "Uhr"],
  ["0.9.2", "Datum"],  
  ["1.8.0", "Verbrauch totaal"],
  ["1.8.1", "Verbrauch tarief1"],
  ["1.8.2", "Verbrauch tarief2"],  
  ["1.8.3", "Verbrauch tarief3"],
  ["1.8.4", "Verbrauch tarief4"],
  ["F.97.0", "Nix1"],
  ["C.241.0", "Nix2"],
  ["C.244.0", "Nix3"],
  ["C.2.0", "Nix4"]
]
 
example_datablock = """
0.0.0(12345678)
C.1.0(12345678)
C.1.1(90123456)
0.9.1(114138)
0.9.2(140420)
1.8.0(25915.768*kWh)
1.8.1(15798.415*kWh)
1.8.2(10117.353*kWh)
1.8.3(00000.000*kWh)
1.8.4(00000.000*kWh)
F.97.0(00000000)
C.241.0(016)
C.244.0(00000000)
0.2.0((ER11))
"""
 
#print(meter_data(example_datablock , map, 1))
#print(meter_data(example_datablock , map, 0))
 
 
file = "meterdata.txt"
previous_data = ""
data = read_datablock()
 
output(file, meter_data(data , map, 1)) # header
while (1):
  if (data == ""):
    print(time.strftime("%Y-%m-%d %H:%M:%S"), "No data received")
  elif (previous_data != data):
    output(file, meter_data(data , map, 0))
    previous_data = data
  time.sleep(3) # minimum waiting time is 3 seconds, less and the meter doesn't return data 
  data = read_datablock()