前面几个例子,虽然抽象的味道越来越浓,但功能都比较单纯,因此接口也容易构建。本节我们面对一个稍微复杂一点的例程:信号复归。

在微机保护装置中,发生保护动作后必须进行人工干预,只有排除故障后才可以重新投入运行。为了确保这一过程,发生保护动作后,相关led状态或类似信号等都会处于自保持状态,为了清除这些状态信息,需要执行一个复归命令,称之为信号复归。

早期,信号复归主要用于清除动作led灯的状态,如我们会发现很多国外产品中信号复归写作ledRst。目前随着微机保护功能越来越多,也越来越智能化,信号复归功能已经参与到诸多保护逻辑中,为了保持逻辑上的清晰,我们团队内部习惯将信号复归直译作SignalRst。

信号复归本身功能很简单,但麻烦在于同很多模块关联耦合在一起。信号复归有多种来源,最简单的就是继保设备前面板信号复归按键,通过按键触发信号复归。但如果一面屏柜上有很多台继保,逐一按键操作也比较麻烦,此时惯例在整个屏柜上统一放置信号复归总按钮。因为该信息一般是通过开入信号传入继保设备的,因此常称之为开入信号复归。

目前很多电力系统变电站都是无人值守的,故障后还需要到现场去执行信号复归显然不可取,必须远传可控,因此又诞生了第三种信号复归入口:通讯信号复归。

汇总后,我们发现至少有三种类型入口:设备本身信号复归、开入信号复归和通讯信号复归。

信号复归不仅输入源头多,发生信号复归命令后,需要执行的功能更多,如需清除保护LED状态,收回一些自保持出口状态、清除通讯远传保护状态、关闭液晶报告弹屏界面,当然更多的是参与到各种复杂的保护逻辑中,如备自投逻辑中为下一次动作做好准备等。

◇◇◇

分析信号复归的需求,我们发现信号复归功能本身并不复杂,但麻烦在关联模块比较多。此时,会出现一个尴尬的局面,每增加一个模块,都需在信号复归模块内部做一点修改,如下图示意:


如增加新模块同信号复归输出存在关联,增加该模块后,需要在信号复归模块中修改多处位置。类似这样的情况持续积累,每增加一个模块,可能需要在多个类信号复归模块中做相应修改,各模块之间耦合开始增加。

记得早期我维护过一款产品,每增加一个规约模块后,必须同步修改的地方竟有几十处。为了让大家少犯错误,项目经理写了一份详细文档用于描述规约修改过程,甚至作为新人年底考核内容。但即使如此,依然挡不住大家持续不停的犯错误。

以前和大家提及,软件本身有强烈的耦合特性,如果不加控制,即使一开始模块分割的比较清晰,也容易在迭代中慢慢的耦合成一团乱麻,然后让新人上不了手,老人脱不开身,进而引发管理灾难。希望通过这个例子能让大家体会一二。

为了去耦合,我们期望每增加一个模块后,仅新增模块代码处发生变化,其他地方(如信号复归模块)不需要改动。如下图示意:


为了做到这一点,策略其实很简单,有两种简单且常见的策略:
1. 回调函数机制
2. 消息机制

回调函数机制常用于前后台系统中。采用回调函数机制时,需要信号复归模块提供一个注册函数接口,并在内部维护注册函数管理数组或列表,在发生信号复归命令后,依次调用所有注册接口函数,新增模块仅需在初始化时调用信号复归注册接口函数。通过简单的回调函数机制,我们可以将所有的改动集中在新增模块内部。

采用回调函数机制存在一个缺点,新增模块的接口函数是在信号复归环境中执行的,如果是前后台系统不存在问题,但基于os调度机制的执行环境中,会引入同步互斥问题。因此基于os,一般选择采用消息机制,相当于异步的回调函数调用。此时,回调函数注册变成了消息注册,回调函数调用变成了发送消息,消息函数在各任务内部执行,回避了同步互斥问题。

◇◇◇

完成信号复归的输出部分抽象后,我们再来分析信号复归的输入源抽象。

信号复归有三种输入源:设备按键、开入和远方通讯。这三种输入源中最简单的是远方通信,因为其功能固定,因此可简单的构建一接口函数让通讯规约调用即可。接口函数如下:

/* 手动触发信号复归命令 */
void apiSignalRstTrip(void);

