Arduinoワイヤレスネットワーク

Arduinoプラットフォームのマイクロコントローラーは、さまざまな複雑さと実用性の趣味プロジェクトに最適なプラットフォームです。 私はArduinoプラットフォームがプロフェッショナルソリューションに最適であるとは言いません(反対の意見に同意します)が、ホームオートメーションの分野のアマチュアの「工芸品」にとってこれが最良の選択肢です。 つまり コントローラーはそれ自体で優れていますが、さらに、それが「それ自身」でなくなり、追加のワイヤーで過剰に成長することなく、それ自体の種類と「通信」できるようになると、その有用性と適用可能性は何度も成長します。 それでは、ホームSkyNetの構築を始めましょう...



予算無線モジュール


私たちのネットワークは、433.90MHzの周波数で動作するバジェット無線モジュールに基づいています。 このようなモジュールのコストは約2.5ドルなので、外の世界とのコミュニケーションを整理するためのコストはわずかです。 もちろん、既成のイーサネットモジュールを通信に使用したり、代替ファームウェアに基づいてワイヤレスルーターと共生することもできますが、多くの場合、そのような無線モジュールですべてを行う方が簡単で安価です。



送信機:







レシーバー:







これらのモジュールの仕事の質と通信範囲は望まれるものが多く残されており、売り手による「> 500m」の範囲に関する楽観的な声明は信じられません。 最良の場合は、オープンエリアで100メートル、まあ、コンクリートパーティションの場合ははるかに少なくなります。 ただし、アパートや小さな郊外地域では十分です。 より高品質の(それぞれ、より高価な)無線モジュールを使用できるため、この記事は、多くの可能な実装オプションに適用可能なイデオロギーの概念と見なすことができます。



重要なポイント:このガイドでは、データ転送の品質管理を備えたネットワークを作成するオプションについては検討しません。 イーサネットプロトコルとの比較が適切であると考えられる場合、TCPパケットを送信するためのネットワークではなく、UDPを構築します。



各モジュールはコントローラーに基本的に接続されています。電源はVcc / Gndを介して供給され、データ出力はマイクロコントローラーの無料のデジタル入力に接続されます。 受信/送信の品質を向上させるために、10-15 cmのワイヤの形でアンテナを追加接続することをお勧めします。ところで、通信範囲はモジュールに供給される電力にも依存します-12Vから給電される場合、通信範囲と信頼性は大幅に向上します



Arduino UNO R3マイクロコントローラーに接続された受信機と送信機:







したがって、2つのデバイスを作成しました。最初のデバイスは、空中の情報を「ブロードキャスト」する送信機です。 2番目はレシーバーであり、それに応じてブロードキャストを「リッスン」します。 さらに、ポイントは、送信と受信の両方が有意義で有用であるべきだということです。



VirtualWireライブラリ


Arduinoプラットフォームの良いところは、あらゆる種類のデバイスを操作するための膨大な数の既製のライブラリが存在することです。 もちろん、ライブラリなしで無線モジュールを使用できますが、チェックサムなどを使用して独自の通信プロトコルを開発する必要があります。 幸いなことに、無線モジュールのデータ(など)をサポートする素晴らしいVirtualWireライブラリがあります。 このライブラリを使用すると、小さな情報パケットの送受信を非常に簡単に整理できます。



使用原理:送信機では、送信用のデータセット(文字列またはバイトコードの形式)を形成し、受信機では、「正しい」データパケットを受信すると、それらを表示します。 これを確認する最も簡単な方法は、ライブラリ自体に付属するサンプルを使用することです。



VirtualWireを使用した送信機コード(ライブラリの使用例から):

コードを表示
// transmitter.pde // // Simple example of how to use VirtualWire to transmit messages // Implements a simplex (one-way) transmitter with an TX-C1 module #include <VirtualWire.h> void setup() { Serial.begin(9600); // Debugging only Serial.println("setup"); // Initialise the IO and ISR vw_set_ptt_inverted(true); // Required for DR3100 vw_setup(2000); // Bits per sec } void loop() { const char *msg = "hello"; digitalWrite(13, true); // Flash a light to show transmitting vw_send((uint8_t *)msg, strlen(msg)); vw_wait_tx(); // Wait until the whole message is gone digitalWrite(13, false); delay(200); }
      
      







受信機コード:

