go-app-sdk: github.com/TheThingsNetwork/go-app-sdk Index | Examples | Files

package ttnsdk

import "github.com/TheThingsNetwork/go-app-sdk"

Package ttnsdk implements the Go SDK for The Things Network.

This package wraps The Things Network's application and device management APIs (github.com/TheThingsNetwork/api) and the publish/subscribe API (github.com/TheThingsNetwork/ttn/mqtt). It works with the Discovery Server to retrieve the addresses of the Handler and MQTT server.

Code:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "os"

    ttnsdk "github.com/TheThingsNetwork/go-app-sdk"
    ttnlog "github.com/TheThingsNetwork/go-utils/log"
    "github.com/TheThingsNetwork/go-utils/log/apex"
    "github.com/TheThingsNetwork/go-utils/random"
    "github.com/TheThingsNetwork/ttn/core/types"
)

const (
    sdkClientName = "my-amazing-app"
)

func main() {
    log := apex.Stdout() // We use a cli logger at Stdout
    log.MustParseLevel("debug")
    ttnlog.Set(log) // Set the logger as default for TTN

    // We get the application ID and application access key from the environment
    appID := os.Getenv("TTN_APP_ID")
    appAccessKey := os.Getenv("TTN_APP_ACCESS_KEY")

    // Create a new SDK configuration for the public community network
    config := ttnsdk.NewCommunityConfig(sdkClientName)
    config.ClientVersion = "2.0.5" // The version of the application

    // If you connect to a private network that does not use trusted certificates on the Discovery Server
    // (from Let's Encrypt for example), you have to manually trust the certificates. If you use the public community
    // network, you can just delete the next code block.
    if caCert := os.Getenv("TTN_CA_CERT"); caCert != "" {
        config.TLSConfig = new(tls.Config)
        certBytes, err := ioutil.ReadFile(caCert)
        if err != nil {
            log.WithError(err).Fatal("my-amazing-app: could not read CA certificate file")
        }
        config.TLSConfig.RootCAs = x509.NewCertPool()
        if ok := config.TLSConfig.RootCAs.AppendCertsFromPEM(certBytes); !ok {
            log.Fatal("my-amazing-app: could not read CA certificates")
        }
    }

    // Create a new SDK client for the application
    client := config.NewClient(appID, appAccessKey)

    // Make sure the client is closed before the function returns
    // In your application, you should call this before the application shuts down
    defer client.Close()

    // Manage devices for the application.
    devices, err := client.ManageDevices()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not get device manager")
    }

    // List the first 10 devices
    deviceList, err := devices.List(10, 0)
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not get devices")
    }
    log.Info("my-amazing-app: found devices")
    for _, device := range deviceList {
        fmt.Printf("- %s", device.DevID)
    }

    // Create a new device
    dev := new(ttnsdk.Device)
    dev.AppID = appID
    dev.DevID = "my-new-device"
    dev.Description = "A new device in my amazing app"
    dev.AppEUI = types.AppEUI{0x70, 0xB3, 0xD5, 0x7E, 0xF0, 0x00, 0x00, 0x24} // Use the real AppEUI here
    dev.DevEUI = types.DevEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} // Use the real DevEUI here

    // Set a random AppKey
    dev.AppKey = new(types.AppKey)
    random.FillBytes(dev.AppKey[:])

    err = devices.Set(dev)
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not create device")
    }

    // Get the device
    dev, err = devices.Get("my-new-device")
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not get device")
    }

    // Personalize the device with random session keys
    err = dev.PersonalizeRandom()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not personalize device")
    }
    log.WithFields(ttnlog.Fields{
        "devAddr": dev.DevAddr,
        "nwkSKey": dev.NwkSKey,
        "appSKey": dev.AppSKey,
    }).Info("my-amazing-app: personalized device")

    // Start Publish/Subscribe client (MQTT)
    pubsub, err := client.PubSub()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not get application pub/sub")
    }

    // Make sure the pubsub client is closed before the function returns
    // In your application, you should call this before the application shuts down
    defer pubsub.Close()

    // Get a publish/subscribe client for all devices
    allDevicesPubSub := pubsub.AllDevices()

    // Make sure the pubsub client is closed before the function returns
    // In your application, you will probably call this before the application shuts down
    // This also stops existing subscriptions, in case you forgot to unsubscribe
    defer allDevicesPubSub.Close()

    // Subscribe to activations
    activations, err := allDevicesPubSub.SubscribeActivations()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not subscribe to activations")
    }
    log.Debug("After this point, the program won't show anything until we receive an activation.")
    for activation := range activations {
        log.WithFields(ttnlog.Fields{
            "appEUI":  activation.AppEUI.String(),
            "devEUI":  activation.DevEUI.String(),
            "devAddr": activation.DevAddr.String(),
        }).Info("my-amazing-app: received activation")
        break // normally you wouldn't do this
    }

    // Unsubscribe from activations
    err = allDevicesPubSub.UnsubscribeActivations()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not unsubscribe from activations")
    }

    // Subscribe to events
    events, err := allDevicesPubSub.SubscribeEvents()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not subscribe to events")
    }
    log.Debug("After this point, the program won't show anything until we receive an application event.")
    for event := range events {
        log.WithFields(ttnlog.Fields{
            "devID":     event.DevID,
            "eventType": event.Event,
        }).Info("my-amazing-app: received event")
        if event.Data != nil {
            eventJSON, _ := json.Marshal(event.Data)
            fmt.Println("Event data:" + string(eventJSON))
        }
        break // normally you wouldn't do this
    }

    // Unsubscribe from events
    err = allDevicesPubSub.UnsubscribeEvents()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not unsubscribe from events")
    }

    // Get a publish/subscribe client scoped to my-test-device
    myNewDevicePubSub := pubsub.Device("my-new-device")

    // Make sure the pubsub client for this device is closed before the function returns
    // In your application, you will probably call this when you no longer need the device
    // This also stops existing subscriptions, in case you forgot to unsubscribe
    defer myNewDevicePubSub.Close()

    // Subscribe to uplink messages
    uplink, err := myNewDevicePubSub.SubscribeUplink()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not subscribe to uplink messages")
    }
    log.Debug("After this point, the program won't show anything until we receive an uplink message from device my-new-device.")
    for message := range uplink {
        hexPayload := hex.EncodeToString(message.PayloadRaw)
        log.WithField("data", hexPayload).Info("my-amazing-app: received uplink")
        break // normally you wouldn't do this
    }

    // Unsubscribe from uplink
    err = myNewDevicePubSub.UnsubscribeUplink()
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not unsubscribe from uplink")
    }

    // Publish downlink message
    err = myNewDevicePubSub.Publish(&types.DownlinkMessage{
        AppID:      appID,           // can be left out, the SDK will fill this
        DevID:      "my-new-device", // can be left out, the SDK will fill this
        PayloadRaw: []byte{0xaa, 0xbc},
        FPort:      10,
        Schedule:   types.ScheduleLast, // allowed values: "replace" (default), "first", "last"
        Confirmed:  false,              // can be left out, default is false
    })
    if err != nil {
        log.WithError(err).Fatal("my-amazing-app: could not schedule downlink message")
    }

}

