STruC++#新しいOPENPLCコンパイラを試しましょう!

今回の記事はAutonomy-Logicの新しいOPENPLCコンパイラSTruCppを紹介します。またSTuCppがAdvantechのIPCにインストールします。

さ、FAを楽しもう!

前書き

いつも私の技術ブログとYouTubeチャンネルをご覧いただき、心より感謝申し上げます。また、いまFullさん(full@桜 八重 (@fulhause) / X)と共に毎週水曜日の夜にお届けしている「高橋クリス」ラジオ番組を運営しています。

技術は独り占めせず、届けるもの

私たちは工場の生産技術や制御に関する技術情報を、ブログや動画などで無料公開しています。「知識は誰でもアクセスできるべき」という信念のもと、現場で役立つ具体的なノウハウやトラブル事例などを発信してきました。すべて無料で続けているのは、「知らなかったせいで困る人」を少しでも減らしたいからです。

また、もしあなたの現場で…

  • 「このPLCとデバイスの組み合わせ、ちゃんと動くのかな?」
  • 「EtherCAT通信でうまくいかない部分を検証してほしい」
  • 「新しいリモートI/Oを試したいけど社内に検証環境がない」

など、困っている構成や試してみたいアイデアがあれば、ぜひお知らせください。機器の貸出や構成の共有が可能であれば、検証し、記事や動画で発信します(ご希望に応じて匿名対応も可能です)。

支援のかたち

現在、私達の活動はほぼ無償で続けており、記事や動画の制作には、時間と検証環境の整備が必要です。この活動を継続的にコンテンツを提供するためには、皆様の温かいご支援が大変重要です。

メンバーシップ(ラジオの応援)

Fullさんとのラジオをより充実させるための支援プランです。

https://note.com/fulhause/membership/join

Amazonギフトリスト

コンテンツ制作に必要な機材・書籍をリストにしています。

https://www.amazon.co.jp/hz/wishlist/ls/H7W3RRD7C5QG?ref_=wl_share

Patreon(ブログ・動画活動への応援)

月額での小さなご支援が、記事の執筆・検証環境の充実につながります。

https://www.patreon.com/user?u=84249391

Paypal

小さな支援が大きな力になります。

https://paypal.me/soup01threes?country.x=JP&locale.x=ja_JP

知ってたら助かること、届けたいだけです

あなたの応援が、知識の共有をもっと自由で持続可能なものにしてくれます。これからもどうぞよろしくお願いします。

soup01threes*gmail.com

https://x.com/3threes2

技術はひとりじゃもったいない。

STruC++

Structured TextをクリーンなC++17へ。STruC++は、PLCプログラマーのためのコンパイラツールチェーンです。IEC 61131-3準拠のStructured Textコードを、可読性の高いC++17へ変換します。名前はST(Structured Text)+stru(ラテン語 struere「構築する」)+C++(変換先言語)を組み合わせたものです。

thiagoralvesさんの一言

実際STruC++を紹介する前に、thiagoralvesさんの話を聞いてみましょう。

https://openplc.discussion.community/post/new-openplc-compiler-13794170

Autonomyでは、産業オートメーション向けのオープンソースツールを10年以上開発してきました。2023年にはOpenPLCをゼロから作り直し、モダンなWebベースのプラットフォームとしてOpenPLC v4をリリースしました。しかし、ひとつ重要な依存関係が残っていました。それがStructured Textコンパイラです。

この20年間、MatIECはオープンソースで唯一の現実的なコンパイラ選択肢でした。MatIECはコミュニティへの素晴らしい貢献であることは間違いありませんが、IEC 61131-3 v2にしか対応しておらず、マクロだらけのCコードを生成し、時代の限界も見え始めています。このギャップを埋めようとする取り組みはいくつか存在しますが、Edition 3を完全にサポートしてプロダクション品質に達したものはまだありません。

そこで私たちはSTruC++の開発に取り組みました。STruC++はクラス・仮想メソッド・テンプレートを活用した読みやすいC++17を生成します。IEC 61131-3 Edition 3のOOP機能(メソッド、インターフェース、継承、プロパティ)、CODESYSの拡張機能(POINTER TO、型付きリテラル、64ビット型)、そしてCODESYSライブラリの直接インポート(OSCAT Basic 335がそのままコンパイルできます)をサポートしています。インタラクティブREPLも搭載しており、–build でコンパイルしてサイクルをステップ実行したり、変数を確認したり、値を強制書き込みしたり、STとC++を並べて確認したりできます。TypeScriptで実装されているので、ブラウザ上でも動作します。

そして私たちが最もワクワクしているのが、組み込みのユニットテストです。現在あるPLCテストソリューション(TcUnit、CfUnit、CODESYS Test Manager)はすべて、特定のIDE・ランタイム・ハードウェアを必要とします。STruC++はテストランナーをコンパイラ本体に内蔵しています。テストをSTで書いて、どこでも実行できます。

