Skip to content

The python client library to connect to the websocket interface of the MSB (Manufacturing Service Bus)

License

Notifications You must be signed in to change notification settings

research-virtualfortknox/msb-client-websocket-python

Repository files navigation

VFK Research Logo

MSB websocket client library for Python

Build Status PyPI version Coverage Status FOSSA Status

Compatibility Matrix

Client version compatibility to MSB versions:

1.5.x-RELEASE 1.6.x-RELEASE
1.0.x x x

Welcome

If you want to contribute, please read the Contribution Guidelines.

If you want to test this client by using its sources and a sample app, read the App Sample.

If you want to know how to use this client in your own project, read below.

What is VFK MSB

TODO: Link to general documentation about VFK MSB

You can use this client to connect a python app to VFK MSB.

Prerequisites

  • Setup Python version 3.8.x
    • last client version supporting Python 2.7 is v1.0.8
    • last client version supporting Python 3.6 is v1.0.11
    • last client version supporting Python 3.7 is v1.0.13
  • MSB client installed using PyPi
  • Optional: Use pipenv to run your python app in a virtual environment to avoid dependency isssues with other apps

Install MSB client from PyPi

pip install msb-client-websocket-python

Import to your applicaton

from msb_client.ComplexDataFormat import ComplexDataFormat
from msb_client.DataType import DataType
from msb_client.Event import Event
from msb_client.Function import Function
from msb_client.MsbClient import MsbClient

Create self-description

The figure below shows a minimal required self-description model of a smart object / application. Every smart object / application requires (must have) a uuid and a token. The uuid is competent for identification and the token is used to verify the smart object / application for its owner on the MSB side.

Self Description

TODO: Here you can find more information about the self-description structure and supported data formats.

Alternative 1 - By application.properties

Add the main description by adding an application.poperties file to the root of your project:

Generate the uuid e.g. by a tool like https://www.uuidgenerator.net/

msb.uuid=76499d88-34cf-4836-8cc1-7e0d9c54dacx
msb.name=YourSmartObjectName
msb.description=YourSmartObjectDesc
msb.token=5e0d9c54dacx
msb.type=SmartObject

When initializing your msb client instance, the application.properties file will be loaded.

myMsbClient = MsbClient()

You can also set a custom path to the application.properties file.

myMsbClient = MsbClient(applicationPropertiesCustomPath="./your/path/to/application.properties")

Alternative 2 - By constructor

If you do not provide an application.properties file, use the constructor to define the basic self description.

SERVICE_TYPE = "SmartObject"
SO_UUID = str(uuid.uuid1()) # you can type in an own uuid here instead of generating it
SO_NAME = "YourSmartObjectName"
SO_DESCRIPTION = "YourSmartObjectDesc"  
SO_TOKEN = SO_UUID[-6:]
myMsbClient = MsbClient(
    SERVICE_TYPE,
    SO_UUID,
    SO_NAME,
    SO_DESCRIPTION,
    SO_TOKEN,
)

Add Events

Add events to your smart object / application which can be send to MSB.

Alternative 1: Simple event creation sample (using method params):

event_id = "E1"
event_name = "EVENT " + event_id
event_description = "EVENT Description " + event_id
event_dataformat = DataType.STRING
event_priority = 1 # 0 (LOW), 1 (MEDIUM), 2 (HIGH)
isArray = False # just one value or array of it?

# add the event
myMsbClient.addEvent(
    event_id,
    event_name,
    event_description,
    event_dataformat,
    event_priority,
    isArray,
)

Alternative 2: Complex event creation sample (using object):

event_id = "E2"
event_name = "EVENT " + event_id
event_description = "EVENT Description " + event_id
event_priority = 1 # 0 (LOW), 1 (MEDIUM), 2 (HIGH)
isArray = False # just one value or array of it?

# define a complex data format to be used in an event
# init the complex data format
myDevice = ComplexDataFormat("MyDevice")
myModule = ComplexDataFormat("MyModule")

# add the properties to the complex objects
# (property_name, property_datatype, isArray)
myModule.addProperty("moduleName", DataType.STRING, False)
myDevice.addProperty("deviceName", DataType.STRING, False)
myDevice.addProperty("deviceWeight", DataType.FLOAT, False)
myDevice.addProperty("submodules", myModule, True)

# add the event (with the root of the nested complex object)
myMsbClient.addEvent(
    event_id,
    event_name,
    event_description,
    myDevice,
    event_priority,
    isArray,
)

Alternative 3: Complex event creation sample (using json object):

event_id = "E3"
event_name = "EVENT " + event_id
event_description = "EVENT Description " + event_id
event_priority = 1 # 0 (LOW), 1 (MEDIUM), 2 (HIGH)
isArray = False # just one value or array of it?

