施耐德开放自动化平台编程笔记(1)

本文记录如何在施耐德 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 下运行。

本例子涉及的内容比较多,有问题,就添加在评论区中吧。如果没有特别明白,也没有关系,可以自己先尝试建立一些小的基本功能块。学习复杂系统最好的方法就是尝试。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值