主な機能

コンパイラ

ST構文を解析し、モダンなC++17へトランスパイル。PLCランタイムへの依存を排除し、汎用環境で動作するコードを生成します。

組み込みユニットテストフレームワーク

ラダー図やFBDに頼らず、STのロジックをコードレベルで検証。CIパイプラインへの統合も容易です。

インタラクティブREPL

プログラムの動作をその場で確認・デバッグ。変数の変化やプログラムの状態をリアルタイムに観察できます。

再利用可能なライブラリシステム

ファンクションブロックやデータ型をパッケージ化し、プロジェクト間で共有。資産の蓄積と再利用を促進します。

なんでSTruC++?

ST to C++は理にかなっている。他のツールはC(MatIEC)やプロプライエタリなバイトコードを出力しますが、STruC++はC++17らしいコードを生成します。ファンクションブロックはクラスで表現し、インターフェースには仮想関数、ジェネリクスにはテンプレートを使用する。出力されたコードは読みやすく、デバッグもできて、既存のC++プロジェクトにそのまま統合できます。

組み込みユニットテスト

現在のPLCテストソリューションはすべて、IDEのアドオン、外部ライブラリ、またはPLCハードウェアを必要とします。STruC++はテストランナーをコンパイラ本体に内蔵しています。テストはSTで書いて、strucpp source.st –test tests.st でどのマシンでも実行可能。PLCは不要で、自動ビルドパイプラインにも最適です。テストガイドを参照してください。

インタラクティブREPL

–build でSTプログラムをスタンドアロンバイナリとしてビルドし、インタラクティブに動作を確認できます。STとC++のコードを並べて表示しながら、入力の設定、サイクルの進行、変数の確認、値の強制書き込みが可能です。REPLガイドを参照してください。

ランタイム依存ゼロ

コンパイラはシングルバイナリ。C++ランタイムはヘッダオンリー。生成されたコードはC++17対応のコンパイラ(g++、clang++、MSVC)であればどれでもコンパイルできます。CLIリファレンスおよびC++ランタイムを参照してください。

STruC++ テストフレームワーク

STruC++には、Structured Textプログラムに対するユニットテストを記述・実行するためのテストフレームワークが組み込まれています。テストは専用の構文で記述され、C++にコンパイルされてネイティブで実行されます。

仕組み

  1. テストインフラを有効にした状態でソースSTファイルをコンパイル
  2. テストファイルをテスト用AST(SETUP/TEARDOWN/TESTブロック、アサーション、モック)にパース
  3. ソースのシンボルテーブルに対してテストファイルを検証
  4. C++テストランナー(test_main.cpp)を生成
    1. テストファイルごとにsetup構造体(SETUPが存在する場合)
    2. TESTブロックごとにテスト関数
    3. main()へのテスト登録
  5. g++(C++17)でテストバイナリをコンパイル
  6. バイナリを実行して結果を出力

全テストが成功した場合の終了コードは0、失敗した場合は0以外となります。

テスト出力

[test_counter.st]

PASS: increments on each call
FAIL: reset clears count
Assertionfailedat line 15: ASSERT_EQ(c.count, 0) – got 3

Results: 1 passed, 1 failed

STruC++ インタラクティブREPL

STruC++はSTプログラムをスタンドアロンバイナリにコンパイルし、ステップ実行・変数の確認・デバッグができるインタラクティブシェルを提供します。

インタラクティブ機能

  • タブ補完:コマンド、プログラム名、program.variable パスを補完
  • コマンド履歴:セッションをまたいで保持(.strucpp_history に保存)、上下矢印キーで操作
  • 履歴検索:Ctrl+Rで逆方向インクリメンタル検索
  • シンタックスハイライト:コマンドと変数参照をカラー表示

要件

–build フラグを使用するには以下が必要です:

  • C++17対応の g++(–gpp <path> でパスを指定可能)
  • Cコンパイラ(cc または gcc、–cc <path> でパスを指定可能)

いずれもビルド時にのみ使用されます。生成されたバイナリはスタンドアロンで動作します。

言語サポート

STruC++はIEC 61131-3 Edition 3の主要なサブセットに加え、CODESYSの一般的な拡張機能を実装しています。

カテゴリ

機能

データ型

BOOL、BYTE/WORD/DWORD/LWORD、SINT/INT/DINT/LINT、USINT/UINT/UDINT/ULINT、REAL/LREAL、STRING/WSTRING、TIME/DATE/DT/TOD(+ L-variants)、配列(1D/2D/3D/VLA)、構造体、列挙型、部分範囲型

POU

PROGRAM、FUNCTION、FUNCTION_BLOCK、INTERFACE

変数

