Docs / Contact / sdks/go/examples

Go SDK: Examples

Practical examples for common use cases with the Tendrl Go SDK.

Periodic Data Collection with Tether#

The Tether method calls a function on a schedule and publishes its return value:

go

// Collect and publish system metrics every 30 seconds
stop := client.Tether("system-metrics", func() (interface{}, error) {
    return map[string]interface{}{
        "cpu_usage":  getCPUUsage(),
        "mem_usage":  getMemUsage(),
        "goroutines": runtime.NumGoroutine(),
    }, nil
}, []string{"system", "metrics"}, 30*time.Second)

// Stop when done
defer stop()

Return an error to skip publishing that cycle without stopping the tether:

go

stop := client.Tether("sensor", func() (interface{}, error) {
    value, err := readSensor()
    if err != nil {
        return nil, err // Skips this cycle, tries again next interval
    }
    return map[string]interface{}{"value": value}, nil
}, []string{"sensor"}, 5*time.Second)

Synchronous Publish with Response#

Wait for the server to acknowledge a message:

go

messageID, err := client.Publish(
    map[string]interface{}{
        "action":  "provision",
        "device":  "sensor-042",
    },
    []string{"provisioning"},
    "",    // entity (empty = self)
    true,  // waitResponse
    10,    // timeout seconds
)
if err != nil {
    log.Printf("Failed: %v", err)
} else {
    log.Printf("Confirmed: %s", messageID)
}

Inbound Message Routing#

Route incoming messages by tag with client.On():

go

client.On(tendrl.MessageRoute{
    Tag: "diagnostic",
    Handler: func(msg tendrl.IncomingMessage) error {
        log.Println("Diagnostic:", msg.Data)
        return nil
    },
})

client.On(tendrl.MessageRoute{
    Tag: "ai-response",
    Handler: func(msg tendrl.IncomingMessage) error {
        log.Println("AI:", msg.Data)
        return nil
    },
})

client.On(tendrl.MessageRoute{
    Tags: []string{"alert", "anomaly"},
    Handler: func(msg tendrl.IncomingMessage) error {
        log.Println("Alert:", msg.Data)
        return nil
    },
})
client.SetMessageCheckRate(2 * time.Second)
client.SetMessageCheckLimit(10)

Cross-Account Messaging#

Send messages to entities in other accounts:

go

err := client.PublishCrossAccount(
    map[string]interface{}{
        "command": "status_request",
    },
    "account123:us-west:entity:device-001",
    []string{"query"},
)

State Table Management#

The Go SDK reads the state table but does not write it. Register a handler and the managed client polls for changes at the message-check interval:

go

client.OnState(func(state map[string]interface{}) error {
    fmt.Printf("state changed: %v\n", state)
    return nil
})

// Or poll once, on your own schedule
if err := client.CheckState(); err != nil {
    log.Println("state poll failed:", err)
}

To write, call the REST endpoints directly with the entity's API key — there is no UpdateState or ReplaceState method:

go

func writeState(apiKey string, method string, body map[string]interface{}) error {
    baseURL := os.Getenv("TENDRL_APP_URL")
    if baseURL == "" {
        baseURL = "https://app.tendrl.com/api"
    }
    payload, err := json.Marshal(body)
    if err != nil {
        return err
    }
    req, err := http.NewRequest(method, baseURL+"/entities/status-table", bytes.NewReader(payload))
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("status-table %s returned %s", method, resp.Status)
    }
    return nil
}

// Merge new data (PATCH; existing keys preserved)
err := writeState(apiKey, http.MethodPatch, map[string]interface{}{
    "firmware":  "2.1.0",
    "last_boot": time.Now().Format(time.RFC3339),
})

// Replace entire state (PUT; removes all previous keys)
err = writeState(apiKey, http.MethodPut, map[string]interface{}{
    "firmware": "2.1.0",
    "status":   "active",
})

Connectivity Monitoring#

Check network status and react to changes:

go

if client.IsOnline() {
    // Send queued data
    client.PublishAsync(data, tags)
} else {
    log.Println("Offline; message will be stored")
}

// Get detailed connectivity state
connState := client.GetConnectivityState()
fmt.Printf("Online: %v, Last check: %s\n",
    connState.Online, connState.LastCheck)

Flexible Data Types#

The SDK accepts any JSON-serializable type:

go

// Maps
client.PublishAsync(map[string]interface{}{"key": "value"}, tags)

// Structs
type SensorReading struct {
    ID    string  `json:"id"`
    Value float64 `json:"value"`
    Unit  string  `json:"unit"`
}
client.PublishAsync(SensorReading{
    ID: "temp-1", Value: 23.5, Unit: "celsius",
}, tags)

// Slices
client.PublishAsync([]int{1, 2, 3}, tags)

// Strings (auto-wrapped as {"data": "..."})
client.PublishAsync("simple message", tags)

// Primitives
client.PublishAsync(42, tags)
client.PublishAsync(3.14, tags)
client.PublishAsync(true, tags)

Custom Heartbeat#

Send a manual heartbeat with specific metrics:

go

err := client.PublishHeartbeat(tendrl.HeartbeatData{
    MemFree:  getMemFree(),
    MemTotal: getMemTotal(),
    DiskFree: getDiskFree(),
    DiskSize: getDiskTotal(),
})