読者です 読者をやめる 読者になる 読者になる

画像処理とか機械学習とか

画像処理や機械学習関連の事について気まぐれで書いていきます。歩行者検出関係が多いと思います。ハリネズミもたまに出現します。

ArduinoにLCDディスプレイを付けて文字を表示させる

温湿度センサーと一緒にAmazonで購入していたArduino用のディスプレイモジュールが届いたので使ってみました。

上の画像のような商品で、Arduinoに上から被せるようにつけるだけで使用可能です。
実際につけた画像がこちらです。
f:id:hiro2o2:20160410195119j:plain

一つ問題がありまして、今まではピンを刺すだけでArduinoと接続できたものが、このモジュールを使う場合はハンダ付けする必要があります。
今回は、ハンダ付けするのが面倒だったので、ピンを接触させて抜けないように固定しているだけです。

このLCDモジュールで使用するピン番号は以下の通りです。

Analog 0 :キー(select, up, right, down and left)
Digital 4 :DB4
Digital 5 :DB5
Digital 6 :DB6
Digital 7 :DB7
Digital 8 :RS (Data or Signal Display Selection)
Digital 9 :Enable

これ以外のピンがLCDモジュールの穴の開いている部分に割り当てられています。
その穴にジャンパワイヤのオス側を入れて抜けないように固定するか、ハンダ付けすれば使えると思います。

また、文字の出力にはライブラリを使うと簡単に表示することが出来ます。
実際に表示したものがこちらです。
f:id:hiro2o2:20160410201102j:plain

今回使用したDHT11のセンサから取得した値をLCDディスプレイモジュールに出力させるコードを以下に載せます。コメントが英語になっている部分はドキュメントのサンプルコードをそのまま使用しています。

#include <DHT.h>
#include <LiquidCrystal.h>

#define DHTPIN 2
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

float temp_c = 0;  // 摂氏値( ℃ )
int red_led = 12;
int blue_led = 13;
int sum = 0;
float value1 = 0;
int data = 0;

// LCD panelで使用するピン番号の選択
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int lcd_key     = 0;
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
#define BACKLIGHT 10

int read_LCD_buttons()
{
 adc_key_in = analogRead(0);      // read the value from the sensor 
 // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
 // we add approx 50 to those values and check to see if we are close
 if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
 // For V1.1 us this threshold
 if (adc_key_in < 50)   return btnRIGHT;  
 if (adc_key_in < 250)  return btnUP; 
 if (adc_key_in < 450)  return btnDOWN; 
 if (adc_key_in < 650)  return btnLEFT; 
 if (adc_key_in < 850)  return btnSELECT;  

 return btnNONE;  // when all others fail, return this...
}

void setup(){
  Serial.begin(9600);  // シリアル通信速度
  pinMode ( red_led, OUTPUT);
  pinMode ( blue_led, OUTPUT);
  dht.begin();
 lcd.begin(16, 2);              // start the library

}

void loop(){

 lcd_key = read_LCD_buttons();  // read the buttons

 switch (lcd_key)               // depending on which button was pushed, we perform an action
 {
   case btnRIGHT:
     {
     break;
     }
   case btnLEFT:
     {
     break;
     }
   case btnUP:
     {
     analogWrite(BACKLIGHT, 255/4);
     break;
     }
   case btnDOWN:
     {
     break;
     }
   case btnSELECT:
     {
     break;
     }
     case btnNONE:
     {
  //バックライトを少し暗めに設定
     analogWrite(BACKLIGHT, 64/4); 
     break;
     }
 }
 
  // アナログピンから計測値を取得(0〜203)
  //センサーの値を50回読み取り平均化する
 sum = 0;
 for (int i = 0; i < 50; i++) {
 data = analogRead(1);
 sum = sum + data;
 delay(2);
 }
  value1 = sum / 50;
  
  // 入力値を摂氏に換算
  temp_c = ((5 * value1) / 1024) * 100;
  Serial.println( temp_c );

 //温湿度センサから値を取得
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    lcd.setCursor(0,0);
    lcd.print("Can't get data!");
    return;
  }

 //文字のクリア
  lcd.clear();
  Serial.println(h);
  lcd.setCursor(0,1);
  lcd.print("Humidity:");
  lcd.setCursor(9,1);
  lcd.print(h);
  lcd.setCursor(13,1);
  lcd.print("\x25");

 lcd.setCursor(0,0);
 lcd.print("Temp:"); 
  Serial.println(t);
  lcd.setCursor(5,0);
  lcd.print(t);
  lcd.setCursor(9,0);
  lcd.print("\xdf");
  lcd.setCursor(10,0);
  lcd.print("C");

  
  if(t > 20 && t < 30){
     // LEDを点灯 ( HIGHは電圧レベル )
    digitalWrite( blue_led, HIGH ); 
    digitalWrite( red_led, LOW );
  }else{
    digitalWrite( red_led, HIGH );
    digitalWrite( blue_led, LOW ); 
  }
  
  
  // 1000ms待機
  delay(10000);
}

ハリネズミウォッチの完成

前回に引き続きハリネズミ管理システムの温度湿度表示Pebbleアプリを開発していきます。前回まででほぼ完成しているので、時計の表示とアニメーションの追加を行って完成とします。
実際に開発したアプリはこのようになりました。

youtu.be

前回までのと比較して変更した部分は

  1. 時計のラベルの追加、一秒ごとに時間取得をして表示
  2. ハリネズミの画像を一秒ごとに切り替えてアニメーションさせる
  3. iPhoneとのBluetooth通信が切れた際にバイブで知らせる
  4. ステータスにハートを追加(適温かつBluetooth接続中)

の主に4つです。バッテリー状態も取得していますが、表示部分が微妙だったので使ってません。
Githubにソースファイル一式を公開しますので、CloudPebbleでインポートして変更して使用することが出来ます。
もし参考にしてみたい方がいればどうぞ!
GitHub - hiro0202/HariWatch

  • システムの全体像

f:id:hiro2o2:20160405133733j:plain:w300

まずハリネズミのマロンがいます。
この子の温度管理の為のシステム構成が以下の通りです。

f:id:hiro2o2:20160404233426p:plain

PebbleからRaspberry piサーバーへ一定間隔でGetリクエストを送ります。
Pebble→iPhone→家のルーター→Raspberrypiという順番にリクエストが送られていき、温度・湿度の情報はJSON形式でその逆に送られていきます。
温度はArduinoにつながっているセンサーから取得し、Raspberry piとArduinoはUSBのシリアル通信で温度・湿度の情報を受け渡ししています。
部屋に居る時に視覚的にすぐに適温かチェックできるようにArduinoでは適温の際に青いLED、それ以外は赤いLEDを点灯させます。
この様な感じで開発しました。

ソースコードは以下の通りです。

#include "pebble.h"

static Window *s_main_window;

static TextLayer *s_temperature_layer;
static TextLayer *s_city_layer;
static TextLayer *s_temperature2_layer;
static TextLayer *text1_layer;
static TextLayer *text2_layer;
static TextLayer *s_time_layer, *s_date_layer;
static BitmapLayer *s_icon_layer;
static BitmapLayer *onpu_layer;
static GBitmap *s_icon_bitmap = NULL;
static GBitmap *onpu_bitmap = NULL;
int counter = 0;
bool isConnect = true;

//バッテリー表示レイヤー
static TextLayer *s_bat_layer;
//Bluetoothステータス
static char bt_status[4] = "O";
//過去のBluetoothステータス
static int bt_st_old = 1;

//バッテリーハンドラ
static void battery_handler(BatteryChargeState new_state) {
   //バッテリー状態取得して表示(ついでにBluetooth接続状態を表示)
  static char s_battery_buffer[32];
  snprintf(s_battery_buffer, sizeof(s_battery_buffer), "%d%%", new_state.charge_percent);
  //text_layer_set_text(s_bat_layer, s_battery_buffer);
}
//Bluetoothハンドラ
void bt_handler(bool connected) {
  if (connected) {
      //接続に変化したらバイブ
      if(bt_st_old == 0)
         vibes_short_pulse();
      bt_st_old = 1;
      snprintf(bt_status,4,"O");
      isConnect = true;
   } else {
      //切断に変化したらバイブ
      if(bt_st_old == 1)
         vibes_short_pulse();
      bt_st_old = 0;
      snprintf(bt_status,4,"-");
      isConnect = false;
  }
   //現在のバッテリーを読む(BT表示)
   battery_handler(battery_state_service_peek());
}


enum WeatherKey {
  WEATHER_ICON_KEY = 0x0,         // TUPLE_INT
  WEATHER_TEMPERATURE_KEY = 0x1,  // TUPLE_CSTRING
  WEATHER_CITY_KEY = 0x2,         // TUPLE_CSTRING
  WEATHER_HUMID_KEY = 0x3,  // TUPLE_CSTRING
};

static AppSync s_sync;
static uint8_t s_sync_buffer[64];


static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) {
  APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Sync Error: %d", app_message_error);
}

static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) {
  
  switch (key) {

    case WEATHER_TEMPERATURE_KEY:
       printf("temp\n");
      // App Sync keeps new_tuple in s_sync_buffer, so we may use it directly
      printf("len = %d\n",strlen(new_tuple->value->cstring));
      if(strlen(new_tuple->value->cstring) > 2){
        text_layer_set_text(s_temperature_layer, new_tuple->value->cstring);
      }
      break;

    case WEATHER_CITY_KEY:
      printf("temp2\n");
    if(strlen(new_tuple->value->cstring) > 2){
      text_layer_set_text(s_city_layer, new_tuple->value->cstring);
    
     if (onpu_bitmap) {
        gbitmap_destroy(onpu_bitmap);
      }
      if(atoi(new_tuple->value->cstring) > 18){
        if(isConnect){
          onpu_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_HEART);
        }else{
          onpu_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ONPU);
        }
      }else{
        onpu_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_IKARI);
      }
      bitmap_layer_set_compositing_mode(onpu_layer,GCompOpSet);
      bitmap_layer_set_bitmap(onpu_layer, onpu_bitmap);
    }
      break;
    
    case WEATHER_HUMID_KEY:
      printf("humid\n");
    if(strlen(new_tuple->value->cstring) > 2){
      text_layer_set_text(s_temperature2_layer, new_tuple->value->cstring);
    }
      break;
    default :
      break;
  }
}

