Simple Scada in Python and Arduino

    In continuation of the article about the possibility of building your own scada system in Python, I want to offer a practical application.

    There was a need to control the air temperature in the server room of the enterprise.
    Such a problem exists in small enterprises due to the limited number of personnel and technical equipment.

    The problem is certainly not of a global scale, but, as a rule, at such enterprises server equipment is located in small rooms, sometimes in former copters or utility rooms.

    Of course, air conditioning is installed there to effectively cool the equipment.
    But this same air conditioner tends to break, as repairmen explain, either the "capacitor burned out" or the "freon is over."

    After such emergency situations, IT engineers have a lot of problems; those who come across this will understand. The task is not difficult, in addition to the network there are many examples of implementation. For this purpose, it was decided to use the Arduino UNO and the temperature sensor DS18b20.

    image

    After reading the article , uploaded to Arduino
    the program.
    #include "ModbusRtu.h"
    #include 
    #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;
    }
    


    Now Arduino acts as a Slave device with address 10 and operates on the modbus RTU protocol. In addition, the program continuously polls the DS18b20 temperature sensor and records the current readings at address 2 of the READ_INPUT_REGISTERS register.

    Since the Slave device is connected to the computer via a USB interface with a dedicated com port, you can use the program to receive data from it:

    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()))
    


    On the one hand, this program is a Master for polling slaves using the modbus RTU protocol, and on the other hand, it is a Slave device and transmits data to the upper level using the modbus TCP protocol.

    image

    The master_rtu.py program is used if you have to collect readings from several devices using the modbus RTU protocol and / or rs485 interface. The com file address and rtu address of the slave devices are indicated in the configuration file. In addition, the polling registers and the addresses of the registers into which the received data are recorded are indicated.

    Description of setting.cfg settings file for master_rtu.py :

    
    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 - комментарий 
    

    In this configuration, the modbus RTU Slave device with address 10 will be polled. In the READ_INPUT_REGISTERS register at address 2, the measured temperature value will be read and written into the READ_INPUT_REGISTERS register at address 0 slave of the program part for modbus TCP polling.

    image

    In the ai.cfg analog signal settings file, write:

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

    Those. we will take the measured value of the temperature of the register READ_INPUT_REGISTERS at 0x00, place it on the canvas at coordinates x = 100, y = 100 and display the mnemonic diagram using the arrow object.

    In the settings.cfg settings file for scada.py we write:

    
    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
    

    The measurement results can be displayed on various objects of the mnemonic diagram, including monitoring on a dynamic chart.

    image

    Source code can be downloaded here.

    Also popular now: