// propertyMapper.js - 阿里云物模型数据映射
|
const logger = require('./logger');
|
const fs = require('fs');
|
const path = require('path');
|
|
class PropertyMapper {
|
constructor() {
|
// 从文件加载物模型,或使用内置配置
|
this.schema = this.loadSchema();
|
this.propertyMap = this.buildPropertyMap();
|
}
|
|
/**
|
* 加载物模型定义
|
*/
|
loadSchema() {
|
try {
|
// 尝试从 schema.json 文件加载
|
const schemaPath = path.join(__dirname, 'schema.json');
|
if (fs.existsSync(schemaPath)) {
|
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
logger.info('✅ 已从 schema.json 加载物模型');
|
return JSON.parse(schemaContent);
|
}
|
} catch (err) {
|
logger.warn(`加载 schema.json 失败: ${err.message}`);
|
}
|
|
// 返回默认配置
|
logger.info('ℹ️ 使用默认物模型配置');
|
return this.getDefaultSchema();
|
}
|
|
/**
|
* 获取默认物模型(完整版 - 与schema.json同步)
|
*/
|
getDefaultSchema() {
|
return {
|
properties: [
|
{ identifier: 'A', name: '脱水目标量' },
|
{ identifier: 'B', name: '脱水量' },
|
{ identifier: 'C', name: '脱水速率' },
|
{ identifier: 'D', name: '血液流速' },
|
{ identifier: 'E', name: '肝素速率' },
|
{ identifier: 'F', name: '透析液温度' },
|
{ identifier: 'G', name: '透析液电导度' },
|
{ identifier: 'H', name: '静脉压' },
|
{ identifier: 'I', name: '透析液压' },
|
{ identifier: 'J', name: '跨膜压' },
|
{ identifier: 'K', name: '透析时间' },
|
{ identifier: 'a', name: '透析液温度报警' },
|
{ identifier: 'b', name: '电导度报警' },
|
{ identifier: 'c', name: '静脉压报警' },
|
{ identifier: 'd', name: '透析液压力报警' },
|
{ identifier: 'e', name: '跨膜压警报' },
|
{ identifier: 'f', name: '气泡侦测器警报' },
|
{ identifier: 'g', name: '漏血报警' },
|
{ identifier: 'h', name: '其他报警' },
|
{ identifier: 'L', name: '透析液流速' },
|
{ identifier: 'M', name: 'BPM监测时间' },
|
{ identifier: 'N', name: 'BPM最高血压' },
|
{ identifier: 'O', name: 'BPM最低血压' },
|
{ identifier: 'P', name: 'BPM脉冲' },
|
{ identifier: 'Q', name: '收缩压上限' },
|
{ identifier: 'R', name: '收缩压下限' },
|
{ identifier: 'S', name: 'BPM压脉带压力' },
|
{ identifier: 'T', name: 'BPM检测间隔时间' },
|
{ identifier: 'U', name: '血流总量' },
|
{ identifier: 'V', name: '静脉压上限报警' },
|
{ identifier: 'W', name: '静脉压下限报警' },
|
{ identifier: 'X', name: '肝素总量' },
|
{ identifier: 'Y', name: '透析液压上限报警' },
|
{ identifier: 'Z', name: '透析液压下限警报' },
|
{ identifier: 'i', name: '单补钠个性化程序' },
|
{ identifier: 'j', name: '脱水个性化程序' },
|
{ identifier: 'k', name: '透析液选择' },
|
{ identifier: 'l', name: '电导度档位' },
|
{ identifier: 'm', name: '数据通信状态' },
|
{ identifier: 'n', name: '序列号' },
|
{ identifier: 'o', name: '动脉压' },
|
{ identifier: 'p', name: '动脉压警报' },
|
{ identifier: 'q', name: '动脉压上限警报' },
|
{ identifier: 'r', name: '动脉压下限警报' },
|
{ identifier: 's', name: '跨膜压上限警报' },
|
{ identifier: 't', name: '跨膜压下限警报' },
|
{ identifier: 'u', name: '置换液速率' },
|
{ identifier: 'v', name: '置换液目标量' },
|
{ identifier: 'w', name: '置换液进程量' },
|
{ identifier: 'x', name: '电导度个性化程序' },
|
{ identifier: 'y', name: '血液流速个性化程序' },
|
{ identifier: 'z', name: '肝素个性化程序' },
|
{ identifier: 'C53', name: '透析液个性化程序' },
|
{ identifier: 'C54', name: '透析液温度初始设置' },
|
{ identifier: 'C55', name: '缺水2警报' },
|
{ identifier: 'suedtime', name: '传输时间' },
|
{ identifier: 'deviceType', name: 'deviceType' },
|
{ identifier: 'IPAddress', name: 'IP地址' },
|
{ identifier: 'deviceName', name: '设备名称' },
|
{ identifier: 'warn', name: '警告' },
|
{ identifier: 'ICCID', name: 'iccid' },
|
{ identifier: 'mb', name: '脉搏-德朗' },
|
{ identifier: 'szy', name: '舒张压-德朗' },
|
{ identifier: 'ssy', name: '收缩压-德朗' },
|
{ identifier: 'sysj', name: '剩余时间-德朗' },
|
{ identifier: 'bjbh', name: '报警编号-德朗' },
|
{ identifier: 'xlllsd', name: '血泵流量设定-德朗' }
|
]
|
};
|
}
|
|
/**
|
* 构建属性映射表 (identifier -> name)
|
*/
|
buildPropertyMap() {
|
const map = {};
|
if (this.schema.properties && Array.isArray(this.schema.properties)) {
|
this.schema.properties.forEach(prop => {
|
map[prop.identifier] = prop.name;
|
});
|
}
|
return map;
|
}
|
|
/**
|
* 获取属性名称
|
* @param {string} identifier - 属性标识符
|
* @returns {string} 属性名称
|
*/
|
getPropertyName(identifier) {
|
return this.propertyMap[identifier] || identifier;
|
}
|
|
/**
|
* 将原始数据转换为标准格式
|
* @param {object} rawData - 原始数据对象
|
* @returns {array} 转换后的数据数组
|
*/
|
mapData(rawData) {
|
if (!rawData || typeof rawData !== 'object') {
|
logger.warn('输入数据无效');
|
return [];
|
}
|
|
const mappedData = [];
|
|
for (const [key, value] of Object.entries(rawData)) {
|
// 跳过内部字段
|
if (key.startsWith('_')) {
|
continue;
|
}
|
|
// 获取属性名称
|
const name = this.getPropertyName(key);
|
|
mappedData.push({
|
identifier: key,
|
name: name,
|
value: value
|
});
|
}
|
|
return mappedData;
|
}
|
|
/**
|
* 将原始数据转换为 HTTP 响应格式
|
* @param {object} rawData - 原始数据
|
* @param {string} deviceNumber - 设备号
|
* @returns {array} 转换后的属性数组
|
*/
|
transformForHTTP(rawData, deviceNumber) {
|
const mappedData = this.mapData(rawData);
|
return mappedData;
|
}
|
|
/**
|
* 将原始数据转换为 MQTT 发布格式
|
* @param {object} rawData - 原始数据
|
* @param {string} deviceNumber - 设备号
|
* @returns {object} 转换后的数据
|
*/
|
transformForMQTT(rawData, deviceNumber) {
|
const mappedData = this.mapData(rawData);
|
|
return {
|
deviceNumber: deviceNumber,
|
deviceId: deviceNumber,
|
timestamp: new Date().toISOString(),
|
data: mappedData
|
};
|
}
|
|
/**
|
* 将原始数据转换为阿里云物联网平台格式
|
* @param {object} rawData - 原始数据
|
* @returns {object} 转换后的属性对象
|
*/
|
transformForAliyun(rawData) {
|
const props = {};
|
|
for (const [key, value] of Object.entries(rawData)) {
|
// 跳过内部字段
|
if (key.startsWith('_')) {
|
continue;
|
}
|
|
// 直接使用 identifier 作为属性
|
props[key] = value;
|
}
|
|
return props;
|
}
|
|
/**
|
* 获取所有属性定义
|
*/
|
getAllProperties() {
|
return this.schema.properties || [];
|
}
|
|
/**
|
* 获取属性定义
|
* @param {string} identifier - 属性标识符
|
* @returns {object} 属性定义
|
*/
|
getPropertyDefinition(identifier) {
|
if (!this.schema.properties) return null;
|
return this.schema.properties.find(p => p.identifier === identifier);
|
}
|
|
/**
|
* 统计属性信息
|
*/
|
getPropertyStats() {
|
const properties = this.schema.properties || [];
|
const readOnlyCount = properties.filter(p => p.accessMode === 'r').length;
|
const readWriteCount = properties.filter(p => p.accessMode === 'rw').length;
|
|
// 统计数据类型分布
|
const dataTypeDistribution = new Map();
|
const dataTypes = new Set();
|
|
properties.forEach(prop => {
|
dataTypes.add(prop.dataType);
|
dataTypeDistribution.set(
|
prop.dataType,
|
(dataTypeDistribution.get(prop.dataType) || 0) + 1
|
);
|
});
|
|
return {
|
totalProperties: properties.length,
|
readOnlyCount: readOnlyCount,
|
readWriteCount: readWriteCount,
|
dataTypes: dataTypes,
|
dataTypeDistribution: Array.from(dataTypeDistribution.entries())
|
};
|
}
|
}
|
|
// 导出单例
|
module.exports = new PropertyMapper();
|