スポンサードリンク

デバイス編#17 ~ようやく見えてきた完成の兆し~

スマートカーテン

8月も後半になってきて、ただただ猛暑だった日から体感できるぐらい最高温度が下がってきた気がします。

まあ、そんな中でもクーラーは24時間運転中ということは変わんないんですけどね。


こんにちは、ポニ丸です。

前回決めた仕様から実際にファームを組んでいきます!

まず、組んでいくにあたって意識する点は一つだけ!(ゆるい笑)

各機能をクラス化する!

理由は大きく2つあります。

1つは、C++という言語の特徴であるオブジェクト指向に則るっていうのがあります。

もう1つは、オブジェクト指向に関連するんですが、今後の開発に対する資産作りですね。

今後Arduino系のマイコンでファーム開発をすることがあったとき、クラス化された資産が残っていれば開発時間の短縮が図れるんです。個人的にはこっちの理由が大きいかな。

周辺機能を扱うときにはArduino系のAPIをだったりを扱うのが定番なんですが、これをラップする形で自分が使いやすいクラスを作ってゆきます!

作成するクラス一覧

クラスを作っていくよーっていう話はしたんですが、実際になんのクラスを作るの?っていう部分を詰めていきます。

あ、ちなみにプログラミングにあまり詳しくない方にお話すると、クラスというのはイメージとして「目的を満たす情報と処理をまとめた型紙」みたいなやつです。

LEDを例に出すと、「LEDとして動作させる」が目的です。

その目的を満たすための情報(プロパティ)として「LEDとして動作させるピン」と「点灯を示すレベル」を持っています。

保有している情報を元に「LEDを点灯させる」「LEDを消灯させる」などの処理(メソッド)を行えます。

これらが全部まとまったのがクラスと呼び、実際に使うときは型紙のままでは使えないので型紙から作り出したインスタンス(実体)と呼ばれるものを生成して扱うんです。

ここまで話して思ったのが、、、文章だけじゃ伝わりづらいということです笑

もしここら辺の理解に苦しんでる方がいらっしゃったらコメントください。わかるまで説明しますので!笑

話を戻しまして、今回は4つのクラスを作ります!

1.スイッチクラス

スイッチクラスは以下のプロパティと以下のメソッドを持ちます。

プロパティ

  • ピン番号
  • ONを示すレベル

メソッド

  • コンストラクタ(プロパティ設定)
  • 状態取得(ONかOFFか)

2.LEDクラス

LEDクラスは以下のプロパティと以下のメソッドを持ちます。

プロパティ

  • ピン番号
  • ONを示すレベル

メソッド

  • コンストラクタ(プロパティ設定)
  • 状態取得(点灯しているか消灯しているか)
  • 点灯状態に設定
  • 消灯状態に設定

3.モータクラス

モータクラスは以下のプロパティと以下のメソッドを持ちます。

プロパティ

  • 正回転用ピン番号
  • 逆回転用ピン番号
  • PWM分解能
  • PWMの最大値

メソッド

  • コンストラクタ(プロパティ設定)
  • 正回転
  • 逆回転
  • 空転
  • 回転状態取得

4.BLE Peripheralクラス

BLE Peripheralクラスは以下のプロパティと以下のメソッドを持ちます。

プロパティ

  • BLEServerクラスのポインタ
  • BLEServiceクラスのポインタ
  • BLECharacteristicクラスのポインタ(Characteristic数分)
  • BLEAdvertisingクラスのポインタ

メソッド

  • コンストラクタ(Serverの生成、Serviceの生成)
  • Characteristicのセットアップ
  • BLE Peripheral動作開始
  • 指定CharacteristicのValue設定
  • 指定CharasteristicのNotify送信

完成したファームウェア

間が抜けたように感じますが、クラスというパーツが揃えばそれを組み合わせるだけなのでほぼゴールです。

クラス定義、そしてそのクラスを使って組んだファームウェアが以下になります!
頑張って作ったファイルごとのタブ表示機能も合わせてお楽しみください笑

#include "smart_curtain.h"

/* Wi-Fiの接続先 */
const char* ssid = "xxxxxxxx";
const char* password = "xxxxxxxx";
/* BLEデバイス名 */
const char* deviceName = "Smart_Curtain";

/**
 * static関数プロトタイプ
 */
static void BLE_OnConnectCallback(void);
static void BLE_WriteMsgCallback(void);
static void OpenCurtain(void);
static void CloseCurtain(void);
static void StopCurtain(void);
static void IRAM_ATTR IsrTimer100ms(void);
static void IRAM_ATTR IsrTimer1sec(void);

/**
 * static変数
 */
static SmartCurtain_t gs_sc;
static chara_param_t gs_msg_param   = {INDEX_MSG_CHARA, UUID_MSG_CHARA, new uint8_t[VALUE_SIZE_MSG_CHARA], VALUE_SIZE_MSG_CHARA, BLECharacteristic::PROPERTY_WRITE_NR};
static chara_param_t gs_state_param = {INDEX_STATE_CHARA, UUID_STATE_CHARA, new uint8_t[VALUE_SIZE_STATE_CHARA], VALUE_SIZE_STATE_CHARA, BLECharacteristic::PROPERTY_NOTIFY};
static chara_param_t gs_time_param  = {INDEX_TIME_CHARA, UUID_TIME_CHARA, new uint8_t[VALUE_SIZE_TIME_CHARA], VALUE_SIZE_TIME_CHARA, BLECharacteristic::PROPERTY_NOTIFY};

static volatile bool gs_send_notify_flag = false;
static volatile bool gs_1sec_flag = false;
static volatile bool gs_time_sync_flag = false;

void setup()
{
#ifdef SC_DEBUG
  Serial.begin(115200);
#endif

  /**
   * Wi-Fiのセットアップ
   */
  WIFI_Setup(ssid, password);

  /**
   * Switchのセットアップ
   */
  pinMode(SWITCH_PWR_PIN, OUTPUT);
  digitalWrite(SWITCH_PWR_PIN, HIGH);
  gs_sc.pSwitchCw = new Switch(SWITCH_CW_PIN, HIGH);
  gs_sc.pSwitchCcw = new Switch(SWITCH_CCW_PIN, HIGH);

  /**
   * モータのセットアップ
   */
  pinMode(MOTOR_PWR_PIN, OUTPUT);
  digitalWrite(MOTOR_PWR_PIN, HIGH);
  gs_sc.pMotor = new Motor(MOTOR_CW_PIN, MOTOR_CCW_PIN, PWM_BIT_NUM);

  /**
   * LEDのセットアップ
   */
  gs_sc.pLed = new Led(LED_PIN, HIGH, LOW);
 
  /**
   * BLE Peripheralのセットアップ
   */
  gs_sc.pBle = new BlePeripheral(deviceName, UUID_SERVICE, BLE_OnConnectCallback, NULL);

  /* 制御指示のCharacteristic */
  gs_msg_param.value[0] = 0x00;
  gs_msg_param.value[1] = 0x00;
  gs_msg_param.value[2] = 0x00;
  gs_msg_param.value[3] = 0x00;
  gs_sc.pBle->SetupChara(gs_msg_param, NULL, BLE_WriteMsgCallback);

  /* 動作状態のCharacteristic */
  gs_state_param.value[0] = 0x00;
  gs_state_param.value[1] = 0x00;
  gs_sc.pBle->SetupChara(gs_state_param, NULL, NULL);

  /* 予約のCharacteristic */
  gs_time_param.value[0] = 0xFF;
  gs_time_param.value[1] = 0xFF;
  gs_time_param.value[2] = 0xFF;
  gs_time_param.value[3] = 0xFF;
  gs_sc.pBle->SetupChara(gs_time_param, NULL, NULL);
  gs_sc.pBle->Startup();

  /**
   * 現在時刻を取得してWi-Fi切断
   */
  gs_sc.now_time = WIFI_GetNowTime();

  /**
   * 動作時Notify間隔用100msタイマ設定
   *
   */
  gs_sc.pNotifyTimer = timerBegin(0, getApbFrequency()/1000000, true);
  timerAttachInterrupt(gs_sc.pNotifyTimer, &IsrTimer100ms, true);
  timerAlarmWrite(gs_sc.pNotifyTimer, 100000, true);

  /**
   * 1秒インターバルタイマ設定
   */
  gs_sc.p1secTimer = timerBegin(1, getApbFrequency()/1000000, true);
  timerAttachInterrupt(gs_sc.p1secTimer, &IsrTimer1sec, true);
  timerAlarmWrite(gs_sc.p1secTimer, 1000000, true);
  timerAlarmEnable(gs_sc.p1secTimer);
}

void loop()
{
  /**
   * スイッチによるステータス更新
   */
  if(gs_sc.pSwitchCw->GetState() == SW_ON)
  {
    gs_state_param.value[0] = MANUAL_OPENING;
  }
  else if(gs_sc.pSwitchCcw->GetState() == SW_ON)
  {
    gs_state_param.value[0] = MANUAL_CLOSING;
  }
  else
  {
    /* 自動動作中でない */
    if((gs_state_param.value[0] != AUTO_OPENING)
    && (gs_state_param.value[0] != AUTO_CLOSING))
    {
      gs_state_param.value[0] = STOPPING;
    }
  }

  /**
   * ステータスに応じた動作
   */
  switch(gs_state_param.value[0])
  {
    /* 開動作 */
    case MANUAL_OPENING:
    case AUTO_OPENING:
      OpenCurtain();
      break;

    /* 閉動作 */
    case MANUAL_CLOSING:
    case AUTO_CLOSING:
      CloseCurtain();
      break;

    /* 動作停止 */
    default:
      StopCurtain();
      break;
  }

  /**
   * Notify送信
   */
  if(gs_send_notify_flag == true)
  {
    gs_send_notify_flag = false;
    gs_sc.pBle->SetCharaValue(INDEX_STATE_CHARA, gs_state_param.value, gs_state_param.valueSize);
    gs_sc.pBle->SendNotify(INDEX_STATE_CHARA);
  }

  /**
   * 時刻同期&予約時間チェック(1分毎)
   */
  if(gs_time_sync_flag == true)
  {
    gs_time_sync_flag = false;
    gs_sc.now_time = WIFI_GetNowTime();

    /* 開閉動作していなければ予約時刻チェック */
    if(gs_state_param.value[0] == STOPPING)
    {
      /* 開予約時間 */
      if((gs_sc.now_time.tm_hour == gs_time_param.value[0])
      && (gs_sc.now_time.tm_min == gs_time_param.value[1]))
      {
        gs_state_param.value[0] = AUTO_OPENING;
        OpenCurtain();
      }
      /* 閉予約時間 */
      else if((gs_sc.now_time.tm_hour == gs_time_param.value[2])
      && (gs_sc.now_time.tm_min == gs_time_param.value[3]))
      {
        gs_state_param.value[0] = AUTO_CLOSING;
        CloseCurtain();
      }
    }
  }

  /**
   * 1秒単位処理(デバッグ用)
   */
  if(gs_1sec_flag == true)
  {
    gs_1sec_flag = false;
#ifdef SC_DEBUG   /* デバッグ用処理 */    
    Serial.printf("[%02d:%02d:%02d] ",
                  gs_sc.now_time.tm_hour, gs_sc.now_time.tm_min, gs_sc.now_time.tm_sec);

    /* 回転状態 */
    switch(gs_sc.pMotor->GetRotateState())
    {
      case ROTATE_CW:
        Serial.printf("CURTAIN OPENING  (%03d%%)\n", gs_state_param.value[1]);
        break;
      case ROTATE_CCW:
        Serial.printf("CURTAIN CLOSING  (%03d%%)\n", gs_state_param.value[1]);
        break;
      default:
        Serial.printf("CURTAIN STOPPING (%03d%%)\n", gs_state_param.value[1]);
        break;    
    }
#endif
  }
}

/**
 * BLE接続時のコールバック関数
 */
static void BLE_OnConnectCallback(void)
{
  /* 動作状態をNotify送信 */
  gs_sc.pBle->SetCharaValue(INDEX_STATE_CHARA, gs_state_param.value, gs_state_param.valueSize);
  gs_sc.pBle->SendNotify(INDEX_STATE_CHARA);
  
  /* 予約時間をNotify送信 */
  gs_sc.pBle->SetCharaValue(INDEX_TIME_CHARA, gs_time_param.value, gs_time_param.valueSize);
  gs_sc.pBle->SendNotify(INDEX_TIME_CHARA);
}

/**
 * 制御指示のCharacteristicにWriteされた際のコールバック関数
 *  - 制御指示Valueの値によってその他のValue更新
 */
