node-red-contrib-huawei-solar
Version:
Node-RED nodes for reading data from Huawei SmartLogger 3000 and SUN2000 inverters via Modbus TCP
201 lines (166 loc) • 8.71 kB
JavaScript
const { HuaweiModbusClient } = require('./utils/modbus-utils');
const { SmartLoggerFunctions } = require('./utils/smartlogger-functions');
module.exports = function(RED) {
function SmartLoggerNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.on('input', async function(msg) {
try {
const operation = config.operation || 'readData';
const host = config.host;
const port = config.port || 502;
const unitId = config.unitId || 3;
const timeout = config.timeout || 5000;
const retries = config.retries || 3;
const namingConvention = config.namingConvention || 'descriptive';
const useIEC = namingConvention === 'iec61850';
// Build data categories array from checkboxes
const dataCategories = [];
if (config.dataSystem) dataCategories.push('system');
if (config.dataPower) dataCategories.push('power');
if (config.dataEnvironmental) dataCategories.push('environmental');
if (config.dataAlarms) dataCategories.push('alarms');
// Ensure at least one category is selected
if (dataCategories.length === 0) {
dataCategories.push('power'); // Default fallback
}
// Create Modbus client configuration
const modbusConfig = {
host,
port,
unitId,
timeout,
retries,
};
// Initialize Modbus client and SmartLogger functions
const modbusClient = new HuaweiModbusClient(modbusConfig);
const smartLogger = new SmartLoggerFunctions(modbusClient, unitId);
let responseData = {};
try {
// Set node status to connecting
node.status({ fill: "yellow", shape: "ring", text: "connecting" });
// Connect to the device
const connected = await modbusClient.connect();
if (!connected) {
throw new Error(`Failed to connect to SmartLogger at ${host}:${port}`);
}
node.status({ fill: "green", shape: "dot", text: "connected" });
// Execute the requested operation
switch (operation) {
case 'readData':
responseData = {};
// Read selected data categories in parallel
const promises = [];
const categories = [];
if (dataCategories.includes('system')) {
promises.push(smartLogger.readSystemData(useIEC));
categories.push('system');
}
if (dataCategories.includes('power')) {
promises.push(smartLogger.readPowerData(useIEC));
categories.push('power');
}
if (dataCategories.includes('environmental')) {
promises.push(smartLogger.readEnvironmentalData(useIEC));
categories.push('environmental');
}
if (dataCategories.includes('alarms')) {
promises.push(smartLogger.readAlarmData(useIEC));
categories.push('alarms');
}
const results = await Promise.all(promises);
// Map results back to their categories
for (let i = 0; i < categories.length; i++) {
responseData[categories[i]] = results[i];
}
break;
case 'discoverDevices':
const discoveryRange = config.discoveryRange || '1-247';
const discoveryTimeout = config.discoveryTimeout || 2000;
const parallelScans = config.parallelScans || 10;
// Parse the discovery range
const unitIds = parseDiscoveryRange(discoveryRange);
// Create a separate client config for discovery with shorter timeout
const discoveryConfig = {
...modbusConfig,
timeout: discoveryTimeout,
retries: 1 // Fewer retries for discovery
};
const discoveryClient = new HuaweiModbusClient(discoveryConfig);
const discoveryLogger = new SmartLoggerFunctions(discoveryClient, unitId);
try {
responseData.allDevices = await discoveryLogger.discoverAllDevicesParallel(unitIds, parallelScans);
} finally {
await discoveryClient.disconnect();
}
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
// Add metadata
responseData._metadata = {
operation,
host,
port,
unitId,
timestamp: new Date().toISOString(),
success: true,
};
node.status({ fill: "green", shape: "dot", text: "success" });
} finally {
// Always disconnect
await modbusClient.disconnect();
}
// Send the response
msg.payload = responseData;
node.send(msg);
} catch (error) {
node.status({ fill: "red", shape: "ring", text: "error" });
node.error(error.message, msg);
if (config.continueOnFail) {
msg.payload = {
error: error.message,
_metadata: {
success: false,
timestamp: new Date().toISOString(),
}
};
node.send(msg);
}
}
});
node.on('close', function() {
node.status({});
});
}
/**
* Parse discovery range string into array of unit IDs
* Examples: "1-15,21-30" -> [1,2,3,...,15,21,22,...,30]
* "12,13,14,15" -> [12,13,14,15]
*/
function parseDiscoveryRange(rangeString) {
const unitIds = [];
const parts = rangeString.split(',').map(s => s.trim());
for (const part of parts) {
if (part.includes('-')) {
// Handle range like "1-15"
const [start, end] = part.split('-').map(Number);
if (!isNaN(start) && !isNaN(end) && start <= end) {
for (let i = start; i <= end; i++) {
if (i >= 1 && i <= 247) { // Valid Modbus unit ID range
unitIds.push(i);
}
}
}
} else {
// Handle individual ID like "12"
const id = Number(part);
if (!isNaN(id) && id >= 1 && id <= 247) {
unitIds.push(id);
}
}
}
return [...new Set(unitIds)].sort((a, b) => a - b); // Remove duplicates and sort
}
RED.nodes.registerType("smartlogger", SmartLoggerNode);
};