闲来无事-夏天防止花被渴死

打印 上一主题 下一主题

主题 875|帖子 875|积分 2635

扯淡时间

前段时间,办了一张流量卡。有了新的手机号码那就可以薅一波资本主义的羊毛了,所以我在京东上使用0.1大洋包邮的价格喜提了一个多肉,(在此之前我养过挺多的花,所有的都是忘了浇水被渴死了)此次痛并思痛,一定要让我0.1大洋的的多肉看到明年的太阳。
思路

养花几乎不用管,只需要两件事

  • 充足的阳光:我现在住的距离太阳还是挺近的,阳光的事情不用担心。
  • 有营养的土和充足的水: 土比较好搞定,去小区的花坛里面扣点就行了,主要就是水,经常想不起来去浇水,所以得搞一个能知道土壤湿度的东西,提醒我浇水。
我的思路如下

  • 收集数据-首先手机花盆里面的土壤湿度
  • 存储数据-将花盆的湿度进行持久化存储
  • 数据展示通知-页面通过读取持久化存储的信息展示出来,并可以设定一个预警值,在一定的规则下面通知到我
思路有了就开干

收集数据

我的这个实现思路也算是传说中的物联网了,毕竟花盆都上网了嘛,现在实现这种的板子有很多,那个便宜来那个就行了。经过百度一下,选择了ESP8266-NodeMCU + 湿度传感器 作为数据收集方式(为啥选择这个呢?因为便宜啊,还自带wifi能上网。硬件真便宜,一共20块搞定,还包邮),esp8266可以使用Arduino进行开发,语法跟c差不多,库啥的自己搜搜吧,我也是自己搜的。俺看的是这个教程
收集数据主要是干两件事:1. 读取湿度,2. 上报数据代码如下
  1. #include <ESP8266WiFi.h>
  2. #include <ESP8266HTTPClient.h>
  3. #include <ArduinoJson.h>
  4. // 读取温度
  5. #define PIN_AO A0  //土壤传感器AO接ESP8266引脚A0
  6. //#define PIN_DO 4  //湿度高于设定值时,DO输出高电平,模块提示灯亮
  7. int M0 = 1024;  //在空气中AO读取的值最大为1024,代表干燥时的读数
  8. int M1 = 164;   //浸泡在水里的最小值 464(最小值会改变),代表100%湿度
  9. float i=0;
  10. float j=0;
  11. #define SERVER_IP "http://局域网ip:3001/sendCurrentTemp"
  12. const char* name = "名字";               //这里改成你的设备当前环境下要连接的接入点名字
  13. const char* password = "密码";  //这里改成你的设备当前环境下要连接的接入点密码
  14. // 上报时间间隔
  15. int outTime = 1000 * 60 * 5;
  16. // int outTime = 1000 * 60;
  17. void setup() {
  18.   // 设置引脚
  19.   pinMode(PIN_AO, INPUT);
  20.   Serial.begin(115200);  // 启动串口通讯,波特率设置为115200
  21.   Serial.println("未连接");
  22.   Serial.println("开始连接");
  23.   WiFi.begin(name, password);
  24.   Serial.print("正在连接到");
  25.   Serial.print(name);
  26.   while (WiFi.status() != WL_CONNECTED)  //判定网络状态
  27.   {
  28.     delay(500);
  29.     Serial.print("网络连接成功");
  30.     Serial.print("连接到的接入点名字:");
  31.     Serial.println(name);  // 告知用户建立的接入点WiFi名
  32.     Serial.print("连接到的接入点密码:");
  33.     Serial.println(password);  // 显示用户建立的接入点WiFi密码
  34.     Serial.print("无线模式成功开启,网络连接成功");
  35.   }
  36.   if (WiFi.status() == WL_CONNECTED) {
  37.     Serial.print("无线IP地址为: ");
  38.     Serial.println(WiFi.localIP());
  39.   }
  40. }
  41. void loop() {
  42.   // put your main code here, to run repeatedly:
  43.   if (WiFi.status() == WL_CONNECTED) {
  44.     http_post();
  45.     WiFi.forceSleepBegin();  // Wifi Off
  46.     delay(outTime);
  47.     WiFi.forceSleepWake();  // Wifi On
  48.   }
  49. }
  50. void http_post() {
  51.   //创建 WiFiClient 实例化对象
  52.   WiFiClient client;
  53.   //创建http对象
  54.   HTTPClient http;
  55.   //配置请求地址
  56.   http.begin(client, SERVER_IP);  //HTTP请求
  57.   Serial.print("[HTTP] begin...\n");
  58.   // 长度
  59.   DynamicJsonDocument doc(96);
  60.   float data = analogRead(PIN_AO);
  61.   Serial.println(data);
  62.   i = data / 1023;
  63.   j = (1 - i) * 100;
  64.   Serial.println(j);
  65.   // 写入当前温度值
  66.   doc["temp"] = j;
  67.   String output;
  68.   serializeJson(doc, output);
  69.   //启动连接并发送HTTP报头和报文
  70.   int httpCode = http.POST(output);
  71.   Serial.print("[HTTP] POST...\n");
  72.   //连接失败时 httpCode时为负
  73.   if (httpCode > 0) {
  74.     //将服务器响应头打印到串口
  75.     Serial.printf("[HTTP] POST... code: %d\n", httpCode);
  76.     //将从服务器获取的数据打印到串口
  77.     if (httpCode == HTTP_CODE_OK) {
  78.       const String& payload = http.getString();
  79.       Serial.println("received payload:\n<<");
  80.       Serial.println(payload);
  81.       Serial.println(">>");
  82.     }
  83.   } else {
  84.     Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
  85.   }
  86.   //关闭http连接
  87.   http.end();
  88. }
复制代码
存储数据

数据存储,得需要一个服务器,我这里正好一个冲动消费买的树莓派,就让他当服务器吧(你要有个这个可以不用买esp8266了,直接开干就完了)我的思路,在树莓派上起一个服务,让esp8266可以通过http的协议上报温度,数据展示也可以通过这个服务来获取数据,当然了我还想外网访问:我这里用的是花生壳,搞了一个内网穿透,这个送1g流量,6块还能给一个https的域名,省了备案的事情。

  • 服务简单的来就使用nodejs启动一个服务吧
  • 数据库我使用的SQLite,看人家说这个挺小的,还支持关联查询。代码如下
  1. const http = require('http');
  2. const os = require('os');
  3. const urlInfo = require('url')
  4. // 读取数据库
  5. const querystring = require("querystring")
  6. var sqlite3 = require('sqlite3').verbose()
  7. // 要使用的端口号
  8. const PORT_NUMBER = 3001
  9. var db = new sqlite3.Database('./temp.db', sqlite3.OPEN_READWRITE, (err) => {
  10.     if (err) {
  11.         return console.log(err.message)
  12.     }
  13.     console.log('数据库链接成功')
  14. })
  15. /** 追加信息 */
  16. const appendTemp = (temp) => {
  17.     return new Promise((resolve, reject) => {
  18.         const currentTime = new Date().getTime()
  19.         const addData = `INSERT INTO temp (time,temp)  VALUES(${currentTime},${temp})`
  20.         db.run(addData, function (err, data) {
  21.             if (err) {
  22.                 console.log(err)
  23.                 reject(err)
  24.             }
  25.             resolve()
  26.         })
  27.     })
  28. }
  29. const selectTemp = (paramInfo) => {
  30.     return new Promise((resolve, reject) => {
  31.         const { pageIndex = 1, pageSize = 10, all = false,startTimestamp,endTimestamp } = paramInfo
  32.         let sqlStr = `select * from temp`
  33.         if (startTimestamp && endTimestamp) {
  34.             sqlStr = `select * from temp where time >= ${startTimestamp} and time <= ${endTimestamp}`
  35.         }
  36.         // 默认展示所有
  37.         if (all === false) {
  38.             sqlStr += ` limit(${(pageIndex - 1) * pageSize}),${pageSize}`
  39.         }
  40.         db.all(sqlStr, function (err, data) {
  41.             if (err) {
  42.                 return console.log(err)
  43.             }
  44.             resolve(data)
  45.         })
  46.     })
  47. }
  48. // 2. 创建服务
  49. // req(request):本次请求  res(response):本次响应     每次收到浏览器的请求,它就会执行一次回调
  50. const server = http.createServer(function (req, res) {
  51.     const { method, url } = req
  52.     // 接收到请求数据
  53.     if (method === 'POST' && url === '/sendCurrentTemp') {
  54.         //创建空字符叠加数据片段
  55.         var data = '';
  56.         //2.注册data事件接收数据(每当收到一段表单提交的数据,该方法会执行一次)
  57.         req.on('data', (chunk) => {
  58.             // chunk 默认是一个二进制数据,和 data 拼接会自动 toString
  59.             data += chunk;
  60.         })
  61.         req.on('end', () => {
  62.             try {
  63.                 console.log(data)
  64.                 const info = JSON.parse(data)
  65.                 appendTemp(info.temp)
  66.                 res.end('{status:200}');
  67.             } catch (e) {
  68.                 console.error(e)
  69.             }
  70.         })
  71.     }
  72.     const { pathname, path } = urlInfo.parse(req.url)
  73.     if (method === 'GET' && pathname === '/getTemp') {
  74.         // 返回查询的信息
  75.         selectTemp(param2Obj(path)).then(list => {
  76.             let retObj = {
  77.                 status: 200,
  78.                 list
  79.             }
  80.             res.end(JSON.stringify(retObj));
  81.         })
  82.     }
  83.     // 获取数据请求
  84. });
  85. // 3. 启动服务
  86. server.listen(PORT_NUMBER, function () {
  87.     console.log(`服务器启动成功,请在http://${getIpAddress()}:${PORT_NUMBER}中访问....`)
  88. });
  89. /** 获取当前ip地址 */
  90. function getIpAddress() {
  91.     var ifaces = os.networkInterfaces()
  92.     for (var dev in ifaces) {
  93.         let iface = ifaces[dev]
  94.         for (let i = 0; i < iface.length; i++) {
  95.             let { family, address, internal } = iface[i]
  96.             if (family === 'IPv4' && address !== '127.0.0.1' && !internal) {
  97.                 return address
  98.             }
  99.         }
  100.     }
  101. }
  102. function param2Obj(url) {
  103.     const search = url.split('?')[1]
  104.     if (!search) {
  105.         return {}
  106.     }
  107.     const paramObj = JSON.parse('{"' + (search).replace(/"/g, '\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
  108.     Object.keys(paramObj).map(key => {
  109.         paramObj[key] = decodeURIComponent(paramObj[key])
  110.     })
  111.     return paramObj
  112. }
复制代码
数据展示

使用vue写一个页面,去从服务拉取到数据

  • 展示曲线
  • 展示所有数据分页展示(后期想加个图片,每隔一段时间上传一个图片,搞一个延迟摄影的效果)
全部代码地址

最后放一张效果图吧


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表