Weekend Project Progress

This post is a progress diary for my weekend project. The final goal of the project is building a tank surveillance system by using ESPs, relays and other electronic devices.

  • ESP8266

如何上传代码,请参考链接[1],与温湿度传感器相连接请参考链接[2],[3],DHT11的使用方法和介绍请见[4].

代码如下(ESP8266WebServer),注意调整DHTTYPE为DHT11,调整温度读取单位为摄氏度。

/* DHTServer - ESP8266 Webserver with a DHT sensor as an input

   Based on ESP8266Webserver, DHTexample, and BlinkWithoutDelay (thank you)

   Version 1.0  5/3/2014  Version 1.0   Mike Barela for Adafruit Industries

   Modified by Ma Can
*/
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DHT.h>
#define DHTTYPE DHT11
#define DHTPIN  2

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

ESP8266WebServer server(80);
 
// Initialize DHT sensor 
// NOTE: For working with a faster than ATmega328p 16 MHz Arduino chip, like an ESP8266,
// you need to increase the threshold for cycle counts considered a 1 or 0.
// You can do this by passing a 3rd parameter for this threshold.  It's a bit
// of fiddling to find the right value, but in general the faster the CPU the
// higher the value.  The default for a 16mhz AVR is a value of 6.  For an
// Arduino Due that runs at 84mhz a value of 30 works.
// This is for the ESP8266 processor on ESP-01 
DHT dht(DHTPIN, DHTTYPE, 11); // 11 works fine for ESP8266
 
float humidity, temp_c;  // Values read from sensor
String webString="";     // String to display
// Generally, you should use "unsigned long" for variables that hold time
unsigned long previousMillis = 0;        // will store last temp was read
const long interval = 2000;              // interval at which to read sensor
 
void handle_root() {
  server.send(200, "text/plain", "Hello from the weather esp8266, read from /temp or /humidity");
  delay(100);
}
 
void setup(void)
{
  // You can open the Arduino IDE Serial Monitor window to see what the code is doing
  Serial.begin(115200);  // Serial connection from ESP-01 via 3.3v console cable
  dht.begin();           // initialize temperature sensor

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.print("\n\r \n\rWorking to connect");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("DHT Weather Reading Server");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
   
  server.on("/", handle_root);
  
  server.on("/temp", [](){  // if you add this subdirectory to your webserver call, you get text below :)
    gettemperature();       // read sensor
    webString="Temperature: "+String((int)temp_c)+" C";   // Arduino has a hard time with float to string
    server.send(200, "text/plain", webString);            // send to someones browser when asked
  });

  server.on("/humidity", [](){  // if you add this subdirectory to your webserver call, you get text below :)
    gettemperature();           // read sensor
    webString="Humidity: "+String((int)humidity)+"%";
    server.send(200, "text/plain", webString);               // send to someones browser when asked
  });
  
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void)
{
  server.handleClient();
} 

void gettemperature() {
  // Wait at least 2 seconds seconds between measurements.
  // if the difference between the current time and last time you read
  // the sensor is bigger than the interval you set, read the sensor
  // Works better than delay for things happening elsewhere also
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis >= interval) {
    // save the last time you read the sensor 
    previousMillis = currentMillis;   

    // Reading temperature for humidity takes about 250 milliseconds!
    // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
    humidity = dht.readHumidity();          // Read humidity (percent)
    temp_c = dht.readTemperature();     // Read temperature
    // Check if any reads failed and exit early (to try again).
    if (isnan(humidity) || isnan(temp_c)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
    }
  }
}

另外,需要注意的是,DHT11 v1.0需要使用3.7v-12V DC供电(支持3.7V锂电池),输出电源除了3.3v之外,还需要至少提供5v以供DHT11使用。

在使用Arduino IDE编译和上传时,开发版配置参数如下:

  1. Flash Mode: “DIO”
  2. Flash Frequency: “40MHz”
  3. CPU Frequency: “80 MHz”
  4. Flash Size: “1M (64K SPIFFS)”
  5. Debug Port: “Disabled”
  6. Debug Level: “None”
  7. Reset Method: “ck”
  8. Upload Speed: “115200”
  • ESP32 CAM