# add the event (with the MSB-ready json object)
myMsbClient.addEvent(
    event_id,
    event_name,
    event_description,
    {
        "Team" : {
            "type" : "object",
            "properties" : {
                "staff" : {
                    "type" : "array",
                    "items" : {
                        "$ref" : "#/definitions/Member"
                    }
                }
            }
        },
        "Member" : {
            "type" : "object",
            "properties" : {
                "name" : {
                    "type" : "string"
                },
                "status" : {
                    "enum" : [ "present", "absent" ],
                    "type" : "string"
                }
            }
        },
        "dataObject" : {
            "$ref" : "#/definitions/Team"
        }
    },
    event_priority,
    isArray,
)

See app_sample.py for more event creation examples.

Add Functions

Add functions and their implementations your smart object / application is able to handle.

Alternative 1: Simple function creation sample (using method params):

function_id = "F1"
function_name = "FUNC " + function_id
function_description = "FUNC Description " + function_id
function_dataformat = DataType.STRING
isArray = False # handle array of values or just one value?
responseEvents = None # you can link to response events here by a list of event is e.g. ["E1"]

# define the function which will be passed to the function description
# this function implementation will be called
def printMsg(msg):
    print(str(msg["dataObject"]))

# add the function
myMsbClient.addFunction(
    function_id,
    function_name,
    function_description,
    function_dataformat,
    printMsg,
    isArray,
    responseEvents,
)

Alternative 2: Complex function creation sample (using object):

function_id = "F2"
function_name = "FUNC " + function_id
function_description = "FUNC Description " + function_id
isArray = False # handle array of values or just one value?
responseEvents = None # you can link to response events here by a list of event is e.g. ["E1"]

# define a complex data format to be used in an event
# init the complex data format
myCar = ComplexDataFormat("MyCar")

# add the properties to the complex objects
# (property_name, property_datatype, isArray)
myCar.addProperty("carColor", DataType.STRING, False)
myCar.addProperty("carNrOfSeats", DataType.INT32, False)
myCar.addProperty("carWeight", DataType.FLOAT, False)

# define the function which will be passed to the function description
# this function implementation will be called
def printMsg(msg):
    print(str(msg["dataObject"]))

# add the function
myMsbClient.addFunction(
    function_id,
    function_name,
    function_description,
    myCar,
    printMsg,
    isArray,
    responseEvents,
)

Alternative 3: Complex function creation sample (using json object):

function_id = "F3"
function_name = "FUNC " + function_id
function_description = "FUNC Description " + function_id
isArray = False # handle array of values or just one value?
responseEvents = None # you can link to response events here by a list of event is e.g. ["E1"]

# define the function which will be passed to the function description
# this function implementation will be called
def printMsg(msg):
    print(str(msg["dataObject"]))

# add the function
myMsbClient.addFunction(
    function_id,
    function_name,
    function_description,
    {
        "MyCar" : {
            "type" : "object",
            "properties" : {
                "carColor" : {
                    "type" : "string"
                },
                "carNrOfSeats" : {
                    "format": "int32",
                    "type": "integer"
                },
                "carWeight" : {
                    "format": "float",
                    "type": "number"
                },
                "wheels" : {
                    "type" : "array",
                    "items" : {
                        "$ref" : "#/definitions/MyWheel"
                    }
                }
            }
        },
        "MyWheel" : {
            "type" : "object",
            "properties" : {
                "position" : {
                    "enum" : [ "br", "bl", "fr", "fl" ],
                    "type" : "string"
                }
            }
        },
        "dataObject" : {
            "$ref" : "#/definitions/MyCar"
        }
    },
    printMsg,
    isArray,
    responseEvents,
)

See app_sample.py of the application template for more (and complex) examples.

Connect and Register Client

msb_url = 'ws://127.0.0.1:8085'
myMsbClient.connect(msb_url)
myMsbClient.register()

You will get an IO_CONNECTED and IO_REGISTERED event from MSB, if successful.

Event publishing

For publishing an event to a websocket broker interface, only the eventId and data are required of the already specified event (see above).

event_id = "E1"
event_value = 'Hello World!'

myMsbClient.publish(
  event_id, 
  event_value
)

The MSB responds with an IO_PUBLISHED event, if successful.

By default events are published with a low priority.

It is also possible to set the priority of an event.

There are three possible priorities for events like it is shown at the following table.

Constant Value
LOW 0
MEDIUM 1
HIGH 2
event_id = "E1"
event_value = 'Hello World!'
event_priority = 2

