ArduinoでRaspberry piの電源on off

Arduinoとraspberry piのi2c通信(smbus,wire.h)については海外のフォーラム[raspberrypi.org](https://www.raspberrypi.org/forums/viewtopic.php?t=73074&p=526884)を参考にしました。
raspberry　piのi2c導入については割愛します。

#ちょっと説明
+ 前回作成したarduinoの[スリープ、割り込みコード](http://qiita.com/ototo/items/6eb6c313fe7d2586cbc6)を流用します。
+ 設定ファイルはjson形式で保存しておきます。
+ 設定値はEEPROMに書き込みます。このEEPROMは書き込みの上限などもあったりしますので、常に書き込むようなコードにならないように注意です。
+ 車載する為の構成となっています。

##改善点
+ arduinoはすべて割り込みで処理してでメインループ内での処理をやめてもいいかも。
+ arduino側はそろそろウォッチドッグタイマによるハング監視した方がいいかも。
+ ウォッチドッグタイマでの起動時はraspberry pi側から停止許可命令を出すようにした方が都合がいいかも。

#コード
##raspberry　pi側
1、起動時にjson形式の設定ファイルより設定の読み込み
2、arduino側の設定を読み込み
3、設定に差異があれば設定の反映を行う
4、メインループ内にてメッセージのやり取りを行う
5、メインループ内で停止命令を受信したら停止を行う
※たまにi2cでエラーを吐くので試行回数の設定と例外処理をしています。

```python:main.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import smbus
import time
import os
import json

# I2Cバスの取得
bus = smbus.SMBus(1)

# arduinoのアドレス
SLAVE_ADDRESS = 0x04

# コンフィグ終了
CONFIG_END = 0x00

# コンフィグ確認用　0x1*
REQUEST_CONFIG = 0x10
# スリープ時間用
GET_INO_SLEEP = 0x11
# ラズパイのシャットダウン時の待機時間用
GET_PI_SHUT = 0x12
# ラズパイの起動までの待機時間用
GET_PI_ISW_TIME = 0x13

# コンフィグ設定 0x2X
CONFIG_SET = 0x20
# スリープ時間用
SET_INO_SLEEP = 0x21
# ラズパイのシャットダウン時の待機時間用
SET_PI_SHUT = 0x22
# ラズパイの起動後の待機時間用
# ACCがオフの時の復帰時
# ウォッチドッグタイマで復帰した時のラズパイの起動時間
SET_PI_ISW_SLEEP = 0x23

# I2C接続時の試行上限回数
I2C_TRY = 5


# コンフィグファイルからコンフィグの読み込み
def read_conf():
    with open('conf.json', 'r') as f:
        file = f.read()
    return file


# ラズパイから現在のコンフィグを読み込む
def get_config(ino_add, name):
    TRY = I2C_TRY
    while TRY:
        try:
            tmp_conf = bus.read_byte_data(ino_add, name)
            return tmp_conf
        except IOError as e:
            print "get config IO error"
            TRY -= 1
        except:
            print "get config error"
            raise
    if not TRY:
        raise


# コンフィグの書き込み
def set_config(ino_add, cmd1, cmd2, cmd3, cmd4):
    TRY = I2C_TRY
    while TRY:
        try:
            bus.write_i2c_block_data(ino_add, cmd1, [cmd2, cmd3, cmd4])
            time.sleep(4.0)
        except IOError as e:
            print "set config IO error"
            TRY -= 1
        except:
            print "set config error"
            raise
    if not TRY:
        raise


# ACCの入力状態の確認用
def check_state(ino_add):
    TRY = I2C_TRY
    while TRY:
        try:
            reading = int(bus.read_byte_data(ino_add, 0x50))
            print(reading)
            if reading == 5:
                os.system("sudo /sbin/shutdown -h now")
        except IOError as e:
            print "check data IO error"
            TRY -= 1
        except:
            print "check data Unexcepted error"

if __name__ == '__main__':

    # コンフィグの読み込み
    config = json.loads(read_conf())
    set_ino_sleep = int(config["config"]["arduino"]["sleep"])
    set_pi_shut = int(config["config"]["pi"]["shut_wait"])
    set_pi_sleep = int(config["config"]["pi"]["on_sleep_wakeup_time"])

    # 現在の設定状態を確認
    config_ino_sleep = get_config(SLAVE_ADDRESS, GET_INO_SLEEP)
    config_pi_shut = get_config(SLAVE_ADDRESS, GET_PI_SHUT)
    config_pi_sleep = get_config(SLAVE_ADDRESS, GET_PI_ISW_TIME)

    # 変更点があればコンフィグの変更
    if (config_ino_sleep != set_ino_sleep):
        set_config(SLAVE_ADDRESS, SET_INO_SLEEP, set_ino_sleep, 0x00, 0x00)
        print "set conf set_ino_sleep"

    if (config_pi_sleep != set_pi_sleep):
        set_config(SLAVE_ADDRESS, SET_PI_ISW_SLEEP, set_pi_sleep, 0x00, 0x00)
        print "set conf set_pi_sleep"

    if (config_pi_shut != set_pi_shut):
        set_config(SLAVE_ADDRESS, SET_PI_SHUT, set_pi_shut, 0x00, 0x00)
        print "set conf set_pi_shut"

    # メインループ
    while True:
        check_state(SLAVE_ADDRESS)
        time.sleep(1.0)

```

##Arduino側
１、起動時　ACCの入力チェックを行う、onの時、raspberry　piの起動、offの時、そのままsleepへ
2、raspberry　piの起動後は受信したメッセージごとに設定の変更、EEPROMへの書き込み、メッセージの返信を行う
3、ACCの入力が切れ、ウォッチドッグタイマによる起動フラグが立っていなければスリープへ
4、スリープ中は、設定時間に従い定期的にスリープを解除し、raspberry　piの起動を行う
5、スリープ中にACCの入力があれば、raspberry　piを起動する
※最初はEEPROMに何もない状態なので、正常な値が取得出来ませんので、異常な値の時は1を設定するようにしています

```c++:serial_sleep.ino
#include <avr/wdt.h>  // ウォッチドッグタイマライブラリ
#include <avr/sleep.h> // スリープライブラリ
#include <Wire.h> // I2Cライブラリ
#include <EEPROM.h> // EEPROMライブラリ　アドレスは0から511(512)

#define LED_PIN (13) // LEDピン用
#define ACC_IN (2) // 割り込みピン用
#define PI_POWER (12) // ラズパイの電源ONリレー用

const int SLAVE_ADDRESS = 0x04; // I2Cアドレス

const int CONFIG_END=0x00; // ノーマル状態用

// 以下の設定はラズパイと共通
// コンフィグ確認用　0x1*
const int REQUEST_CONFIG=0x10;
// スリープ時間用
const int GET_INO_SLEEP=0x11;
// ラズパイのシャットダウン時の待機時間用
const int GET_PI_SHUT=0x12;
// ラズパイの起動後に電源オフ可能になるまでの待機時間
const int GET_PI_ISW_TIME=0x13;

// コンフィグ設定 0x2X
const int CONFIG_SET=0x20;
//  スリープ時間用
const int SET_INO_SLEEP=0x21;
// ラズパイのシャットダウン時の待機時間用
const int SET_PI_SHUT=0x22;
// ラズパイの起動後の待機時間用
// ACCがオフの時の復帰時
// ウォッチドッグタイマで復帰した時のラズパイの起動時間
const int SET_PI_ISW_SLEEP=0x23;

// スリープ時のインターバルフラグ
volatile bool slp_interval_flg;
// スリープモードフラグ
volatile bool slp_counter_flg=false;
// スリープループフラグ
volatile bool slp_loop_flg=false;
// スリープインターバル用カウンター
volatile int interval_counter;
// ラズパイにACC状態送る為の値
volatile int pi_acc_state;
// 受け取ったモード用メッセージの保持用
volatile int message_state;
// arduinoの状態保持用
volatile int ino_state;

// EEPROMアドレス用
// スリープ時間記録アドレス
const int ino_sleep_addr=0;
// スリープ時間指定用アドレス
const int pi_sleep_wakeup_addr=1;
// ラズパイのシャットダウン待機時間用アドレス
const int pi_shut_addr=2;

// 基本はタイマの4秒×指定回数のスリープとなる
// タイマの4秒はウォッチドッグタイマ設定部分で行っている
// 下記を15で指定の場合は4秒 * 15 = 60秒
const int count_max=15; 
// 上記を15で指定の場合は1分毎で指定可能となる
// 以下三個の項目はEEPROM記録された値が優先される
// 下記でスリープ時間の初期状態（分単位）を指定
volatile int wait_minutes=1; 
// ラズパイのシャットダウン待機時間（10秒単位）の初期設定
volatile int pi_shut_time=3;
// ラズパイの起動後の待機時間（分単位）を指定
volatile int pi_osw_time=1;

// ラズパイの起動後に待機解除時間保存用変数
volatile long onslp_max_time; 
// ラズパイの起動後の時間保存用変数
volatile long onslp_past_time;
// onslp_max_timeのオーバーフロー
volatile bool counter_switch=false;

// ステータスのリセット
void init_value()
{
  slp_interval_flg=false;
  message_state=0x00;
  slp_counter_flg=false;
}

// ピンのセット
void init_pins()
{
  pinMode(LED_PIN,OUTPUT);
  pinMode(PI_POWER,OUTPUT);
  pinMode(ACC_IN, INPUT);
}

// 起動時にACC状態の確認
void init_state()
{
  if (digitalRead(ACC_IN))
  {
    ino_state=0x00;
    slp_interval_flg=false;
  }else
  {
    ino_state=0x03;
    // ACC状態の変更
    pi_acc_state=0x05;
  }
}

// EEPROMからコンフィグを読み込む
void read_rom_config()
{
  wait_minutes=EEPROM.read(ino_sleep_addr);
  if( (wait_minutes <= 0) || (wait_minutes > 250) )
  {
    wait_minutes=1;
  }
  pi_shut_time=EEPROM.read(pi_shut_addr);
  if( (pi_shut_time <= 0) || ( pi_shut_time >250) )
  {
    pi_shut_time=1;
  }
  pi_osw_time=EEPROM.read(pi_sleep_wakeup_addr);
  if( (pi_osw_time <= 0 ) || (pi_osw_time > 250) )
  {
    pi_osw_time=1;
  }
}

// EEPROMにコンフィグを書き込む
// アドレスと値を指定
void set_config(int addr, byte data)
{
  noInterrupts();
  EEPROM.write(addr,data);
  interrupts();
}

// ウォッチドッグタイマの設定
void wdt_set()
{
  wdt_reset();
  cli();
  MCUSR = 0;
  //WDCE WDE を設定
  WDTCSR |= 0b00011000;
  //WDIEの設定  WDIFを4秒単位で指定
  WDTCSR =  0b01000000 | 0b100000;
  sei();
}

// ウォッチドッグタイマの設定解除
void wdt_unset()
{
  wdt_reset();
  cli();
  MCUSR = 0;
  //WDCE WDE の設定
  WDTCSR |= 0b00011000;
  // 状態の初期化
  WDTCSR =  0b00000000;
  sei();
}

// ウォッチドッグタイマで復帰時に呼ばれる
// ここはウォッチドッグタイマで
ISR(WDT_vect)
{
  // スリープカウンターフラグの確認
  if(slp_counter_flg)
  {
    // インターバルカウンターを増やす
    interval_counter++;
    // インターバルカウンターが指定の回数に達していればスリープの終了
    // ウォッチドッグタイマの設定で4秒毎に指定されていれば4秒ごとに呼ばれる
    // count_maxが15でありwait_minutesが1であれば1分立ってれば終了
    if( interval_counter >= (count_max * wait_minutes) )
    {
      // スリープの終了
      slp_counter_flg = false;
    }
  }
}


// ACCの割り込みがあったときにフラグを変更し、起動させる
void wakeUp()
{
  slp_counter_flg = false;
}

// arduinoのスリープ
void sleep()
{
  interval_counter = 0;
  // ウォッチドッグタイマのセット
  wdt_set();
  // スリープモードの設定
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); 
  // ACCの割り込みの設定
  // ACCの割り込みがあればフラグを変更し、強制起動される
  attachInterrupt(0,wakeUp, RISING); 
  // スリープカウンターフラグのセット
  slp_counter_flg=true;
  // スリープカウンターフラグが解除されるまでループ
  // ウォッチドッグタイマでISR(WDT_vect)が呼ばれるのでそこで変更もしくは割り込みがあるまで
  while(slp_counter_flg)
  {
    noInterrupts();  //cli();
    sleep_enable();
    interrupts();    //sei();
    sleep_cpu();  //cpu sleep
    sleep_disable();
  }
  // ウォッチドッグタイマの設定解除
  wdt_unset(); 
  // ACCの割り込み設定の解除
  detachInterrupt(0); 
}



// ラズパイからI2C経由でメッセージの受信
void get_message(int n){
  int cmd[4];
  int x = 0;
  while(Wire.available()) {
    cmd[x] = Wire.read();
    x++;
  }
  if ((cmd[0] >= 16) && (cmd[0] < 32)) // 0x10~0x1F get config
  {
    message_state = cmd[0];
  } 
  else if((cmd[0] >= 32) && (cmd[0] < 47)) //0x20~0x2F set config  
  {
    switch (cmd[0])
    {
      case 0x21: //ino_sleep_time (minutes)
      set_config(ino_sleep_addr, cmd[1]);
      read_rom_config(); //reload config
      break;
      case 0x22: //pi shutdown wait time
      set_config(pi_shut_addr, cmd[1]);
      read_rom_config(); //reload config
      break;
      case 0x23: //pi in sleep wakeup time
      set_config(pi_sleep_wakeup_addr, cmd[1]);
      read_rom_config(); //reload config
      break;
    }
  }
  else if ((cmd[0]==0) && (cmd[3]==120))
  {
    toggle();
  }
  else
  {
  }

  if(cmd[0] == 0x50){
    message_state = cmd[0];
  } 
}

//　ラズパイにメッセージの送信
void send_message(){
  //when get cmd switch
  switch (message_state) {
   case 0x11: //ino_sleep_time (minutes)
   Wire.write(wait_minutes);
   break;
   case 0x12: //pi shutdown wait time
   Wire.write(pi_shut_time);
   break; 
   case 0x13: //pi in sleep wakeup time 
   Wire.write(pi_osw_time);
   break; 
   case 0x50: //
   Wire.write(pi_acc_state); //send 
   break;  
 }
}

// 待機用タイマ関数（秒単位で待機）
void wait_time(int t)
{
  volatile unsigned long now = millis();
  volatile unsigned long out_time = (now + 1000* (unsigned long)t);
  if(now < out_time){
    while(millis()< out_time){}
  }
  // カウンターオーバーフロー対策
  else 
  {
    while(millis() > now){}
    while(millis() < out_time){}
  }
}

// ラズパイの電源ON関数
void pi_wakeup()
{
  digitalWrite(PI_POWER,HIGH);
  digitalWrite(LED_PIN,HIGH);
}



// スリープ時間の確認
void read_time_slp()
{
  onslp_max_time = ( millis()+ 60000 * pi_osw_time );
  onslp_past_time = millis();
  // オーバーフローした場合は処理を変えるのでスイッチフラグを立てる
  if (onslp_max_time > onslp_past_time)
  {
    counter_switch=true;
  }
  else
  {
    counter_switch=false;
  }
}

//test
boolean LEDON = false; 
void toggle(){
  LEDON = !LEDON; //true and false change
  digitalWrite(LED_PIN, LEDON);  
}

// セットアップ
void setup()
{
  // 初期化
  init_value();
  // ピンの初期化
  init_pins();
  // I2C開始
  Wire.begin(SLAVE_ADDRESS);
  // メッセージの受信
  Wire.onReceive(get_message); 
  //メッセージの送信
  Wire.onRequest(send_message); 
  // EEPROMからコンフィグの読み込み
  read_rom_config();
  // 状態初期化、ACCの接続なしならスリープ
  init_state();
  // 少し待機
  delay(1000);
}

// メインループ
void loop()
{
  // 毎回ACCの状態を確認
  int acc = digitalRead(ACC_IN);
  switch(ino_state)
  {
    // 初期状態
    // ラズパイの起動とACC状態を記録、通常の状態へ遷移
    case 0x00:
      pi_wakeup();
      pi_acc_state=0x01;
      ino_state++;
      break;
    // 通常の状態
    case 0x01: 
      // ACCがオフ、スリープインターバルフラグが立ってない
      if( (acc==0) && (!slp_interval_flg) )
      {
        // ラズパイのシャットダウンへ
        ino_state++;
      }
      // スリープインターバルフラグのみ
      // case 0x04:でACCオフだとスリープインターバルフラグが立っている
      else if(slp_interval_flg)
      {
        // カウンターのスイッチフラグの確認
        if(counter_switch)
        {
          // 通常の処理
          // 現在の時間がonslp_max_time以上もしくはオーバーフローしてしまいonslp_past_time以下であればインターバルフラグを解除
          if((millis() > onslp_max_time) || (millis() < onslp_past_time))
          {
            slp_interval_flg = false;
          }
        }
       // オーバーフロー後の処理
       // 現在の時間がonslp_past_time以下でありonslp_max_time以上であればインターバルフラグを解除
        else
        {
          if( (millis() < onslp_past_time) && (millis() > onslp_max_time) )
          {
            slp_interval_flg = false;
          }
        }
      }
      break;

    // ラズパイのシャットダウン
    case 0x02: 
      ino_state++;
      // ACCの状態変数の値を変更
      pi_acc_state=0x05; 
      // シャットダウンコマンドが実行されるので待機
      wait_time(pi_shut_time * 10);
      // ラズパイのパワーオフ
      digitalWrite(PI_POWER,LOW); 
      digitalWrite(LED_PIN,LOW); 
      // リレーのオフ後ラズパイの電源がゼロになるまで適当に待機させる
      wait_time(pi_shut_time * 10);
      break;

    // arduinoのスリープ
    case 0x03: 
      sleep();
      ino_state++;
      break;

    // スリープから復帰後
    // スリープインターバルのチェックとACCの状態確認
    case 0x04:
      slp_interval_flg=true;
      // ACCオフならread_time_slp();を実行して、指定の時間はラズパイをシャットダウンせずに待機させる
      if(acc==0)
      {
        read_time_slp();
      }
      // ACCがONであれば通常通り復帰
      else
      {
        slp_interval_flg=false;
      }
      // arduino状態を初期
      ino_state=0x00;
      break;
  }
}
```

#設定ファイル
+ "pi":"shut_wait" => raspberry　piに停止命令を送る準備をしてからの待機時間(×10秒)
+ "pi":"on_sleep_wakeup_time" => arduinoのスリープ中にウォッチドッグタイマで起動させる間隔(分)
+ "arduino":"sleep" => ACC入力がない時にウォッチドッグタイマで起動した後、raspberry　piを停止するまでの時間(分)

```json:conf.json
{"config": {"pi": {"shut_wait": "3", "on_sleep_wakeup_time": "1"}, "arduino": {"sleep": "1"}}}
```

#回路図
+ 作成後に作った回路図なので、見逃しあるかもです。
+ ACC入力の所でチャタリング対策をしないとスリープからすぐに抜け出して、またスリープといった動作をしていまいました。
+ raspberry　piの電源が入る時に電源が安定せず、リセットがかかってしまう為、arduino側の電源にダイオードとコンデンサをつけています。

※水魚堂の回路図エディタを使用
![p power.png](https://qiita-image-store.s3.amazonaws.com/0/133915/2d51a4f7-118d-11d3-ea31-ce762478c434.png)