边缘计算开源框架EdgeXFoundry的部署应用开发(三)设备服务开发

2023-10-30


整个EdgeXFoundry框架整体功能来说是比较全面的,但是也只是框架而已,具体的边缘计算实现的功能:各类传感器数据的上报、各类算法的部署、各类数据的流转、各种场景的控制等等皆须要设备服务的实现

为了实现它,你需要考虑以下:

  • 设备驱动的扩展性
  • 数据的完整及安全性
  • 部署的简易性
  • 设备服务性能(容量、速度、兼容)

使用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

编写温湿度设备接入设备服务

功能框架

参考阿里云方式,控制台以每个边缘网关为首向下拓展

  • 边缘网关,运行设备服务,并预先编写好设备配置文件与服务配置文件部署到网关并建立服务
  • 控制台中设备服务列表代表着不同功能的边缘网关,实际就是多协议支持的网关服务
  • 遵照驱动中支持的通讯协议设备,由控制台端编写不同的设备配置文件(modbus温度传感器、光照等各类型传感器或者其他自定义协议设备)
  • 动态部署使用,在控制台的上级网关的设备服务下建立设备,为其配置特定设备配置文件及其他参数,特别指定设备名(协议-位置信息(应包含上级网关信息+地理位置)-设备名(可重复)-地址号或者序号(同名设备不可重复),这里设备配置即物模型可以下发给网关也可以不下发由设备服务自动获取控制台端的设备配置文件
  • 控制台创建号设备信息后,通过已正常通讯的上级网关设备服务下发更新后的设备驱动库文件到远程网关,给设备服务调用,完成动态加载设备驱动。
  • 完成驱动的更新下发后,下发新增设备后的服务配置文件即configuration.toml文件
    这样后台浏览设备易于查看:物理通讯TOPO关系、通讯地址及协议、地理区域信息。

设备名的解析

#include <stdint.h> /**< need definition of uint8_t */                          
#include <stddef.h> /**< need definition of NULL    */                          
#include <stdbool.h>/**< need definition of BOOL    */                        
#include <stdio.h>  /**< if need printf             */                          
#include <stdlib.h>                                                             
#include <string.h>  

typedef struct 
{
    char protocol_str[64];      /**< 协议名*/
    char location_str[128];     /**< 设备位置信息*/
    char dev_type_name[64];     /**< 设备类型名称*/
    char dev_address[16];       /**< 设备地址号*/
}DEV_INFO_Typedef_t;

int main(int argc ,char *argv[])
{
    DEV_INFO_Typedef_t dev_info;
    int ret = sscanf(argv[1], "'%[^-]-%[^-]-%[^-]-%[^']'", dev_info.protocol_str, 
                    dev_info.location_str, dev_info.dev_type_name, dev_info.dev_address);
    if(ret != 4)
    {
        printf("parse dev_name error.\r\n");
        return -1;
    }
    else
    {
        printf("proto:%s\t\t location_str:%s\t\tdev_type_name:%s\t\tdev_address:%s\n",dev_info.protocol_str, 
                    dev_info.location_str, dev_info.dev_type_name, dev_info.dev_address);
        return 0;
    }
}

测试

#编译运行测试
gcc test.c
sudo chmod +x a.out
./a.out "'modbus_rtu-01_hangzhou-temperature_device-1'"

记住,完善接口即可
以下代码中,我已将设备服务设备驱动分离,即设备驱动为动态库方式存在
设备服务的需求:获取设备数据、控制设备、更新设备
设备驱动的功能:提供数据获取接口、提供控制设备接口、提供更新设备接口

/**                                                                            
 *  @file main_device_service.c                                                   
 *                                                                              
 *  @date 2020年11月08日 11:14:06 星期天
 *
 *  @author aron566
 *
 *  @copyright None
 *
 *  @brief EdgeXFoudry 设备服务驱动.
 *
 *  @details Pseudo-device service illustrating resource aggregation using C SDK .
 *
 *  @version V1.0
 */
#ifdef __cplusplus ///<use C compiler
extern "C" {
#endif
/** Includes -----------------------------------------------------------------*/
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <sys/stat.h>
/* Private includes ----------------------------------------------------------*/
#include "devsdk/devsdk.h"
#include "main_device_service.h"
/** Private typedef ----------------------------------------------------------*/

typedef struct custom_device_driver
{
  iot_logger_t * lc;
} custom_device_driver;
/** Private macros -----------------------------------------------------------*/
#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;}
#define ERR_BUFSZ 1024
#define ERR_CUSTOM_DEVICE_WRITE "PUT called for custom_device device. This is a read-only device."
#define ERR_CUSTOM_DEVICE_NO_PARAM "No parameter attribute in GET request."

/** Private constants --------------------------------------------------------*/
/** Public variables ---------------------------------------------------------*/
/** Private variables --------------------------------------------------------*/

/** Private function prototypes ----------------------------------------------*/
/*初始化设备服务*/
static bool custom_device_init(void * impl, struct iot_logger_t * lc, const iot_data_t * config);
/*响应GET请求*/
static bool custom_device_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);
/*响应PUT请求*/
static bool custom_device_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);
/*响应重置请求*/
static void custom_device_reconfigure(void *impl, const iot_data_t *config);
/*响应发现请求*/
static void custom_device_discover(void *impl);
/*响应停止请求*/
static void custom_device_stop(void * impl, bool force);
/** Private user code --------------------------------------------------------*/                                                                         
/** Private application code -------------------------------------------------*/
/*******************************************************************************
*                                                                               
*       Static code                                                             
*                                                                               
********************************************************************************
*/
/**
 * @brief Function called during service start operation.
 * @param impl The context data passed in when the service was created.
 * @param lc A logging client for the device service.
 * @param config A string map containing the configuration specified in the service's "Driver" section.
 * @return true if the operation was successful, false otherwise.
 */
