kubeedge1.15版本Mapper实现初代

kubuedge1.15版本Mapper实现步骤(详解):

前提:有真实的或者模拟的终端设备,我采用的是PLC作为智能终端设备,PLC利用modbus TCP协议与边缘节点node树莓派进行连接,中心节点采ubuntu系统主机

1、通过Mapper Framework框架生成Mapper文件:

1)从官网下载kubeedge-master.zip ,取其中的staging文件夹下的mapper-framewokr,传输到虚拟机master节点。(备注:Mapper是跑在边缘节点上,但先在云端进行生成和部署,再在边缘端拉取镜像到本身节点上;mapper以pod形式部署仍然是在云端执行kubectl创建的,与边缘节点上的其他pod管理方式没有区别)
在这里插入图片描述

2)进入解压好的kubeedge-framework工程目录下,执行指令:

make generate

但此时会弹出error,没有权限执行,所以给提示出的sh执行文件赋予权限 chmod 777:

chmod 777 拷贝弹出来的文件名称

在这里插入图片描述

3)接下来会弹出你需要输入的Mapper的工程名字,我输入的是modbus,我要用!这个根据自己的需要进行输入(The command below will generate a framework for the customized mapper. Run the command and input your mapper’s name:):

在这里插入图片描述

4)新生成的文件会出现在上一层目录文件中,找到他进入modbus目录(这是我刚刚输入的,我自己工程目录),接下来对官网给的生成的Mapper框架进行梳理,官网链接(kubeedge/staging/src/github.com/kubeedge/mapper-framework at master · kubeedge/kubeedge):

在这里插入图片描述

这个工程目录说明了只需要关心三个文件,分别是config.yaml、driver.go、devicetype.go。目前data工程目录中有三个数据库方法:

在这里插入图片描述

在这里插入图片描述

  1. Mapper通过配置目标地址将数据推送到云/边缘应用程序。
  2. Mapper通过配置目标地址将数据推送到云/边缘数据库。
  3. 用户应用程序通过 API 主动拉取设备数据。
  4. Mapper通过云边通道将设备数据推送到云端。

2、确定自己设备的Device model 和Device instance文件:

对本文采用的是PLC作为终端设备,采用modbus TCP协议进行连接边缘节点(树莓派),参考官方链接modbus RTU协议案例:Add v1beta1 Modbus mapper by wbc6080 · Pull Request #113 · kubeedge/mappers-go (github.com)

PLC的device model:

apiVersion: devices.kubeedge.io/v1beta1  #版本
kind: DeviceModel
metadata:
 name: modbustcp-device
 namespace: default
spec:
 protocol: modbus    
 properties:
  - name: temperature  #温度
    description: temperature in degree celsius
    type: INT
    accessMode: ReadWrite
    maximum: "100"  #要加""表示数值范围的属性,后面数字都需要加双引号
    minimum: "1"
    unit: "degree celsius"
  - name: humidity   #湿度
    description: humidity in Relative Humidity
    type: INT  #源码都是大写
    accessMode: ReadWrite  #源码字母开头大写
    maximum: "100"
    minimum: "1"
    unit: "Relative Humidity" #要加""
  - name: temperature-enable   
    description: enable data collection of sensor  #控制传感器数据的产生
    type: BOOLEAN
    accessMode: ReadWrite
  - name: light-enable   
    description: enable of light  #控制灯的亮灭
    type: BOOLEAN
    accessMode: ReadWrite


PLC的device instance文件:

apiVersion: devices.kubeedge.io/v1beta1
kind: Device
metadata:
  name: modbusplc
  labels:
    model: modbustcp-device
spec:
  deviceModelRef:
    name: modbustcp-device
  protocol:
    protocolName: modbus  
    configData:
    ip: 10.10.18.2   #modbus从站地址(PLC)
    port: 502        #端口号
  nodeName: nodepi
  properties:
   - name: temperature
     visitors: 
       protocolName: modbus
       configData:  
         register: "HoldingRegister"  #PLC采取modbus tcp通信,保持寄存器
         offset: 30001        #寄存器的起始地址
         limit: 1    #读取的寄存器数量
         scale: 0.1   #对采集到的温度进行缩放0.1倍
         isSwap: false   #读取的结果是否交换高低字节顺序
         isRegisterSwap: false  #寄存器字顺序是否交换
     collectCycle: 1000000  #收集传感器数据的时间为1s
     reportCycle: 1000000   #消息上报频率时间为1s
     reportToCloud: true     #是否汇报给云端
     desired:          #温度信息并不需要desired参数
         metadata:
         value: "40"
     pushMethod:
        mqtt:
          address: tcp://127.0.0.1:1883
          topic: tempearture
          qos: 0
          retained: false
        dbMethod:
          influxdb2:
            influxdb2ClientConfig:
              url: http://127.0.0.1:8086
              org: test-org
              bucket: test-bucket
            influxdb2DataConfig:
              measurement: stat
              tag:
                unit: temperature
              fieldKey: beta1test
   - name: humidity
     visitors: 
       protocolName: modbus
       configData:  
         register: "HoldingRegister"  #PLC采取modbus tcp通信,保持寄存器
         offset: 30002        #寄存器的起始地址
         limit: 1    #读取的寄存器数量
         scale: 0.1   #对采集到的湿度进行缩放0.1倍
         isSwap: false   #读取的结果是否交换高低字节顺序
         isRegisterSwap: false  #寄存器字顺序是否交换
     collectCycle: 1000000
     reportCycle: 1000000
     reportToCloud: true
     desired:          #温度信息并不需要desired参数
         metadata:
         value: "20"
     pushMethod:
        mqtt:
          address: tcp://127.0.0.1:1883
          topic: humidity
          qos: 0
          retained: false
        dbMethod:
          influxdb2:
            influxdb2ClientConfig:
              url: http://127.0.0.1:8086
              org: test-org
              bucket: test-bucket
            influxdb2DataConfig:
              measurement: stat
              tag:
                unit: humidity
              fieldKey: beta1test
   - name: light-enable
     collectCycle: 1000000
     reportCycle: 1000000
     reportToCloud: true   
     desired:          #不开灯
         metadata:
         value: 'OFF'
     visitors: 
       protocolName: modbus
       configData:  
         register: "CoilRegister"  #PLC采取modbus tcp通信,保持寄存器
         offset: 00001        #寄存器的起始地址
         limit: 1    #读取的寄存器数量
         scale: 1   #对采集到的湿度进行缩放0.1倍
         isSwap: false   #读取的结果是否交换高低字节顺序
         isRegisterSwap: false  #寄存器字顺序是否交换
status:
  twins:
    - propertyName: temperature
      reported:
        metadata:
          timestamp: '1550049403598'
          type: int
        value: "40"
      observedDesired:
        metadata:
          timestamp: '1550049403598'
          type: int
        value: "40"
    - propertyName: humidity
      reported:
        metadata:
          timestamp: '1550049403598'
          type: int
        value: "20"
      observedDesired:
        metadata:
          timestamp: '1550049403598'
          type: int
        value: "20"
    - propertyName: light-enable
      reported:
        metadata:
          timestamp: '1550049403598'
          type: bool
        value: 'OFF'
      observedDesired:
        metadata:
          timestamp: '1550049403598'
          type: bool
        value: 'OFF'

3、配置生成的Mapper文件:modbus

按照config.yaml、driver.go、devicetype.go这个顺序一个一个的配置,官网链接可以参考(mapper-generator/_template at main · wbc6080/mapper-generator (github.com)):

1)在config.yaml中,需要定义mapper的协议名称。修改config.yaml文件,根据自己的需求,修改protocol和version(Kubeedge的版本):

在这里插入图片描述

2)在devicetype.go文件中,需要按照设备实例instance.yaml文件中定义的方式填写ProtocolConfig和VisitorConfig结构体信息,以便mapper能够正确解析配置信息,本文采用的是modbus TCP协议,以下是自己配置的devicetype.go文件代码:

package driver

import (
	"sync"
	"time"

	"github.com/kubeedge/modbus/pkg/common"
	"github.com/sailorvii/modbus"
)

// CustomizedDev is the customized device configuration and client information.
type CustomizedDev struct {
	Instance         common.DeviceInstance
	CustomizedClient *CustomizedClient
}

type CustomizedClient struct {
	// TODO add some variables to help you better implement device drivers
	//intMaxValue int
	deviceMutex sync.Mutex
	ProtocolConfig
	ModbusTCPProtocolConfig
	ModbusClient modbus.Client
}

type ProtocolConfig struct {
	ProtocolName string `json:"protocolName"`
	ConfigData   `json:"configData"`
}

type ConfigData struct {
	// TODO: add your protocol config data
	//DeviceID   int    `json:"deviceID,omitempty"`
	//SerialPort string `json:"serialPort"`
	//DataBits   int    `json:"dataBits"`
	//BaudRate   int    `json:"baudRate"`
	//Parity     string `json:"parity"`
	//StopBits   int    `json:"stopBits"`
	//ProtocolID int    `json:"protocolID"`
	IP      string
	Port    int
	SlaveID byte
	Timeout int
}
type ModbusTCPProtocolConfig struct {
	IP      string
	Port    int
	SlaveID byte
	Timeout time.Duration
}

type VisitorConfig struct {
	ProtocolName      string `json:"protocolName"`
	VisitorConfigData `json:"configData"`
}

type VisitorConfigData struct {
	// TODO: add your visitor config data
	DataType       string  `json:"dataType"`
	Register       string  `json:"register"`
	Offset         uint16  `json:"offset"`
	Limit          int     `json:"limit"`
	Scale          float64 `json:"scale,omitempty"`
	IsSwap         bool    `json:"isSwap,omitempty"`
	IsRegisterSwap bool    `json:"isRegisterSwap,omitempty"`
}

3)在driver.go文件中,需要自定义初始化设备和获取设备数据的方法,并对mapper收集的数据进行标准化。

以下是采用modbus TCP协议的driver.go文件:

package driver

import (
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"

	"github.com/sailorvii/modbus"
	"k8s.io/klog/v2"
)

var clients *sync.Map

var clientInit sync.Once

func initMap() {
	clientInit.Do(func() {
		if clients == nil {
			clients = new(sync.Map)
		}
	})
}

func NewClient(protocol ProtocolConfig) (*CustomizedClient, error) {

	modbusTCPProtocolConfig := ModbusTCPProtocolConfig{
		IP:      protocol.IP,
		Port:    protocol.Port,
		SlaveID: protocol.SlaveID,
		Timeout: time.Duration(protocol.Timeout),
	}
	client := &CustomizedClient{
		ProtocolConfig:          protocol,
		deviceMutex:             sync.Mutex{},
		ModbusTCPProtocolConfig: modbusTCPProtocolConfig,
		// TODO initialize the variables you added
	}
	return client, nil
}

func (c *CustomizedClient) InitDevice() error {
	// TODO: add init operation
	// you can use c.ProtocolConfig
	initMap()
	klog.Infof("IP: %s, Port: %d", c.ModbusTCPProtocolConfig.IP, c.ModbusTCPProtocolConfig.Port)
	key := fmt.Sprintf("%s:%d", c.ModbusTCPProtocolConfig.IP, c.ModbusTCPProtocolConfig.Port)
	v, ok := clients.Load(key)
	if ok {
		c.ModbusClient = v.(modbus.Client)
		return nil
	}

	handler := modbus.NewTCPClientHandler(c.ModbusTCPProtocolConfig.IP + ":" + strconv.Itoa(c.ModbusTCPProtocolConfig.Port))
	handler.SlaveId = c.ModbusTCPProtocolConfig.SlaveID
	handler.Timeout = c.ModbusTCPProtocolConfig.Timeout
	client := modbus.NewClient(handler)
	clients.Store(key, client)
	c.ModbusClient = client

	return nil
}

func (c *CustomizedClient) GetDeviceData(visitor *VisitorConfig) (interface{}, error) {
	// TODO: add the code to get device's data
	// you can use c.ProtocolConfig and visitor
	c.deviceMutex.Lock()
	defer c.deviceMutex.Unlock()

	var results []byte
	var err error
	switch visitor.Register {
	case "CoilRegister":
		results, err = c.ModbusClient.ReadCoils(visitor.Offset, uint16(visitor.Limit))
	case "DiscreteInputRegister":
		results, err = c.ModbusClient.ReadDiscreteInputs(visitor.Offset, uint16(visitor.Limit))
	case "HoldingRegister":
		results, err = c.ModbusClient.ReadHoldingRegisters(visitor.Offset, uint16(visitor.Limit))
	case "InputRegister":
		results, err = c.ModbusClient.ReadInputRegisters(visitor.Offset, uint16(visitor.Limit))
	default:
		return nil, errors.New("Bad register type")
	}
	klog.V(2).Info("Get result: ", results)

	return results, err
}

