読者です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

boost::asio::serial_port で ZigBee と会話してみた

C++ ZigBee 電子工作

はじめに

部屋やベランダにセンサ仕掛けて色んな値取ってきたいなぁ、と思い ZigBee 始めました。通信部分は Boost Asio Serial_port 使ったのでプラットフォーム依存しないと思います(多分)。

ZigBee 事始め

これ買えば XBee という ZigBee のモジュールが2個と XBee と USB 経由でやり取りできる基板がついてきて通信のテストとか出来ます。第4章くらいまで(40ページくらい)読めば大体使えるようになります。
XBee の足のピッチはブレッドボードより狭いのでピッチ変換基板を仕入れておくと捗ります。
XBeeピッチ変換基板とソケットのセット

XBeeピッチ変換基板とソケットのセット

どういう配線になるかは本を参照して下さい。取り敢えずこんなんなりました。

通信プログラム

通信部分を最低限必要そうな部分だけ書くと次のようになります。本の第4章相当です。

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <iostream>

using namespace boost::asio;

int main(int argc, char *argv[])
{
	// API フレームに AT コマンド(IS: リモートピンの値を取得)を突っ込んだもの
	boost::array<unsigned char, 32> send_api_frame = {0x7e, 0x0, 0xf, 0x17, 0x1, 0x0, 0x13, 0xa2, 0x0, 0x40, 0x71, 0xa2, 0xa9, 0xff, 0xfe, 0x2, 0x49, 0x53, 0x9b};

	// 受信データ
	boost::array<unsigned char, 32> receive_api_frame;

	// API フレーム送りまーす
	try {
		io_service io;
		serial_port port( io, "COM3" );
		port.set_option(serial_port_base::baud_rate(9600));
		port.set_option(serial_port_base::character_size(8));
		port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
		port.set_option(serial_port_base::parity(serial_port_base::parity::none));
		port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

		port.write_some( buffer(send_api_frame) );
		Sleep( 2000 );
		port.read_some( buffer(receive_api_frame) );
	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		exit(EXIT_FAILURE);
	}

	// 受信結果書き出し
	for (size_t i = 0; i < receive_api_frame.size() - 1; ++i) {
		std::cout << std::hex << (unsigned int)receive_api_frame[i] << " ";
	}
	std::cout << std::endl;

	return 0;
}

実行結果:

7e 0 1b 97 1 0 13 a2 0 40 71 a2 a9 0 0 49 53 0 1 0 1 e 0 1 2 42 2 3f 2 40 42

至れり尽くせり感あります。とても楽ちんです。
返信データは、

1.2 [V] * 0x0240/0x03FF 〜 0.68 [V]

と本を参考に計算できます。本当は 3 V の電圧を抵抗で4分割した値を取っているので 0.75 V と出て欲しかったのですが、電池ヘタってきた感あるので、こんなもんでしょう。
ただ、コードを見ていただくと分かるように Sleep(2000) と直打ちして取り敢えずデータが返ってきそうな時間(ここでは2秒)待ってから結果を出力するようなコードになっていてイケてないです。そこで何とかならないかなと思い、read_some の代わりに async_read_some を使ってみました。
参考:C++ のお勉強 - IT戦記

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <iostream>

using namespace boost::asio;

void read_handler(serial_port& port, const boost::system::error_code& error, boost::array<unsigned char, 32> buf, size_t length)
{
	std::cout << length << std::endl;
	for (size_t i = 0; i < length; ++i) {
		std::cout << std::hex << static_cast<unsigned int>(buf[i]) << " ";
	}
	std::cout << std::endl;
}