Index

Examples

Package Files

application_manager.go client.go device_manager.go discovery.go handler.go mqtt.go procedures.go sdk.go simulate.go utils.go

Variables

var ClientVersion = "2.x.x"

ClientVersion to use

var DialOptions = []grpc.DialOption{
    grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
        rpclog.UnaryClientInterceptor(nil),
    )),
    grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
        restartstream.Interceptor(restartstream.DefaultSettings),
        rpclog.StreamClientInterceptor(nil),
    )),
    grpc.WithBlock(),
}

DialOptions to use when connecting to components

func MoveDevice Uses

func MoveDevice(devID string, from, to DeviceManager) (err error)

MoveDevice moves a device to another application

type ApplicationManager Uses

type ApplicationManager interface {
    // Get the payload format used in this application. If the payload format is "custom", you can get the custom JS
    // payload functions with the GetCustomPayloadFunctions() function.
    GetPayloadFormat() (string, error)

    // Set the payload format to use in this application. If you want to use custom JS payload functions, use the
    // SetCustomPayloadFunctions() function instead. If you want to disable payload conversion, pass an empty string.
    SetPayloadFormat(format string) error

    // Get the custom JS payload functions.
    GetCustomPayloadFunctions() (jsDecoder, jsConverter, jsValidator, jsEncoder string, err error)

    // Set the custom JS payload functions.
    //
    // Example Decoder:
    //
    // // Decoder (Array<byte>, uint8) returns (Object)
    // function Decoder(bytes, port) {
    //   var decoded = {};
    //   return decoded;
    // }
    //
    // Example Converter:
    //
    // // Converter (Object, uint8) returns (Object)
    // function Converter(decoded, port) {
    //   var converted = decoded;
    //   return converted;
    // }
    //
    // Example Validator:
    // // Validator (Object, uint8) returns (Boolean)
    // function Validator(converted, port) {
    //   return true;
    // }
    //
    // Example Encoder:
    //
    // // Validator (Object, uint8) returns (Array<byte>)
    // function Encoder(object, port) {
    //   var bytes = [];
    //   return bytes;
    // }
    SetCustomPayloadFunctions(jsDecoder, jsConverter, jsValidator, jsEncoder string) error
}

