Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade example container to use Partner api v3 #7

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions container/application/ble_data.go

This file was deleted.

58 changes: 38 additions & 20 deletions container/application/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.github.com/aruba-iotops-example-ble/LICENSE
// http://www.github.com/aruba-iotops-example-ble/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -16,6 +16,7 @@ package main
import (
"bufio"
"context"
"encoding/json"
"log"
"net/http"
"time"
Expand All @@ -30,21 +31,21 @@ func NewHTTPClient(url, apiKey, method string) *HTTPClient {
URL: url,
APIKey: apiKey,
Method: method,
dataCh: make(chan []byte, 1),
dataCh: make(chan *BleData, 1),
}
}

type HTTPClient struct {
URL string
APIKey string
Method string // HTTP Method: GET/POST/HEAD/OPTIONS/PUT/PATCH/DELETE/TRACE/CONNECT
dataCh chan []byte
dataCh chan *BleData
}

// Connect establish an HTTP connection.
// response data will be put into filed "dataCh".
func (c *HTTPClient) Connect(ctx context.Context) {
log.Default().Println("Http request, url: " + c.URL + " ; apiKey: " + c.APIKey)
log.Println("Http request, url: " + c.URL + " ; apiKey: " + c.APIKey)

req, _ := http.NewRequestWithContext(ctx, c.Method, c.URL, nil)
req.Header.Set("apikey", c.APIKey)
Expand All @@ -59,7 +60,7 @@ func (c *HTTPClient) Connect(ctx context.Context) {
}()

if err != nil {
log.Default().Println("HTTP request error!")
log.Println("HTTP request error!")
// If connect failed, will retry after 1 second
<-time.After(1 * time.Second)

Expand All @@ -68,7 +69,7 @@ func (c *HTTPClient) Connect(ctx context.Context) {
return
}

reader := bufio.NewReader(resp.Body)
scanner := bufio.NewScanner(resp.Body)

go func() {
defer func() {
Expand All @@ -77,24 +78,41 @@ func (c *HTTPClient) Connect(ctx context.Context) {
}
}()

for {
line, err := reader.ReadBytes('\n')
if err != nil {
log.Default().Println(err.Error())

return
}

if len(line) > 0 {
c.dataCh <- line
for scanner.Scan() {
//log.Println(scanner.Text())
bleData := bleDataFromResult(scanner.Bytes())
if bleData != nil {
c.dataCh <- bleData
}
}
if err := scanner.Err(); err != nil {
log.Println(err.Error())
}
}()
}

// GetDataCh return HTTP client filed "dataCh".
// HTTP response data will be put into this field "dataCh".
// method in "process_ble_data.go" file will consume this data from data channel.
func (c *HTTPClient) GetDataCh() <-chan []byte {
func bleDataFromResult(aResult []byte) *BleData {
bleData := new(BleData)
err := json.Unmarshal(aResult, bleData)
if err != nil {
return nil
}
return bleData
}

// GetDataCh returns the streaming ble data channel
func (c *HTTPClient) GetDataCh() <-chan *BleData {
return c.dataCh
}

type BleData struct {
Result struct {
Mac string `json:"mac"`
ApMac string `json:"apMac"`
Payload []byte `json:"payload"`
Rssi int `json:"rssi"`
FrameType string `json:"frameType"`
RadioMac string `json:"radioMac"`
MacAddressType string `json:"macAddressType"`
} `json:"result"`
}
82 changes: 43 additions & 39 deletions container/application/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ package main

import (
"context"
"encoding/hex"
"encoding/json"
"log"
"net/http"
"os"
"strings"

"github.com/google/uuid"
)

// IoT Operations example ble app is a demonstration of how to interact with HPE IoT Operations infrastructure service.
Expand All @@ -41,48 +40,53 @@ func main() {
// Example: http://apiGwUrl/$(api-method)
apiGwURL := os.Getenv("APIGW_URL")

// clientID is used to identify your MQTT connections.
// The value should not be the same as the value in the MQTT web page
// (MQTT web page : http://www.hivemq.com/demos/websocket-client/).
clientID := strings.ReplaceAll(uuid.New().String(), "-", "")
// mqtt client
mqttClient := NewMqttClient()
mqttClient.Connect()

// MQTT url
serverURL := "wss://test.mosquitto.org:8091/mqtt"
// bleAPIURL: example app will get data from HPE IoT Operations infrastructure services through this API url
bleAPIURL := "http://" + apiGwURL + "/api/v3/ble/stream/packets"
httpClient := NewHTTPClient(bleAPIURL, apiKey, http.MethodGet)
httpClient.Connect(context.Background())

// MQTT username/password
userName := "rw"
password := "readwrite"
ProcessBleData(httpClient.GetDataCh(), mqttClient.GetPubDataCh())
}

// MQTT publish data topic.
// default topic name is "app2broker_topic".
// IoT Operations data will be sent into this topic,
// you can subscribe this topic in the MQTT web page.
pubTopic := os.Getenv("APP_TO_BROKER_TOPIC")
if pubTopic == "" {
pubTopic = "app2broker_topic"
}
const minBleDataLen = 30

// MQTT subscribe data topic.
// default topic name is "broker2app_topic"
// you can send data into this topic through MQTT web page,
// Example app will accept data from that topic.
subTopic := os.Getenv("BROKER_TO_APP_TOPIC")
if subTopic == "" {
subTopic = "broker2app_topic"
}
type IBeaconData struct {
DeviceClass string
UUID string
Major string
Minor string
Power string
}

log.Println("Example app start")
// ProcessBleData get data from HPE IoT Operations infrastructure service.
// then decode and decorate and put data into data channel,
// data channel will be consumed by MQTT client.
func ProcessBleData(httpDataCh <-chan *BleData, mqttDataCh chan<- string) {
for bleData := range httpDataCh {
payload := bleData.Result.Payload
if len(payload) < minBleDataLen {
continue
}

// mqtt client
mqttClient := NewMqttClient(serverURL, userName, password, clientID, pubTopic, subTopic)
mqttClient.Connect()
// below is to convert iBeacon byte data to iBeacon string data.
// you need to overwrite this code when you decode your device data.
// note: field "data" is hexadecimal byte array.
// If you want to get string data. please process it with method hex.EncodeToString([]byte)
iBeaconData := &IBeaconData{
DeviceClass: "iBeacon",
UUID: hex.EncodeToString(payload[9:25]),
Major: hex.EncodeToString(payload[25:27]),
Minor: hex.EncodeToString(payload[27:29]),
Power: hex.EncodeToString(payload[29:30]),
}
iBeacon, _ := json.Marshal(iBeaconData)

// bleAPIURL: example app will get data from HPE IoT Operations infrastructure services through this API url
bleAPIURL := "http://" + apiGwURL + "/api/v2/ble/stream/packets"
httpClient := NewHTTPClient(bleAPIURL, apiKey, http.MethodGet)
httpClient.Connect(context.Background())
log.Println("iBeacon uuid: " + iBeaconData.UUID)

// bleClient: process ble data
bleClient := NewBleClient()
bleClient.ProcessBleData(httpClient.GetDataCh(), mqttClient.GetPubDataCh())
mqttDataCh <- string(iBeacon)
}
}
87 changes: 87 additions & 0 deletions container/application/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2021 Hewlett Packard Enterprise (HPE)
//
// Licensed under the MIT License;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.github.com/aruba-iotops-example-ble/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main

import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)

const iBeaconRawHex = "0201041AFF4C000215F7826DA64FA24E988024BC5B71E0893E00000000C5"

func TestExampleApp(t *testing.T) {
server := SEServerMock()

// http client
log.Println("Request Ble data and transfer it to a third party server")

BleRequestURL := server.URL + "/api/v3/ble/stream/packets"
httpClient := NewHTTPClient(BleRequestURL, "", http.MethodGet)
httpClient.Connect(context.Background())

mqttDataCh := make(chan string, 1)

// bleClient process ble data
go ProcessBleData(httpClient.GetDataCh(), mqttDataCh)

iBeaconData := &IBeaconData{}

go func() {
result := <-mqttDataCh
_ = json.Unmarshal([]byte(result), iBeaconData)
}()

<-time.After(20 * time.Millisecond)

if strings.ToUpper(iBeaconData.UUID) != iBeaconRawHex[18:50] {
t.Error("Get iBeacon data failed")
}
}

func SEServerMock() *httptest.Server {
log.Println("start HTTP server. send Ble data to client.")
// mock data
bleDataMock, _ := hex.DecodeString(iBeaconRawHex)
payload := base64.StdEncoding.EncodeToString(bleDataMock)

testData := fmt.Sprintf(`{"result":{"mac":"dc:a6:32:3f:1f:33","apMac":"ff:ff:2c:5d:94:9f","payload":"%s","rssi":-46,"frameType":"BLE_FRAME_TYPE_ADV_IND","radioMac":"ff:11:df:f5:ba:b1","macAddressType":"BLE_MAC_ADDRESS_TYPE_PUBLIC"}}`, payload)

// HTTP server
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.URL.EscapedPath() != "/api/v3/ble/stream/packets" {
_, _ = fmt.Fprintf(writer, "Reqeust path error")
}
if request.Method != http.MethodGet {
_, _ = fmt.Fprintf(writer, "Request method error")
}

flusher, _ := writer.(http.Flusher)

writer.Write(append([]byte(testData), []byte("\n")...))
flusher.Flush()

time.Sleep(1 * time.Second)
}))

return server
}
Loading