static bool custom_device_init(void * impl, struct iot_logger_t * lc, const iot_data_t * config)
{
  custom_device_driver * driver = (custom_device_driver *)impl;
  driver->lc = lc;

  /*初始化设备驱动*/
  device_driver_opt_init(lc, config);

  return true;
}

/**
 * @brief Callback issued to handle GET requests for device readings.
 * @param impl The context data passed in when the service was created.
 * @param devname The name of the device to be queried.
 * @param protocols The location of the device to be queried.
 * @param nreadings The number of readings requested.
 * @param requests An array specifying the readings that have been requested.
 * @param readings An array in which to return the requested readings.
 * @param qparams Query Parameters which were set for this request.
 * @param exception Set this to an IOT_DATA_STRING to give more information if the operation fails.
 * @return true if the operation was successful, false otherwise.
 */
static bool custom_device_get_handler(
  void * impl,                            /**< 自定义数据*/
  const char * devname,                   /**< 设备名称*/
  const devsdk_protocols * protocols,     /**< 请求设备所归属的协议*/
  uint32_t nreadings,                     /**< 请求类型的数量*/
  const devsdk_commandrequest * requests, /**< 请求参数列表*/
  devsdk_commandresult * readings,        /**< 返回结果给Edge控制台*/
  const devsdk_nvpairs * qparams,         /**< 请求的附加参数*/
  iot_data_t ** exception)                /**< 返回请求结果说明信息*/
{
  int ret = 0;
  const char *param;

  custom_device_driver * driver = (custom_device_driver *) impl;

  for(uint32_t i = 0; i < nreadings; i++)
  {
    /*获取GET参数*/
    /*参数来自.yaml中deviceResources列表中attributes项parameter键值*/
    param = devsdk_nvpairs_value (requests[i].attributes, "parameter");
    if(param == NULL)
    {
      iot_log_error (driver->lc, ERR_CUSTOM_DEVICE_NO_PARAM);
      * exception = iot_data_alloc_string (ERR_CUSTOM_DEVICE_NO_PARAM, IOT_DATA_REF);
      return false;
    }

    ret = device_driver_opt_get(devname, param, &readings[i], driver->lc);
    if(ret != 0)
    {
      iot_log_error(driver->lc, "get dev: %s par: error.", devname, param);
    }
  }
  return true;
}

/**
 * @brief Callback issued to handle PUT requests for setting device values.
 * @param impl The context data passed in when the service was created.
 * @param devname The name of the device to be queried.
 * @param protocols The location of the device to be queried.
 * @param nvalues The number of set operations requested.
 * @param requests An array specifying the resources to which to write.
 * @param values An array specifying the values to be written.
 * @param exception Set this to an IOT_DATA_STRING to give more information if the operation fails.
 * @return true if the operation was successful, false otherwise.
 */
static bool custom_device_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)
{
  int ret = 0;
  const char *param;

  custom_device_driver * driver = (custom_device_driver *) impl;
 
  for(uint32_t i = 0; i < nvalues; i++)
  {
    param = devsdk_nvpairs_value (requests[i].attributes, "parameter");
    if(param == NULL)
    {
      iot_log_error (driver->lc, ERR_CUSTOM_DEVICE_NO_PARAM);
      * exception = iot_data_alloc_string (ERR_CUSTOM_DEVICE_NO_PARAM, IOT_DATA_REF);
      continue;
    }

    /*设置请求*/
    ret = device_driver_opt_set(devname, param, values[i], driver->lc);
    if(ret != 0)
    {
      iot_log_error(driver->lc, "set dev: %s par: error.", devname, param);
    }
    
  }
  return true;
}

/**
 * @brief Function called when configuration is updated.
 * @param impl The context data passed in when the service was created.
 * @param config A string map containing the new configuration.
 */
static void custom_device_reconfigure(void *impl, const iot_data_t *config)
{
  custom_device_driver * driver = (custom_device_driver *) impl;

  device_driver_opt_reconfigure(driver->lc, config);
}

/**
 * @brief Optional callback for dynamic discovery of devices. The implementation should detect devices and register them using
 *        the devsdk_add_device API call.
 * @param impl The context data passed in when the service was created.
 */
static void custom_device_discover(void *impl) 
{
  custom_device_driver * driver = (custom_device_driver *) impl;

  device_driver_opt_discover(driver->lc);
}

