This commit is contained in:
2025-11-17 22:54:37 +09:00
commit 1d70c03f12
3 changed files with 604 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <am1008w_k_i2c.h>
#include <Wire.h>
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);
}

View File

@@ -0,0 +1,355 @@
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <am1008w_k_i2c.h>
#include <Wire.h>
#include <NimBLEDevice.h>
#include <esp_task_wdt.h>
#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);
}

View File

@@ -0,0 +1,104 @@
#include <Wire.h>
#include <Adafruit_BME680.h>
#include <Adafruit_INA219.h>
#include <NimBLEDevice.h>
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() {}