pythonを使用して独自のscadaシステムを構築することに関する以前の記事の続きで、データ交換用のテキスト形式であるjsonを使用して、デバイスと出力データ間の交換を整理する方法について説明します。
この場合、
modbusTCPおよび
OPCUAライブラリのクライアント部分を使用します。
その結果、スレーブの
マスターとして機能する
httpサーバーを取得し、
スレーブモードで機能します。

Modbusマスター
modbus TCPウィザードを構成するには、必要なライブラリをインポートします。
import modbus_tk import modbus_tk.defines as cst import modbus_tk.modbus_tcp as modbus_tcp
IPアドレスとポート、および応答を待つための
タイムアウトでウィザードを初期化する必要があります。
master = modbus_tcp.TcpMaster(host='127.0.0.1', port=502) master.set_timeout(2)
レジスタの名前とセルアドレスを示す、
スレーブデバイスのポーリングの周期機能について説明します。
def getModbus(): while True: try: data= master.execute(rtu, cst.READ_INPUT_REGISTERS,0,1 ) except Exception as e: print (e) time.sleep(1)
次に、別のスレッド
threadでポーリングサイクルを開始する必要があり
ます 。
modb = threading.Thread(target=getModbus) modb.daemon = True modb.start()
その結果、
IPアドレス 127.0.0.1およびポート502の
modbusTCPプロトコルを使用したスレーブデバイスの周期的なポーリングが
開始され
、READ_INPUT_REGISTERSレジスタが
読み取られ、0x00にある値が
データ変数に書き込まれます。
OPCUAクライアント
OPCUAサーバーからデータを受信するには、
freeopcuaライブラリを接続する必要があります
from opcua import ua, Client
新しいクライアント接続を作成します。
url="opc.tcp://127.0.0.1:4840/server/" try: client = Client(url) client.connect() root = client.get_root_node() except Exception as e: print(e)
OPCサーバーには、継承の厳密な階層があり、
親と
子の正確な定義が
あるため、多数のネストされたオブジェクトを含む非常に複雑なシステムを構築できます。 しかし、この場合、今日ではそのような多くの関数は必要ありませんでした。そのため、
Objectsのルートフォルダーにノードを作成して値を割り当てることに限定しました。 このような
オブジェクト-> MyNode-> MyNodeValueのようになりましたが、より複雑なシステムの構築にはこのメソッドは受け入れられないことを認めなければなりません。
obj = root.get_child(["0:Objects"]) objChild= obj.get_children() for i in range(0,len(objChild)): unitsChild.append(i) unitsChild[i]=objChild[i].get_children() parName=val_to_string(objChild[i].get_browse_name())[2:] for a in range(0, len( unitsChild[i] ) ): valName=val_to_string(unitsChild[i][a].get_browse_name())[2:] try: valData=unitsChild[i][a].get_value() data =unitsChild[i][a].get_data_value() st=val_to_string(data.StatusCode) ts= data.ServerTimestamp.isoformat() tsc= data.SourceTimestamp.isoformat() except Exception as e: print(e)
変数の値は
valDataで直接確認でき、
StatusCodeは
stに書き込まれ、
tsおよびtscはそれぞれ
ServerTimestampおよび
SourceTimestampのタイムスタンプ
が記録されます。
スレーブをポーリングする場合、ラウンドロビンポーリングも別のスレッドで実行されますが、イベントをサブスクライブする方が適切です。
Json Webサーバー
Webサーバーを作成するには、ライブラリが必要です。
from http.server import BaseHTTPRequestHandler, HTTPServer import json import base64
サーバー自体を起動するのは難しくなく、コマンドは2つだけで、ネットワークには多くの説明と例があります。
server_address = (“127.0.0.1”, 8080) httpd = server_class(server_address, handler_class) try: httpd.serve_forever() except Exception as e: print(e) httpd.server_close()
最も興味深いことは、テストのためにChromeまたはFirefoxブラウザーから作成されたサーバーに接続する必要が生じたときに始まりました。
refuse_connectを常に表示します。
ネットワークを少し検索して、解決策を見つけました-do_GET関数に追加する必要があります:
self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Credentials', 'true')
これで、正常に機能するWebサーバーにアクセスできましたが、オープンアクセスでしたが、何らかの認証、ログインとパスワードによるアクセスを確立したいと思います。
判明したように、ヘッダーを使用してこれを行うのは特に難しいことではありません。
例 def do_GET(self): global key if self.headers.get('Authorization') == None: self.do_AUTHHEAD() response = { 'success': False, 'error': 'No auth header received'} self.wfile.write(bytes(json.dumps(response), 'utf-8')) elif self.headers.get('Authorization') == 'Basic ' + str(key): resp=[] self.send_response(200) self.send_header('Allow', 'GET, OPTIONS') self.send_header("Cache-Control", "no-cache") self.send_header('Content-type','application/json') self.send_header('Access-Control-Allow-Origin', 'null') self.send_header('Access-Control-Allow-Credentials', 'true') self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') self.send_header('Access-Control-Allow-Headers', 'X-Request, X-Requested-With') self.send_header("Access-Control-Allow-Headers", "Authorization") self.end_headers() req=str(self.path)[1:] if(req == "all" ): try: for i in range(0,units): resp.append({varName[i]:[reg[i],varNameData[i]]}) i+=1 self.wfile.write(json.dumps( resp ).encode()) except Exception as e: print('all',e) else: for i in range(0,units): if(req == varName[i] ): try: resp =json.dumps({ varName[i]:varNameData[i] } ) self.wfile.write(resp.encode()) except Exception as e: print(e) i+=1 else: self.do_AUTHHEAD() response = { 'success': False, 'error': 'Invalid credentials'} self.wfile.write(bytes(json.dumps(response), 'utf-8'))
ブラウザーを使用して接続しようとすると、認証が実行されてデータが転送されますが、パーサーなしでブラウザーからデータを受信することはお勧めできません。JavaScryptでGETメソッドを使用し、htmlページのスクリプトを使用してXMLHttpRequest()関数を使用してデータを受信することを想定しています。 しかし、そのような実装では、ブラウザーは最初にGETメソッドではなくOPTIONSメソッドによってリクエストを送信し、GETメソッドによってリクエストが実行された後にのみresponse = 200を受信する必要があります。
別の機能を追加しました:
def do_OPTIONS(self): self.send_response(200) self.send_header('Access-Control-Allow-Credentials', 'true') self.send_header('Access-Control-Allow-Origin', 'null') self.send_header('Access-Control-Allow-Methods', 'GET,OPTIONS') self.send_header('Access-Control-Allow-Headers', 'X-Request, X-Requested-With') self.send_header("Access-Control-Allow-Headers", "origin, Authorization, accept") self.send_header('Content-type','application/json') self.end_headers()
この関数が接続されると、
「Access-Control-Allow-Origin」を使用してチェックが実行され、
「null」に設定されていない場合、交換は行われません。
ログインとパスワードでアクセスできるようになりました。ブラウザーはシナリオに従ってデータを交換しますが、SSLデータ暗号化を整理することをお勧めします。 これを行うには、サーバーを起動する前にSSL証明書ファイルを作成し、次の行を追加します。
httpd.socket = ssl.wrap_socket (httpd.socket, certfile=pathFolder+'json_server.pem',ssl_version=ssl.PROTOCOL_TLSv1, server_side=True)
もちろんこれは自己署名証明書ですが、いずれにしてもオープンプロトコルよりも優れています。
HTMLページのスクリプト内のデータを処理するには、上記のXMLHttpRequest()関数を使用できます。
例 xmlhttp=new XMLHttpRequest(); xmlhttp.open("GET","http://192.168.0.103:8080/all",true); xmlhttp.setRequestHeader("Authorization", "Basic " + btoa(login+":"+password)); xmlhttp.withCredentials = true; xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(null); xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { resp= xmlhttp.responseText; parseResp=JSON.parse(resp); } }
JSON Configuratorの説明
以下は、スクリプトを実行するためのコンフィギュレーター設定の説明例です。
ウィンドウの外観とコントロールボタンの目的:

タスクがパラメーター付きの温度センサーからデータを受信することであるとしましょう:
プロトコル: modbusTCP
IPアドレス: 192.168.0.103
ポート: 502
RTU: 1
登録: READ_INPUT_REGISTERS(0x04)
住所: 0
変数名: tempSensor_1
JSONサーバーでこのデータを印刷します。
フォーマット: json
IPアドレス: 192.168.0.103
ポート: 8080
ログイン: 111
パスワード: 222
json.pyを実行し、
左上に新しいサーバーボタン(+)を追加し、名前を指定して保存します。
次に、作成した
インスタンスを作成し、Webサーバーのパラメーターを入力する必要があります。

スレーブデバイスのポーリングパラメータ、この場合は温度センサーを書き留めます。

その後、スクリプトの保存ボタンをクリックすると、Windowsの場合は
web_( データベース内のサーバーの数).batまたは
Linuxの場合は
web_(データベース内のサーバーの数).shという名前のファイルが
scrフォルダーに表示されます。 スクリプトの実行パスはこのファイルに書き込まれます。
この場合、Windowsの例、
web_15.batファイル:
rem 'ScadaPy Web JSON v.3.14'
rem Web ' '
rem Http '192.168.0.103'
rem Http '8080'
start c:\Python35\python.exe F:\scadapy\main\source\websrv.py 15 F:\scadapy\main\db\webDb.db
保存ボタンの隣にあるボタンをクリックすると、すぐにスクリプトを実行できます(すべてのボタンにはツールチップが装備されています)。
起動後、起動と接続に関する情報を含むコンソールウィンドウが表示されます。

