├── .gitignore ├── Dockerfile ├── Dockerfile.arm64 ├── LICENSE ├── README.cn.md ├── README.md ├── cmd ├── main.go └── res │ ├── configuration.toml │ └── docker │ └── configuration.toml ├── go.mod ├── go.sum ├── internal ├── bootstrap │ ├── config.go │ ├── container │ │ ├── config.go │ │ ├── messaging.go │ │ ├── meta_data_device.go │ │ ├── mqtt.go │ │ └── service_routes.go │ ├── handler │ │ ├── mesaging.go │ │ └── mqtt.go │ ├── interfaces │ │ ├── messaging.go │ │ └── mqtt.go │ └── service_routes.go ├── constants.go ├── device │ └── device.go ├── init.go ├── main.go ├── mqtt_server │ ├── client.go │ └── server.go ├── router.go ├── thingsboard.go ├── thingsboard │ ├── gateway_connect_message.go │ ├── gateway_rpc_message.go │ └── gateway_telemetry_message.go └── utils │ └── http.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.dll -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16-alpine AS builder 2 | ENV CGO_ENABLED=1 3 | ENV GOOS=linux 4 | ENV GOARCH=amd64 5 | ENV GO111MODULE=on 6 | ENV GOPRIVATE="" 7 | ENV GOPROXY="https://goproxy.cn,direct" 8 | ENV GOSUMDB="sum.golang.google.cn" 9 | WORKDIR /root/edgex-thingsboard/ 10 | 11 | # The main mirrors are giving us timeout issues on builds periodically. 12 | # So we can try these. 13 | RUN sed -e 's/dl-cdn[.]alpinelinux.org/mirrors.aliyun.com/g' -i~ /etc/apk/repositories 14 | RUN apk update && apk add zeromq-dev libsodium-dev pkgconfig build-base git 15 | 16 | ADD . . 17 | RUN go mod download \ 18 | && go test --cover $(go list ./... | grep -v /vendor/) \ 19 | && go build -o main cmd/main.go 20 | 21 | FROM alpine 22 | WORKDIR /root/ 23 | ENV TZ Asia/Shanghai 24 | 25 | # The main mirrors are giving us timeout issues on builds periodically. 26 | # So we can try these. 27 | RUN sed -e 's/dl-cdn[.]alpinelinux.org/mirrors.aliyun.com/g' -i~ /etc/apk/repositories 28 | RUN apk --no-cache add zeromq 29 | 30 | COPY --from=builder /root/edgex-thingsboard/main edgex-thingsboard 31 | COPY --from=builder /root/edgex-thingsboard/cmd/res/docker/configuration.toml res/configuration.toml 32 | RUN chmod +x edgex-thingsboard 33 | 34 | ENTRYPOINT ["/root/edgex-thingsboard"] 35 | CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/root/res"] 36 | -------------------------------------------------------------------------------- /Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | FROM arm64v8/golang:1.16-alpine AS builder 2 | ENV CGO_ENABLED=1 3 | ENV GOOS=linux 4 | ENV GOARCH=arm64 5 | ENV GO111MODULE=on 6 | ENV GOPRIVATE="" 7 | ENV GOPROXY="https://goproxy.cn,direct" 8 | ENV GOSUMDB="sum.golang.google.cn" 9 | WORKDIR /root/edgex-thingsboard/ 10 | 11 | # The main mirrors are giving us timeout issues on builds periodically. 12 | # So we can try these. 13 | RUN sed -e 's/dl-cdn[.]alpinelinux.org/mirrors.aliyun.com/g' -i~ /etc/apk/repositories 14 | RUN apk update && apk add zeromq-dev libsodium-dev pkgconfig build-base git 15 | 16 | ADD . . 17 | RUN go mod download \ 18 | && go test --cover $(go list ./... | grep -v /vendor/) \ 19 | && go build -o main cmd/main.go 20 | 21 | FROM arm64v8/alpine 22 | WORKDIR /root/ 23 | ENV TZ Asia/Shanghai 24 | 25 | # The main mirrors are giving us timeout issues on builds periodically. 26 | # So we can try these. 27 | RUN sed -e 's/dl-cdn[.]alpinelinux.org/mirrors.aliyun.com/g' -i~ /etc/apk/repositories 28 | RUN apk --no-cache add zeromq 29 | 30 | COPY --from=builder /root/edgex-thingsboard/main edgex-thingsboard 31 | COPY --from=builder /root/edgex-thingsboard/cmd/res/docker/configuration.toml res/configuration.toml 32 | RUN chmod +x edgex-thingsboard 33 | 34 | ENTRYPOINT ["/root/edgex-thingsboard"] 35 | CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/root/res"] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Inspii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.cn.md: -------------------------------------------------------------------------------- 1 | # Edgex-thingsboard 2 | 3 | 用于将Edgex网关接入Thingsboard物联网平台。 4 | 5 | - 将 Edgex 设备自动连接到 Thingsboard 6 | - 将 Edgex 设备数据上报到 Thingsboard 7 | - 响应 Thingsboard RPC 命令 8 | 9 | ## 使用方式 10 | 11 | 启动服务时,需配置Thingsboard服务端MQTT的连接信息。 12 | 13 | 如使用配置文件方式: 14 | 15 | ``` 16 | [Mqtt] 17 | Address = "tcp://localhost:1883" 18 | Username = "edgex-thingsboard" 19 | ClientId = "client-id" 20 | Timeout = 10000 21 | ``` 22 | 23 | 或使用环境变量方式: 24 | 25 | ``` 26 | MQTT_ADDRESS: tcp://localhost:1883 27 | MQTT_USERNAME: edgex-thingsboard 28 | MQTT_CLIENTID: client-id 29 | MQTT_TIMEOUT: "10000" 30 | ``` 31 | 32 | 其中: 33 | 34 | |参数|名称|描述| 35 | |---|---|---| 36 | | Mqtt.Address | MQTT Broker 地址| | 37 | | Mqtt.Username | 用户名 | | 38 | | Mqtt.ClientId | 客户端ID | | 39 | | Mqtt.Timeout | 超时时间 | 单位为毫秒 | 40 | 41 | ## 编译 42 | 43 | 使用消息总线 zeroMQ [需要先安装 zeroMQ 库](https://github.com/edgexfoundry/edgex-go#zeromq). 44 | 45 | ## 实现原理 46 | 47 | ### 连接设备 48 | 49 | 1. Edgex会按如下格式发送MQTT消息给Thingsboard: 50 | 51 | 发送消息: 52 | 53 | ```json 54 | { 55 | "device": "Virtual-Sensor-01" 56 | } 57 | ``` 58 | 59 | 其中: 60 | 61 | |参数|名称|描述| 62 | |---|---|---| 63 | | device | 设备名称 || 64 | 65 | ### 控制RPC 66 | 67 | 1. Thingsboard会按如下格式发送MQTT消息给Edgex: 68 | 69 | 发送消息: 70 | 71 | ```json 72 | { 73 | "device": "Virtual-Sensor-01", 74 | "data": { 75 | "id": 4, 76 | "method": "GET", 77 | "service": "edgex-core-command", 78 | "uri": "/api/version", 79 | "params": {}, 80 | "api_timeout": 10000 81 | } 82 | } 83 | ``` 84 | 85 | 其中: 86 | 87 | |参数|名称|描述| 88 | |---|---|---| 89 | | device | 设备名称 || 90 | | data.id | 请求ID || 91 | | data.service | 微服务名称 || 92 | | data.uri | HTTP接口地址 || 93 | | data.method | HTTP请求方法 || 94 | | data.params | HTTP请求参数 || 95 | | data.api_timeout | HTTP请求超时时间 | 单位为毫秒 | 96 | 97 | |service值|对应微服务名称|微服务接口地址| 98 | |---|---|---| 99 | | edgex-core-command | 命令微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 100 | | edgex-core-data | 核心数据微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 101 | | edgex-core-metadata | 元数据微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 102 | | edgex-support-notifications | 通知微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 103 | | edgex-support-scheduler | 调度微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-scheduler/1.2.1 | 104 | | edgex-sys-mgmt-agent | 系统管理微服务 | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-scheduler/1.2.1 | 105 | 106 | 2. Edgex处理完RPC消息后,会返回如下MQTT消息给Thingsboard: 107 | 108 | ```json 109 | { 110 | "device": "Virtual-Sensor-01", 111 | "id": 4, 112 | "data": { 113 | "http_status": 200, 114 | "success": true, 115 | "message": "", 116 | "result": {} 117 | } 118 | } 119 | ``` 120 | 121 | 其中: 122 | 123 | |参数|名称|描述| 124 | |---|---|---| 125 | | id | 请求ID || 126 | | device | 设备名称 || 127 | | data.http_status | HTTP状态码 || 128 | | data.success | 响应结果 || 129 | | data.message | 响应错误信息 || 130 | | data.result | 响应数据 || 131 | 132 | 133 | ### 遥测数据 134 | 135 | Edgex会将遥测数据按如下格式发往给Thingsboard: 136 | 137 | ```json 138 | { 139 | "Device A": [{ 140 | "ts": 1483228800000, 141 | "values": { 142 | "temperature": 42, 143 | "humidity": 80 144 | } 145 | }, { 146 | "ts": 1483228801000, 147 | "values": { 148 | "temperature": 43, 149 | "humidity": 82 150 | } 151 | }], 152 | "Device B": [{ 153 | "ts": 1483228800000, 154 | "values": { 155 | "temperature": 42, 156 | "humidity": 80 157 | } 158 | }] 159 | } 160 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Edgex-thingsboard 2 | 3 | [中文](README.cn.md) 4 | 5 | A micro service that connects Edgex to Thingsboard by MQTT. 6 | 7 | - Connect Edgex devices to Thingsboard 8 | - Report Edgex devices events to Thingsboard 9 | - Handle RPC requests from Thingsboard 10 | 11 | ## Usage 12 | 13 | Before start service, you need to configure Thingsboard MQTT client. 14 | 15 | Configure by file: 16 | 17 | ``` 18 | [Mqtt] 19 | Address = "tcp://localhost:1883" 20 | Username = "edgex-thingsboard" 21 | ClientId = "client-id" 22 | Timeout = 10000 23 | ``` 24 | 25 | Or configure by environment variables: 26 | 27 | ``` 28 | MQTT_ADDRESS: tcp://localhost:1883 29 | MQTT_USERNAME: edgex-thingsboard 30 | MQTT_CLIENTID: client-id 31 | MQTT_TIMEOUT: "10000" 32 | ``` 33 | 34 | While: 35 | 36 | |Arguments|Name|Description| 37 | |---|---|---| 38 | | Mqtt.Address | MQTT Address | | 39 | | Mqtt.Username | Username | | 40 | | Mqtt.ClientId | Client ID | | 41 | | Mqtt.Timeout | Timeout | unit: millisecond | 42 | 43 | ## Build 44 | 45 | If you are using zeroMQ as your message bus be sure to first [install the zeroMQ library](https://github.com/edgexfoundry/edgex-go#zeromq). 46 | 47 | ## Internal 48 | 49 | ### Connecting Devices 50 | 51 | 1. Edgex-thingsboard will send MQTT messages to Thingsboard while starting: 52 | 53 | ```json 54 | { 55 | "device": "Virtual-Sensor-01" 56 | } 57 | ``` 58 | 59 | While: 60 | 61 | |Arguments|Name|Description| 62 | |---|---|---| 63 | | device | Device Name || 64 | 65 | ### RPC 66 | 67 | 1. Thingsboard will send MQTT messages to Edgex-thingsboard: 68 | 69 | ```json 70 | { 71 | "device": "Virtual-Sensor-01", 72 | "data": { 73 | "id": 4, 74 | "method": "GET", 75 | "service": "edgex-core-command", 76 | "uri": "/api/version", 77 | "params": {}, 78 | "timeout": 10000 79 | } 80 | } 81 | ``` 82 | 83 | While: 84 | 85 | |Arguments|Name|Description| 86 | |---|---|---| 87 | | device | Device Name || 88 | | data.id | Request ID || 89 | | data.service | Service Name || 90 | | data.uri | HTTP URI || 91 | | data.method | HTTP Method || 92 | | data.params | HTTP Parameters || 93 | | data.timeout | HTTP Timeout | unit: millisecond | 94 | 95 | |Service Key|Service Name|Api Doc| 96 | |---|---|---| 97 | | edgex-core-command | Core Command Service | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 98 | | edgex-core-data | Core Data Service | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 99 | | edgex-core-metadata | Meta Data Service| https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 100 | | edgex-support-notifications | Notification Service | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command | 101 | | edgex-support-scheduler | Scheduler Service | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-scheduler/1.2.1 | 102 | | edgex-sys-mgmt-agent | System Management Service | https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/support-scheduler/1.2.1 | 103 | 104 | 2. After Edgex-thingsboard processed the request, it will reply MQTT messages to Thingsboard: 105 | 106 | ```json 107 | { 108 | "device": "Virtual-Sensor-01", 109 | "id": 4, 110 | "data": { 111 | "http_status": 200, 112 | "success": true, 113 | "message": "", 114 | "result": {} 115 | } 116 | } 117 | ``` 118 | 119 | While: 120 | 121 | |Arguments|Name|Description| 122 | |---|---|---| 123 | | id | Request ID || 124 | | device | Device Name || 125 | | data.http_status | HTTP Status || 126 | | data.success | Response Status || 127 | | data.message | Response Error Message || 128 | | data.result | Response Data || 129 | 130 | ### Telemetry 131 | 132 | Edgex-thingsboard will report devices events to Thingsboard: 133 | 134 | ```json 135 | { 136 | "Device A": [{ 137 | "ts": 1483228800000, 138 | "values": { 139 | "temperature": 42, 140 | "humidity": 80 141 | } 142 | }, { 143 | "ts": 1483228801000, 144 | "values": { 145 | "temperature": 43, 146 | "humidity": 82 147 | } 148 | }], 149 | "Device B": [{ 150 | "ts": 1483228800000, 151 | "values": { 152 | "temperature": 42, 153 | "humidity": 80 154 | } 155 | }] 156 | } 157 | ``` -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/gorilla/mux" 6 | "github.com/inspii/edgex-thingsboard/internal" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | internal.Main(ctx, cancel, mux.NewRouter(), nil) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | 4 | [Registry] 5 | Host = "localhost" 6 | Port = 8500 7 | Type = "consul" 8 | 9 | [Service] 10 | Protocol = 'http' 11 | Host = "localhost" 12 | Port = 49020 13 | ServerBindAddr = '0.0.0.0' 14 | Timeout = 45000 15 | BootTimeout = 30000 16 | CheckInterval = '10s' 17 | StartupMsg = "EdgeX Thingsboard started" 18 | 19 | [MessageQueue] 20 | Protocol = "tcp" 21 | Host = "localhost" 22 | Port = 5563 23 | Type = "zero" 24 | Topic = "events" 25 | [MessageQueue.Optional] 26 | Username ="" 27 | Password ="" 28 | ClientId ="edgex-thingsboard" 29 | Qos = "0" 30 | KeepAlive = "10" 31 | Retained = "false" 32 | AutoReconnect = "true" 33 | ConnectTimeout = "5" 34 | SkipCertVerify = "false" 35 | 36 | [ThingsboardMQTT] 37 | Address = "tcp://localhost:1883" 38 | Username = "edgex-thingsboard" 39 | ClientId = "" 40 | Timeout = 10000 41 | 42 | [Clients] 43 | [Clients.CoreData] 44 | Protocol = 'http' 45 | Host = 'localhost' 46 | Port = 48080 47 | 48 | [Clients.CoreMetadata] 49 | Protocol = 'http' 50 | Host = 'localhost' 51 | Port = 48081 52 | 53 | [Clients.CoreCommand] 54 | Protocol = 'http' 55 | Host = 'localhost' 56 | Port = 48082 57 | 58 | [Clients.SupportNotification] 59 | Protocol = 'http' 60 | Host = 'localhost' 61 | Port = 48060 62 | 63 | [Clients.SupportScheduler] 64 | Protocol = 'http' 65 | Host = 'localhost' 66 | Port = 48085 67 | 68 | [Clients.SystemMgmntAgent] 69 | Protocol = 'http' 70 | Host = 'localhost' 71 | Port = 48090 -------------------------------------------------------------------------------- /cmd/res/docker/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | 4 | [Registry] 5 | Host = "edgex-thingsboard" 6 | Port = 8500 7 | Type = "consul" 8 | 9 | [Service] 10 | Protocol = 'http' 11 | Host = "edgex-thingsboard" 12 | Port = 49020 13 | ServerBindAddr = '0.0.0.0' 14 | Timeout = 45000 15 | BootTimeout = 30000 16 | CheckInterval = '10s' 17 | StartupMsg = "EdgeX Thingsboard started" 18 | 19 | [MessageQueue] 20 | Protocol = "tcp" 21 | Host = "edgex-core-data" 22 | Port = 5563 23 | Type = "zero" 24 | Topic = "events" 25 | [MessageQueue.Optional] 26 | Username ="" 27 | Password ="" 28 | ClientId ="edgex-thingsboard" 29 | Qos = "0" 30 | KeepAlive = "10" 31 | Retained = "false" 32 | AutoReconnect = "true" 33 | ConnectTimeout = "5" 34 | SkipCertVerify = "false" 35 | 36 | [ThingsboardMQTT] 37 | Address = "tcp://localhost:1883" 38 | Username = "edgex-thingsboard" 39 | ClientId = "" 40 | Timeout = 10000 41 | 42 | [Clients] 43 | [Clients.CoreData] 44 | Protocol = 'http' 45 | Host = 'edgex-core-data' 46 | Port = 48080 47 | 48 | [Clients.CoreMetadata] 49 | Protocol = 'http' 50 | Host = 'edgex-core-metadata' 51 | Port = 48081 52 | 53 | [Clients.CoreCommand] 54 | Protocol = 'http' 55 | Host = 'edgex-core-command' 56 | Port = 48082 57 | 58 | [Clients.SupportNotification] 59 | Protocol = 'http' 60 | Host = 'edgex-support-notifications' 61 | Port = 48060 62 | 63 | [Clients.SupportScheduler] 64 | Protocol = 'http' 65 | Host = 'edgex-support-scheduler' 66 | Port = 48085 67 | 68 | [Clients.SystemMgmntAgent] 69 | Protocol = 'http' 70 | Host = 'edgex-sys-mgmt-agent' 71 | Port = 48090 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/inspii/edgex-thingsboard 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/eclipse/paho.mqtt.golang v1.2.0 7 | github.com/edgexfoundry/go-mod-bootstrap v0.0.57 8 | github.com/edgexfoundry/go-mod-core-contracts v0.1.112 9 | github.com/edgexfoundry/go-mod-messaging v0.1.30 10 | github.com/gorilla/mux v1.7.1 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= 4 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 5 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= 6 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 7 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= 8 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 9 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 10 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 11 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 12 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= 17 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 18 | github.com/edgexfoundry/go-mod-bootstrap v0.0.57 h1:zfmDYVHCqFeQL6PeHJZ8dVvGmO1oE2XEZiNIYhIi84w= 19 | github.com/edgexfoundry/go-mod-bootstrap v0.0.57/go.mod h1:nteSrK9q4FsodNRJvAsBSE7Ot/9n7rlXF4qdRV/Qc1U= 20 | github.com/edgexfoundry/go-mod-configuration v0.0.8 h1:pbmR66or9vFVoyfhrAU3tJy68s8PiUYzHFuCYXApcwA= 21 | github.com/edgexfoundry/go-mod-configuration v0.0.8/go.mod h1:4w9ZFQgd2wQ+7X8KMDaWJMYMSPsUGM/C/ruIX8t9fDs= 22 | github.com/edgexfoundry/go-mod-core-contracts v0.1.111/go.mod h1:84hDSh/zad/Tc56pSMW0yVLRS7BjAOxFCjW/2VJ9bio= 23 | github.com/edgexfoundry/go-mod-core-contracts v0.1.112 h1:vaC5fOc2fhFNnJraqawjyBoADwSumfX4n9Y1faZzg5U= 24 | github.com/edgexfoundry/go-mod-core-contracts v0.1.112/go.mod h1:84hDSh/zad/Tc56pSMW0yVLRS7BjAOxFCjW/2VJ9bio= 25 | github.com/edgexfoundry/go-mod-messaging v0.1.30 h1:dBttuz5/0uyOfd3Iu0NbTXAA3e9seElwhuEkJcswcSY= 26 | github.com/edgexfoundry/go-mod-messaging v0.1.30/go.mod h1:5/82RY1fkf7yRU+Gxvuk/4jbKXPMOuRTDfkFTJxlF3Y= 27 | github.com/edgexfoundry/go-mod-registry v0.1.26 h1:LP9xMJc0E5m/JaOqMOdQcKSCH/w4d7EtnvIDJr2zboY= 28 | github.com/edgexfoundry/go-mod-registry v0.1.26/go.mod h1:H780oknnbMe17mBooaU6rKxzIe6K2floNa3K/DJT3Yk= 29 | github.com/edgexfoundry/go-mod-secrets v0.0.26 h1:s+WlGybA6vzfIoOluwkZ9tE7VwnmZe8l9E/Fu13kVuk= 30 | github.com/edgexfoundry/go-mod-secrets v0.0.26/go.mod h1:LV+de4gRPGeGE3EHFcmObmFspDLR4BepxcJRZvOVna8= 31 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 32 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 33 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 34 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 35 | github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= 36 | github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= 37 | github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= 38 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 39 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= 40 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 41 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 42 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 43 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 44 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 45 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 46 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 47 | github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= 48 | github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 49 | github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= 50 | github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 51 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 52 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 53 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 55 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= 57 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 58 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 59 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 60 | github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= 61 | github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 62 | github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= 63 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 64 | github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= 65 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 66 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 67 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 68 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 69 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 70 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 71 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 72 | github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= 73 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 74 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 75 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 76 | github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= 77 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 78 | github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= 79 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 80 | github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= 81 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 82 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 83 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 84 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 85 | github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= 86 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 87 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 88 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 89 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 90 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 91 | github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= 92 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 93 | github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= 94 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 95 | github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= 96 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 97 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 98 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 99 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 100 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 101 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 102 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 103 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 104 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 105 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 106 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 107 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 108 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 109 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 110 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 111 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 112 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 113 | github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= 114 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 115 | github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= 116 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 117 | github.com/mitchellh/consulstructure v0.0.0-20190329231841-56fdc4d2da54 h1:DcITQwl3ymmg7i1XfwpZFs/TPv2PuTwxE8bnuKVtKlk= 118 | github.com/mitchellh/consulstructure v0.0.0-20190329231841-56fdc4d2da54/go.mod h1:dIfpPVUR+ZfkzkDcKnn+oPW1jKeXe4WlNWc7rIXOVxM= 119 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 120 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 121 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 122 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 123 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 124 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 125 | github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= 126 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 127 | github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= 128 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 129 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 130 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 131 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 132 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 133 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 134 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 135 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 136 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 137 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 138 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 139 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= 140 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 141 | github.com/pebbe/zmq4 v1.2.2 h1:RZ5Ogp0D5S6u+tSxopnI3afAf0ifWbvQOAw9HxXvZP4= 142 | github.com/pebbe/zmq4 v1.2.2/go.mod h1:7N4y5R18zBiu3l0vajMUWQgZyjv464prE8RCyBcmnZM= 143 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 144 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 145 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 146 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 147 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 148 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 149 | github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= 150 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 151 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= 152 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 153 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 154 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 155 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 156 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 157 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 158 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 159 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 160 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 161 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 162 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 163 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 164 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 165 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 166 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 167 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 168 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 169 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 171 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= 172 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 173 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 174 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 175 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 176 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 177 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 178 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 179 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= 181 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 182 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 183 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 184 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 185 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= 186 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 187 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 188 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 189 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 190 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 191 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 192 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 193 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 194 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 195 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 196 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 197 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 198 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 199 | -------------------------------------------------------------------------------- /internal/bootstrap/config.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "fmt" 5 | mqtt "github.com/eclipse/paho.mqtt.golang" 6 | bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/config" 7 | msgTypes "github.com/edgexfoundry/go-mod-messaging/pkg/types" 8 | ) 9 | 10 | type WritableInfo struct { 11 | LogLevel string 12 | } 13 | 14 | type MessageQueueInfo struct { 15 | Protocol string 16 | Host string 17 | Port int 18 | Type string 19 | Topic string 20 | Optional map[string]string 21 | } 22 | 23 | func (m MessageQueueInfo) URL() string { 24 | return fmt.Sprintf("%s://%s:%v", m.Protocol, m.Host, m.Port) 25 | } 26 | 27 | type ThingsboardMQTTInfo struct { 28 | Address string 29 | Username string 30 | ClientId string 31 | Timeout int 32 | } 33 | 34 | func (t ThingsboardMQTTInfo) GetMQTTOption() *mqtt.ClientOptions { 35 | return mqtt.NewClientOptions(). 36 | AddBroker(t.Address). 37 | SetUsername(t.Username). 38 | SetClientID(t.ClientId) 39 | } 40 | 41 | type ConfigurationClients map[string]bootstrapConfig.ClientInfo 42 | 43 | type ConfigurationStruct struct { 44 | Writable WritableInfo 45 | Service bootstrapConfig.ServiceInfo 46 | MessageQueue MessageQueueInfo 47 | Registry bootstrapConfig.RegistryInfo 48 | ThingsBoardMQTT ThingsboardMQTTInfo 49 | Clients ConfigurationClients 50 | } 51 | 52 | func (c *ConfigurationStruct) UpdateFromRaw(rawConfig interface{}) bool { 53 | configuration, ok := rawConfig.(*ConfigurationStruct) 54 | if ok { 55 | if configuration.Service.Port == 0 { 56 | return false 57 | } 58 | *c = *configuration 59 | } 60 | return ok 61 | } 62 | 63 | func (c *ConfigurationStruct) EmptyWritablePtr() interface{} { 64 | return &WritableInfo{} 65 | } 66 | 67 | func (c *ConfigurationStruct) UpdateWritableFromRaw(rawWritable interface{}) bool { 68 | writable, ok := rawWritable.(*WritableInfo) 69 | if ok { 70 | c.Writable = *writable 71 | } 72 | return ok 73 | } 74 | 75 | func (c *ConfigurationStruct) GetBootstrap() bootstrapConfig.BootstrapConfiguration { 76 | return bootstrapConfig.BootstrapConfiguration{ 77 | Clients: c.Clients, 78 | Service: c.Service, 79 | Registry: c.Registry, 80 | } 81 | } 82 | 83 | func (c *ConfigurationStruct) GetLogLevel() string { 84 | return c.Writable.LogLevel 85 | } 86 | 87 | func (c *ConfigurationStruct) GetRegistryInfo() bootstrapConfig.RegistryInfo { 88 | return c.Registry 89 | } 90 | 91 | func (c ConfigurationStruct) GetMessagingConfig() msgTypes.MessageBusConfig { 92 | return msgTypes.MessageBusConfig{ 93 | Type: c.MessageQueue.Type, 94 | SubscribeHost: msgTypes.HostInfo{ 95 | Host: c.MessageQueue.Host, 96 | Port: c.MessageQueue.Port, 97 | Protocol: c.MessageQueue.Protocol, 98 | }, 99 | Optional: c.MessageQueue.Optional, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /internal/bootstrap/container/config.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/edgexfoundry/go-mod-bootstrap/di" 5 | "github.com/inspii/edgex-thingsboard/internal/bootstrap" 6 | ) 7 | 8 | var ConfigurationName = di.TypeInstanceToName(bootstrap.ConfigurationStruct{}) 9 | 10 | func ConfigurationFrom(get di.Get) *bootstrap.ConfigurationStruct { 11 | return get(ConfigurationName).(*bootstrap.ConfigurationStruct) 12 | } 13 | -------------------------------------------------------------------------------- /internal/bootstrap/container/messaging.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/edgexfoundry/go-mod-bootstrap/di" 5 | "github.com/edgexfoundry/go-mod-messaging/messaging" 6 | ) 7 | 8 | var MessagingName = di.TypeInstanceToName((*messaging.MessageClient)(nil)) 9 | 10 | func MessagingFrom(get di.Get) messaging.MessageClient { 11 | return get(MessagingName).(messaging.MessageClient) 12 | } 13 | -------------------------------------------------------------------------------- /internal/bootstrap/container/meta_data_device.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/edgexfoundry/go-mod-bootstrap/di" 5 | "github.com/edgexfoundry/go-mod-core-contracts/clients/metadata" 6 | ) 7 | 8 | var MetaDataDeviceClientName = di.TypeInstanceToName((*metadata.DeviceClient)(nil)) 9 | 10 | func MetaDataDeviceClientFrom(get di.Get) metadata.DeviceClient { 11 | return get(MetaDataDeviceClientName).(metadata.DeviceClient) 12 | } 13 | -------------------------------------------------------------------------------- /internal/bootstrap/container/mqtt.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | mqtt "github.com/eclipse/paho.mqtt.golang" 5 | "github.com/edgexfoundry/go-mod-bootstrap/di" 6 | ) 7 | 8 | var MQTTName = di.TypeInstanceToName((*mqtt.Client)(nil)) 9 | 10 | func MQTTFrom(get di.Get) mqtt.Client { 11 | return get(MQTTName).(mqtt.Client) 12 | } 13 | -------------------------------------------------------------------------------- /internal/bootstrap/container/service_routes.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/edgexfoundry/go-mod-bootstrap/di" 5 | "github.com/inspii/edgex-thingsboard/internal/bootstrap" 6 | ) 7 | 8 | var ServiceRoutesName = di.TypeInstanceToName((*bootstrap.ServiceRoutes)(nil)) 9 | 10 | func ServiceRoutesFrom(get di.Get) *bootstrap.ServiceRoutes { 11 | return get(ServiceRoutesName).(*bootstrap.ServiceRoutes) 12 | } 13 | -------------------------------------------------------------------------------- /internal/bootstrap/handler/mesaging.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" 7 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" 8 | "github.com/edgexfoundry/go-mod-bootstrap/di" 9 | "github.com/edgexfoundry/go-mod-messaging/messaging" 10 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 11 | "sync" 12 | ) 13 | 14 | type Messaging struct { 15 | } 16 | 17 | func NewMessaging() *Messaging { 18 | return &Messaging{} 19 | } 20 | 21 | func (p Messaging) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, startupTimer startup.Timer, dic *di.Container) bool { 22 | conf := container.ConfigurationFrom(dic.Get) 23 | lc := bootstrapContainer.LoggingClientFrom(dic.Get) 24 | 25 | var client messaging.MessageClient 26 | for startupTimer.HasNotElapsed() { 27 | var err error 28 | config := conf.GetMessagingConfig() 29 | client, err = messaging.NewMessageClient(config) 30 | if err == nil { 31 | break 32 | } 33 | client = nil 34 | lc.Warn(fmt.Sprintf("couldn't create messaging client: %s", err.Error())) 35 | startupTimer.SleepForInterval() 36 | } 37 | 38 | if client == nil { 39 | lc.Error(fmt.Sprintf("failed to create messaging client in allotted time")) 40 | return false 41 | } 42 | 43 | dic.Update(di.ServiceConstructorMap{ 44 | container.MessagingName: func(get di.Get) interface{} { 45 | return client 46 | }, 47 | }) 48 | 49 | lc.Info("messaging client connected") 50 | wg.Add(1) 51 | go func() { 52 | defer wg.Done() 53 | <-ctx.Done() 54 | for { 55 | select { 56 | case <-ctx.Done(): 57 | if err := client.Disconnect(); err != nil { 58 | lc.Error("failed to disconnect messaging client") 59 | return 60 | } 61 | lc.Info("messaging client disconnected") 62 | return 63 | } 64 | } 65 | }() 66 | 67 | return true 68 | } 69 | -------------------------------------------------------------------------------- /internal/bootstrap/handler/mqtt.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | mqtt "github.com/eclipse/paho.mqtt.golang" 7 | bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" 8 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" 9 | "github.com/edgexfoundry/go-mod-bootstrap/di" 10 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 11 | "github.com/inspii/edgex-thingsboard/internal/mqtt_server" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | type MQTT struct { 17 | } 18 | 19 | func NewMQTT() *MQTT { 20 | return &MQTT{} 21 | } 22 | 23 | func (p *MQTT) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, startupTimer startup.Timer, dic *di.Container) bool { 24 | conf := container.ConfigurationFrom(dic.Get) 25 | lc := bootstrapContainer.LoggingClientFrom(dic.Get) 26 | timeout := time.Duration(conf.ThingsBoardMQTT.Timeout) * time.Millisecond 27 | 28 | var client mqtt.Client 29 | for startupTimer.HasNotElapsed() { 30 | var err error 31 | opt := conf.ThingsBoardMQTT.GetMQTTOption() 32 | client, err = mqtt_server.NewClient(opt, timeout) 33 | if err == nil { 34 | break 35 | } 36 | client = nil 37 | lc.Warn(fmt.Sprintf("couldn't create mqtt client: %s", err.Error())) 38 | startupTimer.SleepForInterval() 39 | } 40 | 41 | if client == nil { 42 | lc.Error(fmt.Sprintf("failed to create mqtt client in allotted time")) 43 | return false 44 | } 45 | 46 | dic.Update(di.ServiceConstructorMap{ 47 | container.MQTTName: func(get di.Get) interface{} { 48 | return client 49 | }, 50 | }) 51 | 52 | lc.Info("mqtt client connected") 53 | wg.Add(1) 54 | go func() { 55 | defer wg.Done() 56 | <-ctx.Done() 57 | for { 58 | select { 59 | case <-ctx.Done(): 60 | client.Disconnect(uint(timeout / time.Millisecond)) 61 | lc.Info("mqtt client disconnected") 62 | return 63 | } 64 | } 65 | }() 66 | 67 | return true 68 | } 69 | -------------------------------------------------------------------------------- /internal/bootstrap/interfaces/messaging.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | msgTypes "github.com/edgexfoundry/go-mod-messaging/pkg/types" 5 | ) 6 | 7 | type MessagingInfo interface { 8 | GetMessagingConfig() msgTypes.MessageBusConfig 9 | } 10 | -------------------------------------------------------------------------------- /internal/bootstrap/interfaces/mqtt.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import mqtt "github.com/eclipse/paho.mqtt.golang" 4 | 5 | type MQTTOption interface { 6 | GetMQTTOption() *mqtt.ClientOptions 7 | } 8 | -------------------------------------------------------------------------------- /internal/bootstrap/service_routes.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import "sync" 4 | 5 | type ServiceRoutes struct { 6 | mutex sync.RWMutex 7 | serviceAddrs map[string]string 8 | } 9 | 10 | func NewServiceRoutes() *ServiceRoutes { 11 | return &ServiceRoutes{ 12 | serviceAddrs: make(map[string]string), 13 | } 14 | } 15 | 16 | func (r *ServiceRoutes) Get(serviceName string) (serviceAddr string, ok bool) { 17 | r.mutex.RLock() 18 | defer r.mutex.RUnlock() 19 | serviceAddr, ok = r.serviceAddrs[serviceName] 20 | return 21 | } 22 | 23 | func (r *ServiceRoutes) Set(serviceName string, serviceAddr string) { 24 | r.mutex.Lock() 25 | defer r.mutex.Unlock() 26 | r.serviceAddrs[serviceName] = serviceAddr 27 | } 28 | -------------------------------------------------------------------------------- /internal/constants.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | const ( 4 | ConfigStemCore = "edgex/core/" 5 | ConfigMajorVersion = "1.0/" 6 | 7 | ControlAgentServiceKey = "edgex-thingsboard" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/device/device.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "context" 5 | "github.com/edgexfoundry/go-mod-bootstrap/di" 6 | "github.com/edgexfoundry/go-mod-core-contracts/models" 7 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 8 | "time" 9 | ) 10 | 11 | func ListDevices(dic *di.Container) ([]models.Device, error) { 12 | client := container.MetaDataDeviceClientFrom(dic.Get) 13 | 14 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 15 | defer cancel() 16 | return client.Devices(ctx) 17 | } 18 | -------------------------------------------------------------------------------- /internal/init.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" 7 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" 8 | "github.com/edgexfoundry/go-mod-bootstrap/di" 9 | "github.com/edgexfoundry/go-mod-core-contracts/clients" 10 | contracts "github.com/edgexfoundry/go-mod-core-contracts/clients" 11 | "github.com/edgexfoundry/go-mod-core-contracts/clients/metadata" 12 | "github.com/edgexfoundry/go-mod-core-contracts/clients/urlclient/local" 13 | "github.com/gorilla/mux" 14 | bootstrap2 "github.com/inspii/edgex-thingsboard/internal/bootstrap" 15 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 16 | "sync" 17 | ) 18 | 19 | type Bootstrap struct { 20 | router *mux.Router 21 | } 22 | 23 | func NewBootstrap(router *mux.Router) *Bootstrap { 24 | return &Bootstrap{ 25 | router: router, 26 | } 27 | } 28 | 29 | func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ startup.Timer, dic *di.Container) bool { 30 | loadRoutes(b.router, dic) 31 | 32 | configuration := container.ConfigurationFrom(dic.Get) 33 | serviceRoutes := bootstrap2.NewServiceRoutes() 34 | lc := bootstrapContainer.LoggingClientFrom(dic.Get) 35 | 36 | for serviceKey, serviceName := range b.listDefaultServices() { 37 | serviceClient := configuration.Clients[serviceName] 38 | serviceAddr := fmt.Sprintf("%s://%s:%d", serviceClient.Protocol, serviceClient.Host, serviceClient.Port) 39 | serviceRoutes.Set(serviceKey, serviceAddr) 40 | } 41 | 42 | dic.Update(di.ServiceConstructorMap{ 43 | container.ServiceRoutesName: func(get di.Get) interface{} { 44 | return bootstrap2.NewServiceRoutes() 45 | }, 46 | container.ServiceRoutesName: func(get di.Get) interface{} { 47 | return serviceRoutes 48 | }, 49 | container.MetaDataDeviceClientName: func(get di.Get) interface{} { 50 | metadataURL, ok := serviceRoutes.Get(contracts.CoreMetaDataServiceKey) 51 | if !ok { 52 | lc.Error("meta data client not set") 53 | } 54 | return metadata.NewDeviceClient(local.New(metadataURL + clients.ApiDeviceRoute)) 55 | }, 56 | }) 57 | 58 | thingsboardGateway := NewThingsboardGateway(dic) 59 | if err := thingsboardGateway.Serve(); err != nil { 60 | lc.Error(fmt.Sprintf("servve thingsboard gateway: %s", err.Error())) 61 | return false 62 | } 63 | 64 | return true 65 | } 66 | 67 | func (Bootstrap) listDefaultServices() map[string]string { 68 | return map[string]string{ 69 | contracts.CoreCommandServiceKey: "CoreCommand", 70 | contracts.CoreDataServiceKey: "CoreData", 71 | contracts.CoreMetaDataServiceKey: "CoreMetadata", 72 | contracts.SupportSchedulerServiceKey: "SupportScheduler", 73 | contracts.SupportNotificationsServiceKey: "SupportNotification", 74 | contracts.SystemManagementAgentServiceKey: "SystemMgmtAgent", 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/main.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap" 6 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/flags" 7 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/handlers/httpserver" 8 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/handlers/message" 9 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/handlers/testing" 10 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" 11 | "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" 12 | "github.com/edgexfoundry/go-mod-bootstrap/di" 13 | "github.com/gorilla/mux" 14 | edgex_thingsboard "github.com/inspii/edgex-thingsboard" 15 | bootstrap2 "github.com/inspii/edgex-thingsboard/internal/bootstrap" 16 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 17 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/handler" 18 | "os" 19 | ) 20 | 21 | func Main(ctx context.Context, cancel context.CancelFunc, router *mux.Router, readyStream chan<- bool) { 22 | startupTimer := startup.NewStartUpTimer(ControlAgentServiceKey) 23 | 24 | f := flags.New() 25 | f.Parse(os.Args[1:]) 26 | configuration := &bootstrap2.ConfigurationStruct{} 27 | dic := di.NewContainer(di.ServiceConstructorMap{ 28 | container.ConfigurationName: func(get di.Get) interface{} { 29 | return configuration 30 | }, 31 | }) 32 | 33 | httpServer := httpserver.NewBootstrap(router, true) 34 | 35 | bootstrap.Run( 36 | ctx, 37 | cancel, 38 | f, 39 | ControlAgentServiceKey, 40 | ConfigStemCore+ConfigMajorVersion, 41 | configuration, 42 | startupTimer, 43 | dic, 44 | []interfaces.BootstrapHandler{ 45 | handler.NewMessaging().BootstrapHandler, 46 | handler.NewMQTT().BootstrapHandler, 47 | NewBootstrap(router).BootstrapHandler, 48 | httpServer.BootstrapHandler, 49 | message.NewBootstrap(ControlAgentServiceKey, edgex_thingsboard.Version).BootstrapHandler, 50 | testing.NewBootstrap(httpServer, readyStream).BootstrapHandler, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /internal/mqtt_server/client.go: -------------------------------------------------------------------------------- 1 | package mqtt_server 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | mqtt "github.com/eclipse/paho.mqtt.golang" 7 | "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" 8 | "time" 9 | ) 10 | 11 | var errConnect = errors.New("failed to connect to MQTT broker") 12 | 13 | func NewClient(opts *mqtt.ClientOptions, timeout time.Duration) (mqtt.Client, error) { 14 | client := mqtt.NewClient(opts) 15 | token := client.Connect() 16 | if token.Error() != nil { 17 | return nil, token.Error() 18 | } 19 | 20 | ok := token.WaitTimeout(timeout) 21 | if ok && token.Error() != nil { 22 | return nil, token.Error() 23 | } 24 | if !ok { 25 | return nil, errConnect 26 | } 27 | 28 | return client, nil 29 | } 30 | 31 | type PubSubClient struct { 32 | client mqtt.Client 33 | qos byte 34 | timeout time.Duration 35 | logger logger.LoggingClient 36 | } 37 | 38 | func NewPubSubClient(client mqtt.Client, qos byte, timeout time.Duration, logger logger.LoggingClient) *PubSubClient { 39 | return &PubSubClient{ 40 | client: client, 41 | qos: qos, 42 | timeout: timeout, 43 | logger: logger, 44 | } 45 | } 46 | 47 | func (f *PubSubClient) Publish(topic string, msg []byte) error { 48 | token := f.client.Publish(topic, f.qos, false, msg) 49 | if token.Error() != nil { 50 | return token.Error() 51 | } 52 | ok := token.WaitTimeout(f.timeout) 53 | if ok && token.Error() != nil { 54 | return token.Error() 55 | } 56 | if !ok { 57 | return errPublishTimeout 58 | } 59 | return nil 60 | } 61 | 62 | func (f *PubSubClient) Subscribe(topic string, handler func(topic string, msg []byte) error) error { 63 | token := f.client.Subscribe(topic, f.qos, func(client mqtt.Client, m mqtt.Message) { 64 | if err := handler(m.Topic(), m.Payload()); err != nil { 65 | f.logger.Warn(fmt.Sprintf("failed to handle message: err=%s, payload=%s", err, m.Payload())) 66 | } 67 | }) 68 | if token.Error() != nil { 69 | return token.Error() 70 | } 71 | ok := token.WaitTimeout(f.timeout) 72 | if ok && token.Error() != nil { 73 | return token.Error() 74 | } 75 | if !ok { 76 | return errSubscribeTimeout 77 | } 78 | return nil 79 | } 80 | 81 | func (f *PubSubClient) Unsubscribe(topic string) error { 82 | token := f.client.Unsubscribe(topic) 83 | if token.Error() != nil { 84 | return token.Error() 85 | } 86 | ok := token.WaitTimeout(f.timeout) 87 | if ok && token.Error() != nil { 88 | return token.Error() 89 | } 90 | if !ok { 91 | return errUnsubscribeTimeout 92 | } 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /internal/mqtt_server/server.go: -------------------------------------------------------------------------------- 1 | package mqtt_server 2 | 3 | import ( 4 | "errors" 5 | mqtt "github.com/eclipse/paho.mqtt.golang" 6 | "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" 7 | "time" 8 | ) 9 | 10 | var ( 11 | errPublishTimeout = errors.New("failed to publish due to timeout reached") 12 | errSubscribeTimeout = errors.New("failed to subscribe due to timeout reached") 13 | errUnsubscribeTimeout = errors.New("failed to unsubscribe due to timeout reached") 14 | ) 15 | 16 | type Handler func(req []byte) (resp []byte, err error) 17 | 18 | type Server struct { 19 | pubsub *PubSubClient 20 | subscriptions []string 21 | } 22 | 23 | func New(client mqtt.Client, qos byte, timeout time.Duration, logger logger.LoggingClient) *Server { 24 | pubsub := NewPubSubClient(client, qos, timeout, logger) 25 | return &Server{ 26 | pubsub: pubsub, 27 | } 28 | } 29 | 30 | func (f Server) HandleFunc(requestTopic, replyTopic string, handler Handler) error { 31 | err := f.pubsub.Subscribe(requestTopic, func(topic string, msg []byte) error { 32 | res, err := handler(msg) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return f.pubsub.Publish(replyTopic, res) 38 | }) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | f.subscriptions = append(f.subscriptions, requestTopic) 44 | return nil 45 | } 46 | 47 | func (f Server) Close() error { 48 | for _, t := range f.subscriptions { 49 | if err := f.pubsub.Unsubscribe(t); err != nil { 50 | return err 51 | } 52 | } 53 | 54 | f.subscriptions = nil 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/router.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/edgexfoundry/go-mod-bootstrap/di" 5 | "github.com/gorilla/mux" 6 | "net/http" 7 | ) 8 | 9 | func loadRoutes(r *mux.Router, _ *di.Container) { 10 | s := r.PathPrefix("/api/v1").Subrouter() 11 | s.HandleFunc("/ping", ping).Methods(http.MethodGet) 12 | } 13 | 14 | func ping(w http.ResponseWriter, _ *http.Request) { 15 | w.Header().Set("Content-Type", "text/plain") 16 | _, _ = w.Write([]byte("pong")) 17 | } 18 | -------------------------------------------------------------------------------- /internal/thingsboard.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" 8 | "github.com/edgexfoundry/go-mod-bootstrap/di" 9 | contract "github.com/edgexfoundry/go-mod-core-contracts/models" 10 | msgType "github.com/edgexfoundry/go-mod-messaging/pkg/types" 11 | "github.com/inspii/edgex-thingsboard/internal/bootstrap/container" 12 | "github.com/inspii/edgex-thingsboard/internal/device" 13 | "github.com/inspii/edgex-thingsboard/internal/mqtt_server" 14 | "github.com/inspii/edgex-thingsboard/internal/thingsboard" 15 | "github.com/inspii/edgex-thingsboard/internal/utils" 16 | "time" 17 | ) 18 | 19 | const ( 20 | defaultAPITimeout = 60 * time.Second 21 | ) 22 | 23 | type ThingsboardGateway struct { 24 | dic *di.Container 25 | } 26 | 27 | func NewThingsboardGateway(dic *di.Container) *ThingsboardGateway { 28 | return &ThingsboardGateway{dic} 29 | } 30 | 31 | func (t *ThingsboardGateway) Serve() error { 32 | lc := bootstrapContainer.LoggingClientFrom(t.dic.Get) 33 | 34 | if err := t.serveConnectDevices(); err != nil { 35 | lc.Error(fmt.Sprintf("connect thingsboard devices: %s", err.Error())) 36 | return err 37 | } 38 | if err := t.serveRPC(); err != nil { 39 | lc.Error(fmt.Sprintf("serve thingsboard rpc: %s", err.Error())) 40 | return err 41 | } 42 | if err := t.serveTelemetry(); err != nil { 43 | lc.Error(fmt.Sprintf("serve thingsboard telemetry: %s", err.Error())) 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (t *ThingsboardGateway) serveConnectDevices() error { 50 | thingsboardMQTTConfig := container.ConfigurationFrom(t.dic.Get).ThingsBoardMQTT 51 | client := container.MQTTFrom(t.dic.Get) 52 | logger := bootstrapContainer.LoggingClientFrom(t.dic.Get) 53 | 54 | mqttTimeout := time.Duration(thingsboardMQTTConfig.Timeout) * time.Millisecond 55 | pubsub := mqtt_server.NewPubSubClient(client, 0, mqttTimeout, logger) 56 | 57 | devices, err := device.ListDevices(t.dic) 58 | if err != nil { 59 | logger.Error("list device names") 60 | return err 61 | } 62 | 63 | for _, d := range devices { 64 | m := thingsboard.GatewayConnectMessage{ 65 | DeviceName: d.Name, 66 | } 67 | if err := pubsub.Publish(thingsboard.GatewayConnectTopic, m.Bytes()); err != nil { 68 | logger.Error(fmt.Sprintf("connect device: %s", err)) 69 | continue 70 | } 71 | logger.Info(fmt.Sprintf("device %s connected", d.Name)) 72 | } 73 | return nil 74 | } 75 | 76 | func (t *ThingsboardGateway) serveRPC() error { 77 | thingsboardMQTTConfig := container.ConfigurationFrom(t.dic.Get).ThingsBoardMQTT 78 | client := container.MQTTFrom(t.dic.Get) 79 | logger := bootstrapContainer.LoggingClientFrom(t.dic.Get) 80 | 81 | mqttTimeout := time.Duration(thingsboardMQTTConfig.Timeout) * time.Millisecond 82 | server := mqtt_server.New(client, 0, mqttTimeout, logger) 83 | return server.HandleFunc(thingsboard.GatewayRPCTopic, thingsboard.GatewayRPCTopic, t.handleRPC) 84 | } 85 | 86 | func (t *ThingsboardGateway) handleRPC(req []byte) ([]byte, error) { 87 | logger := bootstrapContainer.LoggingClientFrom(t.dic.Get) 88 | logger.Debug(fmt.Sprintf("receive rpc message: %s", req)) 89 | 90 | r := &thingsboard.GatewayRPCRequestMessage{} 91 | err := r.FromBytes(req) 92 | if err != nil { 93 | logger.Warn("bad rpc request: %s", req) 94 | return nil, err 95 | } 96 | if r.Data.Service == "" || r.Data.Method == "" || r.Data.URI == "" { 97 | logger.Warn("bad rpc request: %s", req) 98 | return nil, errors.New("bad rpc request") 99 | } 100 | 101 | resp := t.forwardHTTP(r).Bytes() 102 | logger.Debug(fmt.Sprintf("rpc message response: %s", resp)) 103 | return resp, nil 104 | } 105 | 106 | func (t *ThingsboardGateway) forwardHTTP(req *thingsboard.GatewayRPCRequestMessage) *thingsboard.GatewayRPCResponseMessage { 107 | serviceRoutes := container.ServiceRoutesFrom(t.dic.Get) 108 | serviceURL := req.Data.Service 109 | if serviceAddr, ok := serviceRoutes.Get(req.Data.Service); ok { 110 | serviceURL = serviceAddr 111 | } 112 | 113 | var result interface{} 114 | apiURL := serviceURL + req.Data.URI 115 | 116 | timeout := defaultAPITimeout 117 | if req.Data.APITimeout > 0 { 118 | timeout = time.Duration(req.Data.APITimeout) * time.Millisecond 119 | } 120 | err := utils.RequestJSON(req.Data.Method, apiURL, timeout, req.Data.Params, &result) 121 | 122 | httpStatus := utils.GetHTTPStatus(err) 123 | errMsg := "" 124 | if err != nil { 125 | errMsg = err.Error() 126 | } 127 | return &thingsboard.GatewayRPCResponseMessage{ 128 | ID: req.Data.ID, 129 | Device: req.Device, 130 | Data: thingsboard.GatewayRPCResponseData{ 131 | Success: err == nil, 132 | HTTPStatus: httpStatus, 133 | Message: errMsg, 134 | Result: result, 135 | }, 136 | } 137 | } 138 | 139 | func (t *ThingsboardGateway) serveTelemetry() error { 140 | go t.forwardTelemetry() 141 | return nil 142 | } 143 | 144 | func (t *ThingsboardGateway) forwardTelemetry() { 145 | conf := container.ConfigurationFrom(t.dic.Get) 146 | messagingClient := container.MessagingFrom(t.dic.Get) 147 | thingsboardClient := container.MQTTFrom(t.dic.Get) 148 | logger := bootstrapContainer.LoggingClientFrom(t.dic.Get) 149 | 150 | mqttTimeout := time.Duration(conf.ThingsBoardMQTT.Timeout) * time.Millisecond 151 | pubsubClient := mqtt_server.NewPubSubClient(thingsboardClient, 0, mqttTimeout, logger) 152 | 153 | errCh := make(chan error) 154 | defer close(errCh) 155 | msgCh := make(chan msgType.MessageEnvelope) 156 | defer close(msgCh) 157 | ch := msgType.TopicChannel{ 158 | Topic: conf.MessageQueue.Topic, 159 | Messages: msgCh, 160 | } 161 | if err := messagingClient.Subscribe([]msgType.TopicChannel{ch}, errCh); err != nil { 162 | logger.Error(fmt.Sprintf("subscribe message channel: %s", err)) 163 | } 164 | 165 | for { 166 | select { 167 | case msg := <-msgCh: 168 | if msg.ContentType == "application/json" && len(msg.Payload) > 0 { 169 | event := &contract.Event{} 170 | if err := json.Unmarshal(msg.Payload, event); err != nil { 171 | logger.Error(fmt.Sprintf("unmarshal telemetry message error: %s", err)) 172 | continue 173 | } 174 | 175 | msg, err := thingsboard.AdapterGatewayTelemetryMessage(event) 176 | if err != nil { 177 | logger.Error(fmt.Sprintf("adapter telemetry message error: %s", err)) 178 | continue 179 | } 180 | 181 | if err := pubsubClient.Publish(thingsboard.GatewayTelemetryTopic, msg.Bytes()); err != nil { 182 | logger.Error(fmt.Sprintf("publish telemetry message error: %s", err)) 183 | continue 184 | } 185 | logger.Debug(fmt.Sprintf("telemetry reported: %s", msg.Bytes())) 186 | } 187 | case err := <-errCh: 188 | if err != nil { 189 | logger.Error(fmt.Sprintf("message error: %s", err)) 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /internal/thingsboard/gateway_connect_message.go: -------------------------------------------------------------------------------- 1 | package thingsboard 2 | 3 | import "encoding/json" 4 | 5 | const GatewayConnectTopic = "v1/gateway/connect" 6 | 7 | type GatewayConnectMessage struct { 8 | DeviceName string `json:"device"` 9 | } 10 | 11 | func (m GatewayConnectMessage) Bytes() []byte { 12 | bytes, _ := json.Marshal(m) 13 | return bytes 14 | } 15 | -------------------------------------------------------------------------------- /internal/thingsboard/gateway_rpc_message.go: -------------------------------------------------------------------------------- 1 | package thingsboard 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | const GatewayRPCTopic = "v1/gateway/rpc" 8 | 9 | type GatewayRPCRequestData struct { 10 | ID int `json:"id"` 11 | Service string `json:"service"` 12 | URI string `json:"uri"` 13 | Method string `json:"method"` 14 | Params interface{} `json:"params"` 15 | APITimeout int `json:"api_timeout"` // 毫秒 16 | } 17 | 18 | type GatewayRPCRequestMessage struct { 19 | Device string `json:"device"` 20 | Data GatewayRPCRequestData `json:"data"` 21 | } 22 | 23 | func (m *GatewayRPCRequestMessage) FromBytes(data []byte) error { 24 | return json.Unmarshal(data, m) 25 | } 26 | 27 | func (m GatewayRPCRequestMessage) Bytes() []byte { 28 | bytes, _ := json.Marshal(m) 29 | return bytes 30 | } 31 | 32 | type GatewayRPCResponseData struct { 33 | Success bool `json:"success"` 34 | Message string `json:"message"` 35 | HTTPStatus int `json:"http_status"` 36 | Result interface{} `json:"result"` 37 | } 38 | 39 | type GatewayRPCResponseMessage struct { 40 | ID int `json:"id"` 41 | Device string `json:"device"` 42 | Data GatewayRPCResponseData `json:"data"` 43 | } 44 | 45 | func (m *GatewayRPCResponseMessage) FromBytes(data []byte) error { 46 | return json.Unmarshal(data, m) 47 | } 48 | 49 | func (m GatewayRPCResponseMessage) Bytes() []byte { 50 | bytes, _ := json.Marshal(m) 51 | return bytes 52 | } 53 | -------------------------------------------------------------------------------- /internal/thingsboard/gateway_telemetry_message.go: -------------------------------------------------------------------------------- 1 | package thingsboard 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | contract "github.com/edgexfoundry/go-mod-core-contracts/models" 7 | "strconv" 8 | ) 9 | 10 | const GatewayTelemetryTopic = "v1/gateway/telemetry" 11 | 12 | type GatewayTelemetryMessage map[string][]GatewayDeviceDataPoint // deviceName -> []GatewayDeviceDataPoint 13 | 14 | func (m GatewayTelemetryMessage) Bytes() []byte { 15 | b, _ := json.Marshal(m) 16 | return b 17 | } 18 | 19 | type GatewayDeviceDataPoint struct { 20 | TS int `json:"ts"` 21 | Values map[string]interface{} `json:"values"` 22 | } 23 | 24 | func AdapterGatewayTelemetryMessage(event *contract.Event) (GatewayTelemetryMessage, error) { 25 | msgs := make(GatewayTelemetryMessage) 26 | for _, reading := range event.Readings { 27 | value, err := adapterTelemetryValue(reading.ValueType, reading.Value) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | ts := int(reading.Origin / 1000000) 33 | dataPoint := GatewayDeviceDataPoint{ 34 | TS: ts, 35 | Values: map[string]interface{}{ 36 | reading.Name: value, 37 | }, 38 | } 39 | if devicePoints, ok := msgs[event.Device]; ok { 40 | devicePoints = append(devicePoints, dataPoint) 41 | } else { 42 | msgs[reading.Device] = []GatewayDeviceDataPoint{dataPoint} 43 | } 44 | } 45 | return msgs, nil 46 | } 47 | 48 | func adapterTelemetryValue(valueType string, value string) (v interface{}, err error) { 49 | switch valueType { 50 | case contract.ValueTypeBool: 51 | return strconv.ParseBool("value") 52 | case contract.ValueTypeString: 53 | return value, nil 54 | case contract.ValueTypeUint8: 55 | n, err := strconv.Atoi(value) 56 | return uint8(n), err 57 | case contract.ValueTypeUint16: 58 | n, err := strconv.Atoi(value) 59 | return uint16(n), err 60 | case contract.ValueTypeUint32: 61 | n, err := strconv.Atoi(value) 62 | return uint32(n), err 63 | case contract.ValueTypeUint64: 64 | n, err := strconv.Atoi(value) 65 | return uint64(n), err 66 | case contract.ValueTypeInt8: 67 | n, err := strconv.Atoi(value) 68 | return int8(n), err 69 | case contract.ValueTypeInt16: 70 | n, err := strconv.Atoi(value) 71 | return int16(n), err 72 | case contract.ValueTypeInt32: 73 | n, err := strconv.Atoi(value) 74 | return int32(n), err 75 | case contract.ValueTypeInt64: 76 | n, err := strconv.Atoi(value) 77 | return int64(n), err 78 | case contract.ValueTypeFloat32: 79 | n, err := strconv.ParseFloat(value, 32) 80 | return float32(n), err 81 | case contract.ValueTypeFloat64: 82 | n, err := strconv.ParseFloat(value, 32) 83 | return n, err 84 | case contract.ValueTypeBinary: 85 | return value, nil 86 | case contract.ValueTypeBoolArray: 87 | var arr []bool 88 | err := json.Unmarshal([]byte(value), &arr) 89 | return value, err 90 | case contract.ValueTypeStringArray: 91 | var arr []string 92 | err := json.Unmarshal([]byte(value), &arr) 93 | return value, err 94 | case contract.ValueTypeUint8Array: 95 | var arr []uint8 96 | err := json.Unmarshal([]byte(value), &arr) 97 | return value, err 98 | case contract.ValueTypeUint16Array: 99 | var arr []uint16 100 | err := json.Unmarshal([]byte(value), &arr) 101 | return value, err 102 | case contract.ValueTypeUint32Array: 103 | var arr []uint32 104 | err := json.Unmarshal([]byte(value), &arr) 105 | return value, err 106 | case contract.ValueTypeUint64Array: 107 | var arr []uint64 108 | err := json.Unmarshal([]byte(value), &arr) 109 | return value, err 110 | case contract.ValueTypeInt8Array: 111 | var arr []int8 112 | err := json.Unmarshal([]byte(value), &arr) 113 | return value, err 114 | case contract.ValueTypeInt16Array: 115 | var arr []int16 116 | err := json.Unmarshal([]byte(value), &arr) 117 | return value, err 118 | case contract.ValueTypeInt32Array: 119 | var arr []int32 120 | err := json.Unmarshal([]byte(value), &arr) 121 | return value, err 122 | case contract.ValueTypeInt64Array: 123 | var arr []int64 124 | err := json.Unmarshal([]byte(value), &arr) 125 | return value, err 126 | case contract.ValueTypeFloat32Array: 127 | var arr []float32 128 | err := json.Unmarshal([]byte(value), &arr) 129 | return value, err 130 | case contract.ValueTypeFloat64Array: 131 | var arr []float64 132 | err := json.Unmarshal([]byte(value), &arr) 133 | return value, err 134 | default: 135 | return value, errors.New("unsupported value type") 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /internal/utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "reflect" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | const contentTypeJSON = "application/json" 16 | 17 | type HTTPError struct { 18 | Status int 19 | Body []byte 20 | } 21 | 22 | func NewHTTPError(code int, body []byte) error { 23 | return &HTTPError{ 24 | Status: code, 25 | Body: body, 26 | } 27 | } 28 | 29 | func (e HTTPError) HTTPStatus() int { 30 | return e.Status 31 | } 32 | 33 | func (e HTTPError) Error() string { 34 | return fmt.Sprintf("code=%d, body=%s", e.Status, e.Body) 35 | } 36 | 37 | func GetHTTPStatus(err error) int { 38 | if httpErr, ok := err.(interface { 39 | HTTPStatus() int 40 | }); ok { 41 | return httpErr.HTTPStatus() 42 | } 43 | 44 | if err == nil { 45 | return http.StatusOK 46 | } else { 47 | return http.StatusInternalServerError 48 | } 49 | } 50 | 51 | type RawMessage []byte 52 | 53 | // 发起HTTP JSON请求 54 | // 55 | // method 请求方法 56 | // url 请求地址 57 | // timeout 超时时间 58 | // payload 请求数据。会序列化为JSON字节数组(除非 payload 为 RawMessage 类型) 59 | // result 响应数据。会对响应数据进行JSON反序列化(除非 result 为 RawMessage 类型),并将结果存入result 60 | func RequestJSON(method string, url string, timeout time.Duration, requestBody interface{}, responseBody interface{}) error { 61 | var ( 62 | req *http.Request 63 | rep *http.Response 64 | err error 65 | binBody *bytes.Reader 66 | ) 67 | if requestBody != nil { 68 | var buf []byte 69 | switch requestBody.(type) { 70 | case RawMessage: 71 | buf = responseBody.(RawMessage) 72 | case *RawMessage: 73 | if m := responseBody.(*RawMessage); m != nil { 74 | buf = *m 75 | } 76 | default: 77 | buf, _ = json.Marshal(requestBody) 78 | } 79 | binBody = bytes.NewReader(buf) 80 | } else { 81 | binBody = bytes.NewReader(make([]byte, 0)) 82 | } 83 | req, err = http.NewRequest(method, url, binBody) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | req.Header.Add("Content-Type", contentTypeJSON) 89 | req.Header.Add("Accept", contentTypeJSON) 90 | client := http.DefaultClient 91 | client.Timeout = timeout 92 | rep, err = client.Do(req) 93 | if err != nil { 94 | return err 95 | } 96 | defer rep.Body.Close() 97 | 98 | status := rep.StatusCode 99 | body, err := ioutil.ReadAll(rep.Body) 100 | if err != nil { 101 | return err 102 | } 103 | if status < 200 || status >= 300 { 104 | return NewHTTPError(status, body) 105 | } 106 | if body == nil || responseBody == nil { 107 | return nil 108 | } 109 | 110 | switch responseBody.(type) { 111 | case RawMessage: 112 | return errors.New("responseBody not settable") 113 | case *RawMessage: 114 | rv := reflect.ValueOf(responseBody).Elem() 115 | rv.Set(reflect.ValueOf(RawMessage(body))) 116 | default: 117 | contentType := rep.Header.Get("Content-Type") 118 | if !strings.Contains(contentType, contentTypeJSON) { 119 | return nil // 忽略非JSON格式的响应数据 120 | } 121 | 122 | if err = json.Unmarshal(body, responseBody); err != nil { 123 | return err 124 | } 125 | } 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package edgex_control_agent 2 | 3 | var Version = "1.3.0" 4 | --------------------------------------------------------------------------------