iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 18
1
Modern Web

Webduino 不只是物聯網,智慧插座也可以很 Modern~系列 第 18

實戰智慧插座 18 - 用網頁聊天室開關燈 ( 前、後端實作篇 )

上一篇我們已經做出一個基本的網頁聊天室,這篇要來做兩件事情,第一件事情「和燈泡聊天」,第二件事情則是「美化聊天室」。

首先來和燈泡聊天,在這邊我們剛剛做的那個頁面可以完全不用更動,如果忘記剛剛的頁面怎麼做的請參考:實戰智慧插座 17 - 打造網頁聊天室和燈泡聊天 ( 原理篇 ),重點在要設計另外一個頁面給燈泡使用 ( 其實就是你用頁面 A,燈泡用頁面 B 和你聊天,這樣才有聊天室的感覺 )。



撰寫燈泡的聊天程式 ( 網頁前端 )

因為對於燈泡來說,對話的內容不需要顯示出來,只需要在程式內容撰寫對應的邏輯,所以這邊就純粹用 console 印出內容,不過因為我們要透過 Webduino 控制智慧插座,所以一開始在 HTML 的部分要先引入對應的 JavaScript。

<script src="https://www.gstatic.com/firebasejs/3.6.2/firebase.js"></script>
<script src="https://webduino.io/components/webduino-js/dist/webduino-all.min.js"></script>
<script src="https://blockly.webduino.io/webduino-blockly.js"></script>

JavaScript 一開始先對變數做宣告,主要就是初始化 Firebase 資料庫。( 記得要用自己的資料庫呀! )

var firebase;
var led;
var config = {
  databaseURL: "你的資料庫網址"
};

firebase.initializeApp(config);
var database = firebase.database().ref();

然後寫一個獲取時間並且可以自動判斷「如果是個位數就補零」的流程,待會會用到。

function getTime() {
  var date = new Date();
  var h = date.getHours();
  var m = date.getMinutes();
  var s = date.getSeconds();
  if (h < 10) {
    h = '0' + h;
  }
  if (m < 10) {
    m = '0' + m;
  }
  if (s < 10) {
    s = '0' + s;
  }
  var now = h + ':' + m + ':' + s;
  return now;
}

再來就是裝置開發板的程式,這邊先宣告智慧插座是接在板子的 10 號腳上,一開始會出現連線中的提示,然後「當裝置上線之後」,就會觸發 database.push 這一段程式,也就是會發一段「我上線了」的訊息進資料庫,也因為資料庫有變動,有連接資料庫的聊天室就會收到燈泡上線的訊息。

boardReady('evkG', function(board) {
  board.systemReset();
  board.samplingInterval = 250;
  led = getLed(board, 10);

  console.log('裝置連線中...');

  database.push({
    name: '智慧燈泡',
    content: '我上線囉!',
    time: getTime()
  });

  //主程式...
});

在還沒有寫主程式的狀況下,我們讓裝置上線,這時候就會在聊天室裡頭收到智慧燈泡說「我上線囉!」的訊息。

Webduino和燈泡聊天

最後就是主程式,一開始先設定一個 event 的陣列物件,裡面放入收到指定的訊息 ( text ) 後,智慧插座要做的對應行為 ( fn ),還有燈泡要回覆的訊息 ( msg ),接著使用 Firebase 的 .limitToLast(1),來獲取最後一筆訊息,然後內容作出對應的判斷,當然這邊寫的判斷非常簡單 ( 單純判斷有沒有包含文字 ),如果有興趣也可以寫得複雜些。

var event = [
  {
   text:'開燈',
   msg:'燈泡已經打開!',
   fn:function(){led.on();}
  },
  {
   text:'關燈',
   msg:'燈泡已經關起來了!',
   fn:function(){led.off();}
  },
  {
   text:'閃爍',
   msg:'一閃一閃亮晶晶~',
   fn:function(){led.blink(500);}
  }
];

database.limitToLast(1).on('value', function(snapshot) {
  var s = {};
  for(var i in snapshot.val()){
    s = snapshot.val()[i];
    console.log('('+s.time+') '+s.name+' 說:'+s.content);
    for(var j in event){
      if(s.content.indexOf(event[j].text)!=-1){
        event[j].fn();
        database.push({
          name: '智慧燈泡',
          content:event[j].msg,
          time: getTime()
        });
      }
    }     
  }
});

