邊緣計算開源框架EdgeXFoundry的部署應用開發

aron566發表於2020-11-16

EdgeX 歷史記錄和命名

EdgeXFoundry最初是戴爾物聯網營銷部特許的專案,由 CTO 戴爾客戶辦公室開發,於 2015 年 7 月作為名為"Fuse專案"的孵化專案開發。它最初建立為在戴爾的物聯網閘道器介紹行上作為 IoT 軟體應用程式執行。2017 年 4 月 24 日,戴爾通過 Linux 基金會將該專案引入開源。EdgeX 在 2017 年漢諾威展上正式宣佈並展示。漢諾威博覽會是世界上最大的工業貿易展覽會之一。在展會上,Linux基金會還宣佈了由50個創始成員組織(EdgeX生態系統)組成的協會,以幫助推進該專案和建立通用邊緣平臺的目標。

功能及流程簡介

EdgeX 的工作原理

官網詳細說明

說明中的一些名詞定義:
北向通訊(一般指上報資料)
南向通訊(一般指下發命令或者資料)

感測器資料收集

EdgeX 的主要工作是從感測器和裝置收集資料,使這些資料可供北端應用程式和系統使用。資料由表示該裝置協議的裝置服務從感測器收集資料。示例:Modbus 裝置服務將在 Modbus 中進行通訊,以便從 Modbus 泵裝置獲得壓力讀數。裝置服務將感測器資料轉換為 EdgeX 事件物件,並將事件物件通過 REST 通訊(步驟 1)傳送到核心資料服務。

在這裡插入圖片描述

核心資料將感測器資料保留在本地邊緣資料庫中。預設情況下,Redis 用作資料庫(其他資料庫可以替代使用)。事實上,永續性是不需要的,可以關閉。資料保留在邊緣的 EdgeX 中,原因有兩個:

  • 邊緣節點並不總是連線。在斷開連線操作期間,必須儲存感測器資料,以便恢復連線時可以向北傳輸。這稱為儲存和轉發功能。
  • 在某些情況下,感測器資料分析需要回顧歷史,以便了解趨勢,並基於該歷史記錄做出正確的決策。如果感測器報告現在溫度為 72° F,則在決定調整加熱或冷卻系統之前,您可能想知道 10 分鐘前的溫度是多少。如果溫度為 85° F,您可以決定調整以降低您十分鐘前的室溫足以冷卻房間。歷史資料的上下文對本地分析決策非常重要。

核心資料將感測器資料事件放在面向應用程式服務的訊息主題上。預設情況下,ZeroMQ 用作訊息傳遞基礎結構(步驟 2)。

在這裡插入圖片描述

應用程式服務根據需要轉換資料,並將資料推送到終結點。它還可以在將事件傳送到終結點之前對事件進行篩選、豐富、壓縮、加密或執行其他功能(步驟 3)。終結點可能是 HTTP/S 終結點、MQTT 主題、雲系統(雲主題)等。

在這裡插入圖片描述

邊緣分析和驅動

在邊緣計算中,簡單地收集感測器資料只是 EdgeX 等邊緣平臺工作的一部分。邊緣平臺的另一個重要工作是能夠:

  • 本地分析傳入感測器資料
  • 快速執行該分析邊緣或本地分析是執行對在邊緣收集的感測器資料(“本地”)進行評估並基於其看到內容觸發驅動或操作的處理。

Why edge analytics? Local analytics are important for two reasons:

  • 某些決策無法等待感測器收集的資料反饋給企業或雲系統並返回響應。
  • 此外,某些邊緣系統並不總是連線到企業或雲 - 它們具有間歇性的連線期。

本地分析允許系統獨立執行,至少在某些時間上是這樣。例如:當船舶在海上時,運輸集裝箱的冷卻系統必須能夠在本地做出決策,而無需長時間使用網際網路連線。本地分析還允許系統在系統操作關鍵時以低潛在方式快速行動。作為一個極端的情況,想象一下,你的車的安全氣囊發射的基礎上的資料被髮送到雲和分析碰撞。您的汽車有本地分析,以防止這種潛在的緩慢和容易出錯交付在您的汽車的安全驅動。

EdgeX 的構建是為了對從邊緣收集的資料進行本地操作。換句話說,事件由本地分析處理,可用於觸發感測器/裝置上的回工作。

正如應用程式服務準備資料供北端雲系統或應用程式使用一樣,應用程式服務可以處理和獲取 EdgeX 事件(及其包含的感測器資料)到任何分析包(參見步驟 4)。預設情況下,EdgeX 附帶一個簡單的規則引擎(預設 EdgeX 規則引擎是Kuiper - EMQ X 的開源規則引擎)。您自己的分析包(或 ML 代理)可以替換或增強本地規則引擎。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-orTFf0Fh-1604370961024)(EdgeXFoundry簡介.PIC/EdgeX_step4.png)]

分析包可以探索感測器事件資料,並決定觸發裝置的驅動。例如,它可以檢查發動機的壓力讀數是否大於 60 PSI。當確定此規則為 true 時,分析包呼叫核心命令服務以觸發某些操作,例如在某些可控制的裝置上"開啟閥門"(參見步驟 5)。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-El5PNRV5-1604370961027)(EdgeXFoundry簡介.PIC/EdgeX_step5.png)]

核心命令服務獲取驅動請求,並確定它需要對哪個裝置執行該請求;然後呼叫擁有的裝置服務來執行啟用(請參閱步驟 6)。Core 命令允許開發人員在啟用之前設定其他安全措施或檢查。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tTfAyHVa-1604370961028)(EdgeXFoundry簡介.PIC/EdgeX_step6.png)]

裝置服務接收啟用請求,將其轉換為特定於協議的請求,並將請求轉發到所需的裝置(請參閱步驟 7)。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zrjnxPbl-1604370961029)(EdgeXFoundry簡介.PIC/EdgeX_step7.png)]

前提條件

需掌握的技術

  • docker及docker-compose知道常用命令,瞭解Dockerfile的格式含義,知道docker-compose.yml格式含義
  • Go瞭解編譯執行常用命令,如果跨平臺需要自己編譯原始碼
  • 熟練使用一門語言C或者Go,方能基於SDK進行裝置服務的開發、跨平臺開發
  • 瞭解相關主流通訊協議:modbus、mqtt,當然自定義亦可
  • 非主要:熟悉git版本管理工具的使用
  • 非必須:熟悉cmake、跨平臺ide工具

必須的軟體

cmake curl安裝

sudo apt update
sudo apt install cmake curl -y

docker安裝

部落格

IDE VScode的安裝

去VScode官網下載對應平臺的包安裝即可

我這裡使用Linux Mint發行版
下載xxx.deb包

sudo dpkg -i xxx.deb

部署框架

建個目錄EdgeXFoundry,在此執行部署的命令

mkdir EdgeXFoundry && cd EdgeXFoundry
  • 在x86平臺部署
curl https://raw.githubusercontent.com/edgexfoundry/developer-scripts/master/releases/geneva/compose-files/docker-compose-geneva-redis-no-secty.yml -o docker-compose.yml; docker-compose up
  • 在ARM64平臺部署
curl https://raw.githubusercontent.com/edgexfoundry/developer-scripts/master/releases/geneva/compose-files/docker-compose-geneva-redis-no-secty-arm64.yml -o docker-compose.yml; docker-compose up

驗證是否啟動

docker-compose ps

在這裡插入圖片描述
在目錄下有yaml的檔案,裡面官方預設遮蔽了虛擬的隨機數裝置服務,可以解除遮蔽自己去執行它,官網有相關教程步驟,這裡不多做介紹。

這裡給出一份帶ui控制檯的docker-compose.yml檔案
UI專案地址
UI使用介紹視訊:優酷youtube

