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

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

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など温度調節できる方法がないか調べてみます。