Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Solution Thumbnail

PLC interacting with Algorand Blockchain

Overview

Although the industry’s demand for intelligent software solutions is growing, Industry 4.0 remains merely a buzzword in many areas. The reason is often not a lack of readiness, but the extremely long life cycles of the machines. Because of these long operating times, old control units (PLCs) must also be married to future technologies. In the following, a simple solution will be shown that allows Simatic S7 PLCs to communicate with the algorand blockchain. Siemens control systems are the most widely used PLCs. This solution shows how s7 controllers can communicate with the Algorand blockchain in just a few steps, for example to trigger transactions.

Requirements

  1. A PureStake Account and the corresponding API key

  2. An Algorand wallet for the TESTNet

  3. Siemens Simatic S7 PLC

This example is tested with an SIMATIC S7-300, more specific a CPU315-2-PN/DP. The solution is not specific for this type of S7 and should work with any S7 PLC.

  1. A PC. Note that the Siemens Software is only available for windows

  2. Siemens Simatic Manager and python 3.6+

  3. At least three TestNet Accounts. A tutorial for the generation can be found here.

  4. A PureStake API key. For more details visit this tutorial. For this solution the code for the transaction was adapted from this tutorial.

Use Case

The solution is to show how materials are paid for directly where they are used. In the automotive industry, the manufacturing process for producing parts is often fully automated. For example, in a common injection moulding process, pre-materials such as pins are fed directly to the line on a stamping grid and then they get separated and overmoulded in the process. When a stampings grid runs out, the line operator must change it. The operator is informed of the stamping grid to be changed and the change is then checked via a QR code to ensure that the proper spool of stamping grid has been used. As soon as the machine operator confirms the change, the transaction is triggered and the operator is immediately informed about the result of the transaction.

Communication with the PLC

Hardware and Data Configuration of the PLC

The S7-300 is designed to perform control tasks and does not provide the infrastructure to communicate directly with the algorand blockchain. The S7-300 provides Industrial Ethernet protocols and in this solution we will use the S7 communication protocol. A short description of this protocol can be found here. To handle the protocol communications we will use the python library python-snap7. An installation guide can be found here.

The S7-300 controller is configured at the factory to communicate with other devices via industrial Ethernet using s7 communication. To establish communication we have to know the IP-address, the rack and the slot of the PLC. This information is available in the hardware tab of the SIMATIC manager:

EditorImages/2021/03/02 08:56/HardwareConfig.png

Via double tab on the PN-IO module, another window pops up, where you can access the IP-address of your PLC:

EditorImages/2021/03/02 08:57/IP.PNG

The snap7 library enables simple communication to data blocks within the PLC. Therefore a global data block (DB) is created on the PLC. The S7 protocol is always enabled and everyone with the right IP-address, rack und slot information can read and write directly from the PLC. Therefore, make sure that the communication between your PC and your PLC is within a safe local network.

Data Communication between PLC and PC

The purpose of this solution is to trigger an transaction. For the communication from the PLC to the PC a DB gdALGO is used. This DB is created in the SIMATIC Manager and its data structure is

EditorImages/2021/03/02 08:57/DB.PNG

The handshake for this is defined as:

EditorImages/2021/03/02 08:57/PLC_Kommunikation.png

The DBs with which you want to communicate are defined in file db_layouts.py. The data block of the PLC is now mirrored:

"""
Define DB blocks used.
"""

step7toALGO = """
0.0     SendRequest     BOOL
2       SendResponse    INT
4       PartID          INT
6       PartName        STRING[30]
"""

Python Program

The following imports are necessary to perform this solution:

#Snap 7
import snap7
from snap7.util import *
import struct
from db_layouts import step7toALGO

#ALgorand
import json
import time
import base64
from algosdk.v2client import algod
from algosdk import mnemonic
from algosdk import transaction

#Selfmade constants
import constants

The used constants for error codes are defined in the file constants.py:

# constants.py
TRANSACTION_GOOD = 10
TRANSACTION_BAD = 20
UNKNOWN_PART = 30

In the main function of our script the connection to the PLC and the data block of interest is defined by the following lines of code. If the PLC is not reachable already at the start of the communication the program is terminated.

def main():

    plc = snap7.client.Client()

    # Connect to PLC:
    try:
        plc.connect("192.168.0.1",0,2)
    except Exception as e:
        print('No connection possible - program is terminated')
        return None

    #Specify DB number and length
    db_ALGO = 444
    dblength = 38

The rest of the main function consists of a while loop, which repeats 3 functions permanently and can only be terminated with the key combination ctrl+c:

    try:
        while True:
            # check if there is a payment request from the machine
            PartID, PartName = checkSendReq(plc, db_ALGO, dblength)

            #Perform the payment via Algorand
            transaction_res = payMaterial(PartID, PartName)

            #Tell the machine if payment was an success or not
            Result2PLC(plc, db_ALGO, transaction_res)
    except KeyboardInterrupt:
        pass

    print("###### Ending Communication ######")
    plc.disconnect();

In checkSendReq we listen to the Bit SendRequest of gdALGO. If it is true we return the PartID and the PartName sent by the PLC. In industry, hundreds of data blocks are often retrieved from a multitude of systems per production line. Therefore, unnecessary traffic should be prevented and 2 seconds are fast enough for our purposes.

def checkSendReq(plc, db_ALGO, dblength):
    sendIT = False

    while sendIT == False:
        try:
            print("####### Read DB Data #######")

            all_data = plc.db_read(db_ALGO,0,dblength)

            print("Bytearray of DB" + str(db_ALGO) + ":\n" + str(all_data) + ":\n")

            # Format the bytearray to the defined struct, which represents the DB structure
            db1 = snap7.util.DB(
                db_ALGO, # the db we use
                all_data, # bytearray from the plc
                step7toALGO, # layout specification DB variable data
                dblength,  #row size
                1, #size
                id_field=None, # field we can use to identify a row.
                db_offset=0,
                layout_offset=0,
                row_offset=0
            )

            print("Read data of DB" + str(db_ALGO) + str(db1[0]) + ":\n")

            if db1[0]['SendRequest'] == True:
                sendIT = True

            PartID = db1[0]['PartID']
            PartName = db1[0]['PartName']

        except Exception as e:
            try:
                #Try to reconnect
                plc.disconnect()
                plc.connect("192.168.0.1",0,2)
            except Exception as e:
                pass

        time.sleep(2.0)

    return PartID, PartName

The function runs until a request for a transaction is recognized. Then the part ID and the part name are returned. Afterwards, the function payMaterial is called in the main.

def payMaterial(PartID, PartName):
    print("####### Start Sending #######")
    print("Send Request from PLC for Part" + str(PartID))

    if PartID == 1:
        send_to_address = 'Your recipient address Nr.1'
        amount = 777
        TransactionNote = PartName + " has arrived"
    elif PartID == 2:
        send_to_address = 'Your recipient address Nr.2'  
        amount = 555
        TransactionNote = PartName + " has arrived"
    else:
        send_to_address = ' '  
        amount = 0

    if send_to_address != ' ':
        transaction_res = ALGOtransaction(send_to_address, amount, TransactionNote)
    else:
        print("Error: Unknown PartID")
        transaction_res = constants.UNKNOWN_PART

    return transaction_res

The following snippet only has minor modification to the tutorial outlined in the algorand developer documentation.

def ALGOtransaction(send_to_address, amount, TransactionNote):
    # setup http client w/guest key provided by purestake
    ip_address = "https://testnet-algorand.api.purestake.io/ps2"
    token = "YOUR TOKEN"
    headers = {
       "X-API-Key": token,
    }

    mnemonic1 = "your mnemonic"
    account_private_key = mnemonic.to_private_key(mnemonic1)
    account_public_key = 'YOUR PUPLIC KEY'

    algodclient = algod.AlgodClient(token, ip_address, headers)

    # get suggested parameters from algod
    params = algodclient.suggested_params()

    gh = params.gh
    first_valid_round = params.first
    last_valid_round = params.last
    fee = params.min_fee
    send_amount = amount

    existing_account = account_public_key

    # create and sign transaction
    tx = transaction.PaymentTxn(existing_account, fee, first_valid_round, last_valid_round, gh, send_to_address, send_amount,       flat_fee=True,note=TransactionNote.encode())
    signed_tx = tx.sign(account_private_key)

    try:
        tx_confirm = algodclient.send_transaction(signed_tx)
        print('transaction sent with id', signed_tx.transaction.get_txid())
        wait_for_confirmation(algodclient, txid=signed_tx.transaction.get_txid())
        return constants.TRANSACTION_GOOD
    except Exception as e:
        print(e)
        return constants.TRANSACTION_BAD

Last but not least, after the algorand transaction, the result is sent back to the PLC. The logic behind is implemented using the function Result2PLC:

def Result2PLC(plc, db_ALGO, transaction_res):
    print("Write Result to PLC")

    bytearrayResult = bytearray(2)
    snap7.util.set_int(bytearrayResult, 0, transaction_res)

    confirmed = False
    while (confirmed == False):
        try:
            plc.db_write(db_ALGO, 2, bytearrayResult) 
            confirmed = True
            print("Result handover was successful \n")
        except Exception as e:
            try:
                plc.disconnect()
                plc.connect("192.168.0.1",0,2)
            except Exception as e:
                pass

PLC Program

A PLC is operated with a real-time capable operating system and has some limitations in programming. With Siemens controllers, the main function corresponds to OB1, which is called cyclically. For this solution we need only a one liner in the OB1: CALL "funALGO"

When programming PLCs, the programming language Structured Text, which is defined by the IEC 61131-3 standard, is often used. Siemens calls this language Structured Control Language (SCL), which is also defined by the same norm. You can find a detailed description of this language here. Our solution is also written in this language. In order not to hinder the cyclic call by the OB1, it is usual to work with function blocks in which the current state is stored with static variables to jump back to the correct step when called again. Therefore we call the function block funblockALGO in funALGO.

//Function Call
FUNCTION funALGO : VOID
VAR_TEMP