static void request_weather(void) {
  DictionaryIterator *iter;
  app_message_outbox_begin(&iter);

  if (!iter) {
    // Error creating outbound message
    return;
  }

  int value = 1;
  dict_write_int(iter, 1, &value, sizeof(int), true);
  dict_write_end(iter);

  app_message_outbox_send();
}

//-----------------------------------------------------------
static void update_time() {
  // Get a tm structure
  time_t temp = time(NULL); 
  struct tm *tick_time = localtime(&temp);
 
  // Create a long-lived buffer
  static char buffer[] = "00:00";
 
  // Write the current hours and minutes into the buffer
  if(clock_is_24h_style() == true) {
    // Use 24 hour format
    strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
  } else {
    // Use 12 hour format
    strftime(buffer, sizeof("00:00"), "%I:%M", tick_time);
  }
 
  // Display this time on the TextLayer
  text_layer_set_text(s_time_layer, buffer);
  
   // Copy date into buffer from tm structure
  static char date_buffer[16];
  strftime(date_buffer, sizeof(date_buffer), "%a %d %b", tick_time);

  // Show the date
  text_layer_set_text(s_date_layer, date_buffer);
  
}


static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
   update_time();
   counter++;
  
      if (s_icon_bitmap) {
        gbitmap_destroy(s_icon_bitmap);
      }
      
      if(counter%2 == 0){
      s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_HEADGEHOG);
      bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet);
      bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap);
      }else{
      s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_HEADGEHOG2);
      bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet);
      bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap);
      }
  
  if(counter%600 == 0){
    request_weather();
    counter = 0;
  }
    
}
//-----------------------------------------------------------


static void window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  //ハリネズミの画像表示用
  s_icon_layer = bitmap_layer_create(GRect(40, 20, 97, 60));
  layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer));
  
  //ステータス画像表示用
  onpu_layer = bitmap_layer_create(GRect(-50, 15, bounds.size.w, 33));
  layer_add_child(window_layer, bitmap_layer_get_layer(onpu_layer));

  text1_layer = text_layer_create(GRect(0, 80, bounds.size.w, 32));
  text_layer_set_text_color(text1_layer, GColorWhite);
  text_layer_set_background_color(text1_layer, GColorClear);
  text_layer_set_font(text1_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(text1_layer, GTextAlignmentCenter);
  text_layer_set_text(text1_layer, "Room       Cage");
  layer_add_child(window_layer, text_layer_get_layer(text1_layer));
  
  s_temperature_layer = text_layer_create(GRect(-30, 90, bounds.size.w, 32));
  text_layer_set_text_color(s_temperature_layer, GColorWhite);
  text_layer_set_background_color(s_temperature_layer, GColorClear);
  text_layer_set_font(s_temperature_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_temperature_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_temperature_layer));

  s_city_layer = text_layer_create(GRect(30, 90, bounds.size.w, 32));
  text_layer_set_text_color(s_city_layer, GColorWhite);
  text_layer_set_background_color(s_city_layer, GColorClear);
  text_layer_set_font(s_city_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_city_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_city_layer));
  
    text2_layer = text_layer_create(GRect(0, 120, bounds.size.w, 32));
  text_layer_set_text_color(text2_layer, GColorWhite);
  text_layer_set_background_color(text2_layer, GColorClear);
  text_layer_set_font(text2_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(text2_layer, GTextAlignmentCenter);
  text_layer_set_text(text2_layer, "Humidity");
  layer_add_child(window_layer, text_layer_get_layer(text2_layer));
  
   s_temperature2_layer = text_layer_create(GRect(0, 130, bounds.size.w, 32));
  text_layer_set_text_color(s_temperature2_layer, GColorWhite);
  text_layer_set_background_color(s_temperature2_layer, GColorClear);
  text_layer_set_font(s_temperature2_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_temperature2_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_temperature2_layer));
  
  s_bat_layer = text_layer_create(GRect(0, 0, bounds.size.w, 32));
  text_layer_set_text_color(s_bat_layer, GColorWhite);
  text_layer_set_background_color(s_bat_layer, GColorClear);
  text_layer_set_font(s_bat_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(s_bat_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_bat_layer));
  
  
  //---------------------------------------------------------------
  // Create time TextLayer
  s_time_layer = text_layer_create(GRect(0, 30, bounds.size.w, 32));
  text_layer_set_background_color(s_time_layer, GColorClear);
  text_layer_set_text_color(s_time_layer, GColorBlack);
  text_layer_set_text(s_time_layer, "00:00");
  text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24));
  text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
 
  // Create date TextLayer
  s_date_layer = text_layer_create(GRect(0, 53, bounds.size.w, 32));
  text_layer_set_text_color(s_date_layer, GColorBlack);
  text_layer_set_background_color(s_date_layer, GColorClear);
  text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
  
  layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
  layer_add_child(window_layer, text_layer_get_layer(s_date_layer));
  //---------------------------------------------------------------

  Tuplet initial_values[] = {
    TupletCString(WEATHER_TEMPERATURE_KEY, "-\u00B0C"),
    TupletCString(WEATHER_CITY_KEY, "-\u00B0C"),
    TupletCString(WEATHER_HUMID_KEY, "-%"),
    TupletInteger(WEATHER_ICON_KEY, (uint8_t) 1),
  };

  app_sync_init(&s_sync, s_sync_buffer, sizeof(s_sync_buffer),
      initial_values, ARRAY_LENGTH(initial_values),
      sync_tuple_changed_callback, sync_error_callback, NULL);
  
 //バッテリー状態サービスに登録する
   battery_state_service_subscribe(battery_handler);
   //現在のバッテリーを読む
   battery_handler(battery_state_service_peek());
   
   //Bluetooth接続状態サービスに登録する
   connection_service_subscribe((ConnectionHandlers){
     .pebble_app_connection_handler = bt_handler
  });
  
  request_weather();
}

static void window_unload(Window *window) {
  if (s_icon_bitmap) {
    gbitmap_destroy(s_icon_bitmap);
  }

  text_layer_destroy(s_city_layer);
  text_layer_destroy(s_temperature_layer);
  text_layer_destroy(s_temperature2_layer);
  text_layer_destroy(text1_layer);
  text_layer_destroy(text2_layer);
  bitmap_layer_destroy(s_icon_layer);
  bitmap_layer_destroy(onpu_layer);
  text_layer_destroy(s_time_layer);
  text_layer_destroy(s_date_layer);
//  text_layer_destory(s_bat_layer);
}

static void init(void) {
  s_main_window = window_create();
  window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorBlack, GColorBlack));
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload
  });
  window_stack_push(s_main_window, true);

  app_message_open(64, 64);
  
    // Register with TickTimerService
tick_timer_service_subscribe(SECOND_UNIT, tick_handler);
  
}

static void deinit(void) {
  window_destroy(s_main_window);

  app_sync_deinit(&s_sync);
}

int main(void) {
  init();
  app_event_loop();
  deinit();
}

Raspberry pi 3をJSONを投げるWEBサーバーとし、スマートウォッチから情報取得を行う

前回は新しい温度センサを使えるようにしました。
室温、ハリネズミのケージ内の温度、湿度が測れるようになったので、この情報を外部から取得できるようにします。

  • Raspberry piから情報を取得する

今回実装した流れとしては、スマートウォッチ(Pebble)からRaspberry piサーバーにGETリクエストを送り、サーバーはJSON形式で情報を返します。スマートウォッチではJSONをパースし情報を表示させます。

使用したプログラム言語
Raspberry piサーバー:Node.js
スマートウォッチ:c言語(画面表示部分),JavaScript(GETリクエスト送信部分)
となります。

  • スマートウォッチ

前々から持っていたPebble Time Roundを使用します。プログラムがC言語で書けるので気軽に書けます。
f:id:hiro2o2:20160331230031p:plain

ディスプレイはカラー電子ペーパーで他の何とかウォッチと違い常時点灯しています。値段が安いのとiPhoneAndroidどちらでも使えるのが良い点です。

前回までに構成した温度管理システムはこのような構成になっています。
f:id:hiro2o2:20160331232548j:plain

  • 実行結果

先に実行結果を載せます。温度が適温の場合は音符マークが表示され、それ以外は怒っているマークが表示されます。
なかなかいい感じに仕上がりました。ハリネズミの画像はフリー素材の物を使用しました。
f:id:hiro2o2:20160331232722j:plain

以下に実際に書いたコードを載せます。

  • サーバー側のプログラム
//httpモジュールをインポート
var http = require('http');
var url = require('url');
var qs = require('querystring');

//Webサーバーの設定
http.createServer(function (req, res) {


    var serialPort = require("serialport")
    var sp = new serialPort.SerialPort("/dev/ttyACM0", {
        baudrate: 9600,
        parser:serialPort.parsers.readline("\n")
    });
    var count = 0;

    if(req.method=='GET') {
        res.writeHead(200, { 'Content-Type': 'application/json' });

        //getリクエストの時
        var url_parts = url.parse(req.url,true);
        console.log(url_parts.query);

        var data1, data2, data3;

        sp.on('data', function(data) {
                count = count + 1;

            if(count == 3){
                data3 = String(data);

                res.end(JSON.stringify({ 'temp1' : data1, 'humid' : data2, 'temp2' : data3}))\
;

            }else if(count < 3){
                if(count == 1)
                    data1 = String(data);
                if(count == 2)
                    data2 = String(data);


            }
        });

    }else{

    res.writeHead(200, {'Content-Type': 'text/plain'});

    sp.on('data', function(data) {
        count = count + 1;

        if(count == 3){
            res.write('CaseTemp : ');
            res.end(String(data));

            return;
        }else if(count < 3){
            if(count == 1)
                res.write('RoomTemp : ');
            if(count == 2)
                res.write('CaseHumid : ');

            res.write(String(data));
        }else{
            //読み込み終了時

        }

    });
    }


}).listen(8180);
//ポート番号はお好みで
console.log('Server running at http://localhost:8180/');

