施耐德開放自動化平臺編程筆記(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 下運行。

本例子涉及的內容比較多,有問題,就添加在評論區中吧。如果沒有特別明白,也沒有關係,可以自己先嚐試建立一些小的基本功能塊。學習複雜系統最好的方法就是嘗試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章