前回の記事三菱PLCとイーサネット通信では、文字列に書いたコマンドを送信して受信データを文字列で表示しましたが、今回はそれを構造体にして書いてみます。
コードはまたgistsにアップします。
melsec_struct_test.cpp
それではコードを説明します。まずは送信コマンドのヘッダの構造体です。
#pragma pack(push,1)
//送信コマンドヘッダ
struct QNa3E_Frame_Send{
std::uint16_t subheader=0x0050;
std::uint8_t network_number=0x00;
std::uint8_t pc_number=0xFF;
std::uint16_t request_IO_number=0x03FF;
std::uint8_t request_station_number=0x00;
std::uint16_t request_data_length;
std::uint16_t cpu_monitoring_timer=0x10;
} qna;
pragma pack(push,1)を入れることにより、バイナリで転送したときの間を開けずに詰める事ができます。もし入れない場合は「std::uint8_t request_station_number=0x00; //要求先ユニット局番号」の後に00が入ってしまいます。構造体宣言の最後にはpragma pack(pop)と書きます。
構造体はリファレンスマニュアルのQnA互換3Eフレームのバイナリコードで更新する場合を見ながら作成しました。
次に一括読み出しの構造体です。
//一括読み出しコマンド
struct ReadAllCommand{
std::uint16_t command=0x0401;
std::uint16_t sub_command=0x0000;
std::uint32_t top_device;
std::uint16_t number_of_device;
} read_all;
デバイスはDが0xA8、Mが0x90と値が決められているので列挙体で作成しました。std c++11からは列挙体の基礎となる型を指定できます。
//デバイス
enum struct Device_type : std::uint32_t{
D = 0xA8,
X = 0x9C,
Y = 0x9D,
M = 0x90,
};
次にmain関数に移ります。まずはデバイス番号を設定します。前回の例と一緒でD300から3ワードという設定です。
/デバイス番号を設定
std::uint32_t device = static_cast<std::uint32_t>(Device_type::D);
列挙体ではキャストして変数へ格納します。
read_all.top_device = ( device << 24) | 300; // D300
read_all.number_of_device = 3; // 3ワード
デバイスは4バイトのうち先頭の1バイトがDとかMなどの種類で、残り3バイトがアドレスになります。列挙体で格納したデータを3バイト分(24bit)シフトしました。デバイス数はそのまま数値を入力します。
qna.request_data_length=sizeof(qna.cpu_monitoring_timer) + sizeof(read_all)
で、要求データ長を計算しています。
次にUDP送信です。
// UDP送信
std::string const host = "192.168.0.10";
short const port = 1025;
asio::io_context context;
asio::ip::udp::endpoint endpoint(
asio::ip::address::from_string(host), port);
asio::ip::udp::socket socket(context);
socket.open(asio::ip::udp::v4());
socket.send_to(asio::buffer(&qna, sizeof(qna)), endpoint);
socket.send_to(asio::buffer(&read_all, sizeof(read_all)), endpoint);
ASIOについては前回の記事を参照してください。send_toで送信するところは、送信ヘッダと受信コマンドを構造体毎に送っています。
レスポンスの受信については次回説明します。