Project#using PLCNEXT x AXC F ML1000 xRoboDK to start you machine learning

この記事ではAXC F 3152 PLCnext と最近発売し始めた機械学習モジュールAXC F XT ML1000の立ち上げ手順・デモ作成までご紹介します。よろしくおねがいします。

Thanks!

この記事では、Phoenix Contact ジャパンからAXC F 3152とAXC F XT ML1000のお貸出しの提供でお送りします。誠にありがとうございます。

Video

AXC F XT ML1000?

この拡張モジュールAXC F XT ML1000を使用することにより、AXC F 3152 PLCnext controllerから直接機械学習アプリケーションを実装できます。データはPLCと直接に交換でき、ML1000使用するモデルFileはTensorFlow Lite filesで、Edge TPU (Coral)を AXC F 3152 PLCnext controllerと統合、なおかつPythonでAI アプリケーションを作成できます。

Firmware

PLCNEXT ControllerはAXC F 3152 のみでFirmware >2022.0 LTS以上です。

Interneal Cirucit

下図はML1000内部回路です。

  • Tensor Processing unit
  • エラー表示のLED
  • Extension busはAxioline F 拡張モジュール用のもの

Layout

以下はML1000のLayoutです。

  1. Bus base モジュール
  2. Function Identification
  3. Electronics module
  4. 電源供給のConnector
  5. 電源診断のLED

Power Supply

基本は24v DCで、隣のAXCF 3152から電源を取ればよいと思います。ですが、注意するのはAXC F3152とAXC F XT ML1000は必ず同じなので電源をとってください。AXC F 3152だけ電源なくなりAXC F XT ML1000がまだPOWER ONしてる状態になるとAXC F XT ML1000モジュールがエラーで復旧不可になります。

Terminal

ML 1000はAXC F 3152 CPUと同じく小さな電源ターミナルがついています。

赤はa1,a2は24V+側で、青いのはb1,b2はマイナス側です。

LED

AXC F XT ML1000で使えるLEDはその左下だけです。赤になるとエラーです。

ColorStateDescription
Onモジュールエラーがあります。例えば、CPUがSupportしないモジュールが正しくインストールされてない稼働中に単体で電源落としたことある
黄色OnBoot-upシーケンス中
On稼働中、エラーなし

Setup

AXC F XT ML1000をSetupするには、2つの前提が必要です。

  • root 権限でLoginしてる
  • インタネットの接続

PLCNEXT Engineer Version

こちらは自分のPLCNEXT Enginner Versionです。

SFTP Download

Reference Link

以下のStepでRoot、PLCNEXTをSFTP 使用しFileをDownloadする手順を説明しました。

Beckhoff#TwinCAT BSD WinSCP

Step

下記のLinkからML1000のSoftware packageをDownloadします。

https://www.phoenixcontact.com/en-in/products/extension-module-axc-f-xt-ml-1000-1259849

ライセンス同意し、Downloadします。

このようなZIP FileがDownloadされました。

解凍しso.1の2つのFileは先Reference Link通りに、\usr\libに転送してください。

PROTOはまた別のところで使いますので、いまはひとまず放置します。

Download python basic

もしPLCNEXT StoreからすべてのPython Basic appがインストールされたらこのStepが不要です。異なる場合は以下のパッケージをPIPからインストールしてください。

  • numpy
  • pillow
  • pycoral
  • tflite_runtime

Refercence Link

下記のLinkはOfflineでPLCNEXTのApp StoreからAppをインストール手順を説明します。PackageはPython basicなので、その記事に沿ったままインストールしてください。 

PLCNEXT#Install a Solution App in Offline

Check the Module

以下のコマンドでAXC F XT ML 1000にアクセスできるかを確認できます。

lspci –nn | grep 089a

もしアクセスできるのであればモジュールからこのような返答がきます。

逆にもしアクセスできないのであれば、システムからエラーメッセージを返答します。そのときはモジュールが正しくインストールされたか、電源があるかをCheckしてください。

Downloading and installing a sample application

次はあなたのAXC F 3152 PLCNEXT ControllerをInternetと接続してください。そしてGoogleからSampleをDownloadし、モジュールをテストします。今回の例は鳥の画像判定Applicationです。

Controller内にDirectoryを作成し、作業Directoryを移動します。

mkdir coral && cd coral

DirectoryにSample applicationをDownloadします。

git clone https://github.com/google-coral/pycoral.git

Sample アプリケーションをインストラクターします。

bash examples/install_requirements.sh classify_image.py

最後は AXC F 3152 controllerをInternetと切断します。

SSL Certificate problem certificate is not yet valid

もしSSL Certificate エラーがあれば以下の記事を参考にしてください。

PLCNEXT#SSL Certificate problem cerificate is not yet valid

