設定與 Docker (Linux) 的開放原始碼整合 - AWS IoT SiteWise

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

設定與 Docker (Linux) 的開放原始碼整合

若要簡化部署程序,您可以使用 Docker 在 Linux 環境中設定 Node-RED®、InfluxDB® 和 Grafana®。此方法使用預先設定的容器,以便快速部署和更輕鬆地管理元件。

Docker 設定先決條件

開始之前,請確認 具有下列項目:

部署 服務

此部署會在相同的主機上執行 SiteWise Edge、InfluxDB、Node-RED 和 Grafana。

設定環境

  1. 取得根存取權:

    sudo -i
  2. 建立 .env 檔案或匯出這些環境變數:

    export INFLUXDB_PASSWORD=your-secure-influxdb-password export INFLUXDB_TOKEN=your-secure-influxdb-token export GRAFANA_PASSWORD=your-secure-grafana-password

設定 Docker 網路

  • 使用名稱 建立橋接網路SiteWiseEdgeNodeRedDemoNetwork

    docker network create --driver=bridge SiteWiseEdgeNodeRedDemoNetwork

準備 Docker Compose 檔案

將下列 YAML 檔案的內容複製到 SiteWise Edge 閘道裝置。

services: influxdb: image: influxdb:latest container_name: influxdb ports: - "127.0.0.1:8086:8086" volumes: - influxdb-storage:/.influxdbv2 environment: - DOCKER_INFLUXDB_INIT_MODE=setup - DOCKER_INFLUXDB_INIT_USERNAME=admin - DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_PASSWORD} - DOCKER_INFLUXDB_INIT_ORG=iot-sitewise-edge - DOCKER_INFLUXDB_INIT_BUCKET=WindFarmData - DOCKER_INFLUXDB_INIT_RETENTION=0 - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN} networks: - SiteWiseEdgeNodeRedDemoNetwork restart: unless-stopped grafana: image: grafana/grafana:latest container_name: grafana ports: - "127.0.0.1:3000:3000" volumes: - grafana-storage:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning environment: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - GF_PATHS_CONFIG=/etc/grafana/grafana.ini - GF_LOG_LEVEL=info configs: - source: grafana_datasource target: /etc/grafana/provisioning/datasources/influxdb.yaml - source: grafana_preload_dashboard_config target: /etc/grafana/provisioning/dashboards/dashboard.yml - source: grafana_preload_dashboard target: /etc/grafana/provisioning/dashboards/demo_dashboard.json depends_on: - influxdb networks: - SiteWiseEdgeNodeRedDemoNetwork restart: unless-stopped nodered: build: context: . dockerfile_inline: | FROM nodered/node-red:latest RUN npm install node-red-contrib-influxdb container_name: nodered ports: - "127.0.0.1:1880:1880" volumes: - node_red_data:/data environment: - NODE_RED_ENABLE_SAFE_MODE=false - NODE_RED_ENABLE_PALETTE_EDIT=true - NODE_RED_AUTO_INSTALL_MODULES=true configs: - source: nodered_flows target: /data/flows.json - source: nodered_settings target: /data/settings.js - source: nodered_flows_cred target: /data/flows_cred.json depends_on: - influxdb networks: - SiteWiseEdgeNodeRedDemoNetwork restart: unless-stopped volumes: influxdb-storage: grafana-storage: node_red_data: networks: SiteWiseEdgeNodeRedDemoNetwork: external: true configs: grafana_datasource: content: | apiVersion: 1 datasources: - name: windfarm-demo type: influxdb access: proxy url: http://influxdb:8086 jsonData: version: Flux organization: iot-sitewise-edge defaultBucket: WindFarmData tlsSkipVerify: true secureJsonData: token: ${INFLUXDB_TOKEN} editable: false grafana_preload_dashboard_config: content: | apiVersion: 1 providers: - name: "Dashboard provider" orgId: 1 type: file options: path: /etc/grafana/provisioning/dashboards grafana_preload_dashboard: content: | { "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "grafana", "uid": "-- Grafana --" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": 1, "links": [], "panels": [ { "datasource": { "type": "influxdb", "uid": "PEB0DCBF338B3CEB2" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }, "id": 1, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "11.6.0", "targets": [ { "datasource": { "type": "influxdb", "uid": "PEB0DCBF338B3CEB2" }, "query": "from(bucket: \"WindFarmData\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"TurbineData\")\n |> filter(fn: (r) => r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r[\"name\"] == \"/Renton/WindFarm/Turbine/WindSpeed\")\n |> filter(fn: (r) => r[\"quality\"] == \"GOOD\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "refId": "A" } ], "title": "Wind Speed", "type": "timeseries" } ], "preload": false, "schemaVersion": 41, "tags": [], "templating": { "list": [] }, "time": { "from": "now-6h", "to": "now" }, "timepicker": {}, "timezone": "browser", "title": "Demo Dashboard", "uid": "eejtureqjo9a8c", "version": 2 } nodered_flows: content: | [ { "id": "95fce448fdd43b47", "type": "tab", "label": "Demo Flow", "disabled": false, "info": "" }, { "id": "5f63740b66af3386", "type": "mqtt out", "z": "95fce448fdd43b47", "name": "Publish to MQTT broker", "topic": "/Renton/WindFarm/Turbine/WindSpeed", "qos": "1", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "5744207557fa19be", "x": 830, "y": 200, "wires": [] }, { "id": "8f2eb590d596679b", "type": "function", "z": "95fce448fdd43b47", "name": "Translate to SiteWise payload", "func": "let input = msg.payload;\nlet output = {};\n\noutput[\"propertyAlias\"] = input.name;\n\nlet propertyVal = {}\n\nlet timeInSeconds = Math.floor(input.timestamp / 1000);\nlet offsetInNanos = (input.timestamp % 1000) * 1000000;\n\npropertyVal[\"timestamp\"] = {\n \"timeInSeconds\": timeInSeconds,\n \"offsetInNanos\": offsetInNanos,\n};\n\npropertyVal[\"quality\"] = input.quality\n\nlet typeNameConverter = {\n \"number\": (x) => Number.isInteger(x) ? \"integerValue\" : \"doubleValue\",\n \"boolean\": (x) => \"booleanValue\",\n \"string\": (x) => \"stringValue\", \n}\nlet typeName = typeNameConverter[typeof input.value](input.value)\npropertyVal[\"value\"] = {}\npropertyVal[\"value\"][typeName] = input.value;\n\noutput[\"propertyValues\"] = [propertyVal]\n\nreturn {\n payload: JSON.stringify(output)\n};", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 530, "y": 200, "wires": [ [ "5f63740b66af3386" ] ] }, { "id": "4b78cbdea5e3258c", "type": "inject", "z": "95fce448fdd43b47", "name": "Turbine Simulator", "props": [ { "p": "payload.timestamp", "v": "", "vt": "date" }, { "p": "payload.quality", "v": "GOOD", "vt": "str" }, { "p": "payload.value", "v": "$$random()", "vt": "jsonata" }, { "p": "payload.name", "v": "/Renton/WindFarm/Turbine/WindSpeed", "vt": "str" } ], "repeat": "1", "crontab": "", "once": false, "onceDelay": "", "topic": "", "x": 270, "y": 200, "wires": [ [ "8f2eb590d596679b" ] ] }, { "id": "b658bf337ea2e316", "type": "influxdb out", "z": "95fce448fdd43b47", "influxdb": "2f1c38495035d2e4", "name": "Store data in InfluxDB", "measurement": "TurbineData", "precision": "", "retentionPolicy": "", "database": "", "retentionPolicyV18Flux": "", "org": "iot-sitewise-edge", "bucket": "WindFarmData", "x": 840, "y": 340, "wires": [] }, { "id": "9432d39af35b202f", "type": "function", "z": "95fce448fdd43b47", "name": "Translate to InfluxDB payload", "func": "let data = msg.payload;\n\nlet timeInSeconds = data.propertyValues[0].timestamp.timeInSeconds;\nlet offsetInNanos = data.propertyValues[0].timestamp.offsetInNanos;\nlet timestampInMilliseconds = (timeInSeconds * 1000) + (offsetInNanos / 1000000);\n\nmsg.payload = [\n {\n \"timestamp(milliseconds_since_epoch)\": timestampInMilliseconds,\n \"value\": data.propertyValues[0].value.doubleValue\n },\n {\n \"name\": data.propertyAlias,\n \"quality\": data.propertyValues[0].quality\n }\n]\n\nreturn msg", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 560, "y": 340, "wires": [ [ "b658bf337ea2e316" ] ] }, { "id": "b689403d2c80816b", "type": "mqtt in", "z": "95fce448fdd43b47", "name": "Subscribe to MQTT broker", "topic": "/Renton/WindFarm/Turbine/WindSpeed", "qos": "1", "datatype": "auto-detect", "broker": "5744207557fa19be", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 290, "y": 340, "wires": [ [ "9432d39af35b202f" ] ] }, { "id": "4f59bed8e829fc35", "type": "comment", "z": "95fce448fdd43b47", "name": "Data Publish Flow", "info": "dfgh", "x": 270, "y": 160, "wires": [] }, { "id": "b218c7fc58c8b6e7", "type": "comment", "z": "95fce448fdd43b47", "name": "Data Retention flow", "info": "", "x": 270, "y": 300, "wires": [] }, { "id": "5744207557fa19be", "type": "mqtt-broker", "name": "emqx", "broker": "emqx", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": "5", "keepalive": 15, "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" }, { "id": "2f1c38495035d2e4", "type": "influxdb", "hostname": "influxdb", "port": 8086, "protocol": "http", "database": "", "name": "InfluxDB", "usetls": false, "tls": "", "influxdbVersion": "2.0", "url": "http://influxdb:8086", "timeout": "", "rejectUnauthorized": false } ] nodered_flows_cred: content: | { "2f1c38495035d2e4": { "token": "${INFLUXDB_TOKEN}" } } nodered_settings: content: | module.exports = { flowFile: 'flows.json', credentialSecret: false, adminAuth: null, editorTheme: { projects: { enabled: false } } }