# /*******************************************************************************
#  * Copyright 2020 Redis Labs Inc.
#  * Copyright 2020 Intel Corporation.
#  *
#  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
#  * in compliance with the License. You may obtain a copy of the License at
#  *
#  * http://www.apache.org/licenses/LICENSE-2.0
#  *
#  * 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.
#  *
#  * @author: Jim White, Dell
#  * @author: Andre Srinivasan, Redis Labs
#  * @author: Leonard Goodell, Intel
#  * EdgeX Foundry, Geneva, version 1.2.0
#  * added: May 14, 2020
#  *******************************************************************************/

# NOTE:  this Docker Compose file does not contain the security services - namely the API Gateway
# and Secret Store

version: '3.4'

# all common shared environment variables defined here:
x-common-env-variables: &common-variables
  EDGEX_SECURITY_SECRET_STORE: "false"
  Registry_Host: edgex-core-consul
  Clients_CoreData_Host: edgex-core-data
  Clients_Data_Host: edgex-core-data # For device Services
  Clients_Notifications_Host: edgex-support-notifications
  Clients_Metadata_Host: edgex-core-metadata
  Clients_Command_Host: edgex-core-command
  Clients_Scheduler_Host: edgex-support-scheduler
  Clients_RulesEngine_Host: edgex-kuiper
  Clients_VirtualDevice_Host: edgex-device-virtual
  Databases_Primary_Host: edgex-redis
  # Required in case old configuration from previous release used.
  # Change to "true" if re-enabling logging service for remote logging
  Logging_EnableRemote: "false"
  #  Clients_Logging_Host: edgex-support-logging # un-comment if re-enabling logging service for remote logging

volumes:
  db-data:
  log-data:
  consul-config:
  consul-data:

services:
  consul:
    image: edgexfoundry/docker-edgex-consul:1.2.0
    ports:
      - "127.0.0.1:8400:8400"
      - "127.0.0.1:8500:8500"
    container_name: edgex-core-consul
    hostname: edgex-core-consul
    networks:
      - edgex-network
    volumes:
      - consul-config:/consul/config:z
      - consul-data:/consul/data:z
    environment: 
      - EDGEX_DB=redis
      - EDGEX_SECURE=false

  redis:
    image: redis:5.0.8-alpine
    ports:
      - "127.0.0.1:6379:6379"
    container_name: edgex-redis
    hostname: edgex-redis
    networks:
      - edgex-network
    environment:
      <<: *common-variables
    volumes:
      - db-data:/data:z

