op.26 《全领域》-全域开发实战 - 居家植物盆栽 Mvt I (NodeMCU & MQTT)

op.26 打造属於你的时空廊道

为你我打造一个专属你的自由往返通道
让你可以任意地穿越
不再受拘束

今天开始来将前几天用到的东西进行实作,预计架构会长这样。
https://ithelp.ithome.com.tw/upload/images/20201011/20129084uszQWipTVD.png

大略地介绍,会经过 NodeMCU 来定时去监测土壤湿度的变化,经由 MQTT 的方式进行回传,那经由 Broker 处理讯息後,将湿度储存至资料库;而手机也可以实际地收到湿度的数值。

今天是一个硬体的实作部分。
需要准备的材料:

NodeMCU
https://ithelp.ithome.com.tw/upload/images/20201011/201290841ovsnniOt7.jpg

土壤湿度感测器
https://ithelp.ithome.com.tw/upload/images/20201011/20129084jna6XySqxw.jpg

从土壤湿度的感测器的参考资料之中,我们可以看到这个模组具有两种输出模式,一种为 类比 输出;另一种是经过类似史密特触发的电路转成 数位 的输出。

既然要监测 湿度 ,那我们就选择这模组的类比输出。

从这张图片我们可以确定可以使用 A0 当输入脚位,模组的 Vcc 接至 3v3,GND 接到 GND。

那我们就需要先透过 NodeMcu 来读取类比电压。

#define Sensor_Pin A0

void setup() {
  Serial.begin(115200);
  pinMode(Sensor_Pin,INPUT);
}

void loop() { 
  Serial.println(analogRead(Sensor_Pin));
  delay(1000);
}

当感测器插入湿润的土里时,数据呈现是这样。
https://ithelp.ithome.com.tw/upload/images/20201011/20129084p0yxQRJyPj.jpg
https://ithelp.ithome.com.tw/upload/images/20201011/20129084nJpf2SXVKr.png

当感测器拔出湿润的土里时,数据呈现是这样。
https://ithelp.ithome.com.tw/upload/images/20201011/20129084gjiasz6yvk.jpg
https://ithelp.ithome.com.tw/upload/images/20201011/20129084jetw3zfuro.png

所以可以知道湿度上升、类比数值下降,接下来就可以准备将这些的资料透过 MQTT 发送罗。


首先还是需要新增新的程序库,这里需要新增 PubSubClient ,一样地可以在 程序库管理员搜寻到
https://ithelp.ithome.com.tw/upload/images/20201011/20129084oYB2JYWWJD.png

然後开启之前的连上网路那篇文章里的程序码
op.07 《感知层》-连上熟悉的 Wi-Fi 吧

由於需要连接 MQTT 的服务器,所以一样也要来撰写 MQTT 的程序,想必写过 Flutter 後,下面的程序会比较好懂吧XD

const char* mqttServer = "MQTT Broker 位置";
const char* mqttUserName = "User01"; 
const char* mqttPwd = "MQTT Broker 密码";
const char* clientID = "ClientID";
const char* topic = "要发布的主题";

另外因为是定时侦测,需要宣告针对侦测相关的变数

unsigned long prevMillis = 0;   // 暂存经过时间(毫秒) 
const long interval = 20000;    // 上传资料的间隔时间,20秒。 
String msgStr = "";             // 暂存MQTT讯息字串

int Potted_num =0;              // 暂存类比输入的数值

最後要宣告两个物件,分别是 WiFiClient 与 PubSubPubSubClient,将 WiFiClient 的参数带入 PubSubPubSubClient。

WiFiClient NodeMcuClient;
PubSubClient client(NodeMcuClient);

由於受限於韧体限制,这个套件并没有将保持 MQTT 连线一直占用,过一定时间即会断线,初始为 15 秒,所以要先来写一个 reconnect 函式,让我们可以再长时间使用时可以进行重连後传送资料。

void reconnect() {
  while (!client.connected()) {
    if (client.connect(clientID, mqttUserName, mqttPwd)) {
      Serial.println("MQTT connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);  // 等5秒之後再重试
    }
  }
}

接下来需要在 setup() 里加入设定 MQTT 服务器的参数

client.setServer(mqttServer, 1883);    //对应 IP 与 连接埠

进到 loop() 的部分,我们必须先在侦测数据前,确认 MQTT 是否连接,如果没有择要进行连接,有的话则让 MQTT 保持连线。

if (!client.connected()) {
    reconnect();
  }
  client.loop();

接着准备进行土壤湿度的收集。

if (millis() - prevMillis > interval) {
    prevMillis = millis();
    Potted_num =analogRead(Sensor_Pin);
  }

当数据收集完成时,便可以透过 MQTT 来发送,所以必须将数值转成 MQTT 的形式发送。

msgStr=String(Potted_num);
byte arrSize = msgStr.length() + 1;
char msg[arrSize];
msgStr.toCharArray(msg, arrSize); // 把String字串转换成字元阵列格式
client.publish(topic, msg);       // 发布MQTT主题与讯息
msgStr = "";

首先需要先将 int 型态转成 String 并存入 msgStr 里;由於 publish() 的讯息格式为 字元阵列,所以需要将字串转成字元阵列,而根据 C 的特性,字串转成字元阵列的长度要加上 1 (因为字元阵列需要一个空间储存结束字元),最後清空暂存字串。

到这边即可以烧录至 NodeMCU里,并且执行之前写的 C# Broker ,正确的执行画面应该会长这样!
https://ithelp.ithome.com.tw/upload/images/20201011/20129084Vv4DtKiO8p.png

好啦今天就到此结束,结束前附上今天完整的程序码:

#include<PubSubClient.h>
#include<ESP8266WiFi.h>

#define Sensor_Pin A0

const char* ssid = "SSID";
const char* password = "password";

const char* mqttServer = "Mqtt Server IP";
const char* mqttUserName = "User01"; 
const char* mqttPwd = "Mqtt Server Password";
const char* clientID = "User01";
const char* topic = "Data";

unsigned long prevMillis = 0;   // 暂存经过时间(毫秒) 
const long interval = 20000;    // 上传资料的间隔时间,20秒。 
String msgStr = "";             // 暂存MQTT讯息字串

int Potted_num =0;

WiFiClient NodeMcuClient;
PubSubClient client(NodeMcuClient);

void setup(){
  Serial.begin(115200);
  pinMode(Sensor_Pin,INPUT);
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println(".");
  }
  Serial.println("WiFi connected");
  client.setServer(mqttServer, 1883);
}

void reconnect() {
  while (!client.connected()) {
    if (client.connect(clientID, mqttUserName, mqttPwd)) {
      Serial.println("MQTT connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);  // 等5秒之後再重试
    }
  }
}

void loop(){
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  if (millis() - prevMillis > interval) {
    prevMillis = millis();
    Potted_num =analogRead(Sensor_Pin);
    
    msgStr=String(Potted_num);
    byte arrSize = msgStr.length() + 1;
    char msg[arrSize];
    msgStr.toCharArray(msg, arrSize); // 把String字串转换成字元阵列格式
    client.publish(topic, msg);       // 发布MQTT主题与讯息
    msgStr = "";
  }
}

参考来源:
MQTT教学(九):使用ESP8266上传资料到ThingSpeak MQTT服务器

今天就到这里啦~明天又要继续上班了呢!明天的部分我们来玩玩 不一样 的吧!

今日的曲子:<<瑶族舞曲>>茅沅、刘铁山/曲 彭修文/改编


<<:  利用 Grafana Operator 部署 Grafana 到 OpenShift,并建立客制化的 Dashboard。

>>:  JavaScript 之旅 (26):String.prototype.replaceAll()

[Tableau Public] day 13:进阶版的表格呈现方式

第13天,表格不就是栏加列吗,还能变出什麽花样? 没错,tableau public 就是可以~呈现...

Day23 设定Alerts

今日我们要来使用Kibana内的警报功能,看如何设定Alert让我们能收到异常的通知。 设定Aler...

追求JS小姊姊系列 Day10 -- 如果时间能重来,我不想跟工具人聊天(下)

前情提要 在D特的帮助下,试着穿越时空,但时间碎片凑齐了却还是出不去,现在该怎办? 我:...所以我...

Vue.js 从零开始:props 元件的沟通

上篇component元件有说到每个元件范围都应该是独立的,更不应该发生子元件直接改变根元件的情况,...

# TypeScript 能手养成之旅 Day 15 介面(Interface)

前言 上一篇结束後已经将型别部分,大致上了解差不多了,铁人文章也来到一半了。今天开始下半部的铁人,会...