JSON format
This commit is contained in:
parent
7c6d854e01
commit
5c5311f930
4 changed files with 112 additions and 74 deletions
|
|
@ -272,7 +272,9 @@
|
||||||
"subscribe_topics": [
|
"subscribe_topics": [
|
||||||
"picoclaw/input"
|
"picoclaw/input"
|
||||||
],
|
],
|
||||||
|
"subscribe_json_key": null,
|
||||||
"reply_topic": "picoclaw/output",
|
"reply_topic": "picoclaw/output",
|
||||||
|
"reply_json_key": null,
|
||||||
"tls": false,
|
"tls": false,
|
||||||
"tls_ca": "",
|
"tls_ca": "",
|
||||||
"tls_cert": "",
|
"tls_cert": "",
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,12 @@ Add this to `config.json`:
|
||||||
"qos": 1,
|
"qos": 1,
|
||||||
"retain": false,
|
"retain": false,
|
||||||
"tls": false,
|
"tls": false,
|
||||||
"subscribe_topics": ["picoclaw/chat"],
|
"subscribe_topics": [
|
||||||
"reply_topic": "picoclaw/reply",
|
"picoclaw/input"
|
||||||
|
],
|
||||||
|
"subscribe_json_key": null,
|
||||||
|
"reply_topic": "picoclaw/output",
|
||||||
|
"reply_json_key": null,
|
||||||
"allow_from": [],
|
"allow_from": [],
|
||||||
"group_trigger": {
|
"group_trigger": {
|
||||||
"mention_only": true
|
"mention_only": true
|
||||||
|
|
@ -42,7 +46,9 @@ Add this to `config.json`:
|
||||||
| retain | bool | No | Whether to retain messages. Default: false |
|
| retain | bool | No | Whether to retain messages. Default: false |
|
||||||
| tls | bool | No | Enable TLS/SSL connection. Default: false |
|
| tls | bool | No | Enable TLS/SSL connection. Default: false |
|
||||||
| subscribe_topics | []string | Yes | List of MQTT topics to subscribe to for incoming messages |
|
| subscribe_topics | []string | Yes | List of MQTT topics to subscribe to for incoming messages |
|
||||||
|
| subscribe_json_key | string | No | JSON key to extract from incoming messages. If null, treats message as plain text |
|
||||||
| reply_topic | string | No | Topic to publish replies to. Supports placeholders: `{client_id}`, `{topic}` |
|
| reply_topic | string | No | Topic to publish replies to. Supports placeholders: `{client_id}`, `{topic}` |
|
||||||
|
| reply_json_key | string | No | JSON key to use when sending replies. If null, sends as plain text |
|
||||||
| allow_from | []string | No | Client ID whitelist (empty allows all) |
|
| allow_from | []string | No | Client ID whitelist (empty allows all) |
|
||||||
| group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) |
|
| group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) |
|
||||||
| reasoning_channel_id | string | No | Target channel for reasoning output |
|
| reasoning_channel_id | string | No | Target channel for reasoning output |
|
||||||
|
|
@ -54,6 +60,8 @@ Add this to `config.json`:
|
||||||
- JSON format: `{"status": "your message"}`
|
- JSON format: `{"status": "your message"}`
|
||||||
- Plain text: Direct text content
|
- Plain text: Direct text content
|
||||||
- Automatic JSON parsing with fallback to plain text for malformed JSON
|
- Automatic JSON parsing with fallback to plain text for malformed JSON
|
||||||
|
- **JSON Key Extraction**: When `subscribe_json_key` is set, extracts specific field from JSON messages
|
||||||
|
- **JSON Response Formatting**: When `reply_json_key` is set, sends responses as JSON with specified key
|
||||||
- **Authentication**: Username/password authentication support
|
- **Authentication**: Username/password authentication support
|
||||||
- **TLS/SSL**: Secure connections with TLS configuration
|
- **TLS/SSL**: Secure connections with TLS configuration
|
||||||
- **Quality of Service**: Configurable QoS levels (0, 1, 2)
|
- **Quality of Service**: Configurable QoS levels (0, 1, 2)
|
||||||
|
|
@ -67,6 +75,7 @@ Add this to `config.json`:
|
||||||
|
|
||||||
- **Robust Message Handling**: Intelligent parsing that handles malformed JSON gracefully
|
- **Robust Message Handling**: Intelligent parsing that handles malformed JSON gracefully
|
||||||
- **Flexible Topic Configuration**: Support for multiple input topics and dynamic reply topics
|
- **Flexible Topic Configuration**: Support for multiple input topics and dynamic reply topics
|
||||||
|
- **JSON Message Processing**: Configurable JSON key extraction and response formatting
|
||||||
- **Connection Resilience**: Automatic reconnection with configurable retry intervals
|
- **Connection Resilience**: Automatic reconnection with configurable retry intervals
|
||||||
- **Security**: TLS support and authentication for secure communication
|
- **Security**: TLS support and authentication for secure communication
|
||||||
- **Message Routing**: Support for reasoning channel routing and group trigger rules
|
- **Message Routing**: Support for reasoning channel routing and group trigger rules
|
||||||
|
|
@ -77,4 +86,57 @@ Add this to `config.json`:
|
||||||
- Reply topics can use placeholders to dynamically route responses
|
- Reply topics can use placeholders to dynamically route responses
|
||||||
- Client IDs are used as sender identifiers in the messaging system
|
- Client IDs are used as sender identifiers in the messaging system
|
||||||
- Topics are treated as channels for message routing purposes
|
- Topics are treated as channels for message routing purposes
|
||||||
- The instruction field allows adding context or commands to all incoming messages
|
- The instruction field allows adding context or commands to all incoming messages
|
||||||
|
|
||||||
|
## 6. JSON Configuration Examples
|
||||||
|
|
||||||
|
### Plain Text Mode (Default)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"subscribe_json_key": null,
|
||||||
|
"reply_json_key": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Incoming messages are treated as plain text
|
||||||
|
- Outgoing messages are sent as plain text
|
||||||
|
|
||||||
|
### JSON Input Mode
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"subscribe_json_key": "message",
|
||||||
|
"reply_json_key": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Incoming JSON: `{"message": "Hello world", "timestamp": 1234567890}`
|
||||||
|
- Extracted content: `"Hello world"`
|
||||||
|
- Outgoing messages are sent as plain text
|
||||||
|
|
||||||
|
### JSON Output Mode
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"subscribe_json_key": null,
|
||||||
|
"reply_json_key": "response"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Incoming messages are treated as plain text
|
||||||
|
- Outgoing JSON: `{"response": "Bot reply"}`
|
||||||
|
|
||||||
|
### Full JSON Mode
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"subscribe_json_key": "input",
|
||||||
|
"reply_json_key": "output"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Incoming JSON: `{"input": "What's the weather?", "location": "Moscow"}`
|
||||||
|
- Extracted content: `"What's the weather?"`
|
||||||
|
- Outgoing JSON: `{"output": "The weather is sunny"}`
|
||||||
|
|
||||||
|
## 7. Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **JSON parsing fails**: Ensure your JSON messages are valid
|
||||||
|
2. **Key not found**: Verify the JSON key exists in your messages
|
||||||
|
3. **Connection issues**: Check broker URL, credentials, and TLS settings
|
||||||
|
4. **Permission denied**: Verify client ID is in the `allow_from` list if configured
|
||||||
|
|
|
||||||
|
|
@ -154,77 +154,40 @@ func (c *MQTTChannel) onMessage(client mqtt.Client, msg mqtt.Message) {
|
||||||
"payload": string(msg.Payload()),
|
"payload": string(msg.Payload()),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Try to parse as JSON first
|
|
||||||
var mqttMsg MQTTMessage
|
|
||||||
var content string
|
var content string
|
||||||
var err error
|
|
||||||
|
|
||||||
// First try to parse as JSON
|
// Check if subscribe_json_key is configured
|
||||||
if err = json.Unmarshal(msg.Payload(), &mqttMsg); err == nil {
|
if c.config.SubscribeJSONKey != nil && *c.config.SubscribeJSONKey != "" {
|
||||||
// Successfully parsed as JSON
|
// Parse as JSON and extract the specified key
|
||||||
content = mqttMsg.Status
|
var jsonMsg map[string]interface{}
|
||||||
} else {
|
if err := json.Unmarshal(msg.Payload(), &jsonMsg); err == nil {
|
||||||
// If JSON parsing fails, try to clean up common malformed JSON issues
|
// Successfully parsed as JSON
|
||||||
payloadStr := string(msg.Payload())
|
if value, exists := jsonMsg[*c.config.SubscribeJSONKey]; exists {
|
||||||
|
content = fmt.Sprintf("%v", value)
|
||||||
// Try to extract JSON from malformed strings (e.g., extra quotes or braces)
|
logger.InfoCF("mqtt", "Extracted JSON value", map[string]any{
|
||||||
// Look for a valid JSON object within the string
|
"key": *c.config.SubscribeJSONKey,
|
||||||
if strings.HasPrefix(payloadStr, "{") && (strings.HasSuffix(payloadStr, "}") || strings.HasSuffix(payloadStr, "}\"")) {
|
"content": content,
|
||||||
// Try to parse as-is first
|
})
|
||||||
if err2 := json.Unmarshal(msg.Payload(), &mqttMsg); err2 == nil {
|
|
||||||
content = mqttMsg.Status
|
|
||||||
} else {
|
} else {
|
||||||
// Try to clean up common issues
|
logger.WarnCF("mqtt", "JSON key not found in message", map[string]any{
|
||||||
cleaned := strings.TrimSpace(payloadStr)
|
"key": *c.config.SubscribeJSONKey,
|
||||||
|
})
|
||||||
// Remove extra quotes and braces from the end
|
content = string(msg.Payload()) // Fall back to raw payload
|
||||||
for strings.HasSuffix(cleaned, "}") && strings.Count(cleaned, "{") < strings.Count(cleaned, "}") {
|
|
||||||
cleaned = cleaned[:len(cleaned)-1]
|
|
||||||
}
|
|
||||||
for strings.HasSuffix(cleaned, "\"}") && strings.Count(cleaned, "{") < strings.Count(cleaned, "}") {
|
|
||||||
cleaned = cleaned[:len(cleaned)-1]
|
|
||||||
}
|
|
||||||
// Remove any trailing quotes (simple check for extra quotes at the end)
|
|
||||||
for strings.HasSuffix(cleaned, "\"") && !strings.HasSuffix(cleaned, "\"}") {
|
|
||||||
cleaned = cleaned[:len(cleaned)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove extra opening braces
|
|
||||||
for strings.HasPrefix(cleaned, "{") && strings.Count(cleaned, "{") > strings.Count(cleaned, "}") {
|
|
||||||
cleaned = cleaned[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if cleaned != payloadStr {
|
|
||||||
if err3 := json.Unmarshal([]byte(cleaned), &mqttMsg); err3 == nil {
|
|
||||||
content = mqttMsg.Status
|
|
||||||
logger.InfoCF("mqtt", "Successfully parsed cleaned JSON", map[string]any{
|
|
||||||
"original": payloadStr,
|
|
||||||
"cleaned": cleaned,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Fall back to plain text
|
|
||||||
content = payloadStr
|
|
||||||
logger.InfoCF("mqtt", "Received plain text message (JSON parsing failed)", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
"payload": content,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fall back to plain text
|
|
||||||
content = payloadStr
|
|
||||||
logger.InfoCF("mqtt", "Received plain text message (JSON parsing failed)", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
"payload": content,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not JSON-like, treat as plain text
|
// JSON parsing failed, treat as plain text
|
||||||
content = payloadStr
|
logger.InfoCF("mqtt", "JSON parsing failed, treating as plain text", map[string]any{
|
||||||
logger.InfoCF("mqtt", "Received plain text message (not JSON-like)", map[string]any{
|
"error": err.Error(),
|
||||||
"payload": content,
|
"payload": string(msg.Payload()),
|
||||||
})
|
})
|
||||||
|
content = string(msg.Payload())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No JSON key configured, treat as plain text
|
||||||
|
content = string(msg.Payload())
|
||||||
|
logger.InfoCF("mqtt", "Received plain text message", map[string]any{
|
||||||
|
"payload": content,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if content == "" {
|
if content == "" {
|
||||||
|
|
@ -292,13 +255,22 @@ func (c *MQTTChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||||
replyTopic = strings.ReplaceAll(replyTopic, "{topic}", msg.ChatID)
|
replyTopic = strings.ReplaceAll(replyTopic, "{topic}", msg.ChatID)
|
||||||
// Add more placeholders as needed
|
// Add more placeholders as needed
|
||||||
|
|
||||||
mqttMsg := MQTTMessage{
|
var payload []byte
|
||||||
Status: msg.Content,
|
var err error
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := json.Marshal(mqttMsg)
|
// Check if reply_json_key is configured
|
||||||
if err != nil {
|
if c.config.ReplyJSONKey != nil && *c.config.ReplyJSONKey != "" {
|
||||||
return fmt.Errorf("failed to marshal MQTT message: %w", err)
|
// Send as JSON with the specified key
|
||||||
|
jsonMsg := map[string]string{
|
||||||
|
*c.config.ReplyJSONKey: msg.Content,
|
||||||
|
}
|
||||||
|
payload, err = json.Marshal(jsonMsg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal MQTT JSON message: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send as plain text
|
||||||
|
payload = []byte(msg.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
token := c.client.Publish(replyTopic, byte(c.config.QoS), c.config.Retain, payload)
|
token := c.client.Publish(replyTopic, byte(c.config.QoS), c.config.Retain, payload)
|
||||||
|
|
@ -311,4 +283,4 @@ func (c *MQTTChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||||
"payload": string(payload),
|
"payload": string(payload),
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,9 @@ type MQTTConfig struct {
|
||||||
Username string `json:"username" env:"PICOCLAW_CHANNELS_MQTT_USERNAME"`
|
Username string `json:"username" env:"PICOCLAW_CHANNELS_MQTT_USERNAME"`
|
||||||
Password string `json:"password" env:"PICOCLAW_CHANNELS_MQTT_PASSWORD"`
|
Password string `json:"password" env:"PICOCLAW_CHANNELS_MQTT_PASSWORD"`
|
||||||
SubscribeTopics []string `json:"subscribe_topics" env:"PICOCLAW_CHANNELS_MQTT_SUBSCRIBE_TOPICS"`
|
SubscribeTopics []string `json:"subscribe_topics" env:"PICOCLAW_CHANNELS_MQTT_SUBSCRIBE_TOPICS"`
|
||||||
|
SubscribeJSONKey *string `json:"subscribe_json_key,omitempty"`
|
||||||
ReplyTopic string `json:"reply_topic" env:"PICOCLAW_CHANNELS_MQTT_REPLY_TOPIC"`
|
ReplyTopic string `json:"reply_topic" env:"PICOCLAW_CHANNELS_MQTT_REPLY_TOPIC"`
|
||||||
|
ReplyJSONKey *string `json:"reply_json_key,omitempty"`
|
||||||
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_MQTT_TLS"`
|
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_MQTT_TLS"`
|
||||||
TLSCA string `json:"tls_ca" env:"PICOCLAW_CHANNELS_MQTT_TLS_CA"`
|
TLSCA string `json:"tls_ca" env:"PICOCLAW_CHANNELS_MQTT_TLS_CA"`
|
||||||
TLSCert string `json:"tls_cert" env:"PICOCLAW_CHANNELS_MQTT_TLS_CERT"`
|
TLSCert string `json:"tls_cert" env:"PICOCLAW_CHANNELS_MQTT_TLS_CERT"`
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue