diff --git a/docs/doc/apparch/kong.md b/docs/doc/apparch/kong.md index 4078b33..922bdfb 100644 --- a/docs/doc/apparch/kong.md +++ b/docs/doc/apparch/kong.md @@ -1 +1,310 @@ -# 控 \ No newline at end of file +# 控 + +工业生产中控制逻辑的复杂程度千变万化,往往需要具体行业专业人员完成专门的设计,从而提高了行业的技术壁垒,严重阻碍了工业领域的自动化和智能化升级。 + +XiUOS应用程序框架中的“控”子框架从“控制需求”本身出发,同时面向专业用户和非专业用户,通过对“控制需求”本身和复杂的工业控制流程进行深入分析,通过软件定义的方式,提出以“元控制”为核心的“控”制流程。具体地,本设计通过解耦复杂的工业控制流程,将工业生产中的各种复制工业控制流程分解为各种类型的“元控制”命令,这些“元控制”的命令以软件API的形式交互给用户使用,从而屏蔽了以PLC为中心的各种控制器的巨大差异,形成了方便易用的接口,降低了专业的技术壁垒,加速了工业领域的智能化升级。 + +## 1. XiUOS“控”制框架的关键数据结构定义和解析 + +```c +struct xs_PlcAbility { + const char name[XS_NAME_MAX]; /* name of the plc ability instance */ + enum xs_PlcCtlType type; /* type of control the plcable to excute, such as HSC or PID control */ + char address[XS_PLC_ADDRESS_MAX]; /* The address for this function in the plc*/ + struct xs_PlcDevice *pdev;/* corresponding plc device */ + struct XS_DOUBLE_LINKLIST_NODE link;/* link list node */ +}; +``` + +name成员是一个可读的名字,用于唯一标识一个xs_PlcAbility结构。 +type成员表示该xs_PlcAbility可控制的类型,用一个枚举变量表示: + +```c +enum xs_PLcCtlType { + XS_PLC_CONTROL_TYPE_HSC = 0, /* high-speed counter */ + XS_PLC_CONTROL_TYPE_PID , /* proportional,integral,derivative */ + XS_PLC_CONTROL_TYPE_PHASING , /* phasing control*/ + + /* ...... */ + XS_PLC_CONTROL_TYPE_END, +}; +``` + +由于plc里控制指令执行都是向数据模块DB写数据,需要知道该函数功能对应的数据块地址,这个用address标识。 + +pdev成员表示该xs_PlcAbility所属的xs_PlcDevice结构,其具体定义在下文给出。 + +最后,在系统中不同控制类型的xs_PlcAbility被分别组织成不同的双链表,如高速计数器xs_PlcAbility链表、PID控制xs_PlcAbility链表等,使用的链表节点即为link成员。 + +```c +struct xs_PlcDevice { + const char name[XS_NAME_MAX]; /* name of the device */ + struct xs_PlcInfo info;/* Plc info, such as vendor name and model name */ + struct xs_PlcOps ops;/* filesystem-like APIs for data transferring */ + struct xs_PlcInterface interface;/* protocls used for transferring data from program to plc */ + structXS_DOUBLE_LINKLIST_NODE link;/* link list node */ +}; +``` + +name成员记录PLC设备在系统中的名字,用于唯一标识一个xs_PlcDevice结构。 + +```c +structxs_PlcInfo { + uint32_t ability; + const char *vendor; + const char *product_model; +}; +``` + +info成员记录PLC设备的一些属性信息,包括PLC的能力ability、厂家名vendor与型号product_model,其中ability用一个位图表示该PLC设备可以控制进行的操作: + +```c +#define XS_PLC_ABILITY_HSC ((uint32_t)(1 << XS_CONTROL_TYPE_HSC)) +#define XS_PLC_ABILITY_PID ((uint32_t)(1 << XS_CONTROL_TYPE_PID)) +#define XS_PLC_ABILITY_Phasing ((uint32_t)(1 << XS_CONTROL_TYPE_PHASING)) +/* ...... */ +``` + +ps成员包含统一的、类似文件系统的API,用于和PLC设备通信,进行实际的数据读写和对PLC设备实现控制功能。在使用一个PLC设备前后需要打开(open)/关闭(close)该PLC,实际为建立和关闭连接;read、write分别用与从PLC接收数据与向PLC发送数据,ioctl用于向PLC设备发送控制指令: + +```c +structxs_PlcOps { + int (*open)(struct xs_PlcDevice *pdev); + void (*close)(struct xs_PlcDevice*pdev); + int (*read)(struct xs_PlcDevice*pdev, void *buf, size_tlen); + int (*write)(struct xs_PlcDevice*pdev, constvoid *buf, size_tlen); + int (*ioctl)(struct xs_PlcDevice*pdev, intcmd, void *arg); +}; +``` + +interface成员表示用于与PLC进行通信时所用到的协议: + +```c +struct xs_PlcInterface { +enum xs_plc_protocol protocol; +enum xs_plc_transport transport; +}; +``` + +xs_plc_protocol和xs_plc_transport是两个枚举类型,标识PLC设备和自研的两种终端之间用到的通讯协议,其定义如下: + +```c +enum xs_plc_protocol{ + AB-ETH = 0, + ADS/AMS, + BACnet/IP, + DeltaV, + DF1, + EtherNet/IP, + Firmata, + KNXnet/IP, + Modbus, + OPC UA, + S7, + Simulated, +}; +enum xs_plc_transport{ + TCP = 0, + UDP, + Serial, + Raw Socket, + PCAP Replay, +}; +``` + +:::tip +注意两者间有对应关系而不是随意组合,如S7(STEP 7)只能采用TCP协议,而Modbus支持tcp/serial/raw socket/pcap replay,可以定义一个函数检查类型: +xs_PlcProtocolCheck(struct xs_PlcDevice*); +::: + +最后,系统中所有注册过的PLC设备被组织成一个双链表,即link成员。 + +## 2.XiUOS Plc控制框架驱动开发 + +以HSC高速计数器为例。控制框架针对每个具体的控制类型将xs_PlcAbility进行扩充,采用类似面向对象的手段添加其他必要成员,如: + +```c +struct xs_PlcAbilityHsc { + struct xs_PlcAbility parent;/* inherit from xs_PlcAbility*/ + uint32_t (*write)(struct xs_PlcAbilityHsc *abl, void* param); + void (*read)(struct xs_PlcAbilityHsc *abl, void* param) +}; +``` + +实现xs_PlcOps中的数据通信API,具体实现细节取决于PLC型号,无法实现的API可以置为NULL: + +```c +structxs_PlcOpshsc_example_ops = { + .open = hsc_example_open; + .close = hsc_example_close; + .read = hsc_example_read; + .write = hsc_example_write; + .ioctl = hsc_example_ioctl; +}; +``` + +实现xs_PlcAbilityHsc中的write和read接口,该接口用于向PLC的HSC模块发送控制参数和读取返回参数。在实现过程中可以使用xs_PlcOps中的接口与plc进行通信。 + +其中param为一个void类型指针,由于要写入的命令参数和要读取的返回参数往往不止一个,可以根据不同的控制类型定义不同的数据读写结构体,最后使用结构体指针进行强制类型转换即可。例如对于HSC: +定义数据写入结构体: + +```c +struct hsc_write{ + char id[id_max]; /* id_max is to identify the max HSC ctrl instruction of HSC */ + int cmd; /* HSC control type * +}; +``` + +其中id用来标识HSC, cmd表示要发送的具体指令,具体的指令可以用一个枚举类型来表示: + +```c +enum HscCmdSubType{ + EnHsc = 0; /* enable HSC */ + EnCapture; /* enable the function of capturing the inputs */ + EnSync; /* enable the function of synchronous inputs */ + EnHSCRESET + + En_CTRL_UPTODOWN + + ... ... +}; +``` + +同理,其他的类型 + +```c +enum PIDcmdSubType{ + ENHsc = 0 + EN_XS_PLC_CONTROL_TYPE_PID_COMPACT + XS_PLC_CONTROL_TYPE_PID_3STEP /* valve of motor-driven */ + ... ... +}; +enum PHASEcmdSubType{ + ENPhase = 0 + XS_PLC_CONTROL_TYPE_PHASING_MC_HALT + XS_PLC_CONTROL_TYPE_PHASING_MC_HALT_MOVE_ABSOLUTE + ... ... +} +``` + +定义数据输出结构体: + +```c +struct hsc_read{ + uint32_t value; /* the main value you want to get, depends on the cmd */ + bool done; /* indicate whether the SFB has been done */ + bool busy; /* indicate whether the function is busy */ + bool error; /* indicate whether there is error */ +}; +``` + +value是指定返回的变量值,与结构体hsc_write发送的指令cmd类型有关,down/busy/error是一些状态指示位。 +如我们要使用write函数向PLC发送HSC类型的控制指令: + +```c +struct hsc_write write_example; +``` + +struct xs_PlcAbilityHsc hsc_example进行必要的初始化后,用强制类型转换作为参数传递给write函数: +```c +hsc_example ->write(&hsc_example,(struct hsc_write*)&write_example); +``` +最后,将plc设备添加到plc框架。分别填充xs_PlcDevice与对应物理量的xs_PlcAbility结构(高速计数器即为xs_PlcAbilityHsc),并依次使用xs_PlcDeviceRegister和xs_PlcAbilityRegister函数将其注册到plc框架: + +```c +int xs_PlcDeviceRegister (struct xs_PlcDeviceRegister *pdev); +int xs_PlcAbilityRegister (struct xs_PlcAbilityRegister *abl); + +extern struct xs_PlcOps plc_example_ops; +extern uint32_t plc_example_write(struct xs_PlcAbilityHsc *abl, void *param); +extern void plc_example_read(struct xs_PlcAbilityHsc *abl, void *param); + +/* declare xs_PlcDevice and xs_PlcAbilityHsc objects */ +struct xs_PlcDevice hsc_example_pdev; +struct xs_PlcAbilityHsc hsc_example_abl; + +void register_example_plc() +{ + /* initialize and register the xs_PlcDevice object */ + memset(&hsc_example_pdev, 0, sizeof(xs_PlcDevice)); + + hsc_example_pdev.name = "plc1"; + hsc_example_pdev.info.ability |= XS_PLC_ABILITY_HSC; + hsc_example_pdev.info.vendor = "Siemens"; + hsc_example_pdev.info.product_model = "yyy"; + hsc_example_pdev.ops = &hsc_example_ops; + hsc_example_pdev.interface.xs_plc_protocol = S7; + hsc_example_pdev.interface.xs_plc_transport = TCP; + + xs_PlcDeviceRegister(&hsc_example_pdev); + + /* initialize and register the xs_PlcAbility of hsc object */ + memset(&hsc_example_abl, 0, sizeof(xs_PlcAbilityHsc)); + + hsc_example_abl.parent.name = "hsc_1"; + hsc_example_abl.parent.type = XS_PLC_TYPE_HSC; + hsc_example_abl.parent.pdev = &hsc_example_pdev; + hsc_example_abl.read = hsc_example_read; + hsc_example_abl.write = hsc_example_write; + + xs_PlcAbilityRegister((struct xs_PlcAbility *)&hsc_example_abl); + /* initialize and register otherxs_PlcAbilityobject */ + memset(&other_example_abl, 0, sizeof(xs_PlcAbilityOther)); + ... ... +} +``` + +## 3. XiUOS Plc控制框架的使用示例 +PLC控制应用开发者使用PLC控制框架提供的API操作PLC,PLC的API可以分为通用API与控制类型特有API。通用API用于PLC的获取、打开与关闭,控制类型特有API用于不同种类PLC的不同控制指令。以具有HSC高速计时器功能的PLC为例: + +```c +/* generic API: find a plcability instance by its name */ +struct xs_PlcAbility *xs_PlcAbilityFind(const char *name); + +/* generic API: open/close a plc ability instance */ +int xs_PlcAbilityOpen(struct xs_PlcAbility *abl); +void xs_PlcAbilityClose(struct xs_PlcAbility *abl); + +/* HSC API: send control parameter to PLC and read HSC condition*/ +uint32_t xs_PlcHscWrite(struct xs_PlcAbilityHsc *abl, void *param); +void xs_PlcHscRead(struct xs_PlcAbilityHsc *abl, void *param); +``` + +在发送命令和获取数据前,需要先获取并打开要使用的PLC;PLC打开后可以随时对PLC发送指令和对其数据进行读取;使用完毕后,须关闭PLC。完整的使用过程示例如下: + +```c +int main(int argc, char *argv[]) +{ + int ret; + structxs_PlcAbility *abl; + structxs_PlcAbilityHsc *hsc_abl; + + /* get the Plc hsc ability instance */ + abl = xs_PlcAbilityFind("hsc_1"); + XS_ASSERT(abl->type == XS_PLC_CONTROL_TYPE_HSC); + + /* open the Plc hscability instance */ + hsc_abl = (struct xs_PlcAbilityHsc*)abl; + ret = xs_PlcAbilityOpen(abl); + XS_ASSERT(ret == XS_EOK); + + /* initialize the write and read data structure */ + structhsc_write write1={ + .id = "xxx"; + .cmd = EnHsc; + }; + struct hsc_read read1; + /* send control param to the HSC, just for demonstration */ + xs_PlcHscWrite(hsc_abl,write1); + /* read data from hsc, just for demonstration */ + xs_PlcHscRead(hsc_abl,read1); + if(!read1.error) + xs_kprintf("Read data from PLC HSC is \n", read1.value); + else + xs_kprintf("Read data from PLC HSC wrong!\n") + xs_PlcAbilityClose(abl); + + return 0; +} +``` +