跳到主要内容

Shifu Cloud 初面演示

· 阅读需 9 分钟

在 2022.10.18,Shifu Cloud 正式公测,Shifu 的用户可以方便的通过可视化的方式选择需要接入的设备,即可快速完成设备接入 Shifu 的流程。

在这一天的线上直播展示中,来自边无际的杨希杰演示使用 Shifu Cloud 接入三个实际的设备,并在其基础上进行应用开发。下面让我们一起来回顾这一过程吧——

创建集群 安装shifu

在使用 Shifu Cloud 前,我们需要先确保已有一台安装了 Shifu 的电脑。

# 在本地使用kind启动k8s集群
$ sudo kind create cluster --image="kindest/node:v1.24.0"

# 克隆shifu仓库并进行安装
$ git clone https://github.com/Edgenesis/shifu.git
$ cd shifu
$ sudo kubectl apply -f pkg/k8s/crd/install/shifu_install.yml

如果你还不了解 Shifu 的安装和本地测试的方式,你可能需要查阅 下载安装本机安装测试 这两篇文档。

本机连接温湿度计和LED

我们要连接的设备为一个RS485的温湿度计和一个RS485的LED显示屏。温湿度计通过串口服务器连接至上位机(电脑),LED显示屏则通过RS485转USB的芯片连接至上位机。具体细节不做展开,在上位机开启HTTP服务之后:

  • 访问localhost:23330/temperature即可得到温湿度计的温度
  • 访问localhost:23330/humidity即可得到温湿度计的湿度
  • 访问localhost:23331/setfloat\?value=123.4value填入需要显示的数字即可在LED上显示
curl localhost:23330/temperature
curl localhost:23330/humidity
curl localhost:23331/setfloat\?value=123.4

接下来我们要将这两个设备接入 Shifu,也即将两个实际的设备(edgeDevices)转为k8s集群中的数字孪生(deviceShifus)。

一键生成配置文件

Shifu Cloud可以轻松生成 deviceShifu 的配置文件。

登录之后点击所有项目,即可添加设备。对于上面的两个设备,采用的都是HTTP协议,所以选择「公有协议 > HTTP」。「基础信息 > 设备名称」中分别填入my_thermometermy_led。设备的IP地址不能填写localhost,而是需要在电脑的网络设置中找到本机IP:如演示时使用的是192.168.0.123:23330192.168.0.123:23331

信息填写完毕后,网站会弹出一条命令,点击右侧按钮复制到终端执行,即可将设备部署到本机的k8s集群。这省去了手动编写YAML配置文件的时间,也更加直观。

测试设备正常接入

我们进入到集群中查看是否可以使用集群内的网络地址访问到两个设备的数字孪生:

# 运行一个nginx容器
$ sudo kubectl run --image=nginx:1.21 nginx
# 进入nginx容器
$ sudo kubectl exec -it nginx -- bash
# 与设备进行交互
$ curl http://deviceshifu-mythermometer-service.deviceshifu.svc.cluster.local/humidity
$ curl http://deviceshifu-mythermometer-service.deviceshifu.svc.cluster.local/temperature
$ curl http://deviceshifu-myled-service.deviceshifu.svc.cluster.local/setfloat?value=321

可以看到温湿度计的读数与LED的示数设置都没有问题,这说明设备已成功接入转为数字孪生。

打包应用镜像

我们希望在有温湿度计和LED的基础上进行应用开发,因此我们写出如下的Python程序:

main.py

import time
import requests
import json

isLocal = False
localIp = "192.168.0.123"
flag = -1
while True:
flag += 1

# [拿到数据]
if flag % 2 == 0:
# 拿到温度
url = f"http://{localIp}:23330/temperature" if isLocal else "http://deviceshifu-mythermometer-service.deviceshifu.svc.cluster.local/temperature"
else:
# 拿到湿度
url = f"http://{localIp}:23330/humidity" if isLocal else "http://deviceshifu-mythermometer-service.deviceshifu.svc.cluster.local/humidity"
res = requests.get(url)

# [转换数据]
try:
value = json.loads(res.text)['value']
print("DEBUG", value)
# [显示数据]
led_url = f"http://{localIp}:23331/setfloat?value={value}" if isLocal else f"http://deviceshifu-myled-service.deviceshifu.svc.cluster.local/setfloat?value={value}"
requests.get(led_url)
except:
print("DEBUG", res.text)

time.sleep(2)

该程序每2秒交替读取温湿度计的温度和湿度,并将读到的数据显示到LED上。

接下来我们希望将这个程序打包为一个镜像,之后就可以导入集群运行了:

requirements.txt

requests

Dockerfile

FROM python:3.9-slim-bullseye

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .
CMD ["python3", "main.py"]
# 构建镜像
$ sudo docker build -t xxx/connection:v0.0.1 .
# 确认镜像已构建
$ sudo docker images | grep connection
xxx/connection v0.0.1 a9526147ddad 2 minutes ago 125MB
# 将镜像载入集群
$ sudo kind load docker-image xxx/connection:v0.0.1
# 将镜像实例化为容器运行
$ sudo kubectl run --image=xxx/connection:v0.0.1 connection-name

接入摄像头

接下来我们希望接入海康威视摄像头进行监控的操控。该摄像头并没有在笔者的手边,而是通过Wi-Fi的方式无线连接。

可以看到 Shifu Cloud 支持了海康威视这个模块,点击后配置摄像头ip地址、用户名和密码即可一键接入。演示中的设备名称为 mycamera

# 进入nginx容器
$ sudo kubectl exec -it nginx -- bash
# 查看摄像头信息
$ curl http://deviceshifu-mycamera-service.deviceshifu.svc.cluster.local/info

这说明海康威视的摄像头已经接入了 Shifu 并转为了一个数字孪生。

控制摄像头位置

紧接着,我们希望调整摄像头的拍摄位置,使用move/up move/down move/left move/right这样的API即可控制摄像头的朝向:

$ curl http://deviceshifu-mycamera-service.deviceshifu.svc.cluster.local/move/up

为了查看效果,我们在电脑开启视频播放器,打开该串流地址:rtsp://<user_name>:<password>@<ip_address>。在 macOS 中,我们可以打开 IINA.app,菜单栏 > Open Url... > paste and enter,即可打开实时的监控视频码流。

可以看到,摄像头的位置确实发生了变化,我们成功的将实际摄像头的位置调整到了需要查看的朝向(右上角的相机从朝向天花板到朝向桌面上的显示器背侧)。

总结

在此次展示中,我们使用 Shifu Cloud 接入了三个设备,如果您与Meetup线下接入做对比,您会发现接入速度和门槛明显降低。

Shifu Cloud 还加入了啊哈时刻,在您初步熟悉整个网站的过程中提供帮助。

此外,Shifu Cloud 还在不断发展,后续将逐渐支持:

  • 更多的协议支持(边无际与开源社区都会不断增加支持的协议,提高快速接入的覆盖率)
  • 应用开发的支持(此次我们开发应用还是在本地进行应用打包,但是之后我们也可以直接在 shifu.cloud 进行便捷的应用开发)
  • 应用商店支持(其中包含开发者上传的自己开发的应用或者第三方插件,使用者可以一键安装)

感谢您看到这里,让我们一起期待 Shifu Cloud 的未来吧!

Meetup技术回顾

· 阅读需 10 分钟

在 2022.9.29 举办的线下 Shifu Meetup 活动中,来自边无际的杨希杰在现场展示用 Shifu 接入多个实际物联网设备,通过这种直观的接入方式展示了 Shifu 框架接入设备迅速、无单点故障、隔离性好、应用开发便捷等优点。

这次活动总共接入了五个设备,分别是 MQTT服务器、RS485的温湿度计 和 RS485的LED、西门子 S7 PLC、海康威视摄像头——这些都是比较常见的物联网设备。下面来让我们回忆一下接入过程吧。

创建集群并安装Shifu

首先我们需要在本地开启 Docker。使用 WindowsmacOS 的搜索打开 Docker Desktop,最小化到后台即可。

之后我们需要用 kind 创建一个k8s集群。后续 Shifu 和物联网设备的数字孪生都会以 Pod 的形式存在于这个集群中:

# 创建集群
$ sudo kind create cluster --image="kindest/node:v1.24.0"

# 提前准备镜像导入集群
$ sudo docker pull bitnami/kube-rbac-proxy:0.13.1
$ sudo docker pull edgehub/shifu-controller:v0.1.1
$ sudo docker pull nginx:1.21
$ sudo kind load docker-image bitnami/kube-rbac-proxy:0.13.1 edgehub/shifu-controller:v0.1.1 nginx:1.21

Shifu 支持一键安装,只需要先克隆Shifu仓库,之后用一条命令部署即可:

# 安装shifu
$ git clone https://github.com/Edgenesis/shifu.git
$ cd shifu
$ sudo kubectl apply -f pkg/k8s/crd/install/shifu_install.yml

# 跑一个应用程序 之后会用到
$ sudo kubectl run --image=nginx:1.21 nginx

您也可以查看更详细的本地安装Shifu教程

设备接入

MQTT

测试MQTT服务器

我们已经部署了一个MQTT服务器,可以先打开两个shell进行测试:

# shellA
$ mosquitto_sub -h 82.157.170.202 -t topic0

# shellB
$ mosquitto_pub -h 82.157.170.202 -t topic0 -m "哈哈哈"

可以看到发送的信息可以被正确接收。

接入设备

接下来我们可以先修改对应的配置,下载对应的镜像,然后用 kubectl apply 命令一键将MQTT服务器作为一个数字孪生接入 Shifu

修改examples/my_mqtt/mqtt_deploy中的spec.address82.157.170.202:1883spec.protocolSettings.MQTTSetting.MQTTTopictopic0

$ sudo docker pull edgehub/deviceshifu-http-mqtt:v0.1.1
$ sudo kind load docker-image edgehub/deviceshifu-http-mqtt:v0.1.1
$ sudo kubectl apply -f examples/my_mqtt/mqtt_deploy

读取数据

我们可以通过在集群中启动一个 nginx 应用来与数字孪生交互。

$ sudo kubectl exec -it nginx -- bash

$ curl http://deviceshifu-mqtt.deviceshifu.svc.cluster.local/mqtt_data

连接温度计和LED

连接设备至电脑

  • 温度计使用串口服务器通过网线连接至电脑
  • LED使用RS485转USB的芯片连接至电脑

本地启动HTTP服务

因为目前 Shifu 还不支持 Modbus协议,所以我们需要将Modbus读取的数据转为HTTP数据。

$ cd api_thermometer
$ uvicorn --host 0.0.0.0 --port 23330 main:app

$ cd api_led
$ uvicorn --host 0.0.0.0 --port 23331 main:app
api_thermometer文件夹内容
main.py
from fastapi import FastAPI
from typing import List
from pymodbus.client.sync import ModbusTcpClient

app = FastAPI()

def getHumidityAndTemperature() -> List[float]:
"""
返回从TAS-LAN-460得到的温度和湿度
"""
client = ModbusTcpClient(host='192.168.0.80', port=10123) # TAS-LAN-460的端口
client.connect()
SLAVE = 0x01
r = client.read_holding_registers(address=0x0000, count=2, unit=SLAVE)
print("自己拿到的数据", r.registers)
client.close()

result = [r.registers[0] / 10, r.registers[1] / 10]
return result

@app.get("/")
def root():
return { "message": "Hello World" }

@app.get("/temperature")
def getTemperature():
temperature = getHumidityAndTemperature()[1]
return { "value": f"{temperature}" }

@app.get("/humidity")
def getHumidity():
humidity = getHumidityAndTemperature()[0]
return { "value": f"{humidity}" }
requirements.txt
fastapi
pymodbus
api_led文件夹内容
main.py
from fastapi import FastAPI
from pymodbus.client.sync import ModbusSerialClient
from typing import List, Dict

app = FastAPI()

class ZhongshengLed:
"""
DEVICE_NAME = "中盛数码管显示屏"
"""

def __init__(self, device_address: int = 0x01, port: str = '/dev/tty.usbserial-14120') -> None:
self.device_address = device_address
self.client = ModbusSerialClient(method='rtu', port=port, stopbits=1, bytesize=8, parity='N', baudrate=9600, timeout=2.0)

def setLedCharacter(self, position: int, character: str):
self.setLedAscii(position=position, ascii_value=ZhongshengLed.character2ascii[character])

def setLedAscii(self, position: int, ascii_value: int):
self.client.connect()
self.client.write_register(address=position, value=ascii_value, unit=self.device_address)
self.client.close()

def setFourLedsString(self, string: str):
self.setFourLedsAsciis(ascii_values=[ZhongshengLed.character2ascii[string[0]], ZhongshengLed.character2ascii[string[1]], ZhongshengLed.character2ascii[string[2]], ZhongshengLed.character2ascii[string[3]]])

def setFourLedsAsciis(self, ascii_values: List[int]):
self.client.connect()
self.client.write_registers(address=ZhongshengLed.LedPosition.one, values=ascii_values, unit=self.device_address)
self.client.close()

class LedPosition:
one = 0
two = 1
three = 2
four = 3

character2ascii: Dict[str, int] = {
"0": 0x30, "1": 0x31, "2": 0x32, "3": 0x33, "4": 0x34,
"5": 0x35, "6": 0x36, "7": 0x37, "8": 0x38, "9": 0x39,
".": 0x2e, "-": 0x2d, " ": 0x20
}


def setDot(self, count: int = 1):
self.client.connect()
self.client.write_register(address=16, value=count, unit=self.device_address)
self.client.close()

def setNegative(self, isNegative: bool = False):
self.client.connect()
self.client.write_register(address=17, value=1 if isNegative else 0, unit=self.device_address)
self.client.close()

def setFloat(self, value: float):
"""
显示一位的小数
"""
self.setDot(count=1)
if value < 0:
self.setNegative(True)
else:
self.setNegative(False)

data = int(abs(value) * 10)

self.client.connect()
self.client.write_register(address=7, value=data, unit=self.device_address)

# self.client.write_register(address=16, value=value, unit=self.device_address)
self.client.close()

def setBrightness(self, brightness: int = 7):
self.client.connect()
self.client.write_register(address=14, value=brightness, unit=self.device_address)
self.client.close()

device = ZhongshengLed()

@app.get("/")
def root():
return { "message": "Hello World" }

@app.get("/setfloat/{value}")
def setTemperature(value: float):
device.setFloat(value=value)
return { "OK": "OK" }

@app.get("/setfloat/{value}")
def setTemperature(value: float):
device.setFloat(value=value)
return { "OK": "OK" }

@app.get("/setfloat")
def setTemperature(value: float = 0.0):
device.setFloat(value=value)
return { "OK": "OK" }
requirements.txt
fastapi
pymodbus

本地验证

$ curl http://localhost:23330/temperature
$ curl http://localhost:23330/humidity
$ curl http://localhost:23331/setfloat\?value\=123.4

接入设备

  • 修改http_thermometer/deployment/http_edgedevice.yaml中的ip地址。
  • 修改http_led/deployment/http_edgedevice.yaml中的ip地址。
$ sudo docker pull edgehub/deviceshifu-http-http:v0.1.1
$ sudo kind load docker-image edgehub/deviceshifu-http-http:v0.1.1
$ sudo kubectl apply -f examples/my_http_led/deployment
$ sudo kubectl apply -f examples/my_http_thermometer/deployment

与设备交互

打开 nginx 与温湿度计交互:

$ sudo kubectl exec -it nginx -- bash

$ curl http://my-thermometer.deviceshifu.svc.cluster.local/temperature
$ curl http://my-thermometer.deviceshifu.svc.cluster.local/humidity
$ curl http://my-led.deviceshifu.svc.cluster.local/setfloat?value=23.4

应用开发

将温度和湿度读取,然后在LED上面间歇显示出来。

$ sudo docker build -t yangxijie/connection:v0.0.1 .
$ sudo docker images | grep connection
yangxijie/connection v0.0.1 a9526147ddad 2 minutes ago 125MB
$ sudo kind load docker-image yangxijie/connection:v0.0.1
$ sudo kubectl run --image=yangxijie/connection:v0.0.1 connection-name

该应用的图解如下:

可以看到应用跑起来之后,LED显示屏上交替显示

当前文件夹文件
main.py
import time
import requests
import json

isLocal = False
localIp = "192.168.31.138"
flag = -1
while True:
flag += 1

# [拿到数据]
if flag % 2 == 0:
# 拿到温度
url = f"http://{localIp}:23330/temperature" if isLocal else "http://my-thermometer.deviceshifu.svc.cluster.local/temperature"
else:
# 拿到湿度
url = f"http://{localIp}:23330/humidity" if isLocal else "http://my-thermometer.deviceshifu.svc.cluster.local/humidity"
res = requests.get(url)

# [转换数据]
try:
value = json.loads(res.text)['value']
print("DEBUG", value)
# [显示数据]
led_url = f"http://{localIp}:23331/setfloat?value={value}" if isLocal else f"http://my-led.deviceshifu.svc.cluster.local/setfloat?value={value}"
requests.get(led_url)
except:
print("DEBUG", res.text)

time.sleep(2)
requirements.txt
requests
Dockerfile
FROM python:3.9-slim-bullseye

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .
CMD ["python3", "main.py"]

西门子PLC

接入设备

需要先修改IP地址为PLC的地址。

$ sudo docker pull edgehub/deviceshifu-http-http:v0.1.1
$ sudo docker pull edgehub/plc-device:v0.0.1
$ sudo kind load docker-image edgehub/deviceshifu-http-http:v0.1.1 edgehub/plc-device:v0.0.1
$ sudo kubectl apply -f examples/my_plc/plc-deployment

与设备交互

这里我们修改PLC上的一个比特,可以看到PLC的上的指示灯亮起来。在实际场景中,PLC会控制机械臂等大型设备进行操作。

$ sudo kubectl run nginx --image=nginx:1.21 -n deviceshifu 
$ sudo kubectl exec -it nginx -n deviceshifu -- bash

$ curl "deviceshifu-plc/sendsinglebit?rootaddress=Q&address=0&start=0&digit=1&value=1"; echo

海康威视摄像头

接入设备

获取摄像头ip地址,修改rtsp/camera-deployment/deviceshifu-camera-deployment.yaml中的ip地址

$ sudo docker pull edgehub/deviceshifu-http-http:v0.1.1
$ sudo docker pull edgehub/camera-python:v0.0.1
$ sudo kind load docker-image edgehub/deviceshifu-http-http:v0.1.1 edgehub/camera-python:v0.0.1
$ sudo kubectl apply -f examples/my_rtsp/camera-deployment

与设备交互

在集群中可以使用nginx查看设备信息:

# 集群中使用curl交互
$ sudo kubectl exec -it nginx -- bash

$ curl http://deviceshifu-camera.deviceshifu.svc.cluster.local/info

可以看到摄像头的数字孪生支持的指令为:

查看摄像头捕获的图片,我们需要将端口转发到本机,在浏览器上访问。

# 本机浏览器访问
$ sudo kubectl port-forward svc/deviceshifu-camera -n deviceshifu 8080:80
# 输入`localhost:8080/info`查看信息
# 输入`localhost:8080/capture`获取图片
# 输入`localhost:8080/move/{up|down|left|right}`操控相机位置
# 输入`localhost:8080/stream?timeout=0`获取实时视频流

总结

此次 Shifu Meetup 活动的顺利举办。可以看到 Shifu 能够让开发者快速接入设备、将各种协议统一转为HTTP方便管理和后续应用开发。Shifu 也有无单点故障、隔离性好等多种优势。

如果您对 Shifu 产生兴趣,欢迎访问 Shifu官网 了解更多。也欢迎您在 Shifu的GitHub仓库 给项目一个star!

Shifu+WasmEdge实现物联网数据轻松“瘦身”

· 阅读需 5 分钟

本文将简单介绍如何将 WasmEdge 集成到 Shifu 中,从而实现从IoT设备上采集的数据的清洗。

背景 🌇

当我们在使用 Shifu 采集数据的时候,通常会出现从设备所采集到的数据,与我们所需要的数据格式不同的情况。为解决此问题,我们可以使用 Shifu + WasmEdge 实现把 Shifu 采集到的数据通过 WasmEdge 进行处理后再返回给我们的应用程序。

以下为简单逻辑:

WasmEdge 简介 🏬

WasmEdge 是一个轻量级高性能 WebAssembly(WASM) 虚拟机,为边缘进行了优化。WasmEdge 可以应用于severless云函数、SaaS、区块链智能合约、物联网、汽车实时软件应用等多种场景。

准备 🗂

  1. kubectl v1.24.2
  2. docker 20.10.16
  3. kind v0.14.0
  4. git 2.36.1

部署 🔨

为了方便您更快的了解本篇文章,您可以通过以下命令从Github上下载程序。🚀

git clone https://github.com/Edgenesis/wasm-shifu-demo.git
cd wasm-shifu-demo

创建k8s集群 🐝

使用以下命令创建 k8s 集群。

$ kind delete cluster && kind create cluster
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

构建Shifu镜像 🪞

构建 Shifu 镜像。

$ make -f shifu/Makefile build-image-deviceshifu
$ kind load docker-image edgehub/deviceshifu-http-http:v0.0.6
$ docker images | grep edgehub/deviceshifu-http-http
edgehub/deviceshifu-http-http v0.0.6 1d6b3544b8ad 54 minutes ago 36.1MB

运行虚拟设备 🔌

为了方便您的体验,这里我们使用虚拟设备进行模拟。

安装并运行虚拟设备,设备的端口号为8099

$ docker build -f mockDevice/dockerfile -t mockdevice:v0.0.1 .
$ docker run -p 8099:8099 -itd mockdevice:v0.0.1
bdfd2b1323be mockdevice:v0.0.1 "./mockDevice" 19 seconds ago Up 18 seconds 0.0.0.0:8099->8099/tcp admiring_feistel

编写规则&编译wasm

您可以通过使用 JavaScript 编写规则。如果您不熟悉 JavaScript,您可以直接使用默认规则。🥮

规则文件路径: wasmEdge/js-func/src/js/run.js 您可以通过修改该规则实现不同的功能。

$ docker build -t wasm:v0.0.1 -f wasmEdge/js.dockerfile  .
$ kind load docker-image wasm:v0.0.1
$ kubectl apply -f wasmEdge/k8s

您可以通过以下命令检查 WasmEdgepod 运行情况。

$ kubectl get pod -n wasmedge
NAME READY STATUS RESTARTS AGE
wasm-deployment-fbc9564d8-td428 1/1 Running 0 1s

安装并运行Shifu

安装 Shifu

$ kubectl apply -f shifuConfig/shifu_install.yml
$ kubectl get pod -n shifu-crd-system
NAME READY STATUS RESTARTS AGE
shifu-crd-controller-manager-5bbdb4d786-s6h4m 2/2 Running 0 1s

安装 deviceShifumockDeivce 进行连接。在此之前,请先将shifuConfig/task3/task3.yaml 文件中的address修改成您电脑的IP。

spec:
sku: "E93"
connection: Ethernet
address: "192.168.14.163:8099"

通过以下命令,部署运行 deviceShifu。🏖

$ kubectl apply -f shifuConfig/task3
$ kubectl get pod -n deviceshifu
NAME READY STATUS RESTARTS AGE
deviceshifu-demodevice-deployment-5589b55569-l5nb2 1/1 Running 0 4s

体验 🕹

您可以启动一个 nginxdeviceShifu 进行通信。

$ kubectl run nginx --image=nginx:1.21
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 3s

通过以下命令,您即可与 Shifu 进行交互,从而实现从IoT设备上采集的数据的清洗。🛁

$ kubectl exec -it nginx -- curl -v  http://deviceshifu-demodevice-service.deviceshifu.svc.cluster.local/get_info;echo
[
{
"code":375287,
"name":"大气温度",
"val":"24.56",
"unit":"℃",
"exception":"温度过高"
},
{
"code":375287,
"name":"大气湿度",
"val":"81.63",
"unit":"%RH",
"exception":"湿度过高"
}
]

同时我们可以使用以下命令查看IoT设备所产生的数据。

$ curl localhost:8099/getInfo
{
"statusCode":"200",
"message":"success",
"entity":[
{
"dateTime":"2022-09-09 09:46:45",
"eUnit":"℃",
"eValue":"23.87",
"eKey":"e1",
"eName":"大气温度",
"eNum":"101"
},
{
"dateTime":"2022-09-09 09:46:45",
"eUnit":"%RH",
"eValue":"80.62",
"eKey":"e2",
"eName":"大气湿度",
"eNum":"102"
}
],
"deviceId":950920,
"deviceName":"950920",
"deviceRemark":"2022-09-09 09:46:45"
}

比较两个输出,我们成功地将数据采集并进行清洗,从而得到我们想要的数据。对比图如下:

对比图

使用Shifu在OpenYurt集群中接入RTSP协议摄像头

· 阅读需 10 分钟

OpenYurt 是一个云边计端算平台,借助 OpenYurt 的能力,可以将现有的 Kubernetes 集群转换成 OpenYurt 集群,并将 Kubernetes 的能力延伸到边缘侧。 OpenYurt 为云边端协同开发提供了多样化的功能,如打通云边通信的 YurtTunnel,为了方便管理节点单元应用部署/运维的 Yurt-App-Manager 以及提供了边缘自治的 YurtHub

开发者可以专注于云边端产品上的应用开发而不用担心底层架构的运维。Shifu 作为 Kubernetes 原生的开源物联网开发架构,可以兼容各种物联网设备的协议并将其抽象成一个为微服务软件对象。二者的能力有非常好的互补性。尤其是在 OpenYurt 中加入了 YurtDeviceController 以后,Shifu 可以用 OpenYurt 原生的方式来将设备进行抽象,大大提高物联网开发者的开发效率。

使用 OpenYurtShifu 以后,我们可以将原本复杂的 IoT,云边协同开发转化为简单的Web式开发。

简介

本文是一个在 OpenYurt 集群中使用 Shifu 接入 RTSP 协议摄像头的指南,其中包含 Shifu, Docker, Linux, Kubernetes, OpenYurt 的基本操作,任何开发者都可以阅读本文来学习 Shifu 的开发方法。

本文中的 Shifu 架构如下:

北向通过 deviceshifu-http-http 向上开放 HTTP API 接口,南向通过 rtsp-driver 来和实际设备交互。

目标

  1. 在Server端和Edge端通过 yurtctl 部署 OpenYurt, 并将Edge端加入Server端的集群
  2. 在Edge端部署网络摄像头的数字孪生
  3. 实现通过HTTP对网络摄像头的远程自动化管控

所需设备

  1. 两台运行 Linux 的虚拟机,Server和Edge的配置分别为4核16G内存和2核8G内存
  2. 一个 RTSP 协议的网络摄像头,本文中用到的摄像头型号为海康威视的 DS-2DE3Q140CN-W

软件环境

  • CentOS 7.9.2009
  • Go v1.17.1
  • yurtctl v0.6.1
  • kubectl: v1.19.8 (installed by yurtctl)

第一步 安装并部署OpenYurt集群

本文参考了 OpenYurt 的官方教程

首先让我们来下载 OpenYurt,从官方的GitHub直接克隆项目:

git clone https://github.com/openyurtio/openyurt.git

接着让我们下载v0.6.1版本的 yurtctl:

curl -LO https://github.com/openyurtio/openyurt/releases/download/v0.6.1/yurtctl 
chmod +x yurtctl

Server端的部署

在Server端创建 OpenYurt 集群:

./yurtctl init --apiserver-advertise-address <SERVER_IP> --openyurt-version latest --passwd 123 

看见如下信息即表示集群创建完成,这里的 --token 要记录一下用来将Edge节点加入到集群中

接下来看一下各个 Pod 的运行状况,通过 kubectl get pods -A:

遇到的几个问题

如果在 kubectl logs yurt-hub-server -n kube-system 里遇到:

请尝试 kubectl apply -f config/setup/yurt-controller-manager.yaml (方法来自 https://github.com/openyurtio/openyurt/issues/872#issuecomment-1148167419

除此之外,还有几个问题,如在 kubectl logs yurt-hub-server -n kube-system 里遇到如下输出:

请尝试 kubectl apply -f config/setup/ yurthub-cfg.yaml

如果在 yurt-tunnel-serveryurt-tunnel-agent 也遇到了类似的log,通过以下命令来修复 yurt-tunnelRBAC 问题:

kubectl apply -f config/setup/yurt-tunnel-agent.yaml 
kubectl apply -f config/setup/yurt-tunnel-server.yaml

untaint master节点来运行 Shifucontroller

kubectl taint nodes server node-role.kubernetes.io/master-

至此,Server端部署完毕。

Edge 端的部署

首先利用刚才Server端初始化得到的 token,执行:

./yurtctl join <MASTER_IP>:6443 --token <MASTER_INIT_TOKEN>  --node-type=edge --discovery-token-unsafe-skip-ca-verification --v=5 

确认Node状态,通过 kubectl get nodes

至此,一个Server端+一个Edge端的集群建立完毕。

第二步 在集群中部署Shifu

接下来让我们把 Shifu 部署到 OpenYurt 集群中

在Server端,克隆 Shifu 项目到本地:

git clone https://github.com/Edgenesis/shifu.git 
cd shifu/

接下来,安装 Shifu

kubectl apply -f pkg/k8s/crd/install/shifu_install.yml

通过 kubectl get pods -A 检查Pod状态:

看到 shifu-crd-system namespace中的Pod运行即可。

至此,Shifu 安装完毕。

第三步 部署摄像头的数字孪生deviceShifu

OpenYurt 提供了非常方便的节点池(NodePool)功能,可以让我们管理节点集群,对集群进行部署。

创建 beijing 节点池:

export WORKER_NODEPOOL="beijing" 
export EDGE_NODE="edge"
cat <<EOF | kubectl apply -f -
apiVersion: apps.openyurt.io/v1alpha1
kind: NodePool
metadata:
name: $WORKER_NODEPOOL
spec:
type: Edge
EOF

输出如下:

接着将Edge服务器 labelbeijingNodePool

kubectl label node $EDGE_NODE apps.openyurt.io/desired-nodepool=beijing

查看一下 NodePool 的状况,应该有一个 READYNODES

kubectl get nodepool

因为物联网的边缘节点通常是分布式在同一场景内的,这里可以使用 OpenYurtUnitedDeployment 功能根据 NodePool 来自动部署 。

安装 Yurt-app-manager:

git clone https://github.com/openyurtio/yurt-app-manager.git
cd yurt-app-manager
kubectl apply -f config/setup/all_in_one.yaml

使用 UnitedDeployment 来部署虚拟的海康摄像头,YAML文件如下:

deviceshifu-camera-unitedDeployment.yaml
apiVersion: apps.openyurt.io/v1alpha1
kind: UnitedDeployment
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: deviceshifu-hikvision-camera-deployment
spec:
selector:
matchLabels:
app: deviceshifu-hikvision-camera-deployment
workloadTemplate:
deploymentTemplate:
metadata:
labels:
app: deviceshifu-hikvision-camera-deployment
name: deviceshifu-hikvision-camera-deployment
namespace: default
spec:
selector:
matchLabels:
app: deviceshifu-hikvision-camera-deployment
template:
metadata:
labels:
app: deviceshifu-hikvision-camera-deployment
spec:
containers:
- image: edgehub/deviceshifu-http-http:v0.0.1
name: deviceshifu-http
ports:
- containerPort: 8080
volumeMounts:
- name: deviceshifu-config
mountPath: "/etc/edgedevice/config"
readOnly: true
env:
- name: EDGEDEVICE_NAME
value: "deviceshifu-hikvision-camera"
- name: EDGEDEVICE_NAMESPACE
value: "devices"
- image: edgenesis/camera-python:v0.0.1
name: camera-python
ports:
- containerPort: 11112
volumeMounts:
- name: deviceshifu-config
mountPath: "/etc/edgedevice/config"
readOnly: true
env:
- name: EDGEDEVICE_NAME
value: "deviceshifu-hikvision-camera"
- name: EDGEDEVICE_NAMESPACE
value: "devices"
- name: IP_CAMERA_ADDRESS
value: "<CAMERA_IP>"
- name: IP_CAMERA_USERNAME
value: "<CAMERA_USERNAME>"
- name: IP_CAMERA_PASSWORD
value: "<CAMERA_PASSWORD>"
- name: IP_CAMERA_CONTAINER_PORT
value: "11112"
- name: PYTHONUNBUFFERED
value: "1"
volumes:
- name: deviceshifu-config
configMap:
name: deviceshifu-hikvision-camera-configmap-0.0.1
serviceAccountName: edgedevice-sa
topology:
pools:
- name: beijing
nodeSelectorTerm:
matchExpressions:
- key: apps.openyurt.io/nodepool
operator: In
values:
- beijing
replicas: 1
revisionHistoryLimit: 5
deviceshifu-camera-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: deviceshifu-hikvision-camera-deployment
name: deviceshifu-hikvision-camera
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: deviceshifu-hikvision-camera-deployment
type: LoadBalancer
deviceshifu-camera-unitedDeployment.yaml
apiVersion: shifu.edgenesis.io/v1alpha1
kind: EdgeDevice
metadata:
name: deviceshifu-hikvision-camera
namespace: devices
spec:
sku: "HikVision Camera"
connection: Ethernet
address: 0.0.0.0:11112
protocol: HTTP
deviceshifu-camera-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: deviceshifu-hikvision-camera-configmap-0.0.1
namespace: default
data:
driverProperties: |
driverSku: HikVision
driverImage: edgenesis/camera-python:v0.0.1
instructions: |
capture:
info:
stream:
move/up:
move/down:
move/left:
move/right:
telemetries: |
device_health:
properties:
instruction: info
initialDelayMs: 1000
intervalMs: 1000

将这四个文件放到一个目录,如下:

camera-unitedDeployment/ 
├── camera-edgedevice.yaml
├── deviceshifu-camera-configmap.yaml
├── deviceshifu-camera-service.yaml
└── deviceshifu-camera-unitedDeployment.yaml

接下来部署:

kubectl apply -f camera-unitedDeployment/

通过 kubectl get ud 查看 UnitedDeployment 状态:

通过 kubectl get pods -owide 来确认 Pod 部署在了 beijing NodePool 里的Edge服务器中:

我们可以在集群中通过 kubectl get edgedevices -n devices 查看 Shifu 的虚拟设备:

再通过 kubectl describe edgedevices -n devices 查看设备的详细信息如配置,状态等:

至此,摄像头孪生部署完毕。

运行效果

接下来我们来控制摄像头,这里使用一个 nginxPod 来代表应用:

kubectl run nginx --image=nginx

nginx 开始运行时,通过 kubectl exec -it nginx -- bash 来进入 nginx 的命令行:

通过以下命令可以直接控制摄像头:

curl deviceshifu-hikvision-camera/move/{up/down/left/right}

如果我们想查看摄像头当前拍摄以及当前视频流,需要将摄像头的service通过 kubectl port-forward service/deviceshifu-hikvision-camera 30080:80 --address='0.0.0.0' 代理到本地。

在浏览器中输入服务器的IP加端口号,可以直接查看图像/视频流:

<SERVER_IP>:30080/capture
<SERVER_IP>:30080/stream

总结

在此篇文章中,我们讲述了如何将 Shifu 部署在 OpenYurt 集群中来增加 RTSP 摄像头的支持。

在将来的文章中,我们也会尝试将 ShifuOpenYurtYurtDeviceController 进行整合,通过 OpenYurt 原生的方式延申 OpenYurt 的能力到更多物联网设备的管理中。

使用EMQX与Shifu实现设备联动

· 阅读需 5 分钟

EMQX 是一个在世界范围内非常受欢迎的 MQTT Broker。它拥有基于 Kubernetes 的云原生架构,使得自身能力极为适合当今越来越复杂的物联网场景,让设备消息的传输更为高效。因此,Shifu 作为 Kubernetes 原生的框架,可以与 EMQX 完美结合,为 EMQX 提供智能的多协议设备联动的能力。

简介

本文将介绍如何在集群内部署 EMQXShifu,接入一个以 MQTT 为通信方式的温度计和一个以 RTSP 为传输协议的海康威视摄像头,并加入一个应用与 Shifu 进行交互,使得每次温度计检测到超过37度的体温就会让摄像头拍下一张当前照片。

本文使用的简单架构如下:

准备

本文使用了如下服务和工具:

  1. Kubernetes: 1.20.10
    • kubectl
    • kubeadm
    • kubelet
  2. Golang: 1.16.10
  3. Docker: 19.03.9
  4. EMQX: 4.1-rc1

第一步 部署Kubernetes

本步可以参考 Kubernetes 的官方教程进行部署:

https://kubernetes.io/docs/setup/

在部署完成后我们应当看到终端打印出如下信息:

第二步 部署Shifu

Shifu 的 GitHub仓库 克隆到本地:

git clone https://github.com/Edgenesis/shifu.git

然后可以通过下列命令部署 Shifu

kubectl apply -f shifu/pkg/k8s/crd/install/shifu_install.yml

部署完成后我们应当看到 ShifuCRD controller 已经完成部署:

第三步 部署EMQX

首先需要安装 EMQX Operator Controller

$ curl -f -L "https://github.com/emqx/emqx-operator/releases/download/1.1.6/emqx-operator-controller.yaml" | kubectl apply -f -

接着我们写一个最简单的 deployment.yaml

然后就可以部署一个 EMQX 了:

kubectl apply –f deployment.yaml

第四步 接入设备

对于温度计,我们只需要调整它的 MQTT 设置,让其可以向 EMQX 发布 MQTT 信息即可。

(如果是集群外的温度计,我们可以通过 Kubernetes Service 来开放 External IP 供访问)

对于摄像头,Shifu 的仓库已经包括一个使用 RTSP 的海康威视摄像头的配置文件,我们可以轻松更改配置文件中的IP、用户名、密码,将它接入 Shifu

至此,我们的设备已经连接完毕,下面就可以开始联动了。

联动应用

我们简单写一个 Python 应用,用来实现下面的逻辑:

该应用向 EMQX 订阅 temperature-shifu-mqtt 的消息,每次消息都只包括一个表示当前温度的数字;如果当前温度大于37度,则操作摄像头拍摄一张照片并保存在本地。

以下是应用代码:

加个 capture function 封装所有摄像头的动作。接着我们就可以将其部署到集群中,开始监视了:

python3 app.py 10.244.0.33

总结

本文描述了如何让 EMQXShifu 赋予更高效的 MQTT Broker 能力,同时让ShifuMQTT 合作为设备提供联动能力。在现实的应用场景之中,我们可以使用一个仅需一百余元的的红外温度计+摄像头组合,来代替数千元且表现并不稳定的测温摄像头,在大规模部署的情况下节省巨额成本。

在KubeEdge上部署Shifu

· 阅读需 6 分钟

如今十分流行的开源项目 KubeEdge 给开发者提供了一个基于 Kubernetes 的云边协同方案。它成功地将 Kubernetes 的集群编排能力融合到了物联网的边缘场景之中,使得对边缘算力的调度和管理更加轻量、也更加高效。

Shifu 作为同样基于 Kubernetes 的开源物联网开发框架,它对于多种设备的兼容和虚拟化将为 KubeEdge 在边缘端的应用提供助力。事实上,二者在能力上拥有非常好的互补性,在多设备兼容的同时,运行在 KubeEdge 上的 Shifu 可以轻松管理边缘端运行的轻量 Pod

有了 KubeEdge + Shifu 的强强联手,我们就可以把IoT设备抽象成 API,把原本复杂的传统物联网开发模式转化为简单的Web开发模式!

下面就让我们来看一下如何让 Shifu 运行在 KubeEdge 上,并且给开发者们提供价值吧!

简介

本文将简单介绍在 KubeEdge 上部署 Shifu 的步骤,并接入一个海康威视的摄像头(使用 RTSP 进行视频流传输)的实例,为 KubeEdge 的架构加入海康威视摄像头支持。

本文使用的架构如下:

准备

本文使用了如下服务和工具:

  1. Kubernetes: 1.21.5
    • kubectl
    • kubeadm
    • kubelet
  2. Golang: 1.16.10
  3. Docker: 19.03.9
  4. KubeEdge: 1.7.2

同时,KubeEdgeCloud端Edge端 分别运行在不同的 Linux 实例上,环境均为 Ubuntu Server 20.04

上述服务和工具中,Cloud端 需要安装全部上述服务和工具,而 Edge端 只需要安装 DockerKubeEdge

第一步 在Cloud端部署Kubernetes

本步可以参考Kubernetes的官方教程进行部署。

在部署完成后我们应当看到终端打印出如下信息:

第二步 在Cloud端部署Shifu

ShifuGithub 仓库克隆到本地:

git clone https://github.com/Edgenesis/shifu.git

然后可以通过下列命令部署 Shifu:

kubectl apply -f shifu/pkg/k8s/crd/install/shifu_install.yml

部署完成后我们应当看到 ShifuCRD controller 已经完成部署:

第三步 在Cloud端部署KubeEdge

本步可以参考 KubeEdge 的官方教程,使用 keadm 进行部署。

在部署完成后我们应当看到终端打印出如下信息:

第四步 在Cloud端获取token

运行如下命令:

keadm gettoken

请保存获得的token以便Edge端使用。

现在Cloud端的配置告一段落,我们现在切换到Edge端的机器,让它加入集群。

第五步 在Edge端加入集群

在Edge端运行如下命令:

keadm join --cloudcore-ipport="<Cloud端advertise-address>:10000" --token=<第四步获得的token>

在部署完成后我们应当看到终端打印出如下信息:

此时切换回到Cloud端,查看nodes:

我们可以看到Cloud端和Edge端都已经部署完毕了。

现在我们可以开始部署设备了。

通过 KubeEdge,我们可以做到只在Cloud端进行 Kubernetes 操作并且部署到Edge端,同时保持Edge端无需安装 Kubernetes 组件,保证轻量化。

第六步 在Cloud端修改海康威视摄像头的配置文件

Shifu 需要简单的配置文件来实现数字孪生的生成。在 Shifu 中,数字孪生被称为 deviceShifu,以 Pod 的形式运行在集群里。

Shifu 提供了接入海康威视摄像头的配置文件,其路径为 https://github.com/Edgenesis/shifu/tree/main/examples/rtspDeviceShifu/

Shifu 默认将 deviceShifu 部署在拥有完整 Kubernetes 实例的机器上。在 KubeEdge 的环境下,边缘端无需运行完整的 Kubernetes,因此 Shifu 也准备了针对云边协同环境的轻量的 deviceShifu 供使用。我们可以更改 deviceshifu-camera-deployment.yaml,让它使用边缘侧的 deviceShifu,并添加 nodeName 将其部署在 edge node:

第七步 部署海康威视摄像头Pod

在Cloud端,运行下列命令:

kubectl apply -f shifu/examples/rtspDeviceShifu/camera-deployment

此时,我们可以查看camera相关的 Pod

最后一步 在Edge端进行确认

在Edge端,我们可以看到camera相关的Docker容器已经在运行了:

我们可以非常简单地调用 deviceShifu 提供的 capture/stream/info/move 等一系列 HTTP API,对摄像头进行操作,比如下面的动图:

相关命令:

curl edgedevice-camera/move

至此,我们就完成了在 KubeEdge 上运行 Shifu 的全部步骤。

接入私有驱动

· 阅读需 17 分钟

在这一篇文章中,我们会先使用 Python 编写一个 GPIO 的驱动控制 LED,之后将其接入 Shifu 进行交互和管理。

创建驱动

目标

  • 完成简单LED电路连接
  • 基本的树莓派/SSH配置
  • 基本的Python语法以及GPIO库知识

所用设备

  • 树莓派 Raspberry Pi 3B+ 运行64位 Raspberry Pi OS
  • 1个面包板
  • 3个LED灯泡(红 黄 绿)
  • 1个330欧姆的电阻

需要的基本知识

  • 简单的Python语法
  • Linux命令行基本操作(创建文件 安装应用 SSH 运行程序)

第一步 电路设计

首先我们来设计一下电路,我们需要设计一个电路,可以让树莓派的GPIO输出来控制单个LED灯泡的开/关。本文采用了最直接的办法,即直接使用GPIO来对LED灯泡进行供电。

电路图如下:

图中GPIO的22,23,19针分别控制了红,绿,黄三个LED。最后串联的330欧电阻是为了防止LED电流过大烧毁。

第二步 电路实施

根据Raspberry Pi官方文档中的针脚布局(Pin Layout),我们可以看到针脚的具体位置,这里用到了第15,16,35,39号针脚:

将这四个针脚接上线缆的母口,然后在面包板上将剩余的电路连接好:

图中红,绿,黄,灰线缆分别对应了GPIO22 GPIO23 GPIO19和地(ground)。

至此,电路设计与连接部分结束。

第三步 树莓派准备

首先在树莓派中安装一个操作系统,本文中用到的是 Raspberry Pi OS (64-bit)下载链接

将SD卡插入读卡器,连接到电脑的USB端口中,再将下载后的压缩包通过 balenaEtcher 刷入SD卡即可。balenaEtcher链接

将SD卡放入树莓派,插入电源,显示器即可开始配置。

首先,为了方便开发/调试我们需要开启SSH,从桌面上开启终端,然后输入 sudo raspi-config 来进入配置界面,选择 Interface Options

选择 SSH:

按回车,接着按左键选择Yes来开启SSH服务:

之后按右选择Finish,再回车退出即可:

这时,SSH服务已经开启了,但是我们需要知道树莓派的IP才可以SSH,这里我们通过系统自带的 ip addr 来查看:

可以看到,IP地址为 192.168.15.122

回到电脑中,通过 ssh pi@192.168.15.122 即可以远程进入到树莓派的命令行中

至此,树莓派以及硬件的准备就结束了。

备注

早些版本的树莓派OS可能需要手动开启GPIO

第四步 驱动编写

一切就绪,现在我们来编写第一个驱动!

首先先确保系统中安装了 Python,如果没有的话可以运行如下命令:

$ sudo apt-get update && sudo apt install python3 -y

安装完毕后可以通过 python -V 来检查安装状态,如显示版本则成功:

$ python -V
Python 3.9.2

接下来让我们先从一个LED灯泡控制开始,我们要控制红色LED的亮/灭,使用以下代码:

驱动中用到的模块有:

  • RPi.GPIO 用来控制树莓派的GPIO
  • argparse 用来解析命令行输入

先将GPIO的模式置到GPIO.BCM模式,在这个模式下针脚的数字为GPIO的数字,非树莓派板上的针脚顺序。

GPIO.setmode(GPIO.BCM)

然后将警告关闭,本文中树莓派只会被这一个驱动程序控制

GPIO.setwarnings(False)

接下来处理一下程序输入,本驱动程序会接受两个输入:

  1. -p, --port, 代表程序操控的GPIO针
  2. -o, --operate, 代表程序对于GPIO针的操作,on 代表电路中的 1, 即 3.3Voff 代表电路中的 0, 即 0V

这段代码为:

parser = argparse.ArgumentParser()
parser.add_argument("-p", "--pin", type=int, default=None, help="Specify the GPIO pin to operate, e.g.: '17'")
parser.add_argument("-o", "--operate", type=str, default=None, help="Specify the GPIO output, e.g.: 'on/off'")
args = parser.parse_args()

接下来是一些错误处理,当针脚数和操作不为空时,将参数传入函数 turnOnLed 进行操作,否则打印出警告:

if args.pin and args.operate:
turnOnLed(args.pin, args.operate)
else:
print("need to specify both pin and operate arguments, type --help for more information")

主程序部分结束,下面我们看一下控制LED灯泡的函数 turnOnLed

首先是对传参的判断,确定 operate 变量为 on 或者 off ,否则返回。当变量为 on 的时候,将输出变量 gpio_out 设为 GPIO.HIGHoff 时则设为 GPIO.LOW

这两个值分别代表了开或者关:

if operate == "on":
gpio_out = GPIO.HIGH
elif operate == "off":
gpio_out = GPIO.LOW
else:
print("operate is neither on/off, quitting...")
return

最后,就是将该针脚的模式设为输出:GPIO.setup(pin, GPIO.OUT)

并将针脚的输出切换到开/关:GPIO.output(pin, gpio_out)

运行效果

程序通过 python led_driver.py -p {pin #} -o {operate} 来操控

如果想让红色灯泡亮,我们执行 python led_driver.py -p 22 -o on

至此,给树莓派编写的简单控制LED灯泡驱动就编写完成了!

备注

本质上这个驱动实现了树莓派GPIO针的操控,所以我们也可以使用这个驱动来操控任意可以被3.3V控制的电路,针脚也不仅限于本文中的22,23和19。大家可以自行发挥想象力制作各式各样的测试电路。

接入Shifu

接下来我们使用刚刚编写的驱动接入 Shifu 框架进行交互和管理。

本文中的 Shifu 架构如下:

北向通过 deviceshifu-http-http 向上开放 HTTP API接口,南向通过 rpio-gpio-driver 来和实际设备交互。

目标

  1. 在树莓派上安装k3s集群并安装Shifu Framework
  2. 打包树莓派LED驱动到一个容器镜像
  3. 在Shifu中部署树莓派LED的数字孪生
  4. 实现对树莓派LED的远程自动化管控

所用设备

  • 树莓派 Raspberry Pi 3B+ 运行64位 Raspberry Pi OS

需要的基本知识

  • Docker/containerd基本操作
  • K8s/K3s基本操作

第一步 安装k3s

首先我们要在树莓派中运行一个 Kubernetes集群,这里并不限制用户使用的版本,但是为了节省资源本文中使用的是k3s安装教程

安装完毕后,执行 kubectl version 查看当前 Kubernetes版本:

利用 kubectl get nodes 查看当前集群的状态,显示 Ready 即表示集群可以使用:

至此,k3s安装结束。

第二步 安装Shifu

首先将 Shifu 项目仓库克隆到本地,项目地址为:

$ git clone https://github.com/Edgenesis/shifu.git

下面通过 kubectl apply -f shifu/pkg/k8s/crd/install/shifu_install.yml 即可一键将 Shifu 部署到 k3s集群中:

再次执行 kubectl get pods -A,即可看到 Shifu Framework 的控制器被部署到集群中:

我们也可以通过 edgedevices 这个CRD来管理设备资源(当前没有设备):

至此,Shifu 安装完毕。

第三步 打包驱动

我们需要利用 Shifu 提供的一个小工具来实现可以远程操纵本地驱动,详细的教程请看:

这个小工具实现了将用户/程序发送来的HTTP请求转换到本地命令行来执行。

教程里面提供了一个驱动示例,路径为 https://github.com/Edgenesis/shifu/blob/main/examples/driver_utils/simple-alpine/Dockerfile.sample

内容如下:

可以看到实例 Dockerfile 分两部分,首先是用 golang 这个镜像来编译 Shifu 提供的 http_to_ssh_stub.go 来实现HTTP到SSH命令行的转换。接着是利用一个空的 alpine 镜像,配置SSH来供演示。

接下来让我们来正式操作。

考虑到树莓派的性能局限,本次编译将从电脑端执行,将编译好的镜像推送到Docker Hub来供远程调用即可。

首先,我们建立一个新的文件夹,这里用的是 dev,然后将创建的树莓派LED驱动保存到该目录:

dev/
└── led_driver.py

驱动内容不变

Shifu 项目的 driver_util/examples/simple-alpine/ 目录下将 Dockerfile.sample 复制到 dev 目录下:

dev/
├── Dockerfile.sample
└── led_driver.py

更改以下字段将第二部的镜像从 alpine 改为 python:alpine,安装 RPi.GPIOPython

最后将 Python驱动 拷贝到运行容器中,新的 Dockerfile 如下,改动的地方已用注释标记出来:

FROM golang:1.17.1 as builder

WORKDIR /

ENV GOPROXY=https://goproxy.cn,direct
ENV GO111MODULE=on
ENV GOPRIVATE=github.com/Edgenesis

COPY driver_util driver_util

WORKDIR /driver_util
RUN go mod download

# Build the Go app
RUN CGO_ENABLED=0 GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) gobuild -a -o /output/http2ssh-stub http_to_ssh_stub.go

FROM python:alpine # modified

RUN apk add --no-cache --update openrc openssh \
&& mkdir -p /run/openrc \
&& touch /run/openrc/softlevel \
&& sed -ie "s/#PubkeyAuthentication/PubkeyAuthentication/g"/etc/ssh/sshd_config \
&& sed -ie "s/#PasswordAuthenticationyes/PasswordAuthentication no/g" /etc/ssh/sshd_config \
&& sed -ie "s/AllowTcpForwardingno/AllowTcpForwarding yes/g" /etc/ssh/sshd_config \
&& echo"PubkeyAcceptedKeyTypes=+ssh-rsa" >> /etc/ssh/ sshd_config\ # modified
&& ssh-keygen -A \
&& passwd -d root \
&& mkdir ~/.ssh \
&& while ! [ -e/etc/ssh/ssh_host_rsa_key.pub ]; do sleep 1; done \
&& cp /etc/ssh/ssh_host_rsa_key.pub~/.ssh/authorized_keys

RUN apk add --no-cache -Uu --virtual .build-dependencies libffi-devopenssl-dev build-base musl \
&& pip3 install --no-cache --upgrade RPi.GPIO\
&& apk del --purge .build-dependencies \
&& apk add --no-cache --purge curlca-certificates musl \
&& rm -rf /var/cache/apk/* /tmp/* # modified

WORKDIR /root/

COPY --from=builder /output/http2ssh-stub http2ssh-stub
COPY --from=builder/driver_util/examples/simple-alpine/docker-entrypoint.sh docker-entrypoint.sh
COPY dev/led_driver.py led_driver.py # modified
RUN chmod +x docker-entrypoint.sh

# Command to run the executable
ENTRYPOINT ["./docker-entrypoint.sh"]

接下来我们来打包封装Docker镜像,因为树莓派的CPU是 ARM64的处理器,本文中编译使用的电脑为 x86-64,所以我们需要使用 Dockerbuildx 功能来进行镜像构建,有关buildx的教程本文就不再叙述,需要的话可以移步 https://docs.docker.com/buildx/working-with-buildx/

利用 docker buildx build --platform=linux/arm64 -f dev/Dockerfile.sample . -t edgehub/rpi-gpio-driver:v0.0.1 --push 来构建镜像并推送到 Docker Hub 中。

至此,镜像打包部分完成。

第四步 部署设备孪生到树莓派中

有了镜像以后,我们可以将数字孪生部署到集群中,下面我们来准备部署所需要的文件。

首先是一个 Kuberenetes Deployment YAML 文件,用来运行 deviceShifu 和驱动的 Pod

deviceshifu-rpi-gpio-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: edgedevice-rpi-gpio-deployment
name: edgedevice-rpi-gpio-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: edgedevice-rpi-gpio-deployment
template:
metadata:
labels:
app: edgedevice-rpi-gpio-deployment
spec:
containers:
- image: edgehub/deviceshifu-http-http:v0.0.1
name: deviceshifu-http
ports:
- containerPort: 8080
volumeMounts:
- name: edgedevice-config
mountPath: "/etc/edgedevice/config"
readOnly: true
env:
- name: EDGEDEVICE_NAME
value: "edgedevice-rpi-gpio"
- name: EDGEDEVICE_NAMESPACE
value: "devices"
- image: edgehub/rpi-gpio-driver:v0.0.1
name: driver
volumeMounts:
- mountPath: /dev/gpiomem
name: gpiomem
securityContext:
privileged: true
ports:
- containerPort: 11112
env:
- name: EDGEDEVICE_DRIVER_SSH_KEY_PATH
value: "/etc/ssh/ssh_host_rsa_key"
- name: EDGEDEVICE_DRIVER_HTTP_PORT
value: "11112"
- name: EDGEDEVICE_DRIVER_EXEC_TIMEOUT_SECOND
value: "5"
- name: EDGEDEVICE_DRIVER_SSH_USER
value: "root"
volumes:
- name: edgedevice-config
configMap:
name: rpi-gpio-configmap-0.0.1
- name: gpiomem
hostPath:
path: /dev/gpiomem
serviceAccountName: edgedevice-sa

请注意在Deployment文件中我们为了在容器中使用树莓派的GPIO,需要在容器的 securityContext 中加入 privileged: true 再通过volume的形式将树莓派的 /dev/gpiomem 挂载到容器中。

一个 Kubernetes Service YAML 文件,用来将 deviceShifu 的请求从域名代理到真正的 Pod

deviceshifu-rpi-gpio-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: edgedevice-rpi-gpio-deployment
name: edgedevice-rpi-gpio
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: edgedevice-rpi-gpio-deployment
type: LoadBalancer

一个 Kubernetes ConfigMap YAML 文件,用来配置 deviceShifu

deviceshifu-rpi-gpio-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: rpi-gpio-configmap-0.0.1
namespace: default
data:
driverProperties: |
driverSku: RaspberryPiB+
driverImage: edgenesis/rpi-gpio-python:v0.0.1
driverExecution: "python led_driver.py"
instructions: |
pin:
operate:
help:
# Telemetries are configurable health checks of the EdgeDevice
# Developer/user can configure certain instructions to be usedas health check
# of the device. In this example, the device_health telemetry ismapped to
# "get_status" instruction, executed every 1000 ms
telemetries: |
device_health:
properties:
instruction: help
initialDelayMs: 1000
intervalMs: 1000

ConfigMap 中我们需要配置驱动的执行路径,因为在生成镜像时我们将Python文件直接放到了默认路径下,在这里填写 python led_driver.py 即可。如果驱动是一个二进制文件的话这里直接填写二进制的目录即可。

一个 Shifu EdgeDevice YAML 文件,用来生成设备孪生:

edgedevice-rpi-gpio-edgedevice.yaml
apiVersion: shifu.edgenesis.io/v1alpha1
kind: EdgeDevice
metadata:
name: edgedevice-rpi-gpio
namespace: devices
spec:
sku: "RaspberryPi 3B+"
connection: Ethernet
address: 0.0.0.0:11112
protocol: HTTPCommandline

将这四个文件放到树莓派中,目录内容如下:

led-deploy/
├──deviceshifu-rpi-gpio-configmap.yaml
├──deviceshifu-rpi-gpio-deployment.yaml
├──deviceshifu-rpi-gpio-service.yaml
└──edgedevice-rpi-gpio-edgedevice.yaml

利用 kubectl apply -f <dir> 即可将 deviceShifu 部署到 k3s 集群中:

接着通过 kubectl get pods 来查看运行状态:

通过 kubectl get edgedevices -n devices 来查看集群中的所有设备孪生:

再通过 describe,即可查看数字孪生的详细信息:

接下来我们就可以和设备互动了,在这里我们部署一个 nginx 容器来代表实际场景中的应用 部署命令为 kubectl run nginx --image=nginx

接着执行 kubectl exec -it nginx -- bash 进入 nginx 的命令行:

最后,利用 curl 来给设备发送命令,驱动接受的命令格式为: python led_driver --pin <x> --operate <on/off>

利用 Shifu 来发送命令的话将由HTTP转换到命令行,请求地址写法为: http://edgedevice-rpi-gpio/pin?flags_no_parameter=<pin>,--operate,<on/off>

运行效果

程序通过直接给设备的域名发送HTTP请求,即可操控LED灯泡的亮/灭:

至此,将树莓派驱动接入 Shifu 完成