static void BLE_WriteMsgCallback(uint8_t* value)
{
  for(uint8_t i = 0; i < VALUE_SIZE_MSG_CHARA; i++)
  {
    gs_msg_param.value[i] = value[i];
  }
  
  // 1byte目(Device ID)
  if(gs_msg_param.value[0] != 0x01)
  {
    return;
  }

  // 2byte目(Func ID)
  switch(gs_msg_param.value[1])
  {
    // 開動作命令
    case 0x01:
      /* 手動による動作をしていなければ、開動作を行う */
      if((gs_state_param.value[0] != MANUAL_OPENING) && (gs_state_param.value[0] != MANUAL_CLOSING))
      {
        gs_state_param.value[0] = AUTO_OPENING;
      }
      break;

    // 閉動作命令
    case 0x02:
      /* 手動による動作をしていなければ、閉動作を行う */
      if((gs_state_param.value[0] != MANUAL_OPENING) && (gs_state_param.value[0] != MANUAL_CLOSING))
      {
        gs_state_param.value[0] = AUTO_CLOSING;
      }
      break;

    // 開動作予約
    case 0x03:
      /* 有効時間範囲内であれば予約 */
      if((gs_msg_param.value[2] <= 0x17) && (gs_msg_param.value[3] <= 0x3B))
      {
        gs_time_param.value[0] = gs_msg_param.value[2];
        gs_time_param.value[1] = gs_msg_param.value[3];
      }
      /* 有効時間外であれば無効な時間を設定 */
      else
      {
        gs_time_param.value[0] = 0xFF;
        gs_time_param.value[1] = 0xFF;        
      }
      break;

    // 閉動作予約
    case 0x04:
      /* 有効時間範囲内であれば予約 */
      if((gs_msg_param.value[2] <= 0x17) && (gs_msg_param.value[3] <= 0x3B))
      {
        gs_time_param.value[2] = gs_msg_param.value[2];
        gs_time_param.value[3] = gs_msg_param.value[3];
      }
      /* 有効時間外であれば無効な時間を設定 */
      else
      {
        gs_time_param.value[2] = 0xFF;
        gs_time_param.value[3] = 0xFF;        
      }
      break;

    //停止命令
    default:
      /* 手動による動作をしていなければ、動作停止 */
      if((gs_state_param.value[0] != MANUAL_OPENING) && (gs_state_param.value[0] != MANUAL_CLOSING))
      {
        gs_state_param.value[0] = STOPPING;
      }
      break;
  }
}

/**
 * カーテン開動作
 */
static void OpenCurtain(void)
{
  /* 開く余裕があれば開動作を行う */
  if(gs_state_param.value[1] < 100)
  {
    if(gs_sc.pMotor->GetRotateState() != ROTATE_CW)
    {
      gs_sc.pLed->SetOn();
      gs_sc.pMotor->RotateCW(VALUE_MAX);
      timerAlarmEnable(gs_sc.pNotifyTimer);
    }
  }
  /* 開く余裕がなければ停止 */
  else
  {
    gs_state_param.value[1] = 100;
    StopCurtain();
  }
}

/**
 * カーテン閉動作
 */
static void CloseCurtain(void)
{
  /* 閉じる余裕があれば閉動作を行う */
  if(gs_state_param.value[1] > 0)
  {
    if(gs_sc.pMotor->GetRotateState() != ROTATE_CCW)
    {
      gs_sc.pLed->SetOn();
      gs_sc.pMotor->RotateCCW(VALUE_MAX);
      timerAlarmEnable(gs_sc.pNotifyTimer);
    }
  }
  /* 開く余裕がなければ停止 */
  else
  {
    gs_state_param.value[1] = 0;
    StopCurtain();
  }
}

/**
 * カーテン回転停止
 */
static void StopCurtain(void)
{
  gs_state_param.value[0] = STOPPING;
  gs_sc.pLed->SetOff();
  gs_sc.pMotor->RotateCoast();
}

/**
 * 100msインターバルタイマ割り込みハンドラ(Notify用)
 */
static void IRAM_ATTR IsrTimer100ms(void)
{
  static volatile uint8_t stop_count = 0;

  switch(gs_sc.pMotor->GetRotateState())
  {
    /* 開回転中処理 */
    case ROTATE_CW:
      stop_count = 0;
      gs_state_param.value[1]++;
      if(gs_state_param.value[1] >= 100)
      {
        StopCurtain();
      }
      break;

    /* 閉回転中処理 */
    case ROTATE_CCW:
      stop_count = 0;
      gs_state_param.value[1]--;
      if(gs_state_param.value[1] == 0)
      {
        StopCurtain();
      }
      break;

    /* 停止中 */
    default:
      break;
  }

  /* 開閉動作停止状態であればカウントアップ */
  if(gs_state_param.value[0] == STOPPING)
  {
    stop_count++;
  }

  /* 停止命令から3度まではNotify送信 */
  if(stop_count <= 3)
  {
    gs_send_notify_flag = true;
  }

  /* 停止命令から3度Notify送信したらタイマ停止 */
  if(stop_count >= 3)
  {
    timerAlarmDisable(gs_sc.pNotifyTimer);
  }
}

/**
 * 1秒インターバルタイマ割り込みハンドラ
 */
static void IRAM_ATTR IsrTimer1sec(void)
{
  gs_1sec_flag = true;

  gs_sc.now_time.tm_sec++;

  /* 1分経過でフラグセット */
  if(gs_sc.now_time.tm_sec >= 60)
  {
    gs_time_sync_flag = true;
  }
}
					
#ifndef __SMART_CURTAIN_H__
#define __SMART_CURTAIN_H__

#include "switch_module.h"
#include "motor_module.h"
#include "led_module.h"
#include "ble_peripheral_module.h"
#include "wifi_module.h"

#define SC_DEBUG

