MBLogic for an open world in automation
This section describes how to write a simple user client protocol class for a generic client. It is assumed you are familiar with writing simple Python programs.
Generic clients are experimental at this stage, and the interface is subject to change. If you intend to make use of this feature at this stage in its development, be prepared to make minor modifications to your custom generic clients in future versions.
"GenClientLib" must be imported to provide communications with the server.
import GenClientLib
Additional imports can be included to support the client features. This example is
import time import signal import urllib2 import json
The client protocol must be implemented as a class. This class may import other classes as needed. This class may be named anything.
class UserClient:
The generic client framework expects a class which implements the actual client communications. This class must include the following methods:
GetClientMsgs must return a list of strings containing the client messages. Client messages are any messages the generic client wishes to return to the server to be displayed in the communications monitor status interface. These would typically describe errors, but may involve whatever other information was felt appropriate.
def GetClientMsgs(self): return self._ClientMsgs
GetStatus must return a tuple containing the connection status, and the command status. Connection status is a string containing the connection status code. The connection status code must be one of the following:
A list of the valid status codes may be found in the section on "Communications Errors".
The command status must be a dictionary in the following format: "{<command name> : (<status number>, <status code>,<status description>)}
There should be one dictionary entry per command. These have the following definitions.
A list of the valid command status codes may be found in the section on "Communications Errors".
def GetStatus(self): cmdstat = self._CmdStatusBuff self._CmdStatusBuff = [] return self._ConnectStatus, cmdstat
This is a method which is called by the generic client framework when a new set of parameters is available. It must accept 3 parameters. There are
def SetParams(self, hostconfig, clientconfig, cmdlist): # Validate the expected parameters. repeattime = int(clientconfig['repeattime']) / 1000.0 retrytime = int(clientconfig['retrytime']) / 1000.0 # Etc. ... # Parse the commands. # Etc. ... # Send a message back. self._ClientMsgs.append('The parameters were ok.')
This gets called on a regular basis by the generic client framework. The main generic client code would go here. This must accept two input parameters, and return two parameters. The input parameters are:
Note: Server commands to the client are not implemented at this time. However this parameter is required to be present for compatibility with future use. Server commands are used to shut down the system, but these are intercepted by the client framework.
Return parameters:
def NextCommand(self, readtable, servercmd):
The data format used for the data table transfer parameters is as follows. They consist of a dictionary with keys that match the names used in the communications configuration file ("coil", "inp", "inpreg", "holdingreg"). Each dictionary value is a list containing the data values for the data table. Coils and discrete inputs are boolean values, and input and holding registers are signed 16 bit integers.
Unused types may be either an empty list, or the key/value pair may be absent from the dictionary. Generic client writers must not depend on any particular key/value pair to be present if it is unused.
{'coil' : [True, False, True, True], 'inp' : [True, False, True, True], 'inpreg' : [], 'holdingreg' : [1234, 5678]}
The following shows a simple example which uses the HMI protocol to read data from the server data table. The list of HMI "tags" to read was passed as part of the command. This uses the standard Python "urllib2" library to send and receive the messages (over HTTP).
def NextCommand(self, readtable, servercmd): data = {} # First check if we've got a good parameter set. if self._ConfigOK: # Get the next command. try: cmdname, cmdvalue = self._CommandIter.next() nextpoll = self._CommandTime except StopIteration: self._CommandIter = iter(self._CommandList) cmdname, cmdvalue = self._CommandIter.next() nextpoll = self._RepeatTime # Increment the message id. self._MsgID += 1 if self._MsgID > 65535: self._MsgID = 0 self._basemessage['msgid'] = self._MsgID self._basemessage['read'] = cmdvalue # Encode the dictionary into JSON format using the standard Python library. JsonOut = json.dumps(self._basemessage) # Send the data using POST. req = urllib2.Request(url = 'http://localhost:8082/', data = '', headers = {'Cascadas': JsonOut}) f = urllib2.urlopen(req) # Read the response. response = f.read() # Get the response data. We look for the blank line between the headers and # the body and assume that everything after the first blank is the data. # This is a feature of HTTP, and not something which is part of the Cascadas # protocol itself. resplist = response.splitlines() JsonIn = ''.join(resplist) # Convert from JSON into a dictionary. respdata = json.loads(JsonIn) rdata = respdata['read'] coillist = map(lambda x: rdata.get(x, False), ['PL1', 'PL2', 'PL3']) data['coil'] = coillist reglist = map(lambda x: rdata.get(x, 0), ['Tank1Level', 'PL4']) data['holdingreg'] = reglist # Set the connection status. self._ConnectStatus = 'running' # Set the command status. self._AddCmdStatus(cmdname, 'ok') else: # Set the connection status. self._ConnectStatus = 'stopped' nextpoll = 1.0 return data, nextpoll