2014/06/23

小惡魔溫度感測器 + Xively 圖表.
今天不玩 Arduino. 萊恩大兵以前的長官在 Electric Imp 服務, 趁此機緣, 萊恩大兵討來一片小惡魔開發板來嚐嚐鮮. 
鼠來寶, 數來寶.

反覆讀了幾遍開發文件, 再對照 google 來的文章, 萊恩大兵總算和小惡魔交上了朋友, 能讓它點亮 LED 或做些小事情. XD.

說起來, 這個小惡魔還挺有兩下子的, 它內建 WiFi 模組, 再搭配官方提供的雲端開發環境, 可說天生就具備了無線+遠端工作的能力, 這可是 Arduino 得加上 WiFi shield 再搭配 Processing 才能比擬的. 

功能完善的雲端開發環境

當然, 這只是粗略的比較. 真要開發做產品時, 還得考慮板子有提供的腳位, 運作電壓 (小惡魔吃 3.3V 電壓), 甚至開發語言的不同 (小惡魔用 Squirrel 來開發) 等等.

話說, 萊恩大兵實在孤陋寡聞, 竟不知有 WiFiDuino 這樣的好東西. Arduino 擺脫了實體線的束縳, 能玩的花樣可多了. 期待 WiFiDuino 能集資成功.

既然有無線又能遠端工作, 萊恩大兵覺得, 最適合小惡魔的應用場景, 就是做個家用的偵測裝置, 並把收集來的數據上傳到公開的物聯平台 (例如 Xively 之類的), 然後可以從公司或其它地方直接上網看數據或圖表. 
小惡魔溫度感測器, 走 WiFi 的喔.

來動手吧.

Xively

  1. 先去 申請帳號 (free)
  2. 加一個 Device 與 Channel, 並記下其 API key 與 Feed ID.
    增加一個 Device

    記下 Feed ID 與 API Key, 待會兒會用到

小惡魔 (Electric Imp)


線路與開發環境
  1. 線路很簡單, 就是把 LM35 的訊號腳接到 pin-9, 再連接其 Vs 和 GND 到 pin-3V3 和 pin-GND.
    接上 LM35.
     
  2. 至於開發環境設置, 官網有詳細教學, 萊恩大兵就跳過不講了. 只多講一句話, 他們的 BlinkUp, 真的有簡化小惡魔的初始設定步驟, 是個貼心的好設計.
  3. 它的程式碼分兩段, 一是 device, 是要燒錄進板子裡的軔體, 另一是 agent, 負責與板子溝通或接收板子傳來的數據, 且也是和外界 (internet) 互動的主要程式介面.

Device 程式碼

Device 的程式碼很單純, 就是每隔一段時間去讀取 LM35 的量測值, 然後傳給 Agent.

// Based on TempBug Simple Example Device Code
// 
 
/* GLOBALS and CONSTANTS -----------------------------------------------------*/
const INTERVAL = 600;

/* CLASS AND GLOBAL FUNCTION DEFINITIONS -------------------------------------*/
function getTemp() {
 // schedule the next temperature reading
 imp.wakeup(INTERVAL, getTemp);

 // hardware id is used to separate feeds on Xively, so provide it with the data
 local id = hardware.getdeviceid();
    local data=temperature.read();     // Reading analog output
    local volt=hardware.voltage();     // Reading ADC ref. voltage
    local temp = data*volt/65536*100.0 // Calc. temp (16bits ADC)

 // tempreature can also be returned in Kelvin or Celsius
 local datapoint = {
     "id" : id,
     "temp" : temp
 }
 server.log("Temp: "+datapoint.temp);
 agent.send("data",datapoint);
}

/* REGISTER AGENT CALLBACKS --------------------------------------------------*/

/* RUNTIME BEGINS HERE -------------------------------------------------------*/
// Configure Pins
temperature <- hardware.pin9;
temperature.configure(ANALOG_IN);
getTemp();


Agent 程式碼

Agent 的程式碼, 主要是接收 Device 傳來的溫度數據, 然後再透過 https + JSON 的作法, 把數據傳上 Xively. 程式碼裡面要填入 Xively 的 API key 與 Feed ID.

// Copyright (C) 2014 electric imp, inc.
// TempBug Example Agent Code
// 
// Posts new temperature data to Xively to be graphed
 
/* GLOBALS and CONSTANTS -----------------------------------------------------*/

const XIVELY_API_KEY = "YOUR_API_KEY";
const XIVELY_FEED_ID = "YOUR_FEED_ID";
const XIVELYCHANNEL = "temperature";
Xively <- {};  // this makes a 'namespace'

/* CLASS AND GLOBAL FUNCTION DEFINITIONS -------------------------------------*/

// Xively "library". See https://github.com/electricimp/reference/tree/master/webservices/xively

class Xively.Client {
    ApiKey = null;
    triggers = [];

 constructor(apiKey) {
  this.ApiKey = apiKey;
 }

 /*****************************************
  * method: PUT
  * IN:
  *   feed: a XivelyFeed we are pushing to
  *   ApiKey: Your Xively API Key
  * OUT:
  *   HttpResponse object from Xively
  *   200 and no body is success
  *****************************************/
 function Put(feed){
  local url = "https://api.xively.com/v2/feeds/" + feed.FeedID + ".json";
  local headers = { "X-ApiKey" : ApiKey, "Content-Type":"application/json", "User-Agent" : "Xively-Imp-Lib/1.0" };
  local request = http.put(url, headers, feed.ToJson());

  return request.sendsync();
 }