myMsbClient.publish(
  event_id, 
  event_value,
  event_priority
)

Another option is to publish an event as cached event by setting the cache parameter to true. And you can add a post date.

This means that the event is not deleted if the connection is broken.

event_id = "E1"
event_value = 'Hello World!'
event_priority = 2
event_isCached = True
event_postDate = datetime.datetime.utcnow().isoformat()[:-3] + "Z"

myMsbClient.publish(
  event_id, 
  event_value,
  event_priority,
  event_isCached,
  event_postDate
)

You cann also handle correlation ids to identify an event among flows.

event_id = "E1"
event_value = 'Hello World!'
event_priority = 2
event_isCached = True
event_postDate = datetime.datetime.utcnow().isoformat()[:-3] + "Z"
event_correlationId = "72047f33-a9ae-4aa5-b7ae-c1c4a2797cac"

myMsbClient.publish(
  event_id, 
  event_value,
  event_priority,
  event_isCached,
  event_postDate,
  event_correlationId
)

For values based on complex data formats it will look like this:

event_id = "E2"
event_priority = 2
event_isCached = True
event_postDate = datetime.datetime.utcnow().isoformat()[:-3] + "Z"
event_correlationId = "72047f33-a9ae-4aa5-b7ae-c1c4a2797cac"

# pepare the complex ovbject based on a complex data format
# use it as event value
myModuleObj = {}
myModuleObj['moduleName'] = 'Module 1'
myDeviceObj = {}
myDeviceObj['deviceName'] = 'Device 1'
myDeviceObj['deviceWeight'] = 1.3
myDeviceObj['submodules'] = [myModuleObj]

myMsbClient.publish(
  event_id, 
  myDeviceObj,
  event_priority,
  event_isCached,
  event_postDate,
  event_correlationId
)

Function call handling

As shown above the addFunction method includes a function pointer to point to the function implementation.

Configuration parameters

Configuration parameters are a simple list of key value pairs for the smart object / application. It is displayed and can be customized in the MSB UI to change your apps behaviour during runtime.

Add condifuration parameters:

param_name_1 = "testParam1"
param_value_1 = True
param_datatype_1 = DataType.BOOLEAN
myMsbClient.addConfigParameter(param_name_1, param_value_1, param_datatype_1)

param_name_2 = "testParam2"
param_value_2 = "StringValue"
param_datatype_2 = DataType.STRING
myMsbClient.addConfigParameter(param_name_2, param_value_2, param_datatype_2)

param_name_3 = "testParam3"
param_value_3 = 1000
param_datatype_3 = DataType.INT32
myMsbClient.addConfigParameter(param_name_3, param_value_3, param_datatype_3)

Get configuration parameter (after changed in MSB UI) to change your app behaviour:

# get by getConfigParameter using name as key
parameterValueFound_1 = myMsbClient.getConfigParameter(param_name_1)
parameterValueFound_2 = myMsbClient.getConfigParameter(param_name_2)
parameterValueFound_3 = myMsbClient.getConfigParameter(param_name_3)

SSL/TLS connection configuration

To enable SSL/TLS, you need to specify wss:// or https:// in the URL instead of ws:// or http://.

Furthermore, it is necessary to specify a trust store in the client, which contains the public certificate of the MSB interface, so that it is considered trustworthy.

msb_url = 'wss://<hostname>:<port>'
myMsbClient.connect(msb_url)
myMsbClient.register()

If you use an IP instead of a public url during development, it will be necessary to disable the hostname verification to connect via web socket secure.

myMsbClient.disableHostnameVerification(True)  

Connection recovery

If connection to the common websocket interface is broken the client performs a reconnect.

After a reconnect the registration at the MSB will be redone automatically by the client.

You can change this interval by setting an integer value in ms for the reconnect interval.

myMsbClient.setReconnectInterval(10000)

Or you can disable the automatic reconnect.

myMsbClient.disableAutoReconnect(True)

Event caching

If the client loses the connection, the published events are cached in a queue.

After a successfull reconnection, the queued events are published to MSB (FIFO principle). The default size of the queue is 1000 entries. The size can be changed:

myMsbClient.setEventCacheSize(1000)

If no event caching is needed, you can disable it.

myMsbClient.disableEventCache(True)

Debug mode

To debug your clients communication with MSB, you can enable the debug mode

myMsbClient.enableDebug(True)

To enable the trace of the websocket communication use also

myMsbClient.enableTrace(True)

It mgiht be also helpful to enable data format validation, to check if an event value is valid

myMsbClient.enableDataFormatValidation(True)

License

FOSSA Status

About

The python client library to connect to the websocket interface of the MSB (Manufacturing Service Bus)

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages