Skip to main content

Meetup Technical Review

· 8 min read

In the offline Shifu Meetup event held on 2022.9.29, Yang Xijie from Edgenesis demonstrated how Shifu is integrated into multiple physical IoT devices, proving through this intuitive way that Shifu framework enables fast access to devices, good isolation and easy App development without single-point failure.

Five devices were displayed in this activity, namely MQTT server, RS485 temperature and humidity meter and RS485 LED, Siemens S7 PLC, and Hikvision camera - all of which are relatively common IoT devices. Let's review the integration process below.

Create a Cluster and Install Shifu

First we need to start Docker locally. Just open Docker Desktop using Windows or macOS Search and minimize it to the background.

After that we need to create a k8s cluster with kind. Later on Shifu and the digital twins of IoT devices will exist in this cluster as Pod.

# Create a Cluster
$ sudo kind create cluster --image="kindest/node:v1.24.0"

# Prepare the image in advance for import into the cluster
$ 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 supports one-click installation, just clone Shifu repository first, and deploy it later with one single command:

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

# Run an application that will be used later
$ sudo kubectl run --image=nginx:1.21 nginx

You can also check out a more detailed tutorial on installing Shifu locally.

Device Integration

MQTT

Test MQTT Server

We have deployed an MQTT server and can test it by first opening two shells.

# shellA
$ mosquitto_sub -h 82.157.170.202 -t topic0

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

You can see that the message sent can be received correctly.

Integrate the Device

Next we can modify the corresponding configuration, download the corresponding image, and then use the kubectl apply command to integrate the MQTT server into Shifu as a digital twin.

Modify spec.address to 82.157.170.202:1883 and spec.protocolSettings.MQTTSetting.MQTTTopic to topic0 in examples/my_mqtt/mqtt_deploy.

$ 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

Read the Data

We can interact with the digital twin by starting an nginx application in the cluster.

$ sudo kubectl exec -it nginx -- bash

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

Connect Thermometer and LED

Connect Device to Computer

  • The thermometer is connected to the computer using a serial server via a network cable
  • LEDs are connected to the computer using an RS485 to USB chip

Start HTTP Service Locally

Since Shifu does not support Modbus protocol right now, we need to convert the data read by Modbus to HTTP data.

$ 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
contents in api_thermometer
main.py
from fastapi import FastAPI
from typing import List
from pymodbus.client.sync import ModbusTcpClient

app = FastAPI()

def getHumidityAndTemperature() -> List[float]:
"""
return temperature and humidity datas retrieved from TAS-LAN-460
"""
client = ModbusTcpClient(host='192.168.0.80', port=10123) # the port of TAS-LAN-460
client.connect()
SLAVE = 0x01
r = client.read_holding_registers(address=0x0000, count=2, unit=SLAVE)
print("collected data", 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
contents in 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 = "ZhongshengLed"
"""

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):
"""
display one decimal place
"""
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

Local Verification

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

Device Integration

  • Modify the IP address inhttp_thermometer/deployment/http_edgedevice.yaml.
  • Modify the IP address inhttp_led/deployment/http_edgedevice.yaml
$ 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

Interact with Devices

Start nginx to interact with the thermohygrometer:

$ 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

Application Development

Read the temperature and humidity and then display it alternately on the 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

The illustration of this application is as follows:

You can see the temperature and humidity are displayed alternely on the LED once the application is running.

files in current folder
main.py
import time
import requests
import json

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

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

# [converse data]
try:
value = json.loads(res.text)['value']
print("DEBUG", value)
# [display data]
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"]

Simons PLC

Device Integration

First change the IP address to that of the 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

Interact with the Device

Here we modify one bit on the PLC and you can see the indicator light on the PLC turns on. In actual scenarios, the PLC will control large equipments such as an robotic arm.

$ 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

Hikivision Camera

Device Integration

Get the IP address of the camera and then replace the IP address in rtsp/camera-deployment/deviceshifu-camera-deployment.yaml.

$ 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

Interact with Device

Check device information in cluster using nginx:

# Interact through `curl` in cluster
$ sudo kubectl exec -it nginx -- bash

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

The command that the digital twin of the camera supports is:

To view the images captured by the camera, we need to forward the port to the local machine and access it on the browser.

# Local browser access
$ sudo kubectl port-forward svc/deviceshifu-camera -n deviceshifu 8080:80
# Enter`localhost:8080/info`check information
# Enter`localhost:8080/capture`access pictures
# Enter`localhost:8080/move/{up|down|left|right}`move the camera
# Enter`localhost:8080/stream?timeout=0`access real-time streamline

Summary

The Shifu Meetup event was a huge success. As you can see Shifu enables developers to quickly access devices, unify various protocols to HTTP for easy management and subsequent application development. Shifu also has many advantages including running with no single point failure and good isolation.

If you are interested in Shifu, please visit Shifu official website to learn more. You are also welcome to give the project a star at Shifu's GitHub repository!