Skip to content

2.1. Build new Function Blocks

José Rafael Fidalgo Fonseca Matias edited this page Sep 23, 2021 · 4 revisions

The development process of new Function Blocks (FBs) uses 2 different files: 1) a Python file implementing the functionalities, using the standard templates, and 2) a XML (.fbt) file encoding the metadata information about the FB, in the form of XML tags.

XML File

The function block interface is composed by 1) events and 2) variables, both of them can be both inputs and outputs. The difference between an event and a variable is that the event triggers the execution of certain functionality and variables are used to exchange data like any ordinary function in your C#, Python ou Java code.

Each function block has also a unique type that identifies it (FB type) (present in the FBType tag, e.g. Moving Average or Sensor Simulator), and a specific general categories to organize a group of function blocks. As an example, according to the specific behavior of your function block, you might choose the category DEVICE.SENSOR (if you FB is acquiring and providing data) or a SERVICE that represents a Moving Average or Normalization function. Moreover, each function block should be seen as a "module" that can be instantiated as many times as you want. So, if you have three Moving Average function blocks in your configuration, you have three instances of FB Type "MOVING_AVERAGE" and general category SERVICE. You can specify both FB Type and general category inside the XML file. You must use also the FB type as the name of the Python file (FB_TYPE_EXAMPLE.py) and the XML file (FB_TYPE_EXAMPLE.fbt)

The category of the function block is present in the FBType tag in the OpcUa attribute. There are 4 different general categories of function blocks:

  • DEVICE.SENSOR - represents a sensor (e.g. temperature, voltage);
  • SERVICE - represents a processing module (e.g. moving average, normalization);
  • POINT.STARTPOINT - represents a protocol that receives data (e.g MQTT, HTTP);
  • POINT.ENDPOINT - represents a protocol that sends data (e.g. MQTT, HTTP).

NOTE: DO NOT REPEAT VARIABLE AND EVENT NAMES IN THE SAME FUNCTION BLOCK XML

Loop Function Blocks

The DEVICE.SENSOR and the POINT.STARTPOINT automatically execute in loop, because they need to get data continuously both from sensors or protocols. For that are used 2 default input events (INIT and READ) and 2 default output events (INIT_O and READ_O), you only need to link connections in the READ_O event because the others are trigger automatically by the DINASORE engine.

<FBType Name="FB_TYPE_EXAMPLE" OpcUa="POINT.STARTPOINT">
    <InterfaceList>
        <EventInputs>
            <Event Name="INIT" Type="Event"/>
            <Event Name="READ" Type="Event"/>
        </EventInputs>
        <EventOutputs>
            <Event Name="INIT_O" Type="Event"/>
            <Event Name="READ_O" Type="Event">
                <With Var="VAR_EXAMPLE"/>
            </Event>
        </EventOutputs>
        <InputVars>
            <VarDeclaration Name="CONST_EXAMPLE" Type="STRING" OpcUa="Constant"/>
        </InputVars>
        <OutputVars>
            <VarDeclaration Name="VAR_EXAMPLE" Type="REAL" OpcUa="Variable"/>
        </OutputVars>
    </InterfaceList>
</FBType>

To virtualize some variables, it is needed in the InputVars and OutputVars declaration to specify the type of the OpcUa attribute (OpcUa="Variable", OpcUa="Constant"). It's also important to add in the events tag the variables that are related to them (e.g. READ_O event produces data related to the VAR_EXAMPLE (With Var tag)).

Service function blocks

The SERVICE and the POINT.ENDPOINT general categories execute only when triggered by other function blocks. They use also 4 default events, but in this case, the name of the READ/READ_O events are replaced by RUN/RUN_O.

<FBType Name="FB_TYPE_EXAMPLE" OpcUa="SERVICE">
    <InterfaceList>
        <EventInputs>
            <Event Name="INIT" Type="Event"/>
            <Event Name="RUN" Type="Event">
                <With Var="VAR1_EXAMPLE"/>
            </Event>
        </EventInputs>
        <EventOutputs>
            <Event Name="INIT_O" Type="Event"/>
            <Event Name="RUN_O" Type="Event">
                <With Var="VAR2_EXAMPLE"/>
            </Event>
        </EventOutputs>
        <InputVars>
            <VarDeclaration Name="CONST_EXAMPLE" Type="INT" OpcUa="Constant.RUN"/>
            <VarDeclaration Name="VAR1_EXAMPLE" Type="REAL" OpcUa="Variable.RUN"/>
        </InputVars>
        <OutputVars>
            <VarDeclaration Name="VAR2_EXAMPLE" Type="REAL" OpcUa="Variable.RUN"/>
        </OutputVars>
    </InterfaceList>
</FBType>

Data / Variable Types

These are the data types accepted by the DINASORE declared in the FBT files when building a new function block. For data types outside this list, please use STRING as data type and use the variable as it was declared in the first place. For example, if you want to use an array (e.g. x = []), declare in data type in the FBT as STRING, and you can use the variable as declared initially (e.g. x.append([18, 19,20])) and no type cast is required.

PS: This is only applicable to function blocks inside the same instance of DINASORE. If you are using multiple instances of DINASORE (e.g. one in each of 3 Raspberry Pi's) don't forget to use Service Interface Function Blocks, which means publishing and subscribing topics in MQTT, or using Sockets (HTTP), or TCP/IP, or UDP, or etc.

  • STRING: ua.VariantType.String
  • INT: ua.VariantType.Int64
  • UINT: ua.VariantType.UInt64
  • REAL: ua.VariantType.Float
  • LREAL: ua.VariantType.Float
  • BOOL: ua.VariantType.Boolean

Python File

The second step to make a function block is to encapsulate the code that you develop, inside a Python class.

  1. First, you must replace the class name (FB_NAME) by your new function block type.
  2. Implement the state machine (inside schedule method) that checks what event was received and them execute the respective method.
  3. Specify the returned attributes (output_events and output_variables) according to the order specified in the definition file.
  4. Integrate the developed methods (if the method is shared between the function block instances use a static attribute in the class).
class FB_TYPE_EXAMPLE:
    VAR2_EXAMPLE = 0

    def schedule(self, event_input_name, event_input_value, CONST_EXAMPLE, VAR1_EXAMPLE):
        # Checks what events receive
        if event_input_name == 'INIT':
            # Example of code
            self.VAR2_EXAMPLE = CONST_EXAMPLE
            # Returns all the events values and all the variable values
            # The order most be the same like the xml events/variables order
            return [event_input_value, None, self.VAR2_EXAMPLE]
            
        elif event_input_name == 'RUN':
            # Example of code
            self.VAR2_EXAMPLE += VAR1_EXAMPLE
            # Returns all the events values and all the variable values
            # The order most be the same like the xml events/variables order
            return [None, event_input_value, self.VAR2_EXAMPLE]

Examples

Here you can find examples of same basic function blocks. Nevertheless, you can access this link to download a broader range of function blocks. The linked repository contains the function blocks used for the tutorials, as well as instructions on how to populate your DINASORE with these resources.

Function Block Resources Description
Sensor Simulator [XML] [Python] simulates the sensor values according one offset
Moving Average [XML] [Python] calculates the average of the last N values
MQTT Publisher (1v) [XML] [Python] publishes a message with 1 value in the MQTT Broker
MQTT Subscriber (1v) [XML] [Python] subscribes a message with 1 value from the MQTT Broker
MQTT Publisher (2v) [XML] [Python] publishes a message with 2 values in the MQTT Broker
MQTT Subscriber (2v) [XML] [Python] subscribes a message with 2 values from the MQTT Broker
Influx DB Writer [XML] [Python] writes 2 values in a time series database