END_VAR

    funblockALGO.idbfunblockALGO(    
                    DieCuttingTapeChangedID := gdALGOUserInterface.DieCuttingTapeChangedID
                    ,DieCuttingTapeQRCode   := gdALGOUserInterface.DieCuttingTapeQRCode  
                    ,userBookingRequest     := gdALGOUserInterface.userBookingRequest 
                    ,returnCode             := gdALGOUserInterface.returnCode
                    ,returnText             := gdALGOUserInterface.returnText
                    ,SendRequest            := gdALGO.SendRequest
                    ,SendResponse           := gdALGO.SendResponse
                    ,PartID                 := gdALGO.PartID
                    ,PartName               := gdALGO.PartName);

END_FUNCTION

The datablock gdALGOUserInterface is used for a simple graphical visualization, which will be shown later.

The variables we will use in the function block are:

//Define the variables of the function block
FUNCTION_BLOCK funblockALGO
CONST
    STATE_WAIT4REQ          := 0;
    STATE_START_TRANSACTION := 1;
    STATE_WAIT4RESULT       := 2;

    TRANSACTION_GOOD        := 10;
    TRANSACTION_BAD         := 20;
    TRANSACTION_WRONG_PART  := 30;
    WRONG_DIE_CAST          := 40;

END_CONST    
VAR_INPUT   //Call by value
    DieCuttingTapeChangedID : INT;
    DieCuttingTapeQRCode    : INT;
END_VAR    
VAR_IN_OUT  //Call by reference
    userBookingRequest      : BOOL;

    SendRequest             : BOOL;
    SendResponse            : INT;
    PartID                  : INT;
    returnCode              : INT;
    returnText              : STRING[50];
    PartName                : STRING[30];
END_VAR   
VAR //  Internal Variables
    state                   : INT;
END_VAR

The function itself implements a simple state machine to perform the handshake to the PC:

BEGIN

    //Set part Name
    CASE DieCuttingTapeQRCode OF
        1:
            PartName := 'Die-Cast Nr.657';         
        2:
            PartName := 'Die-Cast Nr.647';
    ELSE
            PartName := ' ';
    END_CASE;        

    //State of Transaction Request
    CASE state OF
        STATE_WAIT4REQ:

            IF userBookingRequest THEN
                SendResponse := 0;

                IF DieCuttingTapeChangedID = DieCuttingTapeQRCode THEN
                    PartID := DieCuttingTapeChangedID;
                    state := STATE_START_TRANSACTION;
                ELSE 
                    returnCode :=  WRONG_DIE_CAST;  
                    returnText := 'Wrong Die-Cast';           
                END_IF;  

                userBookingRequest  := false;  
            END_IF;    

        STATE_START_TRANSACTION:

            SendRequest := true;
            state       := STATE_WAIT4RESULT;

        STATE_WAIT4RESULT:

            IF SendResponse <> 0 THEN
                returnCode :=  SendResponse;

                IF SendResponse = TRANSACTION_GOOD THEN 
                    returnText := 'Success';
                ELSIF SendResponse = TRANSACTION_BAD THEN  
                    returnText := 'Failed ';   
                ELSIF SendResponse = TRANSACTION_WRONG_PART then  
                    returnText := 'Unknown Part';      
                END_IF;

                SendRequest         := false;

                state       := STATE_WAIT4REQ;
            END_IF;         

    END_CASE;

END_FUNCTION_BLOCK

User Interface

For this solution a simple user interface was made with WinCC, which is the siemens program for visualizations in the context of PLCs. The graphical creation via WinCC is not explained in detail here, because a graphical interface is not important for the core of this solution. It serves in this document mainly for illustrative purposes and looks like this:

EditorImages/2021/03/02 08:59/UserInterface.PNG

Usage

Set PLC to RUN

Now the cyclic operation of OB1 starts.

EditorImages/2021/03/02 09:00/RUN.PNG

Run the python script

Run the script S7ToALGO.py. The output should look like this:

EditorImages/2021/03/02 09:00/console_waiting_req.PNG

Start transaction via interface

Simply by pressing the button, the machine operator confirms the change and starts the transaction

EditorImages/2021/03/02 09:01/changeReq.PNG

Transaction

The transaction is performed through the python script. The output should look like this:

EditorImages/2021/03/02 09:01/transaction.PNG

PLC Confirmation

The user gets informed about the transaction status on the PLC visualization:

EditorImages/2021/03/02 09:02/transactionInfo.PNG

Testnet Explorer

The transaction can be viewed via the Algorand Testnet Explorer:

EditorImages/2021/03/02 09:02/testnet.PNG

The transaction is visible with the right note, to match it with the paid Die-Cast.

Video-Tutorial

Conclusion

This solution is intended to show how even old industrial controllers can communicate with the algorand blockchain with just a few lines of code. Due to the long lifetimes of machines and systems in industry, it is important that they can be integrated into new systems with little effort. The simple integration shown makes innovative approaches possible in the area of the supply chain. In further projects, we therefore aim to pursue these approaches and define additional use cases in the field of industry 4.0. In addition, the integration is to be extended to other PLCs.