6

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

 1 year ago
source link: https://www.cnblogs.com/niexianda/p/17467341.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

1.闲来无事-夏天防止花被渴死06-08

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

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

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

我的思路如下

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

思路有了就开干

我的这个实现思路也算是传说中的物联网了,毕竟花盆都上网了嘛,现在实现这种的板子有很多,那个便宜来那个就行了。 经过百度一下,选择了ESP8266-NodeMCU + 湿度传感器 作为数据收集方式(为啥选择这个呢?因为便宜啊,还自带wifi能上网。硬件真便宜,一共20块搞定,还包邮),esp8266可以使用Arduino进行开发,语法跟c差不多,库啥的自己搜搜吧,我也是自己搜的。俺看的是这个教程

收集数据主要是干两件事:1. 读取湿度,2. 上报数据 代码如下

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
// 读取温度
#define PIN_AO A0  //土壤传感器AO接ESP8266引脚A0
//#define PIN_DO 4  //湿度高于设定值时,DO输出高电平,模块提示灯亮

int M0 = 1024;  //在空气中AO读取的值最大为1024,代表干燥时的读数
int M1 = 164;   //浸泡在水里的最小值 464(最小值会改变),代表100%湿度

float i=0;
float j=0;

#define SERVER_IP "http://局域网ip:3001/sendCurrentTemp"

const char* name = "名字";               //这里改成你的设备当前环境下要连接的接入点名字
const char* password = "密码";  //这里改成你的设备当前环境下要连接的接入点密码
// 上报时间间隔
int outTime = 1000 * 60 * 5;
// int outTime = 1000 * 60;
void setup() {
  // 设置引脚
  pinMode(PIN_AO, INPUT);

  Serial.begin(115200);  // 启动串口通讯,波特率设置为115200

  Serial.println("未连接");


  Serial.println("开始连接");

  WiFi.begin(name, password);
  Serial.print("正在连接到");
  Serial.print(name);

  while (WiFi.status() != WL_CONNECTED)  //判定网络状态
  {
    delay(500);

    Serial.print("网络连接成功");

    Serial.print("连接到的接入点名字:");

    Serial.println(name);  // 告知用户建立的接入点WiFi名

    Serial.print("连接到的接入点密码:");

    Serial.println(password);  // 显示用户建立的接入点WiFi密码

    Serial.print("无线模式成功开启,网络连接成功");
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("无线IP地址为: ");
    Serial.println(WiFi.localIP());
  }
}

void loop() {
  // put your main code here, to run repeatedly:

  if (WiFi.status() == WL_CONNECTED) {
    http_post();
    WiFi.forceSleepBegin();  // Wifi Off
    delay(outTime);
    WiFi.forceSleepWake();  // Wifi On
  }
}



void http_post() {

  //创建 WiFiClient 实例化对象
  WiFiClient client;

  //创建http对象
  HTTPClient http;

  //配置请求地址
  http.begin(client, SERVER_IP);  //HTTP请求
  Serial.print("[HTTP] begin...\n");
  // 长度
  DynamicJsonDocument doc(96);

  float data = analogRead(PIN_AO);
  Serial.println(data);
  i = data / 1023;
  j = (1 - i) * 100;

  Serial.println(j);

  // 写入当前温度值
  doc["temp"] = j;
  String output;
  serializeJson(doc, output);

  //启动连接并发送HTTP报头和报文
  int httpCode = http.POST(output);
  Serial.print("[HTTP] POST...\n");

  //连接失败时 httpCode时为负
  if (httpCode > 0) {
    //将服务器响应头打印到串口
    Serial.printf("[HTTP] POST... code: %d\n", httpCode);

    //将从服务器获取的数据打印到串口
    if (httpCode == HTTP_CODE_OK) {
      const String& payload = http.getString();
      Serial.println("received payload:\n<<");
      Serial.println(payload);
      Serial.println(">>");
    }
  } else {
    Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  //关闭http连接
  http.end();
}

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

  1. 服务简单的来就使用nodejs启动一个服务吧
  2. 数据库我使用的SQLite,看人家说这个挺小的,还支持关联查询。 代码如下
const http = require('http');
const os = require('os');
const urlInfo = require('url')
// 读取数据库
const querystring = require("querystring")
var sqlite3 = require('sqlite3').verbose()

// 要使用的端口号
const PORT_NUMBER = 3001


var db = new sqlite3.Database('./temp.db', sqlite3.OPEN_READWRITE, (err) => {
    if (err) {
        return console.log(err.message)
    }
    console.log('数据库链接成功')
})

/** 追加信息 */
const appendTemp = (temp) => {
    return new Promise((resolve, reject) => {
        const currentTime = new Date().getTime()
        const addData = `INSERT INTO temp (time,temp)  VALUES(${currentTime},${temp})`
        db.run(addData, function (err, data) {
            if (err) {
                console.log(err)
                reject(err)
            }
            resolve()
        })
    })

}

const selectTemp = (paramInfo) => {
    return new Promise((resolve, reject) => {
        const { pageIndex = 1, pageSize = 10, all = false,startTimestamp,endTimestamp } = paramInfo
        let sqlStr = `select * from temp`
        if (startTimestamp && endTimestamp) {
            sqlStr = `select * from temp where time >= ${startTimestamp} and time <= ${endTimestamp}`
        }
        // 默认展示所有
        if (all === false) {
            sqlStr += ` limit(${(pageIndex - 1) * pageSize}),${pageSize}`
        }
        db.all(sqlStr, function (err, data) {
            if (err) {
                return console.log(err)
            }
            resolve(data)
        })
    })
}


// 2. 创建服务
// req(request):本次请求  res(response):本次响应     每次收到浏览器的请求,它就会执行一次回调
const server = http.createServer(function (req, res) {
    const { method, url } = req
    // 接收到请求数据
    if (method === 'POST' && url === '/sendCurrentTemp') {
        //创建空字符叠加数据片段
        var data = '';
        //2.注册data事件接收数据(每当收到一段表单提交的数据,该方法会执行一次)
        req.on('data', (chunk) => {
            // chunk 默认是一个二进制数据,和 data 拼接会自动 toString
            data += chunk;
        })
        req.on('end', () => {
            try {
                console.log(data)
                const info = JSON.parse(data)
                appendTemp(info.temp)
                res.end('{status:200}');
            } catch (e) {
                console.error(e)
            }
        })
    }
    const { pathname, path } = urlInfo.parse(req.url)
    if (method === 'GET' && pathname === '/getTemp') {
        // 返回查询的信息
        selectTemp(param2Obj(path)).then(list => {
            let retObj = {
                status: 200,
                list
            }
            res.end(JSON.stringify(retObj));
        })
    }

    // 获取数据请求
});


// 3. 启动服务
server.listen(PORT_NUMBER, function () {
    console.log(`服务器启动成功,请在http://${getIpAddress()}:${PORT_NUMBER}中访问....`)
});


/** 获取当前ip地址 */
function getIpAddress() {
    var ifaces = os.networkInterfaces()
    for (var dev in ifaces) {
        let iface = ifaces[dev]
        for (let i = 0; i < iface.length; i++) {
            let { family, address, internal } = iface[i]
            if (family === 'IPv4' && address !== '127.0.0.1' && !internal) {
                return address
            }
        }
    }
}


function param2Obj(url) {
    const search = url.split('?')[1]
    if (!search) {
        return {}
    }
    const paramObj = JSON.parse('{"' + (search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
    Object.keys(paramObj).map(key => {
        paramObj[key] = decodeURIComponent(paramObj[key])
    })
    return paramObj
}

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

  1. 展示所有数据分页展示(后期想加个图片,每隔一段时间上传一个图片,搞一个延迟摄影的效果)

全部代码地址

最后放一张效果图吧

1393523-20230608213039638-1249199718.jpg
1393523-20230608213156025-1650817566.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK