diff --git a/esp-now/esp-mesh-bluetooth/src/main.cpp b/esp-now/esp-mesh-bluetooth/src/main.cpp index 9715224..c626a31 100644 --- a/esp-now/esp-mesh-bluetooth/src/main.cpp +++ b/esp-now/esp-mesh-bluetooth/src/main.cpp @@ -8,7 +8,7 @@ #include #include -int DEVICE_ID = 5; // set device id, need to store in SPIFFS +int DEVICE_ID = 5; // set device id, need to store in SPIFFS String DEVICE_NAME = "Z5"; // set device name String moistureLevel = ""; @@ -17,6 +17,7 @@ int waterValue = 2803; // 1779; // enter your water value here int sensorPin = 32; int soilMoistureValue = 0; int wifiChannel = WIFI_CHANNEL; +int capacitance = 0; float soilmoisturepercent = 0; const char *fwVersion = FIRMWARE_VERSION; DynamicJsonDocument doc(1024); @@ -24,12 +25,14 @@ int espInterval = 80000; // interval for reading data uint8_t gatewayMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; String gatewayMac = "7821848D8840"; -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" BLECharacteristic *pCharacteristic; +BLEServer *pServer = nullptr; -String saveJson() { +String saveJson() +{ String msg = ""; File configFile = SPIFFS.open("/config.json", "w+"); if (configFile) @@ -55,47 +58,52 @@ String saveJson() { } return msg; } -void setWifiChannel(int32_t channel = 11) { +void setWifiChannel(int32_t channel = 11) +{ WiFi.mode(WIFI_STA); // TODO: get channel programmatically - //int32_t channel = getWiFiChannel(WIFI_SSID); + // int32_t channel = getWiFiChannel(WIFI_SSID); WiFi.printDiag(Serial); // Uncomment to verify channel number before esp_wifi_set_promiscuous(true); esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(false); -//WiFi.printDiag(Serial); // Uncomment to verify channel change after - WiFi.disconnect(); // we do not want to connect to a WiFi network -} + // WiFi.printDiag(Serial); // Uncomment to verify channel change after + WiFi.disconnect(); // we do not want to connect to a WiFi network +} -void updateWifiChannel(int channel) { +void updateWifiChannel(int channel) +{ struct_message payload = struct_message(); char msg[80]; sprintf(msg, "%d", channel); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_CHANNEL, BROADCAST, msg, espInterval, 0); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_CHANNEL, BROADCAST, msg, espInterval, 0, capacitance); Serial.printf("\nwifi: %d, %d, %s, %d, %d, %s, %s\n", espInterval, payload.from, payload.msg, payload.task, payload.type, payload.name, payload.senderAddress); payload.msgId = generateMessageHash(payload); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); // Broadcast to newly joined or to any esp still listening on default wifi channel 11 - if(wifiChannel != WIFI_CHANNEL) { + if (wifiChannel != WIFI_CHANNEL) + { setWifiChannel(WIFI_CHANNEL); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); } } -void broadcastWifiResult(int channel) { +void broadcastWifiResult(int channel) +{ struct_message payload = struct_message(); char msg[80]; sprintf(msg, "Updated wifi: from %d to %d", wifiChannel, channel); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); Serial.printf("\n%s\n", payload.msg); payload.msgId = generateMessageHash(payload); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); } -void processWifiUpdates(int channel) { +void processWifiUpdates(int channel) +{ Serial.printf("%d\n", channel); updateWifiChannel(channel); delay(300); @@ -105,11 +113,13 @@ void processWifiUpdates(int channel) { saveJson(); } -void calibrateSensor(int mode) { +void calibrateSensor(int mode) +{ struct_message payload = struct_message(); String msg = ""; char str[80]; - if(mode == CALIBRATE_AIR) { + if (mode == CALIBRATE_AIR) + { sprintf(str, "%d", airValue); std::string s(str); msg += "Air: Old=" + String(s.c_str()); @@ -117,7 +127,9 @@ void calibrateSensor(int mode) { sprintf(str, "%d", airValue); std::string s2(str); msg = msg + ", New=" + String(s2.c_str()); - } else { + } + else + { sprintf(str, "%d", waterValue); std::string s(str); msg += "Water: Old=" + String(s.c_str()); @@ -127,13 +139,14 @@ void calibrateSensor(int mode) { msg += ", New=" + String(s2.c_str()); } saveJson(); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); payload.msgId = generateMessageHash(payload); Serial.printf("\n%s, %d\n\n", payload.msg, payload.msgId); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); } -void calibrateByPercentage(int percent) { +void calibrateByPercentage(int percent) +{ struct_message payload = struct_message(); String msg = ""; char str[80]; @@ -144,15 +157,41 @@ void calibrateByPercentage(int percent) { int val = analogRead(sensorPin); // connect sensor to Analog pin int valueMinDiff = abs(val - airValue); int maxMinDiff = valueMinDiff * 100 / percent; - waterValue = abs(airValue - maxMinDiff); + waterValue = abs(airValue - maxMinDiff); sprintf(str, "%d", waterValue); std::string s2(str); msg += ", New=" + String(s2.c_str()); saveJson(); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); +} + +void setDeviceName(const char *deviceName) +{ + Serial.printf("Update device name: %s\n\n", deviceName); + + DEVICE_NAME = deviceName; + saveJson(); + + char bleName[80] = ""; + sprintf(bleName, "ESP32-LiquidPrep-%s", DEVICE_NAME); + Serial.printf("Changing BLE name to: %s\n", bleName); + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->stop(); + + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + oAdvertisementData.setName(bleName); + + pAdvertising->setAdvertisementData(oAdvertisementData); + + pAdvertising->start(); + + Serial.println("Name change complete. New name is now advertising."); } -class BLECallbacks: public BLECharacteristicCallbacks { + +class BLECallbacks : public BLECharacteristicCallbacks +{ void onWrite(BLECharacteristic *pCharacteristic) { std::string value = pCharacteristic->getValue(); @@ -164,10 +203,12 @@ class BLECallbacks: public BLECharacteristicCallbacks { { Serial.printf("*********: %d\n", value.length()); Serial.print("New value: "); - for (int i = 0; i < value.length(); i++) { - if(i%2 == 0) { + for (int i = 0; i < value.length(); i++) + { + if (i % 2 == 0) + { payload[j++] = value[i]; - Serial.print(value[i]); + Serial.print(value[i]); } } payload[j] = '\0'; @@ -178,24 +219,46 @@ class BLECallbacks: public BLECharacteristicCallbacks { deserializeJson(pdoc, payload); Serial.printf("%s, %s\n", pdoc["type"].as(), pdoc["value"].as()); - if(pdoc["type"].as() == "CHANNEL") { - int channel = atoi(pdoc["value"].as().c_str()); + if (pdoc["type"].as() == "CHANNEL") + { + int channel = atoi(pdoc["value"].as().c_str()); processWifiUpdates(channel); - } else if(pdoc["type"].as() == "CALIBRATE") { + } + else if (pdoc["type"].as() == "CALIBRATE") + { int mode = pdoc["value"].as() == "water" ? CALIBRATE_WATER : CALIBRATE_AIR; calibrateSensor(mode); } + else if (pdoc["type"].as() == "NAME") + { + setDeviceName(pdoc["value"].as().c_str()); + } + else if (pdoc["type"].as() == "PIN") + { + int newSensorPin = atoi(pdoc["value"].as().c_str()); + sensorPin = newSensorPin; // Update the global sensorPin variable + pinMode(sensorPin, INPUT); // Set the pin mode to input + saveJson(); + Serial.printf("Sensor pin updated to: %d and acknowledgment sent.\n", sensorPin); + } } } }; -void calculate() { +void calculate() +{ int val = analogRead(sensorPin); // connect sensor to Analog pin + capacitance = val; char str[8]; - if(val >= airValue) { + if (val >= airValue) + { soilmoisturepercent = 0; - } else if(val <= waterValue) { + } + else if (val <= waterValue) + { soilmoisturepercent = 100.00; - } else { + } + else + { int diff = airValue - waterValue; soilmoisturepercent = ((float)(airValue - val) / diff) * 100; } @@ -232,6 +295,59 @@ void moistureJson() Serial.printf("sensor reading: %s", moistureLevel); } +uint16_t connId = 0; // This should be globally declared if it needs to be accessed in other functions + +void enableBluetooth() +{ + char bleName[80] = ""; + sprintf(bleName, "ESP32-LiquidPrep-%s", DEVICE_NAME); + Serial.printf("Starting BLE work! %s\n", bleName); + + BLEDevice::init(bleName); + pServer = BLEDevice::createServer(); + + // Keep track of connection ID when a device connects + // pServer->setCallbacks(new MyServerCallbacks()); // you may need to define the callback to get the connection ID + + BLEService *pService = pServer->createService(SERVICE_UUID); + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE); + + pCharacteristic->setCallbacks(new BLECallbacks()); + pService->start(); + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + Serial.println("Characteristic defined! Now you can read it on your phone!"); +} + +void disableBluetooth() +{ + if (pServer) + { + uint16_t connId = 0; // Replace with actual connection ID + + pServer->getAdvertising()->stop(); + + pServer->disconnect(connId); // Now passing a connection ID + + delete pServer; + pServer = nullptr; // Reset pServer to nullptr + + Serial.println("Bluetooth disabled"); + } + else + { + Serial.println("Bluetooth is not enabled"); + } +} + // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { @@ -240,113 +356,120 @@ void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) Serial.println(""); } -void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { +void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) +{ struct_message payload = struct_message(); memcpy(&payload, incomingData, sizeof(payload)); - Serial.print("Bytes received: ------\n"); - Serial.printf("%d, moisture: %s from %s, %s, %d, %d, %d, %s\n", len, payload.moisture, payload.name, payload.hostAddress, payload.task, payload.type, payload.from, payload.msg); + + Serial.printf("Bytes received at %s: ------\n", DEVICE_NAME); + Serial.printf("%d, moisture: %s, %d from %s, %d, %d, %d, %s\n", len, payload.moisture, payload.capacitance, payload.name, payload.task, payload.type, payload.from, payload.msg); Serial.printf("=> msgId: %d\n", payload.msgId); Serial.println("------\n"); - if (isMessageSeen(payload.msgId)) { + if (isMessageSeen(payload.msgId)) + { Serial.printf("%d from %s, %d, Message %d already seen, ignoring...\n", len, payload.name, payload.task, payload.msgId); return; // The message is a duplicate, don't send it again - } else { - if(payload.hostAddress == hostMac) { + } + else + { + if (payload.hostAddress == hostMac) + { Serial.println("processing...\n"); int from = payload.from == WEB_REQUEST ? WEB_REQUEST_RESULT : NO_TASK; char msg[80]; - switch(payload.task) { - case PING: - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, from); - Serial.printf("%d, %s, %s, %s, %d, %s\n", payload.id,payload.name,payload.hostAddress,payload.senderAddress,payload.task,payload.msg); - payload.msgId = generateMessageHash(payload); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + int bluetooth = pServer ? 1 : 0; + switch (payload.task) + { + case PING: + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, from, capacitance); + Serial.printf("%d, %s, %s, %s, %d, %s\n", payload.id, payload.name, payload.hostAddress, payload.senderAddress, payload.task, payload.msg); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + break; + case QUERY: + sprintf(msg, "%d,%d,%d,%d,%s,%s,%s,%d", airValue, waterValue, sensorPin, wifiChannel, hostMac.c_str(), "", "?", bluetooth); + Serial.printf("msg: %s -> %d", msg, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", QUERY_RESULT, BROADCAST, msg, espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + break; + case CALIBRATE_AIR: + case CALIBRATE_WATER: + calibrateSensor(payload.task); + break; + case GET_MOISTURE: + calculate(); + sprintf(msg, "%d,%d,%d,%d,%s,%s,%s,%d", airValue, waterValue, sensorPin, wifiChannel, hostMac.c_str(), "", moistureLevel, bluetooth); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", MOISTURE_RESULT, BROADCAST, msg, espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); break; - case QUERY: - sprintf(msg, "%d,%d,%d,%d,%s,%s", airValue, waterValue, sensorPin, wifiChannel, hostMac.c_str(), ""); - Serial.printf("msg: %s -> %d", msg, from); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", QUERY_RESULT, BROADCAST, msg, espInterval, from); - payload.msgId = generateMessageHash(payload); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + case UPDATE_DEVICE_NAME: + Serial.printf("update device name: %s\n\n", payload.name); + DEVICE_NAME = payload.name; + saveJson(); break; - case CALIBRATE_AIR: - case CALIBRATE_WATER: - calibrateSensor(payload.task); + case UPDATE_DEVICE_ID: + Serial.printf("update device id: %d\n\n", payload.id); + DEVICE_ID = payload.id; + saveJson(); break; - case GET_MOISTURE: - calculate(); - sprintf(msg, "%d,%d,%d,%d,%s,%s,%s", airValue, waterValue, sensorPin, wifiChannel, hostMac.c_str(), "", moistureLevel); - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", MOISTURE_RESULT, BROADCAST, msg, espInterval, from); - payload.msgId = generateMessageHash(payload); - esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + case UPDATE_ESP_INTERVAL: + Serial.printf("update esp_interval: %d\n\n", payload.espInterval); + espInterval = payload.espInterval; + saveJson(); break; - case UPDATE_DEVICE_NAME: - Serial.printf("update device name: %s\n\n", payload.name); - DEVICE_NAME = payload.name; - saveJson(); + case ENABLE_BLUETOOTH: + Serial.println("enable bluetooth"); + enableBluetooth(); break; - case UPDATE_DEVICE_ID: - Serial.printf("update device id: %d\n\n", payload.id); - DEVICE_ID = payload.id; - saveJson(); + case DISABLE_BLUETOOTH: + Serial.println("disable bluetooth"); + disableBluetooth(); break; - case UPDATE_ESP_INTERVAL: - Serial.printf("update esp_interval: %d\n\n", payload.espInterval); - espInterval = payload.espInterval; - saveJson(); + case UPDATE_PIN: + Serial.printf("update sensor pin: %d\n\n", payload.espInterval); + sensorPin = payload.espInterval; + pinMode(sensorPin, INPUT); + saveJson(); break; - default: - Serial.println("Nothing to do.\n"); + default: + Serial.println("Nothing to do.\n"); break; } - } else { - if(payload.type == BROADCAST) { - if(payload.task == UPDATE_WIFI_CHANNEL) { + } + else + { + if (payload.type == BROADCAST) + { + if (payload.task == UPDATE_WIFI_CHANNEL) + { int32_t channel = atoi(payload.msg); Serial.printf("Wifi channel: %d", channel); - if(channel != wifiChannel) { + if (channel != wifiChannel) + { processWifiUpdates(channel); - } else { + } + else + { Serial.println("Same wifi channel, nothing to do."); } - } else { + } + else + { Serial.printf("relate broadcast %d from %s, %s, %d, %d, %d, %s\n\n", payload.msgId, payload.name, payload.senderAddress, payload.task, payload.type, payload.from, payload.msg); - esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); - } - } else { - Serial.println("Else nothing to do.\n"); + esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + } + } + else + { + Serial.println("Else nothing to do.\n"); } } } } -void enableBluetooth() { - char bleName[80] = ""; - sprintf(bleName, "ESP32-LiquidPrep-%s", DEVICE_NAME); - Serial.printf("Starting BLE work! %s\n", bleName); - - BLEDevice::init(bleName); - BLEServer *pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(SERVICE_UUID); - pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE - ); - // pCharacteristic->setValue("92"); // use this to hard-code value sent via bluetooth (for testing) - pCharacteristic->setCallbacks(new BLECallbacks()); - pService->start(); - // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility - BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(SERVICE_UUID); - pAdvertising->setScanResponse(true); - pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue - pAdvertising->setMinPreferred(0x12); - BLEDevice::startAdvertising(); - Serial.println("Characteristic defined! Now you can read it in your phone!"); -} - void setup() { int waitCount = 0; @@ -367,11 +490,16 @@ void setup() Serial.println(F("Failed to read file, using default configuration")); Serial.println(error.c_str()); saveJson(); - } else { + } + else + { JsonObject obj = doc.as(); - if(!doc["deviceName"] || doc["espInterval"] <= 0) { - saveJson(); //data corrupted, use default values - } else { + if (!doc["deviceName"] || doc["espInterval"] <= 0) + { + saveJson(); // data corrupted, use default values + } + else + { airValue = doc["airValue"]; waterValue = doc["waterValue"]; sensorPin = doc["sensorPin"]; @@ -391,7 +519,7 @@ void setup() { saveJson(); } - Serial.printf("%d, %d, %d, %d, %s, %d, %d, %s, %s\n", airValue,waterValue,sensorPin,DEVICE_ID,DEVICE_NAME,espInterval,wifiChannel,receiverMac,senderMac); + Serial.printf("%d, %d, %d, %d, %s, %d, %d, %s, %s\n", airValue, waterValue, sensorPin, DEVICE_ID, DEVICE_NAME, espInterval, wifiChannel, receiverMac, senderMac); // Set device as a Wi-Fi Station enableBluetooth(); setWifiChannel(wifiChannel); @@ -407,14 +535,14 @@ void setup() Serial.print("Wi-Fi Channel: "); Serial.println(WiFi.channel()); - //WiFi.mode(WIFI_STA); - // int32_t channel = 1; - // WiFi.printDiag(Serial); // Uncomment to verify channel number before - // esp_wifi_set_promiscuous(true); - // esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); - // esp_wifi_set_promiscuous(false); - // WiFi.printDiag(Serial); // Uncomment to verify channel change after - //WiFi.disconnect(); // we do not want to connect to a WiFi network + // WiFi.mode(WIFI_STA); + // int32_t channel = 1; + // WiFi.printDiag(Serial); // Uncomment to verify channel number before + // esp_wifi_set_promiscuous(true); + // esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + // esp_wifi_set_promiscuous(false); + // WiFi.printDiag(Serial); // Uncomment to verify channel change after + // WiFi.disconnect(); // we do not want to connect to a WiFi network // Init ESP-NOW if (esp_now_init() != ESP_OK) @@ -432,19 +560,21 @@ void setup() peerInfo = {}; memcpy(peerInfo.peer_addr, broadcastAddress, 6); - //peerInfo.ifidx = ESP_IF_WIFI_STA; + // peerInfo.ifidx = ESP_IF_WIFI_STA; peerInfo.encrypt = false; - // Add peer - if (esp_now_add_peer(&peerInfo) != ESP_OK){ + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK) + { Serial.println("Failed to add peer"); return; - } else { + } + else + { Serial.printf("Adding peer: %u\n", peerInfo.peer_addr); } - //stringToInt(gatewayMac, gatewayMacAddress); - //addPeer(gatewayMacAddress); - //Serial.printf("Adding gateway: %u\n", gatewayMacAddress); - + // stringToInt(gatewayMac, gatewayMacAddress); + // addPeer(gatewayMacAddress); + // Serial.printf("Adding gateway: %u\n", gatewayMacAddress); } void loop() @@ -454,17 +584,17 @@ void loop() struct_message payload = struct_message(); payload.id = DEVICE_ID; payload.name = DEVICE_NAME; + payload.capacitance = capacitance; payload.hostAddress = hostMac; payload.senderAddress = hostMac; payload.espInterval = espInterval; - Serial.printf("info: %d, %d, %s, %d, %s, %s, %s\n", wifiChannel, espInterval, moistureLevel, payload.id, payload.name, payload.senderAddress, payload.receiverAddress); + Serial.printf("info: %d, %d, %d, %s, %d, %s, %s, %s\n", wifiChannel, espInterval, capacitance, moistureLevel, payload.id, payload.name, payload.senderAddress, payload.receiverAddress); payload.type = BROADCAST; payload.moisture = moistureLevel; payload.msgId = generateMessageHash(payload); esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); - //esp_now_send(gatewayMacAddress, (uint8_t *)&payload, sizeof(payload)); + // esp_now_send(gatewayMacAddress, (uint8_t *)&payload, sizeof(payload)); pCharacteristic->setValue(moistureLevel.c_str()); - delay(espInterval); } diff --git a/esp-now/esp-now-gateway/src/main.cpp b/esp-now/esp-now-gateway/src/main.cpp index b36b4b5..1b0d293 100644 --- a/esp-now/esp-now-gateway/src/main.cpp +++ b/esp-now/esp-now-gateway/src/main.cpp @@ -25,6 +25,7 @@ unsigned long currentMillis = 0; // TODO: allow input certain values in webtools and writ to SPIFFS at the time of flashing int espInterval=90000; //espInterval for reading data +int capacitance; String wsserver = "192.168.86.24"; //ip address of Express server int wsport= 3000; char path[] = "/"; //identifier of this device @@ -35,7 +36,7 @@ String leaderMac = "7821848D8840"; void calculate() { int val = analogRead(sensorPin); // connect sensor to Analog pin - + capacitance = val; // soilmoisturepercent = map(soilMoistureValue, airValue, waterValue, 0, 100); int valueMinDiff = abs(val - airValue); int maxMinDiff = abs(airValue - waterValue); @@ -98,8 +99,8 @@ void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { struct_message payload = struct_message(); memcpy(&payload, incomingData, sizeof(payload)); - Serial.print("Bytes received: "); - Serial.printf("%d, moisture: %d from %s, %s, %d, %d, %d, %s\n", len, payload.moisture, payload.name, payload.hostAddress, payload.task, payload.type, payload.from, payload.msg); + Serial.printf("Bytes received at %s: ------\n", DEVICE_NAME); + Serial.printf("%d, moisture: %s, %d from %s, %d, %d, %d, %s\n", len, payload.moisture, payload.capacitance, payload.name, payload.task, payload.type, payload.from, payload.msg); Serial.printf("=> msgId: %d\n", payload.msgId); Serial.println("------\n"); String response = ""; @@ -168,7 +169,7 @@ void calibrate() { sendData(response); } else { int task = Server.arg(0) == "air_value" ? CALIBRATE_AIR : CALIBRATE_WATER; - setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, senderMac, receiverMac, task, BROADCAST, "", espInterval, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, senderMac, receiverMac, task, BROADCAST, "", espInterval, from, capacitance); payload.msgId = generateMessageHash(payload); esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); esp_now_send(leaderMacAddress, (uint8_t *) &payload, sizeof(payload)); @@ -196,7 +197,7 @@ void getMoisture() { sendData(response); } else { Serial.printf("from: %d\n", from); - setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", GET_MOISTURE, BROADCAST, "", espInterval, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", GET_MOISTURE, BROADCAST, "", espInterval, from, capacitance); payload.msgId = generateMessageHash(payload); Serial.printf("why why why, %d, %d, %d, %d\n", payload.from, payload.task, payload.type, payload.msgId); esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); @@ -223,7 +224,7 @@ void queryESP() { sendData(response); } else { Serial.printf("from: %d\n", from); - setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", QUERY, BROADCAST, "", espInterval, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", QUERY, BROADCAST, "", espInterval, from, capacitance); payload.msgId = generateMessageHash(payload); Serial.printf("why why why, %d, %d, %d, %d\n", payload.from, payload.task, payload.type, payload.msgId); esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); @@ -245,11 +246,11 @@ void pingESP() { } sprintf(payload.msg, "%d,%d,%d,%s,%s", airValue, waterValue, sensorPin, senderMac.c_str(), receiverMac.c_str()); String response = "{\"mac\": \"" + hostMac + "\", \"id\": " + String(DEVICE_ID) + fromStr + ", \"name\": \"" + DEVICE_NAME + "\", \"msg\": \"" + payload.msg + "\", \"task\": " + String(PING_BACK) + "}"; - setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, WEB_REQUEST_RESULT); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, WEB_REQUEST_RESULT, capacitance); sendData(response); } else { Serial.printf("from: %d\n", from); - setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", PING, BROADCAST, DEVICE_NAME, espInterval, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", PING, BROADCAST, DEVICE_NAME, espInterval, from, capacitance); payload.msgId = generateMessageHash(payload); Serial.printf("%d, %s, %s, %s, %d, %s\n", payload.id,payload.name,payload.hostAddress,payload.senderAddress,payload.task,payload.msg); esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); @@ -304,6 +305,13 @@ void updateESP32() { } else if(task == "device_id") { payload.task = UPDATE_DEVICE_ID; payload.id = atoi(taskValue.c_str()); + } else if(task == "enable_bluetooth") { + payload.task = ENABLE_BLUETOOTH; + } else if(task == "disable_bluetooth") { + payload.task = DISABLE_BLUETOOTH; + } else if(task == "update_pin") { + payload.task = UPDATE_PIN; + payload.espInterval = atoi(taskValue.c_str()); } payload.msgId = generateMessageHash(payload); Serial.printf("Broacast to: %s, %u\n", payload.hostAddress, gatewayReceiverAddress); @@ -318,6 +326,10 @@ void updateESP32() { } } +void updatePin() { + +} + String onHome(AutoConnectAux& aux, PageArgument& args) { calculate(); Serial.println(moistureLevel); diff --git a/esp-now/esp-now-mesh/src/main.cpp b/esp-now/esp-now-mesh/src/main.cpp index 55affa1..4b1311a 100644 --- a/esp-now/esp-now-mesh/src/main.cpp +++ b/esp-now/esp-now-mesh/src/main.cpp @@ -285,6 +285,7 @@ Serial.printf("%d, %d, %d, %d, %s, %d, %d, %s, %s\n", airValue,waterValue,sensor } else { Serial.printf("Adding peer: %u\n", peerInfo.peer_addr); } + } void loop() { diff --git a/esp-now/esp-now-plantmate-gateway/.gitignore b/esp-now/esp-now-plantmate-gateway/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/esp-now/esp-now-plantmate-gateway/.vscode/extensions.json b/esp-now/esp-now-plantmate-gateway/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/esp-now/esp-now-plantmate-gateway/data/config.json b/esp-now/esp-now-plantmate-gateway/data/config.json new file mode 100644 index 0000000..c3bc0e8 --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/data/config.json @@ -0,0 +1,15 @@ +{ + "uuid": { + "SERVICE_UUID": "4fafc201-1fb5-459e-8fcc-c5c9c331914b", + "CHARACTERISTIC_UUID": "beb5483e-36e1-4688-b7f5-ea07361b26a8" + }, + "airValue": 3440, + "waterValue": 1803, + "sensorPin": 36, + "wsserver": "192.168.86.24", + "wsport": 3000, + "espInterval": 80000, + "wifiChannel": 0, + "receiverAddress": "", + "senderAddress": "" +} \ No newline at end of file diff --git a/esp-now/esp-now-plantmate-gateway/data/firmware.bin b/esp-now/esp-now-plantmate-gateway/data/firmware.bin new file mode 100644 index 0000000..fba744b Binary files /dev/null and b/esp-now/esp-now-plantmate-gateway/data/firmware.bin differ diff --git a/esp-now/esp-now-plantmate-gateway/data/page.json b/esp-now/esp-now-plantmate-gateway/data/page.json new file mode 100644 index 0000000..3206906 --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/data/page.json @@ -0,0 +1,126 @@ +[ + { + "title": "Config", + "uri": "/update_config", + "menu": true, + "element": [ + { + "name": "header", + "type": "ACText" + }, + { + "name": "caption1", + "type": "ACText", + "value": "Air value" + }, + { + "name": "airValue", + "type": "ACInput" + }, + { + "name": "caption2", + "type": "ACText", + "value": "Water value" + }, + { + "name": "waterValue", + "type": "ACInput" + }, + { + "name": "caption3", + "type": "ACText", + "value": "Pin" + }, + { + "name": "sensorPin", + "type": "ACInput" + }, + { + "name": "caption4", + "type": "ACText", + "value": "WebSocket Server" + }, + { + "name": "wsserver", + "type": "ACInput" + }, + { + "name": "caption5", + "type": "ACText", + "value": "WebSocket Port" + }, + { + "name": "wsport", + "type": "ACInput" + }, + { + "name": "caption6", + "type": "ACText", + "value": "Receiver Mac Address" + }, + { + "name": "receiverMac", + "type": "ACInput" + }, + { + "name": "caption7", + "type": "ACText", + "value": "Sender Mac Address" + }, + { + "name": "senderMac", + "type": "ACInput" + }, + { + "name": "caption8", + "type": "ACText", + "value": "Interval" + }, + { + "name": "espInterval", + "type": "ACInput" + }, + { + "name": "save", + "type": "ACSubmit", + "value": "SAVE", + "uri": "/save_config" + } + ] + }, + { + "uri": "/save_config", + "title": "Save configuration", + "menu": false, + "element": [ + { + "name": "results", + "type": "ACText", + "value": "" + } + ] + }, + { + "uri": "/", + "title": "Moisture reading", + "menu": false, + "element": [ + { + "name": "moisture", + "type": "ACText", + "value": "Moisture: " + }, + { + "name": "results", + "type": "ACText", + "value": "..." + }, + { + "name": "save", + "type": "ACSubmit", + "value": "Read again...", + "uri": "/" + } + ] + } +] \ No newline at end of file diff --git a/esp-now/esp-now-plantmate-gateway/include/README b/esp-now/esp-now-plantmate-gateway/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/esp-now/esp-now-plantmate-gateway/lib/README b/esp-now/esp-now-plantmate-gateway/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/esp-now/esp-now-plantmate-gateway/platformio.ini b/esp-now/esp-now-plantmate-gateway/platformio.ini new file mode 100644 index 0000000..38bb777 --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/platformio.ini @@ -0,0 +1,28 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = esp-now-plantmate-gateway +description = + +[env:esp-now-plantmate-gateway] +platform = espressif32 +board = esp32doit-devkit-v1 +framework = arduino +upload_port = /dev/cu.usbserial-0001 +upload_speed = 115200 +monitor_speed = 115200 +board_build.partitions = min_spiffs.csv +debug_build_flags = -Os +lib_deps = + hieromon/AutoConnect@^1.2.2 + links2004/WebSockets@^2.3.7 +build_flags = + -I../include diff --git a/esp-now/esp-now-plantmate-gateway/src/main.cpp b/esp-now/esp-now-plantmate-gateway/src/main.cpp new file mode 100644 index 0000000..fa5c99e --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/src/main.cpp @@ -0,0 +1,578 @@ +#include +#include "SPIFFS.h" +#include +#include +#include + +int DEVICE_ID = 0; // set device id, 0 = ESP32 Gateway that will relate all messages to edge gateway via websocket +String DEVICE_NAME = "GATEWAY"; // set device name + +WebServer Server; +AutoConnect Portal(Server); +AutoConnectConfig Config; +String moistureLevel = ""; +int soilMoistureValue = 0; +float soilmoisturepercent=0; +const char* fwVersion = FIRMWARE_VERSION; +DynamicJsonDocument doc(1024); + +WebSocketsClient webSocketClient; +unsigned long previousMillis = 0; +unsigned long currentMillis = 0; + +int sensorPin = 36; // Assuming A0 is where your sensor is connected +int Value_dry; // This will hold the maximum value obtained during dry calibration +int Value_wet; // This will hold the minimum value obtained during wet calibration + + +// TODO: allow input certain values in webtools and writ to SPIFFS at the time of flashing +int espInterval=90000; //espInterval for reading data +int capacitance; +String wsserver = "192.168.86.24"; //ip address of Express server +int wsport= 3000; +char path[] = "/"; //identifier of this device +boolean webSocketConnected=0; +String data= ""; +uint8_t leaderMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +String leaderMac = "7821848D8840"; + +void calculate() +{ + int val = analogRead(sensorPin); // connect sensor to Analog pin + + + soilmoisturepercent = map(val, Value_dry, Value_wet, 0, 100); + soilmoisturepercent = constrain(soilmoisturepercent, 0, 100); + + char str[8]; + + Serial.printf("sensor reading: %d - %f%\n", val, soilmoisturepercent); // print the value to serial port + dtostrf(soilmoisturepercent, 1, 2, str); + moistureLevel = str; +} + +void sendData(String data) { + if (data.length() > 0) { + Serial.println("Sending: " + data); + webSocketClient.sendTXT(data);//send sensor data to websocket server + } else { + } +} + +String saveJson() { + String msg = ""; + File configFile = SPIFFS.open("/config.json", "w+"); + if(configFile) { + Serial.printf("%d, %d, %d, %d, %s, %d, %s, %s\n", Value_dry,Value_wet,sensorPin,DEVICE_ID,DEVICE_NAME,espInterval,receiverMac,senderMac); + doc["deviceId"] = DEVICE_ID; + doc["deviceName"] = DEVICE_NAME; + doc["airValue"] = Value_dry; + doc["waterValue"] = Value_wet; + doc["sensorPin"] = sensorPin; + doc["espInterval"] = espInterval; + doc["receiverMac"] = gatewayReceiverMac; + doc["senderMac"] = senderMac; + doc["wsserver"] = wsserver; + doc["wsport"] = wsport; + + serializeJson(doc, configFile); + msg = "Updated config successfully!"; + configFile.close(); + } else { + msg = "Failed to open config file for writing!"; + Serial.println(msg); + } + Serial.println(msg); + return msg; +} +// callback when data is sent +void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { + Serial.printf("Last Packet Send Status: %u, %u, %s\t", leaderMacAddress, mac_addr, receiverMac); + Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); + Serial.println(""); +} + +void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { + struct_message payload = struct_message(); + memcpy(&payload, incomingData, sizeof(payload)); + Serial.printf("Bytes received at %s: ------\n", DEVICE_NAME); + Serial.printf("%d, moisture: %s, %d from %s, %d, %d, %d, %s\n", len, payload.moisture, payload.capacitance, payload.name, payload.task, payload.type, payload.from, payload.msg); + Serial.printf("=> msgId: %d\n", payload.msgId); + Serial.println("------\n"); + String response = ""; + if(isMessageSeen(payload.msgId)) { + Serial.printf("%d from %s, %d Message already seen, ignoring...\n", len, payload.name, payload.task); + } else { + String from = ""; + if(payload.from == WEB_REQUEST_RESULT) { + from = ", \"from\": " + String(WEB_REQUEST_RESULT); + } + if(payload.task == PING_BACK) { + Serial.printf("Ping: %d, %s, %d, %d\n", payload.msgId, payload.name, payload.type, payload.task); + Serial.println(); + response = "{\"mac\": \"" + payload.senderAddress + "\", \"id\": " + String(payload.id) + from + ", \"name\": \"" + payload.name + "\", \"type\": " + String(payload.type) + ", \"task\": " + String(payload.task) + "}"; + } else if(payload.task == UPDATE_WIFI_RESULT) { + Serial.printf("Wifi updated: %d, %s, %d, %d\n", payload.msgId, payload.name, payload.type, payload.task); + Serial.println(); + response = "{\"mac\": \"" + payload.senderAddress + "\", \"id\": " + String(payload.id) + from + ", \"name\": \"" + payload.name + "\", \"type\": " + String(payload.type) + ", \"task\": " + String(payload.task) + "}"; + } + else if(payload.task == QUERY_RESULT || payload.task == CALIBRATE_RESULT || payload.task == MOISTURE_RESULT) { + Serial.printf("Query: %d, %s, %d, %d, %s, %s\n", payload.msgId, payload.name, payload.type, payload.task, from, payload.msg); + Serial.println(); + response = "{\"mac\": \"" + payload.senderAddress + "\", \"interval\": " + String(payload.espInterval) + ", \"id\": " + String(payload.id) + from + ", \"name\": \"" + payload.name + "\", \"msg\": \"" + payload.msg + "\", \"type\": " + String(payload.type) + ", \"task\": " + String(payload.task) + "}"; + } else { + Serial.println(payload.name + ", " + payload.id + ", " + payload.moisture + ", " + payload.task); + Serial.println(); + response = "{\"mac\": \"" + payload.senderAddress + "\", \"id\": " + String(payload.id) + from + ", \"name\": \"" + payload.name + "\", \"moisture\": \"" + payload.moisture + "\"}"; + } + sendData(response); + } +} + +String moistureJson() { + calculate(); + String response = "{\"mac\": \"" + hostMac + "\", \"id\": " + String(DEVICE_ID) + ", \"name\": \"" + DEVICE_NAME + "\", \"moisture\": " + moistureLevel + "}"; + Server.send(200, "text/json", response); + Serial.printf("sensor reading: %s\n", moistureLevel); + return response; +} +void httpResponse(boolean success, String msg1, String msg2) { + if(success) { + Server.send(200, "text/plain", msg1); + } else { + Server.send(400, "text/plain", msg2); + } +} +void restartESP() { + ESP.restart(); +} +void calibrate() { + boolean success = false; + if(Server.args() == 2 && Server.argName(0) == "value" && Server.argName(1) == "host_addr" && (Server.arg(0) == "air_value" || Server.arg(0) == "water_value")) { + success = true; + String targetHostAddr = removeFromString(Server.arg(1), (char *)":"); + struct_message payload = struct_message(); + int from = Server.arg(2) == "true" && Server.argName(2) == "web_request" ? WEB_REQUEST : NO_TASK; + if(targetHostAddr == hostMac) { + if(Server.arg(0) == "air_value") { + calibrateAirFrequency(Value_dry, sensorPin); + } else { + calibrateWaterFrequency(Value_wet, sensorPin); + } + saveJson(); + sprintf(payload.msg, "%d,%d,%d,%s,%s", Value_dry, Value_wet, sensorPin, senderMac.c_str(), receiverMac.c_str()); + String response = "{\"mac\": \"" + hostMac + "\", \"interval\": " + String(espInterval) + ", \"id\": " + String(DEVICE_ID) + ", \"from\": " + String(WEB_REQUEST_RESULT) + ", \"name\": \"" + DEVICE_NAME + "\", \"msg\": \"" + payload.msg + "\", \"task\": " + String(CALIBRATE_RESULT) + "}"; + sendData(response); + } else { + int task = Server.arg(0) == "air_value" ? CALIBRATE_AIR : CALIBRATE_WATER; + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, senderMac, receiverMac, task, BROADCAST, "", espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(leaderMacAddress, (uint8_t *) &payload, sizeof(payload)); + } + } + httpResponse(success, "Calibrate", "Invalid request params, correct params: value=air_value&host_addr=... OR value=water_value&host_addr=..."); +} +void getMoisture() { + boolean success = false; + if(Server.args() == 1 && Server.argName(0) == "host_addr" || Server.args() == 2 && Server.argName(0) == "host_addr" && Server.argName(1) == "web_request") { + success = true; + String targetHostAddr = removeFromString(Server.arg(0), (char *)":"); + struct_message payload = struct_message(); + int from = Server.arg(1) == "true" && Server.argName(1) == "web_request" ? WEB_REQUEST : NO_TASK; + Serial.printf("from: %d, %s, %s\n", payload.from, Server.argName(1), Server.arg(1)); + if(targetHostAddr == hostMac) { + String fromStr = ""; + if(from == WEB_REQUEST) { + fromStr = ", \"from\": " + String(WEB_REQUEST_RESULT); + } + Serial.printf("why why why, %s\n", fromStr); + calculate(); + sprintf(payload.msg, "%d,%d,%d,%d,%s,%s,%s", Value_dry, Value_wet, sensorPin, WiFi.channel(), hostMac.c_str(), "", moistureLevel); + String response = "{\"mac\": \"" + hostMac + "\", \"interval\": " + String(espInterval) + ", \"id\": " + String(DEVICE_ID) + fromStr + ", \"name\": \"" + DEVICE_NAME + "\", \"msg\": \"" + payload.msg + "\", \"task\": " + String(MOISTURE_RESULT) + "}"; + sendData(response); + } else { + Serial.printf("from: %d\n", from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", GET_MOISTURE, BROADCAST, "", espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + Serial.printf("why why why, %d, %d, %d, %d\n", payload.from, payload.task, payload.type, payload.msgId); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + } + } + httpResponse(success, "Query ESP", "Invalid request params, correct params: host_addr=..."); +} +void queryESP() { + boolean success = false; + if(Server.args() == 1 && Server.argName(0) == "host_addr" || Server.args() == 2 && Server.argName(0) == "host_addr" && Server.argName(1) == "web_request") { + success = true; + String targetHostAddr = removeFromString(Server.arg(0), (char *)":"); + struct_message payload = struct_message(); + int from = Server.arg(1) == "true" && Server.argName(1) == "web_request" ? WEB_REQUEST : NO_TASK; + Serial.printf("from: %d, %s, %s\n", payload.from, Server.argName(1), Server.arg(1)); + if(targetHostAddr == hostMac) { + String fromStr = ""; + if(from == WEB_REQUEST) { + fromStr = ", \"from\": " + String(WEB_REQUEST_RESULT); + } + Serial.printf("why why why, %s\n", fromStr); + sprintf(payload.msg, "%d,%d,%d,%d,%s,%s", Value_dry, Value_wet, sensorPin, WiFi.channel(), senderMac.c_str(), receiverMac.c_str()); + String response = "{\"mac\": \"" + hostMac + "\", \"interval\": " + String(espInterval) + ", \"id\": " + String(DEVICE_ID) + fromStr + ", \"name\": \"" + DEVICE_NAME + "\", \"msg\": \"" + payload.msg + "\", \"task\": " + String(QUERY_RESULT) + "}"; + sendData(response); + } else { + Serial.printf("from: %d\n", from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", QUERY, BROADCAST, "", espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + Serial.printf("why why why, %d, %d, %d, %d\n", payload.from, payload.task, payload.type, payload.msgId); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + } + } + httpResponse(success, "Query ESP", "Invalid request params, correct params: host_addr=..."); +} +void pingESP() { + boolean success = false; + if(Server.args() == 1 && Server.argName(0) == "host_addr" || Server.args() == 2 && Server.argName(0) == "host_addr" && Server.argName(1) == "web_request") { + String targetHostAddr = removeFromString(Server.arg(0), (char *)":"); + success = true; + struct_message payload = struct_message(); + int from = Server.arg(1) == "true" && Server.argName(1) == "web_request" ? WEB_REQUEST : NO_TASK; + if(targetHostAddr == hostMac) { + String fromStr = ""; + if(from == WEB_REQUEST) { + fromStr = ", \"from\": " + String(WEB_REQUEST_RESULT); + } + sprintf(payload.msg, "%d,%d,%d,%s,%s", Value_dry, Value_wet, sensorPin, senderMac.c_str(), receiverMac.c_str()); + String response = "{\"mac\": \"" + hostMac + "\", \"id\": " + String(DEVICE_ID) + fromStr + ", \"name\": \"" + DEVICE_NAME + "\", \"msg\": \"" + payload.msg + "\", \"task\": " + String(PING_BACK) + "}"; + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, WEB_REQUEST_RESULT, capacitance); + sendData(response); + } else { + Serial.printf("from: %d\n", from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, targetHostAddr, hostMac, "", PING, BROADCAST, DEVICE_NAME, espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + Serial.printf("%d, %s, %s, %s, %d, %s\n", payload.id,payload.name,payload.hostAddress,payload.senderAddress,payload.task,payload.msg); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + } + } + if(success) { + Server.send(200, "text/plain", "Ping!"); + } else { + Server.send(400, "text/plain", "Invalid request params, correct params: host_addr=..."); + } +} +void updateESP32() { + boolean success = false; + if(Server.args() == 2) { + String hostAddr = removeFromString(Server.arg(0), (char *)":"); + String taskValue = removeFromString(Server.arg(1), (char *)":"); + + Serial.printf("%s, %s, %u, %u\n", hostAddr, hostMac); + if(Server.argName(0) == "host_addr" && hostAddr == hostMac) { + String task = Server.argName(1); + if(task == "esp_interval") { + int ms = atoi(taskValue.c_str()); + Serial.printf("update esp_interval: %d\n", ms); + espInterval = ms; + saveJson(); + } else if(task == "device_name") { + Serial.printf("update device name: %s\n", taskValue); + DEVICE_NAME = taskValue; + saveJson(); + } else if(task == "device_id") { + int id = atoi(taskValue.c_str()); + Serial.printf("update device id: %d\n", id); + DEVICE_ID = id; + saveJson(); + } + } else { + success = true; + String task = Server.argName(1); + struct_message payload = struct_message(); + payload.hostAddress = hostAddr; + if(task == "recv_addr") { + payload.task = UPDATE_RECEIVER_ADDR; + payload.receiverAddress = taskValue; + } else if(task == "esp_interval") { + Serial.println(hostAddr); + Serial.printf("%s\n", payload.hostAddress); + payload.task = UPDATE_ESP_INTERVAL; + payload.espInterval = atoi(taskValue.c_str()); + } else if(task == "device_name") { + payload.task = UPDATE_DEVICE_NAME; + payload.name = taskValue; + } else if(task == "device_id") { + payload.task = UPDATE_DEVICE_ID; + payload.id = atoi(taskValue.c_str()); + } else if(task == "enable_bluetooth") { + payload.task = ENABLE_BLUETOOTH; + } else if(task == "disable_bluetooth") { + payload.task = DISABLE_BLUETOOTH; + } else if(task == "update_pin") { + payload.task = UPDATE_PIN; + payload.espInterval = atoi(taskValue.c_str()); + } + payload.msgId = generateMessageHash(payload); + Serial.printf("Broacast to: %s, %u\n", payload.hostAddress, gatewayReceiverAddress); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(leaderMacAddress, (uint8_t *) &payload, sizeof(payload)); + } + } + if(success) { + Server.send(200, "text/plain", "Good to go!"); + } else { + Server.send(400, "text/plain", "Invalid request params"); + } +} + +void updatePin() { + +} + +String onHome(AutoConnectAux& aux, PageArgument& args) { + calculate(); + Serial.println(moistureLevel); + aux["results"].as().value = moistureLevel; + return String(); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + try { + switch(type) { + case WStype_DISCONNECTED: + Serial.printf("Websocket Disconnected!\n"); + break; + case WStype_CONNECTED: + Serial.printf("Websocket Connected\n"); + break; + case WStype_TEXT: + Serial.printf("get Text: %s\n", payload); + break; + } + } catch(...) { + Serial.println("Catch web socket errors"); + } + webSocketConnected = 1; +} + +void wsconnect() { + if(!webSocketConnected) { + // Connect to the websocket server + Serial.printf("%s, %d, %s\n", wsserver, wsport, path); + //webSocketClient.begin(wsserver, wsport, path); + webSocketClient.beginSSL(wsserver.c_str(), wsport, path); + // WebSocket event handler + webSocketClient.onEvent(webSocketEvent); + // if connection failed retry every 5s + webSocketClient.setReconnectInterval(5000); + } +} + +String onSaveConfig(AutoConnectAux& aux, PageArgument& args) { + Value_dry = doc["airValue"] = args.arg("airValue").toInt(); + Value_wet = doc["waterValue"] = args.arg("waterValue").toInt(); + sensorPin = doc["sensorPin"] = args.arg("sensorPin").toInt(); + espInterval = doc["espInterval"] = args.arg("espInterval").toInt(); + doc["wsserver"] = args.arg("wsserver"); + JsonObject obj = doc.as(); + wsserver = obj["wsserver"].as(); + wsport = doc["wsport"] = args.arg("wsport").toInt(); + gatewayReceiverMac = args.arg("receiverAddress"); + senderMac = args.arg("senderAddress"); + + String msg = saveJson(); + aux["results"].as().value = msg; + ESP.restart(); + return String(); +} + +String onUpdateConfig(AutoConnectAux &aux, PageArgument &args) { + int value = doc["waterValue"]; + Serial.println(value); + aux["waterValue"].as().value = value; + value = doc["airValue"]; + Serial.println(value); + aux["airValue"].as().value = value; + value = doc["sensorPin"]; + Serial.println(value); + aux["sensorPin"].as().value = value; + String strValue = doc["wsserver"]; + Serial.println(strValue); + aux["wsserver"].as().value = strValue; + value = doc["wsport"]; + Serial.println(value); + aux["wsport"].as().value = value; + + String strValue2 = doc["receiverMac"]; + Serial.println(strValue2); + aux["receiverMac"].as().value = strValue2; + String strValue3 = doc["senderMac"]; + Serial.println(strValue3); + aux["senderMac"].as().value = strValue3; + + value = doc["espInterval"]; + Serial.println(value); + aux["espInterval"].as().value = value; + return String(); +} + +void updateWifiChannel() { + struct_message payload = struct_message(); + payload.id = DEVICE_ID; + payload.name = DEVICE_NAME; + //payload.hostAddress = receiverMac; + payload.hostAddress = ""; + payload.senderAddress = hostMac; + payload.espInterval = espInterval; + payload.type = BROADCAST; + payload.from = NO_TASK; + payload.task = UPDATE_WIFI_CHANNEL; + sprintf(payload.msg, "%d", WiFi.channel()); + Serial.printf("%d: %s", Server.args(), Server.argName(0)); + if(Server.args() == 1 && Server.argName(0) == "web_request") { + payload.from = WEB_REQUEST; + } + Serial.printf("\nwifi: %d, %d, %s, %d, %d, %s, %s\n", espInterval, payload.from, payload.msg, payload.task, payload.type, payload.name, payload.senderAddress); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *) &payload, sizeof(payload)); + esp_now_send(leaderMacAddress, (uint8_t *) &payload, sizeof(payload)); + Server.send(200, "text/plain", "Update wifi channel!"); +} + +void registerAndAdd() { + // Register peer + peerInfo = {}; + memcpy(peerInfo.peer_addr, broadcastAddress, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + Serial.println("Failed to add peer"); + return; + } else { + Serial.printf("Adding peer: %u\n", peerInfo.peer_addr); + } +} +void setup() { + int waitCount = 0; + delay(1000); + Serial.begin(115200); + Serial.println(); + + while (!SPIFFS.begin(true) && waitCount++ < 3) { + delay(1000); + } + + File page = SPIFFS.open("/page.json", "r"); + if(page) { + Portal.load(page); + page.close(); + } + File config = SPIFFS.open("/config.json", "r"); + if(config) { + DeserializationError error = deserializeJson(doc, config); + if(error) { + Serial.println(F("Failed to read file, using default configuration")); + Serial.println(error.c_str()); + saveJson(); + } else { + JsonObject obj = doc.as(); + Value_dry = doc["airValue"]; + Value_wet = doc["waterValue"]; + wsserver = obj["wsserver"].as(); + wsport = doc["wsport"]; + sensorPin = doc["sensorPin"]; + DEVICE_ID = obj["deviceId"]; + DEVICE_NAME = doc["deviceName"] ? doc["deviceName"].as() : DEVICE_NAME; + espInterval = doc["espInterval"] ? doc["espInterval"] : espInterval; + gatewayReceiverMac = doc["receiverMac"] ? doc["receiverMac"].as() : gatewayReceiverMac; + senderMac = doc["senderMac"] ? doc["senderMac"].as() : senderMac; + stringToInt(gatewayReceiverMac, gatewayReceiverAddress); + stringToInt(senderMac, senderAddress); + } + config.close(); + } else { + // save default config + saveJson(); + } + Serial.printf("%d, %d, %d, %d, %s, %d, %s, %s\n", Value_dry,Value_wet,sensorPin,DEVICE_ID,DEVICE_NAME,espInterval,receiverMac,senderMac); + + Config.autoReconnect = true; + Config.hostName = "liquid-prep"; + Config.ota = AC_OTA_BUILTIN; + Config.otaExtraCaption = fwVersion; + Portal.config(Config); + Portal.on("/update_config", onUpdateConfig); + Portal.on("/save_config", onSaveConfig); + Portal.on("/", onHome); + + Server.enableCORS(); + Server.on("/moisture", getMoisture); + Server.on("/moisture.json", moistureJson); + Server.on("/update", updateESP32); + Server.on("/reboot_gateway", restartESP); + Server.on("/ping", pingESP); + Server.on("/query", queryESP); + Server.on("/calibrate", calibrate); + Server.on("/update_wifi", updateWifiChannel); + Serial.println("Connecting"); + if (Portal.begin()) { + Serial.println("WiFi connected: " + WiFi.localIP().toString()); + Serial.println("My MAC address is: " + WiFi.macAddress()); + Serial.print("Wi-Fi Channel: "); + Serial.println(WiFi.channel()); + Serial.print("Wi-Fi SSID: "); + Serial.println(WiFi.SSID()); + hostMac = removeFromString(WiFi.macAddress(), (char *)":"); + Serial.printf("host: %s, receiver: %s, gateway receiver: %s\n", hostMac, receiverMac, gatewayReceiverMac); + stringToInt(hostMac, hostAddress); + stringToInt(gatewayReceiverMac, gatewayReceiverAddress); + Serial.printf("host: %u, gatway receiver: %u\n", hostAddress, gatewayReceiverAddress); + } + waitCount = 0; + while (WiFi.status() != WL_CONNECTED && waitCount++ < 3) { + delay(500); + Serial.print("."); + } + if (WiFi.status() != WL_CONNECTED) { + Serial.print("Failed to connect to WiFi"); + } else { + wsconnect(); + } + // Init ESP-NOW + if (esp_now_init() != ESP_OK) { + Serial.println("Error initializing ESP-NOW"); + return; + } + esp_now_register_recv_cb(OnDataRecv); + esp_now_register_send_cb(OnDataSent); + + //registerAndAdd(); + //updateWifiChannel(); + + // Register peer + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, broadcastAddress, 6); + //peerInfo.channel = 0; + peerInfo.encrypt = false; + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + Serial.println("Failed to add peer"); + return; + } else { + Serial.printf("Adding peer: %u\n", peerInfo.peer_addr); + } + //stringToInt(leaderMac, leaderMacAddress); + //addPeer(leaderMacAddress); + //Serial.printf("Adding leader: %u\n", leaderMacAddress); + //addPeer(broadcastAddress); +} + +void loop() { + Portal.handleClient(); + webSocketClient.loop(); + if (WiFi.status() == WL_CONNECTED) { + currentMillis=millis(); + if (currentMillis - previousMillis >= espInterval) { + previousMillis = currentMillis; + sendData(moistureJson()); + } + } +} \ No newline at end of file diff --git a/esp-now/esp-now-plantmate-gateway/test/README b/esp-now/esp-now-plantmate-gateway/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/esp-now/esp-now-plantmate-gateway/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/esp-now/esp-now-plantmate-mesh/.gitignore b/esp-now/esp-now-plantmate-mesh/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/esp-now/esp-now-plantmate-mesh/.vscode/extensions.json b/esp-now/esp-now-plantmate-mesh/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/esp-now/esp-now-plantmate-mesh/include/README b/esp-now/esp-now-plantmate-mesh/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/esp-now/esp-now-plantmate-mesh/lib/README b/esp-now/esp-now-plantmate-mesh/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/esp-now/esp-now-plantmate-mesh/platformio.ini b/esp-now/esp-now-plantmate-mesh/platformio.ini new file mode 100644 index 0000000..5516208 --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/platformio.ini @@ -0,0 +1,26 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = esp-now-plantmate-mesh +description = + +[env:esp-now-plantmate-mesh] +platform = espressif32 +board = esp32doit-devkit-v1 +framework = arduino +upload_port = /dev/cu.usbserial-0001 +upload_speed = 115200 +monitor_speed = 115200 +board_build.partitions = min_spiffs.csv +debug_build_flags = -Os +lib_deps = bblanchon/ArduinoJson@^6.20.1 +build_flags = + -I../include diff --git a/esp-now/esp-now-plantmate-mesh/src/main.cpp b/esp-now/esp-now-plantmate-mesh/src/main.cpp new file mode 100644 index 0000000..84fdfdf --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/src/main.cpp @@ -0,0 +1,594 @@ +#include +#include "SPIFFS.h" +#include +#include + +#include +#include +#include +#include + +int DEVICE_ID = 5; // set device id, need to store in SPIFFS +String DEVICE_NAME = "Z5"; // set device name + +String moistureLevel = ""; +int sensorPin = 36; +int soilMoistureValue = 0; +int wifiChannel = WIFI_CHANNEL; +int capacitance = 0; +float soilmoisturepercent = 0; +const char *fwVersion = FIRMWARE_VERSION; +DynamicJsonDocument doc(1024); +int espInterval = 80000; // interval for reading data +uint8_t gatewayMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +String gatewayMac = "7821848D8840"; + +int Value_dry; // This will hold the maximum value obtained during dry calibration +int Value_wet; // This will hold the minimum value obtained during wet calibration + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +BLECharacteristic *pCharacteristic; +BLEServer *pServer = nullptr; + +String saveJson() +{ + String msg = ""; + File configFile = SPIFFS.open("/config.json", "w+"); + if (configFile) + { + doc["deviceId"] = DEVICE_ID; + doc["deviceName"] = DEVICE_NAME; + doc["airValue"] = Value_dry; + doc["waterValue"] = Value_wet; + doc["sensorPin"] = sensorPin; + doc["espInterval"] = espInterval; + doc["wifiChannel"] = wifiChannel; + doc["receiverMac"] = receiverMac; + doc["senderMac"] = senderMac; + + serializeJson(doc, configFile); + msg = "Updated config successfully!"; + configFile.close(); + } + else + { + msg = "Failed to open config file for writing!"; + Serial.println(msg); + } + return msg; +} +void setWifiChannel(int32_t channel = 11) +{ + WiFi.mode(WIFI_STA); + // TODO: get channel programmatically + // int32_t channel = getWiFiChannel(WIFI_SSID); + WiFi.printDiag(Serial); // Uncomment to verify channel number before + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + esp_wifi_set_promiscuous(false); + // WiFi.printDiag(Serial); // Uncomment to verify channel change after + WiFi.disconnect(); // we do not want to connect to a WiFi network +} + +void updateWifiChannel(int channel) +{ + struct_message payload = struct_message(); + char msg[80]; + sprintf(msg, "%d", channel); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_CHANNEL, BROADCAST, msg, espInterval, 0, capacitance); + + Serial.printf("\nwifi: %d, %d, %s, %d, %d, %s, %s\n", espInterval, payload.from, payload.msg, payload.task, payload.type, payload.name, payload.senderAddress); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + + // Broadcast to newly joined or to any esp still listening on default wifi channel 11 + if (wifiChannel != WIFI_CHANNEL) + { + setWifiChannel(WIFI_CHANNEL); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + } +} + +void broadcastWifiResult(int channel) +{ + struct_message payload = struct_message(); + char msg[80]; + sprintf(msg, "Updated wifi: from %d to %d", wifiChannel, channel); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", UPDATE_WIFI_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); + + Serial.printf("\n%s\n", payload.msg); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); +} + +void processWifiUpdates(int channel) +{ + Serial.printf("%d\n", channel); + updateWifiChannel(channel); + delay(300); + setWifiChannel(channel); + broadcastWifiResult(channel); + wifiChannel = channel; + saveJson(); +} + +void calibrateSensor(int mode) +{ + struct_message payload = struct_message(); + String msg = ""; + char str[80]; + if (mode == CALIBRATE_AIR) + { + sprintf(str, "%d", Value_dry); + std::string s(str); + msg += "Air: Old=" + String(s.c_str()); + calibrateAirFrequency(Value_dry, sensorPin); + sprintf(str, "%d", Value_dry); + std::string s2(str); + msg = msg + ", New=" + String(s2.c_str()); + } + else + { + sprintf(str, "%d", Value_wet); + std::string s(str); + msg += "Water: Old=" + String(s.c_str()); + calibrateWaterFrequency(Value_wet, sensorPin); + sprintf(str, "%d", Value_wet); + std::string s2(str); + msg += ", New=" + String(s2.c_str()); + } + saveJson(); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); + + payload.msgId = generateMessageHash(payload); + Serial.printf("\n%s, %d\n\n", payload.msg, payload.msgId); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); +} +void calibrateByPercentage(int percent) +{ + struct_message payload = struct_message(); + String msg = ""; + char str[80]; + sprintf(str, "%d", Value_wet); + std::string s(str); + msg += "Water: Old=" + String(s.c_str()); + + int val = analogRead(sensorPin); // connect sensor to Analog pin + int valueMinDiff = abs(val - Value_dry); + int maxMinDiff = valueMinDiff * 100 / percent; + Value_wet = abs(Value_dry - maxMinDiff); + sprintf(str, "%d", Value_wet); + std::string s2(str); + msg += ", New=" + String(s2.c_str()); + + saveJson(); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", CALIBRATE_RESULT, BROADCAST, msg, espInterval, WEB_REQUEST_RESULT, capacitance); +} + +void setDeviceName(const char *deviceName) +{ + Serial.printf("Update device name: %s\n\n", deviceName); + + DEVICE_NAME = deviceName; + saveJson(); + + char bleName[80] = ""; + sprintf(bleName, "ESP32-LiquidPrep-%s", DEVICE_NAME); + Serial.printf("Changing BLE name to: %s\n", bleName); + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->stop(); + + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + oAdvertisementData.setName(bleName); + + pAdvertising->setAdvertisementData(oAdvertisementData); + + pAdvertising->start(); + + Serial.println("Name change complete. New name is now advertising."); +} + +class BLECallbacks : public BLECharacteristicCallbacks +{ + void onWrite(BLECharacteristic *pCharacteristic) + { + std::string value = pCharacteristic->getValue(); + DynamicJsonDocument pdoc(512); + char payload[80]; + int j = 0; + + if (value.length() > 0) + { + Serial.printf("*********: %d\n", value.length()); + Serial.print("New value: "); + for (int i = 0; i < value.length(); i++) + { + if (i % 2 == 0) + { + payload[j++] = value[i]; + Serial.print(value[i]); + } + } + payload[j] = '\0'; + Serial.println(); + Serial.println("*********"); + + Serial.printf("%s\n", payload); + deserializeJson(pdoc, payload); + Serial.printf("%s, %s\n", pdoc["type"].as(), pdoc["value"].as()); + + if (pdoc["type"].as() == "CHANNEL") + { + int channel = atoi(pdoc["value"].as().c_str()); + processWifiUpdates(channel); + } + else if (pdoc["type"].as() == "CALIBRATE") + { + int mode = pdoc["value"].as() == "water" ? CALIBRATE_WATER : CALIBRATE_AIR; + calibrateSensor(mode); + } + else if (pdoc["type"].as() == "NAME") + { + setDeviceName(pdoc["value"].as().c_str()); + } + else if (pdoc["type"].as() == "PIN") + { + int newSensorPin = atoi(pdoc["value"].as().c_str()); + sensorPin = newSensorPin; // Update the global sensorPin variable + pinMode(sensorPin, INPUT); // Set the pin mode to input + saveJson(); + Serial.printf("Sensor pin updated to: %d and acknowledgment sent.\n", sensorPin); + } + } + } +}; +void calculate() +{ + int val = analogRead(sensorPin); // connect sensor to Analog pin + + + soilmoisturepercent = map(val, Value_dry, Value_wet, 0, 100); + soilmoisturepercent = constrain(soilmoisturepercent, 0, 100); + + char str[8]; + + Serial.printf("sensor reading: %d - %f%\n", val, soilmoisturepercent); // print the value to serial port + dtostrf(soilmoisturepercent, 1, 2, str); + moistureLevel = str; +} + +void calculate2() +{ + int val = analogRead(sensorPin); // connect sensor to Analog pin + + // soilmoisturepercent = map(soilMoistureValue, airValue, waterValue, 0, 100); + int valueMinDiff = abs(val - Value_dry); + int maxMinDiff = abs(Value_dry - Value_wet); + soilmoisturepercent = ((float)valueMinDiff / maxMinDiff) * 100; + + char str[8]; + if (soilmoisturepercent < 0) + { + soilmoisturepercent = 0; + } + else if (soilmoisturepercent > 100) + { + soilmoisturepercent = 100; + } + Serial.printf("sensor reading: %d - %f%\n", val, soilmoisturepercent); // print the value to serial port + dtostrf(soilmoisturepercent, 1, 2, str); + moistureLevel = str; +} + +void moistureJson() +{ + calculate(); + Serial.printf("sensor reading: %s", moistureLevel); +} + +uint16_t connId = 0; // This should be globally declared if it needs to be accessed in other functions + +void enableBluetooth() +{ + char bleName[80] = ""; + sprintf(bleName, "ESP32-LiquidPrep-%s", DEVICE_NAME); + Serial.printf("Starting BLE work! %s\n", bleName); + + BLEDevice::init(bleName); + pServer = BLEDevice::createServer(); + + // Keep track of connection ID when a device connects + // pServer->setCallbacks(new MyServerCallbacks()); // you may need to define the callback to get the connection ID + + BLEService *pService = pServer->createService(SERVICE_UUID); + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE); + + pCharacteristic->setCallbacks(new BLECallbacks()); + pService->start(); + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + Serial.println("Characteristic defined! Now you can read it on your phone!"); +} + +void disableBluetooth() +{ + if (pServer) + { + uint16_t connId = 0; // Replace with actual connection ID + + pServer->getAdvertising()->stop(); + + pServer->disconnect(connId); // Now passing a connection ID + + delete pServer; + pServer = nullptr; // Reset pServer to nullptr + + Serial.println("Bluetooth disabled"); + } + else + { + Serial.println("Bluetooth is not enabled"); + } +} + +// callback when data is sent +void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) +{ + Serial.printf("Last Packet Send Status: %u, %s\t", receiverAddress, hostMac); + Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); + Serial.println(""); +} + +void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) +{ + struct_message payload = struct_message(); + memcpy(&payload, incomingData, sizeof(payload)); + + Serial.printf("Bytes received at %s: ------\n", DEVICE_NAME); + Serial.printf("%d, moisture: %s, %d from %s, %d, %d, %d, %s\n", len, payload.moisture, payload.capacitance, payload.name, payload.task, payload.type, payload.from, payload.msg); + Serial.printf("=> msgId: %d\n", payload.msgId); + Serial.println("------\n"); + + if (isMessageSeen(payload.msgId)) + { + Serial.printf("%d from %s, %d, Message %d already seen, ignoring...\n", len, payload.name, payload.task, payload.msgId); + return; // The message is a duplicate, don't send it again + } + else + { + if (payload.hostAddress == hostMac) + { + Serial.println("processing...\n"); + int from = payload.from == WEB_REQUEST ? WEB_REQUEST_RESULT : NO_TASK; + char msg[80]; + int bluetooth = pServer ? 1 : 0; + switch (payload.task) + { + case PING: + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", PING_BACK, BROADCAST, DEVICE_NAME, espInterval, from, capacitance); + Serial.printf("%d, %s, %s, %s, %d, %s\n", payload.id, payload.name, payload.hostAddress, payload.senderAddress, payload.task, payload.msg); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + break; + case QUERY: + sprintf(msg, "%d,%d,%d,%d,%s,%s,%s,%d", Value_dry, Value_wet, sensorPin, wifiChannel, hostMac.c_str(), "", "?", bluetooth); + Serial.printf("msg: %s -> %d", msg, from); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", QUERY_RESULT, BROADCAST, msg, espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + break; + case CALIBRATE_AIR: + case CALIBRATE_WATER: + calibrateSensor(payload.task); + break; + case GET_MOISTURE: + calculate(); + sprintf(msg, "%d,%d,%d,%d,%s,%s,%s,%d", Value_dry, Value_wet, sensorPin, wifiChannel, hostMac.c_str(), "", moistureLevel, bluetooth); + setPayload(payload, DEVICE_ID, DEVICE_NAME, "", hostMac, "", MOISTURE_RESULT, BROADCAST, msg, espInterval, from, capacitance); + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + break; + case UPDATE_DEVICE_NAME: + Serial.printf("update device name: %s\n\n", payload.name); + DEVICE_NAME = payload.name; + saveJson(); + break; + case UPDATE_DEVICE_ID: + Serial.printf("update device id: %d\n\n", payload.id); + DEVICE_ID = payload.id; + saveJson(); + break; + case UPDATE_ESP_INTERVAL: + Serial.printf("update esp_interval: %d\n\n", payload.espInterval); + espInterval = payload.espInterval; + saveJson(); + break; + case ENABLE_BLUETOOTH: + Serial.println("enable bluetooth"); + enableBluetooth(); + break; + case DISABLE_BLUETOOTH: + Serial.println("disable bluetooth"); + disableBluetooth(); + break; + case UPDATE_PIN: + Serial.printf("update sensor pin: %d\n\n", payload.espInterval); + sensorPin = payload.espInterval; + pinMode(sensorPin, INPUT); + saveJson(); + break; + default: + Serial.println("Nothing to do.\n"); + break; + } + } + else + { + if (payload.type == BROADCAST) + { + if (payload.task == UPDATE_WIFI_CHANNEL) + { + int32_t channel = atoi(payload.msg); + Serial.printf("Wifi channel: %d", channel); + if (channel != wifiChannel) + { + processWifiUpdates(channel); + } + else + { + Serial.println("Same wifi channel, nothing to do."); + } + } + else + { + Serial.printf("relate broadcast %d from %s, %s, %d, %d, %d, %s\n\n", payload.msgId, payload.name, payload.senderAddress, payload.task, payload.type, payload.from, payload.msg); + esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + } + } + else + { + Serial.println("Else nothing to do.\n"); + } + } + } +} + +void setup() +{ + int waitCount = 0; + delay(1000); + // Init Serial Monitor + Serial.begin(115200); + + while (!SPIFFS.begin(true) && waitCount++ < 3) + { + delay(1000); + } + File config = SPIFFS.open("/config.json", "r"); + if (config) + { + DeserializationError error = deserializeJson(doc, config); + if (error) + { + Serial.println(F("Failed to read file, using default configuration")); + Serial.println(error.c_str()); + saveJson(); + } + else + { + JsonObject obj = doc.as(); + if (!doc["deviceName"] || doc["espInterval"] <= 0) + { + saveJson(); // data corrupted, use default values + } + else + { + Value_dry = doc["airValue"]; + Value_wet = doc["waterValue"]; + sensorPin = doc["sensorPin"]; + DEVICE_ID = obj["deviceId"]; + DEVICE_NAME = doc["deviceName"].as(); + espInterval = doc["espInterval"]; + wifiChannel = doc["wifiChannel"]; + receiverMac = doc["receiverMac"].as(); + senderMac = doc["senderMac"].as(); + stringToInt(receiverMac, receiverAddress); + stringToInt(senderMac, senderAddress); + } + } + config.close(); + } + else + { + saveJson(); + } + Serial.printf("%d, %d, %d, %d, %s, %d, %d, %s, %s\n", Value_dry, Value_wet, sensorPin, DEVICE_ID, DEVICE_NAME, espInterval, wifiChannel, receiverMac, senderMac); + // Set device as a Wi-Fi Station + enableBluetooth(); + setWifiChannel(wifiChannel); + + Serial.println("Initializing..."); + Serial.println("My MAC address is: " + WiFi.macAddress()); + hostMac = removeFromString(WiFi.macAddress(), (char *)":"); + stringToInt(hostMac, hostAddress); + stringToInt(receiverMac, receiverAddress); + Serial.printf("My MAC address is: %s, %u, %u\n", hostMac, hostAddress, receiverAddress); + Serial.printf("sender: %s, %u, receiver: %s, %u\n", senderMac, senderAddress, receiverMac, receiverAddress); + + Serial.print("Wi-Fi Channel: "); + Serial.println(WiFi.channel()); + + // WiFi.mode(WIFI_STA); + // int32_t channel = 1; + // WiFi.printDiag(Serial); // Uncomment to verify channel number before + // esp_wifi_set_promiscuous(true); + // esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + // esp_wifi_set_promiscuous(false); + // WiFi.printDiag(Serial); // Uncomment to verify channel change after + // WiFi.disconnect(); // we do not want to connect to a WiFi network + + // Init ESP-NOW + if (esp_now_init() != ESP_OK) + { + Serial.println("Error initializing ESP-NOW"); + return; + } + + // Once ESPNow is successfully Init, we will register for Send CB to + // get the status of Trasnmitted packet + // esp_now_set_self_role(MY_ROLE); + esp_now_register_recv_cb(OnDataRecv); // this function will get called once all data is sent + + esp_now_register_send_cb(OnDataSent); + + peerInfo = {}; + memcpy(peerInfo.peer_addr, broadcastAddress, 6); + // peerInfo.ifidx = ESP_IF_WIFI_STA; + peerInfo.encrypt = false; + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK) + { + Serial.println("Failed to add peer"); + return; + } + else + { + Serial.printf("Adding peer: %u\n", peerInfo.peer_addr); + } + // stringToInt(gatewayMac, gatewayMacAddress); + // addPeer(gatewayMacAddress); + // Serial.printf("Adding gateway: %u\n", gatewayMacAddress); +} + +void loop() +{ + calculate(); + // Set values to send + struct_message payload = struct_message(); + payload.id = DEVICE_ID; + payload.name = DEVICE_NAME; + payload.capacitance = capacitance; + payload.hostAddress = hostMac; + payload.senderAddress = hostMac; + payload.espInterval = espInterval; + Serial.printf("info: %d, %d, %d, %s, %d, %s, %s, %s\n", wifiChannel, espInterval, capacitance, moistureLevel, payload.id, payload.name, payload.senderAddress, payload.receiverAddress); + payload.type = BROADCAST; + payload.moisture = moistureLevel; + payload.msgId = generateMessageHash(payload); + esp_now_send(broadcastAddress, (uint8_t *)&payload, sizeof(payload)); + // esp_now_send(gatewayMacAddress, (uint8_t *)&payload, sizeof(payload)); + + pCharacteristic->setValue(moistureLevel.c_str()); + delay(espInterval); +} diff --git a/esp-now/esp-now-plantmate-mesh/test/README b/esp-now/esp-now-plantmate-mesh/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/esp-now/esp-now-plantmate-mesh/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/esp-now/esp-now-websocket/include/common.h b/esp-now/esp-now-websocket/include/common.h index 001a6ef..3e7bde9 100644 --- a/esp-now/esp-now-websocket/include/common.h +++ b/esp-now/esp-now-websocket/include/common.h @@ -32,6 +32,8 @@ enum Task { CALIBRATE_AIR, CALIBRATE_WATER, CALIBRATE_RESULT, + ENABLE_BLUETOOTH, + DISABLE_BLUETOOTH, BROADCAST }; Task str2enum(const std::string& str) { diff --git a/esp-now/include/common.h b/esp-now/include/common.h index 60d8a3e..664ce1d 100644 --- a/esp-now/include/common.h +++ b/esp-now/include/common.h @@ -2,6 +2,7 @@ #include #include #include +#include #include "FS.h" #define FIRMWARE_VERSION "0.2.3" @@ -37,7 +38,10 @@ enum Task { BROADCAST, WEB_REQUEST, WEB_REQUEST_RESULT, - UPDATE_WIFI_RESULT + UPDATE_WIFI_RESULT, + ENABLE_BLUETOOTH, + DISABLE_BLUETOOTH, + UPDATE_PIN }; Task str2enum(const std::string& str) { if(str == "UPDATE_RECEIVER_ADDR") return UPDATE_RECEIVER_ADDR; @@ -52,6 +56,7 @@ Task str2enum(const std::string& str) { else if(str == "CONNECT_WITH_ME") return CONNECT_WITH_ME; else if(str == "MESSAGE_ONLY") return MESSAGE_ONLY; else if(str == "PING") return PING; + else if(str == "UPDATE_PIN") return UPDATE_PIN; else return NO_TASK; } @@ -86,6 +91,8 @@ typedef struct struct_message { int type; int from; uint32_t msgId; + int bluetooth; + int capacitance; } struct_message; // Common utility functions @@ -144,6 +151,48 @@ void stringToInt(String mac, uint8_t *output) { output[i] = ( addr >> ( ( 5 - i ) * 8 ) ) & 0xFF; } } +int getMostFrequent(int arr[], int n) { + std::unordered_map elements; + for (int i = 0; i < n; i++) { + elements[arr[i]]++; + } + int maxCount = 0, res = -1; + for (auto i : elements) { + if (maxCount < i.second) { + res = i.first; + maxCount = i.second; + } + } + return res; +} +void calibrateAirFrequency(int &air, int pin) { + Serial.println("Leave Moisture sensor out of water for calibration"); + int freqValue = 0; + int values[100] = {0}; + for (int i = 0; i < 100; i++) { + int val = analogRead(pin); + Serial.println(val); + values[i] = val; + delay(300); + } + freqValue = getMostFrequent(values, 100); + Serial.println(freqValue); + air = freqValue; +} +void calibrateWaterFrequency(int &water, int pin) { + Serial.println("Put Moisture sensor in water for calibration"); + int freqValue = 0; + int values[100] = {0}; + for (int i = 0; i < 100; i++) { + int val = analogRead(pin); + Serial.println(val); + values[i] = val; + delay(300); + } + freqValue = getMostFrequent(values, 100); + Serial.println(freqValue); + water = freqValue; +} void calibrateAir(int &air, int pin) { Serial.println("Leave Moisture sensor out of water for calibration"); int maxValue = 0; @@ -172,9 +221,12 @@ void calibrateWater(int &water, int pin) { Serial.println(minValue); water = minValue; } -void setPayload(struct_message &payload, int id, String name, String host, String sender, String receiver, int task, int type, String msg, int interval, int from) { +void setPayload(struct_message &payload, int id, String name, String host, String sender, String receiver, int task, int type, String msg, int interval, int from, int capacitance = -1) { // Note: Important for upstream message, set payload.senderAddress=hostMac, payload.hostAddress=receiverMac + payload = struct_message(); + + // Set payload fields payload.id = id; payload.name = name; payload.hostAddress = host; @@ -184,8 +236,16 @@ void setPayload(struct_message &payload, int id, String name, String host, Strin payload.type = type; payload.espInterval = interval; payload.from = from; - sprintf(payload.msg, "%s", msg.c_str()); + + // Set capacitance if provided (default -1 indicates not provided) + if (capacitance != -1) { + payload.capacitance = capacitance; + } + + // Convert msg to a char array and assign to payload + snprintf(payload.msg, sizeof(payload.msg), "%s", msg.c_str()); } + void espNowSend(String receiver, struct_message payload) { stringToInt(receiverMac, tmpAddress); esp_now_send(tmpAddress, (uint8_t *) &payload, sizeof(payload));