ブラウザを起動し
たら 、接続文字列
_https://192.168.0.103:8080 / allを書き込み、パスワードを入力すると、Chromeで次のように表示されます。

またはFirefoxの場合:

実行中のサーバーのコンソールには、接続セッションに関する情報が表示されます。

この場合、パラメーターは
すべて GETリクエストで入力されているため、サーバーで構成されているすべての変数のデータを取得します。 変数の数を増やすと、現在使用されていないデータを受信して処理する必要があるため、これは完全に正しいわけではありません。そのため、値を処理する必要がある変数の直接名を入力する方が良いでしょう:
tempSensor_1この場合:
リクエスト
-tempSensor_1答えは
{"tempSensor_1":[2384]}です。JavaScript処理
リクエストの形成とレスポンスの処理をhtmlページに統合する方法を少し説明したいと思います。
XMLHttpRequest()関数を使用してリクエストを実行できますが、現在利用可能な他の接続方法があります。 接続に成功し、ステータス200を取得し
たら 、
JSON.parse()関数を実行するだけで十分です。
クエリ実行の周期性を確立するには、タイマーを実行する必要があります。
例。 function getTemp() { var dataReq='tempSensor_1'; var login='111', passw='222'; var ip='192.168.0.103'; var port='8080'; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.open("GET","https://"+ip+":"+port+"/"+dataReq,true); xmlhttp.setRequestHeader("Authorization", "Basic " + btoa(login+":"+passw)); xmlhttp.withCredentials = true; xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(null); xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { resp= xmlhttp.responseText; parseResp=JSON.parse(resp); data=parseResp.tempSensor_1[0]; log("Val :" + data +"\n"); resp=data*0.1; } } }
受信したデータをさまざまなウィジェットで表示する例。

OPCUAサーバーからデータを受信すると、JSON応答の構造はわずかに変わりますが、わずかに変わります。 いずれにせよ、そこを理解することは難しくありません。
githubのダウンロードリンク