/**
 * ピン設定
 */
#define SWITCH_PWR_PIN  (18)    // スイッチの電源出力ピン
#define SWITCH_CW_PIN   (19)    // 正回転させるスイッチの入力ピン
#define SWITCH_CCW_PIN  (5)     // 逆回転させるスイッチの入力ピン

#define MOTOR_PWR_PIN   (32)    // モータドライバの電源出力ピン
#define MOTOR_CW_PIN    (25)    // 正回転のPWM出力ピン
#define MOTOR_CCW_PIN   (26)    // 逆回転のPWM出力ピン

#define LED_PIN         (13)    // LEDの出力ピン

#define PWM_BIT_NUM     (10)    // 分解能
#define VALUE_MAX       (1023)  // モータ制御PWMの最大値

/**
 * BLE設定
 */
#define CHARA_NUM               (3)                 // Characteristic数
#define UUID_SERVICE            (uint16_t)0xFFF0    // ServiceのUUID
#define UUID_MSG_CHARA          (uint16_t)0xFFF1    // 制御指示CharacteristicのUUID
#define UUID_STATE_CHARA        (uint16_t)0xFFF2    // 動作状態CharacteristicのUUID
#define UUID_TIME_CHARA         (uint16_t)0xFFF3    // 予約時間CharacteristicのUUID
#define INDEX_MSG_CHARA         (0)                 // 制御指示Characteristicのindex番号
#define INDEX_STATE_CHARA       (1)                 // 動作状態Characteristicのindex番号
#define INDEX_TIME_CHARA        (2)                 // 予約時間Characteristicのindex番号
#define VALUE_SIZE_MSG_CHARA    (4)                 // 制御指示Characteristicのvalueのバイト数
#define VALUE_SIZE_STATE_CHARA  (2)                 // 動作状態Characteristicのvalueのバイト数
#define VALUE_SIZE_TIME_CHARA   (4)                 // 予約時間Characteristicのvalueのバイト数

/**
 * カーテン動作状態
 */
#define STOPPING          (0x00)                    // 停止状態
#define AUTO_OPENING      (0x01)                    // 自動開動作中
#define AUTO_CLOSING      (0x03)                    // 自動閉動作中
#define MANUAL_OPENING    (0x05)                    // 手動開動作中
#define MANUAL_CLOSING    (0x07)                    // 手動閉動作中

/**
 * スマートカーテン型
 */
typedef struct
{
  BlePeripheral* pBle;        /* BLE Peripheral */
  Switch* pSwitchCw;          /* 正回転用Switch */
  Switch* pSwitchCcw;         /* 逆回転用Switch */
  Motor* pMotor;              /* モータ */
  Led* pLed;                  /* デバッグ用LED */
  wifi_time_t now_time;       /* 現在時刻 */
  hw_timer_t* pNotifyTimer;   /* Notify用タイマ */
  hw_timer_t* p1secTimer;     /* 1secインターバルタイマ */
} SmartCurtain_t;

#endif /* __SMART_CURTAIN_H__ */
					
#include "switch_module.h"

/**
 * コンストラクタ
 *  - 指定ピンを入力に設定
 *  - ONと判断するレベルを指定(HIGH, LOW)
 */
Switch::Switch(uint8_t pin, uint8_t onLevel)
{
  this->pin = pin;
  this->onLevel = onLevel;
  pinMode(this->pin, INPUT);

  return;
}

/**
 * 状態取得
 *  - ON/OFFの状態を取得
 */
SwState_t Switch::GetState(void)
{
  if(digitalRead(this->pin) == this->onLevel)
  {
    return SW_ON;
  }
  else
  {
    return SW_OFF;
  }
}
					
#ifndef __SWITCH_MODULE_H__
#define __SWITCH_MODULE_H__

#include "Arduino.h"

/**
 * Switchの点灯状態
 */
typedef enum
{
  SW_ON  = 0,
  SW_OFF = 1,
} SwState_t;

/**
 * Switchクラス
 */
class Switch
{
  private:
    uint8_t pin;        /* ピン番号 */
    uint8_t onLevel;    /* ONとなるレベル */

  public:
    Switch(uint8_t pin, uint8_t onLevel);
    SwState_t GetState(void);
};

#endif /* __SWITCH_MODULE_H__ */
					
#include "led_module.h"

/**
 * コンストラクタ
 *  - 指定ピンを出力に設定
 *  - 点灯するレベルを指定(HIGH, LOW)
 *  - デフォルト時のレベルを指定(HIGH, LOW)
 */
Led::Led(uint8_t pin, uint8_t onLevel, uint8_t defaultLevel)
{
  this->pin = pin;
  this->onLevel = onLevel;
  pinMode(this->pin, OUTPUT);
  digitalWrite(this->pin, defaultLevel);

  return;
}

/**
 * 状態取得
 *  - High/Lowの状態を取得
 */
ledState_t Led::GetState(void)
{
  /* LEDがONレベル */
  if(digitalRead(this->pin) == onLevel)
  {
    return LED_ON;
  }
  /* LEDがOFFレベル */
  else
  {
    return LED_OFF;
  }
}

/**
 * On設定
 *  ^ LEDをON(点灯)に設定
 */
void Led::SetOn(void)
{
  digitalWrite(this->pin, onLevel);
}

/**
 * Off設定
 *  ^ LEDをOFF(消灯)に設定
 */
void Led::SetOff(void)
{
  digitalWrite(this->pin, !onLevel);
}
					
#ifndef __LED_MODULE_H__
#define __LED_MODULE_H__

#include "Arduino.h"

/**
 * LEDの点灯状態
 */
typedef enum
{
  LED_ON  = 0,
  LED_OFF = 1,
} ledState_t;

/**
 * LEDクラス
 */
class Led
{
  private:
    uint8_t pin;        /* ピン番号 */
    uint8_t onLevel;    /* ONとなるレベル */

  public:
    Led(uint8_t pin, uint8_t onLevel, uint8_t defaultLevel);
    ledState_t GetState(void);
    void SetOn(void);
    void SetOff(void);
};

#endif /* __LED_MODULE_H__ */
					
#include "motor_module.h"

/**
 * コンストラクタ
 *  - 正回転と逆回転のPWM出力ピンを設定
 */
Motor::Motor(uint8_t pinCw, uint8_t pinCcw, uint8_t pwmBit)
{
  this->pinCw = pinCw;
  this->pinCcw = pinCcw;
  this->pwmBit = pwmBit;
  this->pwmValueMax = (0x0001 << this->pwmBit) - 1;
  
  pinMode(this->pinCw, OUTPUT);
  pinMode(this->pinCcw, OUTPUT);

  ledcSetup(CHANNEL_0, PWM_FREQ, pwmBit);
  ledcSetup(CHANNEL_1, PWM_FREQ, pwmBit);
  ledcAttachPin(this->pinCw, CHANNEL_0);
  ledcAttachPin(this->pinCcw, CHANNEL_1);
}

/**
 * 正回転
 *  - 指定のonDutyにて正回転
 */
void Motor::RotateCW(uint32_t onDuty)
{
  if(onDuty > this->pwmValueMax)
  {
    onDuty = this->pwmValueMax;
  }

  ledcWrite(CHANNEL_0, onDuty);
  ledcWrite(CHANNEL_1, 0);
}

/**
 * 逆回転
 *  - 指定のonDutyにて逆回転
 */
void Motor::RotateCCW(uint32_t onDuty)
{
  if(onDuty > this->pwmValueMax)
  {
    onDuty = this->pwmValueMax;
  }

  ledcWrite(CHANNEL_0, 0);
  ledcWrite(CHANNEL_1, onDuty);
}

/**
 * 空転
 *  - モータを空転
 */
void Motor::RotateCoast(void)
{
  ledcWrite(CHANNEL_0, 0);
  ledcWrite(CHANNEL_1, 0);
}

/**
 * 回転状態取得
 *  - 正回転・逆回転・停止を取得
 */
rotateState_t Motor::GetRotateState(void)
{
  uint16_t valueCh0 = ledcRead(CHANNEL_0);
  uint16_t valueCh1 = ledcRead(CHANNEL_1);

  if((valueCh0 != 0) && (valueCh1 == 0))
  {
    return ROTATE_CW;
  }
  else if ((valueCh0 == 0) && (valueCh1 != 0))
  {
    return ROTATE_CCW;
  }
  else
  {
    return ROTATE_STOP;
  }
}
					
#ifndef __MOTOR_MODULE_H__
#define __MOTOR_MODULE_H__

#include "Arduino.h"

/* PWMのチャンネル */
#define CHANNEL_0   (0)
#define CHANNEL_1   (1)

/* PWM設定 */
#define PWM_FREQ    (490)    /* 周波数 */

/* 回転状態 */
typedef enum
{
  ROTATE_CW,
  ROTATE_CCW,
  ROTATE_STOP, 
} rotateState_t;

/**
 * Motorクラス
 */
class Motor
{
  private:
    uint8_t  pinCw;          /* 正回転のPWM出力ピン */
    uint8_t  pinCcw;         /* 正回転のPWM出力ピン */
    uint8_t  pwmBit;         /* PWM分解能 */
    uint16_t pwmValueMax;    /* PWMの最大値 */

  public:
    Motor(uint8_t pinCw, uint8_t pinCcw, uint8_t pwmBit);
    void RotateCW(uint32_t onDuty);
    void RotateCCW(uint32_t onDuty);
    void RotateCoast(void);
    rotateState_t GetRotateState(void);
};

#endif  /* __MOTOR_MODULE_H__ */
					
#include "ble_peripheral_module.h"

using namespace std;

/**
 * Serverのコールバッククラス(ユーザ非公開)
 */
class MyServerCallbacks: public BLEServerCallbacks
{
  private:
    OnConnectCallback pOnConnect;
    OnDisconnectCallback pOnDisconnect;

  public:
    MyServerCallbacks(OnConnectCallback pOnConnect, OnDisconnectCallback pOnDisconnect)
    {
      /* 接続時/切断時のコールバック関数登録 */
      this->pOnConnect = pOnConnect;
      this->pOnDisconnect = pOnDisconnect;
    }

    /**
     * Server接続時のイベント
     */
    void onConnect(BLEServer* pServer)
    {
#ifdef BLE_DEBUG
      printf("BLE Connected.\n");
#endif
      if(this->pOnConnect != NULL)
      {
        this->pOnConnect();
      }
    }

    /**
     * Server切断時のイベント
     */
    void onDisconnect(BLEServer* pServer)
    {
#ifdef BLE_DEBUG
      printf("BLE Disconnected.\n");
#endif
      if(this->pOnDisconnect != NULL)
      {
        this->pOnDisconnect();
      }
    }
};

/**
 * Characteristicのコールバッククラス(ユーザ非公開)
 */
class MyCharaCallbacks: public BLECharacteristicCallbacks
{
  private:
    OnReadCallback pRead;
    OnWriteCallback pWrite;

  public:
    MyCharaCallbacks(OnReadCallback pRead, OnWriteCallback pWrite)
    {
      this->pRead = pRead;
      this->pWrite = pWrite;
    }

    /**
     * Write時のコールバック関数
     */
    void onWrite(BLECharacteristic* pCharacteristic)
    {
      std:string value_str = pCharacteristic->getValue();
      uint8_t value[value_str.length()];
    
      for(uint8_t i = 0; i < value_str.length(); i++)
      {
        value[i] = value_str[i];
      }

#ifdef BLE_DEBUG
      printf("WRITTEN : ");
      for(uint8_t i = 0; i < value_str.length(); i++)
      {
        printf("%02x ", value[i]);
      }
      printf("\n");
#endif
      
      if(this->pWrite != NULL)
      {
        this->pWrite(value);
      }
    }

    /**
     * Read時のコールバック関数
     */
    void onRead(BLECharacteristic* pCharacteristic)
    {
      std:string value_str = pCharacteristic->getValue();
      uint8_t value[value_str.length()];
    
      for(int i = 0; i < value_str.length(); i++)
      {
        value[i] = value_str[i];
      }

#ifdef BLE_DEBUG
      printf("READ : ");
      for(uint8_t i = 0; i < value_str.length(); i++)
      {
        printf("%02x ", value[i]);
      }
      printf("\n");
#endif
      
      if(this->pRead != NULL)
      {
        this->pRead(value);
      }
    }
};

/**
 * BLEモジュールのコンストラクタ
 *  - deviceの生成
 *  - serverの生成
 *  - serviceの生成
 *  - 接続時/切断時のコールバック関数登録
 */
BlePeripheral::BlePeripheral(const char* pDeviceName, uint16_t uuid, OnConnectCallback pOnConnect, OnDisconnectCallback pOnDisconnect)
{
  /* Deviceの生成 */
  BLEDevice::init(pDeviceName);

  /* Serverの生成 */
  this->pServer = BLEDevice::createServer();
  this->pServer->setCallbacks(new MyServerCallbacks(pOnConnect, pOnDisconnect));

  /* Serviceの生成 */
  this->pService = this->pServer->createService(BLEUUID(uuid));

  /* Characteristicの初期化 */
  for(uint8_t i = 0; i < CHARA_MAX_NUM; i++)
  {
    this->pChara[i] = NULL;
  }
};

/**
 * Characteristicのセットアップ
 *  - propertyの設定
 *  - valueの設定
 *  - callbackの設定
 */
void BlePeripheral::SetupChara(chara_param_t param, OnReadCallback pRead, OnWriteCallback pWrite)
{
  /* index番号のパラメータチェック */
  if(param.index > CHARA_MAX_NUM)
  {
    return;
  }

  this->pChara[param.index] = this->pService->createCharacteristic(param.uuid, param.property);
  this->pChara[param.index]->setValue(param.value, param.valueSize);
  this->pChara[param.index]->setCallbacks(new MyCharaCallbacks(pRead, pWrite));
};

