1. 程式人生 > 其它 >從零開始的DIY智慧澆水應用

從零開始的DIY智慧澆水應用

前言

作為一個新世紀打工人,平常也會去養一些花草,來給我的房間增加點綠色和活力,但是常常因為工作忙而忘記一些事情。,畢竟我大部分的時間都是陪伴著電腦的(嚴正宣告:我不是個單身狗!!!

(¬◡¬)✧),之前在淘寶上買了個土壤溼度感測器澆水裝置,自己改造了一下,通過 WiFi 和 SDDC 成功接入了愛智盒子,現在準備搞一個愛智應用來進行裝置的控制;

場景演示

先看一下我改造好的成品吧ヾ(๑╹◡╹)ノ",直接放在了我房間的展示櫃上開始用了。

這是澆水器!

這是插進去的土壤溼度感測器的屁股!

(σ゚∀゚)σ..:*☆ 哎喲是不是很厲害啊!

這個花盆裡面我插了之前的土壤溼度感測器和澆水器的出水口,然後澆水器的入水口我放在了一個裝水的瓶子裡,這樣一個自動澆水的東西就完成了。因為這是我自己室內用的小裝置,規模比較小,有想法的兄弟擴充套件擴充套件可以搞到在農業澆灌這方面去試試,想想還有點小興奮呢。ヾ(>∀<

)(ノ∀`●)⊃

應用演示

話不多說 ─=≡Σ(((つ•̀ω•́)つ,還是直接先看一下我的應用頁面長什麼樣子,

一如既往的放了一個數據顯示面板,一個裝置選擇模組,一個裝置引數設定模組。

使用起來也是非常的簡單,直接選擇一下土壤溼度感測器以及澆水裝置,拖動兩個遊標,設定澆水的適宜範圍,每當我花盆裡面的土壤溼度低於60%的時候就會通過 Spirit 向澆水器傳送加水訊號並開始加水,溼度達到85%就停止澆水,這樣一個智慧澆水的場景就完成了。

整體實現邏輯是比較簡單的,後面的話我也會逐步的在網上買一些其他的裝置,搭建一些比較完整的智慧場景出來。

程式碼分析

關於實現的程式碼也是利用了之前的裝置模組 device

,前端的我感覺就不用擺出來顯擺了,比較簡單的一個頁面沒啥好講的,

只貼一下後端js部分的關鍵程式碼吧<( ̄ ﹌  ̄)@m 。

// 因為用的頻率比較高,個人比較懶,所以就基於正常的邏輯封裝了一下,基本可以滿足我目前乃至後面的大部分需求了,其他個別需求遇到再擴充。
// device_manager.js 
const Device = require('device');
const EventEmitter = require('events');

class DeviceManager extends EventEmitter {
  constructor() {
    super();
    this.devMap = new Map();
    this.controllerMap = new Map();
    this.init();
  }

  init() {
    // 獲取當前所有已加入網路的線上裝置!
    Device.list(true, (error, list) => {
      if (error) {
        console.error('Device.list error!' + error);
      } else {
        list.forEach((item) => {
          Device.info(item.devid, (error, info) => {
            if (error) {
              console.error('Device.info error!' + error);
            } else {
              this.devMap.set(item.devid, {
                devid: item.devid,
                alias: info.alias,
                report: info.report
              });
            }
          });
        });
      }
    });
    Device.on('join', async (devid, info) => {
      const dev = { devid, ...info };
      this.devMap.set(devid, dev);
      this.emit('join', dev);
    });
    Device.on('lost', (devid) => {
      const dev = this.devMap.get(devid);
      this.devMap.delete(devid);
      if (this.controllerMap.has(devid)) {
        this.controllerMap.delete(devid);
      }
      if (!dev) {
        this.emit('error', '應用出現未知錯誤,請退出重試!');
      } else {
        this.emit('lost', dev);
      }
    });
  }

  // 構建裝置控制物件
  generateController(devid) {
    if (this.controllerMap.has(devid)) {
      return Promise.resolve(this.controllerMap.get(devid))
    }
    const controller = new Device();
    return new Promise((resolve, reject) => {
      controller.request(devid, (error) => {
        if (error) {
          reject(error);
        } else {
          this.controllerMap.set(devid, controller);
          resolve(controller);
        }
      });
    })
  }

  // 刪除控制器
  deleteController(devid) {
    this.controllerMap.delete(devid);
  }

  // 傳送裝置訊息
  sendDeviceInfo(devid, data) {
    const controller = this.controllerMap.get(devid);
    if (!controller) {
      return Promise.reject('程式出現未知錯誤,請退出重試!')
    }
    return new Promise((resolve, reject) => {
      controller.send(data, (err) => {
        if (err) {
          reject('控制裝置失敗,請重試!')
        } else {
          resolve();
        }
      }, 3)
    })
  }
}

const devManager = new DeviceManager();

module.exports = {
  devManager
}

以上就是封裝的裝置管理模組了,接下來在main.js中就會去使用該模組中的相關方法;

// main.js

...

function generateDevController(devid) {
	return new Promise((resolve, reject) => {
		const dev = devManager.devMap.get(devid);
		devManager.generateController(devid).then((controller) => {
			controller.on('message', (data) => {
				const points = humidity_water_scene.settings.points;
				if (isSceneDev(devid) && getDeviceType(dev) === 'humidity') {
					socketIO.emit('humidity', Number(data.data.soil_humidity.toFixed(1))); // 0-100
					if (!waterDev) {
						return;
					}
					if (data.data.soil_humidity < points[0] && !watering) {
						startWater(); // 澆水
					} else if (data.data.soil_humidity >= points[0] && watering) {
						stopWater(); // 停水
					}
				} else if (getDeviceType(dev) === 'water') {
					if (data.data.watering === 'ON' && watering && t === 0 && humidityDev) {
						setHumidityTimer(1000);
					} else if (data.data.watering === 'OFF' && !watering && t) {
						clearInterval(t);
						t = 0
					}
				}
			});
			resolve(controller);
		}).catch(() => {
			reject(`應用缺少控制${dev.alias}的許可權!`);
		})
	})
}


...

上面的程式碼呼叫封裝的裝置管理模組例項的generateController方法來構造裝置控制物件,

可以看到在裝置訊息監聽事件 message 中如果溼度感應裝置返回的資料值小於我們指定的值就會呼叫startWater方法進行澆水,否則就會停止澆水;

同樣澆水裝置會根據控制澆水開關命令,來返回當前裝置狀態,如果澆水裝置處於執行中,則會主動加快獲取溼度感應器的實時數值,便於實時控制裝置。

總結

整體實現沒有太大的問題,某些細節方面可能還需要優化一下,不過現在已經開始用了,當然肯定不會直接接到水龍頭上,不然程式要是出bug了,那代價就有點太大了( uTェTu )!

今天的分享到此就結束了,具體的詳細程式碼可以去靈感桌面的祕密寶庫裡面去檢視,不說了,言盡於此,睡了。