EdgeX Foundry 心得 之二 -- 設備服務(數據獲取和推送到EdgeX)


在EdgeX Foundry中有南向和北向的概念

在數據獲取的過程中我自己大略畫了一個流程圖

在數據獲取的過程中 南向就是數據的產生方,北向就是最終數據的利用方

在這個系列的文章中,我沒有用官方的虛擬設備服務(device-virtual),因為我個人覺得那樣的描述不能很好的體會EdgeX中的各種概念,

 

 

 這個圖可能沒開發過的也是看不懂的,接下來一點一點的把上圖的內容盡可能的說清楚。

從南向北說

最南端是設備,比如溫度傳感器,門禁控制器,智能電表,攝像頭等等的可以產生數據的裝置甚至是軟件

我們先來三台機器(用三台的原因是這樣更容易把概念區分開)

192.168.1.11(簡稱host1)  設備

192.168.1.12(簡稱host2) 運行EdgeX的主機

192.168.1.13 (簡稱host3)運行deviceService的主機

有小伙伴留言問我這幾個主機的區別,為什么要分開:

在此澄清一下, 整個edgexFoundry的部署方式是可以有很多方式的, 之所以我選擇在這里都分開部署, 是為了寫文章的時候更加清晰

那么先講一下edgexfoundry的組成

1 edgexfoundry的核心微服務,官方已經打包成docker鏡像了,並且有一個docker-compose.yml可以用來啟動所有的edgexfoundry核心服務,

這些核心服務,每個都是一個微服務,是用httpAPI的方式相互調用的,那么這些微服務在生產中,你懂得。。。 你怎么弄都可以咯

本文中講用簡單的PHP程序當做數據的產生器也就是設備, 原因很簡單, Ubuntu安裝Apache + PHP 的環境非常簡單

我也盡可能的同步的用go語言來實現相同的功能

先實現最簡單的功能, 沒隔10秒產生一個[1-127] 的隨機數

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	for {
		time.Sleep(time.Second * 3)
		fmt.Println(rand.Intn(128) + 1)
	}

}

 

先不要糾結這個小程序的功能,先有在慢慢改

從根據圖上說, 要 能用http請求或者是mqtt的方式把產生的隨機數發送上來,我們的案例就以一個http的方式來實現發送產生的數據到設備服務(deviceService)

修改上面的代碼, 主要功能是把產生的隨機數POST到一個指定的url

package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"strings"
	"time"
)

func main() {

	for {
		time.Sleep(time.Second * 3)
		i := rand.Intn(128) + 1
		fmt.Println(i)
		url := "http://host1:1210/bye" //請求地址
		contentType := "application/json"
		//參數,多個用&隔開
		data := strings.NewReader(fmt.Sprintf("%d", i))
		resp, _ := http.Post(url, contentType, data)
		fmt.Println(resp)
	}

}

  

因為現在還么有開始寫deviceService 所以寫了一個簡單的服務端用來驗證, 就是把接收到的隨機數追加到一個文件中, 代碼中的context.txt文件是要和main.go同一個目錄, 並手動創建

 這不是必要的內容, 只是用來驗證上面產生數字並能爭取發送出來而已

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/bye", sayBye)

	server := &http.Server{
		Addr:         ":1210",
		WriteTimeout: time.Second * 3, //設置3秒的寫超時
		Handler:      mux,
	}
	log.Println("Starting v3 httpserver")
	log.Fatal(server.ListenAndServe())
}

func sayBye(w http.ResponseWriter, r *http.Request) {
	str, _ := readBodyAsString(w, r)
	fmt.Println(str)
	currentPath, _ := os.Getwd()
	//這里要自己建一個context.txt文件
	appendToFile(currentPath+"/context.txt", str+"\n")
}

func readBodyAsString(writer http.ResponseWriter, request *http.Request) (string, error) {
	defer request.Body.Close()
	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		return "", err
	}

	if len(body) == 0 {
		return "", fmt.Errorf("no request body provided")
	}

	return string(body), nil
}

func appendToFile(fileName string, content string) error {
	// 以只寫的模式,打開文件
	f, err := os.OpenFile(fileName, os.O_WRONLY, 0644)
	if err != nil {
		fmt.Println("cacheFileList.yml file create failed. err: " + err.Error())
	} else {
		// 查找文件末尾的偏移量
		n, _ := f.Seek(0, os.SEEK_END)
		// 從末尾的偏移量開始寫入內容
		_, err = f.WriteAt([]byte(content), n)
	}
	defer f.Close()
	return err
}

 到現在一個產生隨機數的設備就已經有了, 你可以當它是溫度或者濕度,或者其他的產生數字的設備就可以了

