PythonおよびArduinoのシンプルなScada

Pythonで独自のscadaシステムを構築する可能性に関する記事の続きで、実用的なアプリケーションを提供したいと思います。



企業のサーバールームの気温を制御する必要がありました。

この問題は、人員と技術的手段の数が限られているため、小規模企業に存在します。



もちろん、この問題は世界的な規模ではありませんが、原則として、このような企業ではサーバー機器は小さな部屋、場合によっては以前のヘリコプターやユーティリティルームにあります。



もちろん、機器を効果的に冷却するために、エアコンが設置されています。

しかし、修理業者が説明するように、この同じエアコンは「コンデンサが燃え尽きている」か「フレオンが終わっている」かのどちらかで故障する傾向があります。



このような緊急事態の後、ITエンジニアは多くの問題を抱えています;これに遭遇した人は理解するでしょう。 このタスクは難しくありません。ネットワークに加えて、実装の多くの例があります。 この目的のために、Arduino UNOとDS18b20温度センサーを使用することが決定されました。



画像



記事を読んだ後、Arduinoにアップロードしました
プログラム。
#include "ModbusRtu.h" #include <OneWire.h> #define ID 10 //   Modbus slave(ID, 0, 0); //   modbus uint16_t au16data[20]; const int analogInPin = A0; int8_t state = 0; int DS18S20_Pin = 2; //DS18S20 Signal pin on digital 2 OneWire ds(DS18S20_Pin); // on digital pin 2 int tmp =0; void setup() { //     slave.begin( 9600 ); //    100  } void loop() { float temperature = getTemp(); tmp= temperature * 10; au16data[2] = tmp; state = slave.poll( au16data, 11); delay(10); } float getTemp(){ //returns the temperature from one DS18S20 in DEG Celsius byte data[12]; byte addr[8]; if ( !ds.search(addr)) { //no more sensors on chain, reset search ds.reset_search(); return -1000; } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.println("CRC is not valid!"); return -1000; } if ( addr[0] != 0x10 && addr[0] != 0x28) { Serial.print("Device is not recognized"); return -1000; } ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion, with parasite power on at the end byte present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } ds.reset_search(); byte MSB = data[1]; byte LSB = data[0]; float tempRead = ((MSB << 8) | LSB); //using two's compliment float TemperatureSum = tempRead / 16; return TemperatureSum; }
      
      







現在、Arduinoはアドレス10のスレーブデバイスとして機能し、modbus RTUプロトコルで動作します。 さらに、プログラムはDS18b20温度センサーを継続的にポーリングし、READ_INPUT_REGISTERSレジスタのアドレス2に現在の測定値を記録します。



スレーブデバイスは、専用のcomポートを備えたUSBインターフェイスを介してコンピューターに接続されているため、プログラムを使用してデータを受信できます。