func (c *CustomizedClient) SetDeviceData(data interface{}, visitor *VisitorConfig) (interface{}, error) {
	// TODO: set device's data
	// you can use c.ProtocolConfig and visitor
	var results []byte
	var err error
	c.deviceMutex.Lock()
	defer c.deviceMutex.Unlock()
	klog.V(1).Info("Set: %s %d %d", visitor.Register, visitor.Offset, uint16(visitor.Limit))
	switch visitor.Register {
	case "CoilRegister":
		var valueSet uint16
		switch uint16(visitor.Limit) {
		case 0:
			valueSet = 0x0000 //0x0000 表示设置线圈为OFF状态,关闭
		case 1:
			valueSet = 0xFF00 //0xFF00 表示设置线圈为ON状态,打开
		default:
			return nil, errors.New("Wrong value")
		}
		results, err = c.ModbusClient.WriteSingleCoil(visitor.Offset, valueSet)
	case "HoldingRegister":
		results, err = c.ModbusClient.WriteSingleRegister(visitor.Offset, uint16(visitor.Limit))
	default:
		return nil, errors.New("Bad register type")
	}
	klog.V(1).Info("Set result: %v %v", err, results)
	return results, err
}

func (c *CustomizedClient) StopDevice() error {
	// TODO: stop device
	// you can use c.ProtocolConfig
	//klog.Infof("Stop device%d successful", c.DeviceID)
	err := c.ModbusClient.Close()
	if err != nil {
		return err
	}
	return nil
}
func parity(ori string) string {
	var p string
	switch ori {
	case "even":
		p = "E"
	case "odd":
		p = "O"
	default:
		p = "N"
	}
	return p
}


4、生成配置Device model 和Device instance文件:

注意事项:在高版本golang中会报错,需要修改device.go 263行

err = dev.CustomizedClient.SetDeviceData(value, visitorConfig)---->>

_err = dev.CustomizedClient.SetDeviceData(value, visitorConfig)

2) 从官网找到devices_v1beta1_device.yaml和devices_v1beta1_devicemodel.yaml文件,在master节点apply他们,然后部署自己的model与instance。对修改完成的两个文件在master节点进行apply:

 kubectl apply -f <path to device model yaml>
 kubectl apply -f <path to device instance yaml>

5)部署Mapper

1)生成mapper项目并填充driver文件夹后,用户可以根据Dockerfile文件制作自己的mapper镜像,随后通过deployment等方式将mapper部署在集群中

docker build -t [YOUR MAPPER IMAGE NAME] .

备注:在高版本golang中会报错,需要修改device.go 263行err = dev.CustomizedClient.SetDeviceData(value, visitorConfig)------------------_err = dev.CustomizedClient.SetDeviceData(value, visitorConfig)

2)对修改完成的两个文件在master节点进行apply,部署自己的model和instance:

 kubectl apply -f <path to device model yaml>
 kubectl apply -f <path to device instance yaml>

5)部署Mapper

1)生成mapper项目并填充driver文件夹后,用户可以根据Dockerfile文件制作自己的mapper镜像,随后通过deployment等方式将mapper部署在集群中

docker build -t [YOUR MAPPER IMAGE NAME] .

注:如果边缘节点跑的是ARM架构的设备,比如树莓派这些,自己记得修改Dockfiles文件,不然默认AMD架构
在这里插入图片描述

备注:Mapper设计者:mapper设计的机制就是无论遵从什么协议都只需要修改这三个地方。一般创建mapper的流程是先制作一个device-instance.yaml,里面需要填写用户自己protocol相关的配置字段,之后需要在devicetype文件中对应yaml文件把用户自己填写的字段copy一遍,这样mapper就能正确解析到yaml中字段的信息,然后这些信息会被发送到driver文件中,通过协议字段来完成设备连接、设备数据获取工作

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空有大海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值