SSブログ

Arduinoライブラリで天井照明を制御 家庭でできるIoT! 3パルス目 [ESP32]





2019-12-09 23.47.55.png誰も期待していないでしょうけれど、Blynkで動かす。

今回のネタは天井照明です。家にはLEDタイプのオーデリックの天井照明を全部の部屋に入れました。
アマゾンリンクの前の型ですが、多分リモコン制御に関しては変わらないと思います。
この天井照明は結構コストパフォーマンスに優れていると思います。12畳でリモコン付き、調光付きで実売が1万円切っていますから。家に入れたのは2015年3月ですので、4年以上安定稼働しています。またわざわざIoTにしなくても付属のリモコンに時計が付いていますので、指定時間に照明を消す/点けるができます。


さて、この天井照明をリモコンで制御するためにはリモコンコードを知らなくてはできませんが(※相変わらずリモコンコードの仕様は公開されていない)、、、とは言え秋月電子とかで売られている赤外線受光素子を用意すれば( http://akizukidenshi.com/catalog/g/gI-04659/ )、Arduinoを使って比較的容易にコードを読み取る事ができます。※受光についてはその内やります。
とりあえずこの天井照明はNECフォーマットとなっています。

一般的に家電に使われているリモコンフォーマットは、NECフォーマット、AEHAフォーマット、SONYフォーマットが有ります。これらのフォーマットの詳細については以下のページを読んで下さい。
ChaNさんのリモコンコードの解説ページ
http://elm-chan.org/docs/ir_format.html

ちょっと補足
キャリア:キャリアは使用する赤外線LEDで決まってしまいます。秋月で買える赤外線LED( http://akizukidenshi.com/catalog/g/gI-12612/ )は大概波長940μmです。要らないリモコンを分解して取り出しても構いません。半減角:15°(狭角)~60°(広角)や、LEDに流す電流で使い易さが変わってくるでしょう。
サブキャリア周波数:キャリアをパルス状にして出力する事で、ちまたに溢れている赤外線と、リモコンの赤外線を区別していますが、そのパルスの周波数です。一般的に38kHz前後です。

つまりリモコンコードのビット毎の0または1に対応するパターンに従って赤外線LEDをON/OFFさせれば良いことが判ります。この時ArduinoであればサブキャリアはanalogWrite、パルスの時間はdelayMicrosecondsを使えば実現できます。
/*************************************************************************/
/* NECタイプのデータ送信                                                 */
/*************************************************************************/
void iRRemocon::txNEC(const unsigned char data[], unsigned int sz)
{
  #define T_VALUE  555UL  // 562UL
  #define PPM_ON   128  //170
  #define PPM_OFF  255

  analogWriteRange( 255 );
  analogWriteFreq( 38 * 1000UL );

  analogWrite(_IR_CONT,0);
 //frame ppm on
  analogWrite(_IR_CONT,PPM_ON);
  delayMicroseconds(T_VALUE * 16);  //16T
  //frame ppm off
  analogWrite(_IR_CONT,PPM_OFF);
  delayMicroseconds(T_VALUE * 8);  //8T

  for(int i = 0; i < sz; i++)
  {
    byte temp = data[i];
    for(int j = 0; j < 8; j++)
    {
      if(temp & 0x01)
      {
        //frame ppm on
        analogWrite(_IR_CONT,PPM_ON);
        delayMicroseconds(T_VALUE * 1);  //1T
        //frame ppm off
        analogWrite(_IR_CONT,PPM_OFF);
        delayMicroseconds(T_VALUE * 3);  //3T
      }
      else
      {
        //frame ppm on
        analogWrite(_IR_CONT,PPM_ON);
        delayMicroseconds(T_VALUE * 1);  //1T
        //frame ppm off
        analogWrite(_IR_CONT,PPM_OFF);
        delayMicroseconds(T_VALUE * 1);  //1T
      }
      temp >>= 1;
    }
  }

  //stop bit
  //frame ppm on
  analogWrite(_IR_CONT,PPM_ON);
  delayMicroseconds(T_VALUE * 1);  //1T
  //frame ppm off
  analogWrite(IR_CONT,0);
}

ですがまぁ、折角エアコン制御を実現しているので、そこで使われている赤外線出力のClassを利用する事とします。

Classの使い方を知るために、三菱のエアコンのソースコードを見てみます。三菱のエアコンはAEHAフォーマットです。
MitsubishiHeatpumpIR.cpp
void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV, uint8_t swingH)
{
  長いので抜粋
  // 40 kHz PWM frequency
  IR.setFrequency(38);

  // The Mitsubishi data is repeated twice
  for (int j=0; j<2; j++) {
    // Header
    IR.mark(MITSUBISHI_AIRCON1_HDR_MARK);
    IR.space(MITSUBISHI_AIRCON1_HDR_SPACE);

    // Data
    for (unsigned int i=0; i<sizeof(MitsubishiTemplate); i++) {
      IR.sendIRbyte(MitsubishiTemplate[i], MITSUBISHI_AIRCON1_BIT_MARK, MITSUBISHI_AIRCON1_ZERO_SPACE, MITSUBISHI_AIRCON1_ONE_SPACE);
    }

    // Pause between the first and the second data burst
    // Also modify one byte for the second burst on MSY model. This does not affect the checksum of the second burst
    if (j == 0) {
      IR.mark(MITSUBISHI_AIRCON1_BIT_MARK);
      IR.space(MITSUBISHI_AIRCON1_MSG_SPACE);

      if (_mitsubishiModel == MITSUBISHI_MSY) {
        MitsubishiTemplate[14] = 0x24;
      }
    }
  }

  // End mark
  IR.mark(MITSUBISHI_AIRCON1_BIT_MARK);
  IR.space(0);
}

MitsubishiHeatpumpIR.h
/*
    Mitsubishi MSZ FD-25 heatpump control (remote control P/N KM09D 0052376)
*/
#ifndef MitsubishiHeatpumpIR_h
#define MitsubishiHeatpumpIR_h

#include "HeatpumpIR.h"

  長いので抜粋

// Mitsubishi MSZ FD-25 timing constants (remote control P/N KM09D 0052376)
#define MITSUBISHI_AIRCON1_HDR_MARK   3500
#define MITSUBISHI_AIRCON1_HDR_SPACE  1700
#define MITSUBISHI_AIRCON1_BIT_MARK   430
#define MITSUBISHI_AIRCON1_ONE_SPACE  1250
#define MITSUBISHI_AIRCON1_ZERO_SPACE 390
#define MITSUBISHI_AIRCON1_MSG_SPACE  17500

class MitsubishiHeatpumpIR : public HeatpumpIR
{
  protected: // Cannot create generic Mitsubishi heatpump instances
    MitsubishiHeatpumpIR();
    uint8_t _mitsubishiModel;  // Tells whether this is FD or EF (or other supported model...)

  public:
    void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd);

  private:
    void sendMitsubishi(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingVCmd, uint8_t swingHCmd);
};

#endif

定義のMITSUBISHI_AIRCON1_HDR_MARKは赤外線フレームのLeader部の8Tに、MITSUBISHI_AIRCON1_HDR_SPACEはLeader部の4Tに相当します。
MITSUBISHI_AIRCON1_BIT_MARKはパルスを出している時間、MITSUBISHI_AIRCON1_ONE_SPACEはビット1のパルスの無い時間、MITSUBISHI_AIRCON1_ZERO_SPACEはビット0のパルスの無い時間に相当します。
ChaNさんの解説と微妙に時間が合わないのは、調整が入っているのかもしれません。

上記を参考にオーデリックのライブラリを作成してみました。現時点ではZIPインストールをせず、プロジェクトディレクトリに保存しているので、includeパスに注意が必要です。
odelic.h
/****************************************************************************/
/* お家のオーデリック製LED照明をなんとかするヘッダー                        */
/*                         Copyright (C) 2014 hamayan All Rights Reserved.  */
/****************************************************************************/
#ifndef odelic_h
#define odelic_h

#include  <Arduino.h>
#include  <HeatpumpIR.h>

extern "C" {
//  #include  <mul_tsk.h>
}

/****************************************************************************/
/* なにかの定義                                                             */
/****************************************************************************/
#define  NEC_T_PERIOD  562UL  // 562us
#define  NEC_REPEAT_PERIOD  120UL  // 108ms
#define  ODELIC_CHANNEL_1  0
#define  ODELIC_CHANNEL_2  1
#define  ODELIC_CHANNEL_3  2

typedef struct  /*オーデリックのフォーマットはNECタイプ*/
{
  unsigned char customerCode[2];
  unsigned char data[2];
} ODELIC_CEILING_LIGHT_CODE;

class odelic : public HeatpumpIR
{
private:
  uint8_t channel;
  uint32_t startTim;
  void send( IRSender& IR, uint8_t *code );

public:
  odelic( uint8_t ch = 0 );
  void onOff( IRSender& IR, bool onoff );
  void coldLight( IRSender& IR, int value );
  void warmLight( IRSender& IR, int value );
  char type( void );
};


#endif  /*odelic_h*/

/****************************************************************************/
/*                         Copyright (C) 2014 hamayan All Rights Reserved.  */
/****************************************************************************/

odelic.cpp
/*************************************************************************/
/* お家のオーデリック製LED照明をなんとかするソース                             */
/*                                designed by hamayan since 2015/05/28  */
/*************************************************************************/
#include  "odelic.h"

/*************************************************************************/
/* 大域変数宣言                                                          */
/*************************************************************************/
/*調光を行う時は連続して2つのコードを送信する。つまり寒色と暖色の両方のLEDを制御している*/
/*また、輝度毎にコードが異なる。それ以外に3byte目と4byte目は反転の関係となる*/
static const unsigned char on[] = { 0x84, 0x51, 0x9a ,0x65 };  /*ch2: 0x84, 0x51, 0x9b,0x64*/
static const unsigned char off[] = { 0x84, 0x51, 0x08 ,0xf7 };  /*ch2: 0x84, 0x51, 0x09,0xf6*/
static const unsigned char cold[][4] =
{
  { 0x84, 0x51, 0x6c ,0x93 },  /*10% ch2: 0x84, 0x51, 0x6d,0x92*/
  { 0x84, 0x51, 0x6e ,0x91 },  /*20% ch2: 0x84, 0x51, 0x6f,0x90*/
  { 0x84, 0x51, 0x70 ,0x8f },  /*30% ch2: 0x84, 0x51, 0x71,0x8e*/
  { 0x84, 0x51, 0x72 ,0x8d },  /*40% ch2: 0x84, 0x51, 0x73,0x8c*/
  { 0x84, 0x51, 0x74 ,0x8b },  /*50% ch2: 0x84, 0x51, 0x75,0x8a*/
  { 0x84, 0x51, 0x76 ,0x89 },  /*60% ch2: 0x84, 0x51, 0x77,0x88*/
  { 0x84, 0x51, 0x78 ,0x87 },  /*70% ch2: 0x84, 0x51, 0x79,0x86*/
  { 0x84, 0x51, 0x7a ,0x85 },  /*80% ch2: 0x84, 0x51, 0x7b,0x84*/
  { 0x84, 0x51, 0x7c ,0x83 },  /*90% ch2: 0x84, 0x51, 0x7d,0x82*/
  { 0x84, 0x51, 0x7e ,0x81 },  /*100% ch2: 0x84, 0x51, 0x7f,0x80*/
};
static const unsigned char warm[][4] =
{
  { 0x84, 0x51, 0x58 ,0xa7 },  /*10% ch2: 0x84, 0x51, 0x59,0xa6*/
  { 0x84, 0x51, 0x5a ,0xa5 },  /*20% ch2: 0x84, 0x51, 0x5b,0xa4*/
  { 0x84, 0x51, 0x5c ,0xa3 },  /*30% ch2: 0x84, 0x51, 0x5d,0xa2*/
  { 0x84, 0x51, 0x5e ,0xa1 },  /*40% ch2: 0x84, 0x51, 0x5f,0xa0*/
  { 0x84, 0x51, 0x60 ,0x9f },  /*50% ch2: 0x84, 0x51, 0x61,0x9e*/
  { 0x84, 0x51, 0x62 ,0x9d },  /*60% ch2: 0x84, 0x51, 0x63,0x9c*/
  { 0x84, 0x51, 0x64 ,0x9b },  /*70% ch2: 0x84, 0x51, 0x65,0x9a*/
  { 0x84, 0x51, 0x66 ,0x99 },  /*80% ch2: 0x84, 0x51, 0x67,0x98*/
  { 0x84, 0x51, 0x68 ,0x97 },  /*90% ch2: 0x84, 0x51, 0x69,0x96*/
  { 0x84, 0x51, 0x6a ,0x95 },  /*100% ch2: 0x84, 0x51, 0x6b,0x94*/
};

/*************************************************************************/
/* プロトタイプ宣言                                                      */
/*************************************************************************/

/*************************************************************************/
/* インスタンス                                                          */
/*************************************************************************/

odelic::odelic( uint8_t ch )
{
  channel = ch;
  startTim = millis();
}


/*************************************************************************/
/* send                                                                  */
/*************************************************************************/
void odelic::send( IRSender& IR, uint8_t *code )
{
  uint8_t tempUC[4];
  memcpy( tempUC, code, sizeof(tempUC) );
  tempUC[2] = tempUC[2] + channel;
  tempUC[3] = tempUC[3] - channel;

  // 40 kHz PWM frequency
  IR.setFrequency( 38 );

  //リピートでメソッドが呼ばれた時、強制的に間隔を開ける
  while( (millis() - startTim) < NEC_REPEAT_PERIOD ) delay( 10 );
  // start time
  startTim = millis();

  // Header
  IR.mark( 16 * NEC_T_PERIOD );  //frame ppm on
  IR.space( 8 * NEC_T_PERIOD );  //frame ppm off

  // Data
  for( unsigned int i = 0; i < sizeof( tempUC ); i++ )
  {
    IR.sendIRbyte( tempUC[i], NEC_T_PERIOD, NEC_T_PERIOD, 3 * NEC_T_PERIOD );  // data,562us,562us,1686us
    // pulse on period, pulse off period at ZERO, pulse off period at 1
  }

  // End mark
  IR.mark( NEC_T_PERIOD );
  IR.space( 0 );
}

/*************************************************************************/
/* 引数valueは2以上で100以下の数字                                       */
/*************************************************************************/
void odelic::coldLight( IRSender& IR, int value )
{
  if( value >= 2 && value <= 100 )
  {
    value--;
    value /= 10;
    send( IR, (uint8_t *)cold[ value ] );
  }
}


/*************************************************************************/
/* 引数valueは2以上で100以下の数字                                       */
/*************************************************************************/
void odelic::warmLight( IRSender& IR, int value )
{
  if( value >= 2 && value <= 100 )
  {
    value--;
    value /= 10;
    send( IR, (uint8_t *)warm[ value ] );
  }
}


/*************************************************************************/
/* 引数valueは0または1またはそれ以上で100以下の数字                      */
/*************************************************************************/
void odelic::onOff( IRSender& IR, bool onoff )
{
  if( onoff ) send( IR, (uint8_t *)on );
  else send( IR, (uint8_t *)off );
}


/*************************************************************************/
/* リモコンフォーマットを答える                                          */
/*************************************************************************/
char odelic::type( void )
{
  return 'N';  /*NECのN!*/
}


/*************************************************************************/
/* end of file                                                          */
/*                                designed by hamayan since 2015/05/28  */
/*************************************************************************/

スケッチ側はこんなんなります。
#include  "odelic.h"

#define   IR_CONT    12  // Ir control

IRSenderBitBang irSender( IR_CONT );
odelic *room1 = new odelic( ODELIC_CHANNEL_1 );

/*************************************************************************/
/*  blynk virtual pin 2 function.                                        */
/*************************************************************************/
BLYNK_WRITE( V2 )
{ 
  int value = param.asInt();
  if( value )
  {
    room1->onOff( irSender, true );
    Blynk.virtualWrite( V4, 100 );
    Blynk.virtualWrite( V5, 100 );
  }
  else
  {
    room1->onOff( irSender, false );
    Blynk.virtualWrite( V4, 0 );
    Blynk.virtualWrite( V5, 0 );
  }
}

/*************************************************************************/
/*  blynk virtual pin 3 function.                                        */
/*************************************************************************/
BLYNK_WRITE( V3 )
{ 
  room1->coldLight( irSender, 70 );
  room1->warmLight( irSender, 70 );
  Blynk.virtualWrite( V4, 70 );
  Blynk.virtualWrite( V5, 70 );
}

/*************************************************************************/
/*  blynk virtual pin 4 function.                                        */
/*************************************************************************/
BLYNK_WRITE( V4 )
{ 
  int value = param.asInt();
  room1->warmLight( irSender, value );
}

/*************************************************************************/
/*  blynk virtual pin 5 function.                                        */
/*************************************************************************/
BLYNK_WRITE( V5 )
{ 
  int value = param.asInt();
  room1->coldLight( irSender, value );
}

オーデリックの天井照明は、隣り合う部屋でリモコンを使った時に混信しないように、3チャネルまで設定できます。

ESP-WROOM-02開発ボード

ESP-WROOM-02開発ボード

  • 出版社/メーカー: スイッチサイエンス(Switch Science)
  • メディア: おもちゃ&ホビー






nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。