/**
 * @brief Callback issued during device service shutdown. The implementation should stop processing and release any resources that were being used.
 * @param impl The context data passed in when the service was created.
 * @param force A 'force' stop has been requested. An unclean shutdown may be performed if necessary.
 */
static void custom_device_stop(void *impl, bool force)
{
  /* Stop performs any final actions before the device service is terminated */
  custom_device_driver * driver = (custom_device_driver *) impl;

  device_driver_opt_stop(driver->lc, force);
}

/** Public application code --------------------------------------------------*/
/*******************************************************************************
*                                                                               
*       Public code                                                             
*                                                                               
********************************************************************************
*/
/**
 * @brief 设备服务驱动入口
 */
int main (int argc, char * argv[])
{
  sigset_t set;
  int sigret;
  
  custom_device_driver * impl = malloc (sizeof (custom_device_driver));
  impl->lc = NULL;

  devsdk_error e;
  e.code = 0;

  devsdk_callbacks custom_deviceImpls =
  {
    custom_device_init,         /* Initialize */
    custom_device_reconfigure,  /* Reconfigure */
    custom_device_discover,     /* Discovery */
    custom_device_get_handler,  /* Get */
    custom_device_put_handler,  /* Put */
    custom_device_stop          /* Stop */
  };

  devsdk_service_t * service = devsdk_service_new("device-custom_device", "1.0", impl, custom_deviceImpls, &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");
      return 0;
    }
    else
    {
      printf ("%s: Unrecognized option %s\n", argv[0], argv[n]);
      return 0;
    }
  }

  devsdk_service_start (service, NULL, &e);
  ERR_CHECK (e);

  sigemptyset (&set);
  sigaddset (&set, SIGINT);
  sigprocmask (SIG_BLOCK, &set, NULL);
  sigwait (&set, &sigret);
  sigprocmask (SIG_UNBLOCK, &set, NULL);

  devsdk_service_stop (service, true, &e);
  ERR_CHECK (e);

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

#ifdef __cplusplus ///<end extern c                                             
}                                                                               
#endif                                                                          
/******************************** End of file *********************************/                                                                                                                                                 

配置文件xx.yaml和configuration.toml

xx.yaml的修改需遵照设备服务提供的设备功能
官方对配置的介绍
其他配置的介绍

设备服务配置文件
配置文件中,主要关注AutoEvents项,配置它可以实现主动上报功能,这个可以自行运行demo测试

[Service]
  Port = 50001
  Timeout = 5000
  ConnectRetries = 10
  Labels = [ 'MQTT_Protocol' ,'MODBUS_Protocol' ]
  StartupMsg = 'mqtt modbus device service started'
  CheckInterval = '10s'

[Clients]
  [Clients.Data]
    Host = 'localhost'
    Port = 48080

  [Clients.Metadata]
    Host = 'localhost'
    Port = 48081

[Device]
  DataTransform = false
  Discovery = false
  MaxCmdOps = 128
  MaxCmdResultLen = 256

[Logging]
  LogLevel = 'DEBUG'

[[DeviceList]]
  Name = 'mqtt-gateway_01_hangzhou-gateway_device-1'
  Profile = 'mqtt_gateway_device_profile'
  Description = 'An gatway device'
  [DeviceList.Protocols]
    [DeviceList.Protocols.mqtt]
      Schema = 'tcp'
      Host = 'localhost'
      Port = 1883
      User = ''
      Password = ''
      ClientId = 1
      Topic = ''
      SubTopic = 'subcribe_test'
      PubTopic = 'publish_test'
  [[DeviceList.AutoEvents]]
    Resource = 'temperature'
    OnChange = false
    Frequency = '10s'
  [[DeviceList.AutoEvents]]
    Resource = 'run_state'
    OnChange = true
    Frequency = '15000ms'

[[DeviceList]]
  Name = 'modbus_rtu-gateway_01_hangzhou-temperature_device-1'
  Profile = 'modbus_temperature_device_profile'
  Description = 'An temperature device'
  [DeviceList.Protocols]
    [DeviceList.Protocols.modbus-rtu]
      Address = '/tmp/slave'
      BaudRate = 9600
      DataBits = 8
      StopBits = 1
      Parity = 'N'
      UnitID = 1
  [[DeviceList.AutoEvents]]
    Resource = 'temperature'
    OnChange = false
    Frequency = '10s'
  [[DeviceList.AutoEvents]]
    Resource = 'humidity'
    OnChange = true
    Frequency = '15000ms'

[[DeviceList]]
  Name = 'modbus_rtu-gateway_01_hangzhou-temperature_device-2'
  Profile = 'modbus_temperature_device_profile'
  Description = 'An temperature device'
  [DeviceList.Protocols]
    [DeviceList.Protocols.modbus-rtu]
      Address = '/tmp/slave'
      BaudRate = 9600
      DataBits = 8
      StopBits = 1
      Parity = 'N'
      UnitID = 2
  [[DeviceList.AutoEvents]]
    Resource = 'temperature'
    OnChange = false
    Frequency = '10s'
  [[DeviceList.AutoEvents]]
    Resource = 'humidity'
    OnChange = true
    Frequency = '15000ms'

获取数据接口中的参数使用