完成之後我們讓裝置上線,在聊天室打「開燈」、「關燈」或「閃爍」,就可以看到燈泡作出對應的變化,而且燈泡也會把當前的狀態回報在聊天室裡面。

Webduino和燈泡聊天
( 程式:http://bin.webduino.io/budiw/2/edit?html,js,output )



撰寫燈泡的聊天程式 ( 網頁後端 )

剛剛我們看到的是網頁前端的做法,雖然很方便但某種程度上也會受限於瀏覽器,如果你會使用 Node.js 的話,我們就可以讓燈泡聊天的程式跑在後端運作,要使用 Node.js 首先必須安裝 Node.js ( https://nodejs.org/en/download/ )。

Webduino和燈泡聊天

基本上現在安裝完成之後就會連同 npm ( node package manager ) 一起安裝,透過 npm 我們就可以安裝對應的 package,打開指令輸入工具,進入開發的目錄,安裝 webduino-jswebduino-blocklyfirebase。( Mac 可能要用 sudo 開頭 )

npm install webduino-js webduino-blockly firebase

安裝好了之後應該會看到你的目錄下多一個 node_modules 的資料夾,裡面包含了所需要的程式內容。

Webduino和燈泡聊天

接著建立一個名為 index.js 的檔案,一開始先 require 對應的 package,後面的程式基本上就把剛剛寫得完全複製貼上就可以了

require("webduino-js");
require("webduino-blockly");
var firebase = require('firebase');

編輯完成存檔之後,在指令輸入 node index,就可以透過 Node.js 操控智慧插座上頭的燈泡,我們在前端網頁的聊天室,也就會看到出現燈泡上線的訊息。

Webduino和燈泡聊天

然後我們就可以開始聊天控制燈泡,不論在燈泡、前端、後端或是資料庫,都可以看到即時的變化。

Webduino和燈泡聊天



美化聊天室

到這邊為止基本上我們已經幾乎完成所有的工作,最後一步就是美化一下我們的聊天室,而美化的工作幾乎全部都在 CSS 身上,為了讓設計的結構更完整,我這邊稍微調整了一下 HTML,目的是「讓輸入框在最底下,而訊息顯示在上方」。

<div id="im">
  <div id="input">
    <div>
      <span>姓名:</span><input id="name"><br/>
      <span>內容:</span><input id="content">
    </div>
    <button id="btn">送出訊息</button>
  </div>
  <div id="show"></div>
</div>

CSS 的部分先看到最外層,設定背景色,以及把對話區域的背景色設為半透明的黑色,同時指定最大的寬度與置中, box-sizing 的目的在於不要讓 padding 影響了實際的寬度。

body,html{
  width:100%;
  height:100%;
  margin:0;
  padding:0;
  background:url(http://www.oxxostudio.tw/firebase-webduino-im/bg2.jpg);
  background-size:cover;
}
#im{
  position:relative;
  margin:0 auto;
  width:100%;
  max-width:600px;
  height:100%;
  box-sizing:border-box;
  background:rgba(0,0,0,.4);
}

接下來這幾段則是輸入框的部分,首先把位置設為 absolute 這樣我們才可以將其固定在最下方 ( 不要用 fixed 因為 fixed 是針對最外層視窗 ),z-index 設為 2 讓輸入框可以在比較高層的位置,接下來的幾個屬性是在編排輸入框的內容位置。

#input{
  position:absolute;
  z-index:2;
  height:90px;
  width:100%;
  left:0;
  bottom:0;
  margin:0;
  padding:15px;
  box-sizing:border-box;
  background:#222;
  color:#fff;
}

#input input, #input span{
  display:inline-block;
  margin:5px 0;
}

#input input{
  width:75%;
  border:none;
  padding:5px;
}

#input span{
  width:10%;
  min-width:50px;
}

#input div{
  width:80%;
  float:left;
}

#input button{
  float:right;
  height:90%;
  width:20%;
  margin:5px 0;
  border:none;
  padding:0;
  background:#369;
  font-size:16px;
  color:#fff;
}

再來看到訊息顯示區域,同樣位置設定為 position,高度直接用 CSS 的計算功能,記得減去最下方輸入框的高度,然後透過 overflow-y 讓 y 軸出現捲軸而不會超過版面,其他的屬性大概都是在設定對話區域的樣式、時間的樣式...等。

#show{
  position:absolute;
  top:0;
  left:0;
  z-index:1;
  width:100%;
  height:calc(100% - 90px);
  overflow-y:scroll;
  padding:20px;
  box-sizing:border-box;
}

#show>div{
  position:relative;
  margin:0 0 20px 0;
  clear:both;
  height:40px;
}

#show>div>div{
  display:inline-block;
}

#show .time{
  position:absolute;
  top:-2px;
  font-size:10px;
  color:#777;
}

#show .name{
  color:#fff;
  vertical-align:middle;
}

#show .content{
  background:rgba(255,255,255,.8);
  padding:10px;
  margin-top:15px;
  margin-left:10px;
  border-radius:5px;
  vertical-align:middle;
}

最後這段的目的在設定捲軸樣式,不過基本上只有支援 webkit 的瀏覽器才支援就是了。

#show::-webkit-scrollbar {
  width:5px;
}
#show::-webkit-scrollbar-track {
  background:rgba(255,255,255,.1);
  border-radius: 5px;
}
#show::-webkit-scrollbar-thumb {
  background:rgba(255,255,255,.2);
  border-radius: 2px;
}

不過只有 CSS 還不夠,因為我們的對話內容是動態產生,而且我還想要讓「自己」發出的對話會有點不同 ( 例如說位置在右邊 ),所以 JavaScript 裡面就動態產生一些 style 套進去,當中 $show.scrollTop($show[0].scrollHeight); 這段比較特別,因為我們之前是用 prepend 把新訊息放在最上面,而這邊適用 append 把新的訊息放在最下面,間接導致輸入完訊息會看不到最新的內容,所以就透過網頁捲軸的操控,讓每次捲軸都能保持在最下方的位置

database.limitToLast(1).on('value', function(snapshot) {
    for(var i in snapshot.val()){
       $show.append('<div class="'+snapshot.val()[i].id+'"><div class="time">'+snapshot.val()[i].time+'</div><div class="name">'+snapshot.val()[i].name+' 說</div><div class="content">'+snapshot.val()[i].content+'</div>');
    }
    $show.scrollTop($show[0].scrollHeight);
    $show.find('.id'+ms+' .name').css({
      'float':'right',
      'padding-top':'12px',
      'color':'#fc0'
    });
    $show.find('.id'+ms+' .content').css({
      'float':'right',
      'margin-right':'10px'
    });
    $show.find('.id'+ms+' .time').css({
      'right':'0',
      'color':'#777'
    });
  });

完成後來實際測試看一下長相,總算是比較有聊天室的樣子了,這時候如果我們打開前端的燈泡網頁,或是剛剛後端的 Node.js 程式,就可以在這個聊天室裡面真正和燈泡聊天囉!

Webduino和燈泡聊天
( 聊天室:http://bin.webduino.io/luxe/1/edit?html,css,js,output )



小結

其實透過 FireBase 來實做一個聊天室非常的簡單,我真正花時間的地方反而是在美化聊天室,當然如果背後邏輯能再多花一點時間優化,就可以做出更多種的變化與應用,不然以現在的例子,你輸入「不要開燈」,燈泡還是會打開喔!XD 畢竟沒有做更複雜的判斷,只是判斷有沒有包含字詞而已。

然後我自己比較喜歡把「燈泡」部分的程式運作在後端,畢竟瀏覽器的程式只要瀏覽器關起來就沒作用了,運行在後端至少就可以 24 不間斷的運作。

最後,我們其實也可以透過 Webduino Blockly 編輯工具 ( https://blockly.webduino.io ) 做出燈泡的聊天主程式,因為線上工具真的是做得非常強大呀!

Webduino和燈泡聊天
( 解答:https://blockly.webduino.io/#-K_PVGorhJTD6vbECkiw )


上一篇
實戰智慧插座 17 - 用網頁聊天室開關燈 ( 聊天室原理篇 )
下一篇
實戰智慧插座 19 - 用 Google 試算表開關燈 ( 儲存資料篇 )
系列文
Webduino 不只是物聯網,智慧插座也可以很 Modern~30

尚未有邦友留言

立即登入留言