通过按键和开入为何不能调用该接口函数触发信号复归命令呢?关键在于按键和开入是可变项。

继保设备一般在面板上有单独的信号复归按键,通过该按键即可触发信号复归命令。在一个地铁现场,用户认为这样的操作不严谨,要求增加限制条件:需按下复归和enter组合按键且持续一秒以上的时间,才可以触发信号复归命令。

碰到这个需求就比较尴尬了,做特殊工程版本吧,会导致程序版本混乱,增加额外配置选项,代码被分割,程序比较混乱。有没有更好的策略呢?实际上最佳的策略就是按键模块和信号复归模块解耦,分别提供外部可控制的接口,并用脚本连接。


微机保护行业中,最早采用这种设计理念的是北美的sel保护公司。sel保护为每个按键(包含常用组合按键)提供一输出布尔量,如signalRstKey等,为信号复归提供一输入布尔量signalRstIn,然后通过维护软件设置如下表达式:

signalRstIn = time(signalRstKey, 1000, 0);      //按键持续1000ms后才置位signalRstIn

此时,大家有没有惊奇的发现按键模块和信号复归模块完全解耦了,按键模块仅需处理signalRstKey,而信号复归模块仅需在signalRstIn置位时调用apiSignalRstTrip()即可。

类同按键,开入作为信号复归源最大的问题在于难以确定各现场使用哪路开入作为输入源。解耦后就比较简单了,建设某现场将开入5作为输入源,需编辑如下表达式:

signalRstIn = time(DI5, 300, 50);       //300ms上升沿防抖,50ms下降沿防抖,提升按键可靠性

不仅输入源可以借助signalRstIn解耦,一些信号复归输出模块(大多是保护模块)也可以借助脚本解耦,此时信号复归需要额外增加一输出布尔量signalRstOut,某保护逻辑脚本示例如下:

A=!signalRstOut*A+DI2; //开入2置位时,A信号保持置位,直到触发信号复归操作。

该处提及的关于重定义信号复归按键需求并非杜撰,是一真实现场用户需求。我们在一开始做产品时,很难预判到这样的需求,此时按键和信号复归模块大概率是强耦合在一起的(按键模块直接调用apiSignalRstTrip()函数),被认为是不可变部分。但随着需求的迭代,我们顺势完成了按键和信号复归模块的解耦。工业产品很少是静态的,都是在需求中不断迭代的,希望通过这个例子让大家理解到工业产品这个特点,以及我们应该采取的态度(绝不是hack大法)。

早期的sel保护并不支持上述time函数表达式,也没对按键进行抽象,上述表达式都是持续迭代优化后的产物。实际上,早期的sel保护仅支持布尔量和逻辑表达式,并将这些布尔量称之为继电器字(relayWord),用bit表示,将布尔表达式称之为逻辑方程。

随着需求持续迭代,我们目前不仅支持布尔量而且支持模拟量,逻辑方程优化比较大,不仅支持函数,而且支持类C语法,借助计算机术语,我们团队内部习惯将逻辑方程优化为脚本(script)了。

◇◇◇

至此,我们已经完成了整个信号复归模块的解耦,接口函数汇总如下:

/* 信号复归模块初始化 */
BOOL apiSignalRstInit(void);/* 手动触发信号复归命令 */
void apiSignalRstTrip(void);/**  Description: 注册信号复归消息*  Input: *    HANDLE hReac: 接受reac对象*    DWORD dwMsg: 消息ID*  Return: 成功返回TRUE,否则返回FALSE*/
BOOL apiSignalRstRegister(HANDLE hReac, DWORD dwMsg);输入继电器字:signalRstIn
输出继电器字:signalRstOut

◇◇◇

为了解耦按键、开入和信号复归模块,我们被迫引入了一个新的模块:脚本模块,而且还需要外部维护软件支持。此时,简单的接口抽象已经很难胜任了,需要引入一些程序设计,慢慢的开始需要架构支撑了。脚本是架构设计中很关键的一环,下一章,让我们一起携手迈入精彩纷呈的架构世界。

——————————————

返回目录

我是小马儿,一个渴望良知与灵魂的嵌入式软件工程师,欢迎您的陪伴与同行,如需最新版PDF电子书,或期望深入交流,可加我个人微信nzn_xiaomaer,需备注“异维”二字。