int main(int argc, char *argv[])
{
	// 送信データ
	boost::array<unsigned char, 20> send_api_frame = {0x7e, 0x00, 0x0f, 0x17, 0x01, 0x00, 0x13, 0xa2, 0x00, 0x40, 0x71, 0xa2, 0xa9, 0xff, 0xfe, 0x02, 0x49, 0x53, 0x9b};

	// 受信データ
	boost::array<unsigned char, 32> receive_api_frame;

	// API フレーム送りまーす
	try {
		io_service io;
		serial_port port( io, "COM3" );
		port.set_option(serial_port_base::baud_rate(9600));
		port.set_option(serial_port_base::character_size(8));
		port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
		port.set_option(serial_port_base::parity(serial_port_base::parity::none));
		port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

		port.write_some( buffer(send_api_frame) );
		Sleep(10);
		port.async_read_some(
			buffer(receive_api_frame),
			boost::bind(read_handler, _1, boost::ref(receive_api_frame), _2)
		);
		io.run();
		
	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		exit(EXIT_FAILURE);
	}
}

実行結果:

7e 0 1b 97 1 0 13 a2 0 40 71 a2

あ、全部返って来ない…(´・ω:;.:...
全部返ってくる前に read_handler が呼ばれてしまい、結果、断片的なデータになってしまっています。非同期意味なかった、そりゃそうか。。
しっかりチェックするには受信データの長さと受信データ内の2バイト目、3バイト目の Length MSB/LSB を比較しないと駄目っぽいです。

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <iostream>

using namespace boost::asio;

int main(int argc, char *argv[])
{
	// API フレームに AT コマンド(IS: リモートピンの値を取得)を突っ込んだもの
	boost::array<unsigned char, 32> send_api_frame = {0x7e, 0x0, 0xf, 0x17, 0x1, 0x0, 0x13, 0xa2, 0x0, 0x40, 0x71, 0xa2, 0xa9, 0xff, 0xfe, 0x2, 0x49, 0x53, 0x9b};

	// 受信データ
	boost::array<unsigned char, 32> receive_api_frame;
	receive_api_frame.assign(0xff);

	// API フレーム送りまーす
	try {
		io_service io;
		serial_port port( io, "COM3" );
		port.set_option(serial_port_base::baud_rate(9600));
		port.set_option(serial_port_base::character_size(8));
		port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
		port.set_option(serial_port_base::parity(serial_port_base::parity::none));
		port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

		port.write_some( buffer(send_api_frame) );

		// 全データ受信するまでグルグル
		size_t total_length = 0;
		boost::array<unsigned char, 32> buf;
		for(;;) {
			size_t length = port.read_some( buffer(buf) );
			for (size_t i = 0; i < length; ++i) {
				receive_api_frame[total_length + i] = buf[i];
			}
			total_length += length;
			std::cout << length << " " << total_length << std::endl;

			// 受信データ長チェック
			const int NOT_COUNTED_FRAME_LENGTH = 4;
			int received_length = static_cast<size_t>(receive_api_frame[2]) + NOT_COUNTED_FRAME_LENGTH;
			if (receive_api_frame[2] != 0xff && total_length == received_length) break;
		}
	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		exit(EXIT_FAILURE);
	}

	// 受信結果書き出し
	for (size_t i = 0; i < receive_api_frame.size() - 1; ++i) {
		std::cout << std::hex << static_cast<unsigned int>(receive_api_frame[i]) << " ";
	}
	std::cout << std::endl;

	return 0;
}

実行結果:

10 10
1 11
1 12
1 13
1 14
1 15
1 16
1 17
14 31
7e 0 1b 97 1 0 13 a2 0 40 71 a2 a9 0 0 49 53 0 1 0 1 e 0 1 2 3c 2 3c 2 3d 4e

IS コマンドとかであれば 3 バイト目の LSB だけ使って計算しておけば事足りそうなので手抜きしました。というかそもそも 2 バイト目の MSB がどんなものか理解していない。。

チェックサムとか計算した版

チェックサムとか最終的な値とか計算した版を以下に掲載します。

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <vector>
#include <sstream>
#include <iostream>

using namespace boost::asio;

const std::string START_DELIMITER      = "7E";
const std::string LENGTH_MSB           = "00";
const std::string LENGTH_LSB           = "0F";
const std::string FRAME_TYPE           = "17";
const std::string FRAME_ID             = "01";
const std::string REMOTE_ADDRESS       = "00 13 A2 00 40 71 A2 A9";
const std::string DESTINATION_ADDRESS  = "FF FE";
const std::string APPLY_CHANGES        = "02";
const std::string AT_COMMAND_P1        = "50 31";
const std::string AT_COMMAND_IS        = "49 53";
const std::string PARAM_LOW            = "04";
const std::string PARAM_HIGH           = "05";

int main(int argc, char *argv[])
{
	// 送信する API フレーム
	std::stringstream ss(
		START_DELIMITER       + " "
		+ LENGTH_MSB          + " "
		+ LENGTH_LSB          + " "
		+ FRAME_TYPE          + " "
		+ FRAME_ID            + " "
		+ REMOTE_ADDRESS      + " "
		+ DESTINATION_ADDRESS + " "
		+ APPLY_CHANGES       + " "
		+ AT_COMMAND_IS
	);

	// 16 進数のコマンドに変換
	std::vector<unsigned char> send_api_frame;
	while (!ss.eof()) {
		int val;
		ss >> std::hex >> val;
		send_api_frame.push_back(static_cast<unsigned char>(val));
	}

	// チェックサムを付加
	auto it = send_api_frame.begin() + 3;      // 4桁目から
	unsigned int check_sum = 0;                // 
	for (; it != send_api_frame.end(); ++it) { // 末尾まで総和
		check_sum += static_cast<unsigned int>(*it);
	}
	check_sum = 0xFF - (check_sum & (0xFF)); // 0xFF から総和の下二桁を引いた値
	send_api_frame.push_back(static_cast<unsigned char>(check_sum));

	// 送信コマンド書き出し
	for (auto i = send_api_frame.begin(); i != send_api_frame.end(); ++i) {
		std::cout << std::hex << static_cast<unsigned int>(*i) << " ";
	}
	std::cout << std::endl;

	// 読み取りデータ
	boost::array<unsigned char, 32> receive_api_frame;
	receive_api_frame.assign(0xff);

	// API フレーム送りまーす
	try {
		io_service io;
		serial_port port( io, "COM3" );
		port.set_option(serial_port_base::baud_rate(9600));
		port.set_option(serial_port_base::character_size(8));
		port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
		port.set_option(serial_port_base::parity(serial_port_base::parity::none));
		port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));
		port.write_some( buffer(send_api_frame) );

		// 全データ受信するまでグルグル
		size_t total_length = 0;
		boost::array<unsigned char, 32> buf;
		for(;;) {
			size_t length = port.read_some( buffer(buf) );
			for (size_t i = 0; i < length; ++i) {
				receive_api_frame[total_length + i] = buf[i];
			}
			total_length += length;

			// 受信データ長チェック
			const int NOT_COUNTED_FRAME_LENGTH = 4;
			int received_length = static_cast<size_t>(receive_api_frame[2]) + NOT_COUNTED_FRAME_LENGTH;
			if (receive_api_frame[2] != 0xff && total_length == received_length) break;
		}
	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		exit(EXIT_FAILURE);
	}

	// 受信結果書き出し
	for (size_t i = 0; i < receive_api_frame.size() - 1; ++i) {
		std::cout << std::hex << static_cast<unsigned int>(receive_api_frame[i]) << " ";
	}
	std::cout << std::endl;

	// 計算結果書き出し
	const int AIN3HI_INDEX = 28;
	const int AIN3LO_INDEX = 29;
	unsigned char ain3hi = receive_api_frame[AIN3HI_INDEX];
	unsigned char ain3lo = receive_api_frame[AIN3LO_INDEX];
	float result = 1.2f * (ain3hi * 0x0100 + ain3lo) / 0x03FF;
	std::cout << result << " [V]" << std::endl;

	int wait;
	std::cin >> wait;

	return 0;
}

おわりに

ボタン電池で駆動したとしてどれくらい持つんだろうか…。