コードを表示
 // receiver.pde // // Simple example of how to use VirtualWire to receive messages // Implements a simplex (one-way) receiver with an Rx-B1 module #include <VirtualWire.h> void setup() { Serial.begin(9600); // Debugging only Serial.println("setup"); // Initialise the IO and ISR vw_set_ptt_inverted(true); // Required for DR3100 vw_setup(2000); // Bits per sec vw_rx_start(); // Start the receiver PLL running } void loop() { uint8_t buf[VW_MAX_MESSAGE_LEN]; uint8_t buflen = VW_MAX_MESSAGE_LEN; if (vw_get_message(buf, &buflen)) // Non-blocking { int i; digitalWrite(13, true); // Flash a light to show received good message // Message with a good checksum received, dump it. Serial.print("Got: "); for (i = 0; i < buflen; i++) { Serial.print(buf[i], HEX); Serial.print(" "); } Serial.println(""); digitalWrite(13, false); } }
      
      







通信プロトコル


次のステップは、新しいレベルの抽象化、つまりすべてのデバイスが交換する典型的なパッケージ構造の開発に到達することです。 これにより、将来、既存のデバイスからの信号を使用できる新しい機器をネットワークに接続できるようになります。



利用可能な機器の機能を使用して、最適と思われるデータ構造を提供します。 したがって、以下のテキストでは、各パッケージでブロードキャストされる主なパラメーターのリストを示します。



device_id-パケットを送信したデバイスの識別子。 データ型:unsigned int(長さ2バイト、0から65535までの値の範囲)-どうやら、ホームネットワークにはこれで十分です。



destination_id-パケットの宛先となるデバイスの識別子。 データ型はdevice_idと同じです。 パケットはすべての受信者によって引き続き受信されることに注意することが重要ですが、既に受信者自身のプログラムを使用して、デバイス向けではないパケットを「切断」することができます。 また、このフィールドの値「0」はブロードキャストパケットを意味するというルールを採用することもできます。



packet_id-パケット識別子。 タイプは同じ符号なし整数です。 設計上、パケットを送信するとき、ランダムな「マーク」が付けられます。これは、一定の間隔で同じパケットを数回再送信するために使用できます-プロトコルの信頼性がないため、これは理にかなっていますが、受信デバイスは繰り返しコマンドをフィルタリングして、実行しないようにする必要がありますデータパケットへの応答で同じアクション。



command-コマンドのタイプ。 バイトデータタイプ(長さ1バイト、値の範囲は0〜255)。 これはいわゆる「チームクラス」ですが、実際にはどのようなデータを送信しているかに関する情報です。 たとえば、開閉制御コマンドに番号10を、温度データ送信コマンドに番号15を割り当てることで、独自のコマンドのテーブルを作成できます。 そして、あなたはそれをさらに巧妙に行うことができます-同じZWaveプロトコルで可能なコマンドを調べ、自宅でそれらのテーブルを使用て、すべてが「大人のよう」であり、この貴重な情報の安全性を心配する必要はありません。



データ -実際のデータ。 データ型はint(長さ2バイト、値の範囲は-32.768から32.767です。このフィールドでは、データを単一の数値として直接送信します。十分ではありませんか?それで十分です。温度を渡すことができます(たとえば、100倍すると仮定)モーションセンサーは簡単で、リレーを備えた受信機へのコマンドはテキストデータを送信するのと同じくらい簡単です。もちろん、外部ディスプレイに送信することはできませんが、この目標は設定されておらず、現在および将来のデバイスでは、目と数十個の数字ですべてを説明できます可能なチーム。



その結果、9バイトのパケット長があります。 実際、短いパッケージは非常に優れています。まず、途中で「壊れる」可能性が低くなります。 第二に、転送時間が短くなり、複数のデバイス間で空気を共有する可能性が低くなります。 ちなみに、後者の状況では「s約」が必要になります。 頻繁に情報を送信しません。 同時に、証言が定期的に送信されるとき、セッション間の間隔は多少異なることが望ましい。 ただし、特定のデバイスを統合する場合は、これらすべてをすでに提供する必要があります。 それはそうかもしれませんが、送信されたデータのパケットの最小サイズを犠牲にして構造の普遍性に頼りすぎることはお勧めしません。



そのため、パッケージの構造を決定しました。次に、交換を実装する必要があります。 ここで、 EasyTransferと呼ばれる別の便利なライブラリが役立ちます。 実際、VirtualWireの「上」で動作し、レシーバー/トランスミッターのデータ構造を記述し、バイトコードのセットではなく、構造全体で交換できます。



この場合、データ構造は次の形式になります。

 struct SEND_DATA_STRUCTURE{ unsigned int device_id; unsigned int destination_id; unsigned int packet_id; byte command; int data; };
      
      







レシーバーとトランスミッターの構造が1対1であることが必須です。 したがって、実際には、事前にパッケージの構造を決定することが重要です。



device_idフィールドに関するいくつかの言葉。 デバイスごとに手動で設定できますが、より簡単な方法で行いました。最初の起動時に、この値をランダムに生成し、EEPROMのエネルギーに依存しないメモリ領域に書き込みます。 異なるデバイスがunsigned intフィールド値の範囲から同じ識別子を取得する可能性は非常に小さく、この場合も、私の場合、リスクは完全に正当化されます。



取得した知識を適用して、交換プロトコルで実装例を作成します。 トランスミッタは内部カウンタの値を送信し、レシーバはそれを表示します。



送信機コード:

コードを表示
 #include <VirtualWire.h> #include <EasyTransferVirtualWire.h> #include <EEPROM.h> //       -  const int led_pin = 13; const int transmit_pin = 2; unsigned int unique_device_id = 0; unsigned int count = 1; //create object EasyTransferVirtualWire ET; struct SEND_DATA_STRUCTURE{ //  .          // ,      26  ( VirtualWire) unsigned int device_id; unsigned int destination_id; unsigned int packet_id; byte command; int data; }; //     SEND_DATA_STRUCTURE mydata; //       unsigned int  EEPROM void EEPROMWriteInt(int p_address, unsigned int p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); } unsigned int EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void setup() { //   pinMode(led_pin, OUTPUT); ET.begin(details(mydata)); vw_set_tx_pin(transmit_pin); // ,    data-  vw_setup(2000); //  Serial.begin(9600); randomSeed(analogRead(0)); // / Device ID Serial.print("Getting Device ID... "); unique_device_id=EEPROMReadInt(0); if (unique_device_id<10000 || unique_device_id>60000) { Serial.print("N/A, updating... "); unique_device_id=random(10000, 60000); EEPROMWriteInt(0, unique_device_id); } Serial.println(unique_device_id); } void loop() { mydata.device_id = unique_device_id; mydata.destination_id = 0; mydata.packet_id = random(65535); mydata.command = 0; mydata.data = count; digitalWrite(led_pin, HIGH); //       Serial.print("Transmitting packet "); Serial.print(mydata.packet_id); Serial.print(" device id "); Serial.print(mydata.device_id); Serial.print(" data: "); Serial.print(mydata.data); Serial.print(" ... "); ET.sendData(); //   digitalWrite(led_pin, LOW); Serial.println("DONE"); delay(1000); count = count + 1; }
      
      







この場合の受信機は、単にブロードキャストをリッスンし、送信機によって送信されたすべてのコマンドを表示します。 コマンドを受信する各デバイスについて、必要に応じて、宛先デバイスとコマンドのクラスでフィルターを追加して、コードを変更する必要があります。



受信機コード:

コードを表示
 #include <VirtualWire.h> #include <EasyTransferVirtualWire.h> #include <EEPROM.h> const int led_pin = 13; const int receive_pin = 2; unsigned int unique_device_id = 0; //create object EasyTransferVirtualWire ET; char buf[120]; struct SEND_DATA_STRUCTURE{ //  .          // ,      26  ( VirtualWire) unsigned int device_id; unsigned int destination_id; unsigned int packet_id; byte command; int data; }; //     SEND_DATA_STRUCTURE mydata; //       unsigned int  EEPROM void EEPROMWriteInt(int p_address, unsigned int p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); } unsigned int EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void setup() { pinMode(led_pin, OUTPUT); Serial.begin(9600); // Debugging only ET.begin(details(mydata)); // Initialise the IO and ISR vw_set_rx_pin(receive_pin); vw_setup(2000); //   vw_rx_start(); //    // Device ID Serial.print("Getting Device ID... "); unique_device_id=EEPROMReadInt(0); if (unique_device_id<10000 || unique_device_id>60000) { Serial.print("N/A, updating... "); unique_device_id=random(10000, 60000); EEPROMWriteInt(0, unique_device_id); } Serial.println(unique_device_id); } void loop() { if(ET.receiveData()) //   ,  { digitalWrite(led_pin, HIGH); Serial.print("Got: "); Serial.print("Device ID: "); Serial.print(mydata.device_id); Serial.print(" Destination ID: "); Serial.print(mydata.destination_id); Serial.print(" Packet ID: "); Serial.print(mydata.packet_id); Serial.print(" Command: "); Serial.print(mydata.command); Serial.print(" Data: "); Serial.print(mydata.data); Serial.println(); digitalWrite(led_pin, LOW); } }
      
      







やった! スカイネットが放送中です! すでに多くの便利なことを実行できますが、完璧に制限はありません...先に進みます。



MajorDoMoの統合


次の段階は、「経済」全体をスマートホームのより複雑な管理環境に統合することです。 この場合、 MajorDoMoプラットフォームが使用されますが、他のシステムとの統合も同様に編成できます。



実際、コンピューターと無線ネットワークの間の「橋」の組織における統合の原則。 以下に、「リスニングブリッジ」を作成する例を示します。そのタスクは、ブロードキャストを「リッスン」し、受信したすべてのパケットをMajorDoMo環境にブロードキャストすることです。 後者は、順番に、すでに処理に関与します-いくつかのアクションに応答するか、受信したデータをさまざまなインターフェイスに表示します。



スクリプトコントロールパネルで、 easyRFというメッセージ受信スクリプトを作成します。

スクリプトコード:



 $device_id=$params['did']; $destination_id=$params['dest']; $packet_id=$params['pid']; $command_id=$params['c']; $data=$params['d']; say("  $device_id   $packet_id   $command_id   $data");
      
      







このコードを追加した後、httpリンク経由ですぐに呼び出すことができます。

192.168.0.17/objects/?script=easyRF







(192.168.0.17の代わりにサーバーアドレス)



次のステップでは、受信したデータをArduinoからMajorDoMoにストリーミングします。 オプションがあります-Arduinoレシーバーにイーサネットモジュールを追加して、すぐにネットワーク経由でhttpリクエストを送信できます。または、USB経由でマイクロコントローラーを接続し、COMポートに「リッスン」し、httpの送信に対応するキーシーケンスがある場合、 ArduinoGWプログラムを使用できます-リクエスト。それ自体がネットワークにリダイレクトします。



2番目の方法を使用します。 追加の機器は必要ありません。 この場合、受信者コードは次のようになります。

コードを表示
 #include <VirtualWire.h> #include <EasyTransferVirtualWire.h> #include <EEPROM.h> const int led_pin = 13; const int receive_pin = 2; unsigned int unique_device_id = 0; //create object EasyTransferVirtualWire ET; char buf[120]; struct SEND_DATA_STRUCTURE{ //  .          // ,      26  ( VirtualWire) unsigned int device_id; unsigned int destination_id; unsigned int packet_id; byte command; int data; }; //     SEND_DATA_STRUCTURE mydata; //       unsigned int  EEPROM void EEPROMWriteInt(int p_address, unsigned int p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); } unsigned int EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void setup() { pinMode(led_pin, OUTPUT); Serial.begin(9600); // Debugging only ET.begin(details(mydata)); // Initialise the IO and ISR vw_set_rx_pin(receive_pin); vw_setup(2000); // Bits per sec vw_rx_start(); // Start the receiver PLL running // Device ID Serial.print("Getting Device ID... "); unique_device_id=EEPROMReadInt(0); if (unique_device_id<10000 || unique_device_id>60000) { Serial.print("N/A, updating... "); unique_device_id=random(10000, 60000); EEPROMWriteInt(0, unique_device_id); } Serial.println(unique_device_id); } void loop() { if(ET.receiveData()) //   ,  { digitalWrite(led_pin, HIGH); Serial.print("Got: "); Serial.print("Device ID: "); Serial.print(mydata.device_id); Serial.print(" Destination ID: "); Serial.print(mydata.destination_id); Serial.print(" Packet ID: "); Serial.print(mydata.packet_id); Serial.print(" Command: "); Serial.print(mydata.command); Serial.print(" Data: "); Serial.print(mydata.data); Serial.println(); digitalWrite(led_pin, LOW); sprintf(buf, "GET /objects/?script=easyRF&did=%u&dest=%u&pid=%u&c=%u&d=%i HTTP/1.0", (int)mydata.device_id, (int)mydata.destination_id, (int)mydata.packet_id, (int)mydata.command, (int)mydata.data); Serial.println(buf); //      HTTP- (     ethernet-shield- Serial.println(); } }
      
      







以上です! デバイスを作成し、無線モジュールを追加し、あらゆるものの相互作用を構成します。



さらなる開発


上で書いたように、この記事はアイデアの概念と考えることができ、パッケージの元の構造を変更することなく、多くの方向に展開することができます。



思いついたいくつかの考えを挙げましょう。



*「信頼性の高い」交換のノードの作成(同じデバイスで受信機と送信機の両方を使用し、配信制御でパケットの交換を整理します。配信制御用にコマンドの別のクラスを選択します)

*より高価で信頼性の高い無線モジュールを使用しています

*コードを変更することなく、あるデバイスを別のデバイスに「リンク」する手順を実装します(2つのデバイスを「リンク」モードに切り替え、ペアリングされたデバイスのEEPROMに書き込みます)



それだけです ご清聴ありがとうございました!



All Articles