UNPKG

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
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); };