如下是一个温湿度的设备配置文件,当一个get请求过来
在这里插入图片描述

/*获取 parameter: "temperature" 中temperature字符串*/
const char *param = devsdk_nvpairs_value (requests[i].attributes, "parameter");

/*获取 name: temperature 中temperature字符串*/
const char *name = requests[i].resname;

/*获取数据类型*/
const iot_typecode_t type = *(requests[i].type);

上传设备配置文件即物模型,如遇以下情况,请检查:

  • 网络
  • 文件内容是否标准,因为是yaml格式特别注意空格对齐问题
    在这里插入图片描述

编译

./build make

运行

运行可以使用脚本,当然需要在脚本中修改定义好变量

./build run

输出内容如下

aron566@MinT-machine:~/Workspace/custom_device_driver/build/devices_service$ ./custom-device -c res
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="iot_threadpool_alloc (threads: 8 max_jobs: 0 default_priority: -1 affinity: -1)"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-0 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-1 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-2 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-3 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-5 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-6 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-4 starting"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-0-7 starting"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="iot_scheduler_alloc (priority: -1 affinity: -1)"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="iot_threadpool_start()"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=TRACE ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread waiting for new job"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Setting LogLevel to DEBUG"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Starting device-custom_device device service, version 1.0"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="EdgeX device SDK for C, version 1.3.0"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service configuration follows:"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/Host="MinT-machine""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/Port=50000"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/Timeout=5000"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/ConnectRetries=10"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/StartupMsg="mqtt modbus device service started""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/CheckInterval="10s""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/Labels="""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Service/ServerBindAddr="""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/DataTransform=false"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/Discovery/Enabled=true"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/Discovery/Interval=0"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/MaxCmdOps=128"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/MaxCmdResultLen=256"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/ProfilesDir="res""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/UpdateLastConnected=false"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device/EventQLength=0"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Logging/LogLevel="DEBUG""
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Logging/EnableRemote=false"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Logging/File="""
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="iot_threadpool_alloc (threads: 1 max_jobs: 0 default_priority: -1 affinity: -1)"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Thread iot-1-0 starting"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Found core-data service at localhost:48080"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Found core-metadata service at localhost:48081"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Processing Device Profiles from res"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Checking existence of DeviceProfile mqtt_gateway_device_profile"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="DeviceProfile mqtt_gateway_device_profile already exists: skipped"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Checking existence of DeviceProfile modbus_temperature_device_profile"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="DeviceProfile modbus_temperature_device_profile already exists: skipped"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Starting HTTP server on interface MinT-machine, port 50000"
level=DEBUG ts=2020-11-28T16:49:17Z app=device-custom_device msg="Resolved interface is 2408:823c:815:3a8:5351:4fa2:768d:cd1c"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Processing DeviceList from configuration"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device mqtt-gateway_01_hangzhou-gateway_device-1 already exists: skipped"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device modbus_rtu-gateway_01_hangzhou-temperature_device-1 already exists: skipped"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="Device modbus_rtu-gateway_01_hangzhou-temperature_device-2 already exists: skipped"
level=INFO ts=2020-11-28T16:49:17Z app=device-custom_device msg="start device driver now."
Name = mqtt-gateway_01_hangzhou-gateway_device-1
gatway device register.
Name = modbus_rtu-gateway_01_hangzhou-temperature_device-1
temperature device register.
Name = modbus_rtu-gateway_01_hangzhou-temperature_device-2
temperature device register.

控制台状态
设备服务,与下级温湿度设备存在
在这里插入图片描述
以上为建立一个新设备的过程

假定一个实际需求

  • 协议:modbus-rtu,很常见
  • 设备:温湿度设备,很常见
  • 设备自身可提供的数值信息:温度、湿度
  • 设备地址:modbus地址1
  • 读取数据发送:0x01,0x03,0x00,0x00,0x00,0x02,0xc4,0x0b
  • 需要提供的功能:1、读取设备温湿度数据2、可设定温湿度数值的上下限3、可提供报警功能(掉线报警、高低温、高低湿度报警)

定义它设备配置文件

设备资源

  • 温度参数
  • 湿度参数
  • 温度报警值设定(分高低)
  • 湿度报警值设定(分高低)
  • 设备在线状态
  • 设备事件(高低温事件和高低湿度事件)

设备配置文件定义的一些关系

coreCommands 里面的name就是UI平台上的标签,并无多大意义
定义coreCommands 的path: “/api/v1/device/{deviceId}/modbus_temperature_device” modbus_temperature_device必须是个资源名称
可以是deviceResources里面的name的值
也可以是deviceCommands里面name的值
deviceCommands里面get/set 只能是deviceResources里面的name

浮点数精度问题

如果 rawType 属性存在,设备服务将根据定义的 rawType 解析二进制数据,然后根据设备资源属性中定义的值类型转换值。

  • 比如设备读取的湿度寄存器值为52,实际为52%
  • 依据下面配置则,52 以二进制数读取,存储类型为INT16,之后转为浮点数乘上0.01给设备服务
    在这里插入图片描述
    在这里插入图片描述
    name: humidity
    description: "humidity realtime value"
    attributes:
      { parameter: "humidity", rawType: "INT16" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00", scale: "0.01"}
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}

