用 AWTK 和 AWPLC 快速开发嵌入式应用程序 (4)- 自定义功能块(上)

news2024/10/7 20:25:47

AWPLC 目前还处于开发阶段的早期,写这个系列文章的目的,除了用来验证目前所做的工作外,还希望得到大家的指点和反馈。如果您有任何疑问和建议,请在评论区留言。

1. 背景

AWTK 全称 Toolkit AnyWhere,是 ZLG 开发的开源 GUI 引擎,旨在为嵌入式系统、WEB、各种小程序、手机和 PC 打造的通用 GUI 引擎,为用户提供一个功能强大、高效可靠、简单易用、可轻松做出炫酷效果的 GUI 引擎

AWPLC 是 ZLG 自主研发的 PLC 系统(兼容 IEC61131-3),其中 AWPLC 的运行时库 (Runtime) 基于 ZLG TKC 开发,可以移植到到任何主流 RTOS 和 嵌入式系统。AWPLC 的集成开发环境 (IDE) 基于 AWTK 开发,可以运行在 Windows、MacOS 和 Linux 系统之上。AWPLC 的主要目标之一是把 PLC 中 低代码开发方法 引入到嵌入式软件,从而提高嵌入式软件的开发效率和可靠性。

2. 简介

在前一篇文章中,我们说过,AWPLC 的重要特色之一就是高度可扩展,而且会内置 ZLG 多年在嵌入式系统开发中积累的功能块,包括各种算法、协议和实用功能,这将大大简化嵌入式软件的开发。

那怎么去开发自定义的功能块呢?本文以 ZTIMER 为例介绍一下开发自定义功能块的方法。ZTIMER 是一个带计数功能的定时器,在前一篇文章中,我们用它实现了一个走马灯的演示,其使用方法如下:

plc_fbd.png

在 AWPLC 中,自定义功能块和内置功能块具有同等待遇,因为它们都是按同样的方式加入进来的。在进入正题前,我们先聊一下,系统的可扩展性以及实现方法。

2.1 可扩展性的好处

在设计一个复杂软件的架构时,可扩展性是必须考虑的因素。可扩展性至少带来以下几个好处:

  • 可扩展性将软件的框架与具体的实现分离开来,有助于降低系统的复杂度。系统的复杂性太高,会带来一系列的问题,比如让可理解性、可维护性和可靠性的降低,很多项目因此陷入无法挣脱的焦油坑里,最后士气低落,人员流失,项目取消,公司蒙受巨大损失。在设计复杂软件时,一定要存有敬畏之心。

  • 可扩展性将软件变化的部分隔离开来,不但可以让扩展的功能独立变化,也可以方便的扩展新功能。在 AWPLC 中, 以后会扩展各种协议和算法的功能块,必须保证 AWPLC 框架和这些扩展的功能块是独立的,才能让开发工作顺利进行。

  • 可扩展性有利于团队的协作。 不同的通讯协议和算法,需要不同团队的专家去开发,可扩展性让大家只要按相应的接口去实现,就可以方便的集成起来,不需要太多跨团队的交互。

2.2 如何保证可扩展性

让软件系统具有可扩展性,通常并不是什么难事,只要做到下面两点就可以了:

  • 针对接口编程。这个是大家都知道的,在《软件设计模式》等书里,都反复强调了,这里不再赘述。

  • 利用工厂模式隔离组件的创建。工厂模式也是人人都知道的,而且大家都觉得很"简单"。但是能把工厂模式用好的程序员其实并不多见,一个主要原因就是很多人只会套用《软件设计模式》的工厂模式,而《软件设计模式》里几个工厂模式在现实中并不实用。利用这些这些工厂模式,无法满足 SOLID 原则中的开放封闭原则,增加一个新的扩展时,仍然需要修改对应的工厂。

3. AWPLC 功能块的接口

要让 AWPLC 支持扩展各种自定义的功能块,首要条件条件是定义好功能块的接口。

3.1 功能块的基类

在面向对象的 C 语言编程中,我们用结构 (struct) 来模拟类和接口。这里所说的接口是广义的接口,而不是 C++或其它语言中只包含纯虚函数的 interface,因为除了虚函数指针外,这里还有一些数据成员。

/**
 * @class aw_plc_fb_t
 * AWPLC 功能块接口。
 */
struct _aw_plc_fb_t {
  /** 
   * @property {bool_t} en
   * 是否启用。
   */
  uint8_t en : 1;
  /** 
   * @property {bool_t} eno
   * 是否启用输出。
   */
  uint8_t eno : 1;

  /*private*/
  const aw_plc_fb_vtable_t* vt; 
};

3.2 功能块的虚函数

在功能块的虚函数表中,还定义了一些描述性的常量,让对象具有一点反射的能力,方便在运行时查询它的一些状态。顺便说一下,在定义接口的虚函数时,通常不会有创建函数,因为创建之前对象之前,是拿不到这个虚表对象的。但也不是绝对的,有时为了方便 clone,也可能提供一个 clone 函数或者 create 函数。

任何接口都要定义析构函数 (destroy),在对象需要销毁时,框架可以以统一的方式销毁它。

typedef struct _aw_plc_fb_vtable_t {
  /*功能块的类型名*/
  const char* type;
  /*输入参数名称列表,以 NULL 结束的字符串数组*/
  const char* const* ins;
  /*输出参数名称列表,以 NULL 结束的字符串数组*/
  const char* const* outs;
  /*输入输出参数名称列表,以 NULL 结束的字符串数组*/
  const char* const* in_outs;
  /*执行函数*/
  aw_plc_fb_exec_t exec;
  /*执行函数(带参数)*/
  aw_plc_fb_exec_ex_t exec_ex;
  /*获取属性(输入输出参数)的值*/
  aw_plc_fb_get_prop_t get_prop;
  /*获取输出的值*/
  aw_plc_fb_get_output_t get_output;
  /*设置输出的值*/
  aw_plc_fb_set_input_t set_input;
  /*析构函数*/
  aw_plc_fb_destroy_t destroy;
} aw_plc_fb_vtable_t;

这个虚函数表和 AWTK/TKC 中的 object 虚函数表很相似,考虑到 object 为了做得通用,有点臃肿了,所以决定重新定义一套。

4. AWPLC 功能块的工厂

前面我们说过,可扩展性除了针对接口编程外,离不开工厂模式的支持。功能块的工厂其任务当然是创建功能块了,所以提供了一个创建功能块的函数。参数 type 指定功能块的类型,函数返回对应类型的功能块:

/**
 * @method aw_plc_fb_factory_create_fb
 * 创建 fb。
 * @param {const char*} type 类型。
 *
 * @return {aw_plc_fb_t*} 返回 fb 对象。
 */
aw_plc_fb_t* aw_plc_fb_factory_create_fb(const char* type);

有了这个创建函数,确实把创建任务与功能块的实现分开了。但是请想一下,如果每次增加新的功能块,都要修改这个创建函数,而这个函数又属于框架的一部分,框架是不是还是依赖于具体实现了呢?为了解决这个问题,我们需要提供一种注册机制来实现依赖倒置,让功能块的实现者主动将创建函数注册进来:

/**
 * @method aw_plc_fb_factory_register
 * 注册创建函数。
 * @param {const char*} type 类型。
 * @param {aw_plc_fb_create_t} create 创建函数。
 *
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t aw_plc_fb_factory_register(const char* type, aw_plc_fb_create_t create);

这种机制非常好用,真正满足了 SOLID 原则中的开放封闭原则 (OCP):扩展新的功能无需修改框架代码。在 ZLG 开源 GUI 引擎中,也大量使用了这种带注册功能的工厂模式,有兴趣的朋友可以去看看 AWTK 的代码。

5. ZTIMER

5.1 ZTIMER 的结构

在 C 语言中,一般用结构来模拟类,把基类作为结构的第一个成员来模拟继承。这里必须让 aw_plc_fb_t 作为 aw_plc_fb_ztimer_t 的第一个成员。

/**
 * @class aw_plc_fb_ztimer_t
 * @parent aw_plc_fb_t
 * @annotation ["fb"]
 * 循环定时器。
 * 
 * > 当输入 IN 为 TRUE 时,开始计时,输出 Q 为 FALSE,ET 开始记录过去的时间。
 * > 定时时间到时,COUNT 增加 1, 输出 Q 在本次循环为 TRUE,ET 重置为 0。
 * > 输入 IN 为 FALSE 时重置定时器。
 */
typedef struct _aw_plc_fb_ztimer_t {
  aw_plc_fb_t fb; 

  /** 
   * @property {bool_t} in
   * @annotation ["in"]
   * 为 TRUE 开始计时,为 FALSE 时重置定时器。
   */
  bool_t in : 1;

  /** 
   * @property {iec_time_t} pt
   * @annotation ["in"]
   * 预设时间 (ms)。
   */
  iec_time_t pt;

...
} aw_plc_fb_ztimer_t;

这里的 API 注释采用了 AWTK 中定义的格式,但是对 annotation 做了一点扩展,增加了 3 个新的取值:

  • fb 表示这是一个功能块。
  • in 表示这是一个输入参数。
  • out 表示这是一个输出参数。

5.2 ZTIMER 的实现

每个功能块必须提供虚函数表中定义的函数,不过主要代码集中 exec 函数里(其它函数可以自动生成出来):

static ret_t aw_plc_fb_ztimer_exec(aw_plc_fb_t* fb) {
  aw_plc_fb_ztimer_t* ztimer = AW_PLC_FB_ZTIMER(fb);

  if (aw_plc_fb_before_exec(fb) == RET_OK) {
    ztimer->current_time = aw_plc_now_ms();
    if (ztimer->state == 0 && !ztimer->prev_in && ztimer->in) {
      ztimer->state = 1;
      ztimer->q = FALSE;

      ztimer->et = 0;
      ztimer->count = 0;
      ztimer->start_time = ztimer->current_time;
    } else {
      if (!ztimer->in) {
        ztimer->q = FALSE;
        ztimer->state = 0;

        ztimer->et = 0;
        ztimer->count = 0;
        ztimer->start_time = ztimer->current_time;
      } else if (ztimer->state == 1) {
        if ((ztimer->start_time + ztimer->pt) <= ztimer->current_time) {
          ztimer->q = TRUE;

          ztimer->et = 0;
          ztimer->count++;
          ztimer->start_time = ztimer->current_time;
        } else {
          ztimer->q = FALSE;
          ztimer->et = ztimer->current_time - ztimer->start_time;
        }   
      }   
    }   
    ztimer->prev_in = ztimer->in;
  }

  return RET_OK;
}

5.3 注册 ZTIMER

功能块需要注册到前面介绍的功能块工厂:

aw_plc_fb_factory_register(AW_PLC_FB_TYPE_ZTIMER, aw_plc_fb_ztimer_create);

坦白的讲,本文只是介绍了实现自定义功能块的关键步骤,实际工作要麻烦很多。如果手工去做这些工作,开发一个功能块还觉得好玩,而开发几十个甚至几百个功能块,人不会变疯就会变傻。下一篇文章会我们介绍一下,如何用代码生成器来完成这些单调的工作,让开发自定义功能块成为一项快乐的工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PTA题目 两个数的简单计算器

本题要求编写一个简单计算器程序&#xff0c;可根据输入的运算符&#xff0c;对2个整数进行加、减、乘、除或求余运算。题目保证输入和输出均不超过整型范围。 输入格式&#xff1a; 输入在一行中依次输入操作数1、运算符、操作数2&#xff0c;其间以1个空格分隔。操作数的数…

跟艾文学编程《Python基础》(7)pandas数据分析

作者&#xff1a; 艾文&#xff0c;计算机硕士学位&#xff0c;企业内训讲师和金牌面试官&#xff0c;公司资深算法专家&#xff0c;现就职BAT一线大厂。邮箱&#xff1a; 1121025745qq.com博客&#xff1a;https://wenjie.blog.csdn.net/内容&#xff1a;跟艾文学编程《Python…

汉字风格迁移篇---W-net:基于深度神经网络的一次任意风格汉字生成

文章目录一、摘要二、提出原因已有的一些模型解决方案依然存在的限制三、介绍与创新四、模型介绍预处理w-net结构优化策略和损失函数五、实验实验设置用zi2zi作为基线具体实现1、 W-Net训练期间的超参数设置如下&#xff1a;2、一些细节处理模型评估W-net、zi2zi-v1、zi2zi-v2不…

第2-3-7章 个人网盘服务接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.8 导入其他接口代码5.8.1 接口导入-分页查询附件5.8.2 接口导入-根据业务类型/业务id查询附件5.9 导入网盘服务接口5.9.1 导入FileController5.9.2 导入StatisticsController5.9.3 导入FileRestManager5.9.4 导入FileService5.9.5 导入FileServiceImpl5.9.6 扩展File…

面向OLAP的列式存储DBMS-8-[ClickHouse]的常用聚合函数

ClickHouse 中的常用聚合函数 1 聚合函数 ClickHouse 中的聚合函数&#xff0c;因为和关系型数据库的相似性&#xff0c;本来聚合函数不打算说的&#xff0c;但是 ClickHouse 提供了很多关系型数据库中没有的函数&#xff0c;所以我们还是从头了解一下。 1.1 count count&…

Vue3 用src动态引入本地图片

&#x1f4ad;&#x1f4ad; ✨&#xff1a; Vue3 用src动态引入本地图片   &#x1f49f;&#xff1a;东非不开森的主页   &#x1f49c;: 躲起来的星星也在努力发光 你也要&#x1f49c;&#x1f49c;   &#x1f338;: 如有错误或不足之处&#xff0c;希望可以指正&#…

Qt OpenGL(二十二)——Qt OpenGL 核心模式-VAO和VBO

Qt OpenGL(二十二)——Qt OpenGL 核心模式-VAO和VBO 一、再谈VAO、VBO 上一篇文章,通过VAO、VBO绘制了一个三角形,过程需要创建VAO、VBO和释放。之所以有这些步骤,就是因为OpenGL本质就是一个大的状态机。但是我们如果要继续学习核心模式的OpenGL的话,VAO、VBO是我们必…

Java集合(一):泛型与Collection集合

目录 集合预热&#xff1a;泛型 泛型的优点 自定义泛型类型 自定义泛型类/接口 泛型使用细节 自定义泛型方法 泛型与继承关系 不存在继承关系的情况 通配符与存在继承关系的情况 泛型受限 集合概述 集合的作用与存储内容 集合与数据结构 集合&#xff1a;Collectio…

【基础算法系列】离散化与前缀和算法的运用

⭐️前面的话⭐️ 本篇文章将主要介绍离散化算法&#xff0c;所谓离散化算法&#xff0c;就是将一个无限区间上散点的数&#xff0c;在不改变相对大小的情况下&#xff0c;映射到一个较小的区间当中&#xff0c;然后对这个较小的区间进行操作的过程就是离散化的过程&#xff0…

【C++笔试强训】第二十八天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

微信小程序自定义tabBar(实操)

文章目录一、前言二、固定效果图实现步骤实现步骤完整代码-矢量图images图片app.json代码三、自定义效果图实现步骤实现步骤完整代码-矢量图images图片app.json代码custom-tab-bar下的代码使用自定义TaBar一、前言 一般使用tabBar的样式&#xff0c;固定不能改变。如下固定效果…

java计算机毕业设计springboot+vue村委会管理系统

项目介绍 本村委会管理系统是针对目前村委会管理的实际需求,从实际工作出发,对过去的村委会管理系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概…

DPDK-A3: KVM使用SRIOV和虚机使用DPDK

虚拟机基本管理 如下命令可以修改默认网段 sudo virsh net-edit --network default<network><name>default</name><uuid>45ed012c-3933-4f3e-9575-b37bffa21b83</uuid><forward modenat/><bridge namevirbr0 stpon delay0/><ma…

解决javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)V异常

文章目录异常&#xff1a;不同jar包的多xml解析器冲突解决其他异常&#xff1a; java.lang.AbstractMethodError:javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)可能原因&#xff1a; 在本地WINDOWS编译测试没问题&#xff0c;只在LINUX服务器上面…

通讯录的实现【涉及动态内存管理和文件操作】【从易到难】【详解】

本期介绍&#x1f356; 主要介绍&#xff1a;如何实现一个通讯录&#xff0c;从静态版通讯录&#xff0c;到动态内存版通讯录&#xff0c;再到文件存储版通讯录&#xff0c;详细讲述了每一个通讯录的实现步骤以及思维逻辑&#xff0c;以及通讯录的完整代码&#x1f440;。 文章…

基于Springboot+mybatis+mysql+html图书管理系统

基于Springbootmybatismysqlhtml图书管理系统一、系统介绍二、功能展示1.用户登陆2.图书管理3.读者管理4.借还管理5.密码修改6.图书查询&#xff08;读者&#xff09;7.个人信息&#xff08;读者&#xff09;8.我的借还&#xff08;读者&#xff09;一、系统介绍 系统主要功能…

深究为啥Vue管理的函数不能是箭头函数

首先明确一点&#xff0c;箭头函数的this指向是根据上下文作用域确定的 Vue框架中&#xff0c;容易搞错的一点就是认为对象也有作用域 了解作用域与作用域链这个问题就迎刃而解了 假设Vue管理的函数是箭头函数时&#xff1a; 此时this是windows&#xff0c;Vue中data与metho…

生物识别技术在汽车领域带来了巨大变革

智能汽车时代 2022年10月28日&#xff0c;工信部发布《道路机动车辆生产准入许可管理条例&#xff08;征求意见稿&#xff09;》&#xff08;“《准入管理条例草案》”&#xff09;。包含了更全面的汽车准入管理规定&#xff0c;同时较为系统地增加了针对智能汽车的准入管理规定…

更简单的读取和存储对象

在上一篇文章中我们已经介绍在XML文件注册Bean的具体步骤,这一篇文章将会介绍使用更加简洁的方式(使用注解)来存储和读取Bean.这也是最常用的方法. 1. 创建并配置好Spring项目 和上一篇的步骤相同,下面就相当于复习如何创建Spring项目吧 创建一个 Maven 项目为 Spring 项目…

微信小程序 | 酷炫时钟样式整理【附源码】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …