This commit is contained in:
zhuxu 2024-08-30 09:53:12 +08:00
parent 6b0d056d25
commit ca26fd8eb1
4 changed files with 493 additions and 0 deletions

1
.gitignore vendored
View File

@ -108,3 +108,4 @@ build/
*.rdb
.vscode

104
include/moduleiec.h Normal file
View File

@ -0,0 +1,104 @@
#ifndef MODULEIEC_H
#define MODULEIEC_H
/*******************************************************************************************
*
* ASDU类型标识定义
*
*******************************************************************************************/
//1--1 监视方向的过程信息, RTU向主站上传的报文类型
#define M_SP_NA_1 0x01 //单点信息 (总召唤遥信、变位遥信)
#define M_SP_TA_1 0x02 //带时标单点信息 (SOE事项)
#define M_DP_NA_1 0x03 //双点信息
#define M_DP_TA_1 0x04 //带时标双点信息 ??标准里的定义重复, 根据PMA修订
#define M_ST_NA_1 0x05 //步位置信息
#define M_ST_TA_1 0x06 //带时标步位置信息
#define M_BO_NA_1 0x07 //32比特串
#define M_BO_TA_1 0x08 //带时标32比特串
#define M_ME_NA_1 0x09 //测量值,规一化值 (越限遥测)
#define M_ME_TA_1 0x0A //测量值,带时标规一化值
#define M_ME_NB_1 0x0B //测量值,标度化值
#define M_ME_TB_1 0x0C //测量值,带时标标度化值
#define M_ME_NC_1 0x0D //测量值,短浮点数
#define M_ME_TC_1 0x0E //测量值,带时标短浮点数
#define M_IT_NA_1 0x0F //累计量 (电度量)
#define M_IT_TA_1 0x10 //带时标累计量
#define M_EP_TA_1 0x11 //带时标继电保护装置事件
#define M_EP_TB_1 0x12 //带时标继电保护装置成组启动事件
#define M_EP_TC_1 0x13 //带时标继电保护装置成组输出电路信息
#define M_PS_NA_1 0x14 //具有状态变位检出的成组单点信息 ??标准里的定义重复, 根据PMA修订
#define M_ME_ND_1 0x15 //测量值,不带品质描述的规一化值 (总召唤遥测量)
#define M_SP_TB_1 0x1E //带时标CP56TimE2A的单点信息
#define M_DP_TB_1 0x1F //带时标CP56TimE2A的双点信息
#define M_ST_TB_1 0x20 //带时标CP56TimE2A的步位信息
#define M_BO_TB_1 0x21 //带时标CP56TimE2A的32位串
#define M_ME_TD_1 0x22 //带时标CP56TimE2A的规一化测量值
#define M_ME_TE_1 0x23 //测量值,带时标CP56TimE2A的标度化值
#define M_ME_TF_1 0x24 //测量值,带时标CP56TimE2A的短浮点数
#define M_IT_TB_1 0x25 //带时标CP56TimE2A的累计值
#define M_EP_TD_1 0x26 //带时标CP56TimE2A的继电保护装置事件
#define M_EP_TE_1 0x27 //带时标CP56TimE2A的成组继电保护装置成组启动事件
#define M_EP_TF_1 0x28 //带时标CP56TimE2A的继电保护装置成组输出电路信息
//1--2 在监视方向的系统信息, RTU向主站上传的报文类型
#define M_EI_NA_1 0x46 //初始化结束
//2--1 在控制方向的过程信息, RTU须逐条对命令用相同报文确认
#define C_SC_NA_1 0x2D //单命令 (遥控)
#define C_DC_NA_1 0x2E //双命令 (遥控)
#define C_RC_NA_1 0x2F //升降命令
#define C_SE_NA_1 0x30 //设定值命令,规一化值 (遥调)
#define C_SE_NB_1 0x31 //设定值命令,标度化值
#define C_SE_NC_1 0x32 //设定值命令,短浮点数
#define C_BO_NA_1 0x33 //32比特串
#define C_SC_TA_1 0x3A //带时标CP56TimE2A的单命令
#define C_DC_TA_1 0x3B //带时标CP56TimE2A的双命令
#define C_RC_TA_1 0x3C //带时标CP56TimE2A的升降命令
#define C_SE_TA_1 0x3D //带时标CP56TimE2A的设定值命令,规一化值
#define C_SE_TB_1 0x3E //带时标CP56TimE2A的设定值命令,标度化值
#define C_SE_TC_1 0x3F //带时标CP56TimE2A的设定值命令,短浮点数
#define C_BO_TA_1 0x40 //带时标CP56TimE2A的32比特串
//2--2 在控制方向的系统信息, RTU须逐条形成镜像报文
#define C_IC_NA_1 0x64 //总召唤命令 (总召唤)
#define C_CI_NA_1 0x65 //电能脉冲召唤命令 (召唤电度量)
#define C_RD_NA_1 0x66 //读命令
#define C_CS_NA_1 0x67 //时钟同步命令 (校时)
#define C_TS_NA_1 0x68 //测试命令
#define C_RP_NA_1 0x69 //复位进程命令
#define C_CD_NA_1 0x6A //延时传输命令
#define C_TS_TA_1 0x6B //带时标CP56TimE2A的测试命令
//2--3 在控制方向的参数命令
#define P_ME_NA_1 0x6E //测量值参数,规一化值
#define P_ME_NB_1 0x6F //测量值参数,标度化值
#define P_ME_NC_1 0x70 //测量值参数,短浮点数
#define P_AC_NA_1 0x71 //参数激活
//3--1 文件传输
#define F_FR_NA_1 0x78 //文件准备好
#define F_SR_NA_1 0x79 //节已准备好
#define F_SC_NA_1 0x7A //召唤目录,选择文件,召唤文件,召唤节
#define F_LS_NA_1 0x7B //最后的节,最后的度
#define F_AF_NA_1 0x7C //确认文件,确认节
#define F_SG_NA_1 0x7D //段
#define F_DR_TA_1 0x7E //目录
#define PUB_IDX_COUNT 128
#define INFO_IDX_COUNT 26112 // 0x6600
#define INFO_FIELD_TYPE "type"
#define INFO_FIELD_PUB_IDX "pubidx"
#define INFO_FIELD_INFO_IDX "infoidx"
#define VAR_IDX_COUNT 3
typedef struct Info{
char *cause; // 0: 2 byte
char *info; // 1: 5 byte
char *tms; // 2: 7 byte
} Info;
#endif

138
source/deconstruct.c Normal file
View File

@ -0,0 +1,138 @@
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "../include/redismodule.h"
#include "../include/moduleiec.h"
extern Idx2Infos;
uint16_t getLeInt16(const uint8_t *bytes) {
return (bytes[1] << 8) | bytes[0];
}
uint32_t getLeInt32(const uint8_t *bytes) {
return (bytes[3]<<24 | bytes[2]<<16 | bytes[1]<<8 | bytes[0]);
}
void getLeUint16Bytes(char *bytes, uint16_t uint16) {
bytes[0] = (char)uint16;
bytes[1] = (char)(uint16>>8);
}
void updateTimeBytes(Info *info, char *begin) {
if (info->tms == NULL) {
info->tms = (char *)malloc(8 *sizeof(char));
}
if (begin == NULL) {
struct timeb tms;
ftime(&tms);
time_t ts;
time(&ts);
struct tm *datetime = localtime(&ts);
char smsBytes[2] = {0};
getLeUint16Bytes(smsBytes, tms.millitm+1000*datetime->tm_sec);
info->tms[0] = smsBytes[0];
info->tms[1] = smsBytes[1];
info->tms[2] = (char)datetime->tm_min;
info->tms[3] = (char)datetime->tm_hour;
info->tms[4] = (char)datetime->tm_mday;
info->tms[5] = (char)datetime->tm_mon;
info->tms[6] = (char)(datetime->tm_year - 2000);
}else{
info->tms[0] = begin[0];
info->tms[1] = begin[1];
info->tms[2] = begin[2];
info->tms[3] = begin[3];
info->tms[4] = begin[4];
info->tms[5] = begin[5];
info->tms[6] = begin[6];
}
info->tms[7] = '\0';
}
void updateInfoBytes(Info *info, char *begin, int length) {
if (info->info == NULL) {
info->info = (char *)malloc(6 *sizeof(char));
}
for (int i = 0;i < length;i++) {
info->info[i] = begin[i];
}
info->info[length] = '\0';
}
void updateCauseBytes(Info *info, char *begin) {
if (info->cause == NULL) {
info->cause = (char *)malloc(3 *sizeof(char));
}
info->cause[0] = begin[0];
info->cause[1] = begin[1];
info->cause[2] = '\0';
}
// 轻解构 ASDU
int updateInfos(const char *src) {
if (src == NULL || strlen(src) < 10) { // TODO ASDU最小有效长度 10
return 0;
}
char pubAddrBytes[2] = {src[4],src[5]};
int pubIdx = parseLeInt16(pubAddrBytes);
if (Idx2Infos[pubIdx] == NULL) {
return 0;
}
char type = src[0];
bool sequence = src[1] & 128 >> 7 == 1;
int count = src[1];
if (sequence) {
count = src[1] - 1 << 7;
}
int infoIdx = 0;
if (sequence) {
char firstAddrBytes[4] = {src[6],src[7],src[8],0x00};
infoIdx = parseLeInt32(firstAddrBytes);
}
for (int i = 0;i < count; i++) {
int size;
int infoBegin;
switch (type) {
case M_SP_NA_1:
case M_DP_NA_1:
size = 4;
infoBegin = src + 9 + i;
if (!sequence) {
char addrBytes[4] = {src[6 + i*size], src[6 + i*size + 1], src[6 + i*size + 2], 0x00};
infoIdx = parseLeInt32(addrBytes);
infoBegin = src + 6 + i*size + 3;
}
updateTimeBytes(Idx2Infos[pubIdx][infoIdx], NULL);
updateInfoBytes(Idx2Infos[pubIdx][infoIdx], infoBegin, 1);
updateCauseBytes(Idx2Infos[pubIdx][infoIdx], src+2);
break;
case M_ME_NA_1:
default:
return 0;
}
if (sequence) {
infoIdx++;
}
}
}