ESP32-CAM是一个低成本的CAMERA解决方案。本次使用的目标是将其变为一个鱼缸乌龟台的视频监控。ESP32-CAM在通过TTL上传代码时需要确保3.3v供电,相机类型使用CAMERA_AI_THINKER。同时,根据测试,ESP32-CAM在实际使用时需要确保有5V供电。

ESP32-CAM上传代码时连接3.3v电源,GPIO0保持GND以进入flash模式,TXD/RXD根据标识与USB TTL引脚相连。ESP32-CAM的引脚图如下所示。需要注意的是如何TTL连线上传代码,请参考需要注意的是如何TTL连线上传代码[5]。,ESP32-CAM的代码如下(ESP32CAMWebServer)

// Modified by Ma Can
#include "esp_camera.h"
#include <WiFi.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PubSubClient.h>
#include <DHTesp.h>
//
// WARNING!!! Make sure that you have either selected ESP32 Wrover Module,
//            or another board which has PSRAM enabled
//

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER

#include "camera_pins.h"

const char* ssid[] = { "ap1", "ap2" };
const char* password[] = { "ap1pass", "ap2pss" };
int ssid_idx = 0, pwd_idx = 0;

const char *mqtt_server[] = { "192.168.21.228", "192.168.11.249" };
int sid = 0;

// 定义DS18B20数据口连接arduino的15号IO上
#define ONE_WIRE_BUS 15

// 初始连接在单总线上的单总线设备
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#define DHTTYPE   DHT11         // define sensor type

const int DHTPIN = 14;
float h = 0;
float t = 0;

DHTesp dht;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0, boot = 0;
char msg[50];
int value = 0;

extern void startCameraServer();

int connect_wifi(const char *ssid, const char *password) {
  long last, now;

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.print("\n\r \n\rWorking to connect");
  last = now = millis();

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    now = millis();
    if ((now - last) >= 30000)
      break;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.print("WiFi connect to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    return 1;
  } else {
    Serial.println("");
    Serial.print("WiFi connect to ");
    Serial.print(ssid);
    Serial.println(" failed.");
    return 0;
  }
}

int select_server() {
  return sid++ % (sizeof(mqtt_server) / sizeof(mqtt_server[0]));
}

int select_ssid() {
  return ssid_idx++ % (sizeof(ssid) / sizeof(ssid[0]));
}

int select_password() {
  return pwd_idx++ % (sizeof(password) / sizeof(password[0]));
}

void setup() {
  boot = millis();
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  sensors.begin();

  dht.setup(DHTPIN, DHTesp::DHT11);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  //init with high specs to pre-allocate larger buffers
  if (psramFound()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  //initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1);//flip it back
    s->set_brightness(s, 1);//up the blightness just a bit
    s->set_saturation(s, -2);//lower the saturation
  }
  //drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_QVGA);

#if defined(CAMERA_MODEL_M5STACK_WIDE)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

  for (int i = 0; i < sizeof(ssid) / sizeof(ssid[0]); i++) {
    if (connect_wifi(ssid[i], password[i]) == 1) {
      // it is ok
      break;
    }
  }

  startCameraServer();

  Serial.print("Camera Ready! Use 'http://");
  Serial.print(WiFi.localIP());
  Serial.println("' to connect");

  client.setServer(mqtt_server[select_server()], 1883);
  client.setCallback(callback);
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP32CAM")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("/esp32/sensor/state", "connected");
      // ... and resubscribe
      client.subscribe("/esp32/switch/cmd");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds with another server");
      // Wait 5 seconds before retrying
      client.setServer(mqtt_server[select_server()], 1883);
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch
  int val;
  if ((char)payload[0] == '1') {

  } else if ((char)payload[0] == '2') {

  }
}

void publish_sensor_data(char *msg)
{
  double ctemp;
  
  Serial.print("Requesting temperatures...");
  sensors.requestTemperatures(); // 发送命令获取温度
  ctemp = sensors.getTempCByIndex(0);
  Serial.println("DONE");

  Serial.print("Temperature for the device 1 (index 0) is: ");
  Serial.println(ctemp);

  if (ctemp != -127) {
    snprintf(msg, 40, "%0.2f", ctemp);
    client.publish("/esp32/sensor/waterTemp/state", msg);
    Serial.print("Publish message: ");
    Serial.println(msg);
  }
}

void publish_dht11_data(char *msg)
{
  float t = dht.getTemperature();
  int h = dht.getHumidity();

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  // print data
  Serial.printf("Temperature = %.2f°C\r\n", t);
  Serial.printf("Humidity    = %d %%\r\n", h);

  snprintf(msg, 40, "%0.2f", t);
  client.publish("/esp32/sensor/airTemp/state", msg);
  Serial.print("Publish message: Temp:");
  Serial.println(msg);
  snprintf(msg, 40, "%d", h);
  client.publish("/esp32/sensor/airHumidity/state", msg);
  Serial.print("Publish message: Humidity:");
  Serial.println(msg);
}

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

  long now = millis();
  if (now - lastMsg > 30000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 49, "connected", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("/esp32/sensor/state", msg);
    publish_sensor_data(msg);
    publish_dht11_data(msg);
  }
  if (now - boot > 86400000) {
    ESP.restart();
  }
}

Arduino IDE配置如下:

  • DS18B20

连线方式如下图所示,单一总线协议,DATA线需要连接4.7K欧的上拉电阻。

两种测温度的方法(代码)都可以使用。

备注:使用ESP32采样的话很可能需要调整电阻值!!

备注2:使用ESP8266验证DS18B20的工作还没有开展,现在尝试结果如下。使用5V电源的话,代码是一致的,可以直接使用。

  • ESP-01S 2 Way Relay

代码请见ESP8266Relay2Way,原理比较简单,关键是如何连接2-way relay。目前在面包版的电源模块上实验,按照下图的电路方式连接(12v电压忽略)。隔离JD-VCC和VCC,不加ESP-01S直接测试继电器是成功的,加上ESP-01S测试继电器是失败的,观察了一下VCC的电压,加上ESP-01S之后,电压下降到了2.8V,推测该电压太低,已经不能驱动继电器工作了(原因是面包板的电源模块负载能力不够)。截至到2019年5月27日,通过220v->5V电源驱动JD-VCC,3.3v电源驱动VCC和INx的模式,已经成功工作并集成完毕。

  • 目前的工作进展(截至到2019年6月1日,验证时间超过1周)

1 完成了一个2路的继电器模块,通过220V AC to 5V DC的模块供电,由ESP01S连接到wifi并通过MQTT协议实现控制,与HA进行了集成,能够控制照明灯和乌龟灯;

2 完成了基于ESP32 CAM的水温、气温、湿度采集和视频采集的模块,通过220V AC to 5V DC的模块供电,实现MQTT协议,提供采集数据,与Home Assistant进行了集成,能够实现基本功能,如下图所示。

但是,还存在以下的缺陷:1)数据采样过程存在一定的时间延迟,在此期间整个MCU是无法为视频采集提供服务的,因此会出现一定的视频卡顿现象。

建议如下:

1)继电器模块增加至4路控制,考虑怎么样连接多路之间的线路来减小多路控制时插头的数量;

2)继电器模块增加至多路控制时考虑ESP01S的输出口是否还够用,默认只有IO0和IO2两个口;

3)ESP32 CAM模块增加其他的采样功能时需要考虑是否会对主要功能产生影响;(回复意见:降低温湿度采集的频率为每分钟一次,修正后ESP32 CAM稳定性获得了一定的增强)

4)ESP32 CAM模块的稳定性堪忧,特别是长时间运行后video stream服务会崩溃;(实际上,除了video stream服务崩溃外,还出现过传感器采集故障)

Leave a Reply

Your email address will not be published. Required fields are marked *