Merge pull request #1 from SasaKaranovic/AdjustableFlow

Part3
This commit is contained in:
Sasa Karanovic
2021-10-22 00:27:20 -04:00
committed by GitHub
9 changed files with 1932 additions and 25 deletions
@@ -1,3 +1,4 @@
#include <EEPROM.h>
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include <esp_task_wdt.h>
@@ -7,12 +8,17 @@
#include "Wire.h"
#define WDT_TIMEOUT 20000
#define EEPROM_SIZE 4
AsyncWebServer server(80);
int WiFi_status = WL_IDLE_STATUS;
volatile uint32_t nFlowSensorCount = 0;
volatile uint32_t nFlowSensorCount_last = 0;
char dbgBuff[4024] = {0};
uint32_t dbgBuffPos = 0;
float fImpulsePerML = 0;
void IRAM_ATTR ISR_flowSensor() {
nFlowSensorCount++;
}
@@ -37,6 +43,21 @@ void setup()
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
// EEPROM begin
EEPROM.begin(EEPROM_SIZE);
// Debug EEPROM before read
Serial.println("Before read: fImpulsePerML");
Serial.println((float)(fImpulsePerML),4);
// Read flow value from EEPROM
fImpulsePerML = EEPROM.readFloat(0);
// Debug EEPROM after read
Serial.println("After read: fImpulsePerML");
Serial.println((float)(fImpulsePerML),4);
// Configure WDT
Serial.println("Configuring WDT...");
esp_task_wdt_init(WDT_TIMEOUT, true); //enable panic so ESP32 restarts
@@ -61,7 +82,20 @@ void setup()
setupWebServer();
server.begin();
attachInterrupt(FLOW_SENSOR_PIN, ISR_flowSensor, FALLING);
flowInterruptEnabled(true);
}
void flowInterruptEnabled(bool state)
{
if(state)
{
attachInterrupt(FLOW_SENSOR_PIN, ISR_flowSensor, FALLING);
}
else
{
detachInterrupt(FLOW_SENSOR_PIN);
}
}
@@ -257,5 +291,48 @@ void setupWebServer(void)
request->send(200, "text/plain", "OK");
return;
});
// Calibrate flow sensor
server.on("/calibrateFlowSensor", HTTP_GET, [] (AsyncWebServerRequest *request) {
Serial.println("Calibrate edges per mL");
if ( request->hasParam("edges") )
{
String xEdges;
float fEdges = 0;
xEdges = request->getParam("edges")->value();
fEdges = xEdges.toFloat();
// Update value
fImpulsePerML = fEdges;
EEPROM.writeFloat(0, fEdges);//EEPROM.put(address, param);
EEPROM.commit();
Serial.print("Changing fImpulsePerML to ");
Serial.println((float)(fImpulsePerML),4);
request->send(200, "text/plain", "OK");
return;
}
else
{
request->send(404, "text/plain", "MISSING_ARGUMENT");
return;
}
});
}
void LED_Blink(uint8_t nTimes, uint16_t n_delayPeriod)
{
while(nTimes--)
{
digitalWrite(LED_PIN, HIGH);
delay(n_delayPeriod);
digitalWrite(LED_PIN, LOW);
delay(n_delayPeriod);
}
}
@@ -42,7 +42,7 @@ uint8_t gnSolenoidsOnBusCnt = 0;
// Plant watering definitions
#define WATERING_VOLUME_MINIMUM 10
#define WATERING_VOLUME_MAXIMUM 550
#define WATERING_FLOW_EDGES_PER_ML 4
#define WATERING_FLOW_EDGES_PER_L 5880 //98
// PlantSystem_tick periods
#define PERIOD_RESCAN_BUS 24*3600*1000 // Every 24h
@@ -66,6 +66,7 @@ uint32_t nTimeout = 0;
void PlantSystem_tick(void)
{
bool bWateringError = false;
esp_task_wdt_reset();
// -- Rescan I2C bus
@@ -124,15 +125,23 @@ void PlantSystem_tick(void)
// -- Process watering request
if(bWatering_requestPending || bWatering_inProgress)
{
// Watering logic
// Pump creates high pressure inside watering system. Because of this
// at the beginning of watering process we will first, open valve, then turn on pump.
// When we are done, we will first turn off pump and then solenoid.
//
// In previous version, water pump was too weak so we turned on the pump first to keep the pressure on
// and prevent water from "backing up" (i.e letting air in from plant side).
// With peristaltic pump we can open the solenoids first and still keep air out of the system.
//
Serial.println("bWatering_requestPending | bWatering_inProgress");
// Transition to in progress
bWatering_inProgress = true;
bWatering_requestPending = false;
nFlowSensorCount = 0;
// Note: Solenoid address and volume must have been validated
Serial.println("Turning pump ON");
digitalWrite(WATER_PUMP_EN_PIN, HIGH);
delay(200);
// - Open solenoid
Serial.println("Energizing solenoid");
if (PlantSystem_SetSolenoidState(nWatering_i2cAddress, true) != true)
{
@@ -141,33 +150,84 @@ void PlantSystem_tick(void)
digitalWrite(WATER_PUMP_EN_PIN, LOW);
Serial.println("Turning pump OFF");
Serial.println("Failed to open solenoid. Aborting...");
LED_Blink(4, 250);
return;
}
// - Reset flow counter
nFlowSensorCount = 0;
nFlowSensorCount_last = 0;
flowInterruptEnabled(true);
// - Turn ON pump
bWateringError = true;
Serial.println("Turning pump ON");
digitalWrite(WATER_PUMP_EN_PIN, HIGH);
esp_task_wdt_reset();
nTimeout = millis() + 10000;
while(nFlowSensorCount < (WATERING_FLOW_EDGES_PER_ML*nWatering_volume))
nTimeout = millis() + 10000; // 10sec time-out for watering
// Calculate water flow
uint32_t nWaterFlow_millis_timestamp = millis();
float flowMilliLitres = 0.0;
unsigned long totalMilliLitres = 0;
// Wait until target mL or timeout is reached
while(millis() < nTimeout)
{
if(millis() > nTimeout)
// Calculate mL every second
if( millis() >= nWaterFlow_millis_timestamp)
{
break;
// Disable interrupts
flowInterruptEnabled(false);
// Update mL
flowMilliLitres = nFlowSensorCount/fImpulsePerML;
totalMilliLitres += flowMilliLitres;
// Check if target has been reached
if(totalMilliLitres >= nWatering_volume)
{
bWateringError = false;
break;
}
// Update for next iteration
nWaterFlow_millis_timestamp = millis() + 1000;
nFlowSensorCount = 0;
esp_task_wdt_reset();
flowInterruptEnabled(true);
}
}
esp_task_wdt_reset();
Serial.println("De-Energizing solenoid");
PlantSystem_SetSolenoidState(nWatering_i2cAddress, false);
delay(200);
// - Turn OFF pump
digitalWrite(WATER_PUMP_EN_PIN, LOW);
Serial.println("Turning pump OFF");
delay(200);
// - Close solenoid
Serial.println("De-Energizing solenoid");
PlantSystem_SetSolenoidState(nWatering_i2cAddress, false);
// Store last flow data and prepare for new request
nFlowSensorCount_last = nFlowSensorCount;
nFlowSensorCount_last = 0;
nFlowSensorCount = 0;
nWatering_volume = 0;
// Reset request
bWatering_inProgress = false;
bWatering_requestPending = false;
if(bWateringError)
{
LED_Blink(4, 250);
}
else
{
LED_Blink(2, 500);
}
}
@@ -610,10 +670,8 @@ void PlantSystem_RequestPrimeSystem(void)
bool PlantSystem_PrimeSystemWithWater(void)
{
// Turn ON Pump
Serial.println("Turning pump ON");
digitalWrite(WATER_PUMP_EN_PIN, HIGH);
delay(1000);
bool bSolenoidOpen = false;
// Turn ON all solenoids
Serial.println("Energizing ALL solenoids");
@@ -623,6 +681,7 @@ bool PlantSystem_PrimeSystemWithWater(void)
{
Serial.print("Openned solenoid 0x");
Serial.println(gSolenoidsOnBus[i].i2cAddress, HEX);
bSolenoidOpen = true;
}
else
{
@@ -630,11 +689,25 @@ bool PlantSystem_PrimeSystemWithWater(void)
Serial.println(gSolenoidsOnBus[i].i2cAddress, HEX);
}
delay(500);
// Only turn on pump AFTER solenoid has oppened
if(bSolenoidOpen)
{
// Turn ON Pump
Serial.println("Turning pump ON");
digitalWrite(WATER_PUMP_EN_PIN, HIGH);
delay(1000);
}
}
// Let the water flow for a while
delay(5000);
// Turn OFF pump
delay(1000);
Serial.println("Turning pump OFF");
digitalWrite(WATER_PUMP_EN_PIN, LOW);
// Turn OFF all solenoids
Serial.println("De-energizing ALL solenoids");
for(uint8_t i=0; i<gnSolenoidsOnBusCnt; i++)
@@ -653,11 +726,6 @@ bool PlantSystem_PrimeSystemWithWater(void)
}
// Turn OFF pump
delay(1000);
Serial.println("Turning pump OFF");
digitalWrite(WATER_PUMP_EN_PIN, LOW);
nFlowSensorCount = 0;
return true;
@@ -0,0 +1,632 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 9,
"iteration": 1634868145960,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "MySQL - Plant System",
"description": "",
"fieldConfig": {
"defaults": {},
"overrides": []
},
"fill": 0,
"fillGradient": 0,
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 2,
"legend": {
"alignAsTable": true,
"avg": true,
"current": true,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.5.4",
"pointradius": 2,
"points": true,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "sensorID",
"rawQuery": true,
"rawSql": "SELECT\r\n UNIX_TIMESTAMP(`insertTime`) AS `time`,\r\n `sensor_links`.`plantName` AS `metric`,\r\n `sensor_data`.`T` as `value` \r\nFROM `sensor_data`\r\nLEFT JOIN `sensor_links` ON\r\n`sensor_data`.`address` = `sensor_links`.`sensorAddress`\r\nWHERE $__timeFilter(`insertTime`)",
"refId": "Temperature",
"select": [
[
{
"params": [
"id"
],
"type": "column"
}
]
],
"table": "sensor_data",
"timeColumn": "timestamp",
"timeColumnType": "int",
"where": [
{
"name": "$__unixEpochFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Temperature",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"transformations": [
{
"id": "seriesToColumns",
"options": {
"byField": "Time"
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {
"Cactus Large": 2,
"Cactus Small": 4,
"Palm Tree": 3,
"Pineapple": 1,
"Time": 0
},
"renameByName": {}
}
}
],
"transparent": true,
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:66",
"decimals": 1,
"format": "celsius",
"label": "",
"logBase": 1,
"max": "55",
"min": "20",
"show": true
},
{
"$$hashKey": "object:67",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "MySQL - Plant System",
"description": "",
"fieldConfig": {
"defaults": {},
"overrides": []
},
"fill": 0,
"fillGradient": 0,
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 11
},
"hiddenSeries": false,
"id": 3,
"legend": {
"alignAsTable": true,
"avg": true,
"current": true,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.5.4",
"pointradius": 2,
"points": true,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "sensorID",
"rawQuery": true,
"rawSql": "SELECT\r\n UNIX_TIMESTAMP(`insertTime`) AS `time`,\r\n `sensor_links`.`plantName` AS metric,\r\n (4095-`sensor_data`.`M`) AS `value`\r\nFROM `sensor_data`\r\nLEFT JOIN `sensor_links` ON\r\n`sensor_data`.`address` = `sensor_links`.`sensorAddress`\r\nWHERE $__timeFilter(`insertTime`)",
"refId": "Soil Moisture",
"select": [
[
{
"params": [
"id"
],
"type": "column"
}
]
],
"table": "sensor_data",
"timeColumn": "timestamp",
"timeColumnType": "int",
"where": [
{
"name": "$__unixEpochFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Soil Moisture",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"transformations": [
{
"id": "seriesToColumns",
"options": {
"byField": "Time"
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {
"Cactus Large": 2,
"Cactus Small": 4,
"Palm Tree": 3,
"Pineapple": 1,
"Time": 0
},
"renameByName": {}
}
}
],
"transparent": true,
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:66",
"decimals": 0,
"format": "none",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:67",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "MySQL - Plant System",
"description": "",
"fieldConfig": {
"defaults": {},
"overrides": []
},
"fill": 0,
"fillGradient": 0,
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 21
},
"hiddenSeries": false,
"id": 4,
"legend": {
"alignAsTable": true,
"avg": true,
"current": true,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.5.4",
"pointradius": 2,
"points": true,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "sensorID",
"rawQuery": true,
"rawSql": "SELECT\r\n UNIX_TIMESTAMP(`insertTime`) AS `time`,\r\n `sensor_links`.`plantName` AS metric,\r\n (4095-`sensor_data`.`A`) AS `value`\r\nFROM `sensor_data`\r\nLEFT JOIN `sensor_links` ON\r\n`sensor_data`.`address` = `sensor_links`.`sensorAddress`\r\nWHERE $__timeFilter(`insertTime`)",
"refId": "Soil Moisture",
"select": [
[
{
"params": [
"id"
],
"type": "column"
}
]
],
"table": "sensor_data",
"timeColumn": "timestamp",
"timeColumnType": "int",
"where": [
{
"name": "$__unixEpochFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Ambient Light",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"transformations": [
{
"id": "seriesToColumns",
"options": {
"byField": "Time"
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {
"Cactus Large": 2,
"Cactus Small": 4,
"Palm Tree": 3,
"Pineapple": 1,
"Time": 0
},
"renameByName": {}
}
}
],
"transparent": true,
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:66",
"decimals": 0,
"format": "none",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:67",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "MySQL - Plant System",
"fieldConfig": {
"defaults": {},
"overrides": []
},
"fill": 0,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 31
},
"hiddenSeries": false,
"id": 6,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": true,
"values": true
},
"lines": false,
"linewidth": 2,
"nullPointMode": "null",
"options": {
"alertThreshold": false
},
"percentage": false,
"pluginVersion": "7.5.4",
"pointradius": 5,
"points": true,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\r\n UNIX_TIMESTAMP(`timestamp`) AS `time`,\r\n `sensor_links`.`plantName` AS metric,\r\n `waterVolume` AS `value`\r\nFROM `watering_data`\r\nLEFT JOIN `sensor_links` ON\r\n`watering_data`.`solenoidAddress` = `sensor_links`.`solenoidAddress`\r\nWHERE $__timeFilter(`timestamp`)",
"refId": "A",
"select": [
[
{
"params": [
"waterVolume"
],
"type": "column"
}
]
],
"table": "watering_data",
"timeColumn": "timestamp",
"timeColumnType": "datetime",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Watering data",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"transparent": true,
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:756",
"format": "mlitre",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:757",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "1m",
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"description": null,
"error": null,
"hide": 2,
"label": "Sensor_0x10",
"name": "Sensor_0x10",
"query": "Cactus",
"skipUrlSync": false,
"type": "constant"
},
{
"description": null,
"error": null,
"hide": 2,
"label": "Sensor_0x11",
"name": "Sensor_0x11",
"query": "Palm Tree",
"skipUrlSync": false,
"type": "constant"
},
{
"description": null,
"error": null,
"hide": 2,
"label": "Sensor_0x12",
"name": "Sensor_0x12",
"query": "Avocado Tree",
"skipUrlSync": false,
"type": "constant"
},
{
"description": null,
"error": null,
"hide": 2,
"label": "Sensor_0x13",
"name": "Sensor_0x13",
"query": "Rose",
"skipUrlSync": false,
"type": "constant"
}
]
},
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Plant Overview",
"uid": "VCPJlvuGz",
"version": 33
}
@@ -0,0 +1,112 @@
-- phpMyAdmin SQL Dump
-- version 5.1.0
-- https://www.phpmyadmin.net/
--
-- Host: 192.168.0.25:3306
-- Generation Time: Oct 22, 2021 at 02:07 AM
-- Server version: 8.0.24
-- PHP Version: 7.4.16
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `plantsystem`
--
-- --------------------------------------------------------
--
-- Table structure for table `sensor_data`
--
CREATE TABLE `sensor_data` (
`id` int NOT NULL,
`insertTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`address` tinyint UNSIGNED NOT NULL,
`T` float UNSIGNED NOT NULL,
`M` smallint UNSIGNED NOT NULL,
`A` smallint UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Sensor data';
-- --------------------------------------------------------
--
-- Table structure for table `sensor_links`
--
CREATE TABLE `sensor_links` (
`id` int UNSIGNED NOT NULL,
`plantName` varchar(25) NOT NULL COMMENT 'Plant name',
`sensorAddress` tinyint UNSIGNED NOT NULL COMMENT 'I2C address of sensor attached to this plant',
`solenoidAddress` tinyint UNSIGNED NOT NULL COMMENT 'I2C address of solenoid attached to this plant'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `watering_data`
--
CREATE TABLE `watering_data` (
`id` int UNSIGNED NOT NULL,
`solenoidAddress` tinyint UNSIGNED NOT NULL,
`waterVolume` smallint UNSIGNED NOT NULL,
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `sensor_data`
--
ALTER TABLE `sensor_data`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `sensor_links`
--
ALTER TABLE `sensor_links`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `watering_data`
--
ALTER TABLE `watering_data`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `sensor_data`
--
ALTER TABLE `sensor_data`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `sensor_links`
--
ALTER TABLE `sensor_links`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `watering_data`
--
ALTER TABLE `watering_data`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
@@ -0,0 +1,319 @@
{
"name": "Watering System - Periodic Watering",
"nodes": [
{
"parameters": {},
"name": "Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [
460,
770
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM `sensor_links` WHERE `solenoidAddress` <> '0' ORDER BY `solenoidAddress` ASC"
},
"name": "Select Solenoids",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
660,
570
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"batchSize": 1,
"options": {
"reset": false
}
},
"name": "For Each Solenoid",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"position": [
850,
570
],
"alwaysOutputData": true,
"continueOnFail": true
},
{
"parameters": {
"url": "http://192.168.0.19/waterPlant",
"responseFormat": "string",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "address",
"value": "={{$json[\"solenoidAddress\"]}}"
},
{
"name": "volume",
"value": "={{$json[\"waterVolume\"]}}"
}
]
}
},
"name": "Water Plant",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1260,
570
],
"notesInFlow": false,
"notes": "Send request to water the plant with 100ml of water"
},
{
"parameters": {
"table": "watering_data",
"columns": "solenoidAddress, waterVolume",
"options": {}
},
"name": "Update `watering_data`",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
1450,
770
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"functionCode": "const sleepTime = 25000; // in miliseconds\n\nfunction sleep(milliseconds) {\n return new Promise(\n resolve => setTimeout(resolve, milliseconds)\n );\n}\n// Sleep\nawait sleep(sleepTime );\n// Output data\nreturn items;\n"
},
"name": "Delay",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1450,
570
],
"notesInFlow": false,
"notes": "Sleep"
},
{
"parameters": {
"conditions": {
"number": [],
"boolean": [
{
"value1": "={{$node[\"For Each Solenoid\"].context[\"noItemsLeft\"]}}",
"value2": true
}
]
}
},
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1660,
320
]
},
{
"parameters": {},
"name": "NoOp",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1860,
420
]
},
{
"parameters": {
"values": {
"number": [
{
"name": "waterVolume",
"value": 50
}
]
},
"options": {}
},
"name": "Water Volume",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [
1060,
570
]
},
{
"parameters": {
"chatId": "1254950103",
"text": "=Finished watering the plants!",
"additionalFields": {}
},
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1860,
270
],
"credentials": {
"telegramApi": "Telegram N8N"
}
},
{
"parameters": {
"triggerTimes": {
"item": [
{
"hour": 5
},
{
"hour": 19
}
]
}
},
"name": "Cron",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
460,
570
]
},
{
"parameters": {
"path": "f7048627-a726-4344-8d49-e7ad19c4415a",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
460,
370
],
"webhookId": "f7048627-a726-4344-8d49-e7ad19c4415a"
}
],
"connections": {
"Select Solenoids": {
"main": [
[
{
"node": "For Each Solenoid",
"type": "main",
"index": 0
}
]
]
},
"For Each Solenoid": {
"main": [
[
{
"node": "Water Volume",
"type": "main",
"index": 0
}
]
]
},
"Water Plant": {
"main": [
[
{
"node": "Delay",
"type": "main",
"index": 0
},
{
"node": "Update `watering_data`",
"type": "main",
"index": 0
}
]
]
},
"Delay": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "NoOp",
"type": "main",
"index": 0
},
{
"node": "Telegram",
"type": "main",
"index": 0
}
],
[
{
"node": "For Each Solenoid",
"type": "main",
"index": 0
}
]
]
},
"Water Volume": {
"main": [
[
{
"node": "Water Plant",
"type": "main",
"index": 0
}
]
]
},
"Cron": {
"main": [
[
{
"node": "Select Solenoids",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Select Solenoids",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"id": "8"
}
@@ -0,0 +1,186 @@
{
"name": "Plant System - Read Sensor Data",
"nodes": [
{
"parameters": {},
"name": "Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [
250,
350
]
},
{
"parameters": {
"url": "http://192.168.0.19/sensorData",
"allowUnauthorizedCerts": true,
"options": {
"followRedirect": true,
"ignoreResponseCode": true
}
},
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
500,
350
],
"continueOnFail": true
},
{
"parameters": {
"chatId": "1254950103",
"text": "=HousePlantSystem: Reading {{$node[\"HTTP Request\"].json[\"count\"]}} sensors instead of 4.",
"additionalFields": {}
},
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1000,
150
],
"credentials": {
"telegramApi": "Telegram N8N"
}
},
{
"parameters": {
"table": "sensor_data",
"columns": "address, T, M, A",
"options": {}
},
"name": "MySQL",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
1000,
350
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"functionCode": "const sensorData = [];\n\nfor (let i=0; i<items[0].json.count; i++)\n{\n\tsensorData.push( {json: items[0].json.sensors[i] });\n}\n\nreturn sensorData;\n"
},
"name": "Function",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
750,
350
]
},
{
"parameters": {
"triggerTimes": {
"item": [
{
"mode": "everyX",
"value": 15,
"unit": "minutes"
}
]
}
},
"name": "Cron",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
250,
150
]
},
{
"parameters": {
"conditions": {
"boolean": [],
"number": [
{
"value1": "= {{$json[\"count\"]}}",
"operation": "largerEqual",
"value2": 4
}
]
}
},
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
750,
150
]
}
],
"connections": {
"Start": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Function",
"type": "main",
"index": 0
},
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"Function": {
"main": [
[
{
"node": "MySQL",
"type": "main",
"index": 0
}
]
]
},
"Cron": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[],
[
{
"node": "Telegram",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"id": "3"
}
@@ -0,0 +1,298 @@
{
"name": "PlantSystem - Water All Plants",
"nodes": [
{
"parameters": {},
"name": "Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [
700,
550
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM `sensor_links` ORDER BY `solenoidAddress` ASC"
},
"name": "Select Solenoids",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
900,
550
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"batchSize": 1,
"options": {
"reset": false
}
},
"name": "For Each Solenoid",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"position": [
1100,
550
],
"alwaysOutputData": true,
"continueOnFail": true
},
{
"parameters": {
"url": "http://192.168.0.19/waterPlant",
"responseFormat": "string",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "address",
"value": "={{$json[\"solenoidAddress\"]}}"
},
{
"name": "volume",
"value": "={{$json[\"waterVolume\"]}}"
}
]
}
},
"name": "Water Plant",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1500,
550
],
"notesInFlow": false,
"notes": "Send request to water the plant with 100ml of water"
},
{
"parameters": {
"table": "watering_data",
"columns": "solenoidAddress, waterVolume",
"options": {}
},
"name": "Update `watering_data`",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
1500,
750
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"functionCode": "const sleepTime = 15000; // in miliseconds\n\nfunction sleep(milliseconds) {\n return new Promise(\n resolve => setTimeout(resolve, milliseconds)\n );\n}\n// Sleep\nawait sleep(sleepTime );\n// Output data\nreturn items;\n"
},
"name": "Delay",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1700,
550
],
"notesInFlow": false,
"notes": "Sleep"
},
{
"parameters": {
"conditions": {
"number": [],
"boolean": [
{
"value1": "={{$node[\"For Each Solenoid\"].context[\"noItemsLeft\"]}}",
"value2": true
}
]
}
},
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1900,
300
]
},
{
"parameters": {},
"name": "NoOp",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
2100,
300
]
},
{
"parameters": {
"values": {
"number": [
{
"name": "waterVolume",
"value": 50
}
]
},
"options": {}
},
"name": "Water Volume",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [
1300,
550
]
},
{
"parameters": {
"chatId": "1254950103",
"text": "=Watering {{$json[\"plantName\"]}} with {{$json[\"waterVolume\"]}}mL of water",
"additionalFields": {}
},
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1500,
950
],
"credentials": {
"telegramApi": "Telegram N8N"
}
},
{
"parameters": {
"path": "4d08ae89-8eba-4ee4-80c2-d8f93ff51adb",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
700,
390
],
"webhookId": "4d08ae89-8eba-4ee4-80c2-d8f93ff51adb"
}
],
"connections": {
"Start": {
"main": [
[
{
"node": "Select Solenoids",
"type": "main",
"index": 0
}
]
]
},
"Select Solenoids": {
"main": [
[
{
"node": "For Each Solenoid",
"type": "main",
"index": 0
}
]
]
},
"For Each Solenoid": {
"main": [
[
{
"node": "Water Volume",
"type": "main",
"index": 0
}
]
]
},
"Delay": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"Water Plant": {
"main": [
[
{
"node": "Delay",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "NoOp",
"type": "main",
"index": 0
}
],
[
{
"node": "For Each Solenoid",
"type": "main",
"index": 0
}
]
]
},
"Water Volume": {
"main": [
[
{
"node": "Update `watering_data`",
"type": "main",
"index": 0
},
{
"node": "Telegram",
"type": "main",
"index": 0
},
{
"node": "Water Plant",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Select Solenoids",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"id": "4"
}
@@ -0,0 +1,208 @@
{
"name": "PlantSystem - Water Specific Plant",
"nodes": [
{
"parameters": {},
"name": "Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [
800,
750
]
},
{
"parameters": {
"url": "http://192.168.0.19/waterPlant",
"responseFormat": "string",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "address",
"value": "={{$json[\"solenoidAddress\"]}}"
},
{
"name": "volume",
"value": "={{$node[\"Webhook\"].json[\"query\"][\"volume\"]}}"
}
]
}
},
"name": "Water Plant",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1200,
550
],
"notesInFlow": false,
"alwaysOutputData": true,
"continueOnFail": true,
"notes": "Send request to water the plant with 100ml of water"
},
{
"parameters": {
"path": "756fac76-f3d7-4fce-b586-27e114afba46",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
800,
550
],
"webhookId": "756fac76-f3d7-4fce-b586-27e114afba46"
},
{
"parameters": {
"operation": "executeQuery",
"query": "=SELECT * from `sensor_links` where `solenoidAddress` = '{{$json[\"query\"][\"solenoidAddress\"]}}' LIMIT 1"
},
"name": "MySQL",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
1000,
550
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"table": "watering_data",
"columns": "=solenoidAddress, waterVolume",
"options": {}
},
"name": "Insert Water Amount",
"type": "n8n-nodes-base.mySql",
"typeVersion": 1,
"position": [
1810,
450
],
"credentials": {
"mySql": "NAS - MySQL"
}
},
{
"parameters": {
"functionCode": "items[0].json.waterVolume = $node[\"Webhook\"].json[\"query\"][\"volume\"];\nitems[0].json.solenoidAddress= $node[\"MySQL\"].json[\"solenoidAddress\"];\nreturn items;\n"
},
"name": "Function",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1610,
450
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json[\"data\"]}}",
"operation": "contains",
"value2": "\"result\": OK"
}
]
}
},
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1400,
550
]
},
{
"parameters": {
"chatId": "1254950103",
"text": "=Water specific plant failed: Plant #{{$node[\"MySQL\"].json[\"sensorAddress\"]}} - Volume:{{$node[\"Webhook\"].json[\"query\"][\"volume\"]}}ml",
"additionalFields": {}
},
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1600,
650
],
"credentials": {
"telegramApi": "Telegram N8N"
}
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "MySQL",
"type": "main",
"index": 0
}
]
]
},
"MySQL": {
"main": [
[
{
"node": "Water Plant",
"type": "main",
"index": 0
}
]
]
},
"Function": {
"main": [
[
{
"node": "Insert Water Amount",
"type": "main",
"index": 0
}
]
]
},
"Water Plant": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "Function",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"id": "5"
}
+9 -2
View File
@@ -22,6 +22,13 @@ Blog page: https://sasakaranovic.com/projects/diy-house-plants-watering-system-p
Build watering system for house plants. In part two of this video series, we will create a central unit that takes sensor readings and also waters our plants on command.
## Part 3: Dashboard
## Part 3: Automation and Conclusion
[![DIY House Plants Watering System - Part 3: Automation and Conclusion](http://i3.ytimg.com/vi/jLdTUMHOhRE/maxresdefault.jpg)](https://youtu.be/jLdTUMHOhRE)
Watch: [DIY House Plants Watering System // Part 2: Automation and Conclusion](https://youtu.be/jLdTUMHOhRE)
Blog page: https://sasakaranovic.com/projects/diy-house-plants-watering-system-part-3-automation/
Build watering system for house plants. In part two of this video series, we will create a central unit that takes sensor readings and also waters our plants on command.
TBD