VAR、VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT、VAR_EXTERNAL、VAR_GLOBAL、CONSTANT、RETAIN、AT(located)

制御フロー

IF/ELSIF/ELSE、CASE、FOR、WHILE、REPEAT、EXIT、RETURN

OOP

メソッド、プロパティ(GET/SET)、継承(EXTENDS)、インターフェース(IMPLEMENTS)、ABSTRACT、FINAL、OVERRIDE、アクセス修飾子

ポインタ

POINTER TO、REF_TO、REFERENCE_TO、ADR、^ 逆参照、__NEW / __DELETE

標準関数

80以上の関数:数学、三角関数、文字列、選択、比較、ビット演算、型変換

標準FB

TON、TOF、TP、CTU、CTD、CTUD、R_TRIG、F_TRIG、SR、RS(STライブラリとしてコンパイル)

プロジェクトモデル

CONFIGURATION、RESOURCE、TASK、プログラムスケジューリング

インストール

GitHub ReleasesからOSのプラットフォーム向けの最新版をダウンロードし解凍します。

https://github.com/Autonomy-Logic/STruCpp/releases

oem@ubuntu:~/STruCpp$ tar -xzf strucpp-linux-x64.tar.gz
oem@ubuntu:~/STruCpp$ ls
strucpp strucpp-linux-x64.tar.gz

PATHに追加します。

oem@ubuntu:~/STruCpp$ export PATH=”$PWD/strucpp:$PATH”
oem@ubuntu:~/STruCpp$ str
strace strace-log-merge streamzip strings strip strucpp/

最初のプログラム

今回の記事ではこちらのプログラムを使用します。


FUNCTION_BLOCK Counter
VAR_INPUT
countUp : BOOL; (* Count up on rising edge *)
countDown : BOOL; (* Count down on rising edge *)
reset : BOOL; (* Reset counter to zero *)
maxValue : INT := 100; (* Maximum count value *)
minValue : INT := 0; (* Minimum count value *)
END_VAR

VAR_OUTPUT
count : INT; (* Current count value *)
atMax : BOOL; (* TRUE when at maximum *)
atMin : BOOL; (* TRUE when at minimum *)
END_VAR

VAR
prevUp : BOOL; (* Previous state of countUp *)
prevDown : BOOL; (* Previous state of countDown *)
END_VAR

(* Reset handling *)
IF reset THEN
count := 0;
ELSE
(* Count up on rising edge *)
IF countUp AND NOT prevUp THEN
IF count < maxValue THEN
count := count + 1;
END_IF;
END_IF;

(* Count down on rising edge *)
IF countDown AND NOT prevDown THEN
IF count > minValue THEN
count := count – 1;
END_IF;
END_IF;
END_IF;

(* Update edge detection *)
prevUp := countUp;
prevDown := countDown;

(* Update status outputs *)
atMax := count >= maxValue;
atMin := count <= minValue;

END_FUNCTION_BLOCK

ビルド

次は下記のコマンドでSTプログラムをC++に変換します。

oem@ubuntu:~/STruCpp/tut01Counter$ strucpp counter.st -o counter.cpp
Compiling counter.st…
Output written to /home/oem/STruCpp/tut01Counter/counter.cpp
Header written to /home/oem/STruCpp/tut01Counter/counter.hpp
Compilation successful!

Unit Test(単体テスト)

次は簡単なUnit Testを構築します。Unit Testとは、プログラムを構成する個々の機能や処理単位ごとに、意図したとおりに動作するかを確認するテストです。

こちらのadd.stは2つの整数のパラメータを入力し、結果を出力します。

FUNCTION_BLOCK Adder
VAR_INPUT a, b : INT; END_VAR
VAR_OUTPUT sum : INT; END_VAR
sum := a + b;
END_FUNCTION_BLOCK

こちらのtest_add.stは先程作成したAdderを使用し計算結果を検証します。またプログラムの実行時間が100ms以内を計測します。

TEST ‘Addition works’
VAR uut : Adder; END_VAR
uut(a := 3, b := 7);
ASSERT_EQ(uut.sum, 10);
END_TEST

