From 14232ec7e87035ed02b23fc4189baff1224e6381 Mon Sep 17 00:00:00 2001 From: Sasa Karanovic Date: Wed, 14 Apr 2021 02:21:25 -0400 Subject: [PATCH] Initial commit. --- .../Firmware/CentralUnit/CentralUnit.ino | 197 +++++++++ .../Firmware/CentralUnit/CentralUnit_cfg.h | 13 + .../Firmware/CentralUnit/PlantSystem.h | 43 ++ .../CentralUnit/PlantSystem_MainLogic.ino | 402 ++++++++++++++++++ .../CentralUnit/PlantSystem_WebAPI.ino | 241 +++++++++++ .../Firmware/CentralUnit/wifi_credentials.h | 7 + 6 files changed, 903 insertions(+) create mode 100644 Part2_Central_board/Firmware/CentralUnit/CentralUnit.ino create mode 100644 Part2_Central_board/Firmware/CentralUnit/CentralUnit_cfg.h create mode 100644 Part2_Central_board/Firmware/CentralUnit/PlantSystem.h create mode 100644 Part2_Central_board/Firmware/CentralUnit/PlantSystem_MainLogic.ino create mode 100644 Part2_Central_board/Firmware/CentralUnit/PlantSystem_WebAPI.ino create mode 100644 Part2_Central_board/Firmware/CentralUnit/wifi_credentials.h diff --git a/Part2_Central_board/Firmware/CentralUnit/CentralUnit.ino b/Part2_Central_board/Firmware/CentralUnit/CentralUnit.ino new file mode 100644 index 0000000..405efa7 --- /dev/null +++ b/Part2_Central_board/Firmware/CentralUnit/CentralUnit.ino @@ -0,0 +1,197 @@ +#include "WiFi.h" +#include "ESPAsyncWebServer.h" +#include "CentralUnit_cfg.h" +#include "wifi_credentials.h" +#include "PlantSystem.h" +#include "Wire.h" + +AsyncWebServer server(80); +int WiFi_status = WL_IDLE_STATUS; +uint32_t nFlowSensorCount = 0; + +void IRAM_ATTR ISR_flowSensor() { + nFlowSensorCount++; +} + + +void setup() +{ + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, HIGH); + + pinMode(I2C_EN_PIN, OUTPUT); + digitalWrite(I2C_EN_PIN, HIGH); + + pinMode(WATER_PUMP_EN_PIN, OUTPUT); + digitalWrite(WATER_PUMP_EN_PIN, HIGH); + + pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP); + + Serial.begin(115200); + + Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); + + // Connect to WiFi + while ( WiFi_status != WL_CONNECTED) { + Serial.print("Connecting to SSID: "); + Serial.println(ssid); + WiFi_status = WiFi.begin(ssid, password); + + // wait 5 seconds for connection: + delay(5000); + } + + Serial.print("WiFi IP: "); + Serial.println(WiFi.localIP()); + + digitalWrite(LED_PIN, LOW); + + setupWebServer(); + server.begin(); + + attachInterrupt(WATER_PUMP_EN_PIN, ISR_flowSensor, FALLING); +} + + +void loop() +{ + while(1) + { + PlantSystem_tick(); + } +} + + + +void setupWebServer(void) +{ + server.onNotFound([](AsyncWebServerRequest *request){ + request->send(404); + }); + + // send a file when /index is requested + server.on("/index.html", HTTP_ANY, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Plant System v0.1"); + }); + + // send a file when / is requested + server.on("/", HTTP_ANY, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Plant System v0.1"); + }); + + + // Get sensors on the bus + server.on("/sensorData", HTTP_GET, [] (AsyncWebServerRequest *request) { + Serial.println("Sensor data"); + + char buff[300] = {0}; + if(PlantSystem_WAPI_SensorData(buff, 300) == true) + { + request->send(200, "text/plain", buff); + return; + } + else + { + Serial.println("PlantSystem_WAPI_SensorData() error"); + request->send(200, "text/plain", "PlantSystem_WAPI_SensorData() FAIL"); + return; + } + }); + + // Get solenoids on the bus + server.on("/solenoidData", HTTP_GET, [] (AsyncWebServerRequest *request) { + Serial.println("Solenoid data"); + + char buff[300] = {0}; + if(PlantSystem_WAPI_SolenoidList(buff, 300) == true) + { + request->send(200, "text/plain", buff); + return; + } + else + { + Serial.println("PlantSystem_WAPI_SolenoidList() error"); + request->send(200, "text/plain", "PlantSystem_WAPI_SolenoidList() FAIL"); + return; + } + }); + + + // Get devices on the bus + server.on("/deviceData", HTTP_GET, [] (AsyncWebServerRequest *request) { + Serial.println("Device data"); + + char buff[300] = {0}; + if(PlantSystem_WAPI_GetDevices(buff, 300) == true) + { + request->send(200, "text/plain", buff); + return; + } + else + { + Serial.println("PlantSystem_WAPI_GetDevices() error"); + request->send(200, "text/plain", "PlantSystem_WAPI_GetDevices() FAIL"); + return; + } + }); + + // Get devices on the bus + server.on("/flowSensor", HTTP_GET, [] (AsyncWebServerRequest *request) { + Serial.println("Flow sensor data"); + + char buff[100] = {0}; + int len; + len = snprintf(buff, 100, "{flowSensor:%d}", nFlowSensorCount); + + if(len>0) + { + request->send(200, "text/plain", buff); + return; + } + else + { + Serial.println("PlantSystem_WAPI_GetDevices() error"); + request->send(200, "text/plain", "nFlowSensorCount FAIL"); + return; + } + }); + + + // Get devices on the bus + server.on("/setSolenoid", HTTP_GET, [] (AsyncWebServerRequest *request) { + Serial.println("Set solenoid to state"); + + if ( request->hasParam("address") && request->hasParam("state") ) + { + String xAddress; + String xState; + + uint32_t nAddress = 0; + uint32_t nState = 0; + + xAddress = request->getParam("address")->value(); + nAddress = xAddress.toInt(); + + xState = request->getParam("state")->value(); + nState = xState.toInt(); + + if(PlantSystem_SetSolenoidState(nAddress, nState) == true) + { + request->send(200, "text/plain", "OK"); + return; + } + else + { + Serial.println("PlantSystem_SetSolenoidState() error"); + request->send(200, "text/plain", "PlantSystem_SetSolenoidState() FAIL"); + return; + } + } + else + { + request->send(200, "text/plain", "MISSING ARGUMENTS"); + return; + } + }); +} + diff --git a/Part2_Central_board/Firmware/CentralUnit/CentralUnit_cfg.h b/Part2_Central_board/Firmware/CentralUnit/CentralUnit_cfg.h new file mode 100644 index 0000000..b8c22e1 --- /dev/null +++ b/Part2_Central_board/Firmware/CentralUnit/CentralUnit_cfg.h @@ -0,0 +1,13 @@ +#ifndef __CENTRAL_UNIT_CFG +#define __CENTRAL_UNIT_CFG + +#define LED_PIN 2 + +#define I2C_SDA_PIN 21 +#define I2C_SCL_PIN 22 +#define I2C_EN_PIN 19 + +#define WATER_PUMP_EN_PIN 12 +#define FLOW_SENSOR_PIN 26 + +#endif \ No newline at end of file diff --git a/Part2_Central_board/Firmware/CentralUnit/PlantSystem.h b/Part2_Central_board/Firmware/CentralUnit/PlantSystem.h new file mode 100644 index 0000000..2eba104 --- /dev/null +++ b/Part2_Central_board/Firmware/CentralUnit/PlantSystem.h @@ -0,0 +1,43 @@ +#ifndef __PLANTSYSTEM_COMMS_H +#define __PLANTSYSTEM_COMMS_H + +#include + +// CMD (1byte) + DATATYPE (1byte) + DATALEN (2bytes) +#define PLANTSYSTEM_MIN_HEADER_LENGHT 4 + +typedef enum +{ + CMD_FIRST = 0x00, + CMD_GET_DEVICE_INFO, + CMD_GET_TEMPERATURE, + CMD_GET_SOILMOISTURE, + CMD_GET_AMBIENTLIGHT, + CMD_GET_SOLENOID_STATE, + CMD_SET_SOLENOID_STATE, + CMD_LAST +} plant_cmd_t; + + +typedef enum +{ + DATATYPE_FIRST = 0x00, + DATATYPE_NONE, + DATATYPE_SINGLE_VALUE, + DATATYPE_KEY_VALUE_PAIR, + DATATYPE_LAST +} plant_dataType_t; + + + +typedef enum +{ + DEVICETYPE_FIRST = 0x00, + DEVICETYPE_SENSOR_V1, + DEVICETYPE_SOLENOID_V1, + DEVICETYPE_LAST +} plant_deviceType_t; + +bool PlanSystem_ParseBuffer(uint8_t *pBuffer, uint8_t nBufferLen, uint8_t *cmd, uint8_t *dataType, uint8_t *dataLength); + +#endif \ No newline at end of file diff --git a/Part2_Central_board/Firmware/CentralUnit/PlantSystem_MainLogic.ino b/Part2_Central_board/Firmware/CentralUnit/PlantSystem_MainLogic.ino new file mode 100644 index 0000000..1d20141 --- /dev/null +++ b/Part2_Central_board/Firmware/CentralUnit/PlantSystem_MainLogic.ino @@ -0,0 +1,402 @@ +// Plant system note +// CMD (1byte) + DATATYPE (1byte) + DATALEN (2bytes) + + +// Typedefs to allow us easier tracking on devices on the bus and storing received data +typedef struct I2C_Device_TypeDef +{ + uint8_t i2cAddress; + uint8_t deviceType; +} I2C_Device_TypeDef; + +typedef struct I2C_Sensor_TypeDef +{ + uint8_t i2cAddress; + float temperature; + uint32_t soilMoisture; + uint32_t ambienLight; +} I2C_Sensor_TypeDef; + +typedef struct I2C_Solenoid_TypeDef +{ + uint8_t i2cAddress; + uint8_t lastState; +} I2C_Solenoid_TypeDef; + +// Every device that we saw on the bus +I2C_Device_TypeDef gDevicesOnBus[250] = { {.i2cAddress=0, .deviceType=0} }; +uint8_t gDevicesOnBusCount = 0; + +// All sensors that are registered on the bus +I2C_Sensor_TypeDef gSensorsOnBus[20] = { {.i2cAddress=0, .temperature=0.0, .soilMoisture=0, .ambienLight=0} }; +uint8_t gnSensorsOnBusCnt = 0; + +// All solenoids that are on the bus +I2C_Solenoid_TypeDef gSolenoidsOnBus[20] = { {.i2cAddress=0, .lastState=0} }; +uint8_t gnSolenoidsOnBusCnt = 0; + +// Periods +#define PERIOD_RESCAN_BUS 24*3600*1000 // Every 24h +#define PERIOD_RELOAD_SENSORS 15*1000 // Every 5sec + +uint32_t nPeriod_Rescanbus=0; +uint32_t nPeriod_ReloadSensors=0; + +void PlantSystem_tick(void) +{ + // -- Rescan I2C bus + if(nPeriod_Rescanbus <= millis()) + { + PlantSystem_ScanBus(); + #if 1 + PlantSystem_debugShowDevicesOnBus(); + #endif + nPeriod_Rescanbus = millis() + PERIOD_RESCAN_BUS; + } + + // -- Reload sensor data + if(nPeriod_ReloadSensors <= millis()) + { + PlantSystem_UpdateSensors(); + nPeriod_ReloadSensors = millis() + PERIOD_RELOAD_SENSORS; + } + +} + + +void PlantSystem_ScanBus(void) +{ + Serial.println ("Scanning I2C bus..."); + for (uint8_t i = 8; i< 127; i++) + { + Wire.beginTransmission (i); // Begin I2C transmission Address (i) + if (Wire.endTransmission () == 0) // Receive 0 = success (ACK response) + { + gDevicesOnBus[gDevicesOnBusCount++].i2cAddress = i; + // gDevicesOnBusCount++; + } + } + Serial.print ("Found "); + Serial.print (gDevicesOnBusCount, DEC); // numbers of devices + Serial.println (" device(s)."); + + for(uint8_t i=0; i= DEVICETYPE_LAST) + { + Serial.println (" responded with unsupported value."); + } + + } + + // -- Print all sensors + Serial.print ("There are total of "); + Serial.print (gnSensorsOnBusCnt, DEC); // numbers of devices + Serial.println (" SENSORS on I2C bus."); + for (uint8_t i = 0; i< gnSensorsOnBusCnt; i++) + { + Serial.print("Sensor @ 0x"); + Serial.println (gSensorsOnBus[i].i2cAddress, HEX); // numbers of devices + } + + // -- Print all solenoids + Serial.print ("There are total of "); + Serial.print (gnSolenoidsOnBusCnt, DEC); // numbers of devices + Serial.println (" SOLENOIDS on I2C bus."); + for (uint8_t i = 0; i< gnSolenoidsOnBusCnt; i++) + { + Serial.print("Solenoid @ 0x"); + Serial.println (gSolenoidsOnBus[i].i2cAddress, HEX); // numbers of devices + } + +} + +// -- Get device info +// Used to get information from the device (type of device i.e. sensor or solenoid etc). +// Add each device to gDevicesOnBus +// Also add to gSensorsOnBus if type is sensor or gSolenoidsOnBus if type is solenoid +void PlantSystem_getDeviceInfo(uint8_t devID) +{ + if(devID > gDevicesOnBusCount) + { + Serial.println("Invalid device ID"); + return; + } + + if(gDevicesOnBus[devID].i2cAddress == 0) + { + Serial.println("Invalid device I2C address"); + return; + } + + Serial.print("I2C: Talking to address 0x"); + Serial.println(gDevicesOnBus[devID].i2cAddress, HEX); + + uint8_t txBuffer[4] = {CMD_GET_DEVICE_INFO, DATATYPE_NONE, 0, 0}; + + I2C_SendBuffer(gDevicesOnBus[devID].i2cAddress, txBuffer, 4); + delay(200); + + uint8_t deviceType = 0; + I2C_ReadFrom(gDevicesOnBus[devID].i2cAddress, 10, &deviceType, PLANTSYSTEM_MIN_HEADER_LENGHT+1, 1); + gDevicesOnBus[devID].deviceType = deviceType; + + // Finally add sensors to gSensorsOnBus and solenoids to gSolenoidsOnBus + if(deviceType == DEVICETYPE_SENSOR_V1) + { + gSensorsOnBus[gnSensorsOnBusCnt++].i2cAddress = gDevicesOnBus[devID].i2cAddress; + } + else if(deviceType == DEVICETYPE_SOLENOID_V1) + { + gSolenoidsOnBus[gnSolenoidsOnBusCnt++].i2cAddress = gDevicesOnBus[devID].i2cAddress; + } + + #if 0 + Serial.println ("Response"); + for(uint8_t i=0; i<10; i++) + { + Serial.print ("["); + Serial.print (i, DEC); + Serial.print ("] = 0x"); + Serial.println (rxBuffer[i], HEX); + } + #endif + +} + +// -- +bool PlantSystem_SetSolenoidState(uint32_t I2CAddress, bool energized) +{ + if(I2CAddress < 0x08 || I2CAddress > 0x7F ) + { + return false; + } + + uint8_t txBuffer[5]; + uint8_t requestedState = (energized ? 1 : 0); + + // Send new solenoid value + txBuffer[0] = CMD_SET_SOLENOID_STATE; + txBuffer[1] = DATATYPE_SINGLE_VALUE; + txBuffer[2] = 0; + txBuffer[3] = 1; + txBuffer[4] = requestedState; + I2C_SendBuffer(I2CAddress, txBuffer, 5); + + // Wait 100 ms + delay(100); + + // Send read back value request + txBuffer[0] = CMD_GET_SOLENOID_STATE; + txBuffer[1] = DATATYPE_NONE; + txBuffer[2] = 0; + txBuffer[3] = 0; + I2C_SendBuffer(I2CAddress, txBuffer, 4); + + // Wait 100 ms + delay(100); + + // Read new solenoid state + uint8_t newState = 0; + I2C_ReadFrom(I2CAddress, 10, &newState, PLANTSYSTEM_MIN_HEADER_LENGHT+1, 1); + + if(requestedState == newState) + { + return true; + } + else + { + return false; + } + +} + + +// -- Read data from each registered sensor +// Temperature, Soil moisture and ambient light +void PlantSystem_UpdateSensors(void) +{ + uint8_t txBuffer[4] = {CMD_GET_TEMPERATURE, DATATYPE_NONE, 0, 0}; + uint8_t rxBuffer[4] = {0}; + + Serial.print("Reading data from sensors..."); + for(uint8_t i=0; i 20) + { + Serial.println("Invalid length!"); + return false; + } + + if(pData == NULL && nBytes > 0) + { + Serial.println("Invalid pointer!"); + return false; + } + + if(nBytes+nPos > nLen) + { + Serial.println("Buffer overflow"); + return false; + } + + uint8_t rxBuffer[20] = {0}; + + Wire.requestFrom(address, nLen, true); + while(Wire.available() == 0); + for(uint8_t i=0; i 0) + { + for(uint8_t i=0; i0) + { + currentPos += len; + } + else + { + Serial.println("Len issue"); + Serial.println(len, DEC); + return false; + } + } + + // Close JSON string + len = snprintf(&pBuff[currentPos-1], (nMaxLen-currentPos), "]}"); + + if(len >0 && currentPos 0) + { + currentPos += len; + } + else + { + Serial.println("Len issue"); + Serial.println(len, DEC); + return false; + } + } + + + // Close JSON string + len = snprintf(&pBuff[currentPos-1], (nMaxLen-currentPos), "]}"); + if(len<0) + { + Serial.println("Buffer too short"); + return false; + } + + if(len>0 && currentPos 0) + { + currentPos += len; + } + else + { + Serial.println("Len issue"); + Serial.println(len, DEC); + return false; + } + } + + + // Close JSON string + len = snprintf(&pBuff[currentPos-1], (nMaxLen-currentPos), "]}"); + if(len<0) + { + Serial.println("Buffer too short"); + return false; + } + + if(len>0 && currentPos