/**
 * BLE Peripheralのスタートアップ
 *  - serviceのスタート
 *  - advertigingのスタート
 */
void BlePeripheral::Startup(void)
{
  this->pService->start();
  this->pAdvertising = pServer->getAdvertising();
  this->pAdvertising->start();
};

/**
 * 指定CharacteristicのValueセット
 */
void BlePeripheral::SetCharaValue(uint8_t index, uint8_t* value, uint8_t num)
{
  this->pChara[index]->setValue(value, num);
};

/**
 * 指定CharacteristicのNotify送信
 */
void BlePeripheral::SendNotify(uint8_t index)
{
  std::string value;
  
  this->pChara[index]->notify();

#ifdef BLE_DEBUG
  printf("NOTIFY(%d) : ", index);
  value = this->pChara[index]->getValue();
  for(int i = 0; i < value.length(); i++)
  {
    printf("%02x ", value[i]);
  }
  printf("\n");
#endif
};
					
#ifndef __BLE_PERIPHERAL_MODULE_H__
#define __BLE_PERIPHERAL_MODULE_H__

#include 
#include 
#include 

#define BLE_DEBUG

#define CHARA_MAX_NUM  (10)  /* characteristicの最大数 */

/**
 * Characteristicのパラメータ構造体
 */
typedef struct
{
  uint8_t index;
  uint16_t uuid;
  uint8_t* value;
  uint8_t valueSize;
  uint32_t property;
  
} chara_param_t;

/**
 * BLE Peripheralのコールバック関数型
 */
typedef void (*OnConnectCallback)(void);
typedef void (*OnDisconnectCallback)(void);
typedef void (*OnReadCallback)(uint8_t* value);
typedef void (*OnWriteCallback)(uint8_t* value);

/**
 * BLE Peripheralクラス
 */
class BlePeripheral
{
  private:
    BLEServer* pServer;
    BLEService* pService;
    BLECharacteristic* pChara[CHARA_MAX_NUM];
    BLEAdvertising* pAdvertising;

  public:
    BlePeripheral(const char* pDeviceName, uint16_t uuid, OnConnectCallback pOnConnect, OnDisconnectCallback pOnDisconnect);
    void SetupChara(chara_param_t param, OnReadCallback pRead, OnWriteCallback pWrite);
    void Startup(void);
    void SetCharaValue(uint8_t index, uint8_t* value, uint8_t num);
    void SendNotify(uint8_t index);
};

#endif /* __BLE_PERIPHERAL_MODULE_H__ */
					
#include "wifi_module.h"

/**
 * Wi-Fi設定
 *  - SSIDとパスワードを指定して接続
 */
void WIFI_Setup(const char* ssid, const char* password)
{
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED)
  {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());

  configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeedad.jp");
}

/**
 * 現在時刻取得
 */
wifi_time_t WIFI_GetNowTime(void)
{
  time_t t;
  struct tm* p_tm;
  wifi_time_t retval;

  t = time(NULL);
  p_tm = localtime(&t);
  retval.tm_year = p_tm->tm_year + 1900;
  retval.tm_mon = p_tm->tm_mon + 1;
  retval.tm_mday = p_tm->tm_mday;
  retval.tm_wday =  p_tm->tm_wday;
  retval.tm_hour = p_tm->tm_hour;
  retval.tm_min = p_tm->tm_min;
  retval.tm_sec = p_tm->tm_sec;

  return retval;
}
					
#ifndef __WIFI_MODULE_H__
#define __WIFI_MODULE_H__

#include 
#include 

#define JST   (3600*9)

typedef struct tm wifi_time_t;

void WIFI_Setup(const char* ssid, const char* password);
wifi_time_t WIFI_GetNowTime(void);

#endif /* __WIFI_MODULE_H__ */
					

仕様書から変更した内容

デバッグしていて仕様を変更した箇所や実装しながら考えた追加仕様が以下です。

モータ回転中のNotify送信間隔を1秒から100msに変更

1分に一回(秒針が0秒だと判断したとき)Wi-Fiを介して時刻同期を行い、取得した時間が開閉の予約時間と一致していた際に開閉動作を行う(追加仕様)


さーて、ファームも一段落しまして終わりが見えてきましたね。

今考えれば5月から主に知見がないハードのことから少しづつ積み重ねてきた結果が形になってきてるなーとしみじみ感じます。

次回はやぎ星人が担当してくれているアプリと合わせて結合デバッグやってみます!

ゴールテープが見えてきた我々の泥臭い活動をお楽しみに!笑

ではでは!

コメント

タイトルとURLをコピーしました