modbus_rtu.py。


 #!/usr/bin/env python import sys import time import logging import modbus_tk import modbus_tk.defines as cst import modbus_tk.modbus_tcp as modbus_tcp from modbus_tk import modbus_rtu import serial logger = modbus_tk.utils.create_logger("console") if __name__ == "__main__": serverSlave='' portSlave=0 param = [] reg=[] startAdr=[] rangeAdr=[] setFrom=[] setRange=[] rtuAddress=[] units=0 try: count=0 param = [] i=0 for _ in range(256): param.append(i) reg.append(i) startAdr.append(i) rangeAdr.append(i) setFrom.append(i) setRange.append(i) rtuAddress.append(i) i = i + 1 with open('setting.cfg') as f: for line in f: param[count]=line.split(';') if(param[count][0]=='server'): serverSlave= param[count][1] portSlave = param[count][2] if(param[count][0]=='cport'): serialPort= param[count][1] if(param[count][0]=='rtu'): rtuAddress[count] = param[count][1] reg[count] = param[count][2] startAdr[count] = param[count][3] rangeAdr[count] = param[count][4] setFrom[count] = param[count][5] setRange[count] = param[count][6] count=count + 1 units=count server = modbus_tcp.TcpServer(address=serverSlave, port=int(portSlave) ) server.start() slave = server.add_slave(1) slave.add_block('0', cst.COILS, 0, 1000) slave.add_block('1', cst.DISCRETE_INPUTS, 0, 1000) slave.add_block('2', cst.ANALOG_INPUTS, 0, 1000) slave.add_block('3', cst.HOLDING_REGISTERS, 0, 1000) f.close() serialPort=serial.Serial(port=serialPort, baudrate=9600, bytesize=8, parity='N', stopbits=1, xonxoff=0) master = modbus_rtu.RtuMaster( serialPort ) master.set_timeout(1.0) except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) try: print 'Starting server...' while True: i=0 for i in range(units): if(reg[i] == 'READ_INPUT_REGISTERS'): dataRIR=[] for c in range(0, int(rangeAdr[i]) ): dataRIR.append(c) c+=1 try: dataRIR= master.execute(int(rtuAddress[i]), cst.READ_INPUT_REGISTERS, int(startAdr[i]), int(rangeAdr[i]) ) slave.set_values('2', int(setFrom[i]), dataRIR) serialPort.flushInput() serialPort.flushOutput() serialPort.flush() print 'rtu' , rtuAddress[i],'READ_INPUT_REGISTERS',dataRIR except: for c in range(0,int(rangeAdr[i]) ): dataRIR[c] = 0 c+=1 print 'rtu' , rtuAddress[i],'READ_INPUT_REGISTERS','Fail to connect',dataRIR slave.set_values('2', int(setFrom[i]), dataRIR) if(reg[i] == 'READ_DISCRETE_INPUTS'): dataRDI=[] for c in range(0, int(rangeAdr[i]) ): dataRDI.append(c) c+=1 try: dataRDI= master.execute(int(rtuAddress[i]), cst.READ_DISCRETE_INPUTS, int(startAdr[i]), int(rangeAdr[i]) ) slave.set_values('1', int(setFrom[i]), dataRDI) serialPort.flushInput() serialPort.flushOutput() serialPort.flush() print 'rtu' , rtuAddress[i],'READ_DISCRETE_INPUTS',dataRDI except: for c in range(0,int(rangeAdr[i]) ): dataRDI[c] = 0 c+=1 print 'rtu' , rtuAddress[i],'READ_DISCRETE_INPUTS','Fail to connect' ,dataRDI,len(dataRDI) slave.set_values('1', int(setFrom[i]), dataRDI) if(reg[i] == 'READ_COILS'): dataRC=[] for c in range(0, int(rangeAdr[i]) ): dataRC.append(c) c+=1 try: dataRC= master.execute(int(rtuAddress[i]), cst.READ_COILS, int(startAdr[i]), int(rangeAdr[i]) ) slave.set_values('0', int(setFrom[i]), dataRC) serialPort.flushInput() serialPort.flushOutput() serialPort.flush() print 'rtu' , rtuAddress[i],'READ_COILS',dataRC except: for c in range(0,int(rangeAdr[i]) ): dataRC[c] = 0 c+=1 slave.set_values('0', int(setFrom[i]), dataRC) print 'rtu' , rtuAddress[i],'READ_COILS','Fail to connect',dataRC if(reg[i] == 'READ_HOLDING_REGISTERS'): dataRHR=[] for c in range(0, int(rangeAdr[i]) ): dataRHR.append(c) c+=1 try: dataRHR= master.execute(int(rtuAddress[i]), cst.READ_HOLDING_REGISTERS, int(startAdr[i]), int(rangeAdr[i]) ) slave.set_values('3', int(setFrom[i]), dataRHR) serialPort.flushInput() serialPort.flushOutput() serialPort.flush() print 'rtu' ,rtuAddress[i],'READ_HOLDING_REGISTERS',dataRHR except: for c in range(0,int(rangeAdr[i]) ): dataRHR[c] = 0 c+=1 slave.set_values('3', int(setFrom[i]), dataRHR) print 'rtu ', rtuAddress[i],'READ_HOLDING_REGISTERS','Fail to connect',dataRHR time.sleep(0.1) except modbus_tk.modbus.ModbusError, e: logger.error("%s- Code=%d" % (e, e.get_exception_code()))
      
      







一方では、このプログラムはmodbus RTUプロトコルを使用してスレーブをポーリングするためのマスターであり、他方では、スレーブデバイスであり、modbus TCPプロトコルを使用して上位レベルにデータを送信します。



画像



master_rtu.pyプログラムは、modbus RTUプロトコルやrs485インターフェイスを使用して複数のデバイスから測定値を収集する必要がある場合に使用されます。 スレーブデバイスのcomファイルアドレスとrtuアドレスは、構成ファイルに示されています。 さらに、ポーリングレジスタと、受信データが記録されるレジスタのアドレスが示されます。



master_rtu.pyのsetting.cfg設定ファイルの説明:



 server;192.168.0.200;507; # # server -   # 192.168.0.200 - IP  slave  modbus TCP    # 507 -  slave  modbus TCP    cport;COM5; # # cport -   # COM5 -          modbusRTU rtu;10;READ_INPUT_REGISTERS;0;10;0;0;comment # rtu -   # 10 - rtu  slave    # READ_INPUT_REGISTERS -   slave    # : # READ_DISCRETE_INPUTS # READ_COILS # READ_HOLDING_REGISTERS # 2 -          slave  modbus RTU # 1 -       slave  modbus RTU # 0 -       slave   modbus TCP # comment - 
      
      





この構成では、アドレス10のmodbus RTUスレーブデバイスがポーリングされます。アドレス2のREA​​D_INPUT_REGISTERSレジスタで、測定された温度値が読み取られ、modbus TCPポーリングのプログラム部分のアドレス0スレーブのREAD_INPUT_REGISTERSレジスタに書き込まれます。



画像



ai.cfgアナログ信号設定ファイルで、次のように記述します。



 ai;1;100;100;green;0.1;50;Air Temp A;ameter;
      
      





すなわち レジスタREAD_INPUT_REGISTERSの温度の測定値を0x00で取得し、キャンバス上の座標x = 100、y = 100に配置し、矢印オブジェクトを使用してニーモニックダイアグラムを表示します。



scada.pyのsettings.cfg 設定ファイルに次のように記述します。



 slaveIP=192.168.0.200 -- ip  modbus TCP slave  slavePort=504 --  modbus TCP slave  discretCfg=di.cfg coilCfg=ci.cfg analogCfg=ai.cfg buttonCfg=bt.cfg bgimage=bg.gif delayTime=500 debug=False
      
      





測定結果は、動的チャートでの監視など、ニーモニックダイアグラムのさまざまなオブジェクトに表示できます。



画像



ソースコードはこちらからダウンロードできます。



All Articles