開発環境はCloud Pebbleという開発環境を使います。エミュレータもあるのでデバッグがしやすいのが特徴です。
Pebble側のソースコードは以下のようになりました。サンプルの天気取得のやつをベースに作っているので、変数名などが一部そのままです。

#include "pebble.h"

static Window *s_main_window;

static TextLayer *s_temperature_layer;
static TextLayer *s_city_layer;
static TextLayer *s_temperature2_layer;
static TextLayer *text1_layer;
static TextLayer *text2_layer;
static BitmapLayer *s_icon_layer;
static BitmapLayer *onpu_layer;
static GBitmap *s_icon_bitmap = NULL;
static GBitmap *onpu_bitmap = NULL;

static AppSync s_sync;
static uint8_t s_sync_buffer[64];

enum WeatherKey {
  WEATHER_ICON_KEY = 0x0,         // TUPLE_INT
  WEATHER_TEMPERATURE_KEY = 0x1,  // TUPLE_CSTRING
  WEATHER_CITY_KEY = 0x2,         // TUPLE_CSTRING
  WEATHER_HUMID_KEY = 0x3,  // TUPLE_CSTRING
};

static const uint32_t WEATHER_ICONS[] = {
  RESOURCE_ID_IMAGE_SUN, // 0
  RESOURCE_ID_IMAGE_CLOUD, // 1
  RESOURCE_ID_IMAGE_RAIN, // 2
  RESOURCE_ID_IMAGE_SNOW // 3
};

static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) {
  APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Sync Error: %d", app_message_error);
}

static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) {
  
        if (s_icon_bitmap) {
        gbitmap_destroy(s_icon_bitmap);
      }

      s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_HEADGEHOG);
      bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet);
      bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap);
  

  
  switch (key) {

    case WEATHER_TEMPERATURE_KEY:
    printf("temp\n");
      // App Sync keeps new_tuple in s_sync_buffer, so we may use it directly
      text_layer_set_text(s_temperature_layer, new_tuple->value->cstring);
      break;

    case WEATHER_CITY_KEY:
    printf("temp2\n");
      text_layer_set_text(s_city_layer, new_tuple->value->cstring);
    
     if (onpu_bitmap) {
        gbitmap_destroy(onpu_bitmap);
      }
      if(atoi(new_tuple->value->cstring) > 18){
        onpu_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ONPU);
      }else{
        onpu_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_IKARI);
      }
      bitmap_layer_set_compositing_mode(onpu_layer,GCompOpAssign);
      bitmap_layer_set_bitmap(onpu_layer, onpu_bitmap);
    
      break;
    
    case WEATHER_HUMID_KEY:
      printf("humid\n");
      text_layer_set_text(s_temperature2_layer, new_tuple->value->cstring);
      break;
    
  }
}

static void request_weather(void) {
  DictionaryIterator *iter;
  app_message_outbox_begin(&iter);

  if (!iter) {
    // Error creating outbound message
    return;
  }

  int value = 1;
  dict_write_int(iter, 1, &value, sizeof(int), true);
  dict_write_end(iter);

  app_message_outbox_send();
}

static void window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  s_icon_layer = bitmap_layer_create(GRect(40, 20, 97, 60));
  
  layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer));
  
  
  onpu_layer = bitmap_layer_create(GRect(-50, 15, bounds.size.w, 33));

  layer_add_child(window_layer, bitmap_layer_get_layer(onpu_layer));

  text1_layer = text_layer_create(GRect(0, 80, bounds.size.w, 32));
  text_layer_set_text_color(text1_layer, GColorWhite);
  text_layer_set_background_color(text1_layer, GColorClear);
  text_layer_set_font(text1_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(text1_layer, GTextAlignmentCenter);
  text_layer_set_text(text1_layer, "Room       Cage");
  layer_add_child(window_layer, text_layer_get_layer(text1_layer));
  
  s_temperature_layer = text_layer_create(GRect(-30, 90, bounds.size.w, 32));
  text_layer_set_text_color(s_temperature_layer, GColorWhite);
  text_layer_set_background_color(s_temperature_layer, GColorClear);
  text_layer_set_font(s_temperature_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_temperature_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_temperature_layer));

  s_city_layer = text_layer_create(GRect(30, 90, bounds.size.w, 32));
  text_layer_set_text_color(s_city_layer, GColorWhite);
  text_layer_set_background_color(s_city_layer, GColorClear);
  text_layer_set_font(s_city_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_city_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_city_layer));
  
    text2_layer = text_layer_create(GRect(0, 120, bounds.size.w, 32));
  text_layer_set_text_color(text2_layer, GColorWhite);
  text_layer_set_background_color(text2_layer, GColorClear);
  text_layer_set_font(text2_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(text2_layer, GTextAlignmentCenter);
  text_layer_set_text(text2_layer, "Humidity");
  layer_add_child(window_layer, text_layer_get_layer(text2_layer));
  
   s_temperature2_layer = text_layer_create(GRect(0, 130, bounds.size.w, 32));
  text_layer_set_text_color(s_temperature2_layer, GColorWhite);
  text_layer_set_background_color(s_temperature2_layer, GColorClear);
  text_layer_set_font(s_temperature2_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_temperature2_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_temperature2_layer));

  Tuplet initial_values[] = {
    TupletCString(WEATHER_TEMPERATURE_KEY, "室温\u00B0C"),
    TupletCString(WEATHER_CITY_KEY, "温度\u00B0C"),
    TupletCString(WEATHER_HUMID_KEY, "湿度%"),
    TupletInteger(WEATHER_ICON_KEY, (uint8_t) 1),
  };

  app_sync_init(&s_sync, s_sync_buffer, sizeof(s_sync_buffer),
      initial_values, ARRAY_LENGTH(initial_values),
      sync_tuple_changed_callback, sync_error_callback, NULL);
  

  request_weather();
}