# The logging service has been deprecated in Geneva release and will be removed in the Hanoi release.
# All services are configure to send logging to STDOUT, i.e. not remote which requires this logging service
# If you still must use remote logging, un-comment the block below, all the related depends that have been commented out
# and the related global override that are commented out at the top.
#
#  logging:
#    image: edgexfoundry/docker-support-logging-go:1.2.1
#    ports:
#      - "127.0.0.1:48061:48061"
#    container_name: edgex-support-logging
#    hostname: edgex-support-logging
#    networks:
#      - edgex-network
#    environment:
#      <<: *common-variables
#      Service_Host: edgex-support-logging
#      Writable_Persistence: file
#      Databases_Primary_Type: file
#      Logging_EnableRemote: "false"
#    depends_on:
#      - consul

  system:
    image: edgexfoundry/docker-sys-mgmt-agent-go:1.2.1
    ports:
      - "127.0.0.1:48090:48090"
    container_name: edgex-sys-mgmt-agent
    hostname: edgex-sys-mgmt-agent
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-sys-mgmt-agent
      ExecutorPath: /sys-mgmt-executor
      MetricsMechanism: executor
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:z
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - scheduler
      - notifications
      - metadata
      - data
      - command

  notifications:
    image: edgexfoundry/docker-support-notifications-go:1.2.1
    ports:
      - "127.0.0.1:48060:48060"
    container_name: edgex-support-notifications
    hostname: edgex-support-notifications
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-support-notifications
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - redis

  metadata:
    image: edgexfoundry/docker-core-metadata-go:1.2.1
    ports:
      - "127.0.0.1:48081:48081"
    container_name: edgex-core-metadata
    hostname: edgex-core-metadata
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-core-metadata
      Service_Timeout: "20000"
      Notifications_Sender: edgex-core-metadata
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - redis
      - notifications

  data:
    image: edgexfoundry/docker-core-data-go:1.2.1
    ports:
      - "127.0.0.1:48080:48080"
      - "127.0.0.1:5563:5563"
    container_name: edgex-core-data
    hostname: edgex-core-data
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-core-data
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - redis
      - metadata

  command:
    image: edgexfoundry/docker-core-command-go:1.2.1
    ports:
      - "127.0.0.1:48082:48082"
    container_name: edgex-core-command
    hostname: edgex-core-command
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-core-command
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - redis
      - metadata

  scheduler:
    image: edgexfoundry/docker-support-scheduler-go:1.2.1
    ports:
      - "127.0.0.1:48085:48085"
    container_name: edgex-support-scheduler
    hostname: edgex-support-scheduler
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-support-scheduler
      IntervalActions_ScrubPushed_Host: edgex-core-data
      IntervalActions_ScrubAged_Host: edgex-core-data
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - redis

  app-service-rules:
    image: edgexfoundry/docker-app-service-configurable:1.2.0
    ports:
      - "127.0.0.1:48100:48100"
    container_name: edgex-app-service-configurable-rules
    hostname: edgex-app-service-configurable-rules
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      edgex_profile: rules-engine
      Service_Host: edgex-app-service-configurable-rules
      Service_Port: 48100
      MessageBus_SubscribeHost_Host: edgex-core-data
      Binding_PublishTopic: events
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - data

  rulesengine:
    image: emqx/kuiper:0.4.2-alpine
    ports:
      - "127.0.0.1:48075:48075"
      - "127.0.0.1:20498:20498"
    container_name: edgex-kuiper
    hostname: edgex-kuiper
    networks:
      - edgex-network
    environment:
      # KUIPER_DEBUG: "true"
      KUIPER_CONSOLE_LOG: "true"
      KUIPER_REST_PORT: 48075
      EDGEX_SERVER: edgex-app-service-configurable-rules
      EDGEX_SERVICE_SERVER: http://edgex-core-data:48080
      EDGEX_TOPIC: events
      EDGEX_PROTOCOL: tcp
      EDGEX_PORT: 5566
    depends_on:
      - app-service-rules

  ui:
    image: edgexfoundry/docker-edgex-ui-go:1.2.1
    ports:
      - "127.0.0.1:4000:4000"
    container_name: edgex-ui-go
    hostname: edgex-ui-go
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-ui-go
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - scheduler
      - notifications
      - metadata
      - data
      - command

  # Support RulesEngine has been deprecated in the Geneva (1.2.0) release
  # If still required, simply uncomment the block below and comment out the block above.
  #
  # rulesengine:
  #   image: edgexfoundry/docker-support-rulesengine:1.2.1
  #   ports:
  #     - "127.0.0.1:48075:48075"
  #   container_name: edgex-support-rulesengine
  #   hostname: edgex-support-rulesengine
  #   networks:
  #     - edgex-network
  #   depends_on:
  #     - app-service-rules

#################################################################
# Device Services
#################################################################

  device-virtual:
    image: edgexfoundry/docker-device-virtual-go:1.2.2
    ports:
    - "127.0.0.1:49990:49990"
    container_name: edgex-device-virtual
    hostname: edgex-device-virtual
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-device-virtual
    depends_on:
      - consul
#      - logging  # uncomment if re-enabled remote logging
      - data
      - metadata

  device-rest:
    image: edgexfoundry/docker-device-rest-go:1.1.1
    ports:
      - "127.0.0.1:49986:49986"
    container_name: edgex-device-rest
    hostname: edgex-device-rest
    networks:
      - edgex-network
    environment:
      <<: *common-variables
      Service_Host: edgex-device-rest
    depends_on:
      - data
      - command
  #      - logging  # uncomment if re-enabled remote logging

  # device-random:
  #   image: edgexfoundry/docker-device-random-go:1.2.1
  #   ports:
  #     - "127.0.0.1:49988:49988"
  #   container_name: edgex-device-random
  #   hostname: edgex-device-random
  #   networks:
  #     - edgex-network
  #   environment:
  #     <<: *common-variables
  #     Service_Host: edgex-device-random
  #   depends_on:
  #     - data
  #     - command

