Monday, July 7, 2014

Raspberry Pi SCADA Part 2, Modbus TCP PWM Controller

Raspberr Pi SCADA Part 2, Modbus PWM Controller

Since finding a cheap alternative to PLC whilst using an industrial protocol is a popular idea the Raspberry Pi has caught many eyes on doing this.  I posted once on reading a temperature sensor and serving it up on the Pi using ModbusTCP. This time I expound on it and show you how to control something. In this case it will be a PC 12v fan.

Parts:

  • 1 x Raspberry pi
  • 1 x Darlington Transistor
  • 1 x PC Fan (with about  500ma load)
  • 1 x 12v DC Power Supply


Setup:

  1. Installing pymodbus and dependencies:
    • sudo apt-get install python-pymodbus python-twisted-conch
  2. Wire up your Fan, LED, or motor


  1. View my code from Github.
Video of what I did:


Source Code:
from pymodbus.server.async import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
from twisted.internet.task import LoopingCall
from threading import Thread
import pid
import threading
from time import sleep
import RPi.GPIO as GPIO
import os
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')
#set up Raspberry GPIO 
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(25,GPIO.OUT)
pwm = GPIO.PWM(25,60)
pwmDutyCycle=100
pwm.start(pwmDutyCycle)
temperaturePoll = None

class Temp(Thread):
    """
     A class for getting the current temp of a DS18B20
    """
    def __init__(self, fileName=''):
        Thread.__init__(self)
        super(Temp, self).__init__()
        self._stop = threading.Event()
        self.tempDir = '/sys/bus/w1/devices/'
        list = os.listdir(self.tempDir)
        if(list[0][:2]=="28"):
         fileName=list[0]
        self.fileName = fileName
        self.currentTemp = -999
        self.correctionFactor = 1;
        self.enabled = True
        self.Run=True
    def run(self):
        while self.isEnabled():
   try:
    f = open(self.tempDir + self.fileName + "/w1_slave", 'r')
   except IOError as e:
    print "Error: File " + self.tempDir + self.fileName + "/w1_slave" + " does not exits"
    return;
   lines=f.readlines()
   crcLine=lines[0]
   tempLine=lines[1]
   result_list = tempLine.split("=")
   temp = float(result_list[-1])/1000 # temp in Celcius
   temp = temp + self.correctionFactor # correction factor
   #if you want to convert to Celcius, comment this line
   temp = (9.0/5.0)*temp + 32
   if crcLine.find("NO") > -1:
    temp = -999
   self.currentTemp = temp
   #print "Current: " + str(self.currentTemp) + " " + str(self.fileName)
   sleep(.5)
    #returns the current temp for the probe
    def getCurrentTemp(self):
        return self.currentTemp
    #setter to enable this probe
    def setEnabled(self, enabled):
        self.enabled = enabled
    #getter
    def isEnabled(self):
        return self.enabled


def updating_writer(a):
 context  = a[0]
 register = 3
 slave_id = 0x00
 address  = 0x00
 global pwmDutyCycle,temp
 #uncomment to debug temperature
 print temp.getCurrentTemp()
 values = [int(pwmDutyCycle),temp.getCurrentTemp()*100]
 context[slave_id].setValues(register,address,values)
def read_context(a):
 context  = a[0]
 register = 3
 slave_id = 0x00
 address  = 0x00
 value = context[slave_id].getValues(register,address)[0]
 global pwmDutyCycle
 if(value!=pwmDutyCycle):
  print value
  pwmDutyCycle=value
  pwm.ChangeDutyCycle(pwmDutyCycle)
def main():

 store = ModbusSlaveContext(
  di = ModbusSequentialDataBlock(0, [0]*100),
  co = ModbusSequentialDataBlock(0, [0]*100),
  hr = ModbusSequentialDataBlock(0, [0]*100),
  ir = ModbusSequentialDataBlock(0, [0]*100))
 context = ModbusServerContext(slaves=store, single=True)
 identity = ModbusDeviceIdentification()
 identity.VendorName  = 'pymodbus'
 identity.ProductCode = 'PM'
 identity.VendorUrl   = 'http://github.com/simplyautomationized'
 identity.ProductName = 'pymodbus Server'
 identity.ModelName   = 'pymodbus Server'
 identity.MajorMinorRevision = '1.0'
 time = 5 # 5 seconds delaytime = 5 # 5 seconds delay
 writer = LoopingCall(read_context,a=(context,))
 loop = LoopingCall(updating_writer, a=(context,))
 loop.start(.5) # initially delay by time
 writer.start(.1)
 StartTcpServer(context, identity=identity)#, address=("localhost", 502))
 #cleanup async tasks
 temp.setEnabled(False)
 loop.stop()
 writer.stop()
 GPIO.cleanup()
if __name__ == "__main__":
 temp = Temp()
 temp.start()
 main()

Monday, February 24, 2014

Home Automation Project #4 1-Wire I/O Performance Test

Dallas has a couple 1-Wire I/O chips out there.  I decided to test the one that seemed practical to replace a light switch or plug. The DS2413 was almost perfect.  It has 2 I/O, gets power from the data line, and Adafruit made it easy for me to test by making the breakout board.

Here's what I used for my test:


With these I created a demo to see the read and write speeds through the 1-wire data line.
Source code for benchmark:

As you can see the performance dropped more than half every time I pressed the button.  Not bad if all I care about is whether the light turns on right when I press the button, and it would.
I then ran the loop to see how fast it would go at turning on and off the relay and saw an average of 20.833 requests per second. (Don't worry, the relay wasn't able to click on and off that fast)

Adding a DS18B20 Temperature sensor and requesting it every second I found that when the temperature was requested the requests per second dropped down to 1.950 requests per second and then jumped back up to the 42.27 range until the next second came along.

Requesting the temperature every loop cycle was unworkable.  I had to hold my finger on the button far longer than desired to get the relay to turn on.  I tuned it up by requesting the lesser accurate temperature value "fasttemp".  I also requested it less often (every other second).

t=ow.Sensor('/28.B0A534050000')
t.useCache(False)
print t.fasttemp

The DS2413 datasheet mentions the ability for Overdrive (~10x the communication speed) but I was unsuccessful on getting it to work.  The communication would seize when I tried enabling it on the bus.

Conclusion:
Controlling your lights with the DS2413 is possible, but you are limited on the quantity of chips on the same bus line. If you are able to get the chip's Overdrive feature working then I would suggest getting the 8-port i2c to 1-wire add-on board if you have plans for your whole house.

I'm going to test the DS2408 (8 port 1-Wire I/O chip) soon and am a little more hopeful since you can control multiple rooms and/or lights with one chip.

(Pictures coming soon.)

Tuesday, September 24, 2013

Home Automation Project #3 Light Switch Cont. - WebUI-Source with WebSockets!

For the best and quickest response times with my webgui I decided to go with websockets. This was my first websocket experiment and it took many, many tutorials and websocket engines for me to finally come to this conclusion. I went with autobahn websockets because of its ease of use and ability to do reactor.callLater() which is the equivelent of a setTimout in javascript from what I understand. With that I used it to constantly check the outputs of my PiFace and send the socket message to the client webapp based off of its changes. So if anyone else in your house turns the lights on or off physically, you can instantly see it change on your phone. Now the only problem is older devices (like my wife's phone) are not all html5 websocket compatible. You may have to create a python cgi script to use as a fallback.

If you are kinda lost and need to recap on the lightswitch series here are the links and summaries:
Setting up the Piface communication listener and the button listener scripts HERE
Pics and description of the WebUI HERE

Setup:
Need to install Twisted, AutoBahnPython, and for the static pages I use lighttpd.

sudo apt-get install python-pip
sudo easy_install autobahn
sudo apt-get install twisted lighttpd


Websocket Server Code:

needs to run on boot after out PifaceListener script (source found here) has started.

Webpage Code:

I copied to my /var/www/ directory: