一.esp8266 或 esp32-c3水溢报警器(基于导电传感器)
sean 编辑于2022-04-11 14:26硬件相关
需求:
有时接净化水,忘记打开定时器,导致水漫出来满厨房都是水。这种失误不止一次,虽然问题不大,但时不时的折磨我一下搞的心情都不好了。终于我开始痛定思痛,用一个esp8266和一个水位传感器解决了:
2022.4.11后记:使用一段时候后暴露了一些问题,又制作了第二代防溢出报警器
上图
上代码(这个蜂鸣器播放的是网上抄来的暗黑版欢乐颂,这种带有日本东洋风的欢乐颂听起来并不欢乐,反而平添了些许愁绪(狗头))需要注意的是加入蜂鸣器播放的时候,是无法响应警报解除中断的,也就是说必需要等蜂鸣器播放完,服务器的报警才能关闭,即使你已经擦干了溢出的水也是这样,目前改成报警声默认时长12秒。如果服务器24小时待机的话,可以去掉蜂鸣器逻辑,这样响应还是灵敏。加入蜂鸣器是解决当服务器连接不上时也能自行报警的一个折中方案:
#define Tone_Pin D7
#define Water_Sensor D2 //attach water sensor to digital D2
#define NTD0 -1
#define NTD1 294
#define NTD2 330
#define NTD3 350
#define NTD4 393
#define NTD5 441
#define NTD6 495
#define NTD7 556
#define NTDL1 147
#define NTDL2 165
#define NTDL3 175
#define NTDL4 196
#define NTDL5 221
#define NTDL6 248
#define NTDL7 278
#define NTDH1 589
#define NTDH2 661
#define NTDH3 700
#define NTDH4 786
#define NTDH5 882
#define NTDH6 990
#define NTDH7 112
//列出全部D调的频率
#define WHOLE 1
#define HALF 0.5
#define QUARTER 0.25
#define EIGHTH 0.25
#define SIXTEENTH 0.625
//列出所有节拍
int tune[] = //根据简谱列出各频率
{
NTD3, NTD3, NTD4, NTD5,
NTD5, NTD4, NTD3, NTD2,
NTD1, NTD1, NTD2, NTD3,
NTD3, NTD2, NTD2,
NTD3, NTD3, NTD4, NTD5,
NTD5, NTD4, NTD3, NTD2,
NTD1, NTD1, NTD2, NTD3,
NTD2, NTD1, NTD1,
NTD2, NTD2, NTD3, NTD1,
NTD2, NTD3, NTD4, NTD3, NTD1,
NTD2, NTD3, NTD4, NTD3, NTD2,
NTD1, NTD2, NTDL5, NTD0,
NTD3, NTD3, NTD4, NTD5,
NTD5, NTD4, NTD3, NTD4, NTD2,
NTD1, NTD1, NTD2, NTD3,
NTD2, NTD1, NTD1
};
float durt[] = //根据简谱列出各节拍
{
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
1 + 0.5, 0.5, 1 + 1,
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
1 + 0.5, 0.5, 1 + 1,
1, 1, 1, 1,
1, 0.5, 0.5, 1, 1,
1, 0.5, 0.5, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 0.5, 0.5,
1, 1, 1, 1,
1 + 0.5, 0.5, 1 + 1,
};
int length;//这里定义一个变量,后面用来表示共有多少个音符
//song function
void sing_song()
{
for (int x = 0; x < length; x++) //循环音符的次数
{
tone(Tone_Pin, tune[x]); //此函数依次播放tune序列里的数组,即每个 音符
delay(400 * durt[x]); //每个音符持续的时间,即节拍duration,是调整时间的越大,曲子速度越慢,越小曲子速度越快,自己掌握吧
noTone(Tone_Pin);//停止当前音符,进入下一音符
}
}
void alarm_sound() {
for (int i = 200; i <= 800; i++) //用循环的方式将频率从200HZ 增加到800HZ
{
tone(Tone_Pin, i); //在四号端口输出频率
delay(5); //该频率维持5毫秒
}
delay(12000); //最高频率下维持12秒钟
for (int i = 800; i >= 200; i--)
{
tone(Tone_Pin, i);
delay(10);
}
noTone(Tone_Pin);
}
// ################### song end #####################
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
/* this can be run with an emulated server on host:
cd esp8266-core-root-dir
cd tests/host
make ../../libraries/ESP8266WebServer/examples/PostServer/PostServer
bin/PostServer/PostServer
then put your PC's IP address in SERVER_IP below, port 9080 (instead of default 80):
*/
//#define SERVER_IP "10.0.1.7:9080" // PC address with emulation on host
#define SERVER_DOMIAN_NAME "alarm.freeloong.top"
#ifndef STASSID
#define STASSID "mywifi_ssid"
#define STAPSK "mywifipasswd"
#endif
int water_status = 0; //water's status 0 empty, 1 full
void setup() {
pinMode(Water_Sensor, INPUT); // The water sensor is an Input
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
digitalWrite(LED_BUILTIN, HIGH); //Turn the LED off by making the voltage HIGH
// init song 初始化 歌曲,
pinMode(Tone_Pin, OUTPUT);
length = sizeof(tune) / sizeof(tune[0]); //这里用了一个sizeof函数,可以查出tone序列里有多少个音符
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println();
WiFi.begin(STASSID, STAPSK);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
}
void send_full() {
if ((WiFi.status() == WL_CONNECTED)) {
WiFiClient client;
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// configure traged server and url
http.begin(client, "http://alarm.freeloong.top/listen"); //HTTP
http.addHeader("Content-Type", "application/json");
Serial.print("[HTTP] POST...\n");
// start connection and send HTTP header and body
int httpCode = http.POST("{\"water\":\"full\"}");
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
const String& payload = http.getString();
Serial.println("received payload:\n<<");
Serial.println(payload);
Serial.println(">>");
delay(10000);
}
} else {
Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
}
void send_empty() {
if ((WiFi.status() == WL_CONNECTED)) {
WiFiClient client;
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// configure traged server and url
http.begin(client, "http://alarm.freeloong.top/listen"); //HTTP
http.addHeader("Content-Type", "application/json");
Serial.print("[HTTP] POST...\n");
// start connection and send HTTP header and body
int httpCode = http.POST("{\"water\":\"empty\"}");
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] POST... code: %d\n", httpCode);
// file found at server
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.end();
}
}
// the loop function runs over and over again forever
void loop() {
/* The water sensor will switch LOW when water is detected.
Get the Arduino to illuminate the LED and activate the buzzer
when water is detected, and switch bothse off when no water is present */
if ( digitalRead(Water_Sensor) == HIGH) {
if (water_status == 0) {
digitalWrite(LED_BUILTIN, LOW); //Turn the LED on (Note that LOW is the voltage level
send_full(); //send the full signal to server
//sing_song();
alarm_sound();
water_status = 1;
}
}
else { //当前没有检测到水
if (water_status == 1) {
digitalWrite(LED_BUILTIN, HIGH); //Turn the LED off by making the voltage HIGH
send_empty(); //send the empty signal to server
water_status = 0;
}
}
delay(500);
}
esp32-C3版本的ino代码,这个版本用esp32-C3替代了esp8266,增加了tls加密通信外接硬件不变,另外蜂鸣器报警由于没有了Tone函数,用ledcWriteTone替代:
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#define Tone_Pin 7
#define Water_Sensor 6 //attach water sensor to digital D2
#define SERVER_DOMIAN_NAME "alarm.freeloong.top"
#ifndef APSSID
#define APSSID "mywifi_ssid"
#define PASSWORD "mywifi_password"
#endif
// Set up the rgb led names
uint8_t ledR = 3;
uint8_t ledG = 4;
uint8_t ledB = 5;
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" \
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" \
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" \
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" \
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" \
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" \
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" \
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" \
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" \
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" \
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" \
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" \
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" \
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" \
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" \
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" \
"-----END CERTIFICATE-----\n";
int water_status = 0; //water's status 0 empty, 1 full
void alarm_sound() {
for (int i = 200; i <= 800; i++) //用循环的方式将频率从200HZ 增加到800HZ
{
ledcWriteTone(4, i); //在四号端口输出频率
delay(5); //该频率维持5毫秒
}
delay(12000); //最高频率下维持12秒钟
for (int i = 800; i >= 200; i--)
{
ledcWriteTone(4, i);
delay(10);
}
ledcWriteTone(4, 0);
}
WiFiMulti WiFiMulti;
void setup() {
pinMode(Water_Sensor, INPUT); // The water sensor is an Input
ledcAttachPin(ledR, 1); // assign RGB led pins to channels
ledcAttachPin(ledG, 2);
ledcAttachPin(ledB, 3);
ledcAttachPin(Tone_Pin, 4);
//LEDC总共有16个路通道(0 ~ 15),分为高低速两组,高速通道(0 ~ 7)由80MHz时钟驱动,低速通道(8 ~ 15)由1MHz时钟驱动。
//double ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits)
//(通道号,频率,计数位数)
//channel为通道号,取值0 ~ 15;
//freq,设置频率;
//resolution_bits计数位数,取值0 ~ 20(该值决定后面ledcWrite方法中占空比的最大值,如该值写10,则占空比最大可写2^10-1=1023 ;
//通道最终频率 = 时钟频率 / ( 分频系数 * ( 2^计数位数 ) );(分频系数最大为1024)
//该方法返回值:最终频率;
// Initialize channels
// channels 0-15, resolution 1-16 bits, freq limits depend on resolution
// ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
ledcSetup(1, 12000, 8); // 12 kHz PWM, 8-bit resolution
ledcSetup(2, 12000, 8);
ledcSetup(3, 12000, 8);
ledcSetup(4, 12000, 8);
ledcWrite(1, 255);
ledcWrite(2, 255);
ledcWrite(3, 255);
ledcWriteTone(4, 0);
// init song 初始化 歌曲,
//pinMode(Tone_Pin, OUTPUT);
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println();
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(APSSID, PASSWORD);
// wait for WiFi connection
Serial.print("Waiting for WiFi to connect...");
while ((WiFiMulti.run() != WL_CONNECTED)) {
Serial.print(".");
}
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
}
void send_full_or_empty(String water_status_string) {
WiFiClientSecure *client = new WiFiClientSecure;
if (client) {
client -> setCACert(rootCACertificate);
{
// Add a scoping block for HTTPClient https to make sure it is destroyed before WiFiClientSecure *client is
HTTPClient https;
Serial.print("[HTTPS] begin...\n");
if (https.begin(*client, "https://alarm.freeloong.top/listen")) { // HTTPS
https.addHeader("Content-Type", "application/json");
Serial.print("[HTTPS] POST...\n");
// start connection and send HTTP header
int httpCode = https.POST(water_status_string);
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
Serial.println("received payload:\n<<");
Serial.println(payload);
Serial.println(">>");
}
} else {
Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("[HTTPS] Unable to connect\n");
}
// End extra scoping block
}
delete client;
} else {
Serial.println("Unable to create client");
}
}
// the loop function runs over and over again forever
void loop() {
/* The water sensor will switch LOW when water is detected.
Get the Arduino to illuminate the LED and activate the buzzer
when water is detected, and switch bothse off when no water is present */
if ( digitalRead(Water_Sensor) == HIGH) {
if (water_status == 0) {
//red led open
ledcWrite(1, 0);
ledcWrite(2, 255);
ledcWrite(3, 255);
String water_status_string = "{\"water\":\"full\"}";
send_full_or_empty(water_status_string); //send the full signal to server
alarm_sound();
Serial.println("detect person");
water_status = 1;
}
}
else { //当前没有检测到水
if (water_status == 1) {
//green led open
//ledcWrite(1, 255);
//ledcWrite(2, 0);
//ledcWrite(3, 255);
//关闭led
ledcWrite(1, 255);
ledcWrite(2, 255);
ledcWrite(3, 255);
Serial.println("no person");
String water_status_string = "{\"water\":\"empty\"}";
send_full_or_empty(water_status_string); //send the empty signal to server
water_status = 0;
}
}
delay(500);
}
上服务器代码(服务器我用flask做的单页服务器,完成收到水满信号,播放预存的报警音频提醒,如果报警解除,可以立即停止播放报警音频。另外也可以发邮件到自己的手机(略鸡肋),因意义不大我就没实现,假如我在外面也无法快速赶回来关水,强迫症患者可以自行加上):
from flask import Flask
from flask import request
app = Flask(__name__)
import subprocess
import time
status='empty'
def query_paused_and_stop_task(subp, length=300):
'''
#轮询status状态,如果是则中止传入subprocess生成的子进程。
#参数 length为任务执行的大概时间,不确定可以稍大点,单位为秒,默认300秒
#subp 为 子进程对象
'''
counter = 0
while counter <length:
global status
if status == 'empty':
subp.kill()
break
time.sleep(1)
counter += 1
print('sleep 1 second')
def play_task():
subp = subprocess.Popen(['mplayer', '-af', 'volume=-10', 'https://freeloong.top/admin/upload/shuimanjinshan.mp3'], shell=False)
query_paused_and_stop_task(subp,37)
global status
return status
@app.route("/listen", methods=['GET','POST'])
def water():
if request.method == 'POST':
data = request.get_json()
print(data)
global status
status = data['water']
if status == 'full':
result = play_task()
return result
else:
return status
else:
return "<p>Hello, World!</p>"
服务器的启动脚本run_production.sh:
#!/bin/bash
exec_path=/home/d/alarm
log_path=/home/d/alarm
source /home/d/venvalarm/bin/activate
cd $exec_path
export FLASK_APP=alarm
export FLASK_ENV=prodution
# 禁用该部分是因为采用gunicorn的时候,当关闭报警声音的时候,gunicorn会阻止关闭,导致关闭失效,报警声音混乱.
#nohup python -u /home/d/venvalarm/bin/gunicorn -b localhost:8005 -w 1 alarm:app -t 20 > $log_path/alarm.log 2>&1 &
flask run -p 8005
deactivate
开机启动加入到开机启动脚本/etc/rc.local中:
#/bin/bash!
su yourusername -l -c '/path/to/your/run_production.sh'
肥龙软件分享的软件是本站作者开发的免费,无广告,安全可靠,绝不附带任何无关软件,绝不困绑任何插件的实用软件。如果您感觉好用,可以捐赠我们,这样我们可以有更积极的动力去改进升级软件,维持服务器运转,感谢您的捐助,谢谢!
更新时间:2024.8.31