 /*****************************************
  * method: GET
  * IN:
  *   feed: a XivelyFeed we fulling from
  *   ApiKey: Your Xively API Key
  * OUT:
  *   An updated XivelyFeed object on success
  *   null on failure
  *****************************************/
 function Get(feed){
  local url = "https://api.xively.com/v2/feeds/" + feed.FeedID + ".json";
  local headers = { "X-ApiKey" : ApiKey, "User-Agent" : "xively-Imp-Lib/1.0" };
  local request = http.get(url, headers);
  local response = request.sendsync();
  if(response.statuscode != 200) {
   server.log("error sending message: " + response.body);
   return null;
  }

  local channel = http.jsondecode(response.body);
  for (local i = 0; i < channel.datastreams.len(); i++)
  {
   for (local j = 0; j < feed.Channels.len(); j++)
   {
    if (channel.datastreams[i].id == feed.Channels[j].id)
    {
     feed.Channels[j].current_value = channel.datastreams[i].current_value;
     break;
    }
   }
  }

  return feed;
 }

}
    
class Xively.Feed{
    FeedID = null;
    Channels = null;
    
    constructor(feedID, channels)
    {
        this.FeedID = feedID;
        this.Channels = channels;
    }
    
    function GetFeedID() { return FeedID; }

    function ToJson()
    {
        local json = "{ \"datastreams\": [";
        for (local i = 0; i < this.Channels.len(); i++)
        {
            json += this.Channels[i].ToJson();
            if (i < this.Channels.len() - 1) json += ",";
        }
        json += "] }";
        return json;
    }
}

class Xively.Channel {
    id = null;
    current_value = null;
    
    constructor(_id)
    {
        this.id = _id;
    }
    
    function Set(value) { 
     this.current_value = value; 
    }
    
    function Get() { 
     return this.current_value; 
    }
    
    function ToJson() { 
     return http.jsonencode({id = this.id, current_value = this.current_value }); 
    }
}

function postToXively(data,id) {
    xivelyChannel <- Xively.Channel(XIVELYCHANNEL+id);
    xivelyChannel.Set(data);
    xivelyFeed <- Xively.Feed(XIVELY_FEED_ID, [xivelyChannel]);
    local resp = xivelyClient.Put(xivelyFeed);
    server.log("Posted to Xively: "+data+", got return code: "+resp.statuscode+", msg: "+resp.body);
}

/* REGISTER DEVICE CALLBACKS  ------------------------------------------------*/

device.on("data", function(datapoint) {
    postToXively(datapoint.temp, datapoint.id);
});

/* REGISTER HTTP HANDLER -----------------------------------------------------*/

// This agent does not need an HTTP handler

/* RUNTIME BEGINS HERE -------------------------------------------------------*/

server.log("TempBug Agent Running");

// instantiate our Xively client
xivelyClient <- Xively.Client(XIVELY_API_KEY);
xivelyChannel <- Xively.Channel("temperature");


線路接好, 程式燒錄進去, 插上電池, 把小惡魔溫度感測器放在臥房一角, 讓它記錄溫度去. (注意, 小惡魔溫度感測器在能接收 WiFi 訊號的環境才能正常工作)
就找個角落放著即可.

測量到的溫度圖表. 
半夜有開冷氣, 溫度較低. 
中午的時候接近30度.


檢討

以第一次試作來看, 小惡魔溫度感測器的表現還不錯. 但還是有幾個待改進的地方:


  1. 一顆 2S1P 7.4V 1000mAh 的電池只能供電一天. (每十分鐘抓一次溫度值). 要想辦法拉長使用時間.
    會不會變成吃電怪獸呢?
  2. 小惡魔溫度感測器還沒穿衣服, 只能待在室內. 要找件防水的衣服給它穿, 然後給我出門工作去.
    脫光光(羞).
  3. 只接收溫度數據畫出線圖沒意思, 應該還要能觸發其它事物.
  4. 一點個人感想: 台灣喊物聯網這麼大聲, 竟然連個本土的物聯平台服務都沒有. 這可是物聯網的基礎建設誒.



萊恩大兵再想想看要怎麼做吧.


(2014/9/16, 更新) 這篇文章在臉書上有不少迴響, 擷圖如下.

[萊恩大兵的其它文章]

自動報球速的棒球


CC2540 Bluetooth Low Energy
筆記, CC2540 Bluetooth Low Energy, (1) 開發環境 架設 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (2) 跑第一個範例程式 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (3) SimpleBLEPeripheral 簡單介紹 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (4) 在智慧手機上執行範例程式 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (5) 偵測與發送 iBeacon 訊號 (Bluetooth, CC2540, iBeacon)
實作, iBeacon 發訊器 x 防丟器 (Bluetooth, CC2540, iBeacon)
實作, iBeacon 尋寶遊戲 (Bluetooth, CC2540, iBeacon, iOS app)
實作, BLE + iOS app, 遙控燈泡君 (Bluetooth, CC2540, iOS app)
做實驗, 用 iBeacon 做自動控制的可行性 (Bluetooth, iBeacon, CC2540, Automation, URL Scheme, iOS app)

藍色小鋪一起來做

藍色小鋪一起來做, 用 beacon 控制開關的枱燈
藍色小鋪一起來做, 講解 BLE CC2540 UART 通訊範例程式 (Bluetooth, CC2540, UART)

小惡魔 無線溫度感測器

108 大眼仔
Plot Clock


實作, 電容感應音樂樹

2 意見:

張貼留言