Executing the sample application

以下のコマンドで画像判定を実行し、判定したい鳥の写真をパラメーターとして入力します。

python3 examples/classify_image.py \
–model test_data/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \
–labels test_data/inat_bird_labels.txt \
input test_data/parrot.jpg

TPUから判定結果が返答されました!

Integrate With PLCNEXT Controller

次は判定した結果をどうやってPLCNext Engineerのアプリケーションに転送するかを考えます。もちろんOPCUAやMQTTなどもOKですが、ここではAXCF 3152に統合されたgRPC serverを使用し、判定結果を指定の変数に転送します。

gRPC communication?

まずgRPC通信のことを簡単に紹介しますね。gRPCはオープンソースで・High PerformanceのFrame Work、Remote Procedural Callの略です。

最初にGOOGLEが開発され、いまは Cloud Native Computing Foundationが主導していますgRPC はUser-frinedly、異なるサービスで通信できます。

Communication LayerではgRPC はHTTP2を使用し、Protocol Bufferをを使っています。

例えばいま異なるServicesがあるとします。よくあるのはAMAZONでの買い物ですね。商品を購入するにはBuy Servicesがあり、買った商品は物流センターに頼む必要があります。つまりDelivery Servicesが必要です。買い物してるおすすめ商品の項目もあります。どの商品を客に薦めるのあhPromotion Servicesあります。そしてそれらのServicesがUser seriesとつながっています。つまりあなたです。それらのSeriesは必ずしも同じプログラミング言語を実装するとはかぎらず、お互いにやりとりするためにいまよく使われているのはREST(HTTP-JSON)です。


ですが、RESTに様々な考慮が出てきます。例えば、

  • Data Modelはどうするのか。JSON?XML?他には?
  • End Pointが長くて複雑になる。
    • GETなら、/api/v1/something/123/post/abcsd
    • POSTなら、/api/v1/post/something
  • Errorの検知
  • 効率か?
    • 1回の呼び出しはどれくらいのデータ?
    • 大量のデータは問題ないか?データが少なすぎるとAPIのCALL回数が増えすぎるのではないか?
  • 1000 Clientは大丈夫ですか?
  • Load Balaningは大丈夫か?
  • 複数の言語はSupportしていますか?
  • User認証・監視・Loggin機能は?

そこに出てくるのはAPIです。

APIは簡単に言いますと契約です。Clientがリクエストを送信、そして受信側がそのリスボンを返す。それだけです。すべてDATAです。

先にも言いましたが、gRPCは無料で、オープンソースでGoogleから開発され、現在は Cloud Native Computing Foundationが主導しています。Higher Layerになると、RPCは私達がリクエストとリスボンを定義し処理してくれるものだと思ってください。gRPCは:

  • Modern
  • 高速
  • 効率的
  • HTTP/2で作られ
  • Stream Support
  • プログラミング言語に依存しない
  • Loggin・認証・監視などのPlug-inにはめ込むのは簡単

RPC特徴の一つとしてはClient側が直接Serverからの関数をよび出すようにみえます。

つまり下図のように関数名が同じく、直接Server側から直接呼び出してるみたいなのはRPC Codeです。

実際のアプリケーションではこのようになるかもしれません。

gRPCを使用するには、”Protocol buffers”からメッセージとサービスを定義する必要があります。

Client側はまず読みリクエストと書きリクエストを初期化します。書き読みのリクエストは

.proto Fileで定義されgRPC GitHub repositoryからDownloadできます。.proto Fileは自動的に通信上のプログラムを生成します。

PLCnext Technology and gRPC 

アプリケーションにより、私はPLCnext Runtime Systemとやり取りするにはなにかの通信Intefaceが必要です。100%ができなく、一部は機能上の制限か、プログラム言語の制限にもあるかもしれません。

gRPC がinter process communication (IPC)にInterfaceを追加できます。

RPC Frameworkを使用するとオープンソース・プログラム言語に頼らないProtocolになります。

Step1:Creating the required variables

PLCnext Engineerに変数を定義し、プロジェクトをCPUにDownloadします。

今回の変数はINT_Aの文字列になります。

Step2:Install the packages

次はPackageをインストールします。

もしPLCNEXT StoreからPython Basic appをインストールしてないなら、grpcioとprotobuf Packageをインストールしてください。

pip3 install grpcio
pip3 install protobuf

Phoenix ContactからDownloadしたML1000_packages内にあるPROTO FolderをAXC F 3152 controllerの下記Directoryに移動します。

移動先は実際Pythonの判定Scriptを実行するpyと同じのDirectoryにいれてください。

\home\root\coral\pycoral\examples

Step3:Making changes in the machine-learning application

Python scriptの実行結果をPLCNEXT CPUに転送するにはgRPC Communication を使用すると先ほども言いました。Scriptを開き、プログラムを少し修正します。

Importing modules

まずScriptの最初に必要なモジュールをImportします。

import grpc
from google.protobuf.empty_pb2 import Empty
from PROTO.Authentication_pb2 import CreateSessionRequest
from PROTO.Authentication_pb2_grpc import AuthenticationStub
from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \
    IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest
from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub
from PROTO.PLCnextBase_pb2 import CoreType
from PROTO.ErrorCode_pb2 import ErrorCode

Opening a gRPC channel

gRPCチャンネルを開きます。

channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”)

Defining the function :

PLCNEXT CPUの変数を上書きする関数を作成します。

def WriteSingle_StringValue(P_PortName,P_StringValue):
    stub=IDataAccessServiceStub(channel)
    response1= IDataAccessServiceWriteSingleRequest()
    response1.data.PortName= P_PortName
    response1.data.Value.TypeCode=19
    response1.data.Value.StringValue=P_StringValue
    response=stub.WriteSingle(response1)
    return response

Writing the result into a variable 

最後は判定結果をPLCNEXT CPUの変数に書き込みます。

result=(‘%s’ % (labels.get(c.id, c.id)))
WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result)

Full Example Code

こちらは実際、拡張されたPython Scriptです。

import grpc
from google.protobuf.empty_pb2 import Empty
from PROTO.Authentication_pb2 import CreateSessionRequest
from PROTO.Authentication_pb2_grpc import AuthenticationStub
from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \
    IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest
from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub
from PROTO.PLCnextBase_pb2 import CoreType
from PROTO.ErrorCode_pb2 import ErrorCode

#Opening a gRPC channel
channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”)

import argparse
import time

import numpy as np
from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter


def main():
  parser = argparse.ArgumentParser(
      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  parser.add_argument(
      ‘-m’, ‘–model’, required=True, help=’File path of .tflite file.’)
  parser.add_argument(
      ‘-i’, ‘–input’, required=True, help=’Image to be classified.’)
  parser.add_argument(
      ‘-l’, ‘–labels’, help=’File path of labels file.’)
  parser.add_argument(
      ‘-k’, ‘–top_k’, type=int, default=1,
      help=’Max number of classification results’)
  parser.add_argument(
      ‘-t’, ‘–threshold’, type=float, default=0.0,
      help=’Classification score threshold’)
  parser.add_argument(
      ‘-c’, ‘–count’, type=int, default=5,
      help=’Number of times to run inference’)
  parser.add_argument(
      ‘-a’, ‘–input_mean’, type=float, default=128.0,
      help=’Mean value for input normalization’)
  parser.add_argument(
      ‘-s’, ‘–input_std’, type=float, default=128.0,
      help=’STD value for input normalization’)
  args = parser.parse_args()

  labels = read_label_file(args.labels) if args.labels else {}

  interpreter = make_interpreter(*args.model.split(‘@’))
  interpreter.allocate_tensors()

  # Model must be uint8 quantized
if common.input_details(interpreter, ‘dtype’) != np.uint8:
    raise ValueError(‘Only support uint8 input type.’)

  size = common.input_size(interpreter)
  image = Image.open(args.input).convert(‘RGB’).resize(size, Image.ANTIALIAS)

  # Image data must go through two transforms before running inference:
  # 1. normalization: f = (input – mean) / std
  # 2. quantization: q = f / scale + zero_point
  # The following code combines the two steps as such:
  # q = (input – mean) / (std * scale) + zero_point
  # However, if std * scale equals 1, and mean – zero_point equals 0, the input
  # does not need any preprocessing (but in practice, even if the results are
  # very close to 1 and 0, it is probably okay to skip preprocessing for better
  # efficiency; we use 1e-5 below instead of absolute zero).
  params = common.input_details(interpreter, ‘quantization_parameters’)
  scale = params[‘scales’]
  zero_point = params[‘zero_points’]
  mean = args.input_mean
  std = args.input_std
if abs(scale * std – 1) < 1e-5 and abs(mean – zero_point) < 1e-5:
    # Input data does not require preprocessing.
    common.set_input(interpreter, image)
else:
    # Input data requires preprocessing
    normalized_input = (np.asarray(image) – mean) / (std * scale) + zero_point
    np.clip(normalized_input, 0, 255, out=normalized_input)
    common.set_input(interpreter, normalized_input.astype(np.uint8))

  # Run inference
  print(‘—-INFERENCE TIME—-‘)
  print(‘Note: The first inference on Edge TPU is slow because it includes’,
        ‘loading the model into Edge TPU memory.’)
for _ in range(args.count):
    start = time.perf_counter()
    interpreter.invoke()
    inference_time = time.perf_counter() – start
    classes = classify.get_classes(interpreter, args.top_k, args.threshold)
    print(‘%.1fms’ % (inference_time * 1000))

  print(‘——-RESULTS——–‘)
for c in classes:
    print(‘%s: %.5f’ % (labels.get(c.id, c.id), c.score))

  result=(‘%s’ % (labels.get(c.id, c.id)))
  WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result)


def WriteSingle_StringValue(P_PortName,P_StringValue):
    stub=IDataAccessServiceStub(channel)
    response1= IDataAccessServiceWriteSingleRequest()
    response1.data.PortName= P_PortName
    response1.data.Value.TypeCode=19
    response1.data.Value.StringValue=P_StringValue
    response=stub.WriteSingle(response1)
    return response

if __name__ == ‘__main__’:
  main()

Result

Scriptを実行します。

python3 examples/classify_image_plcnext.py \
–model test_data/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \
–labels test_data/inat_bird_labels.txt \
input test_data/parrot.jpg

判定結果をINT_Aに書き込みました。それで成功!

Try more Fun!

それだけで終わってもよいですが、せっかくなのでもう1つのDEMOを作ります。

Reference Link

Beckhoff#TwinCAT x RoboDK x Pi4 x Coral USB ML – Image Classification

Download a new Model

下記のLINKからMobileNet V1 192x192x5の”Edge TPU modelとLabel fileをDownloadします。

https://coral.ai/models/image-classification/

Transfer to the CPU

Modelをテストするために.tfile、Label File、テストイメージを/home/root/coral/pycoral/test/data/ に移動します。

今回テストするのはToilet Paperです笑。

Run the Script

コマンドでScriptを実行します。

python3 examples/classify_image_plcnext.py –model test_data/mobilenet_v1_0.75_192_quant_edgetpu.tflite –labels test_data/imagenet_labels_q.txt —input test_data/test_image3.jpg

判定するのはこの画像ですね。

Paper towelは79%くらいあるってことはOKですね!

Implementation

次は実際のデモを作ります。下図は今回の構成です。AXC F 3152はOPCUA Serverを立ち上げ、RoboDK側のPython OPCUA Clientと通信しHandshake情報を交換します(プログラム起動・Busy・Endなど)。

ロボットが撮影した写真をSFTP経由でAXF F 3152に送信します。

AXF F XT ML1000とAXC F 3152間はgRPCでHandshakeデータ(判定開始・結果・Busy・Endなど)を交換します。

Handshake Flow

こちらは今回のHandShake Flowになります。

AXC F3152がRoboDKにStart信号を送信します。

RoboDKがロボットプログラムを起動し、Busyを返し、その間撮影した写真をSFTP経由でAXC F3152に送信します。

AXCF 3152がRoboDKからEndのTrue信号をもらったらML1000にStartApps信号を送信します。ML1000はこの信号を受け、画像判定をはじめ、結果をAXF F 3152に書き込みます。ML1000のプロセスが完了したらAXF F 3152にEnd信号を送ります。AXC F3152がStartApps信号をリセットし、ML1000も完了です。

最後はAXC F3152がCanEnd信号をRoboDK送り、RoboDKがプロセスを完了します。そしてまた最初からのStart Signalを待つ状態になります。

PLCNEXT 

OPCUA Server Setup

まずOPCUA serverをSetupします。

CertificateをSelf signed by controllerとSecurity Policiesを実際のClientが対応できるものに合わせてください。

DUT

DUT_ML1000

DUT_ML1000はPLCNEXTとML1000モジュールのHandShakreや交換データを定義します。

TYPE

    DUT_ML1000_ClassificationResult : STRUCT
        Label:STRING;
        Score:REAL;
    END_STRUCT
   
    DUT_TO_ML1000  : STRUCT
        bStart2Classification:BOOL;
        sImageName :STRING;
    END_STRUCT
   
    DUT_From_ML1000 :STRUCT
        bClientReady:BOOL;
        bTPUReady:BOOL;
        bBusy:BOOL;
        bFinished:BOOL;
        Results:ARRAy[1..6]of DUT_ML1000_ClassificationResult;
        Result_label:ARRAY[0..6]of string;
        Result_Score:ARRAY[0..6]of REAL;
    END_STRUCT
       
END_TYPE
DUT_RoboDK

DUT_RoboDKはPLCNEXTとRoboDKのHandShakreや交換データを定義します。

