codesysplc與python的socket“牽手”:開啟工業(yè)控制無限可能
關(guān)鍵詞:codesys、python、socket 通信、raspberry pi、工業(yè)物聯(lián)網(wǎng)、實時數(shù)據(jù)采集、跨語言集成
微信公眾號原文連接:
https://mp.weixin.qq.com/s/0jwwybowub5lj0g26gvjsa
csdn:
https://blog.csdn.net/qq_36063437/article/details/153248117?fromshare=blogdetail&sharetype=blogdetail&sharerid=153248117&sharerefer=pc&sharesource=qq_36063437&sharefrom=from_link
一、引言:讓 plc 擁抱開放的編程世界
傳統(tǒng)工業(yè)里,plc 穩(wěn)守固定邏輯,精準(zhǔn)執(zhí)行控制任務(wù)。在工業(yè)物聯(lián)網(wǎng)與智能制造浪潮下,工程師有了新期待:讓 plc 與 python 等現(xiàn)代高級編程語言聯(lián)手,將 plc 的穩(wěn)定可靠與 python 的開放多元完美融合,從而釋放出無限潛能。
本文將以一個精彩案例,展示 plc 程序通過 socket 請求,讓樹莓派上 python 服務(wù)器抓取實時天氣數(shù)據(jù)并回傳解析存儲的奇妙過程,一起探索!
二、系統(tǒng)總體架構(gòu)與數(shù)據(jù)流
系統(tǒng)由兩個主要部分構(gòu)成:
模塊 | 平臺 | 功能描述 |
codesys plc 程序 | 樹莓派上的 codesys runtime | 客戶端。檢測觸發(fā)信號、建立tcp連接、發(fā)送命令、接收天氣 json 數(shù)據(jù)、解析并輸出到變量。 |
python 服務(wù)端程序 | 樹莓派 / 其他主機(jī) | 服務(wù)器。監(jiān)聽tcp端口,接收plc命令,通過 http 從 weather.com.cn 獲取實時天氣數(shù)據(jù),解析后返回 json 格式響應(yīng)。 |
三、codesys 端:實現(xiàn) plc 調(diào)用外部服務(wù)的關(guān)鍵邏輯
codesys中新建名為socket_fb的功能塊(function block),在plc主循環(huán)中調(diào)用。
3.1 功能塊的引腳設(shè)計
socket_fb功能塊引腳示意圖
3.2 上升沿觸發(fā)與一次通信周期
代碼示例:
brisingedge := bsendtrigger and not btrigold;
btrigold := bsendtrigger;
plc 程序通過檢測輸入 bsendtrigger 的上升沿,觸發(fā)一次完整的通信任務(wù)。這樣可確保每次請求都是用戶或外部事件驅(qū)動,不會連續(xù)觸發(fā)導(dǎo)致網(wǎng)絡(luò)阻塞。一旦觸發(fā)在后續(xù)的程序中會依次執(zhí)行以下命令:
周期初始化,清空所有狀態(tài)變量;
創(chuàng)建 tcp socket;
連接到服務(wù)器;
發(fā)送命令;
等待并接收應(yīng)答;
解析結(jié)果;
關(guān)閉連接。
這是一個典型的“事務(wù)式通信模式”,類似工業(yè)現(xiàn)場中“一次握手、一問一答”的數(shù)據(jù)采集流程。
3.3 周期初始化
代碼示例:
if brisingedge then
bconnectok := false;
bsendok := false;
brecvok := false;
bdone := false;
srecvbuffer := '';
ierrorcode := errors.err_ok;
每次通訊開始前重置所有狀態(tài)標(biāo)志,清空接收緩沖區(qū)。
3.4 socket 通信核心流程
codesys 的 syssocket 庫提供了底層網(wǎng)絡(luò)訪問能力:
函數(shù) | 作用 |
syssockcreate() | 創(chuàng)建 socket,返回句柄 |
syssockconnect() | 與服務(wù)器建立 tcp 連接 |
syssocksend() | 發(fā)送數(shù)據(jù) |
syssockrecv() | 接收數(shù)據(jù) |
syssockclose() | 關(guān)閉連接 |
(1) 創(chuàng)建socket:
代碼示例:
hsocket := syssockcreate(socket_af_inet, socket_stream, socket_ipproto_tcp, adr(iresult));
codesys庫syssockcreate文檔
創(chuàng)建一個新的 socket,并返回該 socket 的句柄(handle)。這個句柄以后會作為參數(shù)傳給其他套接字相關(guān)函數(shù),例如 syssockbind、syssockconnect、syssocklisten、syssockaccept、syssocksend、syssockrecv、syssockclose 等。
參數(shù):socket_af_inet, socket_stream, socket_ipproto_tcp是 codesys 系統(tǒng)庫中定義的常量,初始值如下表所示。
name | type | initial | comment |
socket_af_inet | int | 2 | addressfamily: dinternetwork: udp, tcp, etc. |
socket_stream | dint | 1 | socket types: stream socket |
socket_ipproto_tcp | dint | 6 | protocols: tcp |
(2)設(shè)置socket服務(wù)器地址
代碼示例:
sockinetaddr_result := syssockinetaddr('127.0.0.1', adr(ipaddr));
if sockinetaddr_result = errors.err_ok then
addrserver.sin_family := socket_af_inet;
addrserver.sin_port := syssockhtons(5678);
addrserver.sin_addr.uladdr := ipaddr;
這段代碼的作用是:將字符串形式的 ip 地址 "127.0.0.1" 轉(zhuǎn)換為可用于網(wǎng)絡(luò)通信的數(shù)值格式,并在轉(zhuǎn)換成功后,設(shè)置服務(wù)器地址結(jié)構(gòu) addrserver 的基本參數(shù):指定使用 ipv4 協(xié)議、端口號為 5678,并將目標(biāo) ip 地址設(shè)為 127.0.0.1,為后續(xù)建立 socket 連接做準(zhǔn)備。
codesys庫syssockinetaddr文檔
在使用syssockconnect 前,需要把目標(biāo) ip(字符串)轉(zhuǎn)換為可寫入地址結(jié)構(gòu)的二進(jìn)制值。所以,syssockinetaddr 通常是網(wǎng)絡(luò)通信初始化步驟中的一環(huán)。syssockinetaddr 的作用就是:把 "點分十進(jìn)制" 的 ip 地址(例如 '127.0.0.1')轉(zhuǎn)成一個 32 位無符號整數(shù)(udint)形式。
在實際測試中使用的‘127.0.0.1’通過syssockinetaddr轉(zhuǎn)換結(jié)果是16777343。一個 ipv4 地址本質(zhì)上是 4 個字節(jié)(共 32 位),把它轉(zhuǎn)換為16進(jìn)制按字節(jié)拼起來是0x7f000001。網(wǎng)絡(luò)中數(shù)據(jù)是 big endian(高位在前),但大多數(shù) plc/cpu(x86、arm)是 little endian(低位在前)。也就是說在內(nèi)存中這 4 個字節(jié)的排列是反的:
網(wǎng)絡(luò)字節(jié)序(標(biāo)準(zhǔn)): 7f 00 00 01
plc內(nèi)存(小端表示): 01 00 00 7f
0x0100007f = (1 × 256^3) + (0 × 256^2) + (0 × 256) + 127= 16777343
codesys庫sockaddress文檔
sockaddress 結(jié)構(gòu)用于在 codesys 中描述一個完整的網(wǎng)絡(luò)通信地址,它包含了建立或識別網(wǎng)絡(luò)連接所需的全部信息——包括地址族(如 ipv4)、端口號以及目標(biāo)或本地的 ip 地址。該結(jié)構(gòu)在調(diào)用syssockconnect函數(shù)時作為參數(shù)使用,用來告訴系統(tǒng)“我要與哪個 ip、哪個端口進(jìn)行通信”。其中端口號需要通過 syssockhtons() 轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,ip 地址通常由 syssockinetaddr() 生成。簡單來說,sockaddress 就是 codesys 中 socket 通信的“地址卡片”。
(3)建立socket連接
代碼示例:
sockconnect_result := syssockconnect(hsocket, adr(addrserver), sizeof(addrserver));
這段代碼的作用是:通過已創(chuàng)建的 socket (hsocket),調(diào)用 syssockconnect() 函數(shù),將其連接到由 addrserver 定義的遠(yuǎn)程服務(wù)器地址,并返回連接結(jié)果。
codesys庫syssockconnect文檔
syssockconnect是一個用于實現(xiàn)客戶端連接socket服務(wù)器功能的功能塊。使用時,需將傳入socket句柄和包含服務(wù)器ip地址和端口號等信息的sockaddress結(jié)構(gòu)等。函數(shù)執(zhí)行后會返回一個rts_iec_result類型的值,用于指示連接操作是否成功,若返回0表示連接成功,可進(jìn)行后續(xù)數(shù)據(jù)傳輸?shù)炔僮,否則需根據(jù)返回值進(jìn)行相應(yīng)的錯誤處理。
(4) 發(fā)送命令
代碼示例:
ssendbuffer := 'fun1';
if syssocksend(hsocket, adr(ssendbuffer), len(ssendbuffer), 0, adr(socksend_result)) >; 0 and socksend_result = errors.err_ok then
bsendok := true;
這段代碼的作用是:plc通過已連接的socket發(fā)送字符串 “fun1”,并在確認(rèn)發(fā)送成功后設(shè)置發(fā)送成功標(biāo)志。
codesys庫syssocksend文檔
syssocksend 函數(shù)用于向已建立的 socket發(fā)送數(shù)據(jù)。hsocket 是先前創(chuàng)建并連接成功的 socket 句柄;adr(ssendbuffer) 提供發(fā)送緩沖區(qū)的內(nèi)存地址;len(ssendbuffer) 指定要發(fā)送的數(shù)據(jù)長度;0 表示不使用額外的發(fā)送標(biāo)志;adr(socksend_result) 用于接收運行時系統(tǒng)返回的錯誤碼。函數(shù)返回成功發(fā)送的字節(jié)數(shù)。如果發(fā)送的字節(jié)數(shù)大于 0 且系統(tǒng)返回碼 socksend_result 等于 errors.err_ok,則說明數(shù)據(jù)成功發(fā)出,于是程序?qū)?bsendok 置為 true,表示發(fā)送完成。
(5) 接收數(shù)據(jù)
代碼示例:
direcvbytes := syssockrecv(hsocket, adr(byrecvbuffer), sizeof(byrecvbuffer), 0, adr(sockrecv_result));
if direcvbytes >; 0 and sockrecv_result = errors.err_ok then
if direcvbytes >; sizeof(srecvbuffer) - 1 then
direcvbytes := sizeof(srecvbuffer) - 1;
end_if
sysmemcpy(adr(srecvbuffer), adr(byrecvbuffer), direcvbytes);
srecvbuffer[direcvbytes] := byte#0;
brecvok := true;
這段代碼的主要作用是:從一個已建立的 tcp socket (hsocket) 中接收數(shù)據(jù)并保存到接收緩沖區(qū)中 (srecvbuffer),并在成功接收后標(biāo)記 brecvok := true。
codesys庫syssockrecv文檔
syssockrecv用于從 socket中接收數(shù)據(jù)。它通過指定的socket句柄 hsocket 從端口讀取數(shù)據(jù),并將接收到的字節(jié)寫入由 pbybuffer 指向的接收緩沖區(qū)中,最大接收長度由 dibuffersize 限制。
codesys庫sysmemcpy文檔
sysmemcpy用于內(nèi)存數(shù)據(jù)復(fù)制,其作用是將指定源地址 psrc 中的內(nèi)容復(fù)制到目標(biāo)地址 pdest,復(fù)制的字節(jié)數(shù)由參數(shù) udicount 決定。
實際運行狀態(tài)監(jiān)控
網(wǎng)絡(luò)傳輸?shù)讓硬徽J(rèn)識“字符串”,所有內(nèi)容(包括文字、數(shù)字、圖片)都要被轉(zhuǎn)為字節(jié)流(byte stream)。byrecvbuffer 收到的就是這些 ascii/utf-8 字節(jié),direcvbytes是接收到的字節(jié)數(shù)量。
codesys監(jiān)控byrecvbuffer
byrecvbuffer接收到的字節(jié)數(shù)據(jù)前9個依次為:123,34,110,97,109,101,101,110,34。根據(jù)字符與字節(jié)(ascii / utf-8 編碼)之間的關(guān)系,以上字節(jié)可轉(zhuǎn)譯為:{、"、n、a、m、e、e、n、"。
字符 | 十進(jìn)制字節(jié)值 | 十六進(jìn)制 | 含義 |
{ | 123 | 0x7b | 左花括號 |
} | 125 | 0x7d | 右花括號 |
" | 34 | 0x22 | 雙引號 |
: | 58 | 0x3a | 冒號 |
, | 44 | 0x2c | 逗號 |
空格 | 32 | 0x20 | 空格 |
0–9 | 48–57 | 0x30–0x39 | 數(shù)字字符 |
a–z | 97–122 | 0x61–0x7a | 小寫字母 |
標(biāo)準(zhǔn) ascii 或 utf-8 編碼
文本的案例中接收到的完整字符串為:{"nameen": "baoshan", "temp": "23.9", "wde": "nw", "wse": "11km/h", "sd": "84%", "qy": "1015", "njd": "4km", "updatetime": "20:40", "rain": "0", "rain24h": "0", "aqi": "88", "aqi_pm25": "88", "weathere": "haze"},共211字節(jié),與監(jiān)控的direcvbytes值一致。
(6) json 解析
代碼示例:
s_nameen := getfieldvalue(srecvbuffer, 'nameen');
function getfieldvalue : string
...
spattern := concat(skey, '": "');
istart := find(ssrc, spattern);
...
getfieldvalue := left(stemp, iend - 1);
這段代碼的主要作用是:從 ssrc 字符串中查找以 skey 為字段名的鍵值對,并提取該鍵對應(yīng)的字符串值。類似從 ... "name": "alice", ... 中提取 alice 的功能。
雖然 plc 沒有內(nèi)置完整 json 解析器,但通過字符串查找函數(shù)即可實現(xiàn)簡化版字段提取。這說明即便在嵌入式 plc 環(huán)境中,也可以通過基礎(chǔ)字符串操作解析網(wǎng)絡(luò)數(shù)據(jù)。解析完成后,plc 將天氣各項指標(biāo)寫入輸出變量,如:
s_temp := getfieldvalue(srecvbuffer, 'temp');
s_windspeed := getfieldvalue(srecvbuffer, 'wse');
s_humidity := getfieldvalue(srecvbuffer, 'sd');
s_weather := getfieldvalue(srecvbuffer, 'weathere');
這些變量可用于顯示在 hmi、記錄數(shù)據(jù)庫、或驅(qū)動后續(xù)控制邏輯。
(7) 關(guān)閉socket
syssockclose(hsocket);
關(guān)閉已創(chuàng)建的套接字 hsocket,釋放與該套接字相關(guān)的系統(tǒng)資源,結(jié)束該網(wǎng)絡(luò)連接。
四、python 端:codesys 的“外部智能助手”
4.1 設(shè)計思路
python 在此系統(tǒng)中扮演“中間件服務(wù)層”角色。plc 不直接訪問互聯(lián)網(wǎng),而是請求 python 服務(wù)端,由 python 完成網(wǎng)絡(luò)請求與數(shù)據(jù)解析任務(wù),再將結(jié)果以簡潔 json 返回。
這既保證了:
plc 穩(wěn)定、安全(不直接暴露外網(wǎng)請求);
python 靈活、強(qiáng)大(可訪問任意 api 或算法)。
這種設(shè)計模式是“plc + 外部語言”協(xié)同的典型結(jié)構(gòu)。
本文案例以python監(jiān)聽socket端口,接收來自plc的命令來執(zhí)行獲取當(dāng)前天氣數(shù)據(jù)的功能,并且將天氣數(shù)據(jù)返還給plc進(jìn)行解析。
4.2 主要功能模塊
(1) 天氣數(shù)據(jù)抓取
代碼示例:
def get_weather_data():
url = f"https://d1.weather.com.cn/sk_2d/101020300.html?_{int(time.time() * 1000)}"
headers = {
'referer': 'https://e.weather.com.cn/',
'user-agent': 'mozilla/5.0'
}
response = requests.get(url, headers=headers, timeout=10)
return parse_weather_data(response.text)
這段代碼的作用是:python 使用 requests 庫訪問氣象網(wǎng)站,提取返回數(shù)據(jù)包中的 json 數(shù)據(jù)段。
解析后得到標(biāo)準(zhǔn)字典對象,例如:
{
"nameen": "pudong",
"temp": "26",
"wde": "east",
"wse": "3.4",
"sd": "65%",
"qy": "1012"
}
(2) 端口監(jiān)聽
def handle_client(conn, addr):
data = conn.recv(2048).decode('utf-8').strip()
if data == "fun1":
weather_data = format_weather_data(get_weather_data())
reply = json.dumps(weather_data, ensure_ascii=false)
conn.sendall(reply.encode('utf-8'))
python 服務(wù)監(jiān)聽端口 5678,一旦接收到 "fun1",便執(zhí)行天氣抓取并回傳 json。采用多線程模式,保證可以同時服務(wù)多個 plc 連接。
4.3 codesys 與 python 的契約:數(shù)據(jù)格式 + 通信協(xié)議
項目 | 內(nèi)容 |
連接方式 | tcp |
端口號 | 5678 |
請求命令 | fun1 |
返回格式 | utf-8 編碼的 json 文本 |
通信周期 | 按 plc 觸發(fā)(例如每 30 秒或人工觸發(fā)) |
通過這種契約,plc不需要理解python,只需發(fā)命令并解析字符串即可。這正是 “codesys 調(diào)用 python” 的精髓:plc 不直接運行 python 代碼,但通過 socket 請求 → python 執(zhí)行 → 結(jié)果返回,實現(xiàn)了間接調(diào)用。
五、實驗結(jié)果與運行驗證
實驗環(huán)境:
硬件:raspberry pi 4b + 2gb ram
操作系統(tǒng):raspberry pi os (64-bit)
codesys 版本:3.5 sp21
python 版本:3.11
網(wǎng)絡(luò):同機(jī)運行(127.0.0.1)
運行效果:
1. 啟動 python 服務(wù)器:
[服務(wù)器啟動] 正在監(jiān)聽 127.0.0.1:5678
樹莓派python
2. 在 codesys 中觸發(fā) bsendtrigger 上升沿:
bconnectok = true
bsendok = true
brecvok = true
s_temp = "26"
s_windspeed = "3.4"
s_humidity = "65%"
bdone = true
codesys在線監(jiān)控
3. codesys可視化界面顯示:
codesys可視化界面
驗證:通信成功,數(shù)據(jù)解析正確。
六、codesys 與 python 協(xié)同的技術(shù)意義
6.1 打破 plc 封閉邊界
傳統(tǒng) plc 依賴專有協(xié)議和有限的函數(shù)庫,難以直接與云端 api 或第三方系統(tǒng)交互。通過 syssocket,codesys 實現(xiàn)了跨語言通信的“開放接口”,使 plc 能夠訪問:
web 服務(wù)(restful api)
本地算法服務(wù)(python、c/c++)
數(shù)據(jù)庫代理(通過 python、node.js 等)
6.2 各取所長的架構(gòu)優(yōu)勢
模塊 | 優(yōu)勢 | 角色定位 |
codesys plc | 實時性強(qiáng)、控制邏輯穩(wěn)定 | 數(shù)據(jù)消費者、執(zhí)行層 |
python 程序 | 網(wǎng)絡(luò)與算法能力強(qiáng) | 數(shù)據(jù)提供者、智能層 |
這是一種工業(yè)界常見的分層架構(gòu):plc 負(fù)責(zé)“控制”,python 負(fù)責(zé)“智能”。
七、擴(kuò)展應(yīng)用方向
(1)工業(yè) iot 數(shù)據(jù)融合
可將 python 改為接入 mqtt、modbus、opc ua 等接口,plc 作為訂閱者。
(2)ai 輔助控制
python 端可運行機(jī)器學(xué)習(xí)模型,根據(jù)實時天氣預(yù)測能耗或生產(chǎn)計劃,再通過 socket 返回控制參數(shù)。
(3)邊緣計算節(jié)點
樹莓派同時運行 plc 與 python,形成“混合智能邊緣設(shè)備”,既具實時性又具云連接能力。
(4)云服務(wù)接口
可替換天氣 api 為任意 web 服務(wù)(設(shè)備管理、能源監(jiān)控、物流狀態(tài)等),實現(xiàn) codesys 與云端系統(tǒng)的數(shù)據(jù)橋接。
八、結(jié)語:codesys 與 python 的融合之路
本文以“codesys 獲取實時天氣數(shù)據(jù)”為案例,完整展示了:
l 如何在樹莓派上運行 codesys runtime;
l 如何用 iec 61131-3 語言建立socket通信;
l 如何通過 python 服務(wù)器實現(xiàn)外部數(shù)據(jù)訪問;
l 以及兩者協(xié)作實現(xiàn)“plc 調(diào)用 python”的機(jī)制。
這不是簡單的網(wǎng)絡(luò)實驗,而是一個工業(yè)控制新時代的縮影。plc 不再局限于封閉的邏輯循環(huán),而是可以與 ai、云端、web 世界自由交互。python 也不只是桌面腳本,而能成為工業(yè)現(xiàn)場的“智慧補(bǔ)腦”。
這種模式預(yù)示著未來工業(yè)控制系統(tǒng)的方向:控制邏輯與數(shù)據(jù)智能的融合,實時性與開放性的統(tǒng)一。