Создание мультисенсорного шилда для ESP8266
В этом проекте вы узнаете, как спроектировать и создать мультисенсорный шилд для платы ESP8266 Wemos D1 Mini. Шилд оснащён датчиком температуры (DS18B20), PIR-датчиком движения, LDR (фоторезистором) и клеммой для подключения модуля реле. Мы начнём с подготовки всего оборудования, а затем запрограммируем его.
Смотрите видеоурок
Этот проект доступен в видеоформате и в текстовом формате. Вы можете посмотреть видео ниже или прокрутить вниз для получения письменных инструкций. Этот проект разделён на два видео.
Проектирование и сборка мультисенсорного шилда ESP8266 WeMos D1 Mini — Часть 1
В этом первом видео мы определимся с оборудованием, которое будем использовать. Мы также рассмотрим основные функции этого проекта и то, как спроектировать и собрать свой собственный мультисенсорный шилд WeMos D1 Mini.
Программирование и тестирование мультисенсорного шилда ESP8266 WeMos D1 Mini — Часть 2
Во втором видео мы запрограммируем мультисенсорный шилд Wemos D1 Mini кодом, который запускает веб-сервер, позволяющий контролировать и управлять мультисенсорным шилдом на основе нескольких настраиваемых параметров.
Ресурсы
Все ресурсы, необходимые для создания этого проекта, вы найдёте по ссылкам ниже (или можете посетить GitHub-проект):
Обзор проекта
Шилд состоит из датчика температуры, датчика движения, LDR и 3-контактного разъёма, к которому можно подключить любой выход — мы будем использовать модуль реле.
Мы также напишем программу, которая запускает веб-сервер для управления шилдом с использованием 4 различных режимов с несколькими настраиваемыми параметрами.
К концу этого проекта у вас будет похожее устройство.
Вы сможете включать и выключать реле в ручном режиме.
Или вы можете выбрать автоматические режимы:
Автоматический режим движения — при обнаружении движения реле остаётся включённым в течение определённого количества секунд.
Или вы можете использовать режим освещённости, чтобы реле включалось, когда уровень освещённости опускается ниже определённого порогового значения.
Наконец, есть возможность управлять реле на основе текущего значения освещённости и обнаружения движения.
Мы покажем вам все эти функции в действии позже в этом проекте.
JLCPCB
Этот проект спонсировался компанией JLCPCB. JLCPCB — это известная компания по производству прототипов печатных плат в Китае. Она специализируется на быстром изготовлении прототипов печатных плат и мелкосерийном производстве. Вы можете заказать минимум 10 печатных плат всего за $2.
Если вы хотите превратить схемы на макетной плате в настоящие платы и придать вашим проектам более профессиональный вид, вам просто нужно загрузить Gerber-файлы, чтобы заказать высококачественные печатные платы по низким ценам. Мы покажем, как это сделать, далее в этой статье.
Функции мультисенсорного шилда ESP8266
Мультисенсорный шилд оснащён несколькими датчиками, которые могут быть полезны для мониторинга вашего дома. Шилд позволяет контролировать:
1x SMD-светодиод для индикации статуса
1x Фоторезистор (LDR)
1x Датчик температуры DS18B20
1x PIR-датчик движения
Кроме того, он также оснащён клеммным блоком, который обеспечивает доступ к GND, 5V и GPIO15. Эту клемму можно использовать для подключения реле или любого другого выхода, которым вы хотите управлять.
Назначение выводов мультисенсорного шилда ESP8266
В следующей таблице описано назначение выводов для каждого компонента мультисенсорного шилда:
Компонент |
Назначение выводов Wemos D1 Mini |
|---|---|
PIR-датчик движения |
GPIO5 (D1) |
Датчик температуры DS18B20 |
GPIO4 (D2) |
Фоторезистор (LDR) |
A0 |
Светодиод |
GPIO12 (D6) |
Дополнительный выход |
GPIO15 (D8) |
Если вы хотите назначить и использовать другие выводы, прочитайте наш справочник по распиновке ESP8266.
Тестирование схемы на макетной плате
Прежде чем проектировать и изготавливать печатную плату шилда, важно протестировать схему на макетной плате. Если вы не хотите делать печатную плату, вы всё равно можете следовать этому проекту, собрав схему на макетной плате.
Необходимые компоненты
Для сборки схемы на макетной плате вам понадобятся следующие компоненты:
ESP8266 Wemos D1 Mini — читайте Лучшая плата разработки ESP8266 Wi-Fi
1x резистор 330 Ом (резисторы)
2x резистор 10 кОм (резисторы)
Вы можете воспользоваться ссылками выше или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Схема
Собрав все компоненты, соберите схему, следуя приведённой ниже схеме:
Вот принципиальная схема:
Проектирование печатной платы
Убедившись, что схема правильно работает на макетной плате, я спроектировал печатную плату в KiCad. KiCad — это программное обеспечение с открытым исходным кодом, используемое для проектирования печатных плат.
Проектирование шилда для ESP8266 достаточно простое. Уже существуют готовые компоненты KiCad для WeMos, и вы можете найти ссылку на компоненты KiCad ниже:
Все остальные компоненты уже существуют в KiCad, такие как светодиод, LDR, резисторы, клеммные блоки и так далее.
Проектирование схемы работает так же, как и в любом другом инструменте для проектирования схем — вы размещаете компоненты и соединяете их между собой. Когда вы довольны своей схемой и распределением выводов, пора назначить каждому компоненту посадочное место (footprint).
WeMos D1 Mini уже был создан, и все остальные компоненты также были доступны по умолчанию в библиотеке компонентов KiCad.
Назначив компоненты, вы можете начать размещать каждый из них. Когда вы довольны расположением, выполните все соединения и разведите печатную плату.
Когда вы закончите, сохраните проект и экспортируйте Gerber-файлы.
Примечание: если вы знакомы с KiCad, вы можете взять файлы проекта и отредактировать их, чтобы настроить шилд под свои нужды.
Скачать GERBER .zip файл — нажмите здесь, чтобы скачать .zip файл
Заказ печатных плат
Если вы не знаете, как самостоятельно спроектировать печатную плату, но всё же хотите заказать их, это очень просто. Вам просто нужно:
Скачать Gerber-файлы — нажмите здесь, чтобы скачать .zip файл
Перейти на JLCPCB.com, нажать кнопку «QUOTE NOW» и загрузить только что скачанный .zip файл.
Вы увидите сообщение об успехе внизу. Затем вы можете использовать ссылку «Gerber Viewer» в правом нижнем углу, чтобы проверить, всё ли прошло как ожидалось. Вы можете просматривать верхнюю и нижнюю сторону печатной платы. Вы можете просматривать или скрывать паяльную маску, шелкографию, медь и т.д.
С настройками по умолчанию вы можете заказать 10 печатных плат всего за $2. Однако, если вы хотите выбрать другие настройки, например, другой цвет печатной платы, это обойдётся вам на несколько долларов дороже.
Когда вы довольны своим заказом, нажмите кнопку «SAVE TO CART», чтобы завершить заказ.
Мои печатные платы были изготовлены за 2 дня и прибыли через 3 рабочих дня с помощью службы доставки DHL.
Распаковка
Примерно через неделю я получил печатные платы в офисе. Как обычно, всё было хорошо упаковано, а печатные платы действительно высокого качества.
Буквы на шелкографии действительно хорошо напечатаны и легко читаются. Я не думаю, что вы сможете получить лучший сервис печатных плат за такую цену.
Пайка компонентов
Следующий шаг — припайка компонентов к печатной плате. Я использовал SMD-светодиод и SMD-резисторы. Я знаю, что паять SMD-компоненты немного сложно, но они экономят много места на печатной плате.
Вот инструменты для пайки, которые я использовал:
Прочитайте наш обзор паяльника TS100: TS100 Soldering Iron Review — лучший портативный паяльник.
Вот список всех компонентов, которые нужно припаять к печатной плате (не забудьте штыревые разъёмы для подключения шилда к Wemos D1 Mini):
Начните с пайки SMD-компонентов. Затем припаяйте штыревые разъёмы. Наконец, припаяйте остальные компоненты.
Вот как выглядит мультисенсорный шилд WeMos после сборки всех деталей. Он должен идеально подключаться к ESP8266 WeMos D1 Mini.
3D-печатный корпус
Наконец, вы можете купить корпус для размещения вашей схемы. Если у вас есть 3D-принтер, вы можете изготовить собственный корпус.
Я напечатал простой корпус для размещения мультисенсорного шилда на 3D-принтере, используя 3D-принтер Creality CR-10.
Корпус состоит из двух частей: нижней части и крышки. В крышке есть место для PIR-датчика движения и прямоугольное отверстие для LDR и датчика температуры. Сбоку есть место для проводов реле и ещё одно — для USB-кабеля, который проходит через него для питания и программирования ESP8266.
Вот и всё, шилд готов!
Теперь пора написать код.
Программирование мультисенсорного шилда
Код для этого проекта запускает веб-сервер, который позволяет контролировать и управлять мультисенсорным шилдом на основе нескольких настраиваемых параметров.
Прежде чем продолжить, у вас должна быть установлена поддержка ESP8266 в Arduino IDE. Следуйте следующему руководству, чтобы установить ESP8266 в Arduino IDE, если вы этого ещё не сделали.
Обзор веб-сервера
Давайте продолжим этот проект. Веб-сервер, который мы создадим, позволяет выбрать один из 4 различных режимов управления реле:
Ручной (режим 0): в котором есть кнопка для включения и выключения реле.
Авто PIR (режим 1): включает реле при обнаружении движения. В этом режиме есть поле, в котором вы можете установить количество секунд, в течение которых выход будет включён после обнаружения движения.
LDR (режим 2): реле включается, когда освещённость опускается ниже определённого порога. Вы можете установить пороговое значение LDR от 0 до 100%.
Авто PIR и LDR (режим 3): этот режим объединяет PIR-датчик движения и LDR. Когда выбран этот режим, реле включается, когда PIR-датчик обнаруживает движение и если значение освещённости ниже порогового. В этом режиме вы можете настроить таймер и пороговое значение LDR.
На веб-сервере также есть кнопка, которую можно нажать для запроса показаний температуры. После запроса показаний температуры вы можете нажать кнопку «Remove Sensor Readings», чтобы скрыть показания для оптимизации производительности веб-сервера.
В каждом режиме есть метка, которая показывает выбранный режим, а также текущее состояние выхода.
Мы хотим, чтобы ESP8266 запоминал последнее состояние выхода и настройки на случай сброса или внезапного отключения питания. Поэтому нам нужно сохранить эти параметры в EEPROM ESP8266.
Установка библиотек
Перед загрузкой кода вам нужно установить две библиотеки в Arduino IDE: библиотека OneWire от Paul Stoffregen и библиотека Dallas Temperature, чтобы вы могли использовать датчик DS18B20. Выполните следующие шаги для установки этих библиотек.
Библиотека OneWire
Нажмите здесь, чтобы скачать библиотеку OneWire. У вас должна появиться папка .zip в загрузках.
Разархивируйте папку .zip, и у вас должна получиться папка OneWire-master.
Переименуйте папку из OneWire-master в OneWire.
Переместите папку OneWire в папку libraries установки Arduino IDE.
Наконец, перезапустите Arduino IDE.
Библиотека Dallas Temperature
Нажмите здесь, чтобы скачать библиотеку DallasTemperature. У вас должна появиться папка .zip в загрузках.
Разархивируйте папку .zip, и у вас должна получиться папка Arduino-Temperature-Control-Library-master.
Переименуйте папку из Arduino-Temperature-Control-Library-master в DallasTemperature.
Переместите папку DallasTemperature в папку libraries установки Arduino IDE.
Наконец, перезапустите Arduino IDE.
Код
Скопируйте следующий код в Arduino IDE.
/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/
// Load libraries
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Auxiliary variables for temperature
static char celsiusTemp[7];
static char fahrenheitTemp[7];
String temperatureString = ""; // Variable to hold the temperature reading
// EEPROM size
// Address 0: Last output state (0 = off or 1 = on)
// Address 1: Selected mode (0 = Manual, 1 = Auto PIR,
// 2 = Auto LDR, or 3 = Auto PIR and LDR)
// Address 2: Timer (time 0 to 255 seconds)
// Address 3: LDR threshold value (luminosity in percentage 0 to 100%)
#define EEPROM_SIZE 4
// Set GPIOs for: output variable, status LED, PIR Motion Sensor, and LDR
const int output = 15;
const int statusLed = 12;
const int motionSensor = 5;
const int ldr = A0;
// Store the current output state
String outputState = "off";
// GPIO where the DS18B20 is connected to
const int oneWireBus = 4;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
// Timers - Auxiliary variables
unsigned long now = millis();
unsigned long lastMeasure = 0;
boolean startTimer = false;
unsigned long currentTime = millis();
unsigned long previousTime = 0;
const long timeoutTime = 2000;
// Auxiliary variables to store selected mode and settings
int selectedMode = 0;
int timer = 0;
int ldrThreshold = 0;
int armMotion = 0;
int armLdr = 0;
String modes[4] = { "Manual", "Auto PIR", "Auto LDR", "Auto PIR and LDR" };
// Decode HTTP GET value
String valueString = "0";
int pos1 = 0;
int pos2 = 0;
// Variable to store the HTTP request
String header;
// Set web server port number to 80
WiFiServer server(80);
void setup() {
// Start the DS18B20 sensor
sensors.begin();
// Serial port for debugging purposes
Serial.begin(115200);
// PIR Motion Sensor mode, then set interrupt function and RISING mode
pinMode(motionSensor, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);
Serial.println("start...");
EEPROM.begin(EEPROM_SIZE);
// Uncomment the next lines to test the values stored in the flash memory
/*Serial.println(" bytes read from Flash . Values are:");
for(int i = 0; i < EEPROM_SIZE; i++) {
Serial.print(byte(EEPROM.read(i)));
Serial.print(" ");
}*/
// Initialize the output variable and the LED as OUTPUTs
pinMode(output, OUTPUT);
pinMode(statusLed, OUTPUT);
digitalWrite(output, HIGH);
digitalWrite(statusLed, LOW);
// Read from flash memory on start and store the values in auxiliary variables
// Set output to last state (saved in the flash memory)
if(!EEPROM.read(0)) {
outputState = "off";
digitalWrite(output, HIGH);
}
else {
outputState = "on";
digitalWrite(output, LOW);
}
selectedMode = EEPROM.read(1);
timer = EEPROM.read(2);
ldrThreshold = EEPROM.read(3);
configureMode();
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
WiFiClient client = server.available(); // Listen for incoming clients
if (client) { // If a new client connects,
currentTime = millis();
previousTime = currentTime;
Serial.println("New Client."); // print a message out in the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
currentTime = millis();
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
header += c;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// Display the HTML web page
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
// Feel free to change the background-color and font-size attributes to fit your preferences
client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
client.println(".button2 {background-color: #555555;}</style></head>");
// Request example: GET /?mode=0& HTTP/1.1 - sets mode to Manual (0)
if(header.indexOf("GET /?mode=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
selectedMode = valueString.toInt();
EEPROM.write(1, selectedMode);
EEPROM.commit();
configureMode();
}
// Change the output state - turn GPIOs on and off
else if(header.indexOf("GET /?state=on") >= 0) {
outputOn();
}
else if(header.indexOf("GET /?state=off") >= 0) {
outputOff();
}
// Set timer value
else if(header.indexOf("GET /?timer=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
timer = valueString.toInt();
EEPROM.write(2, timer);
EEPROM.commit();
Serial.println(valueString);
}
// Set LDR Threshold value
else if(header.indexOf("GET /?ldrthreshold=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
ldrThreshold = valueString.toInt();
EEPROM.write(3, ldrThreshold);
EEPROM.commit();
Serial.println(valueString);
}
// Web Page Heading
client.println("<body><h1>ESP8266 Web Server</h1>");
// Drop down menu to select mode
client.println("<p><strong>Mode selected:</strong> " + modes[selectedMode] + "</p>");
client.println("<select id=\"mySelect\" onchange=\"setMode(this.value)\">");
client.println("<option>Change mode");
client.println("<option value=\"0\">Manual");
client.println("<option value=\"1\">Auto PIR");
client.println("<option value=\"2\">Auto LDR");
client.println("<option value=\"3\">Auto PIR and LDR</select>");
// Display current state, and ON/OFF buttons for output
client.println("<p>GPIO - State " + outputState + "</p>");
// If the output is off, it displays the ON button
if(selectedMode == 0) {
if(outputState == "off") {
client.println("<p><button class=\"button\" onclick=\"outputOn()\">ON</button></p>");
}
else {
client.println("<p><button class=\"button button2\" onclick=\"outputOff()\">OFF</button></p>");
}
}
else if(selectedMode == 1) {
client.println("<p>Timer (0 and 255 in seconds): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(2)) + "\" onchange=\"setTimer(this.value)\" min=\"0\" max=\"255\"></p>");
}
else if(selectedMode == 2) {
client.println("<p>LDR Threshold (0 and 100%): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(3)) + "\" onchange=\"setThreshold(this.value)\" min=\"0\" max=\"100\"></p>");
}
else if(selectedMode == 3) {
client.println("<p>Timer (0 and 255 in seconds): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(2)) + "\" onchange=\"setTimer(this.value)\" min=\"0\" max=\"255\"></p>");
client.println("<p>LDR Threshold (0 and 100%): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(3)) + "\" onchange=\"setThreshold(this.value)\" min=\"0\" max=\"100\"></p>");
}
// Get and display DHT sensor readings
if(header.indexOf("GET /?sensor") >= 0) {
sensors.requestTemperatures();
temperatureString = " " + String(sensors.getTempCByIndex(0)) + "C " +
String(sensors.getTempFByIndex(0)) + "F";
client.println("<p>");
client.println(temperatureString);
client.println("</p>");
client.println("<p><a href=\"/\"><button>Remove Sensor Readings</button></a></p>");
}
else {
client.println("<p><a href=\"?sensor\"><button>View Sensor Readings</button></a></p>");
}
client.println("<script> function setMode(value) { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?mode=\" + value + \"&\", true);");
client.println("xhr.send(); location.reload(true); } ");
client.println("function setTimer(value) { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?timer=\" + value + \"&\", true);");
client.println("xhr.send(); location.reload(true); } ");
client.println("function setThreshold(value) { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?ldrthreshold=\" + value + \"&\", true);");
client.println("xhr.send(); location.reload(true); } ");
client.println("function outputOn() { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?state=on\", true);");
client.println("xhr.send(); location.reload(true); } ");
client.println("function outputOff() { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?state=off\", true);");
client.println("xhr.send(); location.reload(true); } ");
client.println("function updateSensorReadings() { var xhr = new XMLHttpRequest();");
client.println("xhr.open('GET', \"/?sensor\", true);");
client.println("xhr.send(); location.reload(true); }</script></body></html>");
// The HTTP response ends with another blank line
client.println();
// Break out of the while loop
break;
} else { // if you got a newline, then clear currentLine
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected.");
}
// Starts a timer to turn on/off the output according to the time value or LDR reading
now = millis();
// Mode selected (1): Auto PIR
if(startTimer && armMotion && !armLdr) {
if(outputState == "off") {
outputOn();
}
else if((now - lastMeasure > (timer * 1000))) {
outputOff();
startTimer = false;
}
}
// Mode selected (2): Auto LDR
// Read current LDR value and turn the output accordingly
if(armLdr && !armMotion) {
int ldrValue = map(analogRead(ldr), 0, 1023, 0, 100);
Serial.println(ldrValue);
if(ldrValue < ldrThreshold && outputState == "on") {
outputOff();
}
else if(ldrValue > ldrThreshold && outputState == "off") {
outputOn();
}
delay(100);
}
// Mode selected (3): Auto PIR and LDR
if(startTimer && armMotion && armLdr) {
int ldrValue = map(analogRead(ldr), 0, 1023, 0, 100);
Serial.println(ldrValue);
if(ldrValue < ldrThreshold) {
outputOff();
startTimer = false;
Serial.println("a");
}
else if(ldrValue > ldrThreshold && outputState == "off") {
outputOn();
Serial.println("b");
}
else if(now - lastMeasure > (timer * 1000)) {
outputOff();
startTimer = false;
Serial.println("c");
}
}
}
// Checks if motion was detected and the sensors are armed. Then, starts a timer.
ICACHE_RAM_ATTR void detectsMovement() {
if(armMotion || (armMotion && armLdr)) {
Serial.println("MOTION DETECTED!!!");
startTimer = true;
lastMeasure = millis();
}
}
void configureMode() {
// Mode: Manual
if(selectedMode == 0) {
armMotion = 0;
armLdr = 0;
// Status LED: off
digitalWrite(statusLed, LOW);
}
// Mode: Auto PIR
else if(selectedMode == 1) {
outputOff();
armMotion = 1;
armLdr = 0;
// Status LED: on
digitalWrite(statusLed, HIGH);
}
// Mode: Auto LDR
else if(selectedMode == 2) {
armMotion = 0;
armLdr = 1;
// Status LED: on
digitalWrite(statusLed, HIGH);
}
// Mode: Auto PIR and LDR
else if(selectedMode == 3) {
outputOff();
armMotion = 1;
armLdr = 1;
// Status LED: on
digitalWrite(statusLed, HIGH);
}
}
// Change output pin to on or off
void outputOn() {
Serial.println("GPIO on");
outputState = "on";
digitalWrite(output, LOW);
EEPROM.write(0, 1);
EEPROM.commit();
}
void outputOff() {
Serial.println("GPIO off");
outputState = "off";
digitalWrite(output, HIGH);
EEPROM.write(0, 0);
EEPROM.commit();
}
Этот код довольно длинный для объяснения, поэтому вы можете просто заменить следующие две переменные вашими сетевыми учётными данными, и код будет работать сразу.
const char* ssid = "";
const char* password = "";
Если вы хотите узнать, как работает этот код, продолжайте чтение.
Как работает код
Начнём с подключения необходимых библиотек.
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
Библиотека ESP8266WiFi необходима для использования возможностей Wi-Fi ESP. Библиотека EEPROM позволяет читать и записывать постоянные данные в EEPROM ESP8266, а библиотеки OneWire и DallasTemperature позволяют считывать температуру с датчика температуры DS18B20.
Настройка сетевых учётных данных
Вам нужно добавить ваши сетевые учётные данные в эти две переменные.
const char* ssid = "";
const char* password = "";
Это вспомогательные переменные для хранения температуры в градусах Цельсия/Фаренгейта:
// Auxiliary variables for temperature and humidity
static char celsiusTemp[7];
static char fahrenheitTemp[7];
static char humidityTemp[7];
String temperatureString = ""; // Variable to hold the temperature reading
EEPROM
Далее мы определяем размер EEPROM, к которому хотим получить доступ.
#define EEPROM_SIZE 4
Нам нужно сохранить четыре значения во флеш-памяти: последнее состояние выхода по адресу 0, выбранный режим по адресу 1, значение таймера по адресу 2 и пороговое значение LDR по адресу 3. Таким образом, нам нужно 4 байта во флеш-памяти.
Адрес 0: Последнее состояние выхода (0 = выключен или 1 = включён)
Адрес 1: Выбранный режим (0 = Ручной, 1 = Авто PIR, 2 = Авто LDR или 3 = Авто PIR и LDR)
Адрес 2: Таймер (время от 0 до 255 секунд)
Адрес 3: Пороговое значение LDR (освещённость в процентах от 0 до 100%)
Определение GPIO
В этом разделе мы определяем GPIO для выхода, статусного светодиода, PIR-датчика движения и LDR.
const int output = 15;
const int statusLed = 12;
const int motionSensor = 5;
const int ldr = A0;
Мы также создаём строковую переменную для хранения outputState, которая будет отображаться на веб-сервере.
String outputState = "off";
Датчик температуры
Далее создаём экземпляры, необходимые для датчика температуры. Датчик температуры подключён к GPIO 4.
// GPIO where the DS18B20 is connected to
const int oneWireBus = 4;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
Таймеры
Далее мы создаём вспомогательные переменные для таймеров:
long now = millis();
long lastMeasure = 0;
boolean startTimer = false;
Выбранный режим и настройки
Здесь мы инициализируем переменные для хранения выбранного режима и настроек:
int selectedMode = 0;
int timer = 0;
int ldrThreshold = 0;
int armMotion = 0;
int armLdr = 0;
String modes[4] = { "Manual", "Auto PIR", "Auto LDR", "Auto PIR and LDR" };
Настройка переменных для веб-сервера
Следующий фрагмент кода связан с веб-сервером. Вы можете ознакомиться с руководство по веб-серверу ESP8266, чтобы разобраться с базовым кодом веб-сервера.
// Decode HTTP GET value
String valueString = "0";
int pos1 = 0;
int pos2 = 0;
// Variable to store the HTTP request
String header;
// Set web server port number to 80
WiFiServer server(80);
setup()
В функции setup() начнём с инициализации датчика температуры DS18B20.
sensors.begin();
Инициализируем последовательный порт на скорости 115200 бод для отладки.
Serial.begin(115200);
Прерывание
Устанавливаем PIR-датчик движения как INPUT_PULLUP и определяем его как прерывание в режиме RISING.
pinMode(motionSensor, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);
Флеш-память
Эта часть кода инициализирует флеш-память с размером EEPROM, определённым ранее.
EEPROM.begin(EEPROM_SIZE);
Устанавливаем статусный светодиод и выходной пин как выходы.
pinMode(output, OUTPUT);
pinMode(statusLed, OUTPUT);
Мы управляем реле с инвертированной логикой, поэтому начинаем с установки его в HIGH, чтобы оно фактически начало в выключенном состоянии.
digitalWrite(output, HIGH);
digitalWrite(statusLed, HIGH);
Затем устанавливаем выход в последнее сохранённое состояние. Состояние выхода сохранено в позиции 0, поэтому используем EEPROM.read(0).
Мы проверяем, сохранено ли состояние как включённое или выключенное, чтобы соответственно обновить состояние выхода.
if(!EEPROM.read(0)) {
outputState = "off";
digitalWrite(output, HIGH);
}
else {
outputState = "on";
digitalWrite(output, LOW);
}
Мы также обновляем все переменные, которые хранят настройки, значениями, сохранёнными в EEPROM, такими как выбранный режим, таймер и пороговое значение LDR.
selectedMode = EEPROM.read(1);
timer = EEPROM.read(2);
ldrThreshold = EEPROM.read(3);
Затем мы вызываем функцию configureMode() для назначения правильных значений для каждого режима.
configureMode();
Функция configureMode()
Давайте рассмотрим, как работает эта функция. Если выбран ручной режим, движение не активировано (armMotion), как и LDR (armLdr). Мы также устанавливаем статусный светодиод в LOW, чтобы показать, что мы находимся в ручном режиме (в автоматических режимах статусный светодиод включён).
if(selectedMode == 0) {
armMotion = 0;
armLdr = 0;
// Status LED: off
digitalWrite(statusLed, LOW);
}
Аналогичный процесс выполняется для настройки других режимов. Вы изменяете переменные arm для активации или деактивации датчика. Теперь давайте вернёмся к setup().
Подключение Wi-Fi
Здесь мы подключаемся к сети Wi-Fi и выводим IP-адрес ESP8266 в Serial Monitor.
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
loop()
В функции loop() мы отображаем веб-сервер и выполняем действия в соответствии с выбранным режимом и настройками.
Мы подробно рассмотрели веб-серверы в руководство по веб-серверу ESP8266. Поэтому мы рассмотрим только те части, которые наиболее важны для этого проекта.
Эту часть кода легче понять, если мы объясним, что происходит, на живой демонстрации.
Когда вы получите доступ к веб-серверу, вы увидите похожую веб-страницу.
Вверху вы можете выбрать один из четырёх различных режимов.
Manual (режим 0)
Auto PIR (режим 1)
Auto LDR (режим 2)
Auto PIR and LDR (режим 3)
Ручной режим
Например, если вы выберете ручной режим, выполняется следующая часть кода.
if(header.indexOf("GET /?mode=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
selectedMode = valueString.toInt();
EEPROM.write(1, selectedMode);
EEPROM.commit();
configureMode();
}
Он сохраняет выбранный режим в переменной selectedMode и сохраняет его во флеш-памяти с помощью:
EEPROM.write(1, selectedMode);
Внешний вид веб-страницы меняется в зависимости от выбранного режима. В данном случае, поскольку мы выбрали ручной режим, который соответствует 0, следующее условие if является истинным, и веб-страница отобразит две кнопки для управления выходом.
if(selectedMode == 0) {
if(outputState == "off") {
client.println("<p><button class=\"button\" onclick=\"outputOn()\">ON</button></p>");
}
else {
client.println("<p><button class=\"button button2\" onclick=\"outputOff()\">OFF</button></p>");
}
}
При нажатии кнопок включения и выключения выполняется следующий код, и одно из двух условий else if включает или выключает выход.
// Change the output state - turn GPIOs on and off
else if(header.indexOf("GET /?state=on") >= 0) {
outputOn();
}
else if(header.indexOf("GET /?state=off") >= 0) {
outputOff();
}
Режим Авто PIR
Теперь выберите в выпадающем меню режим Auto PIR.
Появляется новое поле ввода на веб-странице. Это поле позволяет ввести целое число от 0 до 255 для указания количества секунд, в течение которых выход должен оставаться включённым после обнаружения движения.
Когда вы изменяете число, вызывается следующая часть кода и изменяется переменная timer.
else if(header.indexOf("GET /?timer=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
timer = valueString.toInt();
EEPROM.write(2, timer);
EEPROM.commit();
Serial.println(valueString);
}
В этом режиме (режим 1) отображается только поле ввода для таймера.
else if(selectedMode == 1) {
client.println("<p>Timer (0 and 255 in seconds): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(2)) + "\" onchange=\"setTimer(this.value)\" min=\"0\" max=\"255\"></p>");
}
Режим Авто LDR
Выберите режим Auto LDR, и появится новое поле ввода.
Это устанавливает пороговое значение LDR, и вы можете ввести число от 0 до 100 для указания % освещённости. Когда вы изменяете это поле, вызывается следующая часть кода для обновления порогового значения LDR:
else if(header.indexOf("GET /?ldrthreshold=") >= 0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
ldrThreshold = valueString.toInt();
EEPROM.write(3, ldrThreshold);
EEPROM.commit();
Serial.println(valueString);
}
Это режим 2, и он отображает поле ввода порогового значения LDR.
else if(selectedMode == 2) {
client.println("<p>LDR Threshold (0 and 100%): <input type=\"number\" name=\"txt\" value=\"" +
String(EEPROM.read(3)) + "\" onchange=\"setThreshold(this.value)\" min=\"0\" max=\"100\"></p>");
}
Режим Авто PIR и LDR
Выбор режима Auto PIR и LDR активирует одновременно PIR и LDR. Также загружается новая веб-страница с двумя полями ввода.
Оба поля ввода работают так же, как мы описали ранее.
Показания датчиков
Наконец, есть кнопка для запроса и отображения показаний температуры.
if(header.indexOf("GET /?sensor") >= 0) {
sensors.requestTemperatures();
temperatureString = " " + String(sensors.getTempCByIndex(0)) + "C " +
String(sensors.getTempFByIndex(0)) + "F";
client.println("<p>");
client.println(temperatureString);
client.println("</p>");
Также есть кнопка, которую можно нажать, чтобы убрать эти показания.
client.println("<p><a href=\"/\"><button>Remove Sensor Readings</button></a></p>");
Вот как вы настраиваете параметры вашего мультисенсора. Затем, в зависимости от выбранного режима и настроек, другая часть loop() запускается для проверки того, должен ли выход быть включённым или выключённым.
Управление состоянием выхода
Например, когда обнаруживается движение, вызывается функция detectsMovement(), которая запускает таймер.
void detectsMovement() {
if(armMotion || (armMotion && armLdr)) {
Serial.println("MOTION DETECTED!!!");
startTimer = true;
lastMeasure = millis();
}
}
Затем, в зависимости от прошедшего времени, выход включается или выключается.
// Mode selected (1): Auto PIR
if(startTimer && armMotion && !armLdr) {
if(outputState == "off") {
outputOn();
}
else if((now - lastMeasure > (timer * 1000))) {
outputOff();
startTimer = false;
}
}
Также есть следующий раздел кода для включения или выключения выхода в зависимости от освещённости и порогового значения.
// Mode selected (2): Auto LDR
// Read current LDR value and turn the output accordingly
if(armLdr && !armMotion) {
int ldrValue = map(analogRead(ldr), 0, 1024, 0, 100);
//Serial.println(ldrValue);
if(ldrValue > ldrThreshold && outputState == "on") {
outputOff();
}
else if(ldrValue < ldrThreshold && outputState == "off") {
outputOn();
}
delay(100);
}
Наконец, следующий фрагмент кода выполняется, когда выбран режим авто PIR и LDR и обнаружено движение.
// Mode selected (3): Auto PIR and LDR
if(startTimer && armMotion && armLdr) {
int ldrValue = map(analogRead(ldr), 0, 4095, 0, 100);
//Serial.println(ldrValue);
if(ldrValue > ldrThreshold) {
outputOff();
startTimer = false;
}
else if(ldrValue < ldrThreshold && outputState == "off") {
outputOn();
}
else if(now - lastMeasure > (timer * 1000)) {
outputOff();
startTimer = false;
}
}
В целом, именно так работает код. Мы также приложили усилия, чтобы написать множество комментариев в коде, чтобы его было легче понять.
Мы запрограммировали мультисенсорный шилд WeMos этим кодом, но вы можете написать свой собственный код для интеграции с любой платформой домашней автоматизации. Вам просто нужно учитывать назначение выводов мультисенсорного шилда.
Загрузка кода
Нажмите кнопку загрузки, чтобы загрузить код в ESP8266. Убедитесь, что у вас выбрана правильная плата и COM-порт.
Тестирование мультисенсорного шилда
Откройте Serial Monitor на скорости 112500 бод. Нажмите кнопку enable на ESP8266, чтобы вывести IP-адрес ESP.
Откройте браузер и введите IP-адрес ESP8266. Должна загрузиться следующая страница.
Мы подключили модуль реле к выходному разъёму клеммника, поэтому управляем 12V лампой, но вы можете управлять любым выходом, каким хотите.
Теперь выберите каждый режим, попробуйте установить различные настройки, чтобы проверить, всё ли работает правильно.
Например, выберите ручной режим и включите и выключите лампу.
Выберите режим Auto PIR. В этом режиме лампа включается на установленное вами количество секунд при обнаружении движения.
В режиме LDR вы можете установить пороговое значение, при котором лампа загорится. Когда я закрываю LDR, освещённость опускается ниже порога, и лампа загорается.
В режиме Auto PIR и LDR я могу установить таймер и пороговое значение LDR.
Если движение обнаружено, но интенсивность света выше порога, ничего не происходит. Но если я закрою LDR, что означает отсутствие света, и будет обнаружено движение, лампа включится на количество секунд, которое я задал в настройках.
Вы также можете нажать кнопку «View Sensor Readings», чтобы запросить последние показания температуры.
Подведение итогов
Мы раздаём 5 голых печатных плат тому, кто оставит комментарий ниже! Просто напишите комментарий в этой записи блога о том, что бы вы хотели сделать с печатной платой, и вы получите шанс выиграть одну из этих голых печатных плат. Победители будут объявлены на следующей неделе! Так что следите за обновлениями! [Обновление] розыгрыш завершён, и победителями стали: Ram Chivukula, Phillip R. Hickman, Douglas W Murray, Terry Wegener и Dale Wolver.
Вот и всё для этого проекта. Мы надеемся, что вы нашли этот проект полезным и сможете собрать его самостоятельно. Вы можете запрограммировать мультисенсорный шилд другим кодом, подходящим для ваших нужд. Например, вы можете управлять выходом на основе текущего значения температуры. Вы также можете отредактировать Gerber-файлы и добавить другие функции к мультисенсорному шилду ESP8266.
Если вам понравился этот проект, вам также могут понравиться другие связанные проекты:
Спасибо за чтение.