Get MBLogic at SourceForge.net. Fast, secure and Free Open Source 
	software downloads

Help Topics

Topic Details for Communications

Help - Generic Client User Protocol Class


Overview

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.


Import Modules

"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


Client Protocol Class

Create a Class

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

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

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

SetParams

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.')


NextCommand

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