[{"content":"This program can be used to visualize incoming accelerometer and gyroscope data from an MPU-6050. It does this by using an ESP32 to host a web server. The server exhibits a prism whose 3D rotation is based on the orientation of the accelerometer. This program was originally made using Visual Studio Code and the extension PlatformIO. However, it still uses the Arduino framework, so if needed the Arduino IDE can be used*.\nTo keep the error from accumulating, a simple Kalman Filter is used on the pitch and roll angles. The reason it was not done for the yaw angle (z axis) is because it is not mathematically possible to directly solve for the yaw angle from only the gravitational accelerations in the x, y, and z axes. This is important because for the most basic version of the Kalman Filter to work, it needs to be able to have a physical equation that can create an estimate for that value. This is why many modern accelerometers nowadays come with magnetometers (basically a compass) which can directly measure the accelerometer\u0026rsquo;s heading or yaw angle.\n*Tutorial can be found here.\nMaterials ESP32 DevKit V1 (ESP32-WROOM-32) Micro-USB Cable* MPU-6050 Breadboard Jumper Wire(s) *Must be capable of data transmission for uploading; can not be a power only cable\nWiring The ESP32 communicates with the MPU-6050 using I2C. Simply connect the SCL/SDA pins of the ESP32 and MPU-6050 together, supply it with 3.3 V, and connect it to ground.\nThis is the pinout for the ESP32-WROOM-32 board. If you would like to use a different ESP32 board, make sure to check your own pinout online.\nYouTube Demonstration Software Option 1:\nVisual Studio Code PlatformIO Option 2:\nArduino IDE Source Code See all code and setup instructions in my GitHub repository: https://github.com/Mohamed1628/Kalman-Filter-MPU6050\n#include \u0026lt;Arduino.h\u0026gt; #include \u0026lt;WiFi.h\u0026gt; #include \u0026lt;AsyncTCP.h\u0026gt; #include \u0026lt;ESPAsyncWebServer.h\u0026gt; #include \u0026lt;Adafruit_MPU6050.h\u0026gt; #include \u0026lt;Adafruit_Sensor.h\u0026gt; #include \u0026lt;Arduino_JSON.h\u0026gt; #include \u0026#34;LittleFS.h\u0026#34; #include \u0026#34;KalmanFilter.h\u0026#34; // Declare functions void initMPU(); void initLittleFS(); void initWiFi(); String getGyroReadings(); String getAccReadings(); String getTemperature(); // Replace with your network credentials // Note: ESP32 will not connect with ssid such as \u0026#34;John\u0026#39;s iPhone\u0026#34;; Change your ssid to be \u0026#34;Johns iPhone\u0026#34; for example (no apostrophe) const char* ssid = \u0026#34;YOUR_WIFI_NAME\u0026#34;; const char* password = \u0026#34;YOUR_WIFI_PASSWORD\u0026#34;; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); // Create an Event Source on /events AsyncEventSource events(\u0026#34;/events\u0026#34;); // Json Variable to Hold Sensor Readings JSONVar readings; // Timer variables unsigned long lastTime = 0; unsigned long lastTimeTemperature = 0; unsigned long lastTimeAcc = 0; unsigned long gyroDelay = 10; unsigned long temperatureDelay = 1000; unsigned long accelerometerDelay = 200; // Create a sensor object Adafruit_MPU6050 mpu; sensors_event_t a, g, temp; float gyroX = 0; float gyroY = 0; float gyroZ = 0; float estimateGyroX = 0; float estimateGyroY = 0; float accX, accY, accZ; float temperature; KalmanFilter kalmanX(0.001, 0.003, 0.03); KalmanFilter kalmanY(0.001, 0.003, 0.03); KalmanFilter kalmanz(0.001, 0.003, 0.03); float kalX = 0; float kalY = 0; float kalZ = 0; // Initialize MPU6050 void initMPU(){ if (!mpu.begin()) { Serial.println(\u0026#34;Failed to find MPU6050 chip\u0026#34;); while (1) { delay(10); } } Serial.println(\u0026#34;MPU6050 Found!\u0026#34;); } void initLittleFS() { if (!LittleFS.begin()) { Serial.println(\u0026#34;An error has occurred while mounting LittleFS\u0026#34;); } Serial.println(\u0026#34;LittleFS mounted successfully\u0026#34;); } // Initialize WiFi void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.println(\u0026#34;\u0026#34;); Serial.print(\u0026#34;Connecting to WiFi...\u0026#34;); while (WiFi.status() != WL_CONNECTED) { Serial.print(\u0026#34;.\u0026#34;); delay(1000); } Serial.println(\u0026#34;\u0026#34;); Serial.println(WiFi.localIP()); } String getGyroReadings(){ mpu.getEvent(\u0026amp;a, \u0026amp;g, \u0026amp;temp); accX = a.acceleration.x; accY = a.acceleration.y; accZ = a.acceleration.z; gyroX = g.gyro.x; gyroY = g.gyro.y; // Calculate rotational velocities for X and Y axis (rad/s) estimateGyroY = -atan2(accX, sqrt(accY*accY + accZ*accZ)); estimateGyroX = atan2(accY, accZ); // Kalman filter kalY = kalmanY.update(estimateGyroY, gyroY); kalX = kalmanX.update(estimateGyroX, gyroX); // No trigonometric equation exists to calculate rotational velocity in the z direction given gravitation accelerations // To simplify things, we check if the value is bigger than the rotation velocity when the accelerometer is static (not moving) // If that is true, then we consider that we have a valid reading, as in the accelerometer is non-static (is moving) float gyroZ_temp = g.gyro.z; if(abs(gyroZ_temp) \u0026gt; 0.03) { // current angle (rad) = last angle (rad) + angular velocity (rad/s) * time(s) gyroZ += gyroZ_temp * 0.01; } readings[\u0026#34;gyroX\u0026#34;] = String(kalX); readings[\u0026#34;gyroY\u0026#34;] = String(kalY); readings[\u0026#34;gyroZ\u0026#34;] = String(gyroZ); String jsonString = JSON.stringify(readings); return jsonString; } String getAccReadings(){ mpu.getEvent(\u0026amp;a, \u0026amp;g, \u0026amp;temp); // Get current acceleration values accX = a.acceleration.x; accY = a.acceleration.y; accZ = a.acceleration.z; readings[\u0026#34;accX\u0026#34;] = String(accX); readings[\u0026#34;accY\u0026#34;] = String(accY); readings[\u0026#34;accZ\u0026#34;] = String(accZ); String accString = JSON.stringify (readings); return accString; } String getTemperature(){ mpu.getEvent(\u0026amp;a, \u0026amp;g, \u0026amp;temp); temperature = temp.temperature; return String(temperature); } void setup() { Serial.begin(115200); initWiFi(); initLittleFS(); initMPU(); // Handle Web Server server.on(\u0026#34;/\u0026#34;, HTTP_GET, [](AsyncWebServerRequest *request){ request-\u0026gt;send(LittleFS, \u0026#34;/index.html\u0026#34;, \u0026#34;text/html\u0026#34;); }); server.serveStatic(\u0026#34;/\u0026#34;, LittleFS, \u0026#34;/\u0026#34;); server.on(\u0026#34;/reset\u0026#34;, HTTP_GET, [](AsyncWebServerRequest *request){ gyroX=0; gyroY=0; gyroZ=0; request-\u0026gt;send(200, \u0026#34;text/plain\u0026#34;, \u0026#34;OK\u0026#34;); }); server.on(\u0026#34;/resetX\u0026#34;, HTTP_GET, [](AsyncWebServerRequest *request){ gyroX=0; request-\u0026gt;send(200, \u0026#34;text/plain\u0026#34;, \u0026#34;OK\u0026#34;); }); server.on(\u0026#34;/resetY\u0026#34;, HTTP_GET, [](AsyncWebServerRequest *request){ gyroY=0; request-\u0026gt;send(200, \u0026#34;text/plain\u0026#34;, \u0026#34;OK\u0026#34;); }); server.on(\u0026#34;/resetZ\u0026#34;, HTTP_GET, [](AsyncWebServerRequest *request){ gyroZ=0; request-\u0026gt;send(200, \u0026#34;text/plain\u0026#34;, \u0026#34;OK\u0026#34;); }); // Handle Web Server Events events.onConnect([](AsyncEventSourceClient *client){ if(client-\u0026gt;lastId()){ Serial.printf(\u0026#34;Client reconnected! Last message ID that it got is: %u\\n\u0026#34;, client-\u0026gt;lastId()); } // send event with message \u0026#34;hello!\u0026#34;, id current millis // and set reconnect delay to 1 second client-\u0026gt;send(\u0026#34;hello!\u0026#34;, NULL, millis(), 10000); }); server.addHandler(\u0026amp;events); server.begin(); } void loop() { if ((millis() - lastTime) \u0026gt; gyroDelay) { // Send Events to the Web Server with the Sensor Readings events.send(getGyroReadings().c_str(),\u0026#34;gyro_readings\u0026#34;,millis()); lastTime = millis(); } if ((millis() - lastTimeAcc) \u0026gt; accelerometerDelay) { // Send Events to the Web Server with the Sensor Readings events.send(getAccReadings().c_str(),\u0026#34;accelerometer_readings\u0026#34;,millis()); lastTimeAcc = millis(); } if ((millis() - lastTimeTemperature) \u0026gt; temperatureDelay) { // Send Events to the Web Server with the Sensor Readings events.send(getTemperature().c_str(),\u0026#34;temperature_reading\u0026#34;,millis()); lastTimeTemperature = millis(); } } ","permalink":"https://mohamedalzoubi.onrender.com/posts/accelerometer-web-server/","summary":"This program can be used to visualize incoming accelerometer and gyroscope data from an MPU-6050. It does this by using an ESP32 to host a web server. The server exhibits a prism whose 3D rotation is based on the orientation of the accelerometer. This program was originally made using Visual Studio Code and the extension PlatformIO. However, it still uses the Arduino framework, so if needed the Arduino IDE can be used*.","title":"3D Visualization of Kalman Filtered Accelerometer and Gyroscope Data"},{"content":"This program can be used to see TCP, UDP, ICMP, and other incoming packets to an ESP32 board that is connected to a WiFi network. A different color LED is also turned on when different protocol packets are received (red for ICMP, green for TCP, blue for UDP, and yellow for others). It also outputs the source IP address of each packet. This program uses Visual Studio Code and two extensions: ESP-IDF (Espressif IoT Development Framework) and PlatformIO.\nMaterials ESP32 DevKit V1 (ESP32-WROOM-32) 4 LEDs Breadboard 220 Ω Resistors (optional) Jumper Wire(s) Wiring 4 LEDs (Red, Green, Blue, Yellow) to represent 4 packet types (ICMP, TCP, UDP, and Other). Any GPIO pins can be used, in this case, GPIO 2, 5, 21, and 23 were used (ESP32-WROOM-32). The 220 Ω Resistors are optional.\nThis is the pinout for the ESP32-WROOM-32 board. If you would like to use a different ESP32 board, make sure to check your own pinout online.\nYouTube Demonstration + Explanation Software Visual Studio Code ESP-IDF PlatformIO Source Code See all code and setup instructions in my GitHub repository: https://github.com/Mohamed1628/ESP32-WiFi-Packets\n#include \u0026#34;lwip_hooks.h\u0026#34; // our custom header file #include \u0026lt;stdio.h\u0026gt; #include \u0026#34;freertos/FreeRTOS.h\u0026#34; #include \u0026#34;freertos/task.h\u0026#34; #include \u0026#34;freertos/timers.h\u0026#34; #include \u0026#34;freertos/event_groups.h\u0026#34; #include \u0026#34;esp_wifi.h\u0026#34; #include \u0026#34;esp_log.h\u0026#34; #include \u0026#34;nvs_flash.h\u0026#34; #include \u0026#34;esp_netif.h\u0026#34; #include \u0026#34;esp_http_server.h\u0026#34; #include \u0026#34;driver/gpio.h\u0026#34; #define SSID \u0026#34;NETWORK NAME\u0026#34; // apostrophes will not work in SSID (i.e. John\u0026#39;s iPhone) --\u0026gt; (John iPhone) #define PASS \u0026#34;PASSWORD\u0026#34; static void wifi_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { switch (event_id) { case WIFI_EVENT_STA_START: printf(\u0026#34;WiFi connecting WIFI_EVENT_STA_START ... \\n\u0026#34;); break; case WIFI_EVENT_STA_CONNECTED: printf(\u0026#34;WiFi connected WIFI_EVENT_STA_CONNECTED ... \\n\u0026#34;); break; case WIFI_EVENT_STA_DISCONNECTED: printf(\u0026#34;WiFi lost connection WIFI_EVENT_STA_DISCONNECTED ... \\n\u0026#34;); break; case IP_EVENT_STA_GOT_IP: printf(\u0026#34;WiFi got IP ... \\n\\n\u0026#34;); break; default: break; } } void wifi_connection() { nvs_flash_init(); esp_netif_init(); esp_event_loop_create_default(); esp_netif_create_default_wifi_sta(); wifi_init_config_t wifi_initiation = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(\u0026amp;wifi_initiation); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL); wifi_config_t wifi_configuration = { .sta = { .ssid = SSID, .password = PASS}}; esp_wifi_set_config(ESP_IF_WIFI_STA, \u0026amp;wifi_configuration); esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_start(); esp_wifi_connect(); } static esp_err_t post_handler(httpd_req_t *req) { vTaskDelay(1000 / portTICK_PERIOD_MS); httpd_resp_send(req, \u0026#34;URI POST Response ... from ESP32\u0026#34;, HTTPD_RESP_USE_STRLEN); return ESP_OK; } void server_initiation() { httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); httpd_handle_t server_handle = NULL; httpd_start(\u0026amp;server_handle, \u0026amp;server_config); httpd_uri_t uri_post = { .uri = \u0026#34;/\u0026#34;, .method = HTTP_POST, .handler = post_handler, .user_ctx = NULL}; httpd_register_uri_handler(server_handle, \u0026amp;uri_post); } const char *get_protocol(u16_t type) { switch (type) { case 1: // Red LED is ICMP gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_NUM_2,1); vTaskDelay(10); gpio_set_level(GPIO_NUM_2,0); return \u0026#34;ICMP\u0026#34;; case 6: // Green LED is TCP gpio_set_direction(GPIO_NUM_21, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_NUM_21,1); vTaskDelay(10); gpio_set_level(GPIO_NUM_21,0); return \u0026#34;TCP\u0026#34;; case 17: // Blue LED is UDP gpio_set_direction(GPIO_NUM_5, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_NUM_5,1); vTaskDelay(10); gpio_set_level(GPIO_NUM_5,0); return \u0026#34;UDP\u0026#34;; default: // Yellow LED is Other gpio_set_direction(GPIO_NUM_23, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_NUM_23,1); vTaskDelay(10); gpio_set_level(GPIO_NUM_23,0); return \u0026#34;-\u0026#34;; } } int lwip_hook_ip4_input(struct pbuf *pbuf, struct netif *input_netif) { const struct ip_hdr *iphdr; char ip_address[IP_HLEN]; iphdr = (struct ip_hdr *)pbuf-\u0026gt;payload; // Formatting Terminal Output sprintf(ip_address, \u0026#34;%d.%d.%d.%d\u0026#34;, ip4_addr1_16_val(iphdr-\u0026gt;src), ip4_addr2_16_val(iphdr-\u0026gt;src), ip4_addr3_16_val(iphdr-\u0026gt;src), ip4_addr4_16_val(iphdr-\u0026gt;src)); ESP_LOGI(\u0026#34;HOOK\u0026#34;, \u0026#34;%s: %s\u0026#34;, get_protocol((u16_t)IPH_PROTO(iphdr)), ip_address); return ESP_OK; } void app_main(void) { wifi_connection(); server_initiation(); } ","permalink":"https://mohamedalzoubi.onrender.com/posts/esp32-wifi-packets/","summary":"This program can be used to see TCP, UDP, ICMP, and other incoming packets to an ESP32 board that is connected to a WiFi network. A different color LED is also turned on when different protocol packets are received (red for ICMP, green for TCP, blue for UDP, and yellow for others). It also outputs the source IP address of each packet. This program uses Visual Studio Code and two extensions: ESP-IDF (Espressif IoT Development Framework) and PlatformIO.","title":"ESP32 Wi-Fi Packets"},{"content":"This box can be used to verify that an OBDII device can both send and recieve CAN data frames. It does this using a CAN bus module that allows the Arduino Uno to communicate with the device over the CAN High/Low pins of the OBD port.\nMaterials Arduino Uno R3 MCP2515 CAN Bus Module OBDII Female Port Power Supplies Jumper Wires Wiring Also note that, when not being programmed, the Uno is powered with an external 9V 3A power supply. Also, depending on the device you are testing, it will also need to be powered to sense that it is currently connected to something (our box). You will need an extra power supply for this. Or if you have a larger power supply you can use a simple voltage divider circuit. For example, if you have a device that normally requires 12V, then you can use one 20V power supply. The 20V would split to be 9V for the Arduino and 12V for the device. In my experience, if the device was not being powered and I sent it some frames, I would get no response back from the device. So try powering your device as well.\nHere is an example of what I am talking about. 2 Power Supplies (or just one with a voltage divider).\nThis is the pinout for the Arduino Uno R3 board. If you would like to use a different Uno board, make sure to check your own pinout online.\nSoftware You only need the Arduino IDE for this project.\nSource Code See all code and setup instructions in my GitHub repository: https://github.com/Mohamed1628/CAN-Troubleshooter\n#include \u0026lt;mcp_can.h\u0026gt; #include \u0026lt;SPI.h\u0026gt; long unsigned int rxId; unsigned char len = 0; unsigned char rxBuf[8]; char msgString[128]; // Array to store serial string #define CAN0_INT 2 // Set INT to pin 2 (this pin is optional; just an interrupt pin) MCP_CAN CAN0(10); // Set CS to pin 10 (required) void setup() { Serial.begin(115200); // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled. if(CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_16MHZ) == CAN_OK) // Might need to change baud rate for your device Serial.println(\u0026#34;MCP2515 Initialized Successfully!\u0026#34;); else Serial.println(\u0026#34;Error Initializing MCP2515...\u0026#34;); CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data. pinMode(CAN0_INT, INPUT); // Configuring pin for /INT input Serial.println(\u0026#34;MCP2515 Library Receive Example...\u0026#34;); } // Defining the message we are going to send to the device byte data[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; int ID = 0x18DAF110; // works with standard and extended CAN IDs void loop() { // Here we are sending a CAN data frame to the device from the ID, defined earlier CAN_Send(ID, data); // Ex: CAN_Send(0x18DB33F1, data) // To send multiple frames in a row: CAN_Send(ID, data); CAN_Send(ID, data); CAN_Send(ID, data); // Next we are reading CAN data frames response in from the device CAN_Read(); } void CAN_Send(int ID, byte data[8]) { // send data: ID = 0x100, Standard CAN Frame (0), Data length = 8 bytes, \u0026#39;data\u0026#39; = array of data bytes to send byte sndStat = CAN0.sendMsgBuf(ID, 0, 8, data); if(sndStat == CAN_OK) { Serial.println(\u0026#34;Message Sent Successfully!\u0026#34;); } else { Serial.println(\u0026#34;Error Sending Message...\u0026#34;); } delay(100); // send data per 100ms } void CAN_Read() { if(!digitalRead(CAN0_INT)) { // If CAN0_INT pin is low, read receive buffer CAN0.readMsgBuf(\u0026amp;rxId, \u0026amp;len, rxBuf); // Read data: len = data length, buf = data byte(s) if((rxId \u0026amp; 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits) sprintf(msgString, \u0026#34;Extended ID: 0x%.8lX DLC: %1d Data:\u0026#34;, (rxId \u0026amp; 0x1FFFFFFF), len); else sprintf(msgString, \u0026#34;Standard ID: 0x%.3lX DLC: %1d Data:\u0026#34;, rxId, len); Serial.print(msgString); if((rxId \u0026amp; 0x40000000) == 0x40000000) { // Determine if message is a remote request frame. sprintf(msgString, \u0026#34; REMOTE REQUEST FRAME\u0026#34;); Serial.print(msgString); } else { for(byte i = 0; i\u0026lt;len; i++){ sprintf(msgString, \u0026#34; 0x%.2X\u0026#34;, rxBuf[i]); Serial.print(msgString); } } Serial.println(); } } Included Libraries mcp_can by coryjfowler (1.5.1 as of March 2024) SPI by Arduino ","permalink":"https://mohamedalzoubi.onrender.com/posts/can-communication-troubleshooter/","summary":"This box can be used to verify that an OBDII device can both send and recieve CAN data frames. It does this using a CAN bus module that allows the Arduino Uno to communicate with the device over the CAN High/Low pins of the OBD port.\nMaterials Arduino Uno R3 MCP2515 CAN Bus Module OBDII Female Port Power Supplies Jumper Wires Wiring Also note that, when not being programmed, the Uno is powered with an external 9V 3A power supply.","title":"CAN Communication Troubleshooter"},{"content":"This program lets the user know when a washing machine cycle finished and when the door was opened. It does this using a microphone sensor to listen for the dinging noise at the end of a cycle as well as a humidity sensor to measure when the humidity spikes indicating that steam/vapor is coming out of a freshly opened washing machine. It can also predict the time that a cycle will finish based on the manufacturer cycle time for different modes (delicate, normal, heavy, etc.)\nMaterials ESP8266 NodeMCU ESP-12E 1.0 Electret Microphone Amplifier MAX4466 Module DHT11 Temperature-Humidity Sensor Module Jumper Wires Wiring The DHT11 humidity sensor and microphone module are both powered with 3.3V pins. For the humidity sensor, any GPIO pins can be used. In this case, D1 (GPIO 5) was used. The microphone module, however, must be connected to the one and only analog pin on the NodeMCU which is the A0 pin (GPIO 17).\nThis is the pinout for the ESP8266 NodeMCU ESP-12E board. If you would like to use a different ESP8266 board, make sure to check your own pinout online.\nYouTube Demonstration + Explanation Software Arduino Cloud Arduino Create Agent Source Code See code and setup instructions in my GitHub repository: https://github.com/Mohamed1628/Washing-Machine-Status\n#include \u0026#34;thingProperties.h\u0026#34; #include \u0026#34;DHT.h\u0026#34; #define DHTPIN 5 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); const int micPin = 17; // analog pin used for microphone data pin int sound; // integer to store value from microphone int humidity; // integer to store value from humidity sensor const int audioThreshold = 700; // threshold value that cycle complete tone should surpass const int humidityThreshold = 96; // threshold value that steam from open washer should surpass void setup() { // Initialize serial and dht sensor: Serial.begin(9600); //9600 is baud rate dht.begin(); delay(1500); // Defined in thingProperties.h initProperties(); pinMode(micPin, INPUT); //setting microphone pin as input // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection); cycleStatus = false; // initializing cycle status to false (cycle just started) doorStatus = false; // initializing door status to false (door should be closed initially) setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); } void loop() { ArduinoCloud.update(); float h = dht.readHumidity(); sound = analogRead(micPin); // reading in microphone values to variable sound humidity = h; // reading in humidity values to variable humidity // Uncomment to see sensor values for adjusting threshold // Serial.println(\u0026#34;Sound: \u0026#34;); // Serial.print(sound); // Serial.println(\u0026#34;Humidity: \u0026#34;); // Serial.println(humidity); // if sound is still below threshold then do nothing if (sound \u0026lt; audioThreshold) { // do nothing... } // else set status to true else if (cycleStatus == false) { cycleStatus = true; //complete tone was played completeTime = ArduinoCloud.getLocalTime(); } else { // do nothing since cycleStatus is already true or not above threshold } // if humidity is still below threshold then do nothing if (humidity \u0026lt; humidityThreshold) { //do nothing... } // else set status to true else if (doorStatus == false) { doorStatus = true; //door is open openTime = ArduinoCloud.getLocalTime(); } else { // do nothing since doorStatus is already true or not above threshold } } void onCycleTimeChange() { completeTime = ArduinoCloud.getLocalTime() + (cycleTime*60) - (0.001*millis()); // current time + cycle time - offset of how long arduino has been running // For example: // Let\u0026#39;s say true start time was 12:00pm // 12:05pm + 45 minute cycle = 12:50 - 5 minutes arduino has been running = 12:45pm } ","permalink":"https://mohamedalzoubi.onrender.com/posts/washing-machine-status-checker/","summary":"This program lets the user know when a washing machine cycle finished and when the door was opened. It does this using a microphone sensor to listen for the dinging noise at the end of a cycle as well as a humidity sensor to measure when the humidity spikes indicating that steam/vapor is coming out of a freshly opened washing machine. It can also predict the time that a cycle will finish based on the manufacturer cycle time for different modes (delicate, normal, heavy, etc.","title":"Washing Machine Status Checker"}]