NEWS
[Skriptbeispiel] python Aufruf modbus-poll
-
<size size="150">Beispiel für modbus/tcp</size>
(erweiterte Variante für modbus/rtu im 2. Post)
Aufgabenstellung: Auslesen von Verbrauchsdaten aus einem Energiemessgerät Siemens PAC3200 über modbus/tcp und Import in iobroker.
möglich geworden ist dieses Projekt durch das durch bluefox gefixte javascript zur Ausführung von python-scripten.
Voraussetzung: python (hier 2.7.9) mit pip oder easy_install zur Installation von pymodbus.
Meine Wahl fiel auf pymodbus, da dies sehr viel besser als z.B. modbus-tk dokumentiert ist.
pymodbus ist so vielseitig, dass sich nahezu jede Aufgabe im modbus-Bereich lösen lässt.
https://github.com/bashwork/pymodbus
https://code.google.com/p/pymodbus/
Doku https://pythonhosted.org/pymodbus/
dieses Skript in den javascript adapter einfügen
createState('pythonResult', ''); schedule("*/5 * * * *", function () { var python = require('child_process').spawn('python', // second argument is array of parameters, e.g.: ["/opt/python/kwh.py"]); var result = ''; python.stdout.on('data', function(data){ result += data.toString(); }); python.on('close', function(code){ if (code !== 0) { log('Error: ' + code); } else { log(result); setState('pythonResult', result, true); } }); });
dadurch wird im 5 Min. Intervall das python Skript /opt/python/kwh.sh aufgerufen, welches via modbus/tcp den Inhalt des Registers 2801 im PAC3200 abruft und den Wert über stdout an das obige javascript zurückgibt.
#!/usr/bin/env python from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.client.sync import ModbusTcpClient as ModbusClient siemens = ModbusClient(host='192.168.0.68', port=502) result = siemens.read_holding_registers(2801, 2, unit=1) decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Big) a= decoder.decode_32bit_float() # etwas Formatierung kilowattstunden print ("%.2f" % (a/1000)) #KWh siemens.close()
als Abfallprodukt ergab sich folgende Lösung zum Import der Daten in Zabbix als trapper item mittels zabbix_sender. (naja, eigentlich war dies zuerst - den iobroker-import habe ich davon abgeleitet.)
! –---------------------------------------------------------------------
! #!/usr/bin/env python
! from pymodbus.constants import Endian
! from pymodbus.payload import BinaryPayloadDecoder
! from pymodbus.client.sync import ModbusTcpClient as ModbusClient
! siemens = ModbusClient(host='192.168.0.68', port=502)
! result = siemens.read_holding_registers(2801, 2, unit=1)
! decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Big)
! a= decoder.decode_32bit_float()
! # active energy Zaehler kilowattstunden
! # formatiert für Zabbix_sender host und key (item - key) müssen in Zabbix vorhanden sein
! # siehe zabbix doku trapper item
! print "host key " + ("%.2f" % (a/1000)) #KWh
! siemens.close()
! -------------------------------------------------------------------------
! shell script durch cron alle 5 min gestartet (bzw. nach Bedarf)
! #!/bin/sh
! python /root/kwh.py > /root/kwh.log
! zabbix_sender -z 127.0.0.1 -i /root/kwh.log >/dev/nul
! -------------------------------------------------------------------------sicher lässt sich dies oder jenes eleganter coden, für mich efüllts den Zweck vollauf.
-
Dank @starfish haben wir nun eine Möglichkeit modbus Daten zu pollen. Das ist uU ganz hilfreich (zumindest bis ein Modbus-Adapter fertig ist).
Ich habe für meine Zwecke das Python-Script angepasst, da ich serielle Geräte am Bus habe. Zudem wollte ich mehrere Werte abfragen.
Hier das Python-Script 'pymodhaus.py' (liegt bei mir in /home/pi/modbus-crawler):
#!/usr/bin/env python import sys, time, json from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.client.sync import ModbusSerialClient as ModbusClient from pymodbus.transaction import ModbusRtuFramer def decode32(decval): decoder = BinaryPayloadDecoder.fromRegisters(decval.registers, endian=Endian.Big) return decoder.decode_32bit_float() def decode64(decval): decoder = BinaryPayloadDecoder.fromRegisters(decval.registers, endian=Endian.Big) return decoder.decode_64bit_int() client = ModbusClient(method='rtu', port='/dev/ttyUSB0', stopbits=1, bytesize=8, timeout=0.05, baudrate=19200, parity='N') connection = client.connect() #print "Connection: ", connection * Only for debug try: timestamp = str(time.time()).split('.')[0] # iEM3155 Haus --> Adresse immer 1 kleiner als im Schneider Datenblatt; weiss der Geier warum # HausL01 = decode32(client.read_holding_registers(3028-1, 2, unit=10)) # HausL02 = decode32(client.read_holding_registers(3030-1, 2, unit=10)) # HausL03 = decode32(client.read_holding_registers(3032-1, 2, unit=10)) HausLNM = decode32(client.read_holding_registers(3036-1, 2, unit=10)) HausP01 = decode32(client.read_holding_registers(3054-1, 2, unit=10)) * 1000 HausP02 = decode32(client.read_holding_registers(3056-1, 2, unit=10)) * 1000 HausP03 = decode32(client.read_holding_registers(3058-1, 2, unit=10)) * 1000 HausPower = decode32(client.read_holding_registers(3060-1, 2, unit=10)) * 1000 # HausPowerB = decode32(client.read_holding_registers(3068-1, 2, unit=10)) * 1000 # HausPowerS = decode32(client.read_holding_registers(3076-1, 2, unit=10)) * 1000 HausImport = decode64(client.read_holding_registers(3204-1, 4, unit=10)) / 1000 HausExport = decode64(client.read_holding_registers(3208-1, 4, unit=10)) / 1000 except: print 6 else: json_string = json.dumps({'ts': timestamp, 'HausLNM': round(HausLNM,2), 'HausP01': round(HausP01,0), 'HausP02': round(HausP02,0), 'HausP03': round(HausP03,0), 'HausPower': round(HausPower,0),'HausImport': HausImport, 'HausExport': HausExport}) print json_string client.close()
Nun zum iobroker.javascript:
// Create Datenpunkte für PV-Anlage createState('javascript.1.Solar.pi1Result', ''); createState('javascript.1.Solar.modPoll', 1); // Create Datenpunkte für Schneider EnergyMeter 'Haus' createState('javascript.1.Solar.Schneider.HausLNM', 0); createState('javascript.1.Solar.Schneider.HausP01', 0); createState('javascript.1.Solar.Schneider.HausP02', 0); createState('javascript.1.Solar.Schneider.HausP03', 0); createState('javascript.1.Solar.Schneider.HausPower', 0); createState('javascript.1.Solar.Schneider.HausImport', 0); createState('javascript.1.Solar.Schneider.HausExport', 0); createState('javascript.1.Solar.Schneider.HausTimeStamp', 0); schedule({astro: "sunrise"}, function () { setState('javascript.1.Solar.modPoll', 1, true); }); schedule({astro: "sunset"}, function () { setState('javascript.1.Solar.modPoll', 0, true); }); //Schedule Script Run Haus schedule("*/1 * * * *", function (Haus) { var enabled = getState("javascript.1.Solar.modPoll"/*javascript.1.Solar.modPoll*/).val; if (enabled == 1) { var python = require('child_process').spawn('python', ["/home/pi/modbus-crawler/pymodhaus.py"]); // second argument is array of parameters, e.g.: var result = ''; python.stdout.on('data', function(data){ result += data.toString(); }); python.on('close', function(code1){ if (code1 !== 0) { log('Error: ' + code1, 'error'); } else { if (result == 6) { log('Error: Fehler im Modbus Python Script -Haus-', 'warn'); } else { log('Modbus Python Script -Haus- erfolgreich gelaufen, Werte akzeptiert'); setState('javascript.1.Solar.pi1Result', result); var solar1 = JSON.parse(result); setState('javascript.1.Solar.Schneider.HausLNM', solar1.HausLNM, true); setState('javascript.1.Solar.Schneider.HausP01', solar1.HausP01, true); setState('javascript.1.Solar.Schneider.HausP02', solar1.HausP02, true); setState('javascript.1.Solar.Schneider.HausP03', solar1.HausP03, true); setState('javascript.1.Solar.Schneider.HausPower', solar1.HausPower, true); setState('javascript.1.Solar.Schneider.HausImport', solar1.HausImport, true); setState('javascript.1.Solar.Schneider.HausExport', solar1.HausExport, true); setState('javascript.1.Solar.Schneider.HausTimeStamp', solar1.ts, true); } } }); } });
Etwas komplexer hat sich dann der Umstand erwiesen, dass ich das Script (mangels tcp) lokal am Solar-PI laufen lassen muss. Daher musste ein iobroker als remote-host auf den PI. Aber dass ist ein ganz anderes Thema.
Version 0.1.0 2015-06-22 First release