更新 SiteWise Edge 部署

  1. 導覽至 AWS IoT 主控台

  2. 管理區段下的左側導覽功能表中選擇 Greengrass 裝置,然後選擇核心裝置

  3. 選取連接至 SiteWise Edge Gateway 的核心裝置。

  4. 選擇部署索引標籤,然後選擇部署 ID 值。

  5. 選擇動作,然後選取修訂

  6. 讀取快顯訊息,然後選擇修訂部署

  7. 步驟 2 - 選取元件中,選取下列元件,然後選擇下一步

    • aws.greengrass.clientdevices.mqtt.EMQX

    • aws.iot.SiteWiseEdgePublisher

  8. 步驟 3 - 設定元件中,選取aws.greengrass.clientdevices.mqtt.EMQX元件值並新增下列網路組態:

    { "emqxConfig": { "authorization": { "no_match": "allow" }, "listeners": { "tcp": { "default": { "enabled": true, "enable_authn": false } } } }, "authMode": "bypass", "dockerOptions": "-p 127.0.0.1:1883:1883 --network=SiteWiseEdgeNodeRedDemoNetwork", "requiresPrivilege": "true" }
  9. 選擇下一步

  10. 步驟 4 - 設定進階設定中,選擇下一步

  11. 選擇部署

啟動 服務

  1. 使用 Docker Compose 檔案啟動服務。在包含 compose.yaml 檔案的目錄下執行下列命令。

    docker compose up -d
  2. 建立 SSH 通道以存取 服務:

    ssh -i path_to_your_ssh_key -L 1880:127.0.0.1:1880 -L 3000:127.0.0.1:3000 -L 8086:127.0.0.1:8086 username@gateway_ip_address

此部署會在 中建立下列服務SiteWiseEdgeNodeRedDemoNetwork network

InfluxDB v2 (連接埠 8086)

包括預先設定的組織 (iot-sitewise-edge)、WindFarmData InfluxDB 儲存貯體和管理登入資料

Node-RED (連接埠 1880)

包含 InfluxDB 節點和預先設定的 AWS IoT SiteWise 整合流程

Grafana (連接埠 3000)

包括管理員使用者、InfluxDB 資料來源和監控儀表板

存取 服務

部署之後,請使用下列 URLs和登入資料存取 服務:

注意

您可以從主機或閘道機器存取每個服務。

服務存取詳細資訊
服務 URL 登入資料
Node-RED http://127.0.0.1:1880 不需要登入資料
InfluxDB http://127.0.0.1:8086

使用者名稱:admin

密碼:$INFLUXDB_PASSWORD

格拉法納 http://127.0.0.1:3000

使用者名稱:admin

密碼:$GRAFANA_PASSWORD

驗證部署

若要確保您的部署成功,請執行下列檢查:

  1. 對於 Node-RED,驗證是否存在兩個預先載入的流程:

    • 資料發佈流程

    • 資料保留流程

  2. 對於 AWS IoT SiteWise在 AWS IoT SiteWise 主控台中,使用別名 確認資料串流是否存在/Renton/WindFarm/Turbine/WindSpeed

  3. 對於 InfluxDB,請使用 Data Explorer 驗證WindFarmData儲存貯體內TurbineData測量中的資料儲存。

  4. 對於 Grafana,檢視儀表板以確認顯示從 Node-RED 產生的時間序列資料。