Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ca26fd8eb1 |
|
|
@ -108,3 +108,4 @@ build/
|
|||
|
||||
*.rdb
|
||||
|
||||
.vscode
|
||||
|
|
@ -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
|
||||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue