Package: pymscada
pymscadaDocs@GithubPython Mobile SCADAThis is an open source Python SCADA package intended for use over your
mobile phone. It has been developed to connect custom python scripts to
Modicon and Rockwell PLCs, and present a web based user interface, with
minimal coding.pymscadadoes expect you to be able to use python.pymscadashares values through aTagfor all data. Tag values
are updated by exception through a message bus. Changes are in any
direction with simple loops stopped with a simplisticdon't send updates
back to where they came fromcheck. It's too simple to stop you defeating
it (but there are easier ways to break the code).There are primary owner Tag's. Arequest to setcommand is passed to the
author of the Tag, where it is updated. This allows database information
to pass through the Tag values as well. Tag values are typically float or
int however they may also be megabyte sized binary or dictionary values.Examplesystemdservice files, pymscada module config files and
custom script examples are included. The example scripts include polling
weather from tommorrow.io and a python script based Modbus TCP PLC.IntroductionThis is a small SCADA package that will run on Linux (preferably) or
Windows. The server runs as several modules on the host, sharing
information through a message bus. Asubsetof modules is:Bus server - shares tag values with by exception updatesModbus client - reads and writes to a PLC using Modbus/TCPLogix client - usespycomm3to read / write to Rockwell PLCsSNMP client - polls SNMP OID valuesHistory - saves data changes, serves history to web pagesWeb server - serves web pages which connect with a web socketWeb pages - procedurally generated setpoint, indication and trendsObjectivesTraditional SCADA has a fixed 19:6, 1920x1080 or some equivalent layout.
It's great on a big screen but not good on a phone. HenceMobileSCADA with a responsive layout.I wrote Mobile SCADA to provide a GUI to the other things I was trying to
do, I wanted to leverage web browsers and eliminate a dedicatedviewer.exe. Display on the client is fast, trends, as fast as I can
make them.Uptimes should be excellent. The best I have on an earlier version is
over 5 years for about half of the script modules. This version is a
complete rewrite, however the aim is the same.All tag value updates are by exception. So an update from you setting a
value to seeing the feedback should beFAST.See alsoThe angular projectangmscadaPython container for the compiled angular pagespymscada-htmlAn add-on IO driver for Rockwell using pycomm3pymscada-pycomm3Licencepymscadais distributed under the GPLv3license.Example UseThis was all run on a Raspberry Pi 3B+ with a 16GB SDRAM card.FirstCheckout the example files. Start in an empty directory. Plan to keep
in the directory you check out into as the config file path details
are auto-generated for the location you check out in to.mscada@raspberrypi:~/test$pymscadacheckout
making'history'folder
makingpdfdir
makingconfigdir
Creating/home/mscada/test/config/modbusclient.yaml
Creating/home/mscada/test/config/pymscada-history.service
Creating/home/mscada/test/config/wwwserver.yaml
Creating/home/mscada/test/config/pymscada-demo-modbus_plc.service
Creating/home/mscada/test/config/files.yaml
Creating/home/mscada/test/config/pymscada-modbusserver.service
Creating/home/mscada/test/config/pymscada-wwwserver.service
Creating/home/mscada/test/config/simulate.yaml
Creating/home/mscada/test/config/tags.yaml
Creating/home/mscada/test/config/history.yaml
Creating/home/mscada/test/config/pymscada-files.service
Creating/home/mscada/test/config/bus.yaml
Creating/home/mscada/test/config/modbusserver.yaml
Creating/home/mscada/test/config/modbus_plc.py
Creating/home/mscada/test/config/pymscada-modbusclient.service
Creating/home/mscada/test/config/pymscada-bus.service
Creating/home/mscada/test/config/README.md
mscada@raspberrypi:~/test$ObjectiveTo show a trend of the temperature forecast with a custom pymscada bus
client program. The end result should look like ...ConfigurationBusDefaults inbus.yamlare fine.TagsAdd some tags intags.yaml:temperature:desc:temperaturetype:floatmin:0max:35units:Cdp:1temperature_01:desc:temperature_01type:floatmin:0max:35units:Cdp:1...etc.HistoryDefaults inhistory.yamlare fine.Web ServerYou will need to add a trend page towwwserver.yamlas:-name:Temperature# Creates a Temperature page in the web clientparent:Weather# Add the Temperature page in a submenu under Weatheritems:-type:uplot# Identify the Angular component to usems:desc:Temperatureage:172800legend_pos:lefttime_pos:lefttime_res:maxes:-scale:xrange:[-604800,86400]# initial time range for the trend-scale:'C'range:[0.0,35.0]dp:1series:-tagname:temperature# pymscada Tag namelabel:Current Temperaturescale:'C'color:black# standard html colour nameswidth:2dp:1# number of decimal places...etc for additional seriesYour custom pymscada ModuleFor this example I polledtomorrow.ioweather.pyfromdatetimeimportdatetimeimporttimefrompymscadaimportBusClient,Periodic,TagURL='https://api.tomorrow.io/v4/timelines'QUERY={'location':'-43.527934570040124, 172.6415203551829','fields':['temperature'],'units':'metric','timesteps':'1h','startTime':'now','endTime':'nowPlus24h','apikey':'<your key>'}classPollWeather():def__init__(self):self.tags={}fortagnamein['temperature','temperature_01','temperature_04','temperature_12','temperature_24']:# Create pymscada tags, tags are singletons by 'tagname'self.tags[tagname]=Tag(tagname,float)asyncdefperiodic(self):now=int(time.time())ifnow%3600!=120:return# Get the weather forecast from tomorrow.ioasyncwithaiohttp.ClientSession()assession:asyncwithsession.get(URL,params=QUERY)asresp:response=awaitresp.json()utc_now=Noneforrowinresponse['data']['timelines'][0]['intervals']:convert=row['startTime'].replace('Z','+0000')utc=datetime.strptime(convert,'%Y-%m-%dT%H:%M:%S%z').timestamp()ifutc_nowisNone:utc_now=utcforecast=''else:forecast=f'_{int((utc-utc_now)/3600):02d}'ifforecastnotin['','_01','_04','_12','_24']:continuefork,vinrow['values'].items():ftag=k+forecastvalue=float(v)time_us=int(utc*1000000)ifftaginself.tags:# Write the tag value. This is one of the following:# - value                      # time_us, bus_id auto-generated# - value, time_us             # bus_id auto-generated# - value, time_us, bus_id     # Don't use this oneself.tags[ftag].value=value,time_usasyncdefmain():# Connect to the bus and poll the weather service.bus=BusClient()awaitbus.start()weather=PollWeather()# demo functionperiodic=Periodic(weather.periodic,1.0)# part of pymscadaawaitperiodic.start()awaitasyncio.get_event_loop().create_future()# run foreverif__name__=='__main__':asyncio.run(main())Run the modulesYou can run the modules in one of: individual terminals,nohup ... &or as asystemdservice. I run as a service, the snips are abbreviated (no path) from
the exec line in the auto-generated service files.Run the bus first! This needs to remain running all the time. It does not need to
know the tagnames in advance so it can run forever for most tests. It will gather
dead tagnames over time as you are experimenting, however this only requires a
small amount of memory (unless you are setting tag values in the MB - which does
work).pymscadabus--configbus.yaml
pymscadawwwserver--configwwwserver.yaml--tagstags.yaml
pymscadahistory--confighistory.yaml--tagstags.yaml
pythonweather.py