250
source/moduleiec.c Normal file
View File

@ -0,0 +1,250 @@
/*
** ASDU
**
**
** Key: info_<PubIdx>_<InfoIdx>
** Field: ···
**
**
** PubIdx-->InfoIdx-->Info(bytes)
*/
#include <string.h>
#include "../include/redismodule.h"
#include "../include/moduleiec.h"
Info ***Idx2Infos;
int updateInfos(const char * str);
int IECSetVar(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 校验参数
if (argc < 1+1) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
// 处理参数
for (int i = 1; i < argc; i++) {
// 轻解构 --> Info *info
if (updateInfos(argv[i]) == 0) {
return REDISMODULE_ERR;
}
}
RedisModule_ReplyWithLongLong(ctx, argc);
return REDISMODULE_OK;
}
//
int IECGetAll(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 校验参数: 公共地址 + 信息体地址/偏移量
if (argc < 1+2) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
// 初始化处理参数
long long pubIdx = -1;
long long infoIdx = -1;
RedisModule_StringToLongLong(argv[1], &pubIdx);
if (pubIdx < 0 || pubIdx >= PUB_IDX_COUNT) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_StringToLongLong(argv[2], &infoIdx);
if (infoIdx < 0 || infoIdx >= INFO_IDX_COUNT) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
// 处理计算数据
struct Info **infos = Idx2Infos[pubIdx];
if (infos == NULL) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
struct Info *info = infos[infoIdx];
if (info == NULL) {
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
}
//
char key[128];
sprintf(key, "info_%d_%d", pubIdx, infoIdx); // TODO
RedisModuleCallReply *reply = RedisModule_Call(ctx, "HGETALL", "s", key);
// 检查回复类型
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) {
// 错误处理...
}
// 回复数据
RedisModule_ReplyWithArray(ctx, VAR_IDX_COUNT); // TODO
RedisModule_ReplyWithString(ctx,info->cause);
RedisModule_ReplyWithString(ctx,info->info);
RedisModule_ReplyWithString(ctx,info->tms);
// 遍历回复中的每一对键值
for (size_t i = 0; i < RedisModule_CallReplyLength(reply); i += 2) {
RedisModuleCallReply *keyReply = RedisModule_CallReplyArrayElement(reply, i);
RedisModuleCallReply *valueReply = RedisModule_CallReplyArrayElement(reply, i + 1);
// 获取键和值
RedisModuleString *keyStr = RedisModule_CreateStringFromCallReply(keyReply);
RedisModuleString *valueStr = RedisModule_CreateStringFromCallReply(valueReply);
// 使用键和值...
// 释放字符串对象
RedisModule_FreeString(ctx, keyStr);
RedisModule_FreeString(ctx, valueStr);
}
// 释放回复对象
RedisModule_FreeCallReply(reply);
return REDISMODULE_OK;
}
//
int IECGetVar(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 校验参数: 公共地址 + 信息体地址/偏移量 + field
if (argc < 1+2) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
// 初始化处理参数
long long pubIdx = -1;
long long infoIdx = -1;
long long fieldIdx = -1;
RedisModule_StringToLongLong(argv[1], &pubIdx);
if (pubIdx < 0 || pubIdx >= PUB_IDX_COUNT) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_StringToLongLong(argv[2], &infoIdx);
if (infoIdx < 0 || infoIdx >= INFO_IDX_COUNT) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
if (argc > 1+2) {
RedisModule_StringToLongLong(argv[3], &fieldIdx);
if (fieldIdx < 0 || fieldIdx > VAR_IDX_COUNT) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
}
// 处理计算数据
struct Info **infos = Idx2Infos[pubIdx];
if (infos == NULL) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
struct Info *info = infos[infoIdx];
if (info == NULL) {
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
}
// 回复数据
if (argc == 1+2) {
RedisModule_ReplyWithArray(ctx, VAR_IDX_COUNT);
RedisModule_ReplyWithString(ctx,info->cause);
RedisModule_ReplyWithString(ctx,info->info);
RedisModule_ReplyWithString(ctx,info->tms);
}else if (argc > 1+2) {
switch (fieldIdx) {
case 0:
RedisModule_ReplyWithArray(ctx, 1);
RedisModule_ReplyWithString(ctx, info->cause);
break;
case 1:
RedisModule_ReplyWithArray(ctx, 1);
RedisModule_ReplyWithString(ctx, info->info);
break;
case 2:
RedisModule_ReplyWithArray(ctx, 1);
RedisModule_ReplyWithString(ctx, info->tms);
break;
default:
RedisModule_ReplyWithNull(ctx);
}
}
// 返回
return REDISMODULE_OK;
}
// 启动参数:缓冲区编码(默认128全初始化)
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
int pubIdxCount = PUB_IDX_COUNT;
int initCount = PUB_IDX_COUNT;
if (argc == 1) {
unsigned long long index;
RedisModule_StringToULongLong(argv[0], &index);
if (index > PUB_IDX_COUNT) {
pubIdxCount = index;
initCount = index;
}
}
if (argc > 0 && argc < PUB_IDX_COUNT) {
initCount = argc;
}
Idx2Infos = (Info ***)malloc(pubIdxCount * sizeof(Info **));
if (Idx2Infos == NULL) {
return REDISMODULE_ERR;
}
memset(Idx2Infos, 0, pubIdxCount);
for (int i = 0; i < initCount; i++) {
Info **infos = (Info **)malloc(INFO_IDX_COUNT*sizeof(Info *));
if (infos == NULL) {
return REDISMODULE_ERR;
}
memset(infos, 0, INFO_IDX_COUNT);
for (int j = 0;j < INFO_IDX_COUNT; j++) {
infos[j] = (Info *)malloc(sizeof(Info));
memset(infos[j], 0, sizeof(Info));
}
if (initCount < pubIdxCount) {
unsigned long long index;
RedisModule_StringToULongLong(argv[i], &index);
if (index < 0 || index > PUB_IDX_COUNT) {
return REDISMODULE_ERR;
}
Idx2Infos[index] = infos;
}else{
Idx2Infos[i] = infos;
}
}
const char *module = "moduleiec";
const char *cmdSetVar = "iec.setvar";
const char *cmdGetAll = "iec.getall";
const char *cmdGetVar = "iec.getvar";
if (RedisModule_Init(ctx, module, 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, cmdSetVar, IECSetVar, "", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, cmdGetAll, IECGetAll, "", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, cmdGetVar, IECGetVar, "", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}