ApplicationManager manages an application.

type ApplicationPubSub Uses

type ApplicationPubSub interface {
    Publish(devID string, downlink *types.DownlinkMessage) error
    Device(devID string) DevicePubSub
    AllDevices() DeviceSub
    Close()
}

ApplicationPubSub interface for publishing and subscribing to devices in an application

type Client Uses

type Client interface {
    // Close the client and clean up all connections
    Close() error

    // Subscribe to uplink and events, publish downlink
    PubSub() (ApplicationPubSub, error)

    // Manage the application
    ManageApplication() (ApplicationManager, error)

    // Manage devices in the application
    ManageDevices() (DeviceManager, error)

    // Simulate uplink messages for a device (for testing)
    Simulate(devID string) (Simulator, error)
}

Client interface for The Things Network's API.

type ClientConfig Uses

type ClientConfig struct {
    Logger log.Interface

    // The name of this client
    ClientName string

    // The version of this client (in the default config, this is the value of ttnsdk.ClientVersion)
    ClientVersion string

    // TLS Configuration only has to be set if connecting with servers that do not have trusted certificates.
    TLSConfig *tls.Config

    // Address of the Account Server (in the default config, this is https://account.thethingsnetwork.org)
    AccountServerAddress string

    // Client ID for the account server (if you registered your client)
    AccountServerClientID string

    // Client Secret for the account server (if you registered your client)
    AccountServerClientSecret string

    // Address of the Discovery Server (in the default config, this is discovery.thethings.network:1900)
    DiscoveryServerAddress string

    // Set this to true if the Discovery Server is insecure (not recommended)
    DiscoveryServerInsecure bool

    // Address of the Handler (optional)
    HandlerAddress string

    // Timeout for requests (in the default config, this is 10 seconds)
    RequestTimeout time.Duration
    // contains filtered or unexported fields
}

ClientConfig contains the configuration for the API client. Use the NewConfig() or NewCommunityConfig() functions to initialize your configuration, otherwise NewClient will panic.

func NewCommunityConfig Uses

func NewCommunityConfig(clientName string) ClientConfig

NewCommunityConfig creates a new configuration for the API client that is pre-configured for the Public Community Network.

func NewConfig Uses

func NewConfig(clientName, accountServerAddress, discoveryServerAddress string) ClientConfig

NewConfig creates a new configuration for the API client.

func (ClientConfig) NewClient Uses

func (c ClientConfig) NewClient(appID, appAccessKey string) Client

NewClient creates a new API client from the configuration, using the given Application ID and Application access key.

type Device Uses

type Device struct {
    SparseDevice
    FCntUp                uint32    `json:"f_cnt_up"`
    FCntDown              uint32    `json:"f_cnt_down"`
    DisableFCntCheck      bool      `json:"disable_f_cnt_check"`
    Uses32BitFCnt         bool      `json:"uses32_bit_f_cnt"`
    ActivationConstraints string    `json:"activation_constraints"`
    LastSeen              time.Time `json:"last_seen"`
    // contains filtered or unexported fields
}

Device in an application

func (*Device) Delete Uses

func (d *Device) Delete() error

