├── sdk_info ├── .gitignore ├── go.mod ├── samples ├── file │ └── upload_file.go ├── connect │ └── connect_demo.go ├── bs │ └── bootstrap_demo.go ├── command │ └── platform_command.go ├── log │ └── log_samples.go ├── gateway │ └── gateway_update_status.go ├── incrube │ └── mqtt_client_demo.go ├── samples_utils.go ├── httpdevice │ └── http_device_samples.go ├── message │ └── msg.go ├── async │ └── message │ │ └── msg.go └── properties │ └── device_properties.go ├── info.go ├── info_test.go ├── gateway.go ├── device_test.go ├── util_test.go ├── util.go ├── http_client.go ├── async.go ├── go.sum ├── base_device_test.go ├── http_device.go ├── bootstrap.go ├── options.go ├── LICENSE ├── device.go ├── README.md ├── async_device.go └── base_device.go /sdk_info: -------------------------------------------------------------------------------- 1 | author = chen tong 2 | sdk-version = v2.0.0 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.idea 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | vendor/ 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ctlove0523/huaweicloud-iot-device-sdk-go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/eclipse/paho.mqtt.golang v1.3.0 7 | github.com/go-resty/resty/v2 v2.4.0 8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 9 | github.com/satori/go.uuid v1.2.0 10 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /samples/file/upload_file.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 5 | ) 6 | 7 | func main() { 8 | //创建一个设备并初始化 9 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 10 | device.Init() 11 | 12 | device.UploadFile("D/software/mqttfx/chentong.txt") 13 | device.DownloadFile("D/software/mqttfx/chentong.txt") 14 | } 15 | -------------------------------------------------------------------------------- /samples/connect/connect_demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 6 | ) 7 | 8 | func main() { 9 | device := samples.CreateDevice() 10 | 11 | initResult := device.Init() 12 | 13 | fmt.Printf("device init %v\n", initResult) 14 | 15 | fmt.Printf("device connected to server %v\n", device.IsConnected()) 16 | 17 | device.DisConnect() 18 | 19 | fmt.Printf("device connected to server %v\n", device.IsConnected()) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /samples/bs/bootstrap_demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | id := "611d13360ad1ed028658e089_zhou_sdk" 11 | pwd := "12345678901234567890" 12 | config := iot.DeviceConfig{ 13 | Id: id, 14 | Password: pwd, 15 | UseBootstrap: true, 16 | } 17 | device := iot.CreateIotDeviceWitConfig(config) 18 | initRes := device.Init() 19 | fmt.Println(initRes) 20 | 21 | time.Sleep(1 * time.Minute) 22 | } 23 | -------------------------------------------------------------------------------- /samples/command/platform_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | "time" 8 | ) 9 | 10 | // 处理平台下发的同步命令 11 | func main() { 12 | // 创建一个设备并初始化 13 | device := samples.CreateDevice() 14 | 15 | device.Init() 16 | 17 | // 添加用于处理平台下发命令的callback 18 | commandProcessResult := false 19 | device.AddCommandHandler(func(command iot.Command) (bool, interface{}) { 20 | fmt.Println("I get command from platform") 21 | commandProcessResult = true 22 | return true, map[string]interface{}{ 23 | "cost_time": 12, 24 | } 25 | }) 26 | time.Sleep(10 * time.Minute) 27 | } 28 | -------------------------------------------------------------------------------- /samples/log/log_samples.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | device := samples.CreateDevice() 13 | device.Init() 14 | var entries []iot.DeviceLogEntry 15 | 16 | for i := 0; i < 10; i++ { 17 | entry := iot.DeviceLogEntry{ 18 | Type: "DEVICE_MESSAGE", 19 | //Timestamp: iot.GetEventTimeStamp(), 20 | Content: "message hello " + strconv.Itoa(i), 21 | } 22 | entries = append(entries, entry) 23 | } 24 | 25 | for i := 0; i < 100; i++ { 26 | result := device.ReportLogs(entries) 27 | fmt.Println(result) 28 | 29 | } 30 | 31 | time.Sleep(1 * time.Minute) 32 | } 33 | -------------------------------------------------------------------------------- /samples/gateway/gateway_update_status.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | device := samples.CreateDevice() 12 | 13 | device.SetSubDevicesAddHandler(func(devices iot.SubDeviceInfo) { 14 | for _, info := range devices.Devices { 15 | fmt.Println("handle device add") 16 | fmt.Println(iot.Interface2JsonString(info)) 17 | } 18 | }) 19 | 20 | device.SetSubDevicesDeleteHandler(func(devices iot.SubDeviceInfo) { 21 | for _, info := range devices.Devices { 22 | fmt.Println("handle device delete") 23 | fmt.Println(iot.Interface2JsonString(info)) 24 | } 25 | }) 26 | 27 | device.Init() 28 | time.Sleep(200 * time.Second) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /info.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "bufio" 5 | "github.com/golang/glog" 6 | "io" 7 | "os" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | func OsName() string { 13 | return runtime.GOOS 14 | } 15 | 16 | func SdkInfo() map[string]string { 17 | f, err := os.Open("sdk_info") 18 | if err != nil { 19 | glog.Warning("read sdk info failed") 20 | return map[string]string{} 21 | } 22 | 23 | // 文件很小 24 | info := make(map[string]string) 25 | buf := bufio.NewReader(f) 26 | for { 27 | b, _, err := buf.ReadLine() 28 | if err != nil && err == io.EOF { 29 | glog.Warningf("read sdk info failed or end") 30 | break 31 | } 32 | line := string(b) 33 | if len(line) != 0 { 34 | parts := strings.Split(line, "=") 35 | info[strings.Trim(parts[0], " ")] = strings.Trim(parts[1], " ") 36 | } 37 | } 38 | 39 | return info 40 | } 41 | -------------------------------------------------------------------------------- /samples/incrube/mqtt_client_demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | mqtt "github.com/eclipse/paho.mqtt.golang" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ops := &mqtt.ClientOptions{} 11 | ops.AddBroker("tcp://localhost:8883") 12 | ops.SetClientID("test-republish") 13 | ops.SetUsername("test") 14 | ops.SetPassword("hello") 15 | ops.SetKeepAlive(5 * time.Second) 16 | //ops.SetPingTimeout(2 * time.Second) 17 | 18 | client := mqtt.NewClient(ops) 19 | token := client.Connect() 20 | token.Wait() 21 | 22 | client.Subscribe("test", 1, func(client mqtt.Client, message mqtt.Message) { 23 | fmt.Printf("get message %s\n", string(message.Payload())) 24 | message.Ack() 25 | }) 26 | 27 | for { 28 | time.Sleep(1 * time.Second) 29 | fmt.Println(time.Now().String()) 30 | fmt.Println(client.IsConnected()) 31 | } 32 | ch := make(chan struct{}) 33 | <-ch 34 | 35 | } 36 | -------------------------------------------------------------------------------- /samples/samples_utils.go: -------------------------------------------------------------------------------- 1 | package samples 2 | 3 | import iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 4 | 5 | const deviceId = "625ad023861486498f174c07_golang-sdk" 6 | const devicePassword = "31a9a9a247177daeb6ac9462ac03b700" 7 | const Server = "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883" 8 | 9 | func CreateAsyncDevice() iot.AsyncDevice { 10 | device := iot.CreateAsyncIotDevice(deviceId, devicePassword, Server) 11 | 12 | return device 13 | } 14 | 15 | func CreateDevice() iot.Device { 16 | device := iot.CreateIotDevice(deviceId, devicePassword, Server) 17 | 18 | return device 19 | } 20 | 21 | func CreateHttpDevice() iot.HttpDevice { 22 | config := iot.HttpDeviceConfig{ 23 | Id: deviceId, 24 | Password: devicePassword, 25 | Server: "https://iot-mqtts.cn-north-4.myhuaweicloud.com:443", 26 | MaxConnsPerHost: 2, 27 | MaxIdleConns: 0, 28 | } 29 | return iot.CreateHttpDevice(config) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /samples/httpdevice/http_device_samples.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | ) 8 | 9 | func main() { 10 | httpDevice := samples.CreateHttpDevice() 11 | 12 | s := SelfHttpResponse{ 13 | HttpCode: 404, 14 | HttpMessage: "test http device", 15 | ReportTime: iot.GetEventTimeStamp(), 16 | } 17 | entry := iot.DevicePropertyEntry{ 18 | ServiceId: "http_api", 19 | Properties: s, 20 | EventTime: iot.GetEventTimeStamp(), 21 | } 22 | 23 | var entries []iot.DevicePropertyEntry 24 | entries = append(entries, entry) 25 | properties := iot.DeviceProperties{ 26 | Services: entries, 27 | } 28 | 29 | fmt.Println(httpDevice.ReportProperties(properties)) 30 | } 31 | 32 | type SelfHttpResponse struct { 33 | HttpCode int `json:"http_code"` 34 | HttpMessage string `json:"http_message"` 35 | ReportTime string `json:"report_time"` 36 | } 37 | -------------------------------------------------------------------------------- /info_test.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | // 该测试用力仅能在Windows系统运行 13 | func TestOsName(t *testing.T) { 14 | if !strings.Contains(OsName(), "windows") { 15 | t.Errorf(`OsName must be windwos`) 16 | } 17 | } 18 | 19 | func TestVersion(t *testing.T) { 20 | if SdkInfo()["sdk-version"] != "v2.0.0" { 21 | t.Errorf("sdk version must be v0.0.2") 22 | } 23 | 24 | if SdkInfo()["author"] != "chen tong" { 25 | t.Errorf("sdk author must be chen tong") 26 | } 27 | } 28 | 29 | func TestCreateFileUploadResultResponse(t *testing.T) { 30 | f, err := os.Open("sdk_info") 31 | if err != nil { 32 | fmt.Println(err.Error()) 33 | } 34 | 35 | //建立缓冲区,把文件内容放到缓冲区中 36 | buf := bufio.NewReader(f) 37 | for { 38 | //遇到\n结束读取 39 | b, errR := buf.ReadBytes('\n') 40 | if errR != nil { 41 | if errR == io.EOF { 42 | break 43 | } 44 | fmt.Println(errR.Error()) 45 | } 46 | fmt.Println(string(b)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gateway.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | type baseGateway interface { 4 | // 设置平台添加子设备回调函数 5 | SetSubDevicesAddHandler(handler SubDevicesAddHandler) 6 | 7 | // 设置平台删除子设备回调函数 8 | SetSubDevicesDeleteHandler(handler SubDevicesDeleteHandler) 9 | } 10 | 11 | type Gateway interface { 12 | baseGateway 13 | 14 | // 网关更新子设备状态 15 | UpdateSubDeviceState(subDevicesStatus SubDevicesStatus) bool 16 | 17 | // 网关删除子设备 18 | DeleteSubDevices(deviceIds []string) bool 19 | 20 | // 网关添加子设备 21 | AddSubDevices(deviceInfos []DeviceInfo) bool 22 | 23 | // 网关同步子设备列表,默认实现不指定版本 24 | SyncAllVersionSubDevices() 25 | 26 | // 网关同步特定版本子设备列表 27 | SyncSubDevices(version int) 28 | } 29 | 30 | type AsyncGateway interface { 31 | baseGateway 32 | 33 | // 网关更新子设备状态 34 | UpdateSubDeviceState(subDevicesStatus SubDevicesStatus) AsyncResult 35 | 36 | // 网关删除子设备 37 | DeleteSubDevices(deviceIds []string) AsyncResult 38 | 39 | // 网关添加子设备 40 | AddSubDevices(deviceInfos []DeviceInfo) AsyncResult 41 | 42 | // 网关同步子设备列表,默认实现不指定版本 43 | SyncAllVersionSubDevices() AsyncResult 44 | 45 | // 网关同步特定版本子设备列表 46 | SyncSubDevices(version int) AsyncResult 47 | } 48 | -------------------------------------------------------------------------------- /samples/message/msg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | uuid "github.com/satori/go.uuid" 8 | ) 9 | 10 | func main() { 11 | // 创建一个设备并初始化 12 | device := samples.CreateDevice() 13 | device.Init() 14 | 15 | // 注册平台下发消息的callback,当收到平台下发的消息时,调用此callback. 16 | // 支持注册多个callback,并且按照注册顺序调用 17 | device.AddMessageHandler(func(message iot.Message) bool { 18 | fmt.Println("first handler called" + iot.Interface2JsonString(message)) 19 | return true 20 | }) 21 | 22 | device.AddMessageHandler(func(message iot.Message) bool { 23 | fmt.Println("second handler called" + iot.Interface2JsonString(message)) 24 | return true 25 | }) 26 | 27 | //向平台发送消息 28 | message := iot.Message{ 29 | ObjectDeviceId: uuid.NewV4().String(), 30 | Name: "Fist send message to platform", 31 | Id: uuid.NewV4().String(), 32 | Content: "Hello Huawei IoT Platform", 33 | } 34 | 35 | for i := 0; i < 100; i++ { 36 | sendMsgResult := device.SendMessage(message) 37 | fmt.Printf("send message %v", sendMsgResult) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /samples/async/message/msg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | // 创建一个设备并初始化 12 | device := iot.CreateAsyncIotDevice("5fdb75cccbfe2f02ce81d4bf_liqian", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 13 | 14 | device.Init() 15 | 16 | // 注册平台下发消息的callback,当收到平台下发的消息时,调用此callback. 17 | // 支持注册多个callback,并且按照注册顺序调用 18 | device.AddMessageHandler(func(message iot.Message) bool { 19 | fmt.Println("first handler called" + iot.Interface2JsonString(message)) 20 | return true 21 | }) 22 | 23 | device.AddMessageHandler(func(message iot.Message) bool { 24 | fmt.Println("second handler called" + iot.Interface2JsonString(message)) 25 | return true 26 | }) 27 | 28 | //向平台发送消息 29 | message := iot.Message{ 30 | ObjectDeviceId: uuid.NewV4().String(), 31 | Name: "Fist send message to platform", 32 | Id: uuid.NewV4().String(), 33 | Content: "Hello Huawei IoT Platform", 34 | } 35 | asyncResult := device.SendMessage(message) 36 | if asyncResult.Wait() && asyncResult.Error() != nil { 37 | fmt.Println("async send message failed") 38 | } else { 39 | fmt.Println("async send message success") 40 | } 41 | time.Sleep(2 * time.Minute) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /device_test.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | uuid "github.com/satori/go.uuid" 5 | "testing" 6 | ) 7 | 8 | func TestIotDevice_SendMessage(t *testing.T) { 9 | device := createIotDevice() 10 | device.Init() 11 | 12 | message := Message{ 13 | ObjectDeviceId: uuid.NewV4().String(), 14 | Name: "Fist send message to platform", 15 | Id: uuid.NewV4().String(), 16 | Content: "Hello Huawei IoT Platform", 17 | } 18 | if !device.SendMessage(message) { 19 | t.Errorf("device send message failed") 20 | } 21 | } 22 | 23 | func TestIotDevice_ReportProperties(t *testing.T) { 24 | device := createIotDevice() 25 | device.Init() 26 | 27 | props := DevicePropertyEntry{ 28 | ServiceId: "value", 29 | EventTime: GetEventTimeStamp(), 30 | Properties: struct { 31 | Value string `json:"value"` 32 | MsgType string `json:"msgType"` 33 | }{ 34 | Value: "Test Report", 35 | MsgType: "123", 36 | }, 37 | } 38 | 39 | var content []DevicePropertyEntry 40 | content = append(content, props) 41 | services := DeviceProperties{ 42 | Services: content, 43 | } 44 | 45 | reportResult := device.ReportProperties(services) 46 | if !reportResult { 47 | t.Error("device report property failed") 48 | } 49 | } 50 | 51 | func createIotDevice() Device { 52 | return CreateIotDevice(deviceId, devicePwd, server) 53 | } 54 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTimeStamp(t *testing.T) { 8 | timeStamp := timeStamp() 9 | if len(timeStamp) != 10 { 10 | t.Error(`Time Stamp length must be 10`) 11 | } 12 | } 13 | 14 | func TestDataCollectionTime(t *testing.T) { 15 | if len(GetEventTimeStamp()) != 16 { 16 | t.Errorf(`Data Collection Time length must be 16,but is %d`, len(GetEventTimeStamp())) 17 | } 18 | } 19 | 20 | func TestHmacSha256(t *testing.T) { 21 | encodedPassword := "c0fefa1341fb0647290e93f641a9bcea74cd32111668cdc5f7418553640a55cc" 22 | if hmacSha256("123456789", "202012222200") != encodedPassword { 23 | t.Errorf("encoded password must be %s but is %s", encodedPassword, hmacSha256("123456789", "202012222200")) 24 | } 25 | } 26 | 27 | func TestInterface2JsonString(t *testing.T) { 28 | if Interface2JsonString(nil) != "" { 29 | t.Errorf("nill interface to json string must empty") 30 | } 31 | } 32 | 33 | func TestGetTopicRequestId(t *testing.T) { 34 | topic := "$os/device/down/request=123456789" 35 | if getTopicRequestId(topic) != "123456789" { 36 | t.Errorf("topic request id must be %s", "123456789") 37 | } 38 | } 39 | 40 | func TestFormatTopic(t *testing.T) { 41 | topic := "$os/device/{device_id}/up" 42 | deviceId := "123" 43 | formatTopicName := "$os/device/123/up" 44 | if formatTopicName != formatTopic(topic, deviceId) { 45 | t.Errorf("formated topic must be %s", formatTopicName) 46 | } 47 | 48 | } 49 | 50 | // 仅适用于windows系统 51 | func TestSmartFileName(t *testing.T) { 52 | fileName := "D/go/sdk/test.log" 53 | name := "D:\\go\\sdk\\test.log" 54 | 55 | if name != smartFileName(fileName) { 56 | t.Errorf("in windows file system,smart file name must be %s", name) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // 时间戳:为设备连接平台时的UTC时间,格式为YYYYMMDDHH,如UTC 时间2018/7/24 17:56:20 则应表示为2018072417。 13 | func timeStamp() string { 14 | strFormatTime := time.Now().Format("2006-01-02 15:04:05") 15 | strFormatTime = strings.ReplaceAll(strFormatTime, "-", "") 16 | strFormatTime = strings.ReplaceAll(strFormatTime, " ", "") 17 | strFormatTime = strFormatTime[0:10] 18 | return strFormatTime 19 | } 20 | 21 | // 设备采集数据UTC时间(格式:yyyyMMdd'T'HHmmss'Z'),如:20161219T114920Z。 22 | //设备上报数据不带该参数或参数格式错误时,则数据上报时间以平台时间为准。 23 | func GetEventTimeStamp() string { 24 | now := time.Now().UTC() 25 | return now.Format("20060102T150405Z") 26 | } 27 | 28 | func hmacSha256(data string, secret string) string { 29 | h := hmac.New(sha256.New, []byte(secret)) 30 | h.Write([]byte(data)) 31 | return hex.EncodeToString(h.Sum(nil)) 32 | } 33 | 34 | func Interface2JsonString(v interface{}) string { 35 | if v == nil { 36 | return "" 37 | } 38 | byteData, err := json.Marshal(v) 39 | if err != nil { 40 | return "" 41 | } 42 | return string(byteData) 43 | } 44 | 45 | func getTopicRequestId(topic string) string { 46 | return strings.Split(topic, "=")[1] 47 | } 48 | 49 | func formatTopic(topic, deviceId string) string { 50 | return strings.ReplaceAll(topic, "{device_id}", deviceId) 51 | } 52 | 53 | // 根据当前运行的操作系统重新修改文件路径以适配操作系统 54 | func smartFileName(filename string) string { 55 | // Windows操作系统适配 56 | if strings.Contains(OsName(), "windows") { 57 | pathParts := strings.Split(filename, "/") 58 | pathParts[0] = pathParts[0] + ":" 59 | return strings.Join(pathParts, "\\") 60 | } 61 | 62 | return filename 63 | } 64 | 65 | func CreateMqttClientId(deviceId string) string { 66 | segments := make([]string, 4) 67 | segments[0] = deviceId 68 | segments[1] = "0" 69 | segments[2] = "0" 70 | segments[3] = timeStamp() 71 | 72 | return strings.Join(segments, "_") 73 | } 74 | -------------------------------------------------------------------------------- /http_client.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "github.com/go-resty/resty/v2" 5 | "github.com/golang/glog" 6 | "io/ioutil" 7 | "net/url" 8 | "os" 9 | ) 10 | 11 | // 仅用于设备上传文件 12 | type HttpClient interface { 13 | UploadFile(filename, uri string) bool 14 | DownloadFile(filename, uri string) bool 15 | } 16 | 17 | type httpClient struct { 18 | client *resty.Client 19 | } 20 | 21 | func (client *httpClient) DownloadFile(fileName, downloadUrl string) bool { 22 | glog.Infof("begin to download file %s, url = %s", fileName, downloadUrl) 23 | fileName = smartFileName(fileName) 24 | out, err := os.Create(fileName) 25 | if err != nil { 26 | glog.Errorf("create file in os failed ,file name %s", fileName) 27 | return false 28 | } 29 | 30 | originalUri, err := url.ParseRequestURI(downloadUrl) 31 | if err != nil { 32 | glog.Errorf("parse request uri failed %v", err) 33 | return false 34 | } 35 | 36 | resp, err := client.client.R(). 37 | SetHeader("Content-Type", "text/plain"). 38 | SetHeader("Host", originalUri.Host). 39 | Get(downloadUrl) 40 | 41 | if err != nil { 42 | glog.Errorf("download file request failed %v", err) 43 | return false 44 | } 45 | 46 | _, err = out.Write(resp.Body()) 47 | if err != nil { 48 | glog.Errorf("write file failed") 49 | return false 50 | } 51 | 52 | return true 53 | } 54 | 55 | func (client *httpClient) UploadFile(filename, uri string) bool { 56 | filename = smartFileName(filename) 57 | fileBytes, err := ioutil.ReadFile(filename) 58 | 59 | if err != nil { 60 | glog.Errorf("read file failed %v", err) 61 | return false 62 | } 63 | 64 | originalUri, err := url.ParseRequestURI(uri) 65 | if err != nil { 66 | glog.Errorf("parse request uri failed %v", err) 67 | return false 68 | } 69 | 70 | resp, err := client.client.R(). 71 | SetHeader("Content-Type", "text/plain"). 72 | SetHeader("Host", originalUri.Host). 73 | SetBody(fileBytes). 74 | Put(uri) 75 | 76 | if err != nil { 77 | glog.Errorf("upload request failed %v", err) 78 | } 79 | 80 | return resp.StatusCode() == 200 81 | } 82 | 83 | func CreateHttpClient() HttpClient { 84 | client := resty.New() 85 | 86 | client.SetRetryCount(3) 87 | 88 | httpClient := &httpClient{ 89 | client: client, 90 | } 91 | 92 | return httpClient 93 | 94 | } 95 | -------------------------------------------------------------------------------- /async.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type DeviceError struct { 9 | errorMsg string 10 | } 11 | 12 | func (err *DeviceError) Error() string { 13 | return err.errorMsg 14 | } 15 | 16 | type AsyncResult interface { 17 | Wait() bool 18 | 19 | WaitTimeout(time.Duration) bool 20 | 21 | Done() <-chan struct{} 22 | 23 | Error() error 24 | } 25 | 26 | type baseAsyncResult struct { 27 | m sync.RWMutex 28 | complete chan struct{} 29 | err error 30 | } 31 | 32 | // Wait implements the Token Wait method. 33 | func (b *baseAsyncResult) Wait() bool { 34 | <-b.complete 35 | return true 36 | } 37 | 38 | // WaitTimeout implements the Token WaitTimeout method. 39 | func (b *baseAsyncResult) WaitTimeout(d time.Duration) bool { 40 | timer := time.NewTimer(d) 41 | select { 42 | case <-b.complete: 43 | if !timer.Stop() { 44 | <-timer.C 45 | } 46 | return true 47 | case <-timer.C: 48 | } 49 | 50 | return false 51 | } 52 | 53 | // Done implements the Token Done method. 54 | func (b *baseAsyncResult) Done() <-chan struct{} { 55 | return b.complete 56 | } 57 | 58 | func (b *baseAsyncResult) flowComplete() { 59 | select { 60 | case <-b.complete: 61 | default: 62 | close(b.complete) 63 | } 64 | } 65 | 66 | func (b *baseAsyncResult) Error() error { 67 | b.m.RLock() 68 | defer b.m.RUnlock() 69 | return b.err 70 | } 71 | 72 | func (b *baseAsyncResult) setError(e error) { 73 | b.m.Lock() 74 | b.err = e 75 | b.flowComplete() 76 | b.m.Unlock() 77 | } 78 | 79 | type BooleanAsyncResult struct { 80 | baseAsyncResult 81 | } 82 | 83 | func (bar *BooleanAsyncResult) Result() bool { 84 | bar.m.RLock() 85 | defer bar.m.RUnlock() 86 | 87 | return bar.err == nil 88 | } 89 | 90 | func (bar *BooleanAsyncResult) completeSuccess() { 91 | bar.m.RLock() 92 | defer bar.m.RUnlock() 93 | bar.err = nil 94 | bar.complete <- struct{}{} 95 | } 96 | 97 | func (bar *BooleanAsyncResult) completeError(err error) { 98 | bar.m.RLock() 99 | defer bar.m.RUnlock() 100 | bar.err = err 101 | bar.complete <- struct{}{} 102 | } 103 | 104 | func NewBooleanAsyncResult() *BooleanAsyncResult { 105 | asyncResult := &BooleanAsyncResult{ 106 | baseAsyncResult: baseAsyncResult{ 107 | complete: make(chan struct{}), 108 | }, 109 | } 110 | 111 | return asyncResult 112 | } 113 | -------------------------------------------------------------------------------- /samples/properties/device_properties.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 6 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go/samples" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | // 创建设备并初始化 12 | device := samples.CreateDevice() 13 | device.Init() 14 | fmt.Printf("device connected: %v\n", device.IsConnected()) 15 | 16 | // 注册平台设置属性callback,当应用通过API设置设备属性时,会调用此callback,支持注册多个callback 17 | device.AddPropertiesSetHandler(func(propertiesSetRequest iot.DevicePropertyDownRequest) bool { 18 | fmt.Println("I get property set command") 19 | fmt.Printf("request is %s", iot.Interface2JsonString(propertiesSetRequest)) 20 | return true 21 | }) 22 | 23 | // 注册平台查询设备属性callback,当平台查询设备属性时此callback被调用,仅支持设置一个callback 24 | device.SetPropertyQueryHandler(func(query iot.DevicePropertyQueryRequest) iot.DevicePropertyEntry { 25 | return iot.DevicePropertyEntry{ 26 | ServiceId: "value", 27 | Properties: DemoProperties{ 28 | Value: "QUERY RESPONSE", 29 | MsgType: "query property", 30 | }, 31 | EventTime: "2020-12-19 02:23:24", 32 | } 33 | }) 34 | 35 | // 设备上报属性 36 | props := iot.DevicePropertyEntry{ 37 | ServiceId: "value", 38 | EventTime: iot.GetEventTimeStamp(), 39 | Properties: DemoProperties{ 40 | Value: "Test Code", 41 | MsgType: "34", 42 | }, 43 | } 44 | 45 | var content []iot.DevicePropertyEntry 46 | content = append(content, props) 47 | services := iot.DeviceProperties{ 48 | Services: content, 49 | } 50 | device.ReportProperties(services) 51 | 52 | // 设备查询设备影子数据 53 | device.QueryDeviceShadow(iot.DevicePropertyQueryRequest{ 54 | ServiceId: "value", 55 | }, func(response iot.DevicePropertyQueryResponse) { 56 | fmt.Printf("query device shadow success.\n,device shadow data is %s\n", iot.Interface2JsonString(response)) 57 | }) 58 | 59 | // 批量上报子设备属性 60 | subDevice1 := iot.DeviceService{ 61 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-1", 62 | Services: content, 63 | } 64 | subDevice2 := iot.DeviceService{ 65 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-2", 66 | Services: content, 67 | } 68 | 69 | subDevice3 := iot.DeviceService{ 70 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-3", 71 | Services: content, 72 | } 73 | 74 | var devices []iot.DeviceService 75 | devices = append(devices, subDevice1, subDevice2, subDevice3) 76 | 77 | device.BatchReportSubDevicesProperties(iot.DevicesService{ 78 | Devices: devices, 79 | }) 80 | time.Sleep(1 * time.Minute) 81 | } 82 | 83 | type DemoProperties struct { 84 | Value string `json:"value"` 85 | MsgType string `json:"msgType"` 86 | } 87 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/eclipse/paho.mqtt.golang v1.3.0 h1:MU79lqr3FKNKbSrGN7d7bNYqh8MwWW7Zcx0iG+VIw9I= 2 | github.com/eclipse/paho.mqtt.golang v1.3.0/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= 3 | github.com/go-resty/resty/v2 v2.4.0 h1:s6TItTLejEI+2mn98oijC5w/Rk2YU+OA6x0mnZN6r6k= 4 | github.com/go-resty/resty/v2 v2.4.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= 5 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 6 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 7 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 8 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 9 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 10 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 15 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 16 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 17 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= 18 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 19 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= 20 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 22 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 23 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 27 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 28 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 29 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 30 | -------------------------------------------------------------------------------- /base_device_test.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | const deviceId = "611d13360ad1ed028658e089_device_cli" 8 | const devicePwd = "123456789" 9 | const server = "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883" 10 | const qos = 1 11 | 12 | func TestBaseIotDevice_Init(t *testing.T) { 13 | device := createBaseIotDevice() 14 | 15 | result := device.Init() 16 | 17 | if !result { 18 | t.Errorf("device init failed") 19 | } 20 | } 21 | 22 | func TestBaseIotDevice_IsConnected(t *testing.T) { 23 | device := createBaseIotDevice() 24 | device.Init() 25 | 26 | if !device.IsConnected() { 27 | t.Errorf("device connecte to server failed") 28 | } 29 | } 30 | 31 | func TestBaseIotDevice_DisConnect(t *testing.T) { 32 | device := createBaseIotDevice() 33 | device.Init() 34 | device.DisConnect() 35 | 36 | if device.IsConnected() { 37 | t.Errorf("device disconnect to server failed") 38 | } 39 | } 40 | 41 | func TestBaseIotDevice_AddMessageHandler(t *testing.T) { 42 | device := createBaseIotDevice() 43 | 44 | device.AddMessageHandler(func(message Message) bool { 45 | return true 46 | }) 47 | 48 | if len(device.messageHandlers) == 0 { 49 | t.Errorf("add message handler failed") 50 | } 51 | } 52 | 53 | func TestBaseIotDevice_AddCommandHandler(t *testing.T) { 54 | device := createBaseIotDevice() 55 | 56 | device.AddCommandHandler(func(command Command) bool { 57 | return true 58 | }) 59 | 60 | if len(device.commandHandler) == 0 { 61 | t.Errorf("add command handlers failed") 62 | } 63 | } 64 | 65 | func TestBaseIotDevice_AddPropertiesSetHandler(t *testing.T) { 66 | device := createBaseIotDevice() 67 | 68 | device.AddPropertiesSetHandler(func(message DevicePropertyDownRequest) bool { 69 | return true 70 | }) 71 | 72 | if len(device.propertiesSetHandlers) == 0 { 73 | t.Errorf("add properties handler failed") 74 | } 75 | } 76 | 77 | func TestBaseIotDevice_SetPropertyQueryHandler(t *testing.T) { 78 | device := createBaseIotDevice() 79 | 80 | device.SetPropertyQueryHandler(func(query DevicePropertyQueryRequest) DevicePropertyEntry { 81 | return DevicePropertyEntry{} 82 | }) 83 | 84 | if device.propertyQueryHandler == nil { 85 | t.Errorf("set property query handler failed") 86 | } 87 | } 88 | 89 | func TestBaseIotDevice_SetSwFwVersionReporter(t *testing.T) { 90 | device := createBaseIotDevice() 91 | 92 | device.SetSwFwVersionReporter(func() (string, string) { 93 | return "1.0", "2.0" 94 | }) 95 | 96 | if device.swFwVersionReporter == nil { 97 | t.Errorf("set sw fw version reporter failed") 98 | } 99 | 100 | } 101 | 102 | func TestBaseIotDevice_SetDeviceUpgradeHandler(t *testing.T) { 103 | device := createBaseIotDevice() 104 | 105 | device.SetDeviceUpgradeHandler(func(upgradeType byte, info UpgradeInfo) UpgradeProgress { 106 | return UpgradeProgress{} 107 | }) 108 | 109 | if device.deviceUpgradeHandler == nil { 110 | t.Errorf("set device upgrade handler failed") 111 | } 112 | } 113 | 114 | func createBaseIotDevice() baseIotDevice { 115 | device := baseIotDevice{} 116 | device.Id = deviceId 117 | device.Password = devicePwd 118 | device.Servers = server 119 | device.messageHandlers = []MessageHandler{} 120 | device.commandHandler = []CommandHandler{} 121 | 122 | device.fileUrls = map[string]string{} 123 | 124 | device.qos = qos 125 | device.batchSubDeviceSize = 10 126 | 127 | return device 128 | } 129 | -------------------------------------------------------------------------------- /http_device.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/go-resty/resty/v2" 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // 使用HTTP协议的设备,当前使用HTTP协议的设备只支持上报消息和上报属性 14 | type HttpDevice interface { 15 | 16 | // 上报消息 17 | SendMessage(message Message) bool 18 | 19 | // 上报属性 20 | ReportProperties(properties DeviceProperties) bool 21 | } 22 | 23 | type restyHttpDevice struct { 24 | Id string 25 | Password string 26 | Servers string 27 | client *resty.Client 28 | 29 | lock sync.RWMutex 30 | accessToken string 31 | } 32 | 33 | func (device *restyHttpDevice) SendMessage(message Message) bool { 34 | resp, err := device.client.R(). 35 | SetBody(message). 36 | Post(fmt.Sprintf("%s/v5/devices/%s/sys/messages/up", device.Servers, device.Id)) 37 | if err != nil { 38 | fmt.Printf("send message failed %s\n", err) 39 | } 40 | return err == nil && resp.StatusCode() == http.StatusOK 41 | } 42 | 43 | func (device *restyHttpDevice) ReportProperties(properties DeviceProperties) bool { 44 | response, err := device.client.R(). 45 | SetBody(properties). 46 | Post(fmt.Sprintf("%s/v5/devices/%s/sys/properties/report", device.Servers, device.Id)) 47 | if err != nil { 48 | fmt.Printf("report properties failed %s\n", err) 49 | } 50 | return err == nil && response.StatusCode() == http.StatusOK 51 | } 52 | 53 | func (device *restyHttpDevice) init() { 54 | accessTokenBody := accessTokenRequest{ 55 | DeviceId: device.Id, 56 | SignType: 0, 57 | Timestamp: "2019120219", 58 | Password: hmacSha256(device.Password, "2019120219"), 59 | } 60 | 61 | response, err := device.client.R(). 62 | SetBody(accessTokenBody). 63 | Post(fmt.Sprintf("%s%s", device.Servers, "/v5/device-auth")) 64 | if err != nil { 65 | fmt.Printf("get device access token failed %s\n", err) 66 | return 67 | } 68 | 69 | tokenResponse := &accessTokenResponse{} 70 | err = json.Unmarshal(response.Body(), tokenResponse) 71 | if err != nil { 72 | fmt.Printf("json unmarshal failed %v", err) 73 | return 74 | } 75 | 76 | device.lock.Lock() 77 | device.accessToken = tokenResponse.AccessToken 78 | device.lock.Unlock() 79 | } 80 | 81 | type accessTokenResponse struct { 82 | AccessToken string `json:"access_token"` 83 | } 84 | 85 | type accessTokenRequest struct { 86 | DeviceId string `json:"device_id"` 87 | SignType int `json:"sign_type"` 88 | Timestamp string `json:"timestamp"` 89 | Password string `json:"password"` 90 | } 91 | 92 | func CreateHttpDevice(config HttpDeviceConfig) HttpDevice { 93 | c := resty.New() 94 | c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) 95 | c.SetTimeout(30 * time.Second) 96 | c.SetRetryCount(3) 97 | c.SetRetryWaitTime(10 * time.Second) 98 | c.AddRetryCondition(func(response *resty.Response, err error) bool { 99 | return response.StatusCode() == http.StatusForbidden 100 | }) 101 | 102 | connsPerHost := 10 103 | if config.MaxConnsPerHost != 0 { 104 | connsPerHost = config.MaxConnsPerHost 105 | } 106 | c.SetTransport(&http.Transport{ 107 | MaxConnsPerHost: connsPerHost, 108 | }) 109 | 110 | device := &restyHttpDevice{ 111 | Id: config.Id, 112 | Password: config.Password, 113 | Servers: config.Server, 114 | client: c, 115 | lock: sync.RWMutex{}, 116 | } 117 | 118 | device.init() 119 | device.client.OnBeforeRequest(func(client *resty.Client, request *resty.Request) error { 120 | device.lock.RLock() 121 | request.SetHeader("access_token", device.accessToken) 122 | device.lock.RUnlock() 123 | request.SetHeader("Content-Type", "application/json") 124 | return nil 125 | }) 126 | device.client.OnAfterResponse(func(client *resty.Client, response *resty.Response) error { 127 | if response.StatusCode() == http.StatusForbidden { 128 | device.init() 129 | } 130 | return nil 131 | }) 132 | 133 | return device 134 | } 135 | 136 | type HttpDeviceConfig struct { 137 | Id string 138 | Password string 139 | Server string // https://iot-mqtts.cn-north-4.myhuaweicloud.com:443 140 | MaxConnsPerHost int 141 | MaxIdleConns int 142 | } 143 | -------------------------------------------------------------------------------- /bootstrap.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/json" 7 | "fmt" 8 | mqtt "github.com/eclipse/paho.mqtt.golang" 9 | "github.com/golang/glog" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const ( 15 | bsServer = "tls://iot-bs.cn-north-4.myhuaweicloud.com:8883" 16 | bsServerCa = "-----BEGIN CERTIFICATE-----\n" + 17 | "MIIETjCCAzagAwIBAgINAe5fIh38YjvUMzqFVzANBgkqhkiG9w0BAQsFADBMMSAw\n" + 18 | "HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFs\n" + 19 | "U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xODExMjEwMDAwMDBaFw0yODEx\n" + 20 | "MjEwMDAwMDBaMFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52\n" + 21 | "LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODCCASIw\n" + 22 | "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdaydUMGCEAI9WXD+uu3Vxoa2uP\n" + 23 | "UGATeoHLl+6OimGUSyZ59gSnKvuk2la77qCk8HuKf1UfR5NhDW5xUTolJAgvjOH3\n" + 24 | "idaSz6+zpz8w7bXfIa7+9UQX/dhj2S/TgVprX9NHsKzyqzskeU8fxy7quRU6fBhM\n" + 25 | "abO1IFkJXinDY+YuRluqlJBJDrnw9UqhCS98NE3QvADFBlV5Bs6i0BDxSEPouVq1\n" + 26 | "lVW9MdIbPYa+oewNEtssmSStR8JvA+Z6cLVwzM0nLKWMjsIYPJLJLnNvBhBWk0Cq\n" + 27 | "o8VS++XFBdZpaFwGue5RieGKDkFNm5KQConpFmvv73W+eka440eKHRwup08CAwEA\n" + 28 | "AaOCASkwggElMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G\n" + 29 | "A1UdDgQWBBT473/yzXhnqN5vjySNiPGHAwKz6zAfBgNVHSMEGDAWgBSP8Et/qC5F\n" + 30 | "JK5NUPpjmove4t0bvDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6\n" + 31 | "Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9yb290cjMwNgYDVR0fBC8wLTAroCmgJ4Yl\n" + 32 | "aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9yb290LXIzLmNybDBHBgNVHSAEQDA+\n" + 33 | "MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5j\n" + 34 | "b20vcmVwb3NpdG9yeS8wDQYJKoZIhvcNAQELBQADggEBAJmQyC1fQorUC2bbmANz\n" + 35 | "EdSIhlIoU4r7rd/9c446ZwTbw1MUcBQJfMPg+NccmBqixD7b6QDjynCy8SIwIVbb\n" + 36 | "0615XoFYC20UgDX1b10d65pHBf9ZjQCxQNqQmJYaumxtf4z1s4DfjGRzNpZ5eWl0\n" + 37 | "6r/4ngGPoJVpjemEuunl1Ig423g7mNA2eymw0lIYkN5SQwCuaifIFJ6GlazhgDEw\n" + 38 | "fpolu4usBCOmmQDo8dIm7A9+O4orkjgTHY+GzYZSR+Y0fFukAj6KYXwidlNalFMz\n" + 39 | "hriSqHKvoflShx8xpfywgVcvzfTO3PYkz6fiNJBonf6q8amaEsybwMbDqKWwIX7eSPY=\n" + 40 | "-----END CERTIFICATE-----" 41 | ) 42 | 43 | type BootstrapClient interface { 44 | Boot() string 45 | Close() 46 | } 47 | 48 | func NewBootstrapClient(id, password string) (BootstrapClient, error) { 49 | client := &bsClient{ 50 | id: id, 51 | password: password, 52 | iotdaServer: newResult(), 53 | } 54 | 55 | res, err := client.init() 56 | if res { 57 | return client, nil 58 | } 59 | 60 | return nil, err 61 | } 62 | 63 | type bsClient struct { 64 | id string 65 | password string 66 | client mqtt.Client // 使用的MQTT客户端 67 | iotdaServer *Result // 设备接入平台地址 68 | } 69 | 70 | func (bs *bsClient) init() (bool, error) { 71 | options := mqtt.NewClientOptions() 72 | options.AddBroker(bsServer) 73 | options.SetClientID(CreateMqttClientId(bs.id)) 74 | options.SetUsername(bs.id) 75 | options.SetPassword(hmacSha256(bs.password, timeStamp())) 76 | options.SetKeepAlive(250 * time.Second) 77 | options.SetAutoReconnect(true) 78 | options.SetConnectRetry(true) 79 | options.SetConnectTimeout(2 * time.Second) 80 | 81 | ca := []byte(bsServerCa) 82 | serverCaPool := x509.NewCertPool() 83 | serverCaPool.AppendCertsFromPEM(ca) 84 | 85 | tlsConfig := &tls.Config{ 86 | RootCAs: serverCaPool, 87 | InsecureSkipVerify: true, 88 | MaxVersion: tls.VersionTLS12, 89 | MinVersion: tls.VersionTLS12, 90 | } 91 | options.SetTLSConfig(tlsConfig) 92 | 93 | bs.client = mqtt.NewClient(options) 94 | if token := bs.client.Connect(); token.Wait() && token.Error() != nil { 95 | glog.Warningf("device %s create bootstrap client failed,error = %v", bs.id, token.Error()) 96 | return false, token.Error() 97 | } 98 | 99 | downTopic := fmt.Sprintf("$oc/devices/%s/sys/bootstrap/down", bs.id) 100 | subRes := bs.client.Subscribe(downTopic, 0, func(client mqtt.Client, message mqtt.Message) { 101 | go func() { 102 | fmt.Println("get message from bs server") 103 | serverResponse := &serverResponse{} 104 | err := json.Unmarshal(message.Payload(), serverResponse) 105 | if err != nil { 106 | fmt.Println(err) 107 | bs.iotdaServer.CompleteError(err) 108 | } else { 109 | bs.iotdaServer.Complete(serverResponse.Address) 110 | } 111 | }() 112 | }) 113 | if subRes.Wait() && subRes.Error() != nil { 114 | fmt.Printf("sub topic %s failed,error is %s\n", downTopic, subRes.Error()) 115 | return false, subRes.Error() 116 | } else { 117 | fmt.Printf("sub topic %s success\n", downTopic) 118 | } 119 | 120 | return true, nil 121 | } 122 | 123 | func (bs *bsClient) Boot() string { 124 | upTopic := fmt.Sprintf("$oc/devices/%s/sys/bootstrap/up", bs.id) 125 | pubRes := bs.client.Publish(upTopic, 0, false, "") 126 | if pubRes.Wait() && pubRes.Error() != nil { 127 | fmt.Println(pubRes.Error()) 128 | return "" 129 | } 130 | 131 | bs.iotdaServer.Wait() 132 | return "tls://" + bs.iotdaServer.Value() 133 | } 134 | 135 | func (bs *bsClient) Close() { 136 | bs.client.Disconnect(1000) 137 | } 138 | 139 | type serverResponse struct { 140 | Address string `json:"address"` 141 | } 142 | 143 | type Result struct { 144 | Flag chan int 145 | err error 146 | mErr sync.RWMutex 147 | res string 148 | mRes sync.RWMutex 149 | } 150 | 151 | func (b *Result) Value() string { 152 | b.mRes.RLock() 153 | defer b.mRes.RUnlock() 154 | return b.res 155 | } 156 | 157 | func (b *Result) Wait() bool { 158 | <-b.Flag 159 | return true 160 | } 161 | 162 | func (b *Result) WaitTimeout(d time.Duration) bool { 163 | timer := time.NewTimer(d) 164 | select { 165 | case <-b.Flag: 166 | if !timer.Stop() { 167 | <-timer.C 168 | } 169 | return true 170 | case <-timer.C: 171 | } 172 | 173 | return false 174 | } 175 | 176 | func (b *Result) Complete(res string) { 177 | b.mRes.Lock() 178 | defer b.mRes.Unlock() 179 | b.res = res 180 | b.Flag <- 1 181 | } 182 | 183 | func (b *Result) CompleteError(err error) { 184 | b.mErr.Lock() 185 | defer b.mErr.Unlock() 186 | b.err = err 187 | b.Flag <- 1 188 | } 189 | 190 | func (b *Result) Error() error { 191 | b.mErr.RLock() 192 | defer b.mErr.RUnlock() 193 | return b.err 194 | } 195 | 196 | func newResult() *Result { 197 | return &Result{ 198 | Flag: make(chan int), 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | // 子设备添加回调函数 4 | type SubDevicesAddHandler func(devices SubDeviceInfo) 5 | 6 | //子设备删除糊掉函数 7 | type SubDevicesDeleteHandler func(devices SubDeviceInfo) 8 | 9 | // 处理平台下发的命令 10 | type CommandHandler func(Command) (bool, interface{}) 11 | 12 | // 设备消息 13 | type MessageHandler func(message Message) bool 14 | 15 | // 平台设置设备属性 16 | type DevicePropertiesSetHandler func(message DevicePropertyDownRequest) bool 17 | 18 | // 平台查询设备属性 19 | type DevicePropertyQueryHandler func(query DevicePropertyQueryRequest) DevicePropertyEntry 20 | 21 | // 设备执行软件/固件升级.upgradeType = 0 软件升级,upgradeType = 1 固件升级 22 | type DeviceUpgradeHandler func(upgradeType byte, info UpgradeInfo) UpgradeProgress 23 | 24 | // 设备上报软固件版本,第一个返回值为软件版本,第二个返回值为固件版本 25 | type SwFwVersionReporter func() (string, string) 26 | 27 | // 平台下发的升级信息 28 | type UpgradeInfo struct { 29 | Version string `json:"version"` //软固件包版本号 30 | Url string `json:"url"` //软固件包下载地址 31 | FileSize int `json:"file_size"` //软固件包文件大小 32 | AccessToken string `json:"access_token"` //软固件包url下载地址的临时token 33 | Expires string `json:"expires"` //access_token的超期时间 34 | Sign string `json:"sign"` //软固件包MD5值 35 | } 36 | 37 | // 设备升级状态响应,用于设备向平台反馈进度,错误信息等 38 | // ResultCode: 设备的升级状态,结果码定义如下: 39 | // 0:处理成功 40 | // 1:设备使用中 41 | // 2:信号质量差 42 | // 3:已经是最新版本 43 | // 4:电量不足 44 | // 5:剩余空间不足 45 | // 6:下载超时 46 | // 7:升级包校验失败 47 | // 8:升级包类型不支持 48 | // 9:内存不足 49 | // 10:安装升级包失败 50 | // 255: 内部异常 51 | type UpgradeProgress struct { 52 | ResultCode int `json:"result_code"` 53 | Progress int `json:"progress"` // 设备的升级进度,范围:0到100 54 | Version string `json:"version"` // 设备当前版本号 55 | Description string `json:"description"` // 升级状态描述信息,可以返回具体升级失败原因。 56 | } 57 | 58 | // 设备命令 59 | type Command struct { 60 | ObjectDeviceId string `json:"object_device_id"` 61 | ServiceId string `json:"service_id""` 62 | CommandName string `json:"command_name"` 63 | Paras interface{} `json:"paras"` 64 | } 65 | 66 | type CommandResponse struct { 67 | ResultCode byte `json:"result_code"` 68 | ResponseName string `json:"response_name"` 69 | Paras interface{} `json:"paras"` 70 | } 71 | 72 | // 消息 73 | type Message struct { 74 | ObjectDeviceId string `json:"object_device_id"` 75 | Name string `json:"name"` 76 | Id string `json:"id"` 77 | Content string `json:"content"` 78 | } 79 | 80 | // 定义平台和设备之间的数据交换结构体 81 | 82 | type Data struct { 83 | ObjectDeviceId string `json:"object_device_id,omitempty"` 84 | Services []DataEntry `json:"services"` 85 | } 86 | 87 | type DataEntry struct { 88 | ServiceId string `json:"service_id"` 89 | EventType string `json:"event_type"` 90 | EventTime string `json:"event_time"` 91 | Paras interface{} `json:"paras"` // 不同类型的请求paras使用的结构体不同 92 | } 93 | 94 | // 网关更新子设备状态 95 | type SubDevicesStatus struct { 96 | DeviceStatuses []DeviceStatus `json:"device_statuses"` 97 | } 98 | 99 | type DeviceStatus struct { 100 | DeviceId string `json:"device_id"` 101 | Status string `json:"status"` // 子设备状态。 OFFLINE:设备离线 ONLINE:设备上线 102 | } 103 | 104 | // 添加子设备 105 | type SubDeviceInfo struct { 106 | Devices []DeviceInfo `json:"devices"` 107 | Version int `json:"version"` 108 | } 109 | 110 | type DeviceInfo struct { 111 | ParentDeviceId string `json:"parent_device_id,omitempty"` 112 | NodeId string `json:"node_id,omitempty"` 113 | DeviceId string `json:"device_id,omitempty"` 114 | Name string `json:"name,omitempty"` 115 | Description string `json:"description,omitempty"` 116 | ManufacturerId string `json:"manufacturer_id,omitempty"` 117 | Model string `json:"model,omitempty"` 118 | ProductId string `json:"product_id"` 119 | FwVersion string `json:"fw_version,omitempty"` 120 | SwVersion string `json:"sw_version,omitempty"` 121 | Status string `json:"status,omitempty"` 122 | ExtensionInfo interface{} `json:"extension_info,omitempty"` 123 | } 124 | 125 | // 设备属性相关 126 | 127 | // 设备属性 128 | type DeviceProperties struct { 129 | Services []DevicePropertyEntry `json:"services"` 130 | } 131 | 132 | // 设备的一个属性 133 | type DevicePropertyEntry struct { 134 | ServiceId string `json:"service_id"` 135 | Properties interface{} `json:"properties"` 136 | EventTime string `json:"event_time"` 137 | } 138 | 139 | // 平台设置设备属性================================================== 140 | type DevicePropertyDownRequest struct { 141 | ObjectDeviceId string `json:"object_device_id"` 142 | Services []DevicePropertyDownRequestEntry `json:"services"` 143 | } 144 | 145 | type DevicePropertyDownRequestEntry struct { 146 | ServiceId string `json:"service_id"` 147 | Properties interface{} `json:"properties"` 148 | } 149 | 150 | // 平台设置设备属性================================================== 151 | type DevicePropertyQueryRequest struct { 152 | ObjectDeviceId string `json:"object_device_id"` 153 | ServiceId string `json:"service_id"` 154 | } 155 | 156 | // 设备获取设备影子数据 157 | type DevicePropertyQueryResponseHandler func(response DevicePropertyQueryResponse) 158 | 159 | type DevicePropertyQueryResponse struct { 160 | ObjectDeviceId string `json:"object_device_id"` 161 | Shadow []DeviceShadowData `json:"shadow"` 162 | } 163 | 164 | type DeviceShadowData struct { 165 | ServiceId string `json:"service_id"` 166 | Desired DeviceShadowPropertiesData `json:"desired"` 167 | Reported DeviceShadowPropertiesData `json:"reported"` 168 | Version int `json:"version"` 169 | } 170 | type DeviceShadowPropertiesData struct { 171 | Properties interface{} `json:"properties"` 172 | EventTime string `json:"event_time"` 173 | } 174 | 175 | // 网关批量上报子设备属性 176 | 177 | type DevicesService struct { 178 | Devices []DeviceService `json:"devices"` 179 | } 180 | 181 | type DeviceService struct { 182 | DeviceId string `json:"device_id"` 183 | Services []DevicePropertyEntry `json:"services"` 184 | } 185 | 186 | // 文件上传下载管理 187 | func CreateFileUploadDownLoadResultResponse(filename, action string, result bool) FileResultResponse { 188 | code := 0 189 | if !result { 190 | code = 1 191 | } 192 | 193 | paras := FileResultServiceEventParas{ 194 | ObjectName: filename, 195 | ResultCode: code, 196 | } 197 | 198 | serviceEvent := FileResultResponseServiceEvent{ 199 | Paras: paras, 200 | } 201 | serviceEvent.ServiceId = "$file_manager" 202 | if action == FileActionDownload { 203 | serviceEvent.EventType = "download_result_report" 204 | } 205 | if action == FileActionUpload { 206 | serviceEvent.EventType = "upload_result_report" 207 | } 208 | serviceEvent.EventTime = GetEventTimeStamp() 209 | 210 | var services []FileResultResponseServiceEvent 211 | services = append(services, serviceEvent) 212 | 213 | response := FileResultResponse{ 214 | Services: services, 215 | } 216 | 217 | return response 218 | } 219 | 220 | // 设备获取文件上传下载请求体 221 | type FileRequest struct { 222 | ObjectDeviceId string `json:"object_device_id"` 223 | Services []FileRequestServiceEvent `json:"services"` 224 | } 225 | 226 | // 平台下发文件上传和下载URL响应 227 | type FileResponse struct { 228 | ObjectDeviceId string `json:"object_device_id"` 229 | Services []FileResponseServiceEvent `json:"services"` 230 | } 231 | 232 | type FileResultResponse struct { 233 | ObjectDeviceId string `json:"object_device_id"` 234 | Services []FileResultResponseServiceEvent `json:"services"` 235 | } 236 | 237 | type BaseServiceEvent struct { 238 | ServiceId string `json:"service_id"` 239 | EventType string `json:"event_type"` 240 | EventTime string `json:"event_time,omitempty"` 241 | } 242 | 243 | type FileRequestServiceEvent struct { 244 | BaseServiceEvent 245 | Paras FileRequestServiceEventParas `json:"paras"` 246 | } 247 | 248 | type FileResponseServiceEvent struct { 249 | BaseServiceEvent 250 | Paras FileResponseServiceEventParas `json:"paras"` 251 | } 252 | 253 | type FileResultResponseServiceEvent struct { 254 | BaseServiceEvent 255 | Paras FileResultServiceEventParas `json:"paras"` 256 | } 257 | 258 | // 设备获取文件上传下载URL参数 259 | type FileRequestServiceEventParas struct { 260 | FileName string `json:"file_name"` 261 | FileAttributes interface{} `json:"file_attributes"` 262 | } 263 | 264 | // 平台下发响应参数 265 | type FileResponseServiceEventParas struct { 266 | Url string `json:"url"` 267 | BucketName string `json:"bucket_name"` 268 | ObjectName string `json:"object_name"` 269 | Expire int `json:"expire"` 270 | FileAttributes interface{} `json:"file_attributes"` 271 | } 272 | 273 | // 上报文件上传下载结果参数 274 | type FileResultServiceEventParas struct { 275 | ObjectName string `json:"object_name"` 276 | ResultCode int `json:"result_code"` 277 | StatusCode int `json:"status_code"` 278 | StatusDescription string `json:"status_description"` 279 | } 280 | 281 | // 上报设备信息请求 282 | type ReportDeviceInfoRequest struct { 283 | ObjectDeviceId string `json:"object_device_id,omitempty"` 284 | Services []ReportDeviceInfoServiceEvent `json:"services,omitempty"` 285 | } 286 | 287 | type ReportDeviceInfoServiceEvent struct { 288 | BaseServiceEvent 289 | Paras ReportDeviceInfoEventParas `json:"paras,omitempty"` 290 | } 291 | 292 | // 设备信息上报请求参数 293 | type ReportDeviceInfoEventParas struct { 294 | DeviceSdkVersion string `json:"device_sdk_version,omitempty"` 295 | SwVersion string `json:"sw_version,omitempty"` 296 | FwVersion string `json:"fw_version,omitempty"` 297 | } 298 | 299 | // 上报设备日志请求 300 | type ReportDeviceLogRequest struct { 301 | Services []ReportDeviceLogServiceEvent `json:"services,omitempty"` 302 | } 303 | 304 | type ReportDeviceLogServiceEvent struct { 305 | BaseServiceEvent 306 | Paras DeviceLogEntry `json:"paras,omitempty"` 307 | } 308 | 309 | // 设备状态日志收集器 310 | type DeviceStatusLogCollector func(endTime string) []DeviceLogEntry 311 | 312 | // 设备属性日志收集器 313 | type DevicePropertyLogCollector func(endTime string) []DeviceLogEntry 314 | 315 | // 设备消息日志收集器 316 | type DeviceMessageLogCollector func(endTime string) []DeviceLogEntry 317 | 318 | // 设备命令日志收集器 319 | type DeviceCommandLogCollector func(endTime string) []DeviceLogEntry 320 | 321 | type DeviceLogEntry struct { 322 | Timestamp string `json:"timestamp"` // 日志产生时间 323 | Type string `json:"type"` // 日志类型:DEVICE_STATUS,DEVICE_PROPERTY ,DEVICE_MESSAGE ,DEVICE_COMMAND 324 | Content string `json:"content"` // 日志内容 325 | } 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /device.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/glog" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | type Device interface { 11 | BaseDevice 12 | Gateway 13 | SendMessage(message Message) bool 14 | ReportProperties(properties DeviceProperties) bool 15 | BatchReportSubDevicesProperties(service DevicesService) bool 16 | QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler) 17 | UploadFile(filename string) bool 18 | DownloadFile(filename string) bool 19 | ReportDeviceInfo(swVersion, fwVersion string) 20 | ReportLogs(logs []DeviceLogEntry) bool 21 | } 22 | 23 | type iotDevice struct { 24 | base baseIotDevice 25 | gateway baseGateway 26 | } 27 | 28 | func (device *iotDevice) Init() bool { 29 | return device.base.Init() 30 | } 31 | 32 | func (device *iotDevice) DisConnect() { 33 | device.base.DisConnect() 34 | } 35 | 36 | func (device *iotDevice) IsConnected() bool { 37 | return device.base.IsConnected() 38 | } 39 | 40 | func (device *iotDevice) AddMessageHandler(handler MessageHandler) { 41 | device.base.AddMessageHandler(handler) 42 | } 43 | func (device *iotDevice) AddCommandHandler(handler CommandHandler) { 44 | device.base.AddCommandHandler(handler) 45 | } 46 | func (device *iotDevice) AddPropertiesSetHandler(handler DevicePropertiesSetHandler) { 47 | device.base.AddPropertiesSetHandler(handler) 48 | } 49 | func (device *iotDevice) SetSwFwVersionReporter(handler SwFwVersionReporter) { 50 | device.base.SetSwFwVersionReporter(handler) 51 | } 52 | 53 | func (device *iotDevice) SetDeviceUpgradeHandler(handler DeviceUpgradeHandler) { 54 | device.base.SetDeviceUpgradeHandler(handler) 55 | } 56 | 57 | func (device *iotDevice) SetPropertyQueryHandler(handler DevicePropertyQueryHandler) { 58 | device.base.SetPropertyQueryHandler(handler) 59 | } 60 | 61 | func (device *iotDevice) ReportLogs(logs []DeviceLogEntry) bool { 62 | var services []ReportDeviceLogServiceEvent 63 | 64 | for _, logEntry := range logs { 65 | service := ReportDeviceLogServiceEvent{ 66 | BaseServiceEvent: BaseServiceEvent{ 67 | ServiceId: "$log", 68 | EventType: "log_report", 69 | EventTime: GetEventTimeStamp(), 70 | }, 71 | Paras: logEntry, 72 | } 73 | 74 | services = append(services, service) 75 | } 76 | 77 | request := ReportDeviceLogRequest{ 78 | Services: services, 79 | } 80 | 81 | fmt.Println(Interface2JsonString(request)) 82 | 83 | topic := formatTopic(DeviceToPlatformTopic, device.base.Id) 84 | 85 | token := device.base.Client.Publish(topic, 1, false, Interface2JsonString(request)) 86 | 87 | if token.Wait() && token.Error() != nil { 88 | glog.Errorf("device %s report log failed", device.base.Id) 89 | return false 90 | } else { 91 | return true 92 | } 93 | } 94 | 95 | func (device *iotDevice) SendMessage(message Message) bool { 96 | messageData := Interface2JsonString(message) 97 | if token := device.base.Client.Publish(formatTopic(MessageUpTopic, device.base.Id), device.base.qos, false, messageData); token.Wait() && token.Error() != nil { 98 | glog.Warningf("device %s send message failed", device.base.Id) 99 | return false 100 | } 101 | return true 102 | } 103 | 104 | func (device *iotDevice) ReportProperties(properties DeviceProperties) bool { 105 | propertiesData := Interface2JsonString(properties) 106 | if token := device.base.Client.Publish(formatTopic(PropertiesUpTopic, device.base.Id), device.base.qos, false, propertiesData); token.Wait() && token.Error() != nil { 107 | glog.Warningf("device %s report properties failed", device.base.Id) 108 | return false 109 | } 110 | return true 111 | } 112 | func (device *iotDevice) BatchReportSubDevicesProperties(service DevicesService) bool { 113 | 114 | subDeviceCounts := len(service.Devices) 115 | 116 | batchReportSubDeviceProperties := 0 117 | if subDeviceCounts%device.base.batchSubDeviceSize == 0 { 118 | batchReportSubDeviceProperties = subDeviceCounts / device.base.batchSubDeviceSize 119 | } else { 120 | batchReportSubDeviceProperties = subDeviceCounts/device.base.batchSubDeviceSize + 1 121 | } 122 | 123 | for i := 0; i < batchReportSubDeviceProperties; i++ { 124 | begin := i * device.base.batchSubDeviceSize 125 | end := (i + 1) * device.base.batchSubDeviceSize 126 | if end > subDeviceCounts { 127 | end = subDeviceCounts 128 | } 129 | 130 | sds := DevicesService{ 131 | Devices: service.Devices[begin:end], 132 | } 133 | 134 | if token := device.base.Client.Publish(formatTopic(GatewayBatchReportSubDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(sds)); token.Wait() && token.Error() != nil { 135 | glog.Warningf("device %s batch report sub device properties failed", device.base.Id) 136 | return false 137 | } 138 | } 139 | 140 | return true 141 | } 142 | 143 | func (device *iotDevice) QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler) { 144 | device.base.propertiesQueryResponseHandler = handler 145 | requestId := uuid.NewV4() 146 | if token := device.base.Client.Publish(formatTopic(DeviceShadowQueryRequestTopic, device.base.Id)+requestId.String(), device.base.qos, false, Interface2JsonString(query)); token.Wait() && token.Error() != nil { 147 | glog.Warningf("device %s query device shadow data failed,request id = %s", device.base.Id, requestId) 148 | } 149 | } 150 | 151 | func (device *iotDevice) UploadFile(filename string) bool { 152 | // 构造获取文件上传URL的请求 153 | requestParas := FileRequestServiceEventParas{ 154 | FileName: filename, 155 | } 156 | 157 | serviceEvent := FileRequestServiceEvent{ 158 | Paras: requestParas, 159 | } 160 | serviceEvent.ServiceId = "$file_manager" 161 | serviceEvent.EventTime = GetEventTimeStamp() 162 | serviceEvent.EventType = "get_upload_url" 163 | 164 | var services []FileRequestServiceEvent 165 | services = append(services, serviceEvent) 166 | request := FileRequest{ 167 | Services: services, 168 | } 169 | 170 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 171 | glog.Warningf("publish file upload request url failed") 172 | return false 173 | } 174 | glog.Info("publish file upload request url success") 175 | 176 | ticker := time.Tick(time.Second) 177 | for { 178 | select { 179 | case <-ticker: 180 | _, ok := device.base.fileUrls[filename+FileActionUpload] 181 | if ok { 182 | glog.Infof("platform send file upload url success") 183 | goto BreakPoint 184 | } 185 | 186 | } 187 | } 188 | BreakPoint: 189 | 190 | if len(device.base.fileUrls[filename+FileActionUpload]) == 0 { 191 | glog.Errorf("get file upload url failed") 192 | return false 193 | } 194 | glog.Infof("file upload url is %s", device.base.fileUrls[filename+FileActionUpload]) 195 | 196 | //filename = smartFileName(filename) 197 | uploadFlag := CreateHttpClient().UploadFile(filename, device.base.fileUrls[filename+FileActionUpload]) 198 | if !uploadFlag { 199 | glog.Errorf("upload file failed") 200 | return false 201 | } 202 | 203 | response := CreateFileUploadDownLoadResultResponse(filename, FileActionUpload, uploadFlag) 204 | 205 | token := device.base.Client.Publish(formatTopic(PlatformEventToDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(response)) 206 | if token.Wait() && token.Error() != nil { 207 | glog.Error("report file upload file result failed") 208 | return false 209 | } 210 | 211 | return true 212 | } 213 | 214 | func (device *iotDevice) DownloadFile(filename string) bool { 215 | // 构造获取文件上传URL的请求 216 | requestParas := FileRequestServiceEventParas{ 217 | FileName: filename, 218 | } 219 | 220 | serviceEvent := FileRequestServiceEvent{ 221 | Paras: requestParas, 222 | } 223 | serviceEvent.ServiceId = "$file_manager" 224 | serviceEvent.EventTime = GetEventTimeStamp() 225 | serviceEvent.EventType = "get_download_url" 226 | 227 | var services []FileRequestServiceEvent 228 | services = append(services, serviceEvent) 229 | request := FileRequest{ 230 | Services: services, 231 | } 232 | 233 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 234 | glog.Warningf("publish file download request url failed") 235 | return false 236 | } 237 | 238 | ticker := time.Tick(time.Second) 239 | for { 240 | select { 241 | case <-ticker: 242 | _, ok := device.base.fileUrls[filename+FileActionDownload] 243 | if ok { 244 | glog.Infof("platform send file upload url success") 245 | goto BreakPoint 246 | } 247 | 248 | } 249 | } 250 | BreakPoint: 251 | 252 | if len(device.base.fileUrls[filename+FileActionDownload]) == 0 { 253 | glog.Errorf("get file download url failed") 254 | return false 255 | } 256 | 257 | downloadFlag := CreateHttpClient().DownloadFile(filename, device.base.fileUrls[filename+FileActionDownload]) 258 | if !downloadFlag { 259 | glog.Errorf("down load file { %s } failed", filename) 260 | return false 261 | } 262 | 263 | response := CreateFileUploadDownLoadResultResponse(filename, FileActionDownload, downloadFlag) 264 | 265 | token := device.base.Client.Publish(formatTopic(PlatformEventToDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(response)) 266 | if token.Wait() && token.Error() != nil { 267 | glog.Error("report file upload file result failed") 268 | return false 269 | } 270 | 271 | return true 272 | } 273 | 274 | func (device *iotDevice) ReportDeviceInfo(swVersion, fwVersion string) { 275 | event := ReportDeviceInfoServiceEvent{ 276 | BaseServiceEvent{ 277 | ServiceId: "$device_info", 278 | EventType: "device_info_report", 279 | EventTime: GetEventTimeStamp(), 280 | }, 281 | ReportDeviceInfoEventParas{ 282 | DeviceSdkVersion: SdkInfo()["sdk-version"], 283 | SwVersion: swVersion, 284 | FwVersion: fwVersion, 285 | }, 286 | } 287 | 288 | request := ReportDeviceInfoRequest{ 289 | ObjectDeviceId: device.base.Id, 290 | Services: []ReportDeviceInfoServiceEvent{event}, 291 | } 292 | 293 | device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)) 294 | } 295 | 296 | func (device *iotDevice) SetSubDevicesAddHandler(handler SubDevicesAddHandler) { 297 | device.base.subDevicesAddHandler = handler 298 | } 299 | 300 | func (device *iotDevice) SetSubDevicesDeleteHandler(handler SubDevicesDeleteHandler) { 301 | device.base.subDevicesDeleteHandler = handler 302 | } 303 | 304 | func (device *iotDevice) SetDeviceStatusLogCollector(collector DeviceStatusLogCollector) { 305 | device.base.SetDeviceStatusLogCollector(collector) 306 | } 307 | 308 | func (device *iotDevice) SetDevicePropertyLogCollector(collector DevicePropertyLogCollector) { 309 | device.base.SetDevicePropertyLogCollector(collector) 310 | } 311 | 312 | func (device *iotDevice) SetDeviceMessageLogCollector(collector DeviceMessageLogCollector) { 313 | device.base.SetDeviceMessageLogCollector(collector) 314 | } 315 | 316 | func (device *iotDevice) SetDeviceCommandLogCollector(collector DeviceCommandLogCollector) { 317 | device.base.SetDeviceCommandLogCollector(collector) 318 | } 319 | 320 | func (device *iotDevice) UpdateSubDeviceState(subDevicesStatus SubDevicesStatus) bool { 321 | glog.Infof("begin to update sub-devices status") 322 | 323 | subDeviceCounts := len(subDevicesStatus.DeviceStatuses) 324 | 325 | batchUpdateSubDeviceState := 0 326 | if subDeviceCounts%device.base.batchSubDeviceSize == 0 { 327 | batchUpdateSubDeviceState = subDeviceCounts / device.base.batchSubDeviceSize 328 | } else { 329 | batchUpdateSubDeviceState = subDeviceCounts/device.base.batchSubDeviceSize + 1 330 | } 331 | 332 | for i := 0; i < batchUpdateSubDeviceState; i++ { 333 | begin := i * device.base.batchSubDeviceSize 334 | end := (i + 1) * device.base.batchSubDeviceSize 335 | if end > subDeviceCounts { 336 | end = subDeviceCounts 337 | } 338 | 339 | sds := SubDevicesStatus{ 340 | DeviceStatuses: subDevicesStatus.DeviceStatuses[begin:end], 341 | } 342 | 343 | requestEventService := DataEntry{ 344 | ServiceId: "$sub_device_manager", 345 | EventType: "sub_device_update_status", 346 | EventTime: GetEventTimeStamp(), 347 | Paras: sds, 348 | } 349 | 350 | request := Data{ 351 | ObjectDeviceId: device.base.Id, 352 | Services: []DataEntry{requestEventService}, 353 | } 354 | 355 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 356 | glog.Warningf("gateway %s update sub devices status failed", device.base.Id) 357 | return false 358 | } 359 | } 360 | 361 | glog.Info("gateway update sub devices status failed", device.base.Id) 362 | return true 363 | } 364 | 365 | func (device *iotDevice) DeleteSubDevices(deviceIds []string) bool { 366 | glog.Infof("begin to delete sub-devices %s", deviceIds) 367 | 368 | subDevices := struct { 369 | Devices []string `json:"devices"` 370 | }{ 371 | Devices: deviceIds, 372 | } 373 | 374 | requestEventService := DataEntry{ 375 | ServiceId: "$sub_device_manager", 376 | EventType: "delete_sub_device_request", 377 | EventTime: GetEventTimeStamp(), 378 | Paras: subDevices, 379 | } 380 | 381 | request := Data{ 382 | ObjectDeviceId: device.base.Id, 383 | Services: []DataEntry{requestEventService}, 384 | } 385 | 386 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 387 | glog.Warningf("gateway %s delete sub devices request send failed", device.base.Id) 388 | return false 389 | } 390 | 391 | glog.Warningf("gateway %s delete sub devices request send success", device.base.Id) 392 | return true 393 | } 394 | 395 | func (device *iotDevice) AddSubDevices(deviceInfos []DeviceInfo) bool { 396 | devices := struct { 397 | Devices []DeviceInfo `json:"devices"` 398 | }{ 399 | Devices: deviceInfos, 400 | } 401 | 402 | requestEventService := DataEntry{ 403 | ServiceId: "$sub_device_manager", 404 | EventType: "add_sub_device_request", 405 | EventTime: GetEventTimeStamp(), 406 | Paras: devices, 407 | } 408 | 409 | request := Data{ 410 | ObjectDeviceId: device.base.Id, 411 | Services: []DataEntry{requestEventService}, 412 | } 413 | 414 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 415 | glog.Warningf("gateway %s add sub devices request send failed", device.base.Id) 416 | return false 417 | } 418 | 419 | glog.Warningf("gateway %s add sub devices request send success", device.base.Id) 420 | return true 421 | } 422 | 423 | func (device *iotDevice) SyncAllVersionSubDevices() { 424 | dataEntry := DataEntry{ 425 | ServiceId: "$sub_device_manager", 426 | EventType: "sub_device_sync_request", 427 | EventTime: GetEventTimeStamp(), 428 | Paras: struct { 429 | }{}, 430 | } 431 | 432 | var dataEntries []DataEntry 433 | dataEntries = append(dataEntries, dataEntry) 434 | 435 | data := Data{ 436 | Services: dataEntries, 437 | } 438 | 439 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(data)); token.Wait() && token.Error() != nil { 440 | glog.Errorf("send sub device sync request failed") 441 | } 442 | } 443 | 444 | func (device *iotDevice) SyncSubDevices(version int) { 445 | syncParas := struct { 446 | Version int `json:"version"` 447 | }{ 448 | Version: version, 449 | } 450 | 451 | dataEntry := DataEntry{ 452 | ServiceId: "$sub_device_manager", 453 | EventType: "sub_device_sync_request", 454 | EventTime: GetEventTimeStamp(), 455 | Paras: syncParas, 456 | } 457 | 458 | var dataEntries []DataEntry 459 | dataEntries = append(dataEntries, dataEntry) 460 | 461 | data := Data{ 462 | Services: dataEntries, 463 | } 464 | 465 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(data)); token.Wait() && token.Error() != nil { 466 | glog.Errorf("send sync sub device request failed") 467 | } 468 | } 469 | 470 | func CreateIotDevice(id, password, servers string) Device { 471 | config := DeviceConfig{ 472 | Id: id, 473 | Password: password, 474 | Servers: servers, 475 | Qos: 0, 476 | AuthType: AuthTypePassword, 477 | } 478 | 479 | return CreateIotDeviceWitConfig(config) 480 | } 481 | 482 | func CreateIotDeviceWithQos(id, password, servers string, qos byte) Device { 483 | config := DeviceConfig{ 484 | Id: id, 485 | Password: password, 486 | Servers: servers, 487 | Qos: qos, 488 | } 489 | 490 | return CreateIotDeviceWitConfig(config) 491 | } 492 | 493 | func CreateIotDeviceWitConfig(config DeviceConfig) Device { 494 | device := baseIotDevice{} 495 | device.Id = config.Id 496 | device.Password = config.Password 497 | device.Servers = config.Servers 498 | device.messageHandlers = []MessageHandler{} 499 | 500 | device.fileUrls = map[string]string{} 501 | 502 | device.qos = config.Qos 503 | device.batchSubDeviceSize = 100 504 | device.AuthType = config.AuthType 505 | device.ServerCaPath = config.ServerCaPath 506 | device.CertFilePath = config.CertFilePath 507 | device.CertKeyFilePath = config.CertKeyFilePath 508 | 509 | device.useBootstrap = config.UseBootstrap 510 | 511 | result := &iotDevice{ 512 | base: device, 513 | } 514 | return result 515 | } 516 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # huaweicloud-iot-device-sdk-go 2 | 3 | huaweicloud-iot-device-sdk-go提供设备接入华为云IoT物联网平台的Go版本的SDK,提供设备和平台之间通讯能力,以及设备服务、网关服务、OTA等高级服务。IoT设备开发者使用SDK可以大大简化开发复杂度,快速的接入平台。 4 | 5 | 支持如下功能: 6 | 7 | * [设备连接鉴权](#设备连接鉴权) 8 | 9 | * [设备命令](#设备命令) 10 | 11 | * [设备消息](#设备消息) 12 | 13 | * [设备属性](#设备属性) 14 | 15 | * [文件上传/下载管理](#文件上传/下载管理) 16 | 17 | * [网关与子设备管理](#网关与子设备管理) 18 | 19 | * [设备信息上报](#设备信息上报) 20 | 21 | * [设备日志收集](#设备日志收集) 22 | 23 | * [HTTP协议上报消息和属性](#HTTP协议上报消息和属性) 24 | * [支持设备发放服务](#使用设备发放服务) 25 | 26 | ## 版本说明 27 | 28 | 当前稳定版本:v1.0.1 29 | 30 | 31 | 32 | ## 安装和构建 33 | 34 | 安装和构建的过程取决于你是使用go的 [modules](https://golang.org/ref/mod)(推荐) 还是还是`GOPATH` 35 | 36 | ### Modules 37 | 38 | 如果你使用 [modules](https://golang.org/ref/mod) 只需要导入包"github.com/ctlove0523/huaweicloud-iot-device-sdk-go"即可使用。当你使用go 39 | build命令构建项目时,依赖的包会自动被下载。注意使用go 40 | build命令构建时会自动下载最新版本,最新版本还没有达到release的标准可能存在一些尚未修复的bug。如果想使用稳定的发布版本可以从[release](https://github.com/ctlove0523/huaweicloud-iot-device-sdk-go/releases) 41 | 获取最新稳定的版本号,并在go.mod文件中指定版本号。 42 | 43 | ~~~go 44 | module example 45 | 46 | go 1.15 47 | 48 | require github.com/ctlove0523/huaweicloud-iot-device-sdk-go v0.0.1-alpha 49 | ~~~ 50 | 51 | ### GOPATH 52 | 53 | 如果你使用GOPATH,下面的一条命令即可实现安装 54 | 55 | ~~~go 56 | go get github.com/ctlove0523/huaweicloud-iot-device-sdk-go 57 | ~~~ 58 | 59 | ## 使用API 60 | 61 | > SDK提供了异步client,下面所有的方法都有对应的异步方法。 62 | 63 | ### 设备连接鉴权 64 | 65 | #### 使用密钥鉴权 66 | 67 | 1、首先,在华为云IoT平台创建一个设备,设备的信息如下: 68 | 69 | 设备ID:5fdb75cccbfe2f02ce81d4bf_go-mqtt 70 | 71 | 设备密钥:123456789 72 | 73 | 2、使用SDK创建一个Device对象,并初始化Device。 74 | 75 | ~~~go 76 | import ( 77 | "fmt" 78 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 79 | "time" 80 | ) 81 | 82 | func main() { 83 | // 创建一个设备并初始化 84 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 85 | device.Init() 86 | if device.IsConnected() { 87 | fmt.Println("device connect huawei iot platform success") 88 | } else { 89 | fmt.Println("device connect huawei iot platform failed") 90 | } 91 | } 92 | ~~~ 93 | 94 | > iot-mqtts.cn-north-4.myhuaweicloud.com为华为IoT平台(基础班)在华为云北京四的访问端点,如果你购买了标准版或企业版,请将iot-mqtts.cn-north-4.myhuaweicloud.com更换为对应的MQTT协议接入端点。 95 | 96 | #### 使用x.509证书鉴权 97 | 98 | 1、根据华为云文档创建证书并注册设备 99 | 100 | [华为云文档](https://support.huaweicloud.com/bestpractice-iothub/iot_bp_0077.html) 101 | 102 | 2、使用SDK创建一个使用x.509证书鉴权的设备并初始化 103 | 104 | 使用x.509证书鉴权需要获取平台的ca证书,并已经创建获取了设备的证书,具体可以参考上面的华为云文档。 105 | 106 | ~~~go 107 | caPath := "your ca path" 108 | certFilePath := "your cert path" 109 | certKeyFilePath := "your key path" 110 | 111 | config := iot.DeviceConfig{ 112 | Id: "your device id", 113 | Servers: "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883", 114 | AuthType: iot.AUTH_TYPE_X509, 115 | ServerCaPath: caPath, 116 | CertFilePath: certFilePath, 117 | CertKeyFilePath: certKeyFilePath, 118 | } 119 | 120 | device := iot.CreateIotDeviceWitConfig(config) 121 | 122 | res := device.Init() 123 | fmt.Println(res) 124 | ~~~ 125 | 126 | > 使用x.509证书鉴权必须设置AuthType的值为1(iot.AUTH_TYPE_X509),否则默认使用密码进行鉴权 127 | 128 | ### 设备命令 129 | 130 | 1、首先,在华为云IoT平台创建一个设备,设备的信息如下: 131 | 132 | 设备ID:5fdb75cccbfe2f02ce81d4bf_go-mqtt 133 | 134 | 设备密钥:123456789 135 | 136 | 2、使用SDK创建一个Device对象,并初始化Device。 137 | 138 | ~~~go 139 | // 创建一个设备并初始化 140 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 141 | device.Init() 142 | if device.IsConnected() { 143 | fmt.Println("device connect huawei iot platform success") 144 | } else { 145 | fmt.Println("device connect huawei iot platform failed") 146 | } 147 | ~~~ 148 | 149 | 3、注册命令处理handler,支持注册多个handler并且按照注册的顺序回调 150 | 151 | ~~~go 152 | // 添加用于处理平台下发命令的callback 153 | device.AddCommandHandler(func(command iot.Command) bool { 154 | fmt.Println("First command handler begin to process command.") 155 | return true 156 | }) 157 | 158 | device.AddCommandHandler(func(command iot.Command) bool { 159 | fmt.Println("Second command handler begin to process command.") 160 | return true 161 | }) 162 | ~~~ 163 | 164 | 4、通过应用侧API向设备下发一个命令,可以看到程序输出如下: 165 | 166 | ~~~ 167 | device connect huawei iot platform success 168 | First command handler begin to process command. 169 | Second command handler begin to process command. 170 | ~~~ 171 | 172 | #### 完整样例 173 | 174 | ~~~go 175 | import ( 176 | "fmt" 177 | "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 178 | "time" 179 | ) 180 | 181 | // 处理平台下发的同步命令 182 | func main() { 183 | // 创建一个设备并初始化 184 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 185 | device.Init() 186 | if device.IsConnected() { 187 | fmt.Println("device connect huawei iot platform success") 188 | } else { 189 | fmt.Println("device connect huawei iot platform failed") 190 | } 191 | 192 | // 添加用于处理平台下发命令的callback 193 | device.AddCommandHandler(func(command iot.Command) bool { 194 | fmt.Println("First command handler begin to process command.") 195 | return true 196 | }) 197 | 198 | device.AddCommandHandler(func(command iot.Command) bool { 199 | fmt.Println("Second command handler begin to process command.") 200 | return true 201 | }) 202 | time.Sleep(1 * time.Minute) 203 | } 204 | ~~~ 205 | 206 | > 设备支持的命令定义在产品中 207 | 208 | ### 设备消息 209 | 210 | 1、首先,在华为云IoT平台创建一个设备,设备的信息如下: 211 | 212 | 设备ID:5fdb75cccbfe2f02ce81d4bf_go-mqtt 213 | 214 | 设备密钥:123456789 215 | 216 | 2、使用SDK创建一个Device对象,并初始化Device。 217 | 218 | ~~~go 219 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 220 | device.Init() 221 | ~~~ 222 | 223 | #### 设备消息上报 224 | 225 | ~~~go 226 | message := iot.Message{ 227 | ObjectDeviceId: uuid.NewV4().String(), 228 | Name: "Fist send message to platform", 229 | Id: uuid.NewV4().String(), 230 | Content: "Hello Huawei IoT Platform", 231 | } 232 | device.SendMessage(message) 233 | ~~~ 234 | 235 | #### 平台消息下发 236 | 237 | 接收平台下发的消息,只需注册消息处理handler,支持注册多个handler并按照注册顺序回调。 238 | 239 | ~~~go 240 | // 注册平台下发消息的callback,当收到平台下发的消息时,调用此callback. 241 | // 支持注册多个callback,并且按照注册顺序调用 242 | device.AddMessageHandler(func(message iot.Message) bool { 243 | fmt.Println("first handler called" + iot.Interface2JsonString(message)) 244 | return true 245 | }) 246 | 247 | device.AddMessageHandler(func(message iot.Message) bool { 248 | fmt.Println("second handler called" + iot.Interface2JsonString(message)) 249 | return true 250 | }) 251 | ~~~ 252 | 253 | #### 完整样例 254 | 255 | ~~~go 256 | import ( 257 | "fmt" 258 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 259 | uuid "github.com/satori/go.uuid" 260 | "time" 261 | ) 262 | 263 | func main() { 264 | // 创建一个设备并初始化 265 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 266 | device.Init() 267 | 268 | // 注册平台下发消息的callback,当收到平台下发的消息时,调用此callback. 269 | // 支持注册多个callback,并且按照注册顺序调用 270 | device.AddMessageHandler(func(message iot.Message) bool { 271 | fmt.Println("first handler called" + iot.Interface2JsonString(message)) 272 | return true 273 | }) 274 | 275 | device.AddMessageHandler(func(message iot.Message) bool { 276 | fmt.Println("second handler called" + iot.Interface2JsonString(message)) 277 | return true 278 | }) 279 | 280 | //向平台发送消息 281 | message := iot.Message{ 282 | ObjectDeviceId: uuid.NewV4().String(), 283 | Name: "Fist send message to platform", 284 | Id: uuid.NewV4().String(), 285 | Content: "Hello Huawei IoT Platform", 286 | } 287 | device.SendMessage(message) 288 | time.Sleep(2 * time.Minute) 289 | 290 | } 291 | ~~~ 292 | 293 | ### 设备属性 294 | 295 | 1、首先,在华为云IoT平台创建一个设备,并在该设备下创建3个子设备,设备及子设备的信息如下: 296 | 297 | 设备ID:5fdb75cccbfe2f02ce81d4bf_go-mqtt 298 | 299 | 设备密钥:123456789 300 | 301 | 子设备ID:5fdb75cccbfe2f02ce81d4bf_sub-device-1 302 | 303 | 子设备ID:5fdb75cccbfe2f02ce81d4bf_sub-device-2 304 | 305 | 子设备ID:5fdb75cccbfe2f02ce81d4bf_sub-device-3 306 | 307 | 2、使用SDK创建一个Device对象,并初始化Device。 308 | 309 | ~~~go 310 | // 创建设备并初始化 311 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 312 | device.Init() 313 | fmt.Printf("device connected: %v\n", device.IsConnected()) 314 | ~~~ 315 | 316 | #### 设备属性上报 317 | 318 | 使用`ReportProperties(properties ServiceProperty) bool` 上报设备属性 319 | 320 | ~~~go 321 | // 设备上报属性 322 | props := iot.ServicePropertyEntry{ 323 | ServiceId: "value", 324 | EventTime: iot.DataCollectionTime(), 325 | Properties: DemoProperties{ 326 | Value: "chen tong", 327 | MsgType: "23", 328 | }, 329 | } 330 | 331 | var content []iot.ServicePropertyEntry 332 | content = append(content, props) 333 | services := iot.ServiceProperty{ 334 | Services: content, 335 | } 336 | device.ReportProperties(services) 337 | ~~~ 338 | 339 | #### 网关批量设备属性上报 340 | 341 | 使用`BatchReportSubDevicesProperties(service DevicesService)` 实现网关批量设备属性上报 342 | 343 | ~~~go 344 | // 批量上报子设备属性 345 | subDevice1 := iot.DeviceService{ 346 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-1", 347 | Services: content, 348 | } 349 | subDevice2 := iot.DeviceService{ 350 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-2", 351 | Services: content, 352 | } 353 | 354 | subDevice3 := iot.DeviceService{ 355 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-3", 356 | Services: content, 357 | } 358 | 359 | var devices []iot.DeviceService 360 | devices = append(devices, subDevice1, subDevice2, subDevice3) 361 | 362 | device.BatchReportSubDevicesProperties(iot.DevicesService{ 363 | Devices: devices, 364 | }) 365 | ~~~ 366 | 367 | #### 平台设置设备属性 368 | 369 | 使用`AddPropertiesSetHandler(handler DevicePropertiesSetHandler)` 注册平台设置设备属性handler,当接收到平台的命令时SDK回调。 370 | 371 | ~~~go 372 | // 注册平台设置属性callback,当应用通过API设置设备属性时,会调用此callback,支持注册多个callback 373 | device.AddPropertiesSetHandler(func(propertiesSetRequest iot.DevicePropertyDownRequest) bool { 374 | fmt.Println("I get property set command") 375 | fmt.Printf("request is %s", iot.Interface2JsonString(propertiesSetRequest)) 376 | return true 377 | }) 378 | ~~~ 379 | 380 | #### 平台查询设备属性 381 | 382 | 使用`SetPropertyQueryHandler(handler DevicePropertyQueryHandler)`注册平台查询设备属性handler,当接收到平台的查询请求时SDK回调。 383 | 384 | ~~~go 385 | // 注册平台查询设备属性callback,当平台查询设备属性时此callback被调用,仅支持设置一个callback 386 | device.SetPropertyQueryHandler(func(query iot.DevicePropertyQueryRequest) iot.ServicePropertyEntry { 387 | return iot.ServicePropertyEntry{ 388 | ServiceId: "value", 389 | Properties: DemoProperties{ 390 | Value: "QUERY RESPONSE", 391 | MsgType: "query property", 392 | }, 393 | EventTime: "2020-12-19 02:23:24", 394 | } 395 | }) 396 | ~~~ 397 | 398 | #### 设备侧获取平台的设备影子数据 399 | 400 | 使用`QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler)` 401 | 可以查询平台的设备影子数据,当接收到平台的响应后SDK自动回调`DevicePropertyQueryResponseHandler`。 402 | 403 | ~~~go 404 | // 设备查询设备影子数据 405 | device.QueryDeviceShadow(iot.DevicePropertyQueryRequest{ 406 | ServiceId: "value", 407 | }, func(response iot.DevicePropertyQueryResponse) { 408 | fmt.Printf("query device shadow success.\n,device shadow data is %s\n", iot.Interface2JsonString(response)) 409 | }) 410 | ~~~ 411 | 412 | #### 完整样例 413 | 414 | ~~~go 415 | import ( 416 | "fmt" 417 | iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go" 418 | "time" 419 | ) 420 | 421 | func main() { 422 | // 创建设备并初始化 423 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 424 | device.Init() 425 | fmt.Printf("device connected: %v\n", device.IsConnected()) 426 | 427 | // 注册平台设置属性callback,当应用通过API设置设备属性时,会调用此callback,支持注册多个callback 428 | device.AddPropertiesSetHandler(func(propertiesSetRequest iot.DevicePropertyDownRequest) bool { 429 | fmt.Println("I get property set command") 430 | fmt.Printf("request is %s", iot.Interface2JsonString(propertiesSetRequest)) 431 | return true 432 | }) 433 | 434 | // 注册平台查询设备属性callback,当平台查询设备属性时此callback被调用,仅支持设置一个callback 435 | device.SetPropertyQueryHandler(func(query iot.DevicePropertyQueryRequest) iot.ServicePropertyEntry { 436 | return iot.ServicePropertyEntry{ 437 | ServiceId: "value", 438 | Properties: DemoProperties{ 439 | Value: "QUERY RESPONSE", 440 | MsgType: "query property", 441 | }, 442 | EventTime: "2020-12-19 02:23:24", 443 | } 444 | }) 445 | 446 | // 设备上报属性 447 | props := iot.ServicePropertyEntry{ 448 | ServiceId: "value", 449 | EventTime: iot.DataCollectionTime(), 450 | Properties: DemoProperties{ 451 | Value: "chen tong", 452 | MsgType: "23", 453 | }, 454 | } 455 | 456 | var content []iot.ServicePropertyEntry 457 | content = append(content, props) 458 | services := iot.ServiceProperty{ 459 | Services: content, 460 | } 461 | device.ReportProperties(services) 462 | 463 | // 设备查询设备影子数据 464 | device.QueryDeviceShadow(iot.DevicePropertyQueryRequest{ 465 | ServiceId: "value", 466 | }, func(response iot.DevicePropertyQueryResponse) { 467 | fmt.Printf("query device shadow success.\n,device shadow data is %s\n", iot.Interface2JsonString(response)) 468 | }) 469 | 470 | // 批量上报子设备属性 471 | subDevice1 := iot.DeviceService{ 472 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-1", 473 | Services: content, 474 | } 475 | subDevice2 := iot.DeviceService{ 476 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-2", 477 | Services: content, 478 | } 479 | 480 | subDevice3 := iot.DeviceService{ 481 | DeviceId: "5fdb75cccbfe2f02ce81d4bf_sub-device-3", 482 | Services: content, 483 | } 484 | 485 | var devices []iot.DeviceService 486 | devices = append(devices, subDevice1, subDevice2, subDevice3) 487 | 488 | device.BatchReportSubDevicesProperties(iot.DevicesService{ 489 | Devices: devices, 490 | }) 491 | time.Sleep(1 * time.Minute) 492 | } 493 | 494 | type DemoProperties struct { 495 | Value string `json:"value"` 496 | MsgType string `json:"msgType"` 497 | } 498 | ~~~ 499 | 500 | ### 文件上传/下载管理 501 | 502 | #### 文件上传 503 | 504 | ~~~go 505 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 506 | device.Init() 507 | 508 | device.UploadFile("D/software/mqttfx/chentong.txt") 509 | ~~~ 510 | 511 | ### 网关与子设备管理 512 | 513 | > 当前SDK没有内置mqtt broker模块,对mqtt broker的支持正在开发中 514 | 515 | #### 网关接收子设备新增和删除通知 516 | 517 | 网关如果要处理子设备新增和删除,需要注册对应的handler让SDK调用。 518 | 519 | ~~~go 520 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 521 | 522 | // 处理子设备添加 523 | device.SetSubDevicesAddHandler(func(devices iot.SubDeviceInfo) { 524 | for _, info := range devices.Devices { 525 | fmt.Println("handle device add") 526 | fmt.Println(iot.Interface2JsonString(info)) 527 | } 528 | }) 529 | 530 | // 处理子设备删除 531 | device.SetSubDevicesDeleteHandler(func(devices iot.SubDeviceInfo) { 532 | for _, info := range devices.Devices { 533 | fmt.Println("handle device delete") 534 | fmt.Println(iot.Interface2JsonString(info)) 535 | } 536 | }) 537 | 538 | device.Init() 539 | ~~~ 540 | 541 | 542 | 543 | #### 网关同步子设备列表 544 | 545 | * 同步所有版本的子设备 546 | 547 | ~~~go 548 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 549 | device.Init() 550 | device.SyncAllVersionSubDevices() 551 | ~~~ 552 | 553 | * 同步指定版本的子设备 554 | 555 | ~~~go 556 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 557 | device.Init() 558 | device.SyncSubDevices(version int) 559 | ~~~ 560 | 561 | #### 网关新增子设备 562 | 563 | ```go 564 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 565 | device.Init() 566 | result:= device.AddSubDevices(deviceInfos) // deviceInfos 的类型为[]DeviceInfo 567 | ``` 568 | 569 | 570 | 571 | #### 网关删除子设备 572 | 573 | ```go 574 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 575 | device.Init() 576 | result:= device.DeleteSubDevices(deviceIds) // deviceIds的类型为[]string 577 | ``` 578 | 579 | 580 | 581 | #### 网关更新子设备状态 582 | 583 | ```go 584 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 585 | device.Init() 586 | result:= device.UpdateSubDeviceState(subDevicesStatus) //subDevicesStatus的类型SubDevicesStatus 587 | ``` 588 | 589 | 590 | 591 | ### 设备信息上报 592 | 593 | 设备可以向平台上报SDK版本、软固件版本信息,其中SDK的版本信息SDK自动填充 594 | 595 | ~~~go 596 | device := iot.CreateIotDevice("xxx", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 597 | device.Init() 598 | 599 | device.ReportDeviceInfo("1.0", "2.0") 600 | ~~~ 601 | 602 | 603 | 604 | 605 | 606 | ### 设备日志收集 607 | 608 | 设备日志功能主要包括:平台下发日志收集命令,设备上报平台指定时间段内的日志;设备调用接口主动上报日志。 609 | 610 | * 设备响应平台日志收集命令 611 | 612 | 设备响应日志收集功能需要实现日志收集函数,函数的定义如下: 613 | 614 | ~~~go 615 | // 设备状态日志收集器 616 | type DeviceStatusLogCollector func(endTime string) []DeviceLogEntry 617 | 618 | // 设备属性日志收集器 619 | type DevicePropertyLogCollector func(endTime string) []DeviceLogEntry 620 | 621 | // 设备消息日志收集器 622 | type DeviceMessageLogCollector func(endTime string) []DeviceLogEntry 623 | 624 | // 设备命令日志收集器 625 | type DeviceCommandLogCollector func(endTime string) []DeviceLogEntry 626 | ~~~ 627 | 628 | 函数需要返回endTime之前的所有日志,DeviceLogEntry包括日志记录时间、日志类型以及日志内容。当设备收到平台下发日志收集请求后,SDK会自动的上报日志直到平台关闭日志收集或endTime范围内没有任何日志内容。 629 | 630 | 日志收集函数的设置如下: 631 | 632 | ~~~go 633 | device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "xxx", "tls://iot-mqtts.cn-north-4.myhuaweicloud.com:8883") 634 | 635 | // 设置设备状态日志收集器 636 | device.SetDeviceStatusLogCollector(func(endTime string) []iot.DeviceLogEntry { 637 | return []iot.DeviceLogEntry{} 638 | }) 639 | device.Init() 640 | ~~~ 641 | 642 | * 设备主动上报日志 643 | 644 | 设备可以调用`ReportLogs(logs []DeviceLogEntry) bool` 函数主动上报日志。 645 | 646 | ### HTTP协议上报消息和属性 647 | 648 | 华为云IoT物联网平台支持使用HTTP协议上报消息和属性(该功能目前处于α阶段,尚未对外开放,具体开放时间参考华为云IoT物联网平台公告)。使用HTTP协议上报消息和属性非常简单方便,SDK对接口进行了封装,接口使用的对象和MQTT协议一致。使用HTTP协议的设备接口定义如下: 649 | 650 | ~~~go 651 | type HttpDevice interface { 652 | SendMessage(message Message) bool 653 | ReportProperties(properties DeviceProperties) bool 654 | } 655 | ~~~ 656 | 657 | 使用样例参考:http_device_samples.go 658 | 659 | 660 | 661 | ### 使用设备发放服务 662 | 663 | 有两种方法可以使用设备发放服务动态获取设备连接平台的地址 664 | 665 | 方法1:通过设备发放服务获取设备连接平台的地址,然后创建设备 666 | 667 | ~~~golang 668 | id := "device_id" 669 | pwd := "your device password" 670 | 671 | bootstrapClient, err := iot.NewBootstrapClient(id, pwd) 672 | if err != nil { 673 | fmt.Printf("create bs client failed") 674 | return 675 | } 676 | 677 | server := bootstrapClient.Boot() 678 | if len(server) == 0 { 679 | fmt.Println("get server address failed") 680 | return 681 | } 682 | 683 | device := iot.CreateIotDevice(id, pwd, server) 684 | device.Init() 685 | ~~~ 686 | 687 | 方法2:在创建设备时启动设备发放服务 688 | 689 | ~~~go 690 | id := "device_id" 691 | pwd := "your device password" 692 | config := iot.DeviceConfig{ 693 | Id: id, 694 | Password: pwd, 695 | UseBootstrap: true, 696 | } 697 | device := iot.CreateIotDeviceWitConfig(config) 698 | initRes := device.Init() 699 | fmt.Println(initRes) 700 | 701 | time.Sleep(1 * time.Minute) 702 | ~~~ 703 | 704 | 705 | 706 | 707 | ## 报告bugs 708 | 709 | 如果你在使用过程中遇到任何问题或bugs,请通过issue的方式上报问题或bug,我们将会在第一时间内答复。上报问题或bugs时请尽量提供以下内容: 710 | 711 | * 使用的版本 712 | * 使用场景 713 | * 重现问题或bug的样例代码 714 | * 错误信息 715 | * ······ 716 | 717 | ## 贡献 718 | 719 | 该项目欢迎来自所有人的pull request。 720 | -------------------------------------------------------------------------------- /async_device.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/glog" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | type AsyncDevice interface { 11 | BaseDevice 12 | AsyncGateway 13 | SendMessage(message Message) AsyncResult 14 | ReportProperties(properties DeviceProperties) AsyncResult 15 | BatchReportSubDevicesProperties(service DevicesService) AsyncResult 16 | QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler) AsyncResult 17 | UploadFile(filename string) AsyncResult 18 | DownloadFile(filename string) AsyncResult 19 | ReportDeviceInfo(swVersion, fwVersion string) AsyncResult 20 | ReportLogs(logs []DeviceLogEntry) AsyncResult 21 | } 22 | 23 | func CreateAsyncIotDevice(id, password, servers string) *asyncDevice { 24 | config := DeviceConfig{ 25 | Id: id, 26 | Password: password, 27 | Servers: servers, 28 | Qos: 0, 29 | } 30 | 31 | return CreateAsyncIotDeviceWitConfig(config) 32 | } 33 | 34 | func CreateAsyncIotDeviceWithQos(id, password, servers string, qos byte) *asyncDevice { 35 | config := DeviceConfig{ 36 | Id: id, 37 | Password: password, 38 | Servers: servers, 39 | Qos: qos, 40 | } 41 | 42 | return CreateAsyncIotDeviceWitConfig(config) 43 | } 44 | 45 | func CreateAsyncIotDeviceWitConfig(config DeviceConfig) *asyncDevice { 46 | device := baseIotDevice{} 47 | device.Id = config.Id 48 | device.Password = config.Password 49 | device.Servers = config.Servers 50 | device.messageHandlers = []MessageHandler{} 51 | 52 | device.fileUrls = map[string]string{} 53 | 54 | device.qos = config.Qos 55 | device.batchSubDeviceSize = config.BatchSubDeviceSize 56 | 57 | result := &asyncDevice{ 58 | base: device, 59 | } 60 | return result 61 | } 62 | 63 | type asyncDevice struct { 64 | base baseIotDevice 65 | } 66 | 67 | func (device *asyncDevice) Init() bool { 68 | return device.base.Init() 69 | } 70 | 71 | func (device *asyncDevice) DisConnect() { 72 | device.base.DisConnect() 73 | } 74 | 75 | func (device *asyncDevice) IsConnected() bool { 76 | return device.base.IsConnected() 77 | } 78 | 79 | func (device *asyncDevice) AddMessageHandler(handler MessageHandler) { 80 | device.base.AddMessageHandler(handler) 81 | } 82 | func (device *asyncDevice) AddCommandHandler(handler CommandHandler) { 83 | device.base.AddCommandHandler(handler) 84 | } 85 | func (device *asyncDevice) AddPropertiesSetHandler(handler DevicePropertiesSetHandler) { 86 | device.base.AddPropertiesSetHandler(handler) 87 | } 88 | func (device *asyncDevice) SetPropertyQueryHandler(handler DevicePropertyQueryHandler) { 89 | device.base.SetPropertyQueryHandler(handler) 90 | } 91 | 92 | func (device *asyncDevice) SetSwFwVersionReporter(handler SwFwVersionReporter) { 93 | device.base.SetSwFwVersionReporter(handler) 94 | } 95 | 96 | func (device *asyncDevice) SetDeviceUpgradeHandler(handler DeviceUpgradeHandler) { 97 | device.base.SetDeviceUpgradeHandler(handler) 98 | } 99 | 100 | func (device *asyncDevice) SetDeviceStatusLogCollector(collector DeviceStatusLogCollector) { 101 | device.base.SetDeviceStatusLogCollector(collector) 102 | } 103 | 104 | func (device *asyncDevice) SetDevicePropertyLogCollector(collector DevicePropertyLogCollector) { 105 | device.base.SetDevicePropertyLogCollector(collector) 106 | } 107 | 108 | func (device *asyncDevice) SetDeviceMessageLogCollector(collector DeviceMessageLogCollector) { 109 | device.base.SetDeviceMessageLogCollector(collector) 110 | } 111 | 112 | func (device *asyncDevice) SetDeviceCommandLogCollector(collector DeviceCommandLogCollector) { 113 | device.base.SetDeviceCommandLogCollector(collector) 114 | } 115 | 116 | func (device *asyncDevice) SendMessage(message Message) AsyncResult { 117 | asyncResult := NewBooleanAsyncResult() 118 | go func() { 119 | glog.Info("begin async send message") 120 | 121 | messageData := Interface2JsonString(message) 122 | topic := formatTopic(MessageUpTopic, device.base.Id) 123 | glog.Infof("async send message topic is %s", topic) 124 | token := device.base.Client.Publish(topic, device.base.qos, false, messageData) 125 | if token.Wait() && token.Error() != nil { 126 | glog.Warning("async send message failed") 127 | asyncResult.completeError(token.Error()) 128 | } else { 129 | asyncResult.completeSuccess() 130 | } 131 | }() 132 | 133 | return asyncResult 134 | } 135 | 136 | func (device *asyncDevice) ReportProperties(properties DeviceProperties) AsyncResult { 137 | asyncResult := NewBooleanAsyncResult() 138 | go func() { 139 | glog.Info("begin to report properties") 140 | propertiesData := Interface2JsonString(properties) 141 | if token := device.base.Client.Publish(formatTopic(PropertiesUpTopic, device.base.Id), device.base.qos, false, propertiesData); token.Wait() && token.Error() != nil { 142 | glog.Warningf("device %s async report properties failed", device.base.Id) 143 | asyncResult.completeError(token.Error()) 144 | } else { 145 | asyncResult.completeSuccess() 146 | } 147 | }() 148 | 149 | return asyncResult 150 | } 151 | 152 | func (device *asyncDevice) BatchReportSubDevicesProperties(service DevicesService) AsyncResult { 153 | asyncResult := NewBooleanAsyncResult() 154 | 155 | go func() { 156 | glog.Info("begin async batch report sub devices properties") 157 | subDeviceCounts := len(service.Devices) 158 | batchReportSubDeviceProperties := 0 159 | if subDeviceCounts%device.base.batchSubDeviceSize == 0 { 160 | batchReportSubDeviceProperties = subDeviceCounts / device.base.batchSubDeviceSize 161 | } else { 162 | batchReportSubDeviceProperties = subDeviceCounts/device.base.batchSubDeviceSize + 1 163 | } 164 | 165 | loopResult := true 166 | for i := 0; i < batchReportSubDeviceProperties; i++ { 167 | begin := i * device.base.batchSubDeviceSize 168 | end := (i + 1) * device.base.batchSubDeviceSize 169 | if end > subDeviceCounts { 170 | end = subDeviceCounts 171 | } 172 | 173 | sds := DevicesService{ 174 | Devices: service.Devices[begin:end], 175 | } 176 | 177 | if token := device.base.Client.Publish(formatTopic(GatewayBatchReportSubDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(sds)); token.Wait() && token.Error() != nil { 178 | glog.Warningf("device %s batch report sub device properties failed", device.base.Id) 179 | loopResult = false 180 | asyncResult.completeError(token.Error()) 181 | break 182 | } 183 | } 184 | 185 | if loopResult { 186 | asyncResult.completeSuccess() 187 | } 188 | }() 189 | 190 | return asyncResult 191 | } 192 | 193 | func (device *asyncDevice) QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler) AsyncResult { 194 | device.base.propertiesQueryResponseHandler = handler 195 | asyncResult := NewBooleanAsyncResult() 196 | 197 | go func() { 198 | requestId := uuid.NewV4() 199 | if token := device.base.Client.Publish(formatTopic(DeviceShadowQueryRequestTopic, device.base.Id)+requestId.String(), device.base.qos, false, Interface2JsonString(query)); token.Wait() && token.Error() != nil { 200 | glog.Warningf("device %s query device shadow data failed,request id = %s", device.base.Id, requestId) 201 | asyncResult.completeError(token.Error()) 202 | } else { 203 | asyncResult.completeSuccess() 204 | } 205 | }() 206 | 207 | return asyncResult 208 | } 209 | 210 | func (device *asyncDevice) UploadFile(filename string) AsyncResult { 211 | asyncResult := NewBooleanAsyncResult() 212 | go func() { 213 | // 构造获取文件上传URL的请求 214 | requestParas := FileRequestServiceEventParas{ 215 | FileName: filename, 216 | } 217 | 218 | serviceEvent := FileRequestServiceEvent{ 219 | Paras: requestParas, 220 | } 221 | serviceEvent.ServiceId = "$file_manager" 222 | serviceEvent.EventTime = GetEventTimeStamp() 223 | serviceEvent.EventType = "get_upload_url" 224 | 225 | var services []FileRequestServiceEvent 226 | services = append(services, serviceEvent) 227 | request := FileRequest{ 228 | Services: services, 229 | } 230 | 231 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 232 | glog.Warningf("publish file upload request url failed") 233 | asyncResult.completeError(&DeviceError{ 234 | errorMsg: "publish file upload request url failed", 235 | }) 236 | return 237 | } 238 | glog.Info("publish file upload request url success") 239 | 240 | ticker := time.Tick(time.Second) 241 | for { 242 | select { 243 | case <-ticker: 244 | _, ok := device.base.fileUrls[filename+FileActionUpload] 245 | if ok { 246 | glog.Infof("platform send file upload url success") 247 | goto ENDFOR 248 | } 249 | 250 | } 251 | } 252 | ENDFOR: 253 | 254 | if len(device.base.fileUrls[filename+FileActionUpload]) == 0 { 255 | glog.Errorf("get file upload url failed") 256 | asyncResult.completeError(&DeviceError{ 257 | errorMsg: "get file upload url failed", 258 | }) 259 | return 260 | } 261 | glog.Infof("file upload url is %s", device.base.fileUrls[filename+FileActionUpload]) 262 | 263 | //filename = smartFileName(filename) 264 | uploadFlag := CreateHttpClient().UploadFile(filename, device.base.fileUrls[filename+FileActionUpload]) 265 | if !uploadFlag { 266 | glog.Errorf("upload file failed") 267 | asyncResult.completeError(&DeviceError{ 268 | errorMsg: "upload file failed", 269 | }) 270 | return 271 | } 272 | 273 | response := CreateFileUploadDownLoadResultResponse(filename, FileActionUpload, uploadFlag) 274 | 275 | token := device.base.Client.Publish(formatTopic(PlatformEventToDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(response)) 276 | if token.Wait() && token.Error() != nil { 277 | glog.Error("report file upload file result failed") 278 | asyncResult.completeError(token.Error()) 279 | } else { 280 | asyncResult.completeSuccess() 281 | } 282 | }() 283 | 284 | return asyncResult 285 | } 286 | 287 | func (device *asyncDevice) DownloadFile(filename string) AsyncResult { 288 | asyncResult := NewBooleanAsyncResult() 289 | go func() { 290 | // 构造获取文件上传URL的请求 291 | requestParas := FileRequestServiceEventParas{ 292 | FileName: filename, 293 | } 294 | 295 | serviceEvent := FileRequestServiceEvent{ 296 | Paras: requestParas, 297 | } 298 | serviceEvent.ServiceId = "$file_manager" 299 | serviceEvent.EventTime = GetEventTimeStamp() 300 | serviceEvent.EventType = "get_download_url" 301 | 302 | var services []FileRequestServiceEvent 303 | services = append(services, serviceEvent) 304 | request := FileRequest{ 305 | Services: services, 306 | } 307 | 308 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 309 | glog.Warningf("publish file download request url failed") 310 | asyncResult.completeError(&DeviceError{ 311 | errorMsg: "publish file download request url failed", 312 | }) 313 | return 314 | } 315 | 316 | ticker := time.Tick(time.Second) 317 | for { 318 | select { 319 | case <-ticker: 320 | _, ok := device.base.fileUrls[filename+FileActionDownload] 321 | if ok { 322 | glog.Infof("platform send file upload url success") 323 | goto ENDFOR 324 | } 325 | 326 | } 327 | } 328 | ENDFOR: 329 | 330 | if len(device.base.fileUrls[filename+FileActionDownload]) == 0 { 331 | glog.Errorf("get file download url failed") 332 | asyncResult.completeError(&DeviceError{ 333 | errorMsg: "get file download url failed", 334 | }) 335 | return 336 | } 337 | 338 | downloadFlag := CreateHttpClient().DownloadFile(filename, device.base.fileUrls[filename+FileActionDownload]) 339 | if !downloadFlag { 340 | glog.Errorf("down load file { %s } failed", filename) 341 | asyncResult.completeError(&DeviceError{ 342 | errorMsg: "down load file failedd", 343 | }) 344 | return 345 | } 346 | 347 | response := CreateFileUploadDownLoadResultResponse(filename, FileActionDownload, downloadFlag) 348 | 349 | token := device.base.Client.Publish(formatTopic(PlatformEventToDeviceTopic, device.base.Id), device.base.qos, false, Interface2JsonString(response)) 350 | if token.Wait() && token.Error() != nil { 351 | glog.Error("report file upload file result failed") 352 | asyncResult.completeError(token.Error()) 353 | } else { 354 | asyncResult.completeSuccess() 355 | } 356 | 357 | }() 358 | 359 | return asyncResult 360 | } 361 | 362 | func (device *asyncDevice) ReportDeviceInfo(swVersion, fwVersion string) AsyncResult { 363 | asyncResult := NewBooleanAsyncResult() 364 | go func() { 365 | event := ReportDeviceInfoServiceEvent{ 366 | BaseServiceEvent{ 367 | ServiceId: "$device_info", 368 | EventType: "device_info_report", 369 | EventTime: GetEventTimeStamp(), 370 | }, 371 | ReportDeviceInfoEventParas{ 372 | DeviceSdkVersion: SdkInfo()["sdk-version"], 373 | SwVersion: swVersion, 374 | FwVersion: fwVersion, 375 | }, 376 | } 377 | 378 | request := ReportDeviceInfoRequest{ 379 | ObjectDeviceId: device.base.Id, 380 | Services: []ReportDeviceInfoServiceEvent{event}, 381 | } 382 | 383 | token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)) 384 | if token.Wait() && token.Error() != nil { 385 | asyncResult.completeError(token.Error()) 386 | } else { 387 | asyncResult.completeSuccess() 388 | } 389 | 390 | }() 391 | 392 | return asyncResult 393 | } 394 | 395 | func (device *asyncDevice) ReportLogs(logs []DeviceLogEntry) AsyncResult { 396 | asyncresult := NewBooleanAsyncResult() 397 | 398 | go func() { 399 | var services []ReportDeviceLogServiceEvent 400 | 401 | for _, logEntry := range logs { 402 | service := ReportDeviceLogServiceEvent{ 403 | BaseServiceEvent: BaseServiceEvent{ 404 | ServiceId: "$log", 405 | EventType: "log_report", 406 | EventTime: GetEventTimeStamp(), 407 | }, 408 | Paras: logEntry, 409 | } 410 | 411 | services = append(services, service) 412 | } 413 | 414 | request := ReportDeviceLogRequest{ 415 | Services: services, 416 | } 417 | 418 | fmt.Println(Interface2JsonString(request)) 419 | 420 | topic := formatTopic(DeviceToPlatformTopic, device.base.Id) 421 | 422 | token := device.base.Client.Publish(topic, 1, false, Interface2JsonString(request)) 423 | 424 | if token.Wait() && token.Error() != nil { 425 | glog.Errorf("device %s report log failed", device.base.Id) 426 | asyncresult.completeError(token.Error()) 427 | } else { 428 | asyncresult.completeSuccess() 429 | } 430 | }() 431 | 432 | return asyncresult 433 | } 434 | 435 | func (device *asyncDevice) SetSubDevicesAddHandler(handler SubDevicesAddHandler) { 436 | device.base.subDevicesAddHandler = handler 437 | } 438 | 439 | func (device *asyncDevice) SetSubDevicesDeleteHandler(handler SubDevicesDeleteHandler) { 440 | device.base.subDevicesDeleteHandler = handler 441 | } 442 | 443 | func (device *asyncDevice) UpdateSubDeviceState(subDevicesStatus SubDevicesStatus) AsyncResult { 444 | glog.Infof("begin to update sub-devices status") 445 | 446 | asyncResult := NewBooleanAsyncResult() 447 | 448 | go func() { 449 | subDeviceCounts := len(subDevicesStatus.DeviceStatuses) 450 | 451 | batchUpdateSubDeviceState := 0 452 | if subDeviceCounts%device.base.batchSubDeviceSize == 0 { 453 | batchUpdateSubDeviceState = subDeviceCounts / device.base.batchSubDeviceSize 454 | } else { 455 | batchUpdateSubDeviceState = subDeviceCounts/device.base.batchSubDeviceSize + 1 456 | } 457 | 458 | for i := 0; i < batchUpdateSubDeviceState; i++ { 459 | begin := i * device.base.batchSubDeviceSize 460 | end := (i + 1) * device.base.batchSubDeviceSize 461 | if end > subDeviceCounts { 462 | end = subDeviceCounts 463 | } 464 | 465 | sds := SubDevicesStatus{ 466 | DeviceStatuses: subDevicesStatus.DeviceStatuses[begin:end], 467 | } 468 | 469 | requestEventService := DataEntry{ 470 | ServiceId: "$sub_device_manager", 471 | EventType: "sub_device_update_status", 472 | EventTime: GetEventTimeStamp(), 473 | Paras: sds, 474 | } 475 | 476 | request := Data{ 477 | ObjectDeviceId: device.base.Id, 478 | Services: []DataEntry{requestEventService}, 479 | } 480 | 481 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 482 | glog.Warningf("gateway %s update sub devices status failed", device.base.Id) 483 | asyncResult.completeError(token.Error()) 484 | return 485 | } 486 | } 487 | asyncResult.completeSuccess() 488 | glog.Info("gateway update sub devices status failed", device.base.Id) 489 | }() 490 | 491 | return asyncResult 492 | } 493 | 494 | func (device *asyncDevice) DeleteSubDevices(deviceIds []string) AsyncResult { 495 | glog.Infof("begin to delete sub-devices %s", deviceIds) 496 | 497 | asyncResult := NewBooleanAsyncResult() 498 | 499 | go func() { 500 | subDevices := struct { 501 | Devices []string `json:"devices"` 502 | }{ 503 | Devices: deviceIds, 504 | } 505 | 506 | requestEventService := DataEntry{ 507 | ServiceId: "$sub_device_manager", 508 | EventType: "delete_sub_device_request", 509 | EventTime: GetEventTimeStamp(), 510 | Paras: subDevices, 511 | } 512 | 513 | request := Data{ 514 | ObjectDeviceId: device.base.Id, 515 | Services: []DataEntry{requestEventService}, 516 | } 517 | 518 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 519 | glog.Warningf("gateway %s delete sub devices request send failed", device.base.Id) 520 | asyncResult.completeError(token.Error()) 521 | } else { 522 | asyncResult.completeSuccess() 523 | } 524 | 525 | glog.Warningf("gateway %s delete sub devices request send success", device.base.Id) 526 | }() 527 | 528 | return asyncResult 529 | } 530 | 531 | func (device *asyncDevice) AddSubDevices(deviceInfos []DeviceInfo) AsyncResult { 532 | asyncResult := NewBooleanAsyncResult() 533 | 534 | go func() { 535 | devices := struct { 536 | Devices []DeviceInfo `json:"devices"` 537 | }{ 538 | Devices: deviceInfos, 539 | } 540 | 541 | requestEventService := DataEntry{ 542 | ServiceId: "$sub_device_manager", 543 | EventType: "add_sub_device_request", 544 | EventTime: GetEventTimeStamp(), 545 | Paras: devices, 546 | } 547 | 548 | request := Data{ 549 | ObjectDeviceId: device.base.Id, 550 | Services: []DataEntry{requestEventService}, 551 | } 552 | 553 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(request)); token.Wait() && token.Error() != nil { 554 | glog.Warningf("gateway %s add sub devices request send failed", device.base.Id) 555 | asyncResult.completeError(token.Error()) 556 | } else { 557 | asyncResult.completeSuccess() 558 | } 559 | 560 | glog.Warningf("gateway %s add sub devices request send success", device.base.Id) 561 | }() 562 | 563 | return asyncResult 564 | } 565 | 566 | func (device *asyncDevice) SyncAllVersionSubDevices() AsyncResult { 567 | asyncResult := NewBooleanAsyncResult() 568 | 569 | go func() { 570 | dataEntry := DataEntry{ 571 | ServiceId: "$sub_device_manager", 572 | EventType: "sub_device_sync_request", 573 | EventTime: GetEventTimeStamp(), 574 | Paras: struct { 575 | }{}, 576 | } 577 | 578 | var dataEntries []DataEntry 579 | dataEntries = append(dataEntries, dataEntry) 580 | 581 | data := Data{ 582 | Services: dataEntries, 583 | } 584 | 585 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(data)); token.Wait() && token.Error() != nil { 586 | asyncResult.completeError(token.Error()) 587 | } else { 588 | asyncResult.completeSuccess() 589 | } 590 | }() 591 | 592 | return asyncResult 593 | } 594 | 595 | func (device *asyncDevice) SyncSubDevices(version int) AsyncResult { 596 | asyncResult := NewBooleanAsyncResult() 597 | 598 | go func() { 599 | syncParas := struct { 600 | Version int `json:"version"` 601 | }{ 602 | Version: version, 603 | } 604 | 605 | dataEntry := DataEntry{ 606 | ServiceId: "$sub_device_manager", 607 | EventType: "sub_device_sync_request", 608 | EventTime: GetEventTimeStamp(), 609 | Paras: syncParas, 610 | } 611 | 612 | var dataEntries []DataEntry 613 | dataEntries = append(dataEntries, dataEntry) 614 | 615 | data := Data{ 616 | Services: dataEntries, 617 | } 618 | 619 | if token := device.base.Client.Publish(formatTopic(DeviceToPlatformTopic, device.base.Id), device.base.qos, false, Interface2JsonString(data)); token.Wait() && token.Error() != nil { 620 | glog.Errorf("send sync sub device request failed") 621 | asyncResult.completeError(token.Error()) 622 | } else { 623 | asyncResult.completeSuccess() 624 | } 625 | }() 626 | 627 | return asyncResult 628 | } 629 | -------------------------------------------------------------------------------- /base_device.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/json" 7 | "fmt" 8 | mqtt "github.com/eclipse/paho.mqtt.golang" 9 | "github.com/golang/glog" 10 | "io/ioutil" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | const ( 17 | // MessageDownTopic 平台下发消息topic 18 | MessageDownTopic string = "$oc/devices/{device_id}/sys/messages/down" 19 | 20 | // MessageUpTopic 设备上报消息topic 21 | MessageUpTopic string = "$oc/devices/{device_id}/sys/messages/up" 22 | 23 | // CommandDownTopic 平台下发命令topic 24 | CommandDownTopic string = "$oc/devices/{device_id}/sys/commands/#" 25 | 26 | // CommandResponseTopic 设备响应平台命令 27 | CommandResponseTopic string = "$oc/devices/{device_id}/sys/commands/response/request_id=" 28 | 29 | // PropertiesUpTopic 设备上报属性 30 | PropertiesUpTopic string = "$oc/devices/{device_id}/sys/properties/report" 31 | 32 | // PropertiesSetRequestTopic 平台设置属性topic 33 | PropertiesSetRequestTopic string = "$oc/devices/{device_id}/sys/properties/set/#" 34 | 35 | // PropertiesSetResponseTopic 设备响应平台属性设置topic 36 | PropertiesSetResponseTopic string = "$oc/devices/{device_id}/sys/properties/set/response/request_id=" 37 | 38 | // PropertiesQueryRequestTopic 平台查询设备属性 39 | PropertiesQueryRequestTopic string = "$oc/devices/{device_id}/sys/properties/get/#" 40 | 41 | // PropertiesQueryResponseTopic 设备响应平台属性查询 42 | PropertiesQueryResponseTopic string = "$oc/devices/{device_id}/sys/properties/get/response/request_id=" 43 | 44 | // DeviceShadowQueryRequestTopic 设备侧获取平台的设备影子数据 45 | DeviceShadowQueryRequestTopic string = "$oc/devices/{device_id}/sys/shadow/get/request_id=" 46 | 47 | // DeviceShadowQueryResponseTopic 设备侧响应获取平台设备影子 48 | DeviceShadowQueryResponseTopic string = "$oc/devices/{device_id}/sys/shadow/get/response/#" 49 | 50 | // GatewayBatchReportSubDeviceTopic 网关批量上报子设备属性 51 | GatewayBatchReportSubDeviceTopic string = "$oc/devices/{device_id}/sys/gateway/sub_devices/properties/report" 52 | 53 | // FileActionUpload 平台下发文件上传和下载URL 54 | FileActionUpload string = "upload" 55 | FileActionDownload string = "download" 56 | 57 | // DeviceToPlatformTopic 设备或网关向平台发送请求 58 | DeviceToPlatformTopic string = "$oc/devices/{device_id}/sys/events/up" 59 | 60 | // PlatformEventToDeviceTopic 平台向设备下发事件topic 61 | PlatformEventToDeviceTopic string = "$oc/devices/{device_id}/sys/events/down" 62 | ) 63 | 64 | const ( 65 | AuthTypePassword uint8 = 0 66 | AuthTypeX509 uint8 = 1 67 | ) 68 | 69 | type DeviceConfig struct { 70 | Id string 71 | Password string 72 | VerifyTimestamp bool 73 | Servers string 74 | Qos byte 75 | BatchSubDeviceSize int 76 | AuthType uint8 77 | ServerCaPath string 78 | CertFilePath string 79 | CertKeyFilePath string 80 | UseBootstrap bool // 使用设备引导功能开关,true-使用,false-不使用 81 | } 82 | 83 | type BaseDevice interface { 84 | Init() bool 85 | DisConnect() 86 | IsConnected() bool 87 | 88 | AddMessageHandler(handler MessageHandler) 89 | AddCommandHandler(handler CommandHandler) 90 | AddPropertiesSetHandler(handler DevicePropertiesSetHandler) 91 | SetPropertyQueryHandler(handler DevicePropertyQueryHandler) 92 | SetSwFwVersionReporter(handler SwFwVersionReporter) 93 | SetDeviceUpgradeHandler(handler DeviceUpgradeHandler) 94 | 95 | SetDeviceStatusLogCollector(collector DeviceStatusLogCollector) 96 | SetDevicePropertyLogCollector(collector DevicePropertyLogCollector) 97 | SetDeviceMessageLogCollector(collector DeviceMessageLogCollector) 98 | SetDeviceCommandLogCollector(collector DeviceCommandLogCollector) 99 | } 100 | 101 | type LogCollectionConfig struct { 102 | rw sync.RWMutex 103 | logCollectSwitch bool //on:开启设备侧日志收集功能 off:关闭设备侧日志收集开关 104 | endTime string // format yyyy-MM-dd'T'HH:mm:ss'Z' 105 | } 106 | 107 | func (lcc *LogCollectionConfig) setLogCollectSwitch(switchFlag bool) { 108 | lcc.rw.Lock() 109 | defer lcc.rw.Unlock() 110 | lcc.logCollectSwitch = switchFlag 111 | } 112 | 113 | func (lcc *LogCollectionConfig) setEndTime(endTime string) { 114 | lcc.rw.Lock() 115 | defer lcc.rw.Unlock() 116 | lcc.endTime = endTime 117 | } 118 | 119 | func (lcc *LogCollectionConfig) getLogCollectSwitch() bool { 120 | lcc.rw.RLock() 121 | defer lcc.rw.RUnlock() 122 | return lcc.logCollectSwitch 123 | } 124 | 125 | func (lcc *LogCollectionConfig) getEndTime() string { 126 | lcc.rw.RLock() 127 | defer lcc.rw.RUnlock() 128 | return lcc.endTime 129 | } 130 | 131 | type baseIotDevice struct { 132 | Id string // 设备Id,平台又称为deviceId 133 | Password string // 设备密码 134 | VerifyTimestamp bool 135 | AuthType uint8 // 鉴权类型,0:密码认证;1:x.509证书认证 136 | ServerCaPath string // 平台CA证书 137 | CertFilePath string // 设备证书路径 138 | CertKeyFilePath string // 设备证书key路径 139 | Servers string 140 | Client mqtt.Client 141 | commandHandler CommandHandler 142 | messageHandlers []MessageHandler 143 | propertiesSetHandlers []DevicePropertiesSetHandler 144 | propertyQueryHandler DevicePropertyQueryHandler 145 | propertiesQueryResponseHandler DevicePropertyQueryResponseHandler 146 | subDevicesAddHandler SubDevicesAddHandler 147 | subDevicesDeleteHandler SubDevicesDeleteHandler 148 | swFwVersionReporter SwFwVersionReporter 149 | deviceUpgradeHandler DeviceUpgradeHandler 150 | fileUrls map[string]string 151 | qos byte 152 | batchSubDeviceSize int 153 | lcc *LogCollectionConfig 154 | deviceStatusLogCollector DeviceStatusLogCollector 155 | devicePropertyLogCollector DevicePropertyLogCollector 156 | deviceMessageLogCollector DeviceMessageLogCollector 157 | deviceCommandLogCollector DeviceCommandLogCollector 158 | useBootstrap bool 159 | } 160 | 161 | func (device *baseIotDevice) DisConnect() { 162 | if device.Client != nil { 163 | device.Client.Disconnect(0) 164 | } 165 | } 166 | func (device *baseIotDevice) IsConnected() bool { 167 | if device.Client != nil { 168 | return device.Client.IsConnectionOpen() 169 | } 170 | 171 | return false 172 | } 173 | 174 | func (device *baseIotDevice) Init() bool { 175 | options := mqtt.NewClientOptions() 176 | if device.useBootstrap { 177 | bootstracpClient, err := NewBootstrapClient(device.Id, device.Password) 178 | if err != nil { 179 | fmt.Printf("create bootstrap client failed,err %s\n", err) 180 | return false 181 | } 182 | 183 | serverAddress := bootstracpClient.Boot() 184 | if len(serverAddress) == 0 { 185 | fmt.Println("get server address from bootstrap server failed") 186 | return false 187 | } 188 | options.AddBroker(serverAddress) 189 | } else { 190 | options.AddBroker(device.Servers) 191 | } 192 | 193 | options.SetClientID(assembleClientId(device)) 194 | options.SetUsername(device.Id) 195 | options.SetPassword(hmacSha256(device.Password, timeStamp())) 196 | options.SetKeepAlive(250 * time.Second) 197 | options.SetAutoReconnect(true) 198 | options.SetConnectRetry(true) 199 | options.SetConnectTimeout(2 * time.Second) 200 | if strings.Contains(device.Servers, "tls") || strings.Contains(device.Servers, "ssl") { 201 | glog.Infof("server support tls connection") 202 | 203 | // 设备使用x.509证书认证 204 | if device.AuthType == AuthTypeX509 { 205 | if len(device.ServerCaPath) == 0 || len(device.CertFilePath) == 0 || len(device.CertKeyFilePath) == 0 { 206 | glog.Error("device use x.509 auth but not set cert") 207 | panic("not set cert") 208 | } 209 | 210 | ca, err := ioutil.ReadFile(device.ServerCaPath) 211 | if err != nil { 212 | glog.Error("load server ca failed\n") 213 | panic(err) 214 | } 215 | serverCaPool := x509.NewCertPool() 216 | serverCaPool.AppendCertsFromPEM(ca) 217 | 218 | deviceCert, err := tls.LoadX509KeyPair(device.CertFilePath, device.CertKeyFilePath) 219 | if err != nil { 220 | glog.Error("load device cert failed") 221 | panic("load device cert failed") 222 | } 223 | var clientCerts []tls.Certificate 224 | clientCerts = append(clientCerts, deviceCert) 225 | 226 | cipherSuites := []uint16{ 227 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 228 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 229 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 230 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 231 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 232 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 233 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 234 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 235 | } 236 | tlsConfig := &tls.Config{ 237 | RootCAs: serverCaPool, 238 | Certificates: clientCerts, 239 | InsecureSkipVerify: true, 240 | MaxVersion: tls.VersionTLS12, 241 | MinVersion: tls.VersionTLS12, 242 | CipherSuites: cipherSuites, 243 | } 244 | options.SetTLSConfig(tlsConfig) 245 | 246 | } 247 | 248 | if device.AuthType == 0 { 249 | options.SetTLSConfig(&tls.Config{ 250 | InsecureSkipVerify: true, 251 | }) 252 | } 253 | 254 | } else { 255 | options.SetTLSConfig(&tls.Config{ 256 | InsecureSkipVerify: true, 257 | }) 258 | } 259 | 260 | device.Client = mqtt.NewClient(options) 261 | if token := device.Client.Connect(); token.Wait() && token.Error() != nil { 262 | glog.Warningf("device %s init failed,error = %v", device.Id, token.Error()) 263 | return false 264 | } 265 | 266 | device.subscribeDefaultTopics() 267 | 268 | go logFlush() 269 | 270 | return true 271 | 272 | } 273 | 274 | func (device *baseIotDevice) AddMessageHandler(handler MessageHandler) { 275 | if handler == nil { 276 | return 277 | } 278 | device.messageHandlers = append(device.messageHandlers, handler) 279 | } 280 | func (device *baseIotDevice) AddCommandHandler(handler CommandHandler) { 281 | if handler == nil { 282 | return 283 | } 284 | 285 | device.commandHandler = handler 286 | } 287 | func (device *baseIotDevice) AddPropertiesSetHandler(handler DevicePropertiesSetHandler) { 288 | if handler == nil { 289 | return 290 | } 291 | device.propertiesSetHandlers = append(device.propertiesSetHandlers, handler) 292 | } 293 | func (device *baseIotDevice) SetSwFwVersionReporter(handler SwFwVersionReporter) { 294 | device.swFwVersionReporter = handler 295 | } 296 | 297 | func (device *baseIotDevice) SetDeviceUpgradeHandler(handler DeviceUpgradeHandler) { 298 | device.deviceUpgradeHandler = handler 299 | } 300 | 301 | func (device *baseIotDevice) SetPropertyQueryHandler(handler DevicePropertyQueryHandler) { 302 | device.propertyQueryHandler = handler 303 | } 304 | 305 | func (device *baseIotDevice) SetDeviceStatusLogCollector(collector DeviceStatusLogCollector) { 306 | device.deviceStatusLogCollector = collector 307 | } 308 | 309 | func (device *baseIotDevice) SetDevicePropertyLogCollector(collector DevicePropertyLogCollector) { 310 | device.devicePropertyLogCollector = collector 311 | } 312 | 313 | func (device *baseIotDevice) SetDeviceMessageLogCollector(collector DeviceMessageLogCollector) { 314 | device.deviceMessageLogCollector = collector 315 | } 316 | 317 | func (device *baseIotDevice) SetDeviceCommandLogCollector(collector DeviceCommandLogCollector) { 318 | device.deviceCommandLogCollector = collector 319 | } 320 | 321 | func assembleClientId(device *baseIotDevice) string { 322 | segments := make([]string, 4) 323 | segments[0] = device.Id 324 | segments[1] = "0" 325 | if device.VerifyTimestamp { 326 | segments[2] = "1" 327 | } else { 328 | segments[2] = "0" 329 | } 330 | segments[3] = timeStamp() 331 | 332 | return strings.Join(segments, "_") 333 | } 334 | 335 | func logFlush() { 336 | ticker := time.Tick(5 * time.Second) 337 | for { 338 | select { 339 | case <-ticker: 340 | glog.Flush() 341 | } 342 | } 343 | } 344 | 345 | func (device *baseIotDevice) createCommandMqttHandler() func(client mqtt.Client, message mqtt.Message) { 346 | commandHandler := func(client mqtt.Client, message mqtt.Message) { 347 | go func() { 348 | command := &Command{} 349 | if json.Unmarshal(message.Payload(), command) != nil { 350 | glog.Warningf("unmarshal platform command failed,device id = %s,message = %s", device.Id, message) 351 | } 352 | 353 | flag, response := device.commandHandler(*command) 354 | var res string 355 | if flag { 356 | glog.Infof("device %s handle command success", device.Id) 357 | res = Interface2JsonString(CommandResponse{ 358 | ResultCode: 0, 359 | Paras: response, 360 | }) 361 | } else { 362 | glog.Warningf("device %s handle command failed", device.Id) 363 | res = Interface2JsonString(CommandResponse{ 364 | ResultCode: 1, 365 | Paras: response, 366 | }) 367 | } 368 | if token := device.Client.Publish(formatTopic(CommandResponseTopic, device.Id)+getTopicRequestId(message.Topic()), 1, false, res); token.Wait() && token.Error() != nil { 369 | glog.Infof("device %s send command response failed", device.Id) 370 | } 371 | }() 372 | 373 | } 374 | 375 | return commandHandler 376 | } 377 | 378 | func (device *baseIotDevice) createPropertiesSetMqttHandler() func(client mqtt.Client, message mqtt.Message) { 379 | propertiesSetHandler := func(client mqtt.Client, message mqtt.Message) { 380 | go func() { 381 | propertiesSetRequest := &DevicePropertyDownRequest{} 382 | if json.Unmarshal(message.Payload(), propertiesSetRequest) != nil { 383 | glog.Warningf("unmarshal platform properties set request failed,device id = %s,message = %s", device.Id, message) 384 | } 385 | 386 | handleFlag := true 387 | for _, handler := range device.propertiesSetHandlers { 388 | handleFlag = handleFlag && handler(*propertiesSetRequest) 389 | } 390 | 391 | var res string 392 | response := struct { 393 | ResultCode byte `json:"result_code"` 394 | ResultDesc string `json:"result_desc"` 395 | }{} 396 | if handleFlag { 397 | response.ResultCode = 0 398 | response.ResultDesc = "Set property success." 399 | res = Interface2JsonString(response) 400 | } else { 401 | response.ResultCode = 1 402 | response.ResultDesc = "Set properties failed." 403 | res = Interface2JsonString(response) 404 | } 405 | if token := device.Client.Publish(formatTopic(PropertiesSetResponseTopic, device.Id)+getTopicRequestId(message.Topic()), device.qos, false, res); token.Wait() && token.Error() != nil { 406 | glog.Warningf("unmarshal platform properties set request failed,device id = %s,message = %s", device.Id, message) 407 | } 408 | }() 409 | } 410 | 411 | return propertiesSetHandler 412 | } 413 | 414 | func (device *baseIotDevice) createMessageMqttHandler() func(client mqtt.Client, message mqtt.Message) { 415 | messageHandler := func(client mqtt.Client, message mqtt.Message) { 416 | go func() { 417 | msg := &Message{} 418 | if json.Unmarshal(message.Payload(), msg) != nil { 419 | glog.Warningf("unmarshal device message failed,device id = %s,message = %s", device.Id, message) 420 | } 421 | 422 | for _, handler := range device.messageHandlers { 423 | handler(*msg) 424 | } 425 | }() 426 | } 427 | 428 | return messageHandler 429 | } 430 | 431 | func (device *baseIotDevice) createPropertiesQueryMqttHandler() func(client mqtt.Client, message mqtt.Message) { 432 | propertiesQueryHandler := func(client mqtt.Client, message mqtt.Message) { 433 | go func() { 434 | propertiesQueryRequest := &DevicePropertyQueryRequest{} 435 | if json.Unmarshal(message.Payload(), propertiesQueryRequest) != nil { 436 | glog.Warningf("device %s unmarshal properties query request failed %s", device.Id, message) 437 | } 438 | 439 | queryResult := device.propertyQueryHandler(*propertiesQueryRequest) 440 | responseToPlatform := Interface2JsonString(queryResult) 441 | if token := device.Client.Publish(formatTopic(PropertiesQueryResponseTopic, device.Id)+getTopicRequestId(message.Topic()), device.qos, false, responseToPlatform); token.Wait() && token.Error() != nil { 442 | glog.Warningf("device %s send properties query response failed.", device.Id) 443 | } 444 | }() 445 | } 446 | 447 | return propertiesQueryHandler 448 | } 449 | 450 | func (device *baseIotDevice) createPropertiesQueryResponseMqttHandler() func(client mqtt.Client, message mqtt.Message) { 451 | propertiesQueryResponseHandler := func(client mqtt.Client, message mqtt.Message) { 452 | propertiesQueryResponse := &DevicePropertyQueryResponse{} 453 | if json.Unmarshal(message.Payload(), propertiesQueryResponse) != nil { 454 | glog.Warningf("device %s unmarshal property response failed,message %s", device.Id, Interface2JsonString(message)) 455 | } 456 | device.propertiesQueryResponseHandler(*propertiesQueryResponse) 457 | } 458 | 459 | return propertiesQueryResponseHandler 460 | } 461 | 462 | func (device *baseIotDevice) subscribeDefaultTopics() { 463 | // 订阅平台命令下发topic 464 | topic := formatTopic(CommandDownTopic, device.Id) 465 | if token := device.Client.Subscribe(topic, device.qos, device.createCommandMqttHandler()); token.Wait() && token.Error() != nil { 466 | glog.Warningf("device %s subscribe platform send command topic %s failed", device.Id, topic) 467 | panic(0) 468 | } 469 | 470 | // 订阅平台消息下发的topic 471 | topic = formatTopic(MessageDownTopic, device.Id) 472 | if token := device.Client.Subscribe(topic, device.qos, device.createMessageMqttHandler()); token.Wait() && token.Error() != nil { 473 | glog.Warningf("device % subscribe platform send message topic %s failed.", device.Id, topic) 474 | panic(0) 475 | } 476 | 477 | // 订阅平台设置设备属性的topic 478 | topic = formatTopic(PropertiesSetRequestTopic, device.Id) 479 | if token := device.Client.Subscribe(topic, device.qos, device.createPropertiesSetMqttHandler()); token.Wait() && token.Error() != nil { 480 | glog.Warningf("device %s subscribe platform set properties topic %s failed", device.Id, topic) 481 | panic(0) 482 | } 483 | 484 | // 订阅平台查询设备属性的topic 485 | topic = formatTopic(PropertiesQueryRequestTopic, device.Id) 486 | if token := device.Client.Subscribe(topic, device.qos, device.createPropertiesQueryMqttHandler()); token.Wait() && token.Error() != nil { 487 | glog.Warningf("device %s subscriber platform query device properties topic failed %s", device.Id, topic) 488 | panic(0) 489 | } 490 | 491 | // 订阅查询设备影子响应的topic 492 | topic = formatTopic(DeviceShadowQueryResponseTopic, device.Id) 493 | if token := device.Client.Subscribe(topic, device.qos, device.createPropertiesQueryResponseMqttHandler()); token.Wait() && token.Error() != nil { 494 | glog.Warningf("device %s subscribe query device shadow topic %s failed", device.Id, topic) 495 | panic(0) 496 | } 497 | 498 | // 订阅平台下发到设备的事件 499 | topic = formatTopic(PlatformEventToDeviceTopic, device.Id) 500 | if token := device.Client.Subscribe(topic, device.qos, device.handlePlatformToDeviceData()); token.Wait() && token.Error() != nil { 501 | glog.Warningf("device %s subscribe query device shadow topic %s failed", device.Id, topic) 502 | panic(0) 503 | } 504 | 505 | } 506 | 507 | // 平台向设备下发的事件callback 508 | func (device *baseIotDevice) handlePlatformToDeviceData() func(client mqtt.Client, message mqtt.Message) { 509 | handler := func(client mqtt.Client, message mqtt.Message) { 510 | data := &Data{} 511 | err := json.Unmarshal(message.Payload(), data) 512 | if err != nil { 513 | fmt.Println(err) 514 | return 515 | } 516 | 517 | for _, entry := range data.Services { 518 | eventType := entry.EventType 519 | switch eventType { 520 | case "add_sub_device_notify": 521 | // 子设备添加 522 | subDeviceInfo := &SubDeviceInfo{} 523 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), subDeviceInfo) != nil { 524 | continue 525 | } 526 | device.subDevicesAddHandler(*subDeviceInfo) 527 | case "delete_sub_device_notify": 528 | subDeviceInfo := &SubDeviceInfo{} 529 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), subDeviceInfo) != nil { 530 | continue 531 | } 532 | device.subDevicesDeleteHandler(*subDeviceInfo) 533 | 534 | case "get_upload_url_response": 535 | //获取文件上传URL 536 | fileResponse := &FileResponseServiceEventParas{} 537 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), fileResponse) != nil { 538 | continue 539 | } 540 | device.fileUrls[fileResponse.ObjectName+FileActionUpload] = fileResponse.Url 541 | case "get_download_url_response": 542 | fileResponse := &FileResponseServiceEventParas{} 543 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), fileResponse) != nil { 544 | continue 545 | } 546 | device.fileUrls[fileResponse.ObjectName+FileActionDownload] = fileResponse.Url 547 | case "version_query": 548 | // 查询软固件版本 549 | device.reportVersion() 550 | 551 | case "firmware_upgrade": 552 | upgradeInfo := &UpgradeInfo{} 553 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), upgradeInfo) != nil { 554 | continue 555 | } 556 | device.upgradeDevice(1, upgradeInfo) 557 | 558 | case "software_upgrade": 559 | upgradeInfo := &UpgradeInfo{} 560 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), upgradeInfo) != nil { 561 | continue 562 | } 563 | device.upgradeDevice(0, upgradeInfo) 564 | 565 | case "log_config": 566 | // 平台下发日志收集通知 567 | fmt.Println("platform send log collect command") 568 | logConfig := &LogCollectionConfig{} 569 | if json.Unmarshal([]byte(Interface2JsonString(entry.Paras)), logConfig) != nil { 570 | continue 571 | } 572 | 573 | lcc := &LogCollectionConfig{ 574 | logCollectSwitch: logConfig.logCollectSwitch, 575 | endTime: logConfig.endTime, 576 | } 577 | device.lcc = lcc 578 | device.reportLogsWorker() 579 | } 580 | } 581 | 582 | } 583 | 584 | return handler 585 | } 586 | 587 | func (device *baseIotDevice) reportLogsWorker() { 588 | go func() { 589 | for { 590 | if !device.lcc.getLogCollectSwitch() { 591 | break 592 | } 593 | logs := device.deviceStatusLogCollector(device.lcc.getEndTime()) 594 | if len(logs) == 0 { 595 | fmt.Println("no log about device status") 596 | break 597 | } 598 | device.reportLogs(logs) 599 | } 600 | 601 | }() 602 | 603 | go func() { 604 | for { 605 | if !device.lcc.getLogCollectSwitch() { 606 | break 607 | } 608 | logs := device.devicePropertyLogCollector(device.lcc.getEndTime()) 609 | if len(logs) == 0 { 610 | fmt.Println("no log about device property") 611 | break 612 | } 613 | device.reportLogs(logs) 614 | } 615 | 616 | }() 617 | 618 | go func() { 619 | for { 620 | if !device.lcc.getLogCollectSwitch() { 621 | break 622 | } 623 | logs := device.deviceMessageLogCollector(device.lcc.getEndTime()) 624 | if len(logs) == 0 { 625 | fmt.Println("no log about device message") 626 | break 627 | } 628 | device.reportLogs(logs) 629 | } 630 | 631 | }() 632 | 633 | go func() { 634 | for { 635 | if !device.lcc.getLogCollectSwitch() { 636 | break 637 | } 638 | logs := device.deviceCommandLogCollector(device.lcc.getEndTime()) 639 | if len(logs) == 0 { 640 | fmt.Println("no log about device command") 641 | break 642 | } 643 | device.reportLogs(logs) 644 | } 645 | 646 | }() 647 | 648 | } 649 | 650 | func (device *baseIotDevice) reportLogs(logs []DeviceLogEntry) { 651 | var dataEntries []DataEntry 652 | for _, log := range logs { 653 | dataEntry := DataEntry{ 654 | ServiceId: "$log", 655 | EventType: "log_report", 656 | EventTime: GetEventTimeStamp(), 657 | Paras: log, 658 | } 659 | dataEntries = append(dataEntries, dataEntry) 660 | } 661 | data := Data{ 662 | Services: dataEntries, 663 | } 664 | 665 | reportedLog := Interface2JsonString(data) 666 | device.Client.Publish(formatTopic(DeviceToPlatformTopic, device.Id), 0, false, reportedLog) 667 | } 668 | 669 | func (device *baseIotDevice) reportVersion() { 670 | sw, fw := device.swFwVersionReporter() 671 | dataEntry := DataEntry{ 672 | ServiceId: "$ota", 673 | EventType: "version_report", 674 | EventTime: GetEventTimeStamp(), 675 | Paras: struct { 676 | SwVersion string `json:"sw_version"` 677 | FwVersion string `json:"fw_version"` 678 | }{ 679 | SwVersion: sw, 680 | FwVersion: fw, 681 | }, 682 | } 683 | data := Data{ 684 | ObjectDeviceId: device.Id, 685 | Services: []DataEntry{dataEntry}, 686 | } 687 | 688 | device.Client.Publish(formatTopic(DeviceToPlatformTopic, device.Id), device.qos, false, Interface2JsonString(data)) 689 | } 690 | 691 | func (device *baseIotDevice) upgradeDevice(upgradeType byte, upgradeInfo *UpgradeInfo) { 692 | progress := device.deviceUpgradeHandler(upgradeType, *upgradeInfo) 693 | dataEntry := DataEntry{ 694 | ServiceId: "$ota", 695 | EventType: "upgrade_progress_report", 696 | EventTime: GetEventTimeStamp(), 697 | Paras: progress, 698 | } 699 | data := Data{ 700 | ObjectDeviceId: device.Id, 701 | Services: []DataEntry{dataEntry}, 702 | } 703 | 704 | if token := device.Client.Publish(formatTopic(DeviceToPlatformTopic, device.Id), device.qos, false, Interface2JsonString(data)); token.Wait() && token.Error() != nil { 705 | glog.Errorf("device %s upgrade failed,type %d", device.Id, upgradeType) 706 | } 707 | } 708 | --------------------------------------------------------------------------------