Develop Drivers with Driver SDK

Last Updated on : 2024-04-12 05:48:20download

This topic describes how to use the tuya-edge-driver-sdk-go SDK provided by Tuya to develop drivers and serve edge devices. Before the driver development, we recommend that you deploy Tuya Edge Gateway (TEdge).

SDK description

The tuya-edge-driver-sdk-go SDK is used to develop southbound device services that are connected to TEdge. The SDK supports the following features:

  • Receives device control commands that are sent by the Tuya Developer Platform. The commands to read and write data are supported.
  • Asynchronously receives data of data points (DPs) that are sent by smart devices, and pushes the data to TEdge for subsequent data processing.
  • Synchronizes the metadata of sub-devices.
  • Processes simple scheduled device tasks. To enable advanced scheduled device tasks, go to the Scheduled Tasks page of Tuya Edge Gateway.

Environment requirements

Get the driver configuration template

You can connect to the locally deployed TEdge node at http://your-edge-ip:3000/driver/lib and download the driver configuration template.

Develop Drivers with Driver SDK

The following code block shows the configuration template:

// config.json
{
	"deviceServer": {
	"driver": {

		}
	},
	// Other configurations.
}

You can define specific configurations in the driver field in the key-value format. For example, to develop a device service based on Message Queuing Telemetry Transport (MQTT), you can configure the IP address, port, and quality of service (QoS) of the service.

The following sample code shows the configurations:

// config.json
{
	"deviceServer": {
	"driver": {
			"MQTTBrokerIP": "0.0.0.0",
			"MQTTBrokerPort": "1883"
		}
	},
	// Other configurations.
}

When the service is running, the application can get the driver configurations by using the func DriverConfigs() map[string]string method provided by the SDK.

Get the driver project configuration template

You must use device-service-template that is provided with the SDK to develop a required driver service. The following figure shows the project directories.

Develop Drivers with Driver SDK

The driver service code can be developed in the internal/driver directory. At the end of the development, set Dockerfile and Makefile based on actual conditions. Then, run make build to package the driver service into a Docker image.

Develop the driver service

Initialize the driver

In the main function, call the function func Bootstrap(serviceName string, serviceVersion string, driver interface{}) to initialize the SDK. The driver field includes the ProtocolDriver interface to be implemented in the SDK.

// main.go
package main

import (
	"device-service-template/internal/driver"

	"github.com/tuya/tuya-edge-driver-sdk-go"
	"github.com/tuya/tuya-edge-driver-sdk-go/pkg/startup"
)

const (
	serviceName string = "device-service-template"
)

func main() {
	sd := driver.SimpleDriver{}
	startup.Bootstrap(serviceName, device.Version, &sd)
}

Implement the driver interface

You must implement the ProtocolDriver interface to run multiple tasks. For example, the interfaces can be used to initialize the driver service, send commands to read from and write to devices, and add, modify, or delete device notifications. The following code block shows the definition of the interface:

type ProtocolDriver interface {
	Initialize(lc logger.LoggingClient, asyncCh chan<- *AsyncValues, deviceCh chan<- []DiscoveredDevice) error
	HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []CommandRequest) ([]*CommandValue, error)
	HandleWriteCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []CommandRequest, params []*CommandValue) error
	Stop(force bool) error
	AddDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error
	UpdateDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error
	RemoveDevice(deviceName string, protocols map[string]models.ProtocolProperties) error
}

The following functions are included:

  1. Initialize: initializes the driver. For example, the MQTT broker or HTTP server for connections to devices is initialized.

    Function:

    func (d *SimpleDriver) Initialize(lc logger.LoggingClient, asyncCh chan<- *AsyncValues, deviceCh chan<- []DiscoveredDevice) error`
    

    Sample code for Go:

    func (d *SimpleDriver) Initialize(lc logger.LoggingClient, asyncCh chan<- *dsModels.AsyncValues, deviceCh chan<- []dsModels.DiscoveredDevice) error {
    	d.lc = lc
    	d.asyncCh = asyncCh
    // code here
    // Initializes the service code.
    	return nil
    }
    
  2. HandleReadCommands: reads device data.

    Function:

    func (d *SimpleDriver) HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []CommandRequest) ([]*CommandValue, error)
    

    Sample code for Go:

    // This function is used to send read commands to the service code. You must call this function to read device data.
    func (d *SimpleDriver) HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []dsModels.CommandRequest) (res []*dsModels.CommandValue, err error) {
    	rd := d.retrieveRandomDevice(deviceName)
    
    	res = make([]*dsModels.CommandValue, len(reqs))
    	now := time.Now().UnixNano()
    
    	for i, req := range reqs {
    		t := req.Type
    		v, err := rd.value(t)
    		if err != nil {
    			return nil, err
    		}
    		var cv *dsModels.CommandValue
    		switch t {
    		case contracts.ValueTypeInt8:
    			cv, _ = dsModels.NewInt8Value(req.DeviceResourceName, now, int8(v))
    		case contracts.ValueTypeInt16:
    			cv, _ = dsModels.NewInt16Value(req.DeviceResourceName, now, int16(v))
    		case contracts.ValueTypeInt32:
    			cv, _ = dsModels.NewInt32Value(req.DeviceResourceName, now, int32(v))
    		}
    		res[i] = cv
    	}
    
    	return res, nil
    }
    

    Note:

    • The value of the protocols request parameter can vary based on the protocol that is selected when a sub-device is added. Example:
      In this topic, a standard Modbus device is added. The Modbus driver service initiates connections to the Modbus device. To add the device, you must provide the required parameters of the device. For other types of devices, whether to provide the device parameters depends on specific protocols.
    • After a control command is sent to the driver service, device parameters are stored to protocols when the SDK calls the current function. The driver uses these parameters to connect to the device and send the control command.
      • reqs: the request to send a device control command.
      • commandRequest.DeviceResourceName: the name of the device resource that is specified in the control request. This is also the DP that is defined in a things data model.
      • commandRequest.Type: the type of device resource to be controlled. commandRequest.Attributes: specific attributes of the device resource. For example, the following attributes can be specified for a Modbus device:
        To read the DP 15 for the Modbus device, the read DP code is set to 03, the starting address is set to 0, and the data length to be read is 1 byte. For more information, see the standard Modbus protocol.
    • The contracts package includes the data types that are supported by the SDK. You can call the functions in the models package of the SDK to convert data types.
  3. HandleWriteCommands: writes to the device.

    func (d *SimpleDriver) HandleWriteCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []CommandRequest, params []*CommandValue) error
    
    • params: the parameters of a write command to specify the data to be written.

    • params: Before the data is written to the device, the data type must be converted. You can call the functions in the models package of the SDK to convert data types.