Delete the device. This function panics if this is a new device.

func (*Device) IsNew Uses

func (d *Device) IsNew() bool

IsNew indicates whether the device is new.

func (*Device) Personalize Uses

func (d *Device) Personalize(nwkSKey types.NwkSKey, appSKey types.AppSKey) error

Personalize a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey to the given values. This function panics if this is a new device, so make sure you Get() the device first.

func (*Device) PersonalizeFunc Uses

func (d *Device) PersonalizeFunc(personalizeFunc func(types.DevAddr) (types.NwkSKey, types.AppSKey)) error

PersonalizeFunc personalizes a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey to the result of the personalizeFunc. This function panics if this is a new device, so make sure you Get() the device first.

func (*Device) PersonalizeRandom Uses

func (d *Device) PersonalizeRandom() error

PersonalizeRandom personalizes a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey to randomly generated values. This function panics if this is a new device, so make sure you Get() the device first.

func (*Device) SetManager Uses

func (d *Device) SetManager(manager DeviceManager)

SetManager sets the manager of the device. This function panics if this is not a new device.

func (*Device) Update Uses

func (d *Device) Update() error

Update the device. This function panics if this is a new device.

type DeviceList Uses

type DeviceList []*SparseDevice

DeviceList is a slice of *SparseDevice.

func (DeviceList) AsDevices Uses

func (d DeviceList) AsDevices() []*Device

AsDevices returns the DeviceList as a slice of *Device instead of *SparseDevice

type DeviceManager Uses

type DeviceManager interface {
    // List devices in an application. Use the limit and offset for pagination. Requests that fetch many devices will be
    // very slow, which is often not necessary. If you use this function too often, the response will be cached by the
    // server, and you might receive outdated data.
    List(limit, offset uint64) (DeviceList, error)

    // Get details for a device
    Get(devID string) (*Device, error)

    // Create or Update a device.
    Set(*Device) error

    // Delete a device
    Delete(devID string) error
}

DeviceManager manages devices within an application

type DevicePub Uses

type DevicePub interface {
    Publish(*types.DownlinkMessage) error
}

DevicePub interface for publishing downlink messages to the device

type DevicePubSub Uses

type DevicePubSub interface {
    DevicePub
    DeviceSub
}

DevicePubSub combines the DevicePub and DeviceSub interfaces

type DeviceSub Uses

type DeviceSub interface {
    SubscribeUplink() (<-chan *types.UplinkMessage, error)
    UnsubscribeUplink() error
    SubscribeEvents() (<-chan *types.DeviceEvent, error)
    UnsubscribeEvents() error
    SubscribeActivations() (<-chan *types.Activation, error)
    UnsubscribeActivations() error
    Close()
}

DeviceSub interface for subscribing to uplink messages and events from the device

type Simulator Uses

type Simulator interface {
    Uplink(port uint8, payload []byte) error
}

Simulator simulates messages for devices

type SparseDevice Uses

type SparseDevice struct {
    AppID       string            `json:"app_id"`
    DevID       string            `json:"dev_id"`
    AppEUI      types.AppEUI      `json:"app_eui"`
    DevEUI      types.DevEUI      `json:"dev_eui"`
    Description string            `json:"description,omitempty"`
    DevAddr     *types.DevAddr    `json:"dev_addr,omitempty"`
    NwkSKey     *types.NwkSKey    `json:"nwk_s_key,omitempty"`
    AppSKey     *types.AppSKey    `json:"app_s_key,omitempty"`
    AppKey      *types.AppKey     `json:"app_key,omitempty"`
    Latitude    float32           `json:"latitude,omitempty"`
    Longitude   float32           `json:"longitude,omitempty"`
    Altitude    int32             `json:"altitude,omitempty"`
    Attributes  map[string]string `json:"attributes,omitempty"`
}

SparseDevice contains most, but not all fields of the device. It's returned by List operations to save server resources

func (*SparseDevice) AsDevice Uses

func (d *SparseDevice) AsDevice() *Device

AsDevice wraps the *SparseDevice and returns a *Device containing that sparse device

Package ttnsdk imports 23 packages (graph). Updated 2019-05-19. Refresh now. Tools for package owners.