接下來是設備服務

設備服務主要參考

git clone https://github.com/edgexfoundry/device-rest-go  
cd device-rest-go
make build
注:在windows環境中, make命令好像挺麻煩的,用Windows系統的同學自己查一下把, 我也不太會, Linux比較簡單
 
現在我們可以體驗一下數據上傳到EdgeX了, (注意到目前還沒有往北向發送, 只是數據能正常的發送到EdgeX)
啟動虛擬機運行起來EdgeX服務
例如  在host2 執行命令  sudo docker-compose up -d  請參考第一節的內容
並停掉其中兩個服務(如果啟動了的話)sudo docker-compose stop device-virtual device-random  停掉的原因是為了不要讓這兩個服務產生的數據影響之后的判斷
 
 
啟動以后我們來修改device-rest-go(這個項目是沒有fuji發行的,直接用master就好了)
cd device-rest-go/cmd/res 
里面有一些文件|目錄
-- docker
-- configuration.toml
--sample-image-device.yaml
--sample-json-device.yaml
--sample-numeric-device.yaml
 
我們的設備是產生隨機數所以和我們最相似的是sample-numeric-device.yaml
cp sample-numeric-device.yaml rand-numeric-device.yaml
我們刪掉一些內容在下面的代碼中用刪除線(其實也就是刪除float的部分), 修改的內容把原來的內容在#號后的注釋中寫名
name: "rand-numeric" #舊的值是 sample-numeric manufacturer: "Intel Corp." model: "Some 3rd party App sending int & floats" labels: - "rest"
 - "float64"
 - "int64" description: "REST Device that sends in ints and floats" deviceResources: - name: int description: "Int64 value" properties: value: { type: "Int64", readWrite: "W" , mediaType : "application/json" } #舊的值是 text/plain, 寫成json的原因是我們設備上傳的數據使用json頭,我沒有嘗試不一樣會怎么樣, 反正我是改了 units: { type: "String", readWrite: "R"} - name: float description: "Float64 value" properties: value: { type: "Float64", readWrite: "W" , mediaType : "text/plain" } units: { type: "String", readWrite: "R" } deviceCommands: - name: PostInt get: - { operation: "get", deviceResource: "int"} - name: PostFloat get: - { operation: "get", deviceResource: "float"}

下面來一個清爽的

name: "rand-numeric"
manufacturer: "Intel Corp."
model: "Some 3rd party App sending int & floats"
labels:
 - "rest"
 - "int64"
description: "REST Device that sends in ints and floats"

deviceResources:
  - name: int
    description: "Int64 value"
    properties:
      value:
        { type: "Int64", readWrite: "W" , mediaType : "application/json" }
      units:
        { type: "String", readWrite: "R"}

deviceCommands:
  - name: PostInt
    get:
      - { operation: "get", deviceResource: "int"}

 接下來我們要修改 configuration.toml 文件

打開它找到最后DeviceList的部分,把 name=sample-json 和 name=sample-image的都刪掉

把name=sample-numeric 改成 name=rand-numeric  這是定義了設備名改不改都行

Profile =  ""雙引號中要寫成rand-numeric ,注意這個屬性的值和上面rand-numeric-device.yaml文件中的name屬性值必須一樣

修改文件中所有目前的Host = "localhost" 改為運行EdgeX 服務的虛擬機的ip 即 host2

這里要注意上面修改的host不包括[Service]中的Host,[Service]-->Host的值是將要啟動device-rest-go(deviceService)服務的ip地址 host3

如果你是在物理機來運行device-rest-go服務的話, 那也不要寫localhost,因為這個信息是要注冊給docker的

所以說, 這里一定要寫ip而且是一個和運行docker服務主機能互通的ip

修改完把device-rest-go/cmd目錄中的   二進制文件device-rest-go 和res目錄拷貝到192.168.1.10任意目錄, 本機的話直接啟動即可

./device-rest-go

Init: useRegistry:  profile:  confDir: 
Loading configuration from: /home/koala/project/EdgeX/device-rest-go/cmd/res/configuration.toml

Load configuration from local file and bypassing registration in Registry...
Calling service.Start.
EnableRemote is false, using local log file
level=INFO ts=2020-03-06T09:40:19.996638031Z app=edgex-device-rest source=init.go:138 msg="Check Metadata service's status ..."
level=INFO ts=2020-03-06T09:40:19.996638037Z app=edgex-device-rest source=init.go:138 msg="Check Data service's status ..."
level=INFO ts=2020-03-06T09:40:20.005574323Z app=edgex-device-rest source=init.go:48 msg="Service clients initialize successful."
level=INFO ts=2020-03-06T09:40:20.010404424Z app=edgex-device-rest source=service.go:153 msg="Device Service edgex-device-rest exists"
level=INFO ts=2020-03-06T09:40:20.019431937Z app=edgex-device-rest source=service.go:96 msg="*Service Start() called, name=edgex-device-rest, version=to be replaced by makefile"
level=INFO ts=2020-03-06T09:40:20.022447427Z app=edgex-device-rest source=service.go:100 msg="Listening on port: 49986"
level=INFO ts=2020-03-06T09:40:20.025506267Z app=edgex-device-rest source=resthandler.go:64 msg="Route /api/v1/resource/{deviceName}/{resourceName} added."
level=INFO ts=2020-03-06T09:40:20.033438364Z app=edgex-device-rest source=service.go:127 msg="Service started in: 37.360847ms"

啟動后可以看到監聽端口是49986  uri  /api/v1/resource/{deviceName}/{resourceName}

所以拼接url是   http://host3:49986/api/v1/resource/{deviceName}/{resourceName}

deviceName 根據configuration.toml文件 [DeviceList][Name]屬性的值

resourceName 對應rand-numeric-device.yaml

 

 

 http://host3:49986/api/v1/resource/rand-numeric/int  

發送post數據 任意整數(忽略我的IP)

 

在這里沒有消息就是好消息 ,官方的代碼中沒有寫返回的內容

在瀏覽器中打開下面的鏈接(ip是運行EdgeX 的docker服務的主機IP

http://host2:48080/api/v1/event/count 

每發送一次, 刷新頁面 ,返回的數字就+1

如果是這樣,那么我們就可以修改我們的設備代碼把里面的url替換成

http://host3:49986/api/v1/resource/rand-numeric/int  

func main() {

	for {
		time.Sleep(time.Second * 3)
		i := rand.Intn(128) + 1
		fmt.Println(i)
		url := "http://host3:49986/api/v1/resource/rand-numeric/int" //請求地址
		contentType := "application/json"
		//參數,多個用&隔開
		data := strings.NewReader(fmt.Sprintf("%d", i))
		resp, _ := http.Post(url, contentType, data)
		fmt.Println(resp)
	}

}

http://host2:48080/api/v1/reading/name/int/3  (http://host2:48080/api/v1/reading/name/:name/:limit)

name是resourceName   limit就是看最新的幾條

這個接口比上面count結尾的接口更具有觀賞性

可以隨着設備數據的產生從而看到數據是跟着設備數據變化二變化的

 

下面我們來分析代碼 

 
 
打開這個項目, 最好用一個編輯器
先打開device-rest-go/cmd/main.go的文件

 跟進到

Bootstrap

 

 NewService

 

 s.Start(errChan) 

 

 這里初始化了自己的驅動中的Initialize() 傳了參數s.asyncCh  

這個時候我們回過頭來看 device-rest-go/driver/restdriver.go中的Initialize方法

handler := NewRestHandler(sdk.RunningService(), logger, asyncValues)
return handler.Start()   
------》》
handler.service.AddRoute(apiResourceRoute, handler.addContext(deviceHandler), http.MethodPost)
------》》
deviceHandler是一個方法
func deviceHandler(writer http.ResponseWriter, request *http.Request) {
    handler, ok := request.Context().Value(handlerContextKey).(RestHandler)
    if !ok {
        writer.WriteHeader(http.StatusBadRequest)
        writer.Write([]byte("Bad context pass to handler"))
        return
    }

    handler.processAsyncRequest(writer, request)
    resStr := "success"
    writer.Write([]byte(resStr))
}
processAsyncRequest方法中
asyncValues := &models.AsyncValues{
        DeviceName:    deviceName,
        CommandValues: []*models.CommandValue{value},
    }

    handler.logger.Debug(fmt.Sprintf("Incoming reading received: Device=%s Resource=%s", deviceName, resourceName))

    handler.asyncValues <- asyncValues
asyncValues發送到管道中也是初始化驅動時的傳參 s.asyncCh

 

 

 

 

 

 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM