日曜大工の無線制御スイッチ。 パート3-スイッチソフトウェア

以前の投稿では、2チャネル無線制御スイッチユニットを設計、製造 、および包括的にテストしました。







しかし、これまでのところ、「魂のない鉄片」であり、MKに組み込まれている潜在的な力にもかかわらず、その方法はわかりません。



一般に、メインデバイス(無線モジュールの接続を検討しない場合)は、2つのボタンと1組のLEDが接続される通常のArduinkaほど複雑ではありません(結果のデバイスでは、LEDはリレーを制御するトランジスタキーに置き換えられますが、これは本質を変更しません) )



製造された無線スイッチモジュールは、直接開発およびデバッグするためにあまり廃棄されません。



しかし、すでに気づいたように、モジュールを「復活」させるには、さまざまなプレス(2つのボタン)を実行し、アルゴリズムに従って2つの負荷をオン/オフできるスケッチを書く必要があります(レイアウトでは2つのLEDになります)。 当然、これは「基本機能」です。これに対処した後、「無線チャネル」機能を追加します。



一般に、もちろん、「ブレッドボード」から始める方が正しいでしょうが、この場合、プロトタイプが結果のデバイスよりも後に作成されたことがたまたま起こりました。


レイアウト



したがって、スケッチを準備するための「便利な」環境を得るために、はんだなしブレッドボード、任意のarduino互換ボード(私の場合はcArduino Nano)、2つのクロックボタン、2つのLED(電流制限抵抗器)、およびいくつかのジャンパーを取りましょう:







最初の投稿のコンセプトに従って、レイアウトを組み立てます。



思い出させてください:



実際、このようなレイアウトにより、主な機能を記述およびデバッグできます。



将来的には、プログラマーを使用して、変更なしで最終デバイスにこのスケッチをダウンロードする必要があります。



開発を開始する前に、実装する基本機能を修正する必要があります。



必要な機能



当然、この「ウィッシュリスト」のリストは、プロジェクトの作業を開始する前でも頭の中にあります。今は、それを作成するだけです。



基本機能


バスルームの照明と換気を制御するために2チャンネルスイッチが使用されるため、機能のリストは次のとおりです。



機能のリストを作成するとき-積極的にあなたの家と通信します。 たとえば、換気が自動的にオンになるまでの時間が短すぎるため、不必要なトリップが発生することを合理的に促されました。一般に、動作中にすべての時間パラメーターを調整できる必要があります。


ラジコン


これらの関数は少し後で実装されますが、すぐに念頭に置いておく必要があります(書き直す必要はありません):



プログラミング



基本機能を実装するためのソフトウェアを作成するプロセスでは、次のことを考慮します。

  1. 現在は2つのチャネルがありますが、将来的にはより多く/より少なくなる可能性があり、コードは簡単に調整できるようにする必要があります(大幅な書き換えなし)。
  2. デバイスは内蔵されており、壁から抜け出せない場合は非常に問題があります。


最初の要件は、モジュールのパラメーターを格納するための構造体の配列の使用につながり、2番目はwatchdogの使用を指示します。



チャネルパラメーターを格納するために、次の構造を作成しました。

typedef struct { int button; //   int relay; //   boolean state; //  (/) unsigned long power_on; // ,     unsigned long auto_off; // ,      unsigned long time_off; // ,  boolean autostate; // , ,    unsigned long press_start; // ,    unsigned long press_stop; // ,    } Channel;
      
      





これで、簡単なスケッチを書くことができます。



setup()関数では、必要な初期化をすべて実行し、番犬をコックします。



その後、すべてが簡単です。メインプログラムループ(ループ())で、次の手順を順番に実行します。



追加の作業ロジックが必要ない場合(私の場合は、光の状態に応じて自動的に換気のオンとオフを切り替えます)-chkLogic()関数を簡単に削除できます。



こんなスケッチをもらった
 //  #include <avr/wdt.h> #include <Bounce.h> //#define DEBUG //      -  //    #define CH 2 //      #define LONGPRESS 2000 // 2  //      "" typedef struct { int button; //   int relay; //   boolean state; //  (/) unsigned long power_on; // ,     unsigned long auto_off; // ,      unsigned long time_off; // ,  boolean autostate; // , ,    unsigned long press_start; // ,    unsigned long press_stop; // ,    } Channel; //    Channel MySwitch[CH] = { 15, 3, LOW, 0, 3600000, 0, false, 0, 0, 14, 4, LOW, 0, 600000, 0, false, 0, 0 }; //    Bounce.  ,         . Bounce bouncer0 = Bounce(MySwitch[0].button,5); Bounce bouncer1 = Bounce(MySwitch[1].button,5); //     boolean logicFlag = false; boolean onFlag = false; boolean offFlag = false; void setup() { wdt_disable(); //         bootloop #ifndef DEBUG Serial.begin(9600); Serial.println("Start!"); pinMode(13, OUTPUT); #endif //  pinMode(MySwitch[0].relay, OUTPUT); pinMode(MySwitch[1].relay, OUTPUT); //  pinMode(MySwitch[0].button, INPUT); pinMode(MySwitch[1].button, INPUT); //      digitalWrite(MySwitch[0].button, HIGH); digitalWrite(MySwitch[1].button, HIGH); //delay(5000); // ,        bootloop #ifndef DEBUG Serial.println("Ready!"); #endif wdt_enable (WDTO_8S); //        8 . } void loop() { //    button_read(); //   autoOff(); //     chkLogic(); //    wdt_reset(); } void button_read(){ //    1 if ( bouncer0.update() ) { if ( bouncer0.read() == LOW) { //     MySwitch[0].press_start = millis(); } else { // ,    (  )  ,  . pressDetect(0, millis()); } } //    2 if ( bouncer1.update() ) { if ( bouncer1.read() == LOW) { MySwitch[1].press_start = millis(); } else { pressDetect(1, millis()); } } } //   void doSwitch(int ch, boolean state){ //    MySwitch[ch].state = state; //      ""      if (MySwitch[ch].state == HIGH) { //    MySwitch[ch].power_on = millis(); if(MySwitch[ch].auto_off > 0) { //  ,      MySwitch[ch].time_off = MySwitch[ch].power_on + MySwitch[ch].auto_off; MySwitch[ch].autostate = true; } #ifndef DEBUG Serial.print("ON "); Serial.println(ch); #endif } else { //    MySwitch[ch].autostate = false; //    MySwitch[ch].time_off = 0; #ifndef DEBUG Serial.print("OFF "); Serial.println(ch); #endif } digitalWrite(MySwitch[ch].relay,MySwitch[ch].state); } //  void autoOff(){ //     for (int i=0; i < CH; i++) { //     -   if ((millis() >= MySwitch[i].time_off) && MySwitch[i].autostate) { MySwitch[i].autostate = false; doSwitch(i, LOW); #ifndef DEBUG Serial.print("Auto OFF "); Serial.println(i); #endif } } } //         void pressDetect(int ch, unsigned long p_stop) { if (MySwitch[ch].press_start != 0) { if ((p_stop-MySwitch[ch].press_start) < LONGPRESS) { //   MySwitch[ch].press_stop = p_stop; #ifndef DEBUG Serial.print("Short press "); Serial.println(ch); #endif doSwitch(ch, MySwitch[ch].state ? LOW : HIGH); } else { //   #ifndef DEBUG Serial.print("Long press "); Serial.println(ch); digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); #endif } } } //    void chkLogic(){ /*   ( /) 0- -  / 1- -  /  0   ,  1.5 ,     1 .   0  - 1    10  */ //     1,5 ,     -    if ((onFlag == false) && (millis() > (MySwitch[0].power_on + 90000)) && (MySwitch[0].state == HIGH) && (MySwitch[1].state == LOW) && (MySwitch[1].press_stop < MySwitch[0].power_on)) { //   doSwitch(1, HIGH); //    onFlag = true; logicFlag = true; //      MySwitch[1].autostate = false; #ifndef DEBUG Serial.println("Auto Logic ON"); #endif } //    -      -  10     if ((logicFlag == true) && (offFlag == false) && (MySwitch[1].state == HIGH) && (MySwitch[0].state == LOW)) { MySwitch[1].time_off = millis() + 600000; MySwitch[1].autostate = true; offFlag = true; #ifndef DEBUG Serial.println("Auto Logic OFF started"); #endif } //   ,    if ((logicFlag == true) && (MySwitch[0].state == LOW) && (MySwitch[1].state == LOW)) { offFlag = false; onFlag = false; logicFlag = false; #ifndef DEBUG Serial.println("Logic reset"); #endif } //        -    if ((logicFlag == false) && (offFlag == false) && (MySwitch[0].press_stop > MySwitch[1].power_on) && (MySwitch[1].state == HIGH) && (MySwitch[0].state == LOW)) { logicFlag = true; #ifndef DEBUG Serial.println("Auto OFF 1 after manual OFF 0"); #endif } }
      
      







基本機能は、希望どおりに機能します。

ボタンを短く押すと、対応するLEDがオンになり、追加のロジックがトリガーされます。 いずれかのボタンを長押しすると、1秒間、arduinoの内蔵LED(D13)が点灯します。



これで、ワイヤレス機能を実装できます。



これを行うために、私は初期の投稿の1つであるワイヤレス通信「スマートホーム」に目を向けます。



私がそこで説明した基本原則は、時の試練に耐え、非常に小さな変更を受けました。



次の構造は、パラメーターの操作に適しています。

 typedef struct{ float Value; //  boolean Status; //  // 0 - RO // 1 - RW char Note[16]; //  } Parameter;
      
      





送信データには、次の構造を使用します。

 typedef struct{ int SensorID; //   int CommandTo; //    ... int Command; //  // 0 -  // 1 -   // 2 -   int ParamID; //   float ParamValue; //   boolean Status; //  // 0 -    (RO) // 1 -   (RW) char Comment[16]; //  } Message;
      
      





上記に従って、私のモジュールは次のように説明されます。

 #define SID 701 //   #define NumSensors 8 //   Parameter MySensors[NumSensors+1] = { //   (  ) NumSensors,0,"BR 2Floor", //    0,1,"Ch.1 (Light)", //   1 () 0,1,"Ch.2 (Vent)", //   2 () 0,1,"Ch.1 (LP)", //     1  0,1,"Ch.2 (LP)", //     2  0,1,"Auto-delayON", //      (  ),   0,1,"Auto-delayOFF", //      (  ),   0,1,"Ch.1 AutoOFF", //    1 ,   0,1,"Ch.2 AutoOFF" //    2 ,   }; Message sensor;
      
      





現在の状態と時間のパラメーターを記述するすべての主要なパラメーターが存在することがわかります。



もう少しプログラミングとコードの準備ができました。
 //  #include <avr/wdt.h> #include <Bounce.h> #include <SPI.h> #include "RF24.h" #include <EEPROM.h> #define DEBUG //    -  //    #define CH 2 //      #define LONGPRESS 2000 // 2  //    #define SID 701 //   #define NumSensors 8 //   //      "" typedef struct { int button; //   int relay; //   boolean state; //  (/) unsigned long power_on; // ,     unsigned long auto_off; // ,      unsigned long time_off; // ,  boolean autostate; // , ,    unsigned long press_start; // ,    unsigned long press_stop; // ,    } Channel; //    Channel MySwitch[CH] = { 15, 3, LOW, 0, 0, 0, false, 0, 0, 14, 4, LOW, 0, 0, 0, false, 0, 0 }; //      typedef struct{ float Value; //  boolean Status; //  // 0 - RO // 1 - RW char Note[16]; //  } Parameter; //      typedef struct{ int SensorID; //   int CommandTo; //    ... int Command; //  // 0 -  // 1 -   // 2 -   int ParamID; //   float ParamValue; //   boolean Status; //  // 0 -    (RO) // 1 -   (RW) char Comment[16]; //  } Message; ///////////////////////////////////////////////////////////////////////////// Parameter MySensors[NumSensors+1] = { //   (  ) NumSensors,0,"701 (2F, bath)", //   ""         0,1,"Ch.1 (Light)", //   1 () 0,1,"Ch.2 (Vent)", //   2 () 0,1,"Ch.1 (LP)", //     1  0,1,"Ch.2 (LP)", //     2  0,1,"Auto-delayON", //      (  ),   0,1,"Auto-delayOFF", //      (  ),   0,1,"Ch.1 AutoOFF", //    1 ,   0,1,"Ch.2 AutoOFF" //    2 ,   }; Message sensor; ///////////////////////////////////////////////////////////////////////////// //    Bounce.  ,         . Bounce bouncer0 = Bounce(MySwitch[0].button,5); Bounce bouncer1 = Bounce(MySwitch[1].button,5); //     boolean logicFlag = false; boolean onFlag = false; boolean offFlag = false; //RF24 radio(CE,CSN); RF24 radio(10,9); unsigned long measureTime; #define DELTAMEASURE 15000 //   15      const uint64_t pipes[2] = { 0xF0F0F0F0A1LL, 0xF0F0F0F0A2LL }; volatile boolean waitRF24 = false; void setup() { wdt_disable(); //         bootloop //    EEPROM prepareFromEEPROM(); #ifndef DEBUG Serial.begin(9600); Serial.println("Start!"); pinMode(13, OUTPUT); #endif for(int i=0; i<CH; i++) { //  pinMode(MySwitch[i].relay, OUTPUT); //  pinMode(MySwitch[i].button, INPUT); //      digitalWrite(MySwitch[i].button, HIGH); } //  initRF24(); //    ( -   ) attachInterrupt(0, isr_RF24, FALLING); measureTime = millis()+DELTAMEASURE; //delay(5000); // ,        bootloop #ifndef DEBUG Serial.println("Ready!"); #endif wdt_enable (WDTO_8S); //        8 . } void loop() { //    button_read(); //   autoOff(); //     chkLogic(); //   listenRF24(); //   -    floodRF24(); //    wdt_reset(); } void button_read(){ //    1 if ( bouncer0.update() ) { if ( bouncer0.read() == LOW) { //     MySwitch[0].press_start = millis(); } else { // ,    (  )  ,  . pressDetect(0, millis()); } } //    2 if ( bouncer1.update() ) { if ( bouncer1.read() == LOW) { MySwitch[1].press_start = millis(); } else { //MySwitch[1].press_stop = millis(); pressDetect(1, millis()); } } } //   void doSwitch(int ch, boolean state){ //     MySwitch[ch].state = state; //      ""      if (MySwitch[ch].state == HIGH) { //    MySwitch[ch].power_on = millis(); if((MySwitch[ch].auto_off > 0) && (MySwitch[ch].auto_off != 0)) { //  ,      MySwitch[ch].time_off = MySwitch[ch].power_on + MySwitch[ch].auto_off; MySwitch[ch].autostate = true; } #ifndef DEBUG Serial.print("ON "); Serial.println(ch); #endif } else { //    MySwitch[ch].autostate = false; //    MySwitch[ch].time_off = 0; #ifndef DEBUG Serial.print("OFF "); Serial.println(ch); #endif } digitalWrite(MySwitch[ch].relay,MySwitch[ch].state); } //  void autoOff(){ //     for (int i=0; i < CH; i++) { //     -   if ((millis() >= MySwitch[i].time_off) && MySwitch[i].autostate) { MySwitch[i].autostate = false; doSwitch(i, LOW); #ifndef DEBUG Serial.print("Auto OFF "); Serial.println(i); #endif } } } //         void pressDetect(int ch, unsigned long p_stop) { if (MySwitch[ch].press_start != 0) { if (((p_stop-MySwitch[ch].press_start) < LONGPRESS) && (p_stop-MySwitch[ch].press_start) > 0) { //   MySwitch[ch].press_stop = p_stop; #ifndef DEBUG Serial.print("Short press "); Serial.println(ch); #endif doSwitch(ch, MySwitch[ch].state ? LOW : HIGH); } else { //   #ifndef DEBUG Serial.print("Long press "); Serial.println(ch); digitalWrite(13, !digitalRead(13)); #endif //       MySensors[ch+3].Value = 1; //      ""   //    "" , -  (  ) //        ""  } } } //    void chkLogic(){ /*   ( /) 0- -  / 1- -  /  0   ,  1.5 ,     1 .   0  - 1    10  */ //       ( MySensors[5].Value   - ),     -    if ((onFlag == false) && (millis() > (MySwitch[0].power_on + MySensors[5].Value*60000)) && (MySensors[5].Value != 0) && (MySwitch[0].state == HIGH) && (MySwitch[1].state == LOW) && (MySwitch[1].press_stop < MySwitch[0].power_on)) { //   doSwitch(1, HIGH); //    onFlag = true; logicFlag = true; //      MySwitch[1].autostate = false; #ifndef DEBUG Serial.println("Auto Logic ON"); #endif } //    -      -    (( MySensors[6].Value   - )    if ((logicFlag == true) && (offFlag == false) && (MySensors[6].Value != 0) && (MySwitch[1].state == HIGH) && (MySwitch[0].state == LOW)) { MySwitch[1].time_off = millis() + MySensors[6].Value*60000; MySwitch[1].autostate = true; offFlag = true; #ifndef DEBUG Serial.println("Auto Logic OFF started"); #endif } //   ,    if ((logicFlag == true) && (MySwitch[0].state == LOW) && (MySwitch[1].state == LOW)) { offFlag = false; onFlag = false; logicFlag = false; #ifndef DEBUG Serial.println("Logic reset"); #endif } //        -    if ((logicFlag == false) && (offFlag == false) && (MySwitch[0].press_stop > MySwitch[1].power_on) && (MySwitch[1].state == HIGH) && (MySwitch[0].state == LOW)) { logicFlag = true; #ifndef DEBUG Serial.println("Auto OFF 1 after manual OFF 0"); #endif } } void floodRF24(){ //    (1   DELTAMEASURE ) //    !   -      ! if (millis() > measureTime){ getValue(); //      //for (int i=1; i<=NumSensors; i++) { //     (,    -  ) for (int i=1; i<=4; i++) { sendSlaveMessage(0, i); delay(20); } measureTime = millis()+DELTAMEASURE; } } void getValue(){ MySensors[1].Value = MySwitch[0].state; MySensors[2].Value = MySwitch[1].state; return; } //      void isr_RF24(){ waitRF24 = true; } //   (, ,  ) -   (slave) // !     ParamID void sendSlaveMessage(int To, int ParamID) { //    radio.stopListening(); radio.openWritingPipe(pipes[0]); radio.openReadingPipe(1,pipes[1]); delay(20); //      sensor.SensorID = SID; sensor.CommandTo = To; sensor.Command = 0; sensor.ParamID = ParamID; sensor.ParamValue = MySensors[ParamID].Value; sensor.Status = MySensors[ParamID].Status; memcpy(&sensor.Comment,(char*)MySensors[ParamID].Note, 16); //   RF24 bool ok = radio.write( &sensor, sizeof(sensor) ); delay (20); //    radio.openWritingPipe(pipes[1]); radio.openReadingPipe(1,pipes[0]); radio.startListening(); } //   void listenRF24(){ //   ,       if (waitRF24) { waitRF24 = false; // ,   //    if (radio.available()) { bool done = false; while (!done) { done = radio.read( &sensor, sizeof(sensor) ); //     -  if (sensor.CommandTo == SID) { //   ( , , , ) doCommand(sensor.SensorID, sensor.Command, sensor.ParamID, sensor.ParamValue, sensor.Status, sensor.Comment); } } } } } //   ( , , ID,  , , ) -   void doCommand(int From, int Command, int ParamID, float ParamValue, boolean Status, char* Comment) { //     -      ,    -  switch (Command) { case 0: //    break; case 1: getValue(); //     sendSlaveMessage(From, ParamID); break; case 2: //  setValue(From, ParamID, ParamValue, Comment); //  sendSlaveMessage(From, ParamID); break; default: break; } } //   (, , , ) void setValue(int From, int ParamID, float ParamValue, char* Comment) { //         -    if(MySensors[ParamID].Value != ParamValue){ //   / -  ( ) ""     //    ""    (    ) if((ParamID<3) && (MySwitch[ParamID-1].state != (boolean)ParamValue)) { // " " MySwitch[ParamID-1].press_start = millis()-50; // " " (  ""   ) pressDetect(ParamID-1, millis()); } else { //   MySensors[ParamID].Value = ParamValue; //   ,    -   EEPROM if (ParamID > 4){ EEPROM.write(ParamID-5, MySensors[ParamID].Value); //    if(ParamID > 6) { MySwitch[ParamID-7].auto_off = ((unsigned long)MySensors[ParamID].Value)*60000; } } } } } void initRF24(){ radio.begin(); radio.setRetries(15,15); //     ( ) radio.setChannel(100); radio.openWritingPipe(pipes[0]); radio.openReadingPipe(1,pipes[1]); radio.startListening(); //    } void prepareFromEEPROM() { // 4  = 4    1 : // 0 -      (  ),   // 1 -      (  ),   // 2 -    1 ,   // 3 -    2 ,   for(int i=0; i<4; i++) { MySensors[i+5].Value = EEPROM.read(i); } //        for(int i=0; i<CH; i++) { MySwitch[i].auto_off = ((unsigned long)MySensors[i+7].Value)*60000; } }
      
      





実際には、モジュールをフラッシュする必要があります。



ファームウェアについては、USBtinyISPプログラマーを使用します



私は要求し、動作を確認しました-すべては問題ありませんが、「クリーン」なMKではすべてのEEPROMバイトが255に設定され、対応する遅延が発生することがわかりました。



上記のコードによると、すべての時間パラメータのインストールは無線チャネルを介してのみ実行されることが明らかです。 しかし、私はまだ「制御モジュール」について何も書いていません。したがって、どうにかして「孤立して」この問題を解決する必要があります。



これを行うには、EEPROMライブラリの例を使用し、それらから直接、不揮発性メモリの対応するセルにプライマリ(より関連性の高い)値を書き込みます。



その後のチェックで、今ではすべてが私が望んでいた通りに機能することがわかりました。

ここでも、「スマートホーム」のデバイスの基本原則を繰り返します。 作成された各デバイスは、特定の目標を達成するために作成され、独立して機能する必要があります。


これで、デバイスは自給自足で、メイン機能を実行する準備ができました(無線チャネルがなくても)。 マウントできます。



モジュールのインストール



ラジコンモジュールは乾式壁から壁の内側に取​​り付けられるので、適切なケースを選択しました(モジュール自体とそのための電源が収まるようにし、このケースを問題なく取り付けボックスを取り付けるための穴に押し込むことができるようにします)。前回



と同じ場所で電源ボードを取りました-iPhoneの電源を見ました。原則として、コンデンサ電源を作成するか、既製のオプションを探すことができます(たとえば、こちら)。どういうわけかこのようになりました(すべてがすでにここに接続されています-壁に取り付ける前に最後のテストを実施しました):ケースは少し大きいことが判明しましたが、農場の小さいものは収まりませんでした。















もちろん、最初に特定の建物を選択して適合するようにした方が正確ですが、サイズに特別な制限はありませんでした。



これで、壁のモジュールの「埋め込み」に直接対処できます(残念ながら、プロセスに夢中になり、写真を撮るのを忘れたため、テキストの説明のみ)。



すべて準備完了です。電気をオンにして、すべてが意図したとおりに機能することを確認します。



結果



作成されたデバイスは正常にマウントされ、「ダム」スイッチを完全に置き換えて、少し「心」を追加しました(所有者の「忘れっぽい」場合のエネルギーの節約、フードの自動オン/オフなど)。



継続するには ...



PSは、の議論 の最初の投稿よりコンパクトなサイズを実現するために含めた他の新しい要素の使用についての質問がありました。

最近、私はこの動物を手に入れました:




( ) . / 220 ( , — ). 5, ( ).


これは、すべてをドグマとして扱うべきではないということです(すべてのプロジェクトを「1対1」で繰り返します)-探し、最適な(特定のタスクごとの)ソリューションを選択し、修正します!



便利なリンク



Nikita_Rogatnevが資料の出版準備に協力してくれたことに感謝します。



All Articles