commit 1d70c03f125955f56984a0b9c17e54ce83ec9fe9 Author: Morgan Date: Mon Nov 17 22:54:37 2025 +0900 Init diff --git a/ESP32_MQTT_AM1008WK/ESP32_MQTT_AM1008WK.ino b/ESP32_MQTT_AM1008WK/ESP32_MQTT_AM1008WK.ino new file mode 100644 index 0000000..21ab05c --- /dev/null +++ b/ESP32_MQTT_AM1008WK/ESP32_MQTT_AM1008WK.ino @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include + +const char* WIFI_SSID = ""; +const char* WIFI_PASSWORD = ""; + +const char* MQTT_SERVER = ""; +const int MQTT_PORT = 8883; +const char* MQTT_USER = ""; +const char* MQTT_PASSWORD = ""; +const char* MQTT_CLIENT_ID = "esp32-sensor-001"; + +const char* TOPIC_CO2 = "homeassistant/sensor/esp32/co2"; +const char* TOPIC_VOC = "homeassistant/sensor/esp32/voc"; +const char* TOPIC_TEMP = "homeassistant/sensor/esp32/temperature"; +const char* TOPIC_HUMIDITY = "homeassistant/sensor/esp32/humidity"; +const char* TOPIC_PM1 = "homeassistant/sensor/esp32/pm1"; +const char* TOPIC_PM25 = "homeassistant/sensor/esp32/pm25"; +const char* TOPIC_PM10 = "homeassistant/sensor/esp32/pm10"; +const char* TOPIC_AVAILABILITY = "homeassistant/sensor/esp32/availability"; + +#define DELAY 2000 + +AM1008W_K_I2C am1008w_k_i2c; +WiFiClientSecure espClient; +PubSubClient mqttClient(espClient); + +unsigned long lastReconnect = 0; +unsigned long lastPublish = 0; + +boolean reconnectMQTT() { + Serial.print("Connecting to MQTT..."); + + if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD, + TOPIC_AVAILABILITY, 0, true, "offline")) { + Serial.println("connected!"); + mqttClient.publish(TOPIC_AVAILABILITY, "online", true); + return true; + } + + Serial.print("failed, rc="); + Serial.println(mqttClient.state()); + return false; +} + +void publishSensorData() { + Serial.println(">>"); + + uint8_t ret = am1008w_k_i2c.read_data_command(); + + if (ret == 0) { + int co2 = am1008w_k_i2c.get_co2(); + int voc = am1008w_k_i2c.get_voc(); + float humidity = am1008w_k_i2c.get_humidity(); + float temperature = am1008w_k_i2c.get_temperature(); + int pm1 = am1008w_k_i2c.get_pm1p0(); + int pm25 = am1008w_k_i2c.get_pm2p5(); + int pm10 = am1008w_k_i2c.get_pm10(); + + Serial.printf("> CO2: %d ppm\n", co2); + Serial.printf("> VOC: %d (Lv 0-3)\n", voc); + Serial.printf("> Temp: %.1f°C\n", temperature); + Serial.printf("> Hum: %.1f%%\n", humidity); + Serial.printf("> PM1.0: %d µg/m³\n", pm1); + Serial.printf("> PM2.5: %d µg/m³\n", pm25); + Serial.printf("> PM10: %d µg/m³\n", pm10); + + char payload[50]; + + snprintf(payload, sizeof(payload), "%d", co2); + mqttClient.publish(TOPIC_CO2, payload); + + snprintf(payload, sizeof(payload), "%d", voc); + mqttClient.publish(TOPIC_VOC, payload); + + snprintf(payload, sizeof(payload), "%.1f", temperature); + mqttClient.publish(TOPIC_TEMP, payload); + + snprintf(payload, sizeof(payload), "%.1f", humidity); + mqttClient.publish(TOPIC_HUMIDITY, payload); + + snprintf(payload, sizeof(payload), "%d", pm1); + mqttClient.publish(TOPIC_PM1, payload); + + snprintf(payload, sizeof(payload), "%d", pm25); + mqttClient.publish(TOPIC_PM25, payload); + + snprintf(payload, sizeof(payload), "%d", pm10); + mqttClient.publish(TOPIC_PM10, payload); + + Serial.println(">>"); + + } else { + Serial.println("!! Sensor fail !!"); + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("Initializing I2C..."); + Wire.begin(1, 2); + am1008w_k_i2c.begin(Wire); + Serial.println("Done.\n"); + + Serial.print("Connecting to WiFi"); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("connected."); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(); + + espClient.setInsecure(); + mqttClient.setServer(MQTT_SERVER, MQTT_PORT); + mqttClient.setKeepAlive(60); + mqttClient.setBufferSize(512); + + Serial.println("Init complete.\n"); +} + +void loop() { + if (!mqttClient.connected()) { + if (millis() - lastReconnect > 5000) { + lastReconnect = millis(); + reconnectMQTT(); + } + } else { + mqttClient.loop(); + } + + if (mqttClient.connected() && (millis() - lastPublish > DELAY)) { + publishSensorData(); + lastPublish = millis(); + } + + delay(100); +} \ No newline at end of file diff --git a/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER.ino b/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER.ino new file mode 100644 index 0000000..a27531a --- /dev/null +++ b/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER/ESP32_MQTT_AM1008WK_WITH_BLE_SCANNER.ino @@ -0,0 +1,355 @@ +#include +#include +#include +#include +#include +#include +#include + +#define PRINT_SENSOR_DATA +// #define BLE_DEBUG + +const char* WIFI_SSID = ""; +const char* WIFI_PASSWORD = ""; +const char* MQTT_SERVER = ""; +const int MQTT_PORT = 8883; +const char* MQTT_USER = ""; +const char* MQTT_PASSWORD = ""; +const char* MQTT_CLIENT_ID = "esp32-sensor-001"; + +// AM1008W_K topics +const char* TOPIC_CO2 = "homeassistant/sensor/esp32/co2"; +const char* TOPIC_VOC = "homeassistant/sensor/esp32/voc"; +const char* TOPIC_TEMP = "homeassistant/sensor/esp32/temperature"; +const char* TOPIC_HUMIDITY = "homeassistant/sensor/esp32/humidity"; +const char* TOPIC_PM1 = "homeassistant/sensor/esp32/pm1"; +const char* TOPIC_PM25 = "homeassistant/sensor/esp32/pm25"; +const char* TOPIC_PM10 = "homeassistant/sensor/esp32/pm10"; +const char* TOPIC_AVAILABILITY = "homeassistant/sensor/esp32/availability"; + +// EnvSensor BLE topics +const char* TOPIC_ENV_TEMP = "homeassistant/sensor/env_sensor_1/temperature"; +const char* TOPIC_ENV_HUM = "homeassistant/sensor/env_sensor_1/humidity"; +const char* TOPIC_ENV_PRES = "homeassistant/sensor/env_sensor_1/pressure"; +const char* TOPIC_ENV_VOLT = "homeassistant/sensor/env_sensor_1/voltage"; +const char* TOPIC_ENV_CURR = "homeassistant/sensor/env_sensor_1/current"; +const char* TOPIC_ENV_PWR = "homeassistant/sensor/env_sensor_1/power"; + +// Timing constants +#define SENSOR_DELAY 2000 +#define COMPANY_ID 0xFFFF +#define TARGET_MAC "10:51:db:1c:c2:5e" + +AM1008W_K_I2C am1008w_k_i2c; +WiFiClientSecure espClient; +PubSubClient mqttClient(espClient); +NimBLEScan* pBLEScan; + +unsigned long lastReconnect = 0; +unsigned long lastPublish = 0; +unsigned long lastBLEScan = 0; + +struct NonceKey { + String address; + uint16_t nonce; +}; +#define MAX_NONCE_HISTORY 5 +NonceKey nonceHistory[MAX_NONCE_HISTORY]; +int nonceHistoryIndex = 0; + +struct EnvSensorData { + uint16_t nonce; + float temperature; + float humidity; + float pressure; + float voltage; + float current; + float power; +}; + +boolean isDuplicateNonce(String address, uint16_t nonce) { + for (int i = 0; i < MAX_NONCE_HISTORY; i++) { + if (nonceHistory[i].address == address && nonceHistory[i].nonce == nonce) { + return true; + } + } + return false; +} + +void addNonce(String address, uint16_t nonce) { + nonceHistory[nonceHistoryIndex].address = address; + nonceHistory[nonceHistoryIndex].nonce = nonce; + nonceHistoryIndex = (nonceHistoryIndex + 1) % MAX_NONCE_HISTORY; +} + +boolean parseEnvSensorData(uint8_t* data, size_t length, EnvSensorData* result) { + if (length != 16) { + return false; + } + + uint16_t nonce = data[0] | (data[1] << 8); + int16_t temp_raw = data[2] | (data[3] << 8); + uint16_t hum_raw = data[4] | (data[5] << 8); + uint32_t pres_raw = data[6] | (data[7] << 8) | (data[8] << 16) | (data[9] << 24); + uint16_t voltage_raw = data[10] | (data[11] << 8); + int32_t current_raw = data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24); + + result->nonce = nonce; + result->temperature = temp_raw / 100.0; + result->humidity = hum_raw / 100.0; + result->pressure = pres_raw / 10.0; + result->voltage = voltage_raw / 100.0; + result->current = current_raw / 100.0; + result->power = result->voltage * result->current; + + return true; +} + +void publishEnvSensorData(EnvSensorData* data, String address) { + char payload[50]; + + #ifdef PRINT_SENSOR_DATA + Serial.println(">>"); + Serial.printf("> EnvSensor %s (Nonce: %04X)\n", address.c_str(), data->nonce); + Serial.printf("> T=%.1f°C H=%.1f%% P=%.1fhPa\n", + data->temperature, data->humidity, data->pressure); + Serial.printf("> V=%.2fV I=%.2fmA P=%.2fmW", + data->voltage, data->current, data->power); + Serial.println(">>"); + #endif + + snprintf(payload, sizeof(payload), "%.1f", data->temperature); + mqttClient.publish(TOPIC_ENV_TEMP, payload); + + snprintf(payload, sizeof(payload), "%.1f", data->humidity); + mqttClient.publish(TOPIC_ENV_HUM, payload); + + snprintf(payload, sizeof(payload), "%.1f", data->pressure); + mqttClient.publish(TOPIC_ENV_PRES, payload); + + snprintf(payload, sizeof(payload), "%.2f", data->voltage); + mqttClient.publish(TOPIC_ENV_VOLT, payload); + + snprintf(payload, sizeof(payload), "%.2f", data->current); + mqttClient.publish(TOPIC_ENV_CURR, payload); + + snprintf(payload, sizeof(payload), "%.2f", data->power); + mqttClient.publish(TOPIC_ENV_PWR, payload); + + Serial.println(); +} + +class AdvDeviceCallbacks : public NimBLEScanCallbacks { + void onDiscovered(const NimBLEAdvertisedDevice* advertisedDevice) override { + String address = advertisedDevice->getAddress().toString().c_str(); + + if (address != TARGET_MAC) return; + if (!advertisedDevice->haveManufacturerData()) return; + + #ifdef BLE_DEBUG + Serial.print("Found EnvSensor: "); + Serial.println(address); + #endif + + std::string manufData = advertisedDevice->getManufacturerData(); + + #ifdef BLE_DEBUG + Serial.print("Manufacturer data length: "); + Serial.println(manufData.length()); + Serial.print("Hex dump: "); + for (size_t i = 0; i < manufData.length(); i++) { + if ((uint8_t)manufData[i] < 0x10) Serial.print("0"); + Serial.print((uint8_t)manufData[i], HEX); + Serial.print(" "); + } + Serial.println(); + #endif + + // Adv data length check + if (manufData.length() != 18) { + Serial.print("!! Wrong length (expected 18, got "); + Serial.print(manufData.length()); + Serial.println(") !!"); + return; + } + + // Use companyId for simple verification + uint16_t companyId = (uint8_t)manufData[0] | ((uint8_t)manufData[1] << 8); + if (companyId != COMPANY_ID) { + Serial.println("!! Wrong company ID !!"); + return; + } + + // Parse actual adv data + uint8_t* data = (uint8_t*)manufData.data() + 2; + EnvSensorData sensorData; + if (parseEnvSensorData(data, 16, &sensorData)) { + if (isDuplicateNonce(address, sensorData.nonce)) { + #ifdef BLE_DEBUG + Serial.println("-> Duplicate nonce\n"); + #endif + return; + } + addNonce(address, sensorData.nonce); + + if (mqttClient.connected()) { + publishEnvSensorData(&sensorData, address); + } else { + Serial.println("!! MQTT not connected !!"); + } + } else { + Serial.println("!! data parse failed !!"); + } + } +}; + +boolean reconnectMQTT() { + Serial.print("Connecting to MQTT..."); + if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD, + TOPIC_AVAILABILITY, 0, true, "offline")) { + Serial.println("connected!"); + mqttClient.publish(TOPIC_AVAILABILITY, "online", true); + return true; + } + Serial.print("failed, rc="); + Serial.println(mqttClient.state()); + return false; +} + +void publishSensorData() { + Serial.println(">>"); + uint8_t ret = am1008w_k_i2c.read_data_command(); + if (ret == 0) { + int co2 = am1008w_k_i2c.get_co2(); + int voc = am1008w_k_i2c.get_voc(); + float humidity = am1008w_k_i2c.get_humidity(); + float temperature = am1008w_k_i2c.get_temperature(); + int pm1 = am1008w_k_i2c.get_pm1p0(); + int pm25 = am1008w_k_i2c.get_pm2p5(); + int pm10 = am1008w_k_i2c.get_pm10(); + + #ifdef PRINT_SENSOR_DATA + Serial.printf("> CO2: %d ppm\n", co2); + Serial.printf("> VOC: %d (Lv 0-3)\n", voc); + Serial.printf("> Temp: %.1f°C\n", temperature); + Serial.printf("> Hum: %.1f%%\n", humidity); + Serial.printf("> PM1.0: %d µg/m³\n", pm1); + Serial.printf("> PM2.5: %d µg/m³\n", pm25); + Serial.printf("> PM10: %d µg/m³\n", pm10); + Serial.println(">>"); + #endif + + char payload[50]; + + snprintf(payload, sizeof(payload), "%d", co2); + mqttClient.publish(TOPIC_CO2, payload); + + snprintf(payload, sizeof(payload), "%d", voc); + mqttClient.publish(TOPIC_VOC, payload); + + snprintf(payload, sizeof(payload), "%.1f", temperature); + mqttClient.publish(TOPIC_TEMP, payload); + + snprintf(payload, sizeof(payload), "%.1f", humidity); + mqttClient.publish(TOPIC_HUMIDITY, payload); + + snprintf(payload, sizeof(payload), "%d", pm1); + mqttClient.publish(TOPIC_PM1, payload); + + snprintf(payload, sizeof(payload), "%d", pm25); + mqttClient.publish(TOPIC_PM25, payload); + + snprintf(payload, sizeof(payload), "%d", pm10); + mqttClient.publish(TOPIC_PM10, payload); + + } else { + Serial.println("!! Sensor fail !!"); + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("\n=== ESP32 Sensor Hub Starting ===\n"); + + Serial.println("Initializing I2C..."); + Wire.begin(1, 2); + am1008w_k_i2c.begin(Wire); + Serial.println("I2C Done.\n"); + + Serial.println("Initializing NimBLE..."); + NimBLEDevice::init(""); + NimBLEDevice::setPower(ESP_PWR_LVL_P9); + pBLEScan = NimBLEDevice::getScan(); + pBLEScan->setScanCallbacks(new AdvDeviceCallbacks()); + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); + pBLEScan->setDuplicateFilter(false); + pBLEScan->start(0, false, true); + Serial.println("Started BLE scanning.\n"); + + Serial.print("Connecting to WiFi"); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("connected!"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(); + + espClient.setInsecure(); + mqttClient.setServer(MQTT_SERVER, MQTT_PORT); + mqttClient.setKeepAlive(60); + mqttClient.setBufferSize(512); + + for (int i = 0; i < MAX_NONCE_HISTORY; i++) { + nonceHistory[i].address = ""; + nonceHistory[i].nonce = 0; + } + + esp_task_wdt_config_t wdt_config = { + .timeout_ms = 10000, + .idle_core_mask = 0, + .trigger_panic = true + }; + esp_task_wdt_init(&wdt_config); + esp_task_wdt_add(NULL); + + Serial.println("=== Init Complete ===\n"); +} + +void loop() { + esp_task_wdt_reset(); + + if (WiFi.status() != WL_CONNECTED) { + Serial.println("WiFi disconnected! Reconnecting..."); + WiFi.reconnect(); + delay(5000); + return; + } + + if (!mqttClient.connected()) { + if (millis() - lastReconnect > 5000) { + lastReconnect = millis(); + reconnectMQTT(); + } + } else { + mqttClient.loop(); + } + + if (mqttClient.connected() && (millis() - lastPublish > SENSOR_DELAY)) { + publishSensorData(); + lastPublish = millis(); + } + + if (!pBLEScan->isScanning()) { + Serial.println("Restarting BLE scanning...?"); + pBLEScan->start(0, false, true); + } + + delay(100); +} \ No newline at end of file diff --git a/ESP32_SENSOR_BLE_BEACON/ESP32_SENSOR_BLE_BEACON.ino b/ESP32_SENSOR_BLE_BEACON/ESP32_SENSOR_BLE_BEACON.ino new file mode 100644 index 0000000..7ec3af7 --- /dev/null +++ b/ESP32_SENSOR_BLE_BEACON/ESP32_SENSOR_BLE_BEACON.ino @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +Adafruit_BME680 bme; +Adafruit_INA219 ina219(0x41); + +#define ADVERTISE_MS 500 +#define SLEEP_TIME_MS 15000 +#define DEBUG + +void setup() { + #ifdef DEBUG + Serial.begin(115200); + delay(100); + #endif + + Wire.begin(); + + bme.begin(); + ina219.begin(); + + delay(100); + + NimBLEDevice::init("EnvSensor"); + NimBLEDevice::setPower(ESP_PWR_LVL_P9); + + uint16_t nonce = (uint16_t)esp_random(); + + float temp = bme.readTemperature(); + float hum = bme.readHumidity(); + float pres = bme.readPressure() / 100.0; + float busVoltage = ina219.getBusVoltage_V(); + + float total_current = 0; + for(int i = 0; i < 10; i++) { + total_current += ina219.getCurrent_mA(); + delay(5); + } + float wake_current = total_current / 10.0; + + #ifdef DEBUG + Serial.printf("Nonce: 0x%04X\n", nonce); + Serial.printf("T:%.1f H:%.1f P:%.0f V:%.2fV I:%.1fmA\n", + temp, hum, pres, busVoltage, wake_current); + #endif + + uint8_t payload[18]; + payload[0] = 0xFF; + payload[1] = 0xFF; + payload[2] = nonce & 0xFF; + payload[3] = (nonce >> 8) & 0xFF; + + int16_t temp_raw = (int16_t)(temp * 100); + payload[4] = temp_raw & 0xFF; + payload[5] = (temp_raw >> 8) & 0xFF; + + uint16_t hum_raw = (uint16_t)(hum * 100); + payload[6] = hum_raw & 0xFF; + payload[7] = (hum_raw >> 8) & 0xFF; + + uint32_t pres_raw = (uint32_t)(pres * 10); + payload[8] = pres_raw & 0xFF; + payload[9] = (pres_raw >> 8) & 0xFF; + payload[10] = (pres_raw >> 16) & 0xFF; + payload[11] = (pres_raw >> 24) & 0xFF; + + uint16_t voltage_raw = (uint16_t)(busVoltage * 100); + payload[12] = voltage_raw & 0xFF; + payload[13] = (voltage_raw >> 8) & 0xFF; + + int32_t current_raw = (int32_t)(wake_current * 100); + payload[14] = current_raw & 0xFF; + payload[15] = (current_raw >> 8) & 0xFF; + payload[16] = (current_raw >> 16) & 0xFF; + payload[17] = (current_raw >> 24) & 0xFF; + + #ifdef DEBUG + Serial.print("Payload: "); + for(int i=0; i<18; i++) { + Serial.printf("%02X ", payload[i]); + } + Serial.println(); + #endif + + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + std::string payloadStr((char*)payload, 18); + pAdvertising->setManufacturerData(payloadStr); + pAdvertising->start(); + + delay(ADVERTISE_MS); + pAdvertising->stop(); + + #ifdef DEBUG + Serial.printf("Sleep: %d ms\n", SLEEP_TIME_MS); + #endif + + delay(50); + esp_sleep_enable_timer_wakeup(SLEEP_TIME_MS * 1000ULL); + esp_deep_sleep_start(); +} + +void loop() {} \ No newline at end of file