5.5 信号复归——一个强耦合模块的解耦过程相关推荐

  1. 1第一个Chisel模块

    本节的目的 通过上一章的学习,您应该对Scala已经有所了解,我们可以开始来看看怎么来实现硬件.Chisel的全称是嵌入在Scala中的硬件构造语言(Constructing Hardware In ...

  2. Chisel教程——02.Chisel环境配置和第一个Chisel模块的实现与测试

    Chisel环境配置和第一个Chisel模块的实现与测试 动机 现在已经对Scala有一定的了解了,可以开始构造一些硬件了.Chisel的全称为Constructing Hardware In a S ...

  3. GPUtil是一个Python模块,使用nvidia-smi从NVIDA GPU获取GPU状态

    GPUtil是一个Python模块,使用nvidia-smi从NVIDA GPU获取GPU状态 一个Python模块,用于在Python中使用nvidia-smi以编程方式从NVIDA GPU获取GP ...

  4. 每周一个 Python 模块 | time

    专栏地址:每周一个 Python 模块 几乎所有的正式代码中,我们都需要与时间打交道.在Python中,与时间处理有关的模块包括time,datetime以及calendar,本节主要讲解time模块 ...

  5. 如何在React Native中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  6. 处理程序“WebServiceHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”

    HTTP 错误 500.21 - Internal Server Error 处理程序"WebServiceHandlerFactory-Integrated"在其模块列表中有一个 ...

  7. 错误:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”

    开发web项目时需要安装IIS,在安装好IIS的Windows7本上发布asp.net网站时,web程序已经映射到了本地IIS上,但运行如下错误提示"处理程序"PageHandle ...

  8. asp.net发布到IIS中出现错误:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”...

    开发web项目时需要安装IIS,在安装好IIS的Windows7本上发布asp.net网站时,web程序已经映射到了本地IIS上,但运行如下错误提示"处理程序"PageHandle ...

  9. maven进阶:一个多模块项目

    一个多模块项目通过一个父POM 引用一个或多个子模块来定义.父项目,通过以下配置,将子项目关联. [xhtml] view plaincopy <packaging>pom</pac ...

最新文章

  1. matlab 线模式密度,环形腔窄线宽光纤激光器的研究
  2. Jquery 将表单序列化为Json对象
  3. python3 decode encode 字符串 字节 互转
  4. [YTU]_2390( 抽象一个形状类)
  5. [BZOJ 4827][Hnoi2017]礼物
  6. 上市开放式基金(LOF)
  7. 什么是IPsec协议
  8. 再次挑戰UCOSII内核源码
  9. 从soso改版说如何针对soso做优化
  10. jvisualvm工具使用
  11. WindowsX64下tftp的安装
  12. CSM (Compatility Suport Module)兼容支持模块
  13. Mysql 最全教程
  14. 一文带你了解知识图谱融入预训练模型哪家强?九大模型集中放送
  15. SQL——查询和1002号的同学学习的课程完全相同的其他同学的学号和姓名
  16. 移动端适配 - 小结
  17. win7已经阻止此发行者在您的计算机上运行软件,win7提示由于无法验证发行者所以Windows已经阻止此软件怎么办...
  18. Java基础之匿名内部类,匿名内部类是什么?为什么要用匿名内部类,匿名内部类详解。
  19. 多元线性回归和正规方程解
  20. 汝州九峰山下自产自销的好蜂蜜

热门文章

  1. 相机调整GAMMA和曝光
  2. Perforce研讨会回顾 | Helix Core在芯片行业的应用实例:芯片项目的版本控制、持续集成及自动化
  3. python数据分析要不要爬虫_数据分析师需要对爬虫掌握到什么程度?
  4. 关于用Scratch制作“打砖块”游戏时发现的问题思考及延伸
  5. 短线抄底+优化源码指标
  6. python mysql 的默认值_python--MySQL 库,表的详细操作
  7. 2,【electron+vue】 构建桌面应用——常见的功能及问题(修改桌面图标,软件图标,窗口图标,图标不显示问题,影藏默认菜单栏,开机自启,手动或被动关闭应用)
  8. thzbt.co forum.php,无线城市掌上公交
  9. 用树莓派计算模块搭建的工业单板计算机(转)
  10. 医疗信息管理系统(业务介绍)