Envoy 物联网模块开发---串口服务器 (一)

2023-11-22 15:59

本文主要是介绍Envoy 物联网模块开发---串口服务器 (一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、背景

最近业余时间想基于Envoy 开发一个串口网关,主要是想把一些 modbus、bacnet 以及 mqtt 等物联网协议接入Envoy中,当读到串口数据后可以转发成对应的网络协议

二、Envoy的优势

选择Envoy的话主要是因为Envoy的代码已经十分健全了,零信任、连接池、DNS解析、健康检查、集群调度等等Envoy都支持的很完善了,思来想去还是决定在Envoy基础上走二开,Envoy应该是C++里写的最好的网关了,内存小,而且扩展性极强。

三、Envoy在物联网方面的劣势

但是Envoy也有一些缺点

1、Envoy是基于互联网的网关对物联网模块支持不足、对物联网协议支持的不多

2、Envoy 代码巨大,开发难度和成本非常的高,开发起来非常的困难以及复杂,对技术要求十分的高。

3、Envoy代码巨大,变动一个文件就可能要几个小时

4、Envoy当前ListenerManager 并没有很好的扩展性,甚至在Bazel 文件里可见性只有几个模块,而且只支持UDP和TCP两种通信,要加一个串口通信难度并不小。

四、我们该怎么做?

尽量不要自己写基本的串口代码,使用第三方库libserialport

libserial

串口的第三方库

https://github.com/crayzeewulf/libserial

我们需要做的事情是两部:

1、加入新的listener

2、引入第三方串口库

1、加入新的listener

Envoy本身是不支持Listener模块扩展的,只支持Filter,Listener模块如果我们想扩展,就需要动ListenerManager的代码,动刀需要谨慎,所以我拷贝出来一份ListenerMangaer

 

核心代码改动:

修改配置下发格式为:

listeners:- name: listener_0address:socket_address:address: 0.0.0.0port_value: 10001rtu:path: /dev/ttyS0filter_chains:- filters:- name: envoy.filters.network.http_connection_manager

修改proto配置文件

api/envoy/config/core/v3/address.proto

加入串口的Rtu形式:


// Addresses specify either a logical or physical address and port, which are
// used to tell Envoy where to bind/listen, connect to upstream and find
// management servers.
message Address {option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Address";oneof address {option (validate.required) = true;SocketAddress socket_address = 1;Pipe pipe = 2;// Specifies a user-space address handled by :ref:`internal listeners// <envoy_v3_api_field_config.listener.v3.Listener.internal_listener>`.EnvoyInternalAddress envoy_internal_address = 3;Rtu rtu = 4;}
}message Rtu {option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Rtu";// Unix Domain Socket path. On Linux, paths starting with '@' will use the// abstract namespace. The starting '@' is replaced by a null byte by Envoy.// Paths starting with '@' will result in an error in environments other than// Linux.string path = 1 [(validate.rules).string = {min_len: 1}];// The mode for the Rtu. Not applicable for abstract sockets.uint32 mode = 2 [(validate.rules).uint32 = {lte: 511}];
}

source/common/network/utility.cc

适配串口:

    case envoy::config::core::v3::SocketAddress::UDP:return Socket::Type::Datagram;case envoy::config::core::v3::SocketAddress::SERIAL:return Socket::Type::Stream;}}

由于watchermen listener manager 原来Envoy代码不能用了,需要我们扩展,所以注释掉原来Envoy的ListenerManager入口


//REGISTER_FACTORY(DefaultListenerManagerFactoryImpl, ListenerManagerFactory);

加入新的串口实例化方式

source/common/network/utility.cc

    return std::make_shared<Address::EnvoyInternalInstance>(proto_address.envoy_internal_address().server_listener_name(),proto_address.envoy_internal_address().endpoint_id());case envoy::config::core::v3::Address::AddressCase::kRtu:return std::make_shared<Address::RtuInstance>(proto_address.rtu().path(),proto_address.rtu().mode());case envoy::config::core::v3::Address::AddressCase::ADDRESS_NOT_SET:PANIC_DUE_TO_PROTO_UNSET;}

 case envoy::config::core::v3::Address::AddressCase::kEnvoyInternalAddress:// Currently internal address supports stream operation only.return Socket::Type::Stream;case envoy::config::core::v3::Address::AddressCase::kRtu:return Socket::Type::Stream;case envoy::config::core::v3::Address::AddressCase::ADDRESS_NOT_SET:PANIC_DUE_TO_PROTO_UNSET;}
