Skip to content

Instantly share code, notes, and snippets.

@biemond
Last active February 6, 2022 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save biemond/87403d5c9a28ef89a6ec3e5142ec4c85 to your computer and use it in GitHub Desktop.
Save biemond/87403d5c9a28ef89a6ec3e5142ec4c85 to your computer and use it in GitHub Desktop.
solaredge nodejs modbus tcp with node-modbus
console.log('-------------------')
const modbus = require('jsmodbus');
const net = require('net');
const socket = new net.Socket();
let options = {
'host': '192.168.107.25',
'port': 1502,
'unitId': 1,
'timeout': 62,
'autoReconnect': true,
'reconnectTimeout': 62,
'logLabel' : 'solaredge Inverter',
'logLevel': 'error',
'logEnabled': true
}
let client = new modbus.client.TCP(socket)
socket.connect(options);
var delay = ( function() {
var timer = 0;
return function(callback, ms) {
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
socket.on('connect', () => {
console.log('Connected ...');
// this.pollingInterval = setInterval(() => {
registers = {
"c_manufacturer": [0x9c44, 16, 'STRING', "Manufacturer"],
"c_model": [0x9c54, 16, 'STRING', "Model"],
"c_version": [0x9c6c, 8, 'STRING', "Version"],
"c_serialnumber": [0x9c74, 16, 'STRING', "Serial"],
"c_deviceaddress": [0x9c84, 1, 'UINT16', "Modbus ID"],
"c_sunspec_did": [0x9c85, 1, 'UINT16', "SunSpec DID"],
"current": [0x9c87, 1, 'UINT16', "Current"],
"l1_current": [0x9c88, 1, 'UINT16', "L1 Current"],
"l2_current": [0x9c89, 1, 'UINT16', "L2 Current"],
"l3_current": [0x9c8a, 1, 'UINT16', "L3 Current"],
"current_scale": [0x9c8b, 1, 'SCALE', "Current Scale Factor"],
"l1_voltage": [0x9c8c, 1, 'UINT16', "L1 Voltage"],
"l2_voltage": [0x9c8d, 1, 'UINT16', "L2 Voltage"],
"l3_voltage": [0x9c8e, 1, 'UINT16', "L3 Voltage"],
"l1n_voltage": [0x9c8f, 1, 'UINT16', "L1-N Voltage"],
"l2n_voltage": [0x9c90, 1, 'UINT16', "L2-N Voltage"],
"l3n_voltage": [0x9c91, 1, 'UINT16', "L3-N Voltage"],
"voltage_scale": [0x9c92, 1, 'SCALE', "Voltage Scale Factor"],
"power_ac": [0x9c93, 1, 'INT16', "Power"],
"power_ac_scale": [0x9c94, 1, 'SCALE', "Power Scale Factor"],
"frequency": [0x9c95, 1, 'UINT16', "Frequency"],
"frequency_scale": [0x9c96, 1, 'SCALE', "Frequency Scale Factor"],
"power_apparent": [0x9c97, 1, 'INT16', "Power [Apparent]"],
"power_apparent_scale": [0x9c98, 1, 'SCALE', "Power [Apparent] Scale Factor"],
"power_reactive": [0x9c99, 1, 'INT16', "Power [Reactive]"],
"power_reactive_scale": [0x9c9a, 1, 'SCALE', "Power [Reactive] Scale Factor"],
"power_factor": [0x9c9b, 1, 'INT16', "Power Factor"],
"power_factor_scale": [0x9c9c, 1, 'SCALE', "Power Factor Scale Factor"],
"energy_total": [0x9c9d, 2, 'ACC32', "Total Energy"],
"energy_total_scale": [0x9c9f, 1, 'SCALE', "Total Energy Scale Factor"],
"current_dc": [0x9ca0, 1, 'UINT16', "DC Current"],
"current_dc_scale": [0x9ca1, 1, 'SCALE', "DC Current Scale Factor"],
"voltage_dc": [0x9ca2, 1, 'UINT16', "DC Voltage"],
"voltage_dc_scale": [0x9ca3, 1, 'SCALE', "DC Voltage Scale Factor"],
"power_dc": [0x9ca4, 1, 'INT16', "DC Power"],
"power_dc_scale": [0x9ca5, 1, 'SCALE', "DC Power Scale Factor"],
"temperature": [0x9ca7, 1, 'INT16', "Temperature"],
"temperature_scale": [0x9caa, 1, 'SCALE', "Temperature Scale Factor"],
"status": [0x9cab, 1, 'UINT16', "Status"],
"vendor_status": [0x9cac, 1, 'UINT16', "Vendor Status"],
"rrcr_state": [0xf000, 1, 'UINT16', "RRCR State"],
"active_power_limit": [0xf001, 1, 'UINT16', "Active Power Limit"],
"cosphi": [0xf002, 2, 'FLOAT32', "CosPhi"],
"storage_control_mode": [0xe004, 1, 'UINT16', "Storage Control Mode"],
"storage_accharge_policy": [0xe005, 1, 'UINT16', "Storage AC Charge Policy"],
"storage_accharge_Limit": [0xe006, 2, 'FLOAT32', "Storage AC Charge Limit"],
"remote_control_command_mode": [0xe00d, 1, 'UINT16', "Remote Control Command Mode"],
"remote_control_charge_limit": [0xe00e, 2, 'FLOAT32', "Remote Control Charge Limit"],
"remote_control_command_discharge_limit": [0xe010, 2, 'FLOAT32', "Remote Control Command Discharge Limit"]
}
storage_control_mode = [
"Disabled",
"Maximize Self Consumption",
"Time of Use" ,
"Backup Only",
"Remote Control"
]
remote_control_mode = [
'Off',
'Charge excess PV power only',
'Charge from PV first',
'Charge from PV+AC according to the max battery power',
'Maximize export',
'Discharge to meet loads consumption',
'NOT',
'Maximize self-consumption'
]
INVERTER_STATUS_MAP = [
"Undefined",
"Off",
"Sleeping",
"Grid Monitoring",
"Producing",
"Producing (Throttled)",
"Shutting Down",
"Fault",
"Standby"
]
// let meter_offset = [
// 0x0,
// 0xae,
// 0x15c
// ]
meter_dids = {
"meter1 Modbus ID": [0x9cfc, 1, 'UINT16', 0x0],
"meter2 Modbus ID": [0x9daa, 1, 'UINT16', 0xae],
"meter3 Modbus ID": [0x9e59, 1, 'UINT16', 0x15c]
}
meter_registers = {
"c_manufacturer": [0x9cbb , 16, 'STRING', "Manufacturer"],
"c_model": [0x9ccb , 16, 'STRING', "Model"],
"c_option": [0x9cdb , 8, 'STRING', "Mode"],
"c_version": [0x9ce3 , 8, 'STRING', "Version"],
"c_serialnumber": [0x9ceb , 16, 'STRING', "Serial"],
"c_deviceaddress": [0x9cfb , 1, 'UINT16', "Modbus ID"],
"c_sunspec_did": [0x9cfc , 1, 'UINT16', "SunSpec DID"],
"current": [0x9cfe , 1, 'INT16', "Current"],
"l1_current": [0x9cff , 1, 'INT16', "L1 Current"],
"l2_current": [0x9d00 , 1, 'INT16', "L2 Current"],
"l3_current": [0x9d01 , 1, 'INT16', "L3 Current"],
"current_scale": [0x9d02 , 1, 'SCALE', "Current Scale Factor"],
"voltage_ln": [0x9d03 , 1, 'INT16', "L-N Voltage"],
"l1n_voltage": [0x9d04 , 1, 'INT16', "L1-N Voltage"],
"l2n_voltage": [0x9d05 , 1, 'INT16', "L2-N Voltage"],
"l3n_voltage": [0x9d06 , 1, 'INT16', "L3-N Voltage"],
"voltage_ll": [0x9d07 , 1, 'INT16', "L-L Voltage"],
"l12_voltage": [0x9d08 , 1, 'INT16', "L1-l2 Voltage"],
"l23_voltage": [0x9d09 , 1, 'INT16', "L2-l3 Voltage"],
"l31_voltage": [0x9d0a , 1, 'INT16', "L3-l1 Voltage"],
"voltage_scale": [0x9d0b , 1, 'SCALE', "Voltage Scale Factor"],
"frequency": [0x9d0c , 1, 'INT16', "Frequency"],
"frequency_scale": [0x9d0d , 1, 'SCALE', "Frequency Scale Factor"],
"power": [0x9d0e , 1, 'INT16', "Power"],
"l1_power": [0x9d0f , 1, 'INT16', "L1 Power"],
"l2_power": [0x9d10 , 1, 'INT16', "L2 Power"],
"l3_power": [0x9d11 , 1, 'INT16', "L3 Power"],
"power_scale": [0x9d12 , 1, 'SCALE', "Power Scale Factor"],
"power_apparent": [0x9d13 , 1, 'INT16', "Power (Apparent)"],
"l1_power_apparent": [0x9d14 , 1, 'INT16', "L1 Power (Apparent)"],
"l2_power_apparent": [0x9d15 , 1, 'INT16', "L2 Power (Apparent)"],
"l3_power_apparent": [0x9d16 , 1, 'INT16', "L3 Power (Apparent)"],
"power_apparent_scale": [0x9d17 , 1, 'SCALE', "Power (Apparent) Scale Factor"],
"power_reactive": [0x9d18 , 1, 'INT16', "Power (Reactive)"],
"l1_power_reactive": [0x9d19 , 1, 'INT16', "L1 Power (Reactive)"],
"l2_power_reactive": [0x9d1a , 1, 'INT16', "L2 Power (Reactive)"],
"l3_power_reactive": [0x9d1b , 1, 'INT16', "L3 Power (Reactive)"],
"power_reactive_scale": [0x9d1c , 1, 'SCALE', "Power (Reactive) Scale Factor"],
"power_factor": [0x9d1d, 1,'INT16', "Power Factor"],
"l1_power_factor": [0x9d1e, 1,'INT16', "L1 Power Factor"],
"l2_power_factor": [0x9d1f, 1,'INT16', "L2 Power Factor"],
"l3_power_factor": [0x9d20, 1,'INT16', "L3 Power Factor"],
"power_factor_scale": [0x9d21, 1,'SCALE', "Power Factor Scale Factor"],
"export_energy_active": [0x9d22, 2,'UINT32', "Total Exported Energy (Active)"],
"l1_export_energy_active": [0x9d24, 2,'UINT32', "L1 Exported Energy (Active)"],
"l2_export_energy_active": [0x9d26, 2,'UINT32', "L2 Exported Energy (Active)"],
"l3_export_energy_active": [0x9d28, 2,'UINT32', "L3 Exported Energy (Active)"],
"import_energy_active": [0x9d2a, 2,'UINT32', "Total Imported Energy (Active)"],
"l1_import_energy_active": [0x9d2c, 2,'UINT32', "L1 Imported Energy (Active)"],
"l2_import_energy_active": [0x9d2e, 2,'UINT32', "L2 Imported Energy (Active)"],
"l3_import_energy_active": [0x9d30, 2,'UINT32', "L3 Imported Energy (Active)"],
"energy_active_scale": [0x9d32, 1,'SCALE', "Energy (Active) Scale Factor"],
"export_energy_apparent": [0x9d33, 2,'UINT32', "Total Exported Energy (Apparent)"],
"l1_export_energy_apparent": [0x9d35, 2,'UINT32', "L1 Exported Energy (Apparent)"],
"l2_export_energy_apparent": [0x9d37, 2,'UINT32', "L2 Exported Energy (Apparent)"],
"l3_export_energy_apparent": [0x9d39, 2,'UINT32', "L3 Exported Energy (Apparent)"],
"import_energy_apparent": [0x9d3b, 2,'UINT32', "Total Imported Energy (Apparent)"],
"l1_import_energy_apparent": [0x9d3d, 2,'UINT32', "L1 Imported Energy (Apparent)"],
"l2_import_energy_apparent": [0x9d3f, 2,'UINT32', "L2 Imported Energy (Apparent)"],
"l3_import_energy_apparent": [0x9d41, 2,'UINT32', "L3 Imported Energy (Apparent)"],
"energy_apparent_scale": [0x9d43, 1,'SCALE', "Energy (Apparent) Scale Factor"],
"import_energy_reactive_q1": [0x9d44, 2,'UINT32', "Total Imported Energy (Reactive) Quadrant 1"],
"l1_import_energy_reactive_q1": [0x9d46, 2,'UINT32', "L1 Imported Energy (Reactive) Quadrant 1"],
"l2_import_energy_reactive_q1": [0x9d48, 2,'UINT32', "L2 Imported Energy (Reactive) Quadrant 1"],
"l3_import_energy_reactive_q1": [0x9d4a, 2,'UINT32', "L3 Imported Energy (Reactive) Quadrant 1"],
"import_energy_reactive_q2": [0x9d4c, 2,'UINT32', "Total Imported Energy (Reactive) Quadrant 2"],
"l1_import_energy_reactive_q2": [0x9d4e, 2,'UINT32', "L1 Imported Energy (Reactive) Quadrant 2"],
"l2_import_energy_reactive_q2": [0x9d50, 2,'UINT32', "L2 Imported Energy (Reactive) Quadrant 2"],
"l3_import_energy_reactive_q2": [0x9d52, 2,'UINT32', "L3 Imported Energy (Reactive) Quadrant 2"],
"export_energy_reactive_q3": [0x9d54, 2,'UINT32', "Total Exported Energy (Reactive) Quadrant 3"],
"l1_export_energy_reactive_q3": [0x9d56, 2,'UINT32', "L1 Exported Energy (Reactive) Quadrant 3"],
"l2_export_energy_reactive_q3": [0x9d58, 2,'UINT32', "L2 Exported Energy (Reactive) Quadrant 3"],
"l3_export_energy_reactive_q3": [0x9d5a, 2,'UINT32', "L3 Exported Energy (Reactive) Quadrant 3"],
"export_energy_reactive_q4": [0x9d5c, 2,'UINT32', "Total Exported Energy (Reactive) Quadrant 4"],
"l1_export_energy_reactive_q4": [0x9d5e, 2,'UINT32', "L1 Exported Energy (Reactive) Quadrant 4"],
"l2_export_energy_reactive_q4": [0x9d60, 2,'UINT32', "L2 Exported Energy (Reactive) Quadrant 4"],
"l3_export_energy_reactive_q4": [0x9d62, 2,'UINT32', "L3 Exported Energy (Reactive) Quadrant 4"],
"energy_reactive_scale": [0x9d64, 1,'SCALE', "Energy (Reactive) Scale Factor"]
}
battery_dids = {
"batt1 Modbus ID": [0xe140, 1, 'UINT16', 0x0],
"batt2 Modbus ID": [0xe240, 1, 'UINT16', 0x100]
}
BATTERY_STATUS_MAP = [
"Off",
"Standby",
"Init",
"Charge",
"Discharge",
"Fault",
"Idle"
]
batt_registers = {
"c_manufacturer": [0xe100, 16, 'STRING', "Manufacturer"],
"c_model": [0xe110, 16, 'STRING', "Model"],
"c_version": [0xe120, 16, 'STRING', "Version"],
"c_serialnumber": [0xe130, 16, 'STRING', "Serial"],
"c_deviceaddress": [0xe140, 1, 'UINT16', "Modbus ID"],
"c_sunspec_did": [0xe141, 1, 'UINT16', "SunSpec DID"],
"rated_energy": [0xe142, 2, 'SEFLOAT', "Rated Energy"],
"maximum_charge_continuous_power": [0xe144, 2, 'SEFLOAT', "Maximum Charge Continuous Power"],
"maximum_discharge_continuous_power": [0xe146, 2, 'SEFLOAT', "Maximum Discharge Continuous Power"],
"maximum_charge_peak_power": [0xe148, 2, 'SEFLOAT', "Maximum Charge Peak Power"],
"maximum_discharge_peak_power": [0xe14a, 2, 'SEFLOAT', "Maximum Discharge Peak Power"],
"average_temperature": [0xe16c, 2, 'SEFLOAT', "Average Temperature"],
"maximum_temperature": [0xe16e, 2, 'SEFLOAT', "Maximum Temperature"],
"instantaneous_voltage": [0xe170, 2, 'SEFLOAT', "Instantaneous Voltage"],
"instantaneous_current": [0xe172, 2, 'SEFLOAT', "Instantaneous Current"],
"instantaneous_power": [0xe174, 2, 'SEFLOAT', "Instantaneous Power"],
"lifetime_export_energy_counter": [0xe176, 4, 'UINT64', "Total Exported Energy"],
"lifetime_import_energy_counter": [0xe17A, 4, 'UINT64', "Total Imported Energy"],
"maximum_energy": [0xe17e, 2, 'SEFLOAT', "Maximum Energy"],
"available_energy": [0xe180, 2, 'SEFLOAT', "Available Energy"],
"soh": [0xe182, 2, 'SEFLOAT', "State of Health [SOH)"],
"soe": [0xe184, 2, 'SEFLOAT', "State of Energy [SOE)"],
"status": [0xe186, 2, 'UINT32', "Status"],
"status_internal": [0xe188, 2, 'UINT32', "Internal Status"],
"event_log": [0xe18a, 2, 'UINT16', "Event Log"],
"event_log_internal": [0xe192, 2, 'UINT16', "Internal Event Log"]
}
for (const [key, value] of Object.entries(registers)) {
// console.log(key, value);
// start normale poll
client.readHoldingRegisters(value[0],value[1])
.then(function(resp) {
// console.log(resp.response._body);
if ( value[2] == 'UINT16') {
console.log(value[3] + ": " + resp.response._body._valuesAsBuffer.readInt16BE());
} else if ( value[2] == 'ACC32') {
console.log(value[3] + ": " + resp.response._body._valuesAsBuffer.readUInt32BE());
} else if ( value[2] == 'FLOAT') {
console.log(value[3] + ": " + resp.response._body._valuesAsBuffer.readFloatBE());
} else if ( value[2] == 'STRING') {
console.log(value[3] + ": " + Buffer.from(resp.response._body._valuesAsBuffer, 'hex').toString());
} else if ( value[2] == 'INT16' || value[2] == 'SCALE') {
console.log(value[3] + ": " + resp.response._body._valuesAsBuffer.readInt16BE());
} else if ( value[2] == 'FLOAT32' ) {
console.log(value[3] + ": " + Buffer.from(resp.response._body._valuesAsBuffer, 'hex').swap16().swap32().readFloatBE());
} else {
console.log(key + ": type not found " + value[2]);
}
})
.catch((err) => {
console.log(err);
});
}
for (const [key, value] of Object.entries(meter_dids)) {
// console.log(key, value);
client.readHoldingRegisters(value[0],value[1])
.then(function(resp) {
// console.log(resp.response._body);
if ( value[2] == 'UINT16') {
console.log(key + ": " + resp.response._body._valuesAsBuffer.readUInt16BE());
for (const [key2, value2] of Object.entries(meter_registers)) {
// console.log(key2, value2);
// console.log( "offset "+value[3]);
client.readHoldingRegisters(value2[0] + value[3], value2[1])
.then(function(resp2) {
// console.log(resp2.response._body);
if ( value2[2] == 'UINT16') {
console.log(key + "-" + value2[3] + ": " + resp2.response._body._valuesAsBuffer.readUInt16BE());
} else if ( value2[2] == 'UINT32') {
console.log(key + "-" +value2[3] + ": " + resp2.response._body._valuesAsBuffer.readUInt32BE());
} else if ( value2[2] == 'SEFLOAT') {
console.log(key + "-" + value2[3] + ": " + Buffer.from(resp2.response._body._valuesAsBuffer, 'hex').swap16().swap32().readFloatBE());
} else if ( value2[2] == 'STRING') {
console.log(key + "-" + value2[3] + ": " + Buffer.from(resp2.response._body._valuesAsBuffer, 'hex').toString());
} else if ( value2[2] == 'UINT64') {
console.log(key + "-" + value2[3] + ": " + resp2.response._body._valuesAsBuffer.readBigUInt64LE());
} else if ( value2[2] == 'INT16' || value2[2] == 'SCALE') {
console.log(key + "-" + value2[3] + ": " + resp2.response._body._valuesAsBuffer.readInt16BE());
} else {
console.log(key2 + ": type not found " + value2[2]);
}
})
.catch((err) => {
console.log(err);
});
}
}
})
.catch((err) => {
// console.log(err);
});
}
for (const [key, value] of Object.entries(battery_dids)) {
// console.log(key, value);
// start normale poll
client.readHoldingRegisters(value[0],value[1])
.then(function(resp) {
// console.log(resp.response._body);
if ( value[2] == 'UINT16') {
if ( resp.response._body._valuesAsBuffer.readUInt16BE() != 255 ) {
console.log(key + ": " + resp.response._body._valuesAsBuffer.readUInt16BE());
let offset = 0x0;
for (const [key2, value2] of Object.entries(batt_registers)) {
// console.log(key2, value2);
// start normale poll
client.readHoldingRegisters(value2[0] + value[3] ,value2[1])
.then(function(resp) {
// console.log(resp.response._body);
if ( value2[2] == 'SEFLOAT') {
console.log(key + "-" + value2[3] + ": " + Buffer.from(resp.response._body._valuesAsBuffer, 'hex').swap16().swap32().readFloatBE());
} else if ( value2[2] == 'STRING') {
console.log(key + "-" + value2[3] + ": " + Buffer.from(resp.response._body._valuesAsBuffer, 'hex').toString());
} else if ( value2[2] == 'UINT16') {
console.log(key + "-" + value2[3] + ": " + resp.response._body._valuesAsBuffer.readInt16BE());
} else if ( value2[2] == 'UINT32') {
console.log(key + "-" + value2[3] + ": " + resp.response._body._valuesAsArray[0]);
} else if ( value2[2] == 'UINT64') {
console.log(key + "-" + value2[3] + ": " + resp.response._body._valuesAsBuffer.readBigUInt64LE());
} else {
console.log(key2 + ": type not found " + value2[2]);
}
})
.catch((err) => {
console.log(err);
});
}
}
}
})
.catch((err) => {
console.log(err);
});
}
// Dit sluit de polling
// }, 5 * 1000)
delay(function(){
socket.end();
}, 8000 );
})
//avoid all the crash reports
socket.on('error', (err) => {
console.log(err);
socket.end();
})
// socket.on('close', () => {
// console.log('Client closed, retrying in xx seconds');
// setTimeout(() => {
// socket.connect(options);
// console.log('Reconnecting now ...');
// }, 20 * 1000)
// })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment