MQTT library block written in SCL for S7-300 with internal (PN) or external (CP) Ethernet.
This started as a port of knolleary's MQTT library for Arduino & ESP8266. The implementation is following the MQtt v3.1.1 protocol documentation (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html)
Main functionallity complete, still work in Progress!
MQTT is a popular communications protocol in the IoT space. Its demand on most types of networks and CPUs make it a good option for M2M applications. MQTT devices can easily be utilized to send a message anywhere in the world.
MQTT will enable a PLC to connect to the cloud without using proprietary hardware or protocols. PLC programmers can use it to build customized programs that send info to a web server, log plant data, and communicate with any other MQTT client device. Developers can use it to build customized dashboards (physical, website, or mobile device), providing valuable data for analytics and business. MQTT enabled IO could serve as an inexpensive alternative for the PLC. (although not recommended for mission critical IO and for safety reasons)
At this time, this code has been successfully tested on:
The PLC is connected to a Mosquitto MQtt broker. All main functionallity has been testet: connect, disconnect, subscribe, unsubscribe, ping, publish. I am locally connecting to a Mosquitto broker.
It should work with most S7-300/400 CPU's. This code is written in Step7 SCL v5.3 SP1. It probably needs modification for it to compile in TIA.
You need to add the following Objects Library blocks to your project:
Only needed for internal ethernet support (PN): Library: Standard Library -> Communication Blocks -> Blocks
FB66 TDISCON
Only needed for external ethernet support (CP): Library: SIMATIC_NET_PC -> CP300
FC10 AG_CTRL
Additional Objects needed: Library: IEC Function Blocks
FC10 EQ_STRNG
Library: System Function Block
SFC59 RD_REC
Important: there will be an import conflict between FC10 AG_CTRL and FC10 EQ_STRING, as a solution rename one of the the FC-numbers during import
There are two ways to generate the Block numbers required by the project. Block numbers can be generated automatically or they can be manually setup in the projects Symbols table. Block number auto generation can be configured by setting the “Create block numbers automatically” option in the Options -> Customize menu of the SCL editor, in the “General” tab the “Create block numbers automatically” option must be set. If block numbers must be set manually, open the “S7 Symbols.xlsx” document in the Support folder, set the block numbers according to your requirements and copy&paste them into the S7 projects Symbols table.
There are two Makefiles, please choose the right one for the Ethernet requirements (ProfiNet, CP) of the project:
Makefile-PN will compile the project with ProfiNet (integrated) Ethernet support (PN)
Important: only compile the Makefile that suits your requirements.
Imporant: You must call the MQTT function block in your OB1 program loop.
Optional: You may compile the MQTT_Example.scl
The MQTT FB can use the internal Ethernet adapter of a CPU (PN, choose MQTT_Main_PN.scl) or an external Ethernet adapter (CP, choose MQTT_Main_CP.scl).
Remarks for internal Ethernet (PN) adapter configuration:
you must set the MQTT Functionblock parameter connectionID to a desired value, f.e. 1 Example for a MQTT FB call in OB1 configured for internal Ethernet (PN) usage: MQTT.DB71(net_config := DB_NET_CONFIG, connectionID := 1);
Remarks for external Ethernet (CP) adapter configuration:
To reduce the memory usage of the MQtt DBs you can set the receive and transmit buffer sizes.
Keep in mind: data from tcpRecBuf is transfered to buffer and also data from buffer is tranfered to tcpSendBuf within the Code. So match the sizes appropriately.
You can check the connection status by monitoring two Flags in mqttData DB:
mqttData.ethTCPConnected : this Boolean will show you the state of the TCP connection to the broker
mqttData._state : will show you the connection status of the MQtt connection to the broker. Check for value > 0 to check if MQTT is connected. I recommend to check this status before trying to send a message.
mqttData.mqttErrorCode : will hold the last MQtt error code
mqttData.tcp_sendBufferFull : will be true if the send buffer is full. Important: this also indicates that the last message could not be added to the send buffer, the last message therefore was discarded. To avoid problems with the send buffer, give it an appropriate size to hold multiple messages and also only send if mqttData._state > 0.
Included is an example application function block (FB70) that is typically called from OB1. Inputs for this block can trigger a MQTT broker connect, publish a message or subscribe to a MQTT channel.
There is also some example code for calculating a payload CRC to check the data integrity. The used CRC_GEN function can be found in the oscat.de PLC library.
Porting C++ to Siemens SCL has taught many differences between systems. Differences include:
One reason for this is a PLC is a real-time system. A consequence is that the entire program code, from the first to last line, must complete in a certain amount of time. (Typically <10ms) The workaround I make is using a FB with a state machine that can wait for a procedure to finish before moving on.
After working with SCL for a while it feels that the language is primitive in many ways in comparison with most PC languages. I realise that some of it is because of program limitation for safety and resource conservation, but general program principles like DRY, OOP, etc. are sometimes difficult to achieve if not impossible.
Another annoying difference is the idea of having symbol entries for symbolic block names. On one side I want to refactor and break behaviour up in more functions like in general programming. But in Siemens I don't want to create a new symbol entry for each FC that I make. Also, this is probably not encouraged in PLC programming anyway as it increases the maximum size of the call stack in every scan cycle and increases the scan time. This makes it difficult to make small sized program code that is also performant.
Furthermore, the low-level programming and memory limitation reminds of microcontroller programming.