/*** Implementation of a pipe address (unix domain socket on unix).*/
class RtuInstance : public InstanceBase {
public:/*** Construct from an existing unix address.*/explicit RtuInstance(const sockaddr_un* address, socklen_t ss_len, mode_t mode = 0,const SocketInterface* sock_interface = nullptr);/*** Construct from a string pipe path.*/explicit RtuInstance(const std::string& pipe_path, mode_t mode = 0,const SocketInterface* sock_interface = nullptr);static absl::Status validateProtocolSupported() { return absl::OkStatus(); }// Network::Address::Instancebool operator==(const Instance& rhs) const override;const Ip* ip() const override { return nullptr; }const Pipe* pipe() const override { return &pipe_; }const EnvoyInternalAddress* envoyInternalAddress() const override { return nullptr; }const sockaddr* sockAddr() const override {return reinterpret_cast<const sockaddr*>(&pipe_.address_);}const sockaddr_un& getSockAddr() const { return pipe_.address_; }socklen_t sockAddrLen() const override {if (pipe_.abstract_namespace_) {return offsetof(struct sockaddr_un, sun_path) + pipe_.address_length_;}return sizeof(pipe_.address_);}absl::string_view addressType() const override { return "default"; }private:/*** Construct from an existing unix address.* Store the error status code in passed in parameter instead of throwing.* It is called by the factory method and the partially constructed instance will be discarded* upon error.*/RtuInstance(absl::Status& error, const sockaddr_un* address, socklen_t ss_len, mode_t mode = 0,const SocketInterface* sock_interface = nullptr);struct PipeHelper : public Pipe {bool abstractNamespace() const override { return abstract_namespace_; }mode_t mode() const override { return mode_; }sockaddr_un address_;// For abstract namespaces.bool abstract_namespace_{false};uint32_t address_length_{0};mode_t mode_{0};};absl::Status initHelper(const sockaddr_un* address, mode_t mode);PipeHelper pipe_;friend class InstanceFactory;
};
//RTU
RtuInstance::RtuInstance(const sockaddr_un* address, socklen_t ss_len, mode_t mode,const SocketInterface* sock_interface): InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {if (address->sun_path[0] == '\0') {
#if !defined(__linux__)throw EnvoyException("Abstract AF_UNIX sockets are only supported on linux.");
#endifRELEASE_ASSERT(static_cast<unsigned int>(ss_len) >= offsetof(struct sockaddr_un, sun_path) + 1,"");pipe_.abstract_namespace_ = true;pipe_.address_length_ = ss_len - offsetof(struct sockaddr_un, sun_path);}absl::Status status = initHelper(address, mode);throwOnError(status);
}RtuInstance::RtuInstance(const std::string& pipe_path, mode_t mode,const SocketInterface* sock_interface): InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {if (pipe_path.size() >= sizeof(pipe_.address_.sun_path)) {throw EnvoyException(fmt::format("Path \"{}\" exceeds maximum UNIX domain socket path size of {}.", pipe_path,sizeof(pipe_.address_.sun_path)));}memset(&pipe_.address_, 0, sizeof(pipe_.address_));pipe_.address_.sun_family = AF_UNIX;if (pipe_path[0] == '@') {// This indicates an abstract namespace.// In this case, null bytes in the name have no special significance, and so we copy all// characters of pipe_path to sun_path, including null bytes in the name. The pathname must also// be null terminated. The friendly name is the address path with embedded nulls replaced with// '@' for consistency with the first character.
#if !defined(__linux__)throw EnvoyException("Abstract AF_UNIX sockets are only supported on linux.");
#endifif (mode != 0) {throw EnvoyException("Cannot set mode for Abstract AF_UNIX sockets");}pipe_.abstract_namespace_ = true;pipe_.address_length_ = pipe_path.size();// The following statement is safe since pipe_path size was checked at the beginning of this// functionmemcpy(&pipe_.address_.sun_path[0], pipe_path.data(), pipe_path.size()); // NOLINT(safe-memcpy)pipe_.address_.sun_path[0] = '\0';pipe_.address_.sun_path[pipe_path.size()] = '\0';friendly_name_ = friendlyNameFromAbstractPath(absl::string_view(pipe_.address_.sun_path, pipe_.address_length_));} else {// Throw an error if the pipe path has an embedded null character.if (pipe_path.size() != strlen(pipe_path.c_str())) {throw EnvoyException("UNIX domain socket pathname contains embedded null characters");}StringUtil::strlcpy(&pipe_.address_.sun_path[0], pipe_path.c_str(),sizeof(pipe_.address_.sun_path));friendly_name_ = pipe_.address_.sun_path;}pipe_.mode_ = mode;
}RtuInstance::RtuInstance(absl::Status& error, const sockaddr_un* address, socklen_t ss_len,mode_t mode, const SocketInterface* sock_interface): InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {if (address->sun_path[0] == '\0') {
#if !defined(__linux__)error = absl::FailedPreconditionError("Abstract AF_UNIX sockets are only supported on linux.");return;
#endifRELEASE_ASSERT(static_cast<unsigned int>(ss_len) >= offsetof(struct sockaddr_un, sun_path) + 1,"");pipe_.abstract_namespace_ = true;pipe_.address_length_ = ss_len - offsetof(struct sockaddr_un, sun_path);}error = initHelper(address, mode);
}bool RtuInstance::operator==(const Instance& rhs) const { return asString() == rhs.asString(); }absl::Status RtuInstance::initHelper(const sockaddr_un* address, mode_t mode) {pipe_.address_ = *address;if (pipe_.abstract_namespace_) {if (mode != 0) {return absl::FailedPreconditionError("Cannot set mode for Abstract AF_UNIX sockets");}// Replace all null characters with '@' in friendly_name_.friendly_name_ = friendlyNameFromAbstractPath(absl::string_view(pipe_.address_.sun_path, pipe_.address_length_));} else {friendly_name_ = address->sun_path;}pipe_.mode_ = mode;return absl::OkStatus();
}

加入新的常量:

enum class Type { Ip, Pipe, EnvoyInternal, Rtu };

二、引入第三方串口库

第三方串口库我使用的是

GitHub - crayzeewulf/libserial: Serial Port Programming in C++

定义iot.bzl

load("@envoy_api//bazel:envoy_http_archive.bzl", "envoy_http_archive")
load("@envoy_api//bazel:external_deps.bzl", "load_repository_locations")
load("repository_locations.bzl", "WATCHERMEN_REPOSITORY_LOCATIONS_SPEC")# archives, e.g. cares.
def _build_all_content(exclude = []):return """filegroup(name = "all", srcs = glob(["**"], exclude={}), visibility = ["//visibility:public"])""".format(repr(exclude))BUILD_ALL_CONTENT = _build_all_content()WATCHERMEN_REPOSITORY_LOCATIONS = load_repository_locations(WATCHERMEN_REPOSITORY_LOCATIONS_SPEC)# Use this macro to reference any HTTP archive from bazel/repository_locations.bzl.
def external_http_archive(name, **kwargs):envoy_http_archive(name,locations = WATCHERMEN_REPOSITORY_LOCATIONS,**kwargs)def watchermen_iot_dependencies():external_http_archive(name = "com_github_serial",build_file_content = BUILD_ALL_CONTENT,)native.bind(name = "serial",actual = "//bazel/foreign_cc:serial",)

 定义仓库字典:

# This should match the schema defined in external_deps.bzl.WATCHERMEN_REPOSITORY_LOCATIONS_SPEC = dict(com_github_serial = dict(project_name = "serial",project_desc = "C library for serial port",project_url = "https://github.com/crayzeewulf/libserial",version = "master",strip_prefix = "libserial-{version}",# urls = ["https://github.com/crayzeewulf/libserial/archive/refs/tags/v{version}.tar.gz"],urls = ["https://github.com/crayzeewulf/libserial/archive/refs/heads/master.zip"],release_date = "2022-05-29",sha256 = "9f0c6137e56027d496a205072c527d47f552d4c170f24ae5cea2668da54e2a1b",use_category = ["dataplane_core"],cpe = "cpe:2.3:a:c-serial_project:c-serial:*",license = "libserial",license_url = "https://github.com/crayzeewulf/libserial/blob/master/LICENSE.txt",),
)

加入bazel cmake

envoy_cmake(name = "serial",lib_source = "@com_github_serial//:all",cache_entries = {# "CMAKE_INSTALL_LIBDIR": "lib",# "CMAKE_CXX_COMPILER_FORCED": "on","LIBSERIAL_ENABLE_TESTING": "off","LIBSERIAL_BUILD_EXAMPLES": "off",},# linkopts = select({#     # "//bazel:apple": ["-lresolv"],#     "//conditions:default": [],# }),cmake_files_dir = "$BUILD_TMPDIR/",out_static_libs = select({"//conditions:default": ["libserial.a"],}),# postfix_script = select({#     # "//bazel:windows_x86_64": "cp -L $EXT_BUILD_ROOT/external/com_github_libserial/src/lib/ares_nameser.h $INSTALLDIR/include/ares_nameser.h && cp -L $EXT_BUILD_ROOT/external/com_github_c_ares_c_ares/include/ares_dns.h $INSTALLDIR/include/ares_dns.h",#     # "//conditions:default": "rm -f $INSTALLDIR/include/ares_dns.h && cp -L $EXT_BUILD_ROOT/external/com_github_c_ares_c_ares/include/ares_dns.h $INSTALLDIR/include/ares_dns.h",# }),
)

 

在模块中引入Envoy 

envoy_cc_library(name = "watchermen_iot_factory_lib",hdrs = ["watchermen_rtu_socket_handle.h","watchermen_rtu_listener_socket.h",],srcs = ["watchermen_rtu_listener_socket.cc","watchermen_rtu_socket_handle.cc",],repository = "@envoy",external_deps = ["serial"],deps = [# "//external:serial"],
)

这篇关于Envoy 物联网模块开发---串口服务器 (一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/411287

相关文章

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。