本文记录如何在施耐德 EAE 开法环境下,如何构建一个 MQTT 功能块,通过外部的一个Soft Gateway 实现MQTT 通信。
概述
任何一个控制系统的开发提供的功能块或者程序库都是有限的。在实际应用中经常会感觉少了一些库和功能块。开放性系统的好处在于提供了一整套工具,让用户,或者第三方开发者参与开发功能块和支撑服务。基于IEC61499 功能块技术的施耐德EAE 就是这样的系统。用户可以编写自己的功能块。EAE 编写功能块特性如下:
用户可编写的功能块
- IEC61499基本功能块(BASIC FUNCTION BLOCK)
- 复合功能块(Conposite Function Block)
- 子应用 (Sub Application)
用户使用IEC61131-3 的ST语言(Structured Text) 编写功能块内部的算法。
用户编写的功能块可以在EAE 中直接编译,下载到运行时中运行。
用户不需要任何干预。使用起来比较方便。至于编译是在EAE 中完成的,还是在运行时中完成的,目前不得而知。
MQTT 网关实现
施耐德EAE 没有提供MQTT 协议的功能块。我们尝试使用EAE 现有的功能块和编写功能块的方法,自己来构建一个MQTT 软件网关(Soft Gateway)。具体的思路如下
使用已有的NETIO 功能块实现与外部程序的TCP 通信。外部程序(Soft Gateway)将TCP 数据转化成为 MQTT 消息发布。另外一个外部程序(GatewayClient订阅该主题的消息。
为了实现IEC61499 功能块网中的数据打包成TCP 数据块。我们编写了一个基本功能块(package)。整个实验的结构如下:
MQTT 代理使用了公网上的一个公开的代理 broker.emqx.io 。你也可以在自己的电脑上安装一个Mosqquitto MQTT Broker。Soft Gateway和MQTT Client 都是为了本次测试临时编写的。使用Go 语言编写。
功能块package 的设计
功能块package 是一个IEC61499 基本功能块。实现将MQTT Topic 和counter 数据组成一个TCP 包。由于NETIO 是完成数据的透明TCP 传输。没有定义更高级的数据格式。我们在这里简单地使用IEC61499 标准推荐的ASN.1 数据格式。有关ASN.1 的更多信息可以参阅我的博文-关于IEC61499 的数据交换信息抽象语法ASN.1
建立基本功能块
在EAE 左边项目树中选择Basic 击右键,选择 New Item
出现如下画面,添加一个topic 和VAL1 输入两个数据输入脚,点击REQ 的With 框,选择将这两个输入与REQ 关联。类似方式添加OUT和OUT_LEN 两个数据 输出,并且与CNF 关联
REQ 的算法(ST)
点击工作区中的Algorithms。选择REQ 事件的算法。使用ST语言编写。
ALGORITHM REQ IN ST:
(* Add your comment (as per IEC 61131-3) here
Normally executed algorithm
*)
VAR
BUF:ARRAY [64] OF BYTE;
INDEX:UINT;
i:INT;
n:DINT;
END_VAR
INDEX:=0;
n:=LEN(TOPIC);
BUF[INDEX]:=86;
INDEX:=INDEX+1;
BUF[INDEX]:=0;
INDEX:=INDEX+1;
BUF[INDEX]:=DINT_TO_BYTE(n);
INDEX:=INDEX+1;
i:=1;
REPEAT
BUF[INDEX]:= CHAR_TO_BYTE(TOPIC[i]);
INDEX:=INDEX+1;
i:=i+1;
UNTIL i > n
END_REPEAT;
BUF[INDEX]:=70;
INDEX:=INDEX+1;
BUF[INDEX]:=UINT_TO_BYTE(VAL1/256);
INDEX:=INDEX+1;
BUF[INDEX]:=UINT_TO_BYTE(VAL1);
INDEX:=INDEX+1;
OUT_LEN:=INDEX;
i:=0;
OUT:='';
REPEAT
OUT:=CONCAT (OUT,CHAR_TO_STRING(BYTE_TO_CHAR(BUF[i])));
i:=i+1;
UNTIL i>=OUT_LEN
END_REPEAT;
END_ALGORITHM
这个算法实现在OUT 数据中输出ASN.1 格式的字符串。有两个ASN.1 数据项,一个是STRING类型的Topic ,另一个是UINT 类型的Counter。
SoftGateway.go
package main
import (
"fmt"
"net"
"os"
// "strings"
"strconv"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
const (
CONN_HOST = "localhost"
CONN_PORT = "9201"
CONN_TYPE = "tcp"
)
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
}
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
fmt.Println("Connected to MQTT Broker")
}
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
fmt.Printf("Connect lost: %v", err)
}
func main() {
var broker = "broker.emqx.io"
var port = 1883
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port))
opts.SetClientID("go_mqtt_client")
opts.SetUsername("emqx")
opts.SetPassword("public")
opts.SetDefaultPublishHandler(messagePubHandler)
opts.OnConnect = connectHandler
opts.OnConnectionLost = connectLostHandler
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
//subscribe(client,"dPACWrite")
// Listen for incoming connections.
l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when the application closes.
defer l.Close()
fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for {
// Listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
// Handle connections in a new goroutine.
go handleRequest(conn,client)
}
}
// Handles incoming requests.
func handleRequest(conn net.Conn ,client mqtt.Client) {
// Make a buffer to hold incoming data.
buf := make([]byte, 1024)
//index:=0
for {
// Read the incoming connection into the buffer.
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
}
fmt.Printf("message len=%d\n",cnt)
// line := strings.TrimSpace(string(buf[0:cnt]))
// fmt.Println(line)
len:=int(buf[1]<<8)|int(buf[2])
fmt.Printf("topic len=%d",len)
topicArray := make([]byte, len)
for index:=0;index<len;index++ {
topicArray[index]=buf[index+3]
}
topic:=string(topicArray)
fmt.Println(topic)
counter:=int(buf[len+4]<<8)|int(buf[len+5])
fmt.Printf("Counter=%d\n",counter)
client.Publish(topic, 0, false, strconv.Itoa(counter))
}
conn.Close()
}
MQTTClient.go
package main
import (
"fmt"
mqtt "github.com/eclipse/paho.mqtt.golang"
"time"
)
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
}
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
fmt.Println("Connected")
}
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
fmt.Printf("Connect lost: %v", err)
}
func main() {
var broker = "broker.emqx.io"
var port = 1883
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port))
opts.SetClientID("Gateway_client")
opts.SetUsername("client")
opts.SetPassword("public")
opts.SetDefaultPublishHandler(messagePubHandler)
opts.OnConnect = connectHandler
opts.OnConnectionLost = connectLostHandler
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
topic:="dPACWrite"
token:=client.Subscribe("dPACWrite", 0, nil)
token.Wait()
fmt.Printf("Subscribed to topic: %s\n", topic)
for {
time.Sleep(time.Second)
}
}
运行
EAE 的Soft dPAC 在windows 下运行。而SoftGateway 和MQTTClient 在同一台PC 中windows 10 下的ubuntu wsl 下运行。
本例子涉及的内容比较多,有问题,就添加在评论区中吧。如果没有特别明白,也没有关系,可以自己先尝试建立一些小的基本功能块。学习复杂系统最好的方法就是尝试。
来源:oschina
链接:https://my.oschina.net/u/4358108/blog/4844705