#  device-mqtt:
#    image: edgexfoundry/docker-device-mqtt-go:1.2.1
#    ports:
#      - "127.0.0.1:49982:49982"
#    container_name: edgex-device-mqtt
#    hostname: edgex-device-mqtt
#    networks:
#      - edgex-network
#    environment:
#      <<: *common-variables
#      Service_Host: edgex-device-mqtt
#    depends_on:
#      - data
#      - command
#
#  device-modbus:
#    image: edgexfoundry/docker-device-modbus-go:1.2.1
#    ports:
#      - "127.0.0.1:49991:49991"
#    container_name: edgex-device-modbus
#    hostname: edgex-device-modbus
#    networks:
#      - edgex-network
#    environment:
#      <<: *common-variables
#      Service_Host: edgex-device-modbus
#    depends_on:
#      - data
#      - command
#
#  device-snmp:
#    image: edgexfoundry/docker-device-snmp-go:1.2.1
#    ports:
#      - "127.0.0.1:49993:49993"
#    container_name: edgex-device-snmp
#    hostname: edgex-device-snmp
#    networks:
#      - edgex-network
#    environment:
#      <<: *common-variables
#      Service_Host: edgex-device-snmp
#    depends_on:
#      - data
#      - command

networks:
  edgex-network:
    driver: "bridge"

在這裡插入圖片描述
Consul訪問地址:http://localhost:8500/ui
UI控制檯訪問地址:http://localhost:4000

裝置接入服務開發

登入看了下阿里雲的邊緣計算,驅動都有市場了,商機無限,相對於EdgeXFoundry的開發,簡直傻瓜式部署應用邊緣計算框架——一時感慨
在這裡插入圖片描述

安裝必須的庫檔案

sudo apt-get install libcurl4-openssl-dev libmicrohttpd-dev libyaml-dev libcbor-dev

需執行在其他平臺的,需要自行編譯對應平臺的庫
看下官網對版本的要求:

  • A version of GCC supporting C11.
  • CMake version 3 or greater and make.
  • Development libraries and headers for:
    • curl (version 7.32 or later)
    • microhttpd (version 0.9)
    • libyaml (version 0.1.6 or later)
    • libcbor (version 0.5)
    • libuuid (from util-linux v2.x)

獲取官方的SDK

這裡使用C

#指定分支版本
git clone -b 1.2.2 https://github.com/edgexfoundry/device-sdk-c.git
#或者直接下載最新版
git clone https://github.com/edgexfoundry/device-sdk-c.git

編譯SDK

進入原始碼目錄,直接make

cd device-sdk-c
make

順利無誤的情況下將得到x86_64平臺的sdk庫檔案(release目錄下),這可能不是我們目標所需平臺,先進行測試可用。
在這裡插入圖片描述
生成的xx.deb不建議使用,開發的裝置服務一般情況下是非x86平臺,更多可能是ARM64、ARMHF、AARCH64等架構的平臺,安裝沒啥用。

SDK的使用測試

在原始碼目錄下src/c/example下有模板檔案template.c

  • 按照官方說明,sdk提供了一些介面完善這些介面,即可完成裝置接入的開發,與阿里雲邊緣Link IoT Edge開發類似(裝置驅動開發即裝置接入)
  • 需要完善的介面在devsdk/devsdk.h中,當然template.c就是完善後的樣版
  • src/c/example目錄下有較多的例程,結合xx.yaml與xx.toml檔案看對應的原始碼

以下模板檔案可以看出只要完善以下回撥介面:

1、完善以下回撥介面
    template_init,         /* Initialize */
    template_reconfigure,  /* Reconfigure */
    template_discover,     /* Discovery */
    template_get_handler,  /* Get */
    template_put_handler,  /* Put */
    template_stop          /* Stop */