我的温湿度设备配置文件如下,其实还是可以写寄存器地址给设备驱动读取

name: "modbus_temperature_device_profile"
manufacturer: "IoTechSystems"
model: "IoT6"
description: "Temperature & Humidity Device Profile"
labels:
  - "temperature_sensor"
  - "modbus_protocol"

deviceResources:
  -
    name: temperature
    description: "temperature realtime value"
    attributes:
      { parameter: "temperature" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "degrees Celsius"}
  -
    name: humidity
    description: "humidity realtime value"
    attributes:
      { parameter: "humidity" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}
  -
    name: humiditymin
    description: "humidity min value"
    attributes:
      { parameter: "humiditymin" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "RW", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}
  -
    name: temperaturemin
    description: "temperature min value"
    attributes:
      { parameter: "temperaturemin" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "RW", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "degrees Celsius"}
  -
    name: humiditymax
    description: "humidity max value"
    attributes:
      { parameter: "humiditymax" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "RW", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}
  -
    name: temperaturemax
    description: "temperature max value"
    attributes:
      { parameter: "temperaturemax" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "RW", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "degrees Celsius"}
  -
    name: temperatureHI
    description: "temperature max value is arrived"
    attributes:
      { parameter: "temperatureHI" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "degrees Celsius"}
  -
    name: humidityHI
    description: "humidity max value is arrived"
    attributes:
      { parameter: "humidityHI" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}
  -
    name: temperatureLOW
    description: "temperature low value is arrived"
    attributes:
      { parameter: "temperatureLOW" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "degrees Celsius"}
  -
    name: humidityLOW
    description: "humidity low value is arrived"
    attributes:
      { parameter: "humidityLOW" }
    properties:
      value:
       { type: "Float32", size: "4", readWrite: "R", defaultValue: "0.00", minimum: "-1000.00", maximum: "1000.00" }
      units:
       { type: "String", readWrite: "R", defaultValue: "%RH"}
  -
    name: online_state
    description: "device online state"
    attributes:
      { parameter: "online_state" }
    properties:
      value:
       { type: "int32", size: "4", readWrite: "R", defaultValue: "0", minimum: "0", maximum: "1" }
      units:
       { type: "String", readWrite: "R", defaultValue: "leave time"}

deviceCommands:
  -
    name: modbus_temperature_device
    get:
      - { deviceResource: "temperature" }
      - { deviceResource: "humidity" }
      - { deviceResource: "temperaturemin" }
      - { deviceResource: "humiditymin" }
      - { deviceResource: "temperaturemax" }
      - { deviceResource: "humiditymax" }
      - { deviceResource: "online_state" }
    set:
      - { deviceResource: "temperaturemin" }
      - { deviceResource: "humiditymin" }
      - { deviceResource: "temperaturemax" }
      - { deviceResource: "humiditymax" }
coreCommands:
  -
    name: get_temperature_device_all
    get:
      path: "/api/v1/device/{deviceId}/modbus_temperature_device"
      responses:
      - code: "200"
        description: "Successfully read the modbus_temperature_device sensors."
        expectedValues: [ "modbus_temperature_device" ]
        #expectedValues: [ "temperature", "humidity", "temperaturemin", "humiditymin", "temperaturemax", "humiditymax", "online_state" ]
      - code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/modbus_temperature_device"
      parameterNames: [ "temperaturemin", "humiditymin", "temperaturemax", "humiditymax" ]
      responses:
      - code: "200"
        description: "Successfully set the temperaturemin and humiditymin."
        expectedValues: []
      - code: "503"
        description: "service unavailable"
        expectedValues: []
  -
    name: temperature
    get:
        path: "/api/v1/device/{deviceId}/temperature"
        responses:
          -
            code: "200"
            description: "Get the temperature reading."
            expectedValues: ["temperature"]
          -
            code: "503"
            description: "service unavailable"
            expectedValues: []

  -
    name: humidity
    get:
      path: "/api/v1/device/{deviceId}/humidity"
      responses:
        -
          code: "200"
          description: "Get the humidity reading."
          expectedValues: ["humidity"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []

  -
    name: temperaturemin
    get:
      path: "/api/v1/device/{deviceId}/temperaturemin"
      responses:
      - code: "200"
        description: "Get the temperaturemin value."
        expectedValues: ["temperaturemin"]
      - code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/temperaturemin"
      parameterNames: ["temperaturemin"]
      responses:
      - code: "200"
        description: "Successfully set the temperaturemin value."
        expectedValues: []
      - code: "503"
        description: "service unavailable"
        expectedValues: []
  -
    name: humiditymin
    get:
      path: "/api/v1/device/{deviceId}/humiditymin"
      responses:
      - code: "200"
        description: "Get the humiditymin value."
        expectedValues: ["humiditymin"]
      - code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/humiditymin"
      parameterNames: ["humiditymin"]
      responses:
      - code: "200"
        description: "Successfully set the humiditymin value."
        expectedValues: []
      - code: "503"
        description: "service unavailable"
        expectedValues: []
  -
    name: temperaturemax
    get:
      path: "/api/v1/device/{deviceId}/temperaturemax"
      responses:
      - code: "200"
        description: "Get the temperaturemax value."
        expectedValues: ["temperaturemax"]
      - code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/temperaturemax"
      parameterNames: ["temperaturemax"]
      responses:
      - code: "200"
        description: "Successfully set the temperaturemax value."
        expectedValues: []
      - code: "503"
        description: "service unavailable"
        expectedValues: []
  -
    name: humiditymax
    get:
      path: "/api/v1/device/{deviceId}/humiditymax"
      responses:
      - code: "200"
        description: "Get the humiditymax value."
        expectedValues: ["humiditymax"]
      - code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/humiditymax"
      parameterNames: ["humiditymax"]
      responses:
      - code: "200"
        description: "Successfully set the humiditymax value."
        expectedValues: []
      - code: "503"
        description: "service unavailable"
        expectedValues: []
  -
    name: temperatureHI
    get:
      path: "/api/v1/device/{deviceId}/temperatureHI"
      responses:
        -
          code: "200"
          description: "Get the temperatureHI reading."
          expectedValues: ["temperatureHI"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []
  -
    name: humidityHI
    get:
      path: "/api/v1/device/{deviceId}/humidityHI"
      responses:
        -
          code: "200"
          description: "Get the humidityHI reading."
          expectedValues: ["humidityHI"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []
  -
    name: temperatureLOW
    get:
      path: "/api/v1/device/{deviceId}/temperatureLOW"
      responses:
        -
          code: "200"
          description: "Get the temperatureLOW reading."
          expectedValues: ["temperatureLOW"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []
  -
    name: humidityLOW
    get:
      path: "/api/v1/device/{deviceId}/humidityLOW"
      responses:
        -
          code: "200"
          description: "Get the humidityLOW reading."
          expectedValues: ["humidityLOW"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []
  -
    name: online_state
    get:
      path: "/api/v1/device/{deviceId}/online_state"
      responses:
        -
          code: "200"
          description: "Get the online state reading."
          expectedValues: ["online_state"]
        -
          code: "503"
          description: "service unavailable"
          expectedValues: []

出现如下错误请先检查参数配置文件中是否有问题
在这里插入图片描述

设备驱动

以上贴出的main_device_service.c实则实现设备服务,其内部get、put接口将调用设备驱动的接口
设备驱动你需要做的事:

  • 设备硬件初始化
  • 实现解析网关下挂载的设备类型信息
  • 实现取对应不同类型的硬件设备数据
  • 实现对不同硬件设备的控制
  • 实现事件功能(功能的延申)
  • 实现驱动更新功能(动态扩展)

其实边缘网关实现减轻云端压力,释放边缘端的计算能力,最终各种算法的部署(更多种情况数据分析那种利用云端处理例如阿里,你上报的数据在云端数据库存储,它提供云计算服务你缴费.!),计算对于我来说就是一种设备,这个设备可以是虚拟的、也可是真实的

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

边缘计算开源框架EdgeXFoundry的部署应用开发(三)设备服务开发 的相关文章

  • 边缘计算

    多接入边缘计算 xff08 MEC xff09 是云计算之后的又一项突破性技术 xff0c 该技术有望使得应用和内容更接近网络边缘 xff0c 能够降低网络延迟 xff0c 并提供新的服务 iGR Research的分析师Iain Gill
  • TX2上如何查看cuda版本

    如果知道安装的路径 一般都是在local下 可以直接查看 nvidia tegra ubuntu cat usr local cuda version txt CUDA Version 9 0 252 能看到机器上的CUDA版本是9 0 2
  • JavaScript在小程序网页部署实战

    服务端推理和前端推理对比 前端推理趋势 落地场景 单模型 多模型 全链路流程 JS2 0介绍 核心指标 技术方案 模型转换 核心组件 初始化和预测 计算方案 模型安全处理 加密目标 方案介绍 方案流程 关键路径 性能优化 代码处理 视频获取
  • 基于人工智能与边缘计算Aidlux的工业表面缺陷检测

    一 训练yolov8得到onnx模型 相关教程有很多 二 模型转化 网站 https aimo aidlux com 输入试用账号和密码 账号 AIMOTC001 密码 AIMOTC001 我们选择 TensorFlowLite 一步步完成
  • 【毕业设计】基于M5Stack UnitV2的手写识别计算器的设计与制作

    目录 一 简介 二 设计原理 三 系统设计 四 注意点 五 困难 一 简介 耗时五个月 终于完成了毕业设计和毕业答辩 在这里记录一下学习与实现过程 题目是导师给的 这是主要任务和工作内容 利用M5Stack UnitV2的目标检测功能 用摄
  • 使用C对TOML文件的解析

    使用C对TOML文件的解析 toml书写语法 解析toml文件 测试输出内容如下 TOML是前GitHub CEO Tom Preston Werner 于2013年创建的语言 其目标是成为一个小规模的易于使用的语义化配置文件格式 TOML
  • 【模型压缩】网络层与算子融合

    由于深度学习网络层数深 结构复杂 生成的算子数量众多 带了巨大的计算资源在和时间的消耗 业界对于加速算子的计算展开了一定研究 比较经典的方法是将多个算子重新组合成一个新的算子 同时对生成的代码进行底层的性能优化 融合成新算子后计算相对于多个
  • IOTCS+Ekuiper搭建物联网边缘计算平台

    背景介绍 IOTCS 是专为物联网平台而设计的工业智能网关 自从 2020 年 10 月以来 我们从需求调研 设计 定型 研发 测试经过漫长的沉淀与孵化 最终顺利实现工业智能网关最初的设想 我们凭借创新设计理念 快捷的安装以及部署 易用的扩
  • 第十二章 YOLO的部署实战篇(下篇)

    文章目录 前言 专栏概括 1 cuda教程目录 2 cuda教程背景 3 cuda教程内容 一 yolov5部署整体逻辑 1 yolov5的main函数 2 yolo h头文件 3 整体架构显示 二 yolov5模型的onnx转engne代
  • 边缘计算浅析

    最近 新基建 是个热词 那么新基建到底是什么 与之紧密相关的5G何去何从 这给边缘计算带来了什么机遇 边缘计算的生态产业链条是怎样的 它的典型特征和经济效益是什么 本文将为大家带来分享 新基建是什么 近日 国家发改委官方首次明确了 新基建
  • 缘计算开源框架EdgeXFoundry的配置参数

    EdgeXFoundry的配置参数 Notes on device profiles 关于设备配置文件的说明 The device profile 设备配置文件 coreCommands deviceCommands deviceResou
  • [转]什么是边缘计算?

    转自 https blog csdn net gui951753 article details 80952907 注 本篇翻译自施巍松教授的论文 Edge Computing Vision and Challenges 目录 文章目录 摘
  • 边缘计算概念以及应用

    什么是边缘计算 边缘计算是为应用开发者和服务提供商在网络的边缘侧提供云服务和IT环境服务 边缘计算的目标是在靠近数据输入或用户的地方提供计算 存储和网络带宽 边缘计算 是一种分散式运算的架构 在这种架构下 将应用程序 数据资料与服务的运算
  • 工业协议网关IOTCS企业版正式发布

    一 发布前言 继今年5月份IOTCS开源版本发布以后 我们依然在致力于开源版的功能升级迭代 目前南向驱动类型支持OPCUA与Siemens S7协议 1200 1500型号 以及Modbus全协议 北向资源有HTTP MQTT 通过不同的连
  • 什么是边缘计算网关?

    边缘计算网关 简称 边缘网关 将云端功能扩展到本地的边缘设备 使边缘设备能够快速自主地响应本地事件 提供低延时 低成本 隐私安全 本地自治的本地计算服务 同时所有服务都以 Docker 镜像方式安装 真正做到了跨平台 部署快捷 易管理 在链
  • IOTCS解决Actor异常报告、驱动优化

    近日 我们发布了 IOTCS0 8 版本 版本主要解决了Actor异常报告 解决消息处理问题 此外 新增南向驱动BACNet 修复了PLC驱动连接参数 实时监测驱动连接状态 统一的数据格式处理 与此同时我们对现有版本其它模块进行了功能优化
  • #13文献学习--边缘计算的计算卸载建模综述

    文献 A survey on computation offloading modeling for edge computing 一 介绍 边缘计算 在网络边缘 代表云服务的下游数据和代表物联网服务的上游数据上执行计算 优势 计算或云计算
  • 大数据时代移动边缘计算架构中的差分隐私保护(二)

    大数据时代移动边缘计算架构中的差分隐私保护 二 实际上 给数据加拉普拉斯噪声或者是指数噪声是针对中心式的差分隐私处理框架的 对于本地化的差分隐私处理框架 现在已有的是采用随即相应技术 Bloom Filter等技术满足 本地化差分隐私 LD
  • 边缘计算操作系统安装及测试实验报告

    边缘计算操作系统安装及测试 一 实验目的 二 实验环境 三 实验原理 1 系统组成部分 2 总体数据流程 四 实验步骤及结果 1 安装 Docker 和 Docker Compose 2 下载 EdgeX compose 文件 3 运行Ed
  • 5G MEC在5G网络中的部署-与UPF的关系

    MEC主机部署在边缘或者核心数据网络中 而UPF负责牵引用户平面流量到目标MEC应用所在的数据网络 网络运营商除了选择数据网络和UPF之外 还需要根据技术和商业因素 例如 站点设施 应用需求 用户负载实测值或估算值 来选择物理计算资源的部署

随机推荐

  • 三步搞定ABAP DOI操作EXCEL

    前言 ABAP可以使用OLE与DOI两种方式实现操作EXCEL 使用OLE时 每个单元格的值和样式都需要写代码实现 特别是对于不规则的格式 代码量巨大 而DOI是从服务器已经上传的EXCEL模板中下载模板然后打开修改实现数据保存 当然 也可
  • sum(1),sum(2,3,4),sum(2)(3)(4)

  • 高级人工智能课程笔记

    课程部分笔记 依据 人工智能 一种现代化方法 第三版 目录 智能概述 搜索search Uninformed Search Informed Search 约束满足问题CSP MDP 值迭代方法 策略迭代 RL 朴素贝叶斯 其他 智能概述
  • el-input针对手机号的校验优化

    注意 以下代码均只展示与本文相符的内容 并不提供完整项目代码 先看代码 tool js文件 function isvalidPhone str const reg 1 3 4 5 7 8 0 9 d 8 return reg test st
  • tomcat 修改默认访问项目名称和项目发布路径

    摘要 本次主要介绍tomcat设置访问的默认项目的名称和项目存放的路径 1 修改项目发布路径 tomcat默认的而发布路径为 tomcat webapps 目录 但是这个目录下有一些默认的项目 在tomcat启动的时候会跟着一起加载 如果不
  • 数据库连接池的实现及原理

    对于一个简单的数据库应用 由于对于数据库的访问不是很频繁 这时可以简单地在需要访问数据库时 就新创建一个连接 用完后就关闭它 这样做也不会带来什么明显的性能上的开销 但是对于一个复杂的数据库应用 情况就完全不同了 频繁的建立 关闭连接 会极
  • Docker (一)如何打dockerfile

    熟悉docker 是高级java必备的技术素质 在面试中 经常会有公司问到 你会打dockerfile吗 面试中很少问及docker的其他知识点 那是因为docker是运维范围内的事 如果你的公司拥有强大的运维平台的话 基本上打docker
  • windows下编译dlib

    dlib 1 下载dlib源码 dlib18 17 http pan baidu com s 1gey9Wd1 2 解压源码包 3 打开cmake 设置source code路径为解压目录 新建生成目录 起名为build 设为二进制生成目录
  • 如何提高训练模型准确率

    如何提高训练模型准确率 原文链接 https blog csdn net Winteeena article details 78997696 提升一个模型的表现有时很困难 尝试所有曾学习过的策略和算法 但模型正确率并没有改善 这才是考验真
  • call、apply、bind的基本概念

    const dog name 旺财 getName console log 我的名字叫 this name setFood food console log 我的名字叫 this name 我喜欢吃 food const eat name
  • 聚簇索引与主键的选择

    聚簇索引与主键的选择 一 什么是聚簇索引 二 什么是非聚簇索引 1 InnoDB引擎中 2 MyISAM引擎中 三 聚簇索引的优劣与主键选择的关系 一 什么是聚簇索引 首先 聚簇索引不是一种单独的索引类型 其实是数据的存储方式 聚簇索引将数
  • Kotlin的互操作——Kotlin与Java互相调用

    互操作就是在Kotlin中可以调用其他编程语言的接口 只要它们开放了接口 Kotlin就可以调用其成员属性和成员方法 这是其他编程语言所无法比拟的 同时 在进行Java编程时也可以调用Kotlin中的API接口 Kotlin与Java互操作
  • 【phaser微信抖音小游戏开发002】hello world!

    执行效果 将以下代码文本内容 放入到game js中即可 目录结构如下图 import js libs weapp adapter import js libs symbol GameGlobal window scrollTo gt 防止
  • uniapp request请求同步化

    第一种方式 内容区
  • ChatGPT介绍世界杯历史与编写足球游戏python程序

    ChatGPT聊天机器人最近非常流行 是由OpenAI于本月发布的 花了一点时间注册了一个账号 如有需要帮助注册的可以随时与我交流 注册过程相对有一些复杂 除了常规的聊天对话功能之外 ChatGPT聊天机器具备强大的文本生成能力 例如博客
  • html5 key键值屏蔽,javascript键值对中的key为变量

    javascript键值对中的key是可以为变量的 比如js json对象定义的时候也有可能key就是变量的 我们就可以这样做 js代码如下 var userJson 假如userId就是一个js变量 var userId getUserI
  • 代码+步骤GM(1,1)灰色预测模型-案例长江水质综合评价赛题-级比检测C的确定-matlab完整代码附送

    GM 1 1 灰色预测模型 案例长江水质综合评价赛题第三题 matlab完整代码附送 1 对长江近两年多的水质情况做出定量的综合评价 并分析各地区水质的污染状况 2 研究 分析长江干流近一年多主要污染物高锰酸盐指数和氨氮的污染源主要在哪些地
  • 动态代理的过程

    package com jd calculator import java lang reflect InvocationHandler import java lang reflect Method import java lang re
  • java类里面再定一个类_java类的里面可以再定义一个类吗 java里可不可以在一个...

    java类里面还可以定义一个类 即内部类 java内部类分为 成员内部类 静态嵌套类 方法内部类 匿名内部类 内部类的共性 1 内部类仍然是一个独立的类 在编译之后内部类会被编译成独立的 class文件 但是前面冠以外部类的类名和 符号 2
  • 边缘计算开源框架EdgeXFoundry的部署应用开发(三)设备服务开发

    边缘计算开源框架EdgeXFoundry的部署应用开发 三 设备服务开发 使用SDK开发真实设备接入服务 着手编写一个温湿度设备接入 准备相关文件及目录 脚本可选 用于单文件编译测试 编写温湿度设备接入设备服务 功能框架 设备名的解析 配置