This guide helps you create your first SHIP (Smart Home IP) device using ship-go. For complete protocol specifications, see SHIP TS 1.0.1.
SHIP is a protocol for secure communication between smart home devices like heat pumps, EV chargers, and energy management systems. It provides:
- Device Discovery - Find devices on your network via mDNS (Section 5)
- Secure Communication - TLS encryption with certificate-based identity (Section 9)
- Trust Management - Explicit pairing before data exchange (Section 13.4.4.2)
- Protocol Negotiation - 5-phase handshake for reliable connections (Section 13.4.4)
- Go 1.19+ - Required for ship-go
- Linux with Avahi (recommended) or any OS for Zeroconf fallback
- Network with mDNS - Most local networks support multicast traffic
- Port 4712 - Must be accessible (SHIP standard port)
go mod init my-ship-device
go get github.com/enbility/ship-goCopy and run our quickstart example:
# Get the example
curl -o quickstart.go https://raw.githubusercontent.com/enbility/ship-go/dev/examples/quickstart/main.go
# Run it
go mod tidy
go run quickstart.goYou should see:
π Starting SHIP Hub Quickstart
================================
π Creating new certificate...
π Device SKI (identifier): a1b2c3d4e5f6...
β
SHIP hub running on port 4712
Your hub is now discoverable and accepting connections!
The quickstart example:
- Created a certificate (SHIP Section 12) - Self-signed cert with unique SKI
- Started mDNS announcement (SHIP Section 5) - Broadcasts "I'm available"
- Opened WebSocket server (SHIP Section 9) - Listens on port 4712
- Set auto-accept pairing - Automatically trusts new devices (dev mode only!)
package main
import (
"github.com/enbility/ship-go/api"
"github.com/enbility/ship-go/cert"
"github.com/enbility/ship-go/hub"
"github.com/enbility/ship-go/mdns"
)
// Implement HubReaderInterface to receive SHIP events
type MyHubReader struct{}
func (h *MyHubReader) RemoteSKIConnected(ski string) {
// Device connected - start SPINE communication
}
func (h *MyHubReader) AllowWaitingForTrust(ski string) bool {
// Production: Ask user for approval
// Development: return true
return askUserForApproval(ski)
}
// ... implement other required methods
func main() {
// 1. Create certificate
cert, _ := cert.CreateCertificate("MyUnit", "MyOrg", "DE", "MyDevice")
// 2. Set up mDNS
mdns := mdns.NewMDNS(/* parameters */)
// 3. Create hub
reader := &MyHubReader{}
hub := hub.NewHub(reader, mdns, 4712, cert, serviceDetails)
// 4. Start
hub.Start()
}Every SHIP device needs a unique certificate:
// Create new certificate
certificate, err := cert.CreateCertificate(
"DeviceUnit", // Organizational Unit
"YourCompany", // Organization
"DE", // Country
"HeatPump-001", // Common Name (device model + serial)
)
// Extract SKI (device identifier)
x509Cert, _ := x509.ParseCertificate(certificate.Certificate[0])
ski, _ := cert.SkiFromCertificate(x509Cert)Important: In production, save and reuse certificates! See SECURITY.md.
Configure mDNS to announce your device:
deviceCategories := []api.DeviceCategoryType{
// Empty for generic device
}
mdnsManager := mdns.NewMDNS(
ski, // Device SKI
"YourBrand", // Device brand
"YourModel", // Device model
"HeatPump", // Device type
"SN12345", // Serial number
deviceCategories, // Device categories
"your-ship-id", // SHIP identifier
"SHIP-YourDevice", // Service name
4712, // Port
[]string{}, // Interfaces (empty = all)
mdns.MdnsProviderSelectionAll, // Provider (auto-select)
)Your hub reader handles all SHIP events:
type ProductionHubReader struct {
userInterface UserInterface
spineHandler SpineHandler
}
// Connection events
func (h *ProductionHubReader) RemoteSKIConnected(ski string) {
log.Printf("Device %s connected", ski)
// Set up SPINE message handling
}
func (h *ProductionHubReader) RemoteSKIDisconnected(ski string) {
log.Printf("Device %s disconnected", ski)
// Clean up resources
}
// SPINE integration
func (h *ProductionHubReader) SetupRemoteDevice(
ski string,
writer api.ShipConnectionDataWriterInterface,
) api.ShipConnectionDataReaderInterface {
// Return your SPINE message handler
return h.spineHandler.NewDeviceHandler(ski, writer)
}
// Discovery events
func (h *ProductionHubReader) VisibleRemoteServicesUpdated(services []api.RemoteService) {
for _, service := range services {
log.Printf("Found device: %s (%s %s)", service.Ski, service.Brand, service.Model)
}
}
// Pairing management
func (h *ProductionHubReader) AllowWaitingForTrust(ski string) bool {
// CRITICAL: Never auto-accept in production!
return h.userInterface.PromptUserForTrust(ski)
}
func (h *ProductionHubReader) ServicePairingDetailUpdate(ski string, detail *api.ConnectionStateDetail) {
// Update pairing progress in UI
h.userInterface.UpdatePairingProgress(ski, detail)
}Once your hub is running, it will automatically discover other SHIP devices:
func (h *MyHubReader) VisibleRemoteServicesUpdated(services []api.RemoteService) {
for _, service := range services {
fmt.Printf("Found: %s (Brand: %s, Model: %s)\n",
service.Ski, service.Brand, service.Model)
}
}To connect to a discovered device:
// In your hub reader
hub.RegisterRemoteService(ski, shipID) // Register device
hub.ConnectSKI(ski, true) // Initiate connectionThe pairing process (SHIP Section 13.4.4.2) involves trust verification:
func (h *MyHubReader) AllowWaitingForTrust(ski string) bool {
// Show user the device requesting connection
deviceInfo := h.getDeviceInfo(ski)
// Prompt user for approval
fmt.Printf("Device '%s %s' wants to connect. Accept? (y/n): ",
deviceInfo.Brand, deviceInfo.Model)
var response string
fmt.Scanln(&response)
return response == "y"
}-
Never auto-accept pairing in production
func (h *MyHubReader) AllowWaitingForTrust(ski string) bool { return false // Force user verification }
-
Set connection limits for resource-constrained devices
hub.SetMaxConnections(10) // Adjust based on device capacity
-
Validate certificates and monitor failed connections
// Monitor connections
func (h *MyHubReader) ServiceConnectionStateChanged(ski string, state api.ConnectionState) {
switch state {
case api.ConnectionStateError:
h.logSecurityEvent("connection_failed", ski)
case api.ConnectionStateCompleted:
h.metrics.IncrementConnections()
}
}
// Graceful shutdown
defer func() {
log.Println("Shutting down...")
hub.Shutdown()
}()// Robust startup
if err := hub.Start(); err != nil {
log.Fatalf("Failed to start hub: %v", err)
}
// Connection monitoring
func (h *MyHubReader) RemoteSKIDisconnected(ski string) {
log.Printf("Device %s disconnected", ski)
// Implement reconnection logic if needed
go h.scheduleReconnection(ski)
}- Check port 4712 is accessible
- Verify firewall settings
- Ensure device is on same network
- Check mDNS/multicast support on network
- On Linux, ensure
avahi-daemonis running - Verify network interfaces are up
- Check certificate validity
- Verify trust establishment works
- Enable debug logging for details
- Ensure certificates have valid SKI
- Don't share certificates between devices
- Check certificate hasn't expired
- Integration with SPINE - Add higher-level protocol support
- Persistent storage - Save trusted devices and certificates
- User interface - Create pairing and device management UI
- Production deployment - See production guide
- Examples Directory - Working code samples
- SECURITY.md - Security model and best practices
- API Reference - Complete API documentation
- SHIP Specification - Official protocol documentation
- EEBUS Architecture - Overview of smart energy ecosystem
- Check troubleshooting guide for common issues
- Review error handling patterns
- Search existing GitHub issues
- For security issues, see SECURITY.md for reporting guidelines
Welcome to the SHIP protocol ecosystem! π