Docs / Contact / sdks/go/getting-started

Go SDK: Getting Started

Go from zero to publishing device data in under a minute. The SDK handles message batching, BoltDB offline storage, automatic heartbeats, and connectivity monitoring, so you can focus on your application.

Requirements: Go 1.21+

Install

bash

go get github.com/tendrl-inc-labs/go-sdk
Need an API key?

Every client needs an entity API key. If you haven't created one yet, create your first entity and copy its API key from the Connection Instructions dialog.

Your First Message

go


client, err := tendrl.NewClient(true, "your_api_key")
if err != nil {
    panic(err)
}
defer client.Stop()

client.PublishAsync(map[string]interface{}{"temperature": 23.5}, []string{"sensor"})

The message is queued, batched, and delivered. Tags route it to your flows and connectors.

Automate with Tether

Most IoT work is "collect data, publish, repeat." Tether does this in one call: give it a function and an interval, and the SDK handles the rest.

go

stop := client.Tether("sensors", func() (interface{}, error) {
    return map[string]interface{}{
        "temperature": readTemp(),
        "humidity":    readHumidity(),
        "pressure":    readPressure(),
    }, nil
}, []string{"sensor", "environment"}, 10*time.Second)
defer stop()

Every 10 seconds, your function runs and the result is published with the tags you specify. No goroutine management, no tickers, no manual publish calls.

Return an error to skip a 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)

Route Inbound Messages

Handle messages sent back from Contact flows. Route by tag with client.On():

go

client.On(tendrl.MessageRoute{
    Tag: "diagnostic",
    Handler: func(msg tendrl.IncomingMessage) error {
        // run diagnostics and publish result
        return nil
    },
})

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

client.On(tendrl.MessageRoute{
    Tags: []string{"alert", "anomaly"},
    Handler: func(msg tendrl.IncomingMessage) error {
        fmt.Println("Alert:", msg.Data)
        return nil
    },
})

client.SetMessageCheckRate(3 * time.Second)

SetMessageCallback remains available as a catch-all fallback for unmatched messages.

Track Device State

Every entity has a persistent state table, a key-value store accessible from the dashboard and other entities:

go

// Merge new data (existing keys preserved)
client.UpdateState(map[string]interface{}{
    "firmware": "2.1.0",
    "status":   "active",
}, nil)

// Read it back
state, _ := client.GetState()

// Trigger a flow when state changes
client.UpdateState(map[string]interface{}{
    "status": "maintenance",
}, []string{"status-change"})

Receive remote state changes with OnState(), which is polled at the same interval as messages:

go

client.OnState(func(state map[string]interface{}) error {
    if status, _ := state["status"].(string); status == "needs_maintenance" {
        runDiagnostics()
    }
    return nil
})

What You Get for Free

When you create a managed client, the SDK automatically:

You don't configure any of this. It just works.

Complete Example

go

package main

    "fmt"
    "os"
    "os/signal"
    "time"
    "github.com/tendrl-inc-labs/go-sdk/tendrl"
)

func main() {
    client, err := tendrl.NewClient(true, "your_api_key")
    if err != nil {
        panic(err)
    }
    defer client.Stop()

    // Route inbound messages by tag
    client.On(tendrl.MessageRoute{
        Tag: "ai-response",
        Handler: func(msg tendrl.IncomingMessage) error {
            fmt.Printf("[%s] %v\n", msg.MsgType, msg.Data)
            return nil
        },
    })
    client.SetMessageCheckRate(3 * time.Second)

    // Collect and publish sensor data every 10 seconds
    stop := client.Tether("sensors", func() (interface{}, error) {
        return map[string]interface{}{
            "temperature": 23.5,
            "humidity":    60,
        }, nil
    }, []string{"sensor"}, 10*time.Second)
    defer stop()

    // Wait for interrupt
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt)
    <-sig
}

Operating Modes

Managed Mode (Default)

go

client, err := tendrl.NewClient(true, "your_key")

Background goroutines handle batching, offline storage, heartbeats, and connectivity. Recommended for most use cases.

Headless Mode

go

client, err := tendrl.NewClient(false, "your_key")

Direct HTTP calls only. No background processing. Best for simple tools or when you need full control.

What's Next