TEST ‘Timer reaches preset’
VAR t : TON; END_VAR
t(IN := TRUE, PT := T#100ms);
ADVANCE_TIME(T#100ms);
t(IN := TRUE, PT := T#100ms);
ASSERT_TRUE(t.Q);
END_TEST

次は下記のコマンドで単体テストを行います。

oem@ubuntu:~/STruCpp/tut01Counter$ strucpp add.st –test test_add.st
STruC++ Test Runner v1.0

Done!すべての単体テストがバスしました。

test_add.st
[PASS] Addition works
[PASS] Timer reaches preset

—————————————–
2 tests, 2 passed, 0 failed

こちらのtest_add.stは3と11をパスし、わざと計算結果が10になるかのCHECKを追加します。(もちろん10になりません)

TEST ‘Addition works’
VAR uut : Adder; END_VAR
uut(a := 3, b := 7);
ASSERT_EQ(uut.sum, 10);
uut(a := 3, b := 11);
ASSERT_EQ(uut.sum, 10);
END_TEST

TEST ‘Timer reaches preset’
VAR t : TON; END_VAR
t(IN := TRUE, PT := T#100ms);
ADVANCE_TIME(T#100ms);
t(IN := TRUE, PT := T#100ms);
ASSERT_TRUE(t.Q);
END_TEST

次は下記のコマンドで単体テストを行います。

oem@ubuntu:~/STruCpp/tut01Counter$ strucpp add.st –test test_add.st
STruC++ Test Runner v1.0

今回2つのテストケースがパスになり、1つが失敗だとわかります。そして失敗したテストはLine6であり、10の計算結果の期待に対して14が出たことも把握できます。

test_add.st
ASSERT_EQ failed: UUT.SUM expected 10, got 14
at test_add.st:6
[FAIL] Addition works
[PASS] Timer reaches preset

—————————————–
2 tests, 1 passed, 1 failed

インタラクティブシェル

最後は下記のコマンドでSTプログラムをもう一回ビルドしましょう。

oem@ubuntu:~/STruCpp/tut01Counter$ strucpp MotorControl.st -o MotorControl.cpp –build

Done!STプログラムがビルドでき、C++に変換されました。

Compiling MotorControl.st…
Output written to /home/oem/STruCpp/tut01Counter/MotorControl.cpp
Header written to /home/oem/STruCpp/tut01Counter/MotorControl.hpp
Compilation successful!
Generating REPL harness…
REPL main written to /home/oem/STruCpp/tut01Counter/main.cpp
Compiling isocline…
Building binary: MotorControl
Binary built: /home/oem/STruCpp/tut01Counter/MotorControl
Run it with: /home/oem/STruCpp/tut01Counter/MotorControl

次は下記のコマンドでプログラムを実行します。

oem@ubuntu:~/STruCpp/tut01Counter$ ./MotorControl

Done!インタラクティブシェルが起動されました。

STruC++ Interactive PLC Test REPL
Programs: MOTORCONTROL(9 vars)
Cycle: 20ms
Source: 40 lines loaded
Type help for commands, Tab for completion, Ctrl+R to search history.

runコマンドでプログラムを指定するサイクル回数を実行します。

strucpp> run 20
Executed 20 cycle(s). Total: 21

varsコマンドで現在プログラムのすべての変数の現在値を確認します。

strucpp> vars
MOTORCONTROL.STARTBUTTON : BOOL = FALSE
MOTORCONTROL.STOPBUTTON : BOOL = FALSE
MOTORCONTROL.EMERGENCYSTOP : BOOL = FALSE
MOTORCONTROL.OVERLOADTRIP : BOOL = FALSE
MOTORCONTROL.MOTORCONTACTOR : BOOL = FALSE
MOTORCONTROL.RUNNINGLAMP : BOOL = FALSE
MOTORCONTROL.FAULTLAMP : BOOL = TRUE
MOTORCONTROL.MOTORRUNNING : BOOL = FALSE
MOTORCONTROL.FAULTACTIVE : BOOL = TRUE

code コマンドで指定する行数のプログラムを表示します(STとC++も)。

strucpp> code 5 10

Done!

set コマンドで指定する変数の値を変更します。

strucpp> set MOTORCONTROL.STARTBUTTON TRUE
MOTORCONTROL.STARTBUTTON = TRUE

vars コマンドでプログラム内の変数の現在値をすべて表示します。

strucpp> vars
MOTORCONTROL.STARTBUTTON : BOOL = TRUE
MOTORCONTROL.STOPBUTTON : BOOL = FALSE
MOTORCONTROL.EMERGENCYSTOP : BOOL = FALSE
MOTORCONTROL.OVERLOADTRIP : BOOL = FALSE
MOTORCONTROL.MOTORCONTACTOR : BOOL = FALSE
MOTORCONTROL.RUNNINGLAMP : BOOL = FALSE
MOTORCONTROL.FAULTLAMP : BOOL = TRUE
MOTORCONTROL.MOTORRUNNING : BOOL = FALSE
MOTORCONTROL.FAULTACTIVE : BOOL = TRUE
strucpp> watch MOTORCONTROL.var

最後はvars コマンドで指定する変数をモニタリングします。

strucpp> watch MOTORCONTROL.FAULTLAMP
Watching: MOTORCONTROL.FAULTLAMP

もう一回RUNコマンドを実行すると先程登録したコマンドが表示されるようになりました。

strucpp> run 10
Executed 10 cycle(s). Total: 91
— watch —
MOTORCONTROL.FAULTLAMP : BOOL = TRUE

シェアする

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

フォローする