gohab
Home IoT server with LLM integration using MCP. Connects to devices such as lights, speakers, and cameras.
GitHub Stars
0
User Rating
Not Rated
Forks
0
Issues
0
Views
4
Favorites
0
GoHab - Home Automation Server
GoHab is a home automation server implementing a message-based architecture with device capabilities and pub/sub messaging for managing IoT devices and sensors.
Installation
go get github.com/mbocsi/gohab
Quick Start
Running the Server
Build and run the GoHab server:
make server
./bin/server
- Web interface: http://localhost:8080
- TCP transport: localhost:8888
Creating a Client
Import the GoHab client library in your Go project:
import (
"github.com/mbocsi/gohab/client"
"github.com/mbocsi/gohab/proto"
)
Basic client setup:
// Create TCP transport and client
tcp := client.NewTCPTransport()
c := client.NewClient("my-device", tcp)
// Define a capability
capability := proto.Capability{
Name: "temperature",
Description: "Temperature sensor",
Methods: proto.CapabilityMethods{
Data: proto.Method{
Description: "Temperature readings",
OutputSchema: map[string]proto.DataType{
"temperature": {Type: "number", Unit: "Celsius"},
"timestamp": {Type: "string"},
},
},
},
}
// Add capability and start
c.AddCapability(capability)
c.Start("localhost:8888")
Architecture
Core Components
Server:
GohabServer
- Main server coordinating all componentsBroker
- Pub/sub message broker for topic-based communicationDeviceRegistry
- Manages connected devices and their metadataTransport
- Abstraction for client connections (TCP, Web)
Client:
Client
- Main client with capability managementTransport
- Client transport abstraction (TCP implementation)- Device capabilities define supported actions/data
Message Protocol
JSON-based messages with types:
identify
- Device registration with capabilitiescommand
- Control device actionsquery
- Request information from devicesresponse
- Reply to queriesdata
- Publish sensor readingsstatus
- Report device statussubscribe
- Subscribe to topics
Capabilities System
Devices declare capabilities during identification. Each capability can support four method types:
- Data - Publish sensor readings or state updates
- Status - Report device health/status
- Command - Accept control commands
- Query - Respond to information requests
Client API
Creating a Client
// Create transport and client
tcp := client.NewTCPTransport()
client := client.NewClient("device-name", tcp)
// Connect to server
err := client.Start("localhost:8888")
Defining Capabilities
capability := proto.Capability{
Name: "led_strip",
Description: "RGB LED strip controller",
Methods: proto.CapabilityMethods{
Command: proto.Method{
Description: "Set LED color and brightness",
InputSchema: map[string]proto.DataType{
"color": {Type: "string", Description: "Hex color code"},
"brightness": {Type: "number", Range: []float64{0, 100}},
},
},
Status: proto.Method{
Description: "LED strip status",
OutputSchema: map[string]proto.DataType{
"status": {Type: "enum", Enum: []string{"on", "off", "error"}},
},
},
},
}
client.AddCapability(capability)
Handling Commands
client.RegisterCommandHandler("led_strip", func(msg proto.Message) error {
var cmd struct {
Color string `json:"color"`
Brightness float64 `json:"brightness"`
}
if err := json.Unmarshal(msg.Payload, &cmd); err != nil {
return err
}
// Apply LED settings
setLEDColor(cmd.Color, cmd.Brightness)
return nil
})
Publishing Data
// Get data function for a capability
dataFn, err := client.GetDataFunction("temperature")
if err != nil {
panic(err)
}
// Publish temperature data
dataFn(map[string]interface{}{
"temperature": 23.5,
"timestamp": time.Now().Format(time.RFC3339),
})
Responding to Queries
client.RegisterQueryHandler("temperature", func(msg proto.Message) (any, error) {
currentTemp := readTemperatureSensor()
return map[string]interface{}{
"temperature": currentTemp,
"timestamp": time.Now().Format(time.RFC3339),
}, nil
})
Subscribing to Topics
client.Subscribe("temperature", func(msg proto.Message) error {
fmt.Printf("Received temperature update: %s\n", string(msg.Payload))
return nil
})
Sending Commands to Other Devices
client.SendCommand("display_brightness", map[string]interface{}{
"brightness": 75.0,
})
Querying Other Devices
response, err := client.SendQuery("temperature", nil)
if err != nil {
log.Printf("Query failed: %v", err)
return
}
fmt.Printf("Temperature response: %s\n", string(response.Payload))
Data Types
The capability system supports typed schemas for validation:
proto.DataType{
Type: "number", // number, string, bool, enum, object
Unit: "Celsius", // Optional unit
Description: "Temperature value", // Optional description
Range: []float64{-40, 85}, // Optional range for numbers
Enum: []string{"on", "off"}, // Required for enum type
Optional: false, // Whether field is optional
}
Web Interface
The server provides a web interface at http://localhost:8080 showing:
- Connected devices and their capabilities
- Available features (topics) and their sources
- Transport connections and status
- Real-time updates via Server-Sent Events
Development
Building from Source
# Build server
make server
# Build example clients (for reference)
make client1 client2 client3
# Build everything
make all
# Clean build artifacts
make clean
Project Structure
├── cmd/ # Server and example clients
├── server/ # Server components
├── client/ # Client library (import this)
├── proto/ # Message and capability definitions
├── services/ # Server services
├── web/ # Web interface
├── templates/ # HTML templates
└── assets/ # Static web assets
Example Implementation
Here's a complete example of a temperature sensor client:
package main
import (
"log"
"math/rand"
"time"
"github.com/mbocsi/gohab/client"
"github.com/mbocsi/gohab/proto"
)
func main() {
// Create client
tcp := client.NewTCPTransport()
c := client.NewClient("temp-sensor-01", tcp)
// Define temperature capability
tempCapability := proto.Capability{
Name: "temperature",
Description: "Indoor temperature sensor",
Methods: proto.CapabilityMethods{
Data: proto.Method{
Description: "Temperature readings",
OutputSchema: map[string]proto.DataType{
"temperature": {Type: "number", Unit: "Celsius"},
"humidity": {Type: "number", Unit: "percent"},
"timestamp": {Type: "string"},
},
},
Query: proto.Method{
Description: "Get current temperature",
OutputSchema: map[string]proto.DataType{
"temperature": {Type: "number", Unit: "Celsius"},
"humidity": {Type: "number", Unit: "percent"},
},
},
},
}
// Add capability
c.AddCapability(tempCapability)
// Handle queries
c.RegisterQueryHandler("temperature", func(msg proto.Message) (any, error) {
return map[string]interface{}{
"temperature": readTemperature(),
"humidity": readHumidity(),
}, nil
})
// Get data publishing function
dataFn, _ := c.GetDataFunction("temperature")
// Start client
go c.Start("localhost:8888")
// Publish data every 30 seconds
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
dataFn(map[string]interface{}{
"temperature": readTemperature(),
"humidity": readHumidity(),
"timestamp": time.Now().Format(time.RFC3339),
})
}
}
func readTemperature() float64 {
return rand.Float64()*10 + 20 // Mock sensor
}
func readHumidity() float64 {
return rand.Float64()*40 + 40 // Mock sensor
}
License
This project is licensed under the MIT License.