Mqtt home assistant working

This commit is contained in:
koenieeee
2026-02-08 22:02:49 +01:00
parent b1e3ed27ab
commit e052a6b349
7 changed files with 934 additions and 37 deletions
+207
View File
@@ -0,0 +1,207 @@
# MQTT Troubleshooting Guide
## Changes Made
### 1. **Fixed Discovery Topic**
- Changed from `homeassistant/event/wand_spell/config` to `homeassistant/sensor/wand_spell/config`
- The `event` component type doesn't work properly for spell data
- Using `sensor` component allows state tracking in Home Assistant
### 2. **Added Comprehensive Debug Logging**
All MQTT operations now show detailed logs:
- **Connection**: Shows broker URI, username, network status
- **Publishing**: Shows topic, payload, QoS, msg_id
- **Published confirmation**: `MQTT_EVENT_PUBLISHED` confirms message was acknowledged by broker
- **Subscriptions**: Confirms when subscriptions are successful
- **Received data**: Shows any data received from broker
### 3. **Added Connectivity Test**
- ESP32 now subscribes to `wand/test` topic on connect
- This verifies bidirectional MQTT communication
- You can test by publishing to this topic from Home Assistant
## How to Test
### Step 1: Recompile and Flash
```bash
./build-s3.sh flash monitor
```
### Step 2: Watch Serial Output
After connecting to WiFi, you should see:
```
I (xxxxx) ha_mqtt: ✓ Connected to MQTT broker
I (xxxxx) ha_mqtt: 📤 Publishing discovery to: homeassistant/sensor/wand_spell/config
I (xxxxx) ha_mqtt: 📤 Discovery payload: {"name":"Magic Wand Spell",...}
I (xxxxx) ha_mqtt: Spell discovery msg_id: 1
I (xxxxx) ha_mqtt: 📤 Publishing discovery to: homeassistant/sensor/wand_battery/config
I (xxxxx) ha_mqtt: 📤 Discovery payload: {"name":"Wand Battery",...}
I (xxxxx) ha_mqtt: Battery discovery msg_id: 2
I (xxxxx) ha_mqtt: 📥 Subscribed to wand/test for connectivity verification [msg_id=3]
I (xxxxx) ha_mqtt: ✓ MQTT subscription successful [msg_id=3]
```
### Step 3: Test Bidirectional Communication
In Home Assistant, go to **Developer Tools****MQTT**:
1. **Publish a test message:**
- Topic: `wand/test`
- Payload: `hello`
- Click "PUBLISH"
2. **Check ESP32 serial output:**
```
I (xxxxx) ha_mqtt: 📥 MQTT data received on topic: wand/test
I (xxxxx) ha_mqtt: Payload: hello
```
If you see this, MQTT is working bidirectionally! ✓
### Step 4: Cast a Spell
Wave your wand and cast a spell. You should see:
```
I (xxxxx) main: 🎯 Spell detected in callback - processing...
I (xxxxx) main: → Checking MQTT connection (isConnected=1)
I (xxxxx) main: → Calling mqttClient.publishSpell()
I (xxxxx) ha_mqtt: publishSpell() called: spell_name='Incendio', confidence=0.994
I (xxxxx) ha_mqtt: Connection status: connected=1, mqtt_client=0x...
I (xxxxx) ha_mqtt: 📤 Publishing to topic 'wand/spell'
I (xxxxx) ha_mqtt: 📤 Payload: {"spell":"Incendio","confidence":0.994}
I (xxxxx) ha_mqtt: 📤 QoS: 1, Retain: false
I (xxxxx) ha_mqtt: ✓ Published spell: Incendio (99.4%) [msg_id=XXX]
I (xxxxx) ha_mqtt: ✓ MQTT message published successfully [msg_id=XXX]
```
The **✓ MQTT message published successfully** line is critical - it means the broker acknowledged receipt!
### Step 5: Listen in Home Assistant
In Home Assistant **Developer Tools** → **MQTT**:
- Topic: `wand/spell`
- Click "START LISTENING"
Cast a spell - you should immediately see:
```json
{"spell":"Incendio","confidence":0.994}
```
## Common Issues
### Issue 1: No "MQTT message published successfully" Event
**Symptom:** You see "Published spell" but no "✓ MQTT message published successfully"
**Causes:**
- QoS 1 requires broker acknowledgment
- Broker might not be acknowledging messages
- Check Home Assistant MQTT broker logs
**Solution:**
```bash
# In Home Assistant container or OS
docker logs homeassistant 2>&1 | grep -i mqtt
# or
journalctl -u hassio-supervisor -f | grep -i mqtt
```
### Issue 2: Discovery Not Working
**Symptom:** No "Magic Wand Gateway" device appears in Home Assistant
**Check:**
1. Home Assistant MQTT integration is enabled
2. Discovery is enabled in MQTT integration settings
3. ESP32 logs show discovery messages published (msg_id > 0)
**Manual verification:**
```bash
# Subscribe to discovery topics in HA Developer Tools → MQTT
Topic: homeassistant/sensor/wand_spell/config
```
You should see the discovery payload appear immediately after ESP32 connects.
### Issue 3: Messages Not Appearing in HA
**Symptom:** ESP32 shows "published successfully" but HA sees nothing
**Debug steps:**
1. **Check MQTT broker is running:**
```bash
# From Linux machine on same network
nc -zv 192.168.2.29 1883
```
2. **Use mosquitto_sub to listen:**
```bash
mosquitto_sub -h 192.168.2.29 -p 1883 -u magicwandesp32 -P wlqJQtAfLvcYK5 -t 'wand/#' -v
```
This bypasses Home Assistant and listens directly to broker.
3. **Check Home Assistant MQTT integration:**
- Settings → Devices & Services → MQTT
- Should show "Connected"
- Check broker address matches: `192.168.2.29`
4. **Check MQTT broker logs:**
If using Mosquitto add-on in HA:
- Settings → Add-ons → Mosquitto broker → Log tab
### Issue 4: Wrong Broker Configuration
**Check your NVS settings match:**
```
MQTT broker: mqtt://192.168.2.29:1883 (or just "192.168.2.29" - code adds mqtt:// prefix)
Username: magicwandesp32
Password: wlqJQtAfLvcYK5
```
Verify in ESP32 logs:
```
I (xxxxx) main: MQTT Configuration:
I (xxxxx) main: Broker: mqtt://192.168.2.29:1883
I (xxxxx) main: Username: magicwandesp32
```
## Expected Behavior
After these changes, you should see:
1. ✅ MQTT connects successfully
2. ✅ Discovery messages published (msg_id > 0)
3. ✅ Subscription to wand/test successful
4. ✅ Test message from HA appears in ESP32 logs
5. ✅ Spell published with positive msg_id
6. ✅ "MQTT message published successfully" confirmation
7. ✅ Spell appears in HA when listening to wand/spell
## Home Assistant Configuration
After successful connection, check for the auto-discovered entity:
**Settings** → **Devices & Services** → **MQTT** → Look for:
- Device: "Magic Wand Gateway"
- Entity: `sensor.magic_wand_spell`
- Entity: `sensor.wand_battery`
Create a simple automation to test:
```yaml
alias: Test Wand MQTT
trigger:
- platform: mqtt
topic: wand/spell
action:
- service: notify.persistent_notification
data:
title: "🪄 Wand Spell Detected!"
message: "{{ trigger.payload_json.spell }} ({{ (trigger.payload_json.confidence * 100) | round }}%)"
```
## Next Steps
If all tests pass but you still don't see data in Home Assistant:
1. Check Home Assistant logs for MQTT errors
2. Verify MQTT integration configuration in HA
3. Try restarting Home Assistant after first discovery message
4. Check if HA MQTT discovery is enabled:
```yaml
# In configuration.yaml
mqtt:
discovery: true
```
+154
View File
@@ -0,0 +1,154 @@
# Home Assistant Automation Examples for Magic Wand
# Add these to your configuration.yaml or create separate automation files
# Example 1: Lumos - Turn on lights
automation:
- alias: "Wand: Lumos - Lights On"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Lumos' }}"
- condition: template
value_template: "{{ trigger.payload_json.confidence > 0.7 }}"
action:
- service: light.turn_on
target:
entity_id: light.living_room # Change to your light entity
data:
brightness: 255
# Example 2: Nox - Turn off lights
- alias: "Wand: Nox - Lights Off"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Nox' }}"
- condition: template
value_template: "{{ trigger.payload_json.confidence > 0.7 }}"
action:
- service: light.turn_off
target:
entity_id: light.living_room
# Example 3: Incendio - Red lights
- alias: "Wand: Incendio - Red Fire"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Incendio' }}"
- condition: template
value_template: "{{ trigger.payload_json.confidence > 0.7 }}"
action:
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness: 255
rgb_color: [255, 0, 0] # Red
- service: notify.persistent_notification
data:
message: "🔥 Incendio cast with {{ (trigger.payload_json.confidence * 100) | round }}% confidence!"
# Example 4: Aguamenti - Blue water effect
- alias: "Wand: Aguamenti - Blue Water"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Aguamenti' }}"
action:
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness: 200
rgb_color: [0, 100, 255] # Blue
# Example 5: Alohomora - Unlock door
- alias: "Wand: Alohomora - Unlock"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Alohomora' }}"
action:
- service: lock.unlock
target:
entity_id: lock.front_door # Change to your lock entity
- service: notify.mobile_app
data:
message: "Door unlocked by magic wand!"
# Example 6: Protego - Activate security
- alias: "Wand: Protego - Arm Security"
trigger:
- platform: mqtt
topic: "wand/spell"
condition:
- condition: template
value_template: "{{ trigger.payload_json.spell == 'Protego' }}"
action:
- service: alarm_control_panel.alarm_arm_home
target:
entity_id: alarm_control_panel.home_alarm
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness: 100
rgb_color: [0, 0, 255] # Blue shield
# Example 7: Low battery notification
- alias: "Wand: Low Battery Alert"
trigger:
- platform: mqtt
topic: "wand/battery"
condition:
- condition: template
value_template: "{{ trigger.payload_json.level | int < 20 }}"
action:
- service: notify.persistent_notification
data:
title: "⚡ Wand Battery Low"
message: "Magic wand battery at {{ trigger.payload_json.level }}%"
# Sensor to track last spell (alternative to event entity)
sensor:
- platform: mqtt
name: "Wand Last Spell"
state_topic: "wand/spell"
value_template: "{{ value_json.spell }}"
json_attributes_topic: "wand/spell"
json_attributes_template: "{{ {'confidence': value_json.confidence | float | round(3)} | tojson }}"
- platform: mqtt
name: "Wand Battery Level"
state_topic: "wand/battery"
value_template: "{{ value_json.level }}"
unit_of_measurement: "%"
device_class: battery
# Script: All available spells list
script:
cast_random_spell:
alias: "Cast Random Spell Effect"
sequence:
- choose:
- conditions: "{{ states('sensor.wand_last_spell') == 'Lumos' }}"
sequence:
- service: light.turn_on
target:
area_id: living_room
- conditions: "{{ states('sensor.wand_last_spell') == 'Nox' }}"
sequence:
- service: light.turn_off
target:
area_id: living_room
+1
View File
@@ -163,6 +163,7 @@ public:
const char *getSKU() const { return sku; }
const char *getDeviceId() const { return device_id; }
const char *getWandType() const { return wand_type; }
const char *getWandMacAddress() const;
// Status
bool isStreaming() const { return imuStreaming; }
+13
View File
@@ -11,6 +11,8 @@ class HAMqttClient
private:
void *mqtt_client; // esp_mqtt_client_handle_t
bool connected;
char chip_id[16]; // Store chip ID for topic paths
void (*on_connected_callback)(); // Callback when MQTT connects
// MQTT event handler
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
@@ -31,8 +33,19 @@ public:
// Publish battery level to Home Assistant
bool publishBattery(uint8_t level);
// Publish wand information (firmware, serial, etc)
bool publishWandInfo(const char *firmware_version, const char *serial_number,
const char *sku, const char *device_id, const char *wand_type,
const char *wand_mac);
// Publish wand disconnected status
bool publishWandDisconnected();
// Check if connected to MQTT broker
bool isConnected() const { return connected; }
// Set callback for when MQTT connects
void onConnected(void (*callback)()) { on_connected_callback = callback; }
};
#endif // HA_MQTT_H
+10 -1
View File
@@ -1017,7 +1017,7 @@ void WandBLEClient::updateAHRS(const IMUSample &sample)
dy = usbHID.getGamepadInvertY() ? -dy : dy;
}
#else
dy = -dy; // Default: inverted
dy = -dy; // Default: inverted
#endif
accum_dx += dx;
accum_dy += dy;
@@ -1137,6 +1137,15 @@ bool WandBLEClient::requestWandInfo()
return success;
}
const char *WandBLEClient::getWandMacAddress() const
{
static char mac_str[18]; // "XX:XX:XX:XX:XX:XX" + null terminator
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X",
peer_addr.val[5], peer_addr.val[4], peer_addr.val[3],
peer_addr.val[2], peer_addr.val[1], peer_addr.val[0]);
return mac_str;
}
void WandBLEClient::processFirmwareVersion(const uint8_t *data, size_t length)
{
// Response format: [opcode][version_string...]
+423 -36
View File
@@ -2,13 +2,18 @@
#include "config.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "esp_netif.h"
#include "esp_idf_version.h"
#include "esp_mac.h"
#include <string.h>
#include <stdio.h>
#define FIRMWARE_VERSION "1.0.0"
static const char *TAG = "ha_mqtt";
HAMqttClient::HAMqttClient()
: mqtt_client(nullptr), connected(false)
: mqtt_client(nullptr), connected(false), on_connected_callback(nullptr)
{
}
@@ -30,37 +35,259 @@ void HAMqttClient::mqtt_event_handler(void *handler_args, esp_event_base_t base,
// Publish Home Assistant MQTT discovery configuration
{
// Discovery topic: homeassistant/event/wand/config
const char *discovery_topic = "homeassistant/event/wand_spell/config";
char config_json[512];
snprintf(config_json, sizeof(config_json),
"{\"name\":\"Magic Wand Spell\","
"\"state_topic\":\"wand/spell\","
"\"value_template\":\"{{ value_json.spell }}\","
"\"json_attributes_topic\":\"wand/spell\","
"\"device\":{\"identifiers\":[\"esp32_wand\"],"
"\"name\":\"Magic Wand Gateway\","
"\"manufacturer\":\"DIY\","
"\"model\":\"ESP32 Wand Gateway\"}}");
// Get unique chip ID for this ESP32
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char chip_id[16];
snprintf(chip_id, sizeof(chip_id), "%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, config_json, 0, 1, true);
// Store chip_id in class for later use
strncpy(client->chip_id, chip_id, sizeof(client->chip_id) - 1);
client->chip_id[sizeof(client->chip_id) - 1] = '\0';
// Get ESP32 IP address
char ip_str[16] = "unknown";
esp_netif_ip_info_t ip_info;
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK)
{
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
}
// Build ESP-IDF version string
char idf_version[32];
snprintf(idf_version, sizeof(idf_version), "v%d.%d.%d",
ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
ESP_LOGI(TAG, "Device Chip ID: %s", chip_id);
ESP_LOGI(TAG, "Device IP: %s", ip_str);
ESP_LOGI(TAG, "Firmware: %s (ESP-IDF %s)", FIRMWARE_VERSION, idf_version);
// Reusable buffer for all discovery payloads (reduce stack usage)
char *discovery_buffer = (char *)malloc(1200);
if (!discovery_buffer)
{
ESP_LOGE(TAG, "Failed to allocate memory for discovery messages");
break;
}
// Common device info JSON block (used by all sensors)
char device_info[400];
snprintf(device_info, sizeof(device_info),
"\"device\":{"
"\"identifiers\":[\"wand_%s\"],"
"\"name\":\"Wand Gateway %s\","
"\"manufacturer\":\"DIY\","
"\"model\":\"ESP32-S3\","
"\"sw_version\":\"%s\","
"\"hw_version\":\"ESP-IDF %s\","
"\"configuration_url\":\"http://%s\","
"\"connections\":[[\"mac\",\"%s\"]]"
"}",
chip_id, chip_id, FIRMWARE_VERSION, idf_version, ip_str, chip_id);
// Spell sensor discovery - tracks last detected spell
char discovery_topic[128];
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_spell/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Last Spell Cast\","
"\"unique_id\":\"wand_%s_spell\","
"\"object_id\":\"wand_%s_spell\","
"\"state_topic\":\"wand/%s/spell\","
"\"value_template\":\"{{ value_json.spell }}\","
"\"json_attributes_topic\":\"wand/%s/spell\","
"\"icon\":\"mdi:magic-staff\","
"%s"
"}",
chip_id, chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
ESP_LOGD(TAG, "📤 Discovery payload: %s", discovery_buffer);
int msg_id1 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Spell discovery msg_id: %d", msg_id1);
// Battery sensor discovery
const char *battery_discovery = "homeassistant/sensor/wand_battery/config";
char battery_json[512];
snprintf(battery_json, sizeof(battery_json),
"{\"name\":\"Wand Battery\","
"\"state_topic\":\"wand/battery\","
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_battery/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Wand Battery\","
"\"unique_id\":\"wand_%s_battery\","
"\"object_id\":\"wand_%s_battery\","
"\"state_topic\":\"wand/%s/battery\","
"\"unit_of_measurement\":\"%%\","
"\"device_class\":\"battery\","
"\"state_class\":\"measurement\","
"\"value_template\":\"{{ value_json.level }}\","
"\"device\":{\"identifiers\":[\"esp32_wand\"]}}");
"\"icon\":\"mdi:battery\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
battery_discovery, battery_json, 0, 1, true);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
ESP_LOGD(TAG, "📤 Discovery payload: %s", discovery_buffer);
int msg_id2 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Battery discovery msg_id: %d", msg_id2);
ESP_LOGI(TAG, "Published Home Assistant discovery config");
// Spell confidence sensor discovery
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_confidence/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Spell Confidence\","
"\"unique_id\":\"wand_%s_confidence\","
"\"object_id\":\"wand_%s_confidence\","
"\"state_topic\":\"wand/%s/spell\","
"\"unit_of_measurement\":\"%%\","
"\"value_template\":\"{{ (value_json.confidence * 100) | round(1) }}\","
"\"icon\":\"mdi:gauge\","
"\"state_class\":\"measurement\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
ESP_LOGD(TAG, "📤 Discovery payload: %s", discovery_buffer);
int msg_id3 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Confidence discovery msg_id: %d", msg_id3);
// Wand connection status sensor
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/binary_sensor/wand_%s_connected/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Wand Connected\","
"\"unique_id\":\"wand_%s_connected\","
"\"object_id\":\"wand_%s_connected\","
"\"state_topic\":\"wand/%s/info\","
"\"value_template\":\"{{ value_json.connected }}\","
"\"payload_on\":\"True\","
"\"payload_off\":\"False\","
"\"device_class\":\"connectivity\","
"\"icon\":\"mdi:magic-staff\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
int msg_id4 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Wand status discovery msg_id: %d", msg_id4);
// Wand firmware version sensor
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_firmware/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Wand Firmware\","
"\"unique_id\":\"wand_%s_firmware\","
"\"object_id\":\"wand_%s_firmware\","
"\"state_topic\":\"wand/%s/info\","
"\"value_template\":\"{{ value_json.firmware }}\","
"\"icon\":\"mdi:chip\","
"\"entity_category\":\"diagnostic\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
int msg_id5 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Wand firmware discovery msg_id: %d", msg_id5);
// Wand serial number sensor
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_serial/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Wand Serial Number\","
"\"unique_id\":\"wand_%s_serial\","
"\"object_id\":\"wand_%s_serial\","
"\"state_topic\":\"wand/%s/info\","
"\"value_template\":\"{{ value_json.serial }}\","
"\"icon\":\"mdi:identifier\","
"\"entity_category\":\"diagnostic\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
int msg_id6 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Wand serial discovery msg_id: %d", msg_id6);
// Wand MAC address sensor
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/sensor/wand_%s_mac/config", chip_id);
snprintf(discovery_buffer, 1200,
"{"
"\"name\":\"Wand MAC Address\","
"\"unique_id\":\"wand_%s_mac\","
"\"object_id\":\"wand_%s_mac\","
"\"state_topic\":\"wand/%s/info\","
"\"value_template\":\"{{ value_json.wand_mac }}\","
"\"icon\":\"mdi:bluetooth\","
"\"entity_category\":\"diagnostic\","
"%s"
"}",
chip_id, chip_id, chip_id, device_info);
ESP_LOGI(TAG, "📤 Publishing discovery to: %s", discovery_topic);
int msg_id7 = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
discovery_topic, discovery_buffer, 0, 1, true);
ESP_LOGI(TAG, " Wand MAC discovery msg_id: %d", msg_id7);
// Free the discovery buffer
free(discovery_buffer);
ESP_LOGI(TAG, "✓ Published Home Assistant discovery config (7 sensors + 1 binary_sensor)");
// Publish initial state for wand info (no wand connected yet)
char wand_info_topic[64];
snprintf(wand_info_topic, sizeof(wand_info_topic), "wand/%s/info", chip_id);
const char *initial_wand_info = "{\"firmware\":\"unknown\",\"serial\":\"unknown\",\"sku\":\"unknown\",\"device_id\":\"unknown\",\"wand_type\":\"unknown\",\"wand_mac\":\"unknown\",\"connected\":false}";
int init_msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
wand_info_topic,
initial_wand_info,
strlen(initial_wand_info),
1,
true); // retain=true
ESP_LOGI(TAG, "📤 Published initial wand state (no wand connected) [msg_id=%d]", init_msg_id);
// Publish initial battery state (unavailable until wand connects)
char battery_topic[64];
snprintf(battery_topic, sizeof(battery_topic), "wand/%s/battery", chip_id);
const char *initial_battery = "{\"level\":0}";
int battery_msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
battery_topic,
initial_battery,
strlen(initial_battery),
1,
true); // retain=true
ESP_LOGI(TAG, "📤 Published initial battery state [msg_id=%d]", battery_msg_id);
// Publish initial spell state (no spell detected yet)
char spell_topic[64];
snprintf(spell_topic, sizeof(spell_topic), "wand/%s/spell", chip_id);
const char *initial_spell = "{\"spell\":\"No spell yet\",\"confidence\":0.0}";
int spell_msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)client->mqtt_client,
spell_topic,
initial_spell,
strlen(initial_spell),
1,
true); // retain=true
ESP_LOGI(TAG, "📤 Published initial spell state [msg_id=%d]", spell_msg_id);
// Subscribe to a test topic to verify bidirectional MQTT connectivity
int sub_id = esp_mqtt_client_subscribe((esp_mqtt_client_handle_t)client->mqtt_client,
"wand/test", 0);
ESP_LOGI(TAG, "📥 Subscribed to wand/test for connectivity verification [msg_id=%d]", sub_id);
// Call onConnected callback if registered
if (client->on_connected_callback)
{
ESP_LOGI(TAG, "Calling MQTT connected callback...");
client->on_connected_callback();
}
}
break;
@@ -69,6 +296,19 @@ void HAMqttClient::mqtt_event_handler(void *handler_args, esp_event_base_t base,
client->connected = false;
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "✓ MQTT message published successfully [msg_id=%d]", event->msg_id);
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "✓ MQTT subscription successful [msg_id=%d]", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "📥 MQTT data received on topic: %.*s", event->topic_len, event->topic);
ESP_LOGI(TAG, " Payload: %.*s", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "MQTT error occurred");
if (event->error_handle && event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
@@ -80,6 +320,7 @@ void HAMqttClient::mqtt_event_handler(void *handler_args, esp_event_base_t base,
break;
default:
ESP_LOGD(TAG, "MQTT event: %d", event_id);
break;
}
}
@@ -141,8 +382,26 @@ void HAMqttClient::stop()
bool HAMqttClient::publishSpell(const char *spell_name, float confidence)
{
if (!connected || !mqtt_client || !spell_name)
ESP_LOGI(TAG, "publishSpell() called: spell_name='%s', confidence=%.3f",
spell_name ? spell_name : "(null)", confidence);
ESP_LOGI(TAG, " Connection status: connected=%d, mqtt_client=%p",
connected, mqtt_client);
if (!connected)
{
ESP_LOGW(TAG, " ❌ Cannot publish: Not connected to MQTT broker");
return false;
}
if (!mqtt_client)
{
ESP_LOGW(TAG, " ❌ Cannot publish: MQTT client is NULL");
return false;
}
if (!spell_name)
{
ESP_LOGW(TAG, " ❌ Cannot publish: spell_name is NULL");
return false;
}
@@ -152,38 +411,63 @@ bool HAMqttClient::publishSpell(const char *spell_name, float confidence)
"{\"spell\":\"%s\",\"confidence\":%.3f}",
spell_name, confidence);
char topic[64];
snprintf(topic, sizeof(topic), "wand/%s/spell", chip_id);
ESP_LOGI(TAG, " 📤 Publishing to topic '%s'", topic);
ESP_LOGI(TAG, " 📤 Payload: %s", json);
ESP_LOGI(TAG, " 📤 QoS: 1, Retain: false");
int msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)mqtt_client,
MQTT_TOPIC_SPELL,
topic,
json,
0, // length (0 = use strlen)
1, // QoS 1
false); // retain
strlen(json), // Explicit length instead of 0
1, // QoS 1
false); // retain
if (msg_id >= 0)
{
ESP_LOGI(TAG, "Published spell: %s (%.1f%%) [msg_id=%d]",
ESP_LOGI(TAG, "Published spell: %s (%.1f%%) [msg_id=%d]",
spell_name, confidence * 100.0f, msg_id);
return true;
}
else
{
ESP_LOGW(TAG, "Failed to publish spell");
ESP_LOGE(TAG, "Failed to publish spell [msg_id=%d]", msg_id);
return false;
}
}
bool HAMqttClient::publishBattery(uint8_t level)
{
if (!connected || !mqtt_client)
ESP_LOGI(TAG, "publishBattery() called: level=%d%%", level);
ESP_LOGI(TAG, " Connection status: connected=%d, mqtt_client=%p",
connected, mqtt_client);
if (!connected)
{
ESP_LOGW(TAG, " ❌ Cannot publish: Not connected to MQTT broker");
return false;
}
if (!mqtt_client)
{
ESP_LOGW(TAG, " ❌ Cannot publish: MQTT client is NULL");
return false;
}
char json[64];
snprintf(json, sizeof(json), "{\"level\":%d}", level);
char topic[64];
snprintf(topic, sizeof(topic), "wand/%s/battery", chip_id);
ESP_LOGI(TAG, " 📤 Publishing to topic '%s'", topic);
ESP_LOGI(TAG, " 📤 Payload: %s", json);
ESP_LOGI(TAG, " 📤 QoS: 1, Retain: false");
int msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)mqtt_client,
"wand/battery",
topic,
json,
0,
1,
@@ -191,9 +475,112 @@ bool HAMqttClient::publishBattery(uint8_t level)
if (msg_id >= 0)
{
ESP_LOGD(TAG, "Published battery: %d%% [msg_id=%d]", level, msg_id);
ESP_LOGI(TAG, "Published battery: %d%% [msg_id=%d]", level, msg_id);
return true;
}
return false;
else
{
ESP_LOGE(TAG, " ❌ Failed to publish battery [msg_id=%d]", msg_id);
return false;
}
}
bool HAMqttClient::publishWandInfo(const char *firmware_version, const char *serial_number,
const char *sku, const char *device_id, const char *wand_type,
const char *wand_mac)
{
ESP_LOGI(TAG, "publishWandInfo() called");
ESP_LOGI(TAG, " Connection status: connected=%d, mqtt_client=%p",
connected, mqtt_client);
if (!connected || !mqtt_client)
{
ESP_LOGW(TAG, " ❌ Cannot publish: Not connected to MQTT broker");
return false;
}
// Build JSON with wand information
// Check for empty strings, not just NULL
const char *fw = (firmware_version && firmware_version[0]) ? firmware_version : "unknown";
const char *sn = (serial_number && serial_number[0]) ? serial_number : "unknown";
const char *sk = (sku && sku[0]) ? sku : "unknown";
const char *did = (device_id && device_id[0]) ? device_id : "unknown";
const char *wt = (wand_type && wand_type[0]) ? wand_type : "unknown";
const char *mac = (wand_mac && wand_mac[0]) ? wand_mac : "unknown";
char json[512];
snprintf(json, sizeof(json),
"{"
"\"firmware\":\"%s\","
"\"serial\":\"%s\","
"\"sku\":\"%s\","
"\"device_id\":\"%s\","
"\"wand_type\":\"%s\","
"\"wand_mac\":\"%s\","
"\"connected\":true"
"}",
fw, sn, sk, did, wt, mac);
char topic[64];
snprintf(topic, sizeof(topic), "wand/%s/info", chip_id);
ESP_LOGI(TAG, " 📤 Publishing to topic '%s'", topic);
ESP_LOGI(TAG, " 📤 Wand FW: %s, Serial: %s, Type: %s, MAC: %s",
fw, sn, wt, mac);
ESP_LOGI(TAG, " 📤 Payload: %s", json);
int msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)mqtt_client,
topic,
json,
strlen(json),
1,
true); // retain=true so HA always has latest
if (msg_id >= 0)
{
ESP_LOGI(TAG, " ✓ Published wand info [msg_id=%d]", msg_id);
return true;
}
else
{
ESP_LOGE(TAG, " ❌ Failed to publish wand info [msg_id=%d]", msg_id);
return false;
}
}
bool HAMqttClient::publishWandDisconnected()
{
ESP_LOGI(TAG, "publishWandDisconnected() called");
if (!connected || !mqtt_client)
{
ESP_LOGW(TAG, " ⚠ Not connected to MQTT broker (skipping)");
return false;
}
// Publish empty wand info with connected=false
const char *json = "{\"firmware\":\"unknown\",\"serial\":\"unknown\",\"sku\":\"unknown\",\"device_id\":\"unknown\",\"wand_type\":\"unknown\",\"wand_mac\":\"unknown\",\"connected\":false}";
char topic[64];
snprintf(topic, sizeof(topic), "wand/%s/info", chip_id);
ESP_LOGI(TAG, " 📤 Publishing disconnection to topic '%s'", topic);
int msg_id = esp_mqtt_client_publish((esp_mqtt_client_handle_t)mqtt_client,
topic,
json,
strlen(json),
1,
true); // retain=true
if (msg_id >= 0)
{
ESP_LOGI(TAG, " ✓ Published wand disconnection [msg_id=%d]", msg_id);
return true;
}
else
{
ESP_LOGE(TAG, " ❌ Failed to publish wand disconnection [msg_id=%d]", msg_id);
return false;
}
}
+126
View File
@@ -189,29 +189,100 @@ void onSpellDetected(const char *spell_name, float confidence)
#endif
#if ENABLE_HOME_ASSISTANT
ESP_LOGI(TAG, "🎯 Spell detected in callback - processing...");
// Broadcast to web clients
ESP_LOGI(TAG, " → Broadcasting to web clients");
webServer.broadcastSpell(spell_name, confidence);
// Send to Home Assistant via MQTT (only if connected)
ESP_LOGI(TAG, " → Checking MQTT connection (isConnected=%d)", mqttClient.isConnected());
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, " → Calling mqttClient.publishSpell()");
mqttClient.publishSpell(spell_name, confidence);
}
else
{
ESP_LOGW(TAG, " ⚠ MQTT not connected - skipping MQTT publish");
}
#endif
}
// Callback when MQTT connects - check if wand is already connected and publish its info
void onMQTTConnected()
{
ESP_LOGI(TAG, "MQTT connected callback triggered");
// Check if wand is already connected
if (wandClient.isConnected())
{
ESP_LOGI(TAG, "Wand already connected - publishing info to Home Assistant...");
// Request fresh wand info
if (wandClient.requestWandInfo())
{
vTaskDelay(pdMS_TO_TICKS(300));
mqttClient.publishWandInfo(
wandClient.getFirmwareVersion(),
wandClient.getSerialNumber(),
wandClient.getSKU(),
wandClient.getDeviceId(),
wandClient.getWandType(),
wandClient.getWandMacAddress());
}
}
else
{
ESP_LOGI(TAG, "No wand connected yet");
}
}
// Callback when connection state changes
void onConnectionChange(bool connected)
{
if (connected)
{
ESP_LOGI(TAG, "✓ Connected to wand");
// Request wand information (firmware, serial, etc.)
if (wandClient.requestWandInfo())
{
vTaskDelay(pdMS_TO_TICKS(500)); // Give time for info to be retrieved
// Publish wand info to Home Assistant
#if ENABLE_HOME_ASSISTANT
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, "Publishing wand information to Home Assistant...");
mqttClient.publishWandInfo(
wandClient.getFirmwareVersion(),
wandClient.getSerialNumber(),
wandClient.getSKU(),
wandClient.getDeviceId(),
wandClient.getWandType(),
wandClient.getWandMacAddress());
}
#endif
}
// Notify web GUI
webServer.broadcastWandStatus(true);
}
else
{
ESP_LOGI(TAG, "✗ Disconnected from wand");
// Publish disconnected status to Home Assistant
#if ENABLE_HOME_ASSISTANT
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, "Publishing wand disconnection to Home Assistant...");
mqttClient.publishWandDisconnected();
}
#endif
// Check if this was a user-initiated disconnect
if (wandClient.isUserDisconnectRequested())
{
@@ -852,6 +923,9 @@ extern "C" void app_main()
{
ESP_LOGI(TAG, "✓ MQTT client initialized for Home Assistant");
ESP_LOGI(TAG, " Connection errors will retry every 30 seconds");
// Register callback to publish wand info if already connected
mqttClient.onConnected(onMQTTConnected);
}
else
{
@@ -1017,6 +1091,30 @@ extern "C" void app_main()
{
ESP_LOGW(TAG, "WARNING: Failed to request wand information");
}
else
{
// Wait for info to be retrieved
vTaskDelay(300 / portTICK_PERIOD_MS);
// Publish wand info to Home Assistant
#if ENABLE_HOME_ASSISTANT
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, "Publishing wand info to Home Assistant...");
mqttClient.publishWandInfo(
wandClient.getFirmwareVersion(),
wandClient.getSerialNumber(),
wandClient.getSKU(),
wandClient.getDeviceId(),
wandClient.getWandType(),
wandClient.getWandMacAddress());
}
else
{
ESP_LOGW(TAG, "MQTT not connected - wand info not published");
}
#endif
}
// Wait before starting IMU streaming
vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -1145,6 +1243,26 @@ extern "C" void app_main()
{
ESP_LOGW(TAG, "WARNING: Failed to request wand information");
}
else
{
// Wait for info to be retrieved
vTaskDelay(300 / portTICK_PERIOD_MS);
// Publish wand info to Home Assistant
#if ENABLE_HOME_ASSISTANT
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, "Publishing wand info to Home Assistant...");
mqttClient.publishWandInfo(
wandClient.getFirmwareVersion(),
wandClient.getSerialNumber(),
wandClient.getSKU(),
wandClient.getDeviceId(),
wandClient.getWandType(),
wandClient.getWandMacAddress());
}
#endif
}
// Wait before starting IMU streaming
vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -1219,16 +1337,24 @@ extern "C" void app_main()
if (battery_check_counter >= BATTERY_CHECK_INTERVAL)
{
uint8_t battery = wandClient.getBatteryLevel();
ESP_LOGI(TAG, "🔋 Battery check: level=%d%%", battery);
if (battery > 0)
{
#if ENABLE_HOME_ASSISTANT
ESP_LOGI(TAG, " → Broadcasting battery to web clients");
webServer.broadcastBattery(battery);
// Publish to Home Assistant (only if connected)
ESP_LOGI(TAG, " → Checking MQTT connection (isConnected=%d)", mqttClient.isConnected());
if (mqttClient.isConnected())
{
ESP_LOGI(TAG, " → Calling mqttClient.publishBattery()");
mqttClient.publishBattery(battery);
}
else
{
ESP_LOGW(TAG, " ⚠ MQTT not connected - skipping battery publish");
}
#endif
}
battery_check_counter = 0;