2、再定義裝置提供的資源,如溫溼度裝置,提供溫度、溼度資料資訊,提供裝置啟動關閉、資料採集週期等擴充套件控制資源
其實這些就類似於阿里雲LinkIoTEdge的物模型概念,阿里雲的是雲端介面配置自動生成json格式,這個就需要自己手寫了,而且是yaml格式的,不過提供了不少樣版,難度不是太大
3、編譯執行
/* template implementation of an Edgex device service using C SDK */

/*
 * Copyright (c) 2018-2020
 * IoTech Ltd
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

#include "devsdk/devsdk.h"
#include "edgex/edgex-base.h"

#include <unistd.h>
#include <signal.h>

#define ERR_CHECK(x) if (x.code) { fprintf (stderr, "Error: %d: %s\n", x.code, x.reason); devsdk_service_free (service); free (impl); return x.code; }

typedef struct template_driver
{
  iot_logger_t * lc;
} template_driver;

static void dump_protocols (iot_logger_t *lc, const devsdk_protocols *prots)
{
  iot_log_debug (lc, " [Other] protocol:");
  for (const devsdk_nvpairs *nv = devsdk_protocols_properties (prots, "Other"); nv; nv = nv->next)
  {
    iot_log_debug (lc, "    %s = %s", nv->name, nv->value);
  }
}

static void dump_attributes (iot_logger_t *lc, const devsdk_nvpairs *attrs)
{
  for (const devsdk_nvpairs *a = attrs; a; a = a->next)
  {
    iot_log_debug (lc, "    %s = %s", a->name, a->value);
  }
}

/* --- Initialize ---- */
/* Initialize performs protocol-specific initialization for the device
 * service.
 */
static bool template_init
(
  void *impl,
  struct iot_logger_t *lc,
  const iot_data_t *config
)
{
  template_driver *driver = (template_driver *) impl;
  iot_log_debug (lc, "Template Init. Driver Config follows:");
  if (config)
  {
    iot_data_map_iter_t iter;
    iot_data_map_iter (config, &iter);
    while (iot_data_map_iter_next (&iter))
    {
      iot_log_debug (lc, "    %s = %s", iot_data_map_iter_string_key (&iter), iot_data_map_iter_string_value (&iter));
    }
  }
  driver->lc = lc;
  iot_log_debug (lc, "Template Init done");
  return true;
}

/* --- Reconfigure ---- */
/* Reconfigure is called if the driver configuration is updated.
 */
static void template_reconfigure
(
  void *impl,
  const iot_data_t *config
)
{
  iot_data_map_iter_t iter;
  template_driver *driver = (template_driver *) impl;
  iot_log_debug (driver->lc, "Template Reconfiguration. New Config follows:");
  iot_data_map_iter (config, &iter);
  while (iot_data_map_iter_next (&iter))
  {
    iot_log_debug (driver->lc, "    %s = %s", iot_data_map_iter_string_key (&iter), iot_data_map_iter_string_value (&iter));
  }
}

/* ---- Discovery ---- */
/* Device services which are capable of device discovery should implement it
 * in this callback. It is called in response to a request on the
 * device service's discovery REST endpoint. New devices should be added using
 * the devsdk_add_device() method
 */
static void template_discover (void *impl) {}

/* ---- Get ---- */
/* Get triggers an asynchronous protocol specific GET operation.
 * The device to query is specified by the protocols. nreadings is
 * the number of values being requested and defines the size of the requests
 * and readings arrays. For each value, the commandrequest holds information
 * as to what is being requested. The implementation of this method should
 * query the device accordingly and write the resulting value into the
 * commandresult.
 *
 * Note - In a commandrequest, the DeviceResource represents a deviceResource
 * which is defined in the device profile.
*/
static bool template_get_handler
(
  void *impl,
  const char *devname,
  const devsdk_protocols *protocols,
  uint32_t nreadings,
  const devsdk_commandrequest *requests,
  devsdk_commandresult *readings,
  const devsdk_nvpairs *qparams,
  iot_data_t **exception
)
{
  template_driver *driver = (template_driver *) impl;

  /* Access the location of the device to be accessed and log it */
  iot_log_debug(driver->lc, "GET on device:");
  dump_protocols (driver->lc, protocols);

  for (uint32_t i = 0; i < nreadings; i++)
  {
    /* Log the attributes for each requested resource */
    iot_log_debug (driver->lc, "  Requested reading %u:", i);
    dump_attributes (driver->lc, requests[i].attributes);
    /* Fill in a result regardless */
    readings[i].value = iot_data_alloc_string ("Template result", IOT_DATA_REF);
  }
  return true;
}

/* ---- Put ---- */
/* Put triggers an asynchronous protocol specific SET operation.
 * The device to set values on is specified by the protocols.
 * nvalues is the number of values to be set and defines the size of the
 * requests and values arrays. For each value, the commandresult holds the
 * value, and the commandrequest holds information as to where it is to be
 * written. The implementation of this method should effect the write to the
 * device.
 *
 * Note - In a commandrequest, the DeviceResource represents a deviceResource
 * which is defined in the device profile.
*/
static bool template_put_handler
(
  void *impl,
  const char *devname,
  const devsdk_protocols *protocols,
  uint32_t nvalues,
  const devsdk_commandrequest *requests,
  const iot_data_t *values[],
  iot_data_t **exception
)
{
  template_driver *driver = (template_driver *) impl;
  /* Access the location of the device to be accessed and log it */
  iot_log_debug (driver->lc, "PUT on device:");
  dump_protocols (driver->lc, protocols);

  for (uint32_t i = 0; i < nvalues; i++)
  {
    /* A Device Service again makes use of the data provided to perform a PUT */
    /* Log the attributes */
    iot_log_debug (driver->lc, "  Requested device write %u:", i);
    dump_attributes (driver->lc, requests[i].attributes);
    switch (edgex_propertytype_data (values[i]))
    {
      case Edgex_String:
        iot_log_debug (driver->lc, "  Value: %s", iot_data_string (values[i]));
        break;
      case Edgex_Uint64:
        iot_log_debug (driver->lc, "  Value: %lu", iot_data_ui64 (values[i]));
        break;
      case Edgex_Bool:
        iot_log_debug (driver->lc, "  Value: %s", iot_data_bool (values[i]) ? "true" : "false");
        break;
      /* etc etc */
      default:
        iot_log_debug (driver->lc, "  Value has unexpected type %s: %s", iot_data_type_name (values[i]), iot_data_to_json (values[i]));
    }
  }
  return true;
}

/* ---- Stop ---- */
/* Stop performs any final actions before the device service is terminated */
static void template_stop (void *impl, bool force) {}

int main (int argc, char *argv[])
{
  sigset_t set;
  int sigret;

  template_driver * impl = malloc (sizeof (template_driver));
  memset (impl, 0, sizeof (template_driver));

  devsdk_error e;
  e.code = 0;

  /* Device Callbacks */
  devsdk_callbacks templateImpls =
  {
    template_init,         /* Initialize */
    template_reconfigure,  /* Reconfigure */
    template_discover,     /* Discovery */
    template_get_handler,  /* Get */
    template_put_handler,  /* Put */
    template_stop          /* Stop */
  };

  /* Initalise a new device service */
  devsdk_service_t *service = devsdk_service_new
    ("device-template", "1.0", impl, templateImpls, &argc, argv, &e);
  ERR_CHECK (e);

  int n = 1;
  while (n < argc)
  {
    if (strcmp (argv[n], "-h") == 0 || strcmp (argv[n], "--help") == 0)
    {
      printf ("Options:\n");
      printf ("  -h, --help\t\t\tShow this text\n");
      devsdk_usage ();
      return 0;
    }
    else
    {
      printf ("%s: Unrecognized option %s\n", argv[0], argv[n]);
      return 0;
    }
  }

  /* Set default config */
  iot_data_t *confparams = iot_data_alloc_map (IOT_DATA_STRING);
  iot_data_string_map_add (confparams, "TestParam1", iot_data_alloc_string ("X", IOT_DATA_REF));
  iot_data_string_map_add (confparams, "TestParam2", iot_data_alloc_string ("Y", IOT_DATA_REF));

  /* Start the device service*/
  devsdk_service_start (service, confparams, &e);
  ERR_CHECK (e);

  /* Wait for interrupt */
  sigemptyset (&set);
  sigaddset (&set, SIGINT);
  sigprocmask (SIG_BLOCK, &set, NULL);
  sigwait (&set, &sigret);
  sigprocmask (SIG_UNBLOCK, &set, NULL);

  /* Stop the device service */
  devsdk_service_stop (service, true, &e);
  ERR_CHECK (e);

  devsdk_service_free (service);
  free (impl);
  iot_data_free (confparams);
  return 0;
}

測試編譯

export CSDK_DIR=你下載sdk原始碼目錄路徑
export CSDK_LIB=你下載sdk編譯後生成的動態庫檔案路徑
gcc -I$CSDK_DIR/include -L$CSDK_LIB -o template template.c -lcsdk

執行

下圖只為說明介面獲取的方法,裝置名在哪看
在這裡插入圖片描述

#執行前,請先準備好configuration.toml服務配置檔案與xxx.yaml裝置配置檔案放置在同一個目錄下,如dir
./template -c dir

#讀取可用API介面
GET http://localhost:48082/api/v1/device/name/configuration.toml檔案中的裝置名

#獲得類似上圖中的資料即可
GET 獲取的url,例子如下
GET http://localhost:48082/api/v1/device/c5c93b51-226b-4b0f-a4e5-8bfbfa096382/command/2bb9992c-3b74-422a-b4c6-74b33492ca79

PUT 獲取的url 需設定的引數,例子如下
PUT http://localhost:48082/api/v1/device/59f51186-af23-4340-823f-0bbd8a65449f/command/db0dbed8-ad94-4706-9f34-6110e2b45797
{"Min_Int8":"-10", "Max_Int8":"10" }

#PUT與GET的資料不盡相同,皆是基於你所定義的裝置配置檔案中提供的裝置資源,而裝置配置檔案又是基於你依靠SDK編寫的裝置服務驅動

至此,皆是對官網的使用流程進行說明,下面進入正題。

使用SDK開發真實裝置接入服務

著手編寫一個溫溼度裝置接入

準備相關檔案及目錄

建立一個資料夾如:temperature-device-driver

mkdir temperature-device-driver && cd temperature-device-driver

建立device-temperature.c檔案、建立res目錄
temperature-device-driver目錄下建立build.sh指令碼檔案,內容如下:

#!/bin/sh

#定義SDK原始碼標頭檔案目錄
SDK_INC_DIR=/home/aron566/Workspace/C_SDK/device-sdk-c/include

#定義SDK動態庫檔案目錄
SDK_LIB_DIR=/home/aron566/Workspace/C_SDK/device-sdk-c/build/release/_CPack_Packages/Linux/TGZ/csdk-1.3.0/lib

#定義編譯生成的APP名稱
TARGET_APP_NAME=device_driver

#定義原始碼檔名稱
SOURCE_FILE_NAME=device_driver.c

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$SDK_LIB_DIR

case "$1" in
	make)
		gcc -I$SDK_INC_DIR -L$SDK_LIB_DIR -o $TARGET_APP_NAME $SOURCE_FILE_NAME -lcsdk
		;;
	run)
		./$TARGET_APP_NAME -c res
		;;
	*)
		echo "Usage: $0 {make|run}"
		exit 1
esac

新增可執行許可權

sudo chmod +x build.sh

編寫溫溼度裝置接入裝置服務

記住,完善介面即可

#include "devsdk/devsdk.h"
#include "edgex/edgex-base.h"

#include <unistd.h>
#include <signal.h>

#define ERR_CHECK(x) if (x.code) { fprintf (stderr, "Error: %d: %s\n", x.code, x.reason); devsdk_service_free (service); free (impl); return x.code; }


配置檔案xx.yaml和xx.toml

xx.yaml的修改需遵照裝置服務提供的裝置功能
官方對配置的介紹
其他配置的介紹

編譯

./build make

執行

執行可以使用指令碼,當然需要在指令碼中修改定義好變數

./build run

未完待續

相關文章