static void window_unload(Window *window) {
  if (s_icon_bitmap) {
    gbitmap_destroy(s_icon_bitmap);
  }

  text_layer_destroy(s_city_layer);
  text_layer_destroy(s_temperature_layer);
  text_layer_destroy(s_temperature2_layer);
  text_layer_destroy(text1_layer);
  text_layer_destroy(text2_layer);
  bitmap_layer_destroy(s_icon_layer);
  bitmap_layer_destroy(onpu_layer);
}

static void init(void) {
  s_main_window = window_create();
  window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorBlack, GColorBlack));
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload
  });
  window_stack_push(s_main_window, true);

  app_message_open(64, 64);
}

static void deinit(void) {
  window_destroy(s_main_window);

  app_sync_deinit(&s_sync);
}

int main(void) {
  init();
  app_event_loop();
  deinit();
}

JavaScript部分

function iconFromWeatherId(weatherId) {
  if (weatherId < 600) {
    return 2;
  } else if (weatherId < 700) {
    return 3;
  } else if (weatherId > 800) {
    return 1;
  } else {
    return 0;
  }
}

function fetchWeather() {
  var req = new XMLHttpRequest();
  req.open('GET', 'http://******', true);
  req.onload = function () {
    if (req.readyState === 4) {
      if (req.status === 200) {
        console.log(req.responseText);
        var response = JSON.parse(req.responseText);
        var temp1 = Math.round(response.temp1);
        var key = iconFromWeatherId(response.humid);
        var temp2 = Math.round(response.temp2);
        var humid = Math.round(response.humid);
        console.log(temp1);
        console.log(key);
        console.log(temp2);
        console.log(humid);
        Pebble.sendAppMessage({
          'WEATHER_ICON_KEY': key,
          'WEATHER_TEMPERATURE_KEY': temp1 + '\xB0C',
          'WEATHER_CITY_KEY': temp2 + '\xB0C',
          'WEATHER_HUMID_KEY':humid + '%'
        });
      } else {
        console.log('Error');
      }
    }
  };
  req.send(null);
}

Pebble.addEventListener('ready', function (e) {
  console.log('connect!' + e.ready);

  fetchWeather();
  console.log(e.type);
});

Pebble.addEventListener('appmessage', function (e) {

  fetchWeather();
  console.log(e.type);
  console.log(e.payload.temperature);
  console.log('message!');
});

Pebble.addEventListener('webviewclosed', function (e) {
  console.log('webview closed');
  console.log(e.type);
  console.log(e.response);
});


次はリモートでエアコンONなど温度調節できる方法がないか調べてみます。

温湿度センサをArduinoで使ってみる

Amazonで300円くらいで売られていたDHT11という温湿度センサが届いたので使用してみます。
f:id:hiro2o2:20160329115758j:plain

見た目はこんな感じで、メスメスのワイヤーとセットで送られてきました。
説明書などは無しです。調べてみるとディジタル出力で温度と湿度を取得できるみたいです。
便利なライブラリがあり、簡単に使用できそうでしたので試してみました。

ライブラリは以下のGithubからダウンロードします。
GitHub - adafruit/DHT-sensor-library: Arduino library for DHT11DHT22, etc Temp & Humidity Sensors

ダウンロードしてきたzipファイルは解凍せずにそのままArduinoIDEからzip形式のライブラリをインストールで先ほどダウンロードしてきたファイルを選択するとOKです。
f:id:hiro2o2:20160329120222p:plain

コードはこちらのサイトを参考にしました。
ArduinoでDHT11デジタル温度センサーをAdafruitライブラリから使う - Qiita

#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

float a_in;          // アナログ入力値(0〜203)
float temp_c = 0;  // 摂氏値( ℃ )
int red_led = 2;
int blue_led = 3;

void setup(){
  Serial.begin(9600);  // シリアル通信速度
  pinMode ( red_led, OUTPUT);
  pinMode ( blue_led, OUTPUT);
  dht.begin();
}

void loop(){

  
  // アナログピンから計測値を取得(0〜203)
  a_in = analogRead(0);
  // 入力値を摂氏に換算
  temp_c = ((5 * a_in) / 1024) * 100;
  // 改行しながら出力
  Serial.print("Temperature: "); 
  Serial.print( temp_c );
  Serial.println(" *C ");
  

  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  Serial.print("Humidity: "); 
  Serial.print(h);
  Serial.println(" %\t");
  Serial.print("Inside Temperature: "); 
  Serial.print(t);
  Serial.println(" *C ");

  
  if(t > 20 && t < 30){
     // LEDを点灯 ( HIGHは電圧レベル )
    digitalWrite( blue_led, HIGH ); 
    digitalWrite( red_led, LOW );
  }else{
    digitalWrite( red_led, HIGH );
    digitalWrite( blue_led, LOW ); 
  }
  
  
  // 1000ms待機
  delay(10000);
}

出力結果
f:id:hiro2o2:20160329120353p:plain

こんな感じですぐにセンサの値が取得できました。
ライブラリが用意してあるので簡単ですね。このセンサはハリネズミのケージ内の温湿度を測るために使おうと思います。

Raspberry piで温度管理システムをバックグラウンドで起動できるようにする

前回の記事でRaspberry piの温度管理システムから、メールが送信できるように変更しました。今回は、前回のPythonのプログラムをバックグラウンドで起動させておく方法を紹介します。

Linuxで何かサービスをバックグラウンドで起動させておくにはデーモンという名前の物を使えばいいみたいです。
Pythonのプログラムをデーモン化するには、python-daemonを使いました。

$sudo apt-get install python-daemon

でインストールしてください。

実際に作成したプログラムを以下に載せます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import serial
import time
import os.path
import datetime
import smtplib
from email import Encoders
from email.Utils import formatdate
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
import daemon.runner


#Google account
ADDRESS = "送信元のアカウント"
PASSWARD = "パスワード"

#SMTP Server
SMTP = "smtp.gmail.com"
PORT = 587

def create_message(from_addr, to_addr, subject, body):

    msg = MIMEMultipart()
    msg["From"] = from_addr
    msg["To"] = to_addr
    msg["Date"] = formatdate()
    msg["Subject"] = subject
    body = MIMEText(body)
    msg.attach(body)

    return msg

def send(from_addr, to_addrs, msg):

    smtpobj = smtplib.SMTP(SMTP, PORT)
    smtpobj.ehlo()
    smtpobj.starttls()
    smtpobj.ehlo()
    smtpobj.login(ADDRESS, PASSWARD)
    smtpobj.sendmail(from_addr, to_addrs, msg.as_string())
    smtpobj.close()



class TempDaemon:

        def __init__(self):
            self.stdin_path = self.stdout_path = self.stderr_path = '/dev/null'
            self.pidfile_timeout = 10
            self.directory = os.path.expanduser('~/.test/')
                os.mkdir(self.directory)
            self.pidfile_path = os.path.join(self.directory, 'test.pid')

        def run(self):

            count = 0;
            ser = serial.Serial('/dev/ttyACM0', 9600)
            time.sleep(3)

            while True:
                count = count + 1
                time.sleep(1)
                data = ser.readline()

                if count > 1:
                    data = float(data)
                    to_addr = "送信先アドレス"

                    if data < 18 or data > 30 :
                        #subject and body
                        subject = "温度警告"
                        body = "現在の温度 " + str(data) + "度です。\n温度調節して下さい。"

                        #create message
                        msg = create_message(ADDRESS, to_addr, subject, body)

                        #send
                       send(ADDRESS, [to_addr], msg)

                    elif count%1440 == 0:
                        #4時間ごとに定期報告
                        subject = "ハリネズミ管理システム"
                        body = time.ctime()+"\n現在の温度 " + str(data) + "度です。"

                        #create message
                        msg = create_message(ADDRESS, to_addr, subject, body)

                        #send
                        send(ADDRESS, [to_addr], msg)



def main():
    daemon_runner = daemon.runner.DaemonRunner(TempDaemon())
    daemon_runner.do_action()

if __name__ == '__main__':
    main()

このプログラムでは、温度が18度を下回るか、30度より高くなった場合にあらかじめ設定しておいたメールアドレスに警告メールを送ります。
また、警告メール以外に4時間おきに温度をメールで報告してくれます。

上記コードの以下の部分がデーモンの本体です。

class TempDaemon:

        def __init__(self):
            self.stdin_path = self.stdout_path = self.stderr_path = '/dev/null'
            self.pidfile_timeout = 10
            self.directory = os.path.expanduser('~/.test/')
                os.mkdir(self.directory)
            self.pidfile_path = os.path.join(self.directory, 'test.pid')

        def run(self):
      while True:
       #何か処理を記述

バックグラウンドで起動させるには
./temp.py start

終了させるには
./temp.py stop

を入力すればオッケーです。
これでとりあえずバックグラウンドでの起動が可能となりました。

Raspberry pi 3とArduinoの連携によるハリネズミ温度管理システムの改良Ver1.1

前回はArduinoを用いた温度の取得とLEDによる警告を行うシステムを作りました。
今回はRaspberry piとシリアル通信によりデータを共有し、もう少し改良を行います。

  • やる事
  1. ArduinoとRaspberry piの連携
  2. Arduinoで取得した温度を投げる
  3. Raspberry pi 側で温度を取得し、温度により警告をメールで飛ばす

前回Arduino側のソースコードを少し変更しました。
↓変更部分

