支持文件上传功能

This commit is contained in:
ctlove0523 2020-12-22 23:35:42 +08:00
parent 71e032de65
commit 11cfffae71
13 changed files with 453 additions and 52 deletions

View File

@ -2,15 +2,16 @@
huaweicloud-iot-device-sdk-go提供设备接入华为云IoT物联网平台的Go版本的SDK提供设备和平台之间通讯能力以及设备服务、网关服务、OTA等高级服务。IoT设备开发者使用SDK可以大大简化开发复杂度快速的接入平台。
## 安装和构建
安装和构建的过程取决于你是使用go的 [modules](https://golang.org/ref/mod)(推荐) 还是还是`GOPATH`
安装和构建的过程取决于你是使用go的 [modules](https://golang.org/ref/mod)(推荐) 还是还是`GOPATH`
### Modules
如果你使用 [modules](https://golang.org/ref/mod) 只需要导入包"github.com/ctlove0523/huaweicloud-iot-device-sdk-go"即可使用。当你使用go build命令构建项目时依赖的包会自动被下载。注意使用go build命令构建时会自动下载最新版本最新版本还没有达到release的标准可能存在一些尚未修复的bug。如果想使用稳定的发布版本可以从[release](https://github.com/ctlove0523/huaweicloud-iot-device-sdk-go/releases) 获取最新稳定的版本号并在go.mod文件中指定版本号。
如果你使用 [modules](https://golang.org/ref/mod) 只需要导入包"github.com/ctlove0523/huaweicloud-iot-device-sdk-go"即可使用。当你使用go
build命令构建项目时依赖的包会自动被下载。注意使用go
build命令构建时会自动下载最新版本最新版本还没有达到release的标准可能存在一些尚未修复的bug。如果想使用稳定的发布版本可以从[release](https://github.com/ctlove0523/huaweicloud-iot-device-sdk-go/releases)
获取最新稳定的版本号并在go.mod文件中指定版本号。
~~~go
module example
@ -28,8 +29,6 @@ require github.com/ctlove0523/huaweicloud-iot-device-sdk-go v0.0.1-alpha
go get github.com/ctlove0523/huaweicloud-iot-device-sdk-go
~~~
## 使用API
### 创建设备并初始化
@ -63,8 +62,6 @@ func main() {
> iot-mqtts.cn-north-4.myhuaweicloud.com为华为IoT平台基础班在华为云北京四的访问端点如果你购买了标准版或企业版请将iot-mqtts.cn-north-4.myhuaweicloud.com更换为对应的MQTT协议接入端点。
### 设备处理平台下发的命令
1、首先在华为云IoT平台创建一个设备设备的信息如下
@ -143,8 +140,6 @@ func main() {
}
~~~
> 设备支持的命令定义在产品中
### 设备消息
@ -232,8 +227,6 @@ func main() {
}
~~~
### 设备属性
1、首先在华为云IoT平台创建一个设备并在该设备下创建3个子设备设备及子设备的信息如下
@ -257,8 +250,6 @@ device.Init()
fmt.Printf("device connected: %v\n", device.IsConnected())
~~~
#### 设备属性上报
使用`ReportProperties(properties ServiceProperty) bool` 上报设备属性
@ -282,8 +273,6 @@ services := iot.ServiceProperty{
device.ReportProperties(services)
~~~
#### 网关批量设备属性上报
使用`BatchReportSubDevicesProperties(service DevicesService)` 实现网关批量设备属性上报
@ -312,8 +301,6 @@ device.BatchReportSubDevicesProperties(iot.DevicesService{
})
~~~
#### 平台设置设备属性
使用`AddPropertiesSetHandler(handler DevicePropertiesSetHandler)` 注册平台设置设备属性handler当接收到平台的命令时SDK回调。
@ -327,8 +314,6 @@ device.AddPropertiesSetHandler(func(propertiesSetRequest iot.DevicePropertyDownR
})
~~~
#### 平台查询设备属性
使用`SetPropertyQueryHandler(handler DevicePropertyQueryHandler)`注册平台查询设备属性handler当接收到平台的查询请求时SDK回调。
@ -347,11 +332,10 @@ device.SetPropertyQueryHandler(func(query iot.DevicePropertyQueryRequest) iot.Se
})
~~~
#### 设备侧获取平台的设备影子数据
使用`QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler)` 可以查询平台的设备影子数据当接收到平台的响应后SDK自动回调`DevicePropertyQueryResponseHandler`。
使用`QueryDeviceShadow(query DevicePropertyQueryRequest, handler DevicePropertyQueryResponseHandler)`
可以查询平台的设备影子数据当接收到平台的响应后SDK自动回调`DevicePropertyQueryResponseHandler`。
~~~go
// 设备查询设备影子数据
@ -450,8 +434,6 @@ type DemoProperties struct {
}
~~~
## 报告bugs
如果你在使用过程中遇到任何问题或bugs请通过issue的方式上报问题或bug我们将会在第一时间内答复。上报问题或bugs时请尽量提供以下内容

View File

@ -23,19 +23,26 @@ const (
PropertiesSetResponseTopic string = "$oc/devices/{device_id}/sys/properties/set/response/request_id="
// 平台查询设备属性
PropertiesQueryRequestTopicName string = "propertiesQueryRequestTopicName"
PropertiesQueryRequestTopic string = "$oc/devices/{device_id}/sys/properties/get/#"
PropertiesQueryRequestTopicName string = "propertiesQueryRequestTopicName"
PropertiesQueryRequestTopic string = "$oc/devices/{device_id}/sys/properties/get/#"
PropertiesQueryResponseTopicName string = "propertiesQueryResponseTopicName"
PropertiesQueryResponseTopic string = "$oc/devices/{device_id}/sys/properties/get/response/request_id="
// 设备侧获取平台的设备影子数据
DeviceShadowQueryRequestTopicName string = "deviceShadowQueryRequestTopicName"
DeviceShadowQueryRequestTopic string = "$oc/devices/{device_id}/sys/shadow/get/request_id="
DeviceShadowQueryRequestTopicName string = "deviceShadowQueryRequestTopicName"
DeviceShadowQueryRequestTopic string = "$oc/devices/{device_id}/sys/shadow/get/request_id="
DeviceShadowQueryResponseTopicName string = "deviceShadowQueryResponseTopicName"
DeviceShadowQueryResponseTopic string = "$oc/devices/{device_id}/sys/shadow/get/response/#"
DeviceShadowQueryResponseTopic string = "$oc/devices/{device_id}/sys/shadow/get/response/#"
// 网关批量上报子设备属性
GatewayBatchReportSubDeviceTopicName string = "gatewayBatchReportSubDeviceTopicName"
GatewayBatchReportSubDeviceTopic string = "$oc/devices/{device_id}/sys/gateway/sub_devices/properties/report"
)
GatewayBatchReportSubDeviceTopic string = "$oc/devices/{device_id}/sys/gateway/sub_devices/properties/report"
// 文件上传
FileUploadUrlRequestTopicName string = "fileUploadUrlRequestTopicName"
FileUploadUrlRequestTopic string = "$oc/devices/{device_id}/sys/events/up"
FileUploadUrlResponseTopicName string = "fileUploadUrlResponseTopicName"
FileUploadUrlResponseTopic string = "$oc/devices/{device_id}/sys/events/down"
FileUploadResultTopicName string = "FileUploadResultTopic"
FileUploadResultTopic string = "$oc/devices/{device_id}/sys/events/up"
)

View File

@ -119,3 +119,90 @@ type DeviceService struct {
DeviceId string `json:"device_id"`
Services []ServicePropertyEntry `json:"services"`
}
// 文件上传下载管理
func CreateFileUploadResultResponse(filename string, result bool) FileUploadResultResponse {
code := 0
if result {
code = 0
} else {
code = 1
}
paras := UploadFileResultResponseServiceEventParas{
ObjectName: filename,
ResultCode: code,
}
serviceEvent := UploadResultResponseServiceEvent{
Paras: paras,
}
serviceEvent.ServiceId = "$file_manager"
serviceEvent.EventType = "upload_result_report"
serviceEvent.EventTime = GetEventTimeStamp()
var services []UploadResultResponseServiceEvent
services = append(services, serviceEvent)
response := FileUploadResultResponse{
Services: services,
}
return response
}
type FileUploadUrlRequest struct {
ObjectDeviceId string `json:"object_device_id"`
Services []UploadRequestServiceEvent `json:"services"`
}
type FileUploadUrlResponse struct {
ObjectDeviceId string `json:"object_device_id"`
Services []UploadResponseServiceEvent `json:"services"`
}
type FileUploadResultResponse struct {
ObjectDeviceId string `json:"object_device_id"`
Services []UploadResultResponseServiceEvent `json:"services"`
}
type BaseServiceEvent struct {
ServiceId string `json:"service_id"`
EventType string `json:"event_type"`
EventTime string `json:"event_time"`
}
type UploadRequestServiceEvent struct {
BaseServiceEvent
Paras UploadRequestServiceEventParas `json:"paras"`
}
type UploadResponseServiceEvent struct {
BaseServiceEvent
Paras UploadResponseServiceEventParas `json:"paras"`
}
type UploadResultResponseServiceEvent struct {
BaseServiceEvent
Paras UploadFileResultResponseServiceEventParas `json:"paras"`
}
type UploadRequestServiceEventParas struct {
FileName string `json:"file_name"`
FileAttributes interface{} `json:"file_attributes"`
}
type UploadResponseServiceEventParas struct {
Url string `json:"url"`
BucketName string `json:"bucket_name"`
ObjectName string `json:"object_name"`
Expire int `json:"expire"`
FileAttributes interface{} `json:"file_attributes"`
}
type UploadFileResultResponseServiceEventParas struct {
ObjectName string `json:"object_name"`
ResultCode int `json:"result_code"`
StatusCode int `json:"status_code"`
StatusDescription string `json:"status_description"`
}

40
info.go Normal file
View File

@ -0,0 +1,40 @@
package iot
import (
"bufio"
"github.com/golang/glog"
"io"
"os"
"runtime"
"strings"
)
func OsName() string {
return runtime.GOOS
}
func SdkInfo() map[string]string {
f, err := os.Open("sdk_info")
if err != nil {
glog.Warning("read sdk info failed")
return map[string]string{}
}
// 文件很小
info := make(map[string]string)
buf := bufio.NewReader(f)
for {
b, _, err := buf.ReadLine()
if err != nil && err == io.EOF {
glog.Warningf("read sdk info failed or end")
break
}
line := string(b)
if len(line) != 0 {
parts := strings.Split(line, "=")
info[strings.Trim(parts[0], " ")] = strings.Trim(parts[1], " ")
}
}
return info
}

48
info_test.go Normal file
View File

@ -0,0 +1,48 @@
package iot
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"testing"
)
// 该测试用力仅能在Windows系统运行
func TestOsName(t *testing.T) {
if !strings.Contains(OsName(), "windows") {
t.Errorf(`OsName must be windwos`)
}
}
func TestVersion(t *testing.T) {
if SdkInfo()["sdk-version"] != "v0.0.1" {
t.Errorf("sdk version must be v0.0.1")
}
if SdkInfo()["author"] != "chen tong" {
t.Errorf("sdk author must be chen tong")
}
}
func TestCreateFileUploadResultResponse(t *testing.T) {
f, err := os.Open("sdk_info")
if err != nil {
fmt.Println(err.Error())
}
//建立缓冲区,把文件内容放到缓冲区中
buf := bufio.NewReader(f)
for {
//遇到\n结束读取
b, errR := buf.ReadBytes('\n')
if errR != nil {
if errR == io.EOF {
break
}
fmt.Println(errR.Error())
}
fmt.Println(string(b))
}
}

86
iot_http_client.go Normal file
View File

@ -0,0 +1,86 @@
package iot
import (
"bytes"
"github.com/golang/glog"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"time"
)
// 仅用于设备上传文件
type HttpClient interface {
UploadFile(filename, uri string) bool
}
type httpClient struct {
client *http.Client
}
func (client *httpClient) UploadFile(filename, uri string) bool {
bodyBuffer := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuffer)
fileWriter, err := bodyWriter.CreateFormFile("files", filename)
if err != nil {
glog.Errorf("create form file failed %v", err)
return false
}
file, err := os.Open(filename)
if err != nil {
glog.Errorf("open file failed %v", err)
return false
}
defer file.Close()
_, err = io.Copy(fileWriter, file)
if err != nil {
glog.Errorf("copy file to writer failed %v", err)
}
//contentType := bodyWriter.FormDataContentType()
defer bodyWriter.Close()
req, err := http.NewRequest("PUT", uri, bodyBuffer)
if err != nil {
glog.Errorf("create request filed %v", err)
}
req.Header.Add("Content-Type", "text/plain")
originalUri, err := url.ParseRequestURI(uri)
if err != nil {
glog.Errorf("parse request uri failed %v", err)
}
req.Header.Add("Host", originalUri.Host)
resp, _ := client.client.Do(req)
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
return err == nil
}
func CreateHttpClient() HttpClient {
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
innerClient := &http.Client{Transport: tr}
httpClient := &httpClient{
client: innerClient,
}
return httpClient
}

View File

@ -2,6 +2,7 @@ package iot
import (
"encoding/json"
"fmt"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/golang/glog"
"github.com/satori/go.uuid"
@ -20,6 +21,7 @@ type Device interface {
AddCommandHandler(handler CommandHandler)
AddPropertiesSetHandler(handler DevicePropertiesSetHandler)
SetPropertyQueryHandler(handler DevicePropertyQueryHandler)
UploadFile(filename string) bool
}
type iotDevice struct {
@ -35,6 +37,75 @@ type iotDevice struct {
topics map[string]string
}
type FileAtt struct {
HashCode string `json:"hash_code"`
Size int `json:"size"`
}
func (device *iotDevice) UploadFile(filename string) bool {
// first subscribe
uploadUrlChan := make(chan string)
device.client.Subscribe(device.topics[FileUploadUrlResponseTopicName], 1, func(client mqtt.Client, message mqtt.Message) {
response := &FileUploadUrlResponse{}
if json.Unmarshal(message.Payload(), response) != nil {
glog.Errorf("unmarshal file upload response failed")
uploadUrlChan <- ""
} else {
uploadUrlChan <- response.Services[0].Paras.Url
}
})
// 构造获取文件上传URL的请求
requestParas := UploadRequestServiceEventParas{
FileName: filename,
}
serviceEvent := UploadRequestServiceEvent{
Paras: requestParas,
}
serviceEvent.ServiceId = "$file_manager"
serviceEvent.EventTime = GetEventTimeStamp()
serviceEvent.EventType = "get_upload_url"
var services []UploadRequestServiceEvent
services = append(services, serviceEvent)
request := FileUploadUrlRequest{
Services: services,
}
if token := device.client.Publish(device.topics[FileUploadUrlRequestTopicName], 1, false, Interface2JsonString(request));
token.Wait() && token.Error() != nil {
glog.Warningf("publish file upload request url failed")
return false
}
upLoadUrl := <-uploadUrlChan
if len(upLoadUrl) == 0 {
glog.Errorf("get file upload url failed")
return false
}
glog.Infof("file upload url is %s", upLoadUrl)
filename = SmartFileName(filename)
uploadFlag := CreateHttpClient().UploadFile(filename, upLoadUrl)
if !uploadFlag {
glog.Errorf("upload file failed")
return false
}
response := CreateFileUploadResultResponse(filename, uploadFlag)
token := device.client.Publish(device.topics[FileUploadResultTopicName], 1, false, Interface2JsonString(response))
if token.Wait() && token.Error() != nil {
glog.Error("report file upload file result failed")
return false
}
return true
}
func (device *iotDevice) createMessageMqttHandler() func(client mqtt.Client, message mqtt.Message) {
messageHandler := func(client mqtt.Client, message mqtt.Message) {
msg := &Message{}
@ -141,6 +212,8 @@ func (device *iotDevice) Init() bool {
options.SetClientID(assembleClientId(device))
options.SetUsername(device.Id)
options.SetPassword(HmacSha256(device.Password, TimeStamp()))
fmt.Println(assembleClientId(device))
fmt.Println(HmacSha256(device.Password, TimeStamp()))
device.client = mqtt.NewClient(options)
@ -249,6 +322,9 @@ func CreateIotDevice(id, password, servers string) Device {
device.topics[DeviceShadowQueryRequestTopicName] = FormatTopic(DeviceShadowQueryRequestTopic, id)
device.topics[DeviceShadowQueryResponseTopicName] = FormatTopic(DeviceShadowQueryResponseTopic, id)
device.topics[GatewayBatchReportSubDeviceTopicName] = FormatTopic(GatewayBatchReportSubDeviceTopic, id)
device.topics[FileUploadUrlRequestTopicName] = FormatTopic(FileUploadUrlRequestTopic, id)
device.topics[FileUploadUrlResponseTopicName] = FormatTopic(FileUploadUrlResponseTopic, id)
device.topics[FileUploadResultTopicName] = FormatTopic(FileUploadResultTopic, id)
return device
}
@ -264,7 +340,7 @@ func assembleClientId(device *iotDevice) string {
func logFlush() {
ticker := time.Tick(5 * time.Second)
for{
for {
select {
case <-ticker:
glog.Flush()

View File

@ -0,0 +1,17 @@
package main
import (
iot "github.com/ctlove0523/huaweicloud-iot-device-sdk-go"
"time"
)
func main() {
//创建一个设备并初始化
device := iot.CreateIotDevice("5fdb75cccbfe2f02ce81d4bf_go-mqtt", "123456789", "tcp://iot-mqtts.cn-north-4.myhuaweicloud.com:1883")
device.Init()
device.UploadFile("D/software/mqttfx/chentong.txt")
time.Sleep(time.Hour)
}

View File

@ -1,17 +0,0 @@
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.Tick(5 * time.Second)
for ; ; {
select {
case <-ticker:
fmt.Println(time.Now().Second())
}
}
}

View File

@ -34,7 +34,7 @@ func main() {
// 设备上报属性
props := iot.ServicePropertyEntry{
ServiceId: "value",
EventTime: iot.DataCollectionTime(),
EventTime: iot.GetEventTimeStamp(),
Properties: DemoProperties{
Value: "chen tong",
MsgType: "23",

2
sdk_info Normal file
View File

@ -0,0 +1,2 @@
author = chen tong
sdk-version = v0.0.1

17
util.go
View File

@ -20,7 +20,7 @@ func TimeStamp() string {
// 设备采集数据UTC时间格式yyyyMMdd'T'HHmmss'Z'20161219T114920Z。
//设备上报数据不带该参数或参数格式错误时,则数据上报时间以平台时间为准。
func DataCollectionTime() string {
func GetEventTimeStamp() string {
now := time.Now().UTC()
return now.Format("20060102T150405Z")
}
@ -32,6 +32,9 @@ func HmacSha256(data string, secret string) string {
}
func Interface2JsonString(v interface{}) string {
if v == nil {
return ""
}
byteData, err := json.Marshal(v)
if err != nil {
return ""
@ -46,3 +49,15 @@ func GetTopicRequestId(topic string) string {
func FormatTopic(topic, deviceId string) string {
return strings.ReplaceAll(topic, "{device_id}", deviceId)
}
// 根据当前运行的操作系统重新修改文件路径以适配操作系统
func SmartFileName(filename string) string {
// Windows操作系统适配
if strings.Contains(OsName(), "windows") {
pathParts := strings.Split(filename, "/")
pathParts[0] = pathParts[0] + ":"
return strings.Join(pathParts, "\\\\")
}
return filename
}

58
util_test.go Normal file
View File

@ -0,0 +1,58 @@
package iot
import (
"testing"
)
func TestTimeStamp(t *testing.T) {
timeStamp := TimeStamp()
if len(timeStamp) != 10 {
t.Error(`Time Stamp length must be 10`)
}
}
func TestDataCollectionTime(t *testing.T) {
if len(GetEventTimeStamp()) != 16 {
t.Errorf(`Data Collection Time length must be 16,but is %d`, len(GetEventTimeStamp()))
}
}
func TestHmacSha256(t *testing.T) {
encodedPassword := "c0fefa1341fb0647290e93f641a9bcea74cd32111668cdc5f7418553640a55cc"
if HmacSha256("123456789", "202012222200") != encodedPassword {
t.Errorf("encoded password must be %s but is %s", encodedPassword, HmacSha256("123456789", "202012222200"))
}
}
func TestInterface2JsonString(t *testing.T) {
if Interface2JsonString(nil) != "" {
t.Errorf("nill interface to json string must empty")
}
}
func TestGetTopicRequestId(t *testing.T) {
topic := "$os/device/down/request=123456789"
if GetTopicRequestId(topic) != "123456789" {
t.Errorf("topic request id must be %s", "123456789")
}
}
func TestFormatTopic(t *testing.T) {
topic := "$os/device/{device_id}/up"
deviceId := "123"
formatTopic := "$os/device/123/up"
if formatTopic != FormatTopic(topic, deviceId) {
t.Errorf("formated topic must be %s", formatTopic)
}
}
// 仅适用于windows系统
func TestSmartFileName(t *testing.T) {
fileName := "D/go/sdk/test.log"
smartFileName := "D:\\\\go\\\\sdk\\\\test.log"
if smartFileName != SmartFileName(fileName) {
t.Errorf("in windows file system,smart file name must be %s", smartFileName)
}
}