TYPE
   
    DUT_OPCUA_ReadFromRoboDK  : STRUCT
        sImagePath:ARRAY[1..6]of string;
        bImageisSaved:BOOL;
        bBusy:BOOL;
        bEnd:BOOL;
    END_STRUCT
   
    DUT_OPCUA_Write2RoboDK  : STRUCT
        bStartProgram:BOOL;
        bCanEnd      :BOol;
    END_STRUCT
   
    DUT_OPCUA_Write2PI4  : STRUCT
        bStart2Classification:BOOL;
        sImageName :STRING;
    END_STRUCT
   
END_TYPE

Program

プログラムを作ります。

VAR

Main>Variables Tabで以下の変数を定義します。

PROGRAM

こちらはプログラムになります。Case文を使って先程書いたFlowの通り、HandShakeやデータ交換を行います。

case iStep of
   
    0:
        ReadFromRoboDK.bBusy:=False;
        ReadFromRoboDK.bEnd:=False;
        ReadFromRoboDK.bImageisSaved:=False;
        Write2RoboDK.bCanEnd:=False;
        Write2RoboDK.bStartProgram:=False;
        for i:=1 to 6 DO
            ReadFromRoboDK.sImagePath[i]:=’ ‘;
        END_for;       

        ReadFromML1000.bBusy:=False;
        ReadFromML1000.bClientReady:=False;
        ReadFromML1000.bTPUReady:=False;
        ReadFromML1000.bFinished:=False;

       
        Write2ML1000.bStart2Classification:=False ;
        Write2ML1000.sImageName:= ‘ ‘;
        iStep:=10;
        Started:=False;
   
    10:
        if Write2RoboDK.bStartProgram and not Started THEN
            Started:=True;
            iStep:=20;
        end_if;
    20:
        if ReadFromRoboDK.bBusy THEN
            Write2RoboDK.bStartProgram:=False;
            iStep:=30;
                    for i:=1 to 6 DO
            ReadFromML1000.Result_label[i]:=’ ‘;
            ReadFromML1000.Result_Score[i]:=0.0;
        END_for;
        end_If;
    30:
        if ReadFromRoboDK.bEnd THEN
            Write2ML1000.bStart2Classification:=True;
            iStep:=40;
        end_if;
    40:
        if ReadFromML1000.bFinished THEN
            Write2RoboDK.bCanEnd:=True;
            Write2ML1000.bStart2Classification:=False; 
            iStep:=50;
        end_if;
    50:
      if not ReadFromRoboDK.bEnd  then
            Write2RoboDK.bCanEnd:=False;
            iStep:=0;
        end_if; 
END_CASE;

HMI Setup

PLCNEXT Enginnerで簡単なHMI Applicationを作成し、CPUにDownloadします。

StartRobotProgramはボタンで、それ以外はAll表示です。

ちなみにStartRobotProgramは先程書いたFlowの一番最初のStart 信号になります。

RoboDK Side

RoboDK のPython ScriptはReference Linkのプロジェクトから修正します。

from robodk import robolink    # RoboDK API
from robodk import robomath    # Robot toolbox
import datetime
import time
from opcua import Client,ua
import random
import os
import paramiko

SFTP_FROM=’C:\FTPFiles\RoboDK’
SFTP_TO=’/home/root/coral/pycoral/test_data/’

config = {
“host” : “192.168.3.10”,
“port” : 22,
“username” : “SFTPUSER”,
“password”   : “SFTPPASSWORD”
}
SFTPClient=paramiko.SSHClient()
SFTPClient.set_missing_host_key_policy(paramiko.AutoAddPolicy)


os.chdir(r’C:\Users\root\Desktop\ML1000_package\ML1000_package’)

RDK = robolink.Robolink()


UR=RDK.Item(“UR5”)
CameraPosition1=RDK.Item(‘tCamera1’)
CameraPosition2=RDK.Item(‘tCamera2’)
CameraPosition3=RDK.Item(‘tCamera3’)
CameraPosition4=RDK.Item(‘tCamera4’)
CameraPosition5=RDK.Item(‘tCamera5’)
CameraPosition6=RDK.Item(‘tCamera6’)
Prog=RDK.Item(‘pMain’)
ENDPOINT=’opc.tcp://user:passwd@192.168.3.10:4840′
USERNAME=’PLCNEXTUSERNAME’
PASSWORD=’PLCNEXTPASSWORD’


NODE_BUSY=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bBusy’
NODE_END=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bEnd’
NODE_IMAGEISSAVED=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bImageisSaved’
NODE_SIMAGEPATH=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.sImagePath’
NODE_STARTPROGRAM=’ns=5;s=Arp.Plc.Eclr/Main1.Write2RoboDK.bStartProgram’
NODE_CANEND=’ns=5;s=Arp.Plc.Eclr/Main1.Write2RoboDK.bCanEnd’

client=Client(ENDPOINT)
client.set_user(USERNAME)
client.set_password(PASSWORD)
client.set_security_string(‘Basic256,Sign,cert.der,key.pem’)

try:
    client.connect()
    client.load_type_definitions()
    print(‘Node2Server is loaded..’)
    NodeBusy=client.get_node(NODE_BUSY)
    NodeEnd=client.get_node(NODE_END)
    NodeImageIsSaved=client.get_node(NODE_IMAGEISSAVED)
    NodeSImagePath=client.get_node(NODE_SIMAGEPATH)
    NodeCanEnd=client.get_node(NODE_CANEND)

    print(‘NodeFromServer is loaded..’)
    NodeStartProgram=client.get_node(NODE_STARTPROGRAM)

    Busy=NodeBusy.get_value()
    End=NodeEnd.get_value()
    print(Busy,End)
   
except Exception as e:
    print(e)
    quit()


def WriteNode():
    NodeBusy=client.get_node(NODE_BUSY)
    NodeEnd=client.get_node(NODE_END)
    NodeImageIsSaved=client.get_node(NODE_IMAGEISSAVED)
    NodeSImagePath=client.get_node(NODE_SIMAGEPATH)
    return NodeBusy.get_value(),NodeEnd.get_value(),NodeImageIsSaved.get_value(),NodeSImagePath.get_value()

def ReadNode():
    NodeStart=client.get_node(NODE_STARTPROGRAM)
    NodeCanEnd=client.get_node(NODE_CANEND) 
    return NodeStart.get_value(),NodeCanEnd.get_value()
   
cam_item = RDK.Item(“Camera1”)
if not cam_item.Valid():
    print(‘No camera..’)
    quit()

         
def createImagePath(s):
    path=getCurrentTime()
    return “C:\FTPFiles\RoboDK”+path+”.jpg”,path
   

def getCurrentTime():
    return time.strftime(‘%Y_%m_%d_%H_%M_%S_’+str(random.randint(0,99)))


def Sanp(Path,Cam):
    return RDK.Cam2D_Snapshot(Path,Cam)


def SetNodeValue(Node,value):
    DataType=Node.get_data_type_as_variant_type()
    DataValue=ua.DataValue(ua.Variant(value,DataType))
    Node.set_data_value(DataValue)

NodeSImagePath=client.get_node(NODE_SIMAGEPATH)
flag=[False,False,False,False,False,False]
photoTaken=[False,False,False,False,False,False]
counts=0
start=0
End=False
_busy=False
try :
    while True:
        Busy,End,ImageIsSaved,Path=WriteNode()
        bStartProgram,bCanEnd=ReadNode()
        Pose=UR.Pose()
        if not _busy and bStartProgram :
            _busy=True
            PathArray=[]
            value = Prog.RunProgram()
            End=False
        if Prog.Busy():
            NodeBusy.set_value(ua.DataValue(True))
            if Pose == CameraPosition1.Pose() and not flag[0]:
                path,p=createImagePath(“001”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos1..’)
                if ret:
                    flag[0]=True
                    PathArray.append(p)
                    UR.setDO(‘CamIO_1’,1)

            elif Pose == CameraPosition2.Pose()and not flag[1]:
                path,p=createImagePath(“002”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos2..’)
                if ret:
                    UR.setDO(‘CamIO_2’,1)
                    PathArray.append(p)
                    flag[1]=True

            elif Pose == CameraPosition3.Pose()and not flag[2]:
                path,p=createImagePath(“003”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos3..’)
                if ret:
                    UR.setDO(‘CamIO_3’,1)
                    PathArray.append(p)
                    flag[2]=True

            elif Pose == CameraPosition4.Pose()and not flag[3]:
                path,p=createImagePath(“004”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos4..’)
                if ret:
                    UR.setDO(‘CamIO_4’,1)
                    PathArray.append(p)
                    flag[3]=True

            elif Pose == CameraPosition5.Pose()and not flag[4]:
                path,p=createImagePath(“005”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos5..’)
                if ret:
                    UR.setDO(‘CamIO_5’,1)
                    PathArray.append(p)
                    flag[4]=True

            elif Pose == CameraPosition6.Pose()and not flag[5]:
                path,p=createImagePath(“006”)
                ret=Sanp(path,cam_item)
                print(‘Camera pos6..’)
                if ret:
                    UR.setDO(‘CamIO_6’,1)
                    PathArray.append(p)
                    flag[5]=True

            if flag[0] and flag[1] and flag[2] and flag[3] and flag[4] and flag[5]:
                SFTPClient.connect(config[‘host’], port = config[‘port’],username = config[‘username’],password = config[‘password’])
                sftp_con = SFTPClient.open_sftp()
                for i in range(0,6):
                    sftp_con.put(“C:\FTPFiles\RoboDK”+PathArray[i]+”.jpg”, “/home/root/coral/pycoral/test_data/”+PathArray[i]+”.jpg”)
                SFTPClient.close()
                SetNodeValue(NodeSImagePath,PathArray)
                NodeEnd.set_value(ua.DataValue(True))
                End=True
                start=time.time()
        else:
            NodeBusy.set_value(ua.DataValue(False))
            PathArray=[]
            for i in range(0,6):
                flag[i]=False
                       
        if End:
            print(‘NodeEnd=True..’)
            NodeEnd.set_value(ua.DataValue(True))
            if time.time()-start > 2 and False :
                print(‘NodeEnd=False..’)
                NodeEnd.set_value(ua.DataValue(False))
               
        if bCanEnd:
            print(‘NodeEnd=False–Cam End..’)
            NodeEnd.set_value(ua.DataValue(False))
            _busy=False
           
except Exception as e:
    print(e)
    client.disconnect()
Point1:SFTP Configuration

SFTP Server(今回はPLCNEXT AXC F3152)のIP・Username・Passwordを実機に合わせてください。

Point2:cert.der/key.pem Location

こちらはAXCF 3152 OPCUA Serverにアクセスするときに使用するCertとKeyの場合に合わせる必要があります。

Point3:OPC-UA ENDPOINT

PLCNEXTの実機に合わせてENDPOINT・Username・Passwordを設定してください。

Point4: OPCUA Endpoint/username/Password/Security

OPCUA ENDPOINT(今回はAXCF 3152)をあわせてUser/Password/Securityに合わせてください。

ML1000 Python Script

最後は先程の画像判定Scriptですね。注意するのは前のScritpから–InputのArgあり、Python実行するときにパラメタを入力する必要がありますが、いまは直接ImageをFile Systemから読み込むので、Argの部分を削除します。RoboDKから画面するとき、画面の名前も一緒に送りますので、Pythonはその画面の名前を沿って画面を読み込むだけです。

import grpc
from google.protobuf.empty_pb2 import Empty
from PROTO.Authentication_pb2 import CreateSessionRequest
from PROTO.Authentication_pb2_grpc import AuthenticationStub
from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \
    IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest
from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub
from PROTO.PLCnextBase_pb2 import CoreType
from PROTO.ErrorCode_pb2 import ErrorCode

#Opening a gRPC channel
channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”)

import argparse
import time

import numpy as np
from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter


def WriteSingle_StringValue(P_PortName,P_StringValue):
    stub=IDataAccessServiceStub(channel)
    response1= IDataAccessServiceWriteSingleRequest()
    response1.data.PortName= P_PortName
    response1.data.Value.TypeCode=19
    response1.data.Value.StringValue=P_StringValue
    response=stub.WriteSingle(response1)
    return response

def WriteSingle_BoolValue(P_PortName,value):
    stub=IDataAccessServiceStub(channel)
    response1= IDataAccessServiceWriteSingleRequest()
    response1.data.PortName= P_PortName
    response1.data.Value.TypeCode=2
    response1.data.Value.BoolValue=value
    response=stub.WriteSingle(response1)
    return response   

def read_single_value(stub, port_name):

    single_read_request = IDataAccessServiceReadSingleRequest()
    single_read_request.portName=port_name

    return stub.ReadSingle(single_read_request)


def read_multiple_values(stub, port_names):

    read_request = IDataAccessServiceReadRequest()
    read_request.portNames.extend(port_names)

    return stub.Read(read_request)

def write_single_Stringarray(stub, port_name, value,index):
   
    single_write_request = IDataAccessServiceWriteSingleRequest()
    single_write_request.data.PortName = port_name+'[‘+str(index)+’]’
    single_write_request.data.Value.TypeCode = 19
    single_write_request.data.Value.StringValue=value
    return stub.WriteSingle(single_write_request)

def write_single_Realarray(stub, port_name, value,index):
   
    single_write_request = IDataAccessServiceWriteSingleRequest()
    single_write_request.data.PortName = port_name+'[‘+str(index)+’]’
    single_write_request.data.Value.TypeCode = 13
    single_write_request.data.Value.FloatValue=value
    return stub.WriteSingle(single_write_request)




#TPU Path setup
LABELPATH=’/home/root/coral/pycoral/test_data/imagenet_labels_2.txt’
MODELPATH=’/home/root/coral/pycoral/test_data/mobilenet_v1_0.75_192_quant_edgetpu.tflite’
top_k=2
threshold=0.0
count=2
input_mean=128
input_std=128

DEBUG=False

PORTS=[‘Arp.Plc.Eclr/Main1.ReadFromRoboDK.sImagePath’]

WORKING_DIR=’/home/root/coral/pycoral/test_data/’

def main():

  labels = read_label_file(LABELPATH) if LABELPATH else {}

  interpreter = make_interpreter(*MODELPATH.split(‘@’))
  interpreter.allocate_tensors()

  # Model must be uint8 quantized
if common.input_details(interpreter, ‘dtype’) != np.uint8:
    raise ValueError(‘Only support uint8 input type.’)

  size = common.input_size(interpreter)

  params = common.input_details(interpreter, ‘quantization_parameters’)
  scale = params[‘scales’]
  zero_point = params[‘zero_points’]
  mean = input_mean
  std = input_std

  bInit=False
  bBusy=False

  stub=IDataAccessServiceStub(channel)
  step=0
  #————————————————————-

while True:

   
    PathArray=[]
    bStart2Classification = read_single_value(stub, ‘Arp.Plc.Eclr/Main1.Write2ML1000.bStart2Classification’)._ReturnValue.Value.BoolValue
    bPaths=read_multiple_values(stub, PORTS)

    if bStart2Classification and step ==0 :
      print(‘command is received.’)
      bBusy=True
      WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bBusy”, True)
      if not bInit:

        bInit=True
        Paths=bPaths._ReturnValue[0].Value.ArrayValue
        for path in Paths.ArrayElements:
          PathArray.append(path.StringValue)
          print(path.StringValue)
        step=10
      # Run inference
    if step==10:
      i=0
      for path in PathArray:
       
        image = Image.open(WORKING_DIR+path+”.jpg”).convert(‘RGB’).resize(size, Image.ANTIALIAS)
        if abs(scale * std – 1) < 1e-5 and abs(mean – zero_point) < 1e-5:
          # Input data does not require preprocessing.
          common.set_input(interpreter, image)
        else:
          # Input data requires preprocessing
          normalized_input = (np.asarray(image) – mean) / (std * scale) + zero_point
          np.clip(normalized_input, 0, 255, out=normalized_input)
          common.set_input(interpreter, normalized_input.astype(np.uint8))

        print(‘—-INFERENCE TIME—-‘)
        print(‘Note: The first inference on Edge TPU is slow because it includes’,
              ‘loading the model into Edge TPU memory.’)
        for _ in range(count):
          start = time.perf_counter()
          interpreter.invoke()
          inference_time = time.perf_counter() – start
          classes = classify.get_classes(interpreter, top_k, threshold)
          print(‘%.1fms’ % (inference_time * 1000))

        print(‘——-RESULTS——–‘)
        write=False
        for c in classes:
          print(‘%s: %.5f’ % (labels.get(c.id, c.id), c.score))
          if write is False:
            result=labels.get(c.id, c.id)
            score=c.score
            write=True

        write_single_Stringarray(stub,’Arp.Plc.Eclr/Main1.ReadFromML1000.Result_label’,result,i)
        write_single_Realarray(stub,’Arp.Plc.Eclr/Main1.ReadFromML1000.Result_Score’,score,i)
        print(i)
        i=i+1

        result=(‘%s’ % (labels.get(c.id, c.id)))
        WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result)
        step=20
    if step ==20:
        bBusy=False   
        WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bBusy”, False)
        WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bFinished”, True)

    if not bStart2Classification and step==20:
      WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bFinished”, False)
      step=0
      bInit=False


if __name__ == ‘__main__’:
  main()
Point1:Label/tfilte Location

このScriptはPython Scriptが実行するときにパラメータを入力しないようになっています。なので、Model FileとLabel Fileなどを設定してください。

Point2; The Image location

RoboDKから転送した画像のDirectoryを実際に合わせましょう。

Result

Download the Project

下記のLinkでProjectをDownloadしてください。
https://github.com/soup01Threes/PLCNEXT/blob/main/Demo_PLCNEXT_DEMO_ML1000_ROBODK.zip

  • cert.der,key.pemはPLCNEXT AXC F3152のOPCUA ServerにLoginするとき使用するKey証明書です。
  • Demo_RoboDK_PLCNEXT_Image.rdkはRoboDK Projectです。
  • imagenet_labels_2.txtはLabel Fileです。
  • mobilenet_v1_0.75_192_quant_edgetpu.tfiteはModel Fileです。
  • image_classification_plcnext_robodk.pyは画像判定するScriptです。
  • robodk_script.pyはRoboDK Project内のPython Scriptです。
  • PLCNEXT_DEMO_ML1000_ROBODK.pcweaxはPLCNEXT Projectです。
Footer_Basic

Please Support some devices for my blog

Amazon Gift List

Find ME

Twitter:@3threes2
Email:soup01threes*gmail.com (* to @)
YoutubeChannel:https://www.youtube.com/channel/UCQ3CHGAIXZAbeOC_9mjQiWQ

シェアする

  • このエントリーをはてなブックマークに追加

フォローする