void loop(){
  // アナログピンから計測値を取得(0〜203)
  a_in = analogRead(0);
  // 入力値を摂氏に換算
  temp_c = ((5 * a_in) / 1024) * 100;
  // 改行しながら出力
  Serial.println( temp_c );

シリアル通信でデータを投げる部分は温度だけ投げるように変更しました。
これでRaspberry pi とUSB接続してRaspberry pi 側で取得のプログラムを書けば温度が取得できるはずです。

こちらはPythonを用いてプログラムを書いていきます。
シリアル通信によるデータ取得とメール送信用のプログラムは以下のようになります。

# -*- coding: utf-8 -*-
import serial
import time
import os.path
import datetime
import smtplib
from email import Encoders
from email.Utils import formatdate
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText

#Google account
ADDRESS = "送信用のアドレス"
PASSWARD = "パスワード"

#SMTP Server
SMTP = "smtp.gmail.com"
PORT = 587


def create_message(from_addr, to_addr, subject, body):

msg = MIMEMultipart()
msg["From"] = from_addr
msg["To"] = to_addr
msg["Date"] = formatdate()
msg["Subject"] = subject
body = MIMEText(body)
msg.attach(body)

return msg

def send(from_addr, to_addrs, msg):

smtpobj = smtplib.SMTP(SMTP, PORT)
smtpobj.ehlo()
smtpobj.starttls()
smtpobj.ehlo()
smtpobj.login(ADDRESS, PASSWARD)
smtpobj.sendmail(from_addr, to_addrs, msg.as_string())
smtpobj.close()


if __name__ == '__main__':

count = 0;

ser = serial.Serial('/dev/ttyACM0', 9600)
print(ser.portstr)
time.sleep(3)

while 1:
time.sleep(1)
data = ser.readline()

if count > 0:
data = float(data)
print(time.ctime() + ', ' + str(data))

if data < 20 :

#adress
to_addr = "送信先のアドレス"

#subject and body
subject = "温度警告"
body = "現在の温度 " + str(data) + "度です。\n温度調節して下さい。"

#create message
msg = create_message(ADDRESS, to_addr, subject, body)

#send
send(ADDRESS, [to_addr], msg)

print('send aleart message.\n')

count = count + 1

  • 実行結果

f:id:hiro2o2:20160325173337p:plain

計測した温度が20度を下回った際に警告のメールが送信されるようになりました。

f:id:hiro2o2:20160325173615p:plain

実際に送られてきたメール


  • まとめ

ArduinoとRaspberry piを連携させたシステムを構築した。
Arduinoではセンサーの管理、Raspberry piではメール送信などの仕事をさせた。
何か他に良いアイディアが思いついたら随時追加していきます。

ハリネズミ管理のためのArduinoを使った温度管理システムVer1.0

前回、ハリネズミを見守るためのRaspberry pi 3を用いたネットワークカメラの構築を行いました。こんな感じでiPhoneからも見れます。

f:id:hiro2o2:20160323213535p:plain


今回は、家で眠っていたArduinoを掘り出してきて、ちょっと賢い温度計を作ります。

  • 必要な物

Arduino uno
温度センサ(LM35)
LED(赤と青二つ)
抵抗
ワイヤ
フレットボード
USBケーブルなど

以下のセットで全て手に入ります。

 

家で眠っていて何か使えないかという事が一点と、LM35という温度センサーをraspberry piで使用するためにはAD変換しないと使用できないのがもう一点。
Arduinoはディジタルもアナログもいける子なのでこいつを使います。

 

f:id:hiro2o2:20160323213704j:plain

 配線はこんな感じで行いました。
LM35の温度センサの配線はこんな感じです。

f:id:hiro2o2:20160323213807j:plain

LEDは青と赤を使用し、一定間隔毎に計測した温度を元に、温度が20度より高く30度より低い時はちょうど良い温度なので青色のLEDを点灯させ、それ以外の時は寒すぎるか暑すぎるときなので、赤色のLEDを点灯させます。

ArduinoC言語で書いたソースで制御できるので、手軽に使用できます。
今回のソースコードは以下のようになります。

float a_in;          // アナログ入力値(0〜203)
float temp_c = 0;  // 摂氏値( ℃ )
int red_led = 2;
int blue_led = 3;
void setup(){
  Serial.begin(9600);  // シリアル通信速度
  pinMode ( red_led, OUTPUT);
  pinMode ( blue_led, OUTPUT);
}

void loop(){
  // アナログピンから計測値を取得(0〜203)
  a_in = analogRead(0);
  // 入力値を摂氏に換算
  temp_c = ((5 * a_in) / 1024) * 100;
  // 出力
  Serial.print( temp_c );
  Serial.println('C');

  if(temp_c > 20 && temp_c < 30){
     // 青色LEDを点灯 ( HIGHは電圧レベル )
    digitalWrite( blue_led, HIGH );
    digitalWrite( red_led, LOW );
  }else{
    // 赤色LEDを点灯 ( HIGHは電圧レベル )
    digitalWrite( red_led, HIGH );
    digitalWrite( blue_led, LOW );
  }
 
  // 10s待機
  delay(10000);
}

 

  • 全体図

ArduinoはRaspberry piから給電を行い、動作させます。
後々、部品や環境が整ったらシリアル通信をさせて何かリッチな表示をさせたいと思います。

f:id:hiro2o2:20160323214559j:plain