@atelieruldetehnologii/node-red-custome-solutions 0.62.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +56 -0
- package/PROPRIETARY-LICENSE.txt +157 -0
- package/README.md +105 -0
- package/TERMS-AND-CONDITIONS.md +311 -0
- package/advantech-read/advantech-read.html +775 -0
- package/advantech-read/advantech-read.js +449 -0
- package/advantech-write/advantech-write.html +413 -0
- package/advantech-write/advantech-write.js +338 -0
- package/bess-control/bess-control.html +569 -0
- package/bess-control/bess-control.js +541 -0
- package/bess-control/lib/allocation.js +149 -0
- package/bess-control/lib/batteries.js +86 -0
- package/bess-control/lib/helpers.js +157 -0
- package/bess-control/lib/messages.js +131 -0
- package/bess-control/lib/runtime.js +156 -0
- package/carlo-gavazzi/carlo-gavazzi.html +4327 -0
- package/carlo-gavazzi/carlo-gavazzi.js +3081 -0
- package/control-export/control-export.html +3644 -0
- package/control-export/control-export.js +2192 -0
- package/control-reactive/control-reactive.html +3136 -0
- package/control-reactive/control-reactive.js +2124 -0
- package/fronius-emulator/README.md +258 -0
- package/fronius-emulator/fronius-emulator.html +170 -0
- package/fronius-emulator/fronius-emulator.js +520 -0
- package/fronius-emulator/lib/binary-codec.js +65 -0
- package/fronius-emulator/lib/fronius-register-map.js +185 -0
- package/fronius-emulator/lib/modbus-parser.js +61 -0
- package/fronius-emulator/lib/modbus-tcp-server.js +224 -0
- package/fronius-emulator/lib/validation.js +206 -0
- package/fronius-emulator/lib/virtual-meter.js +132 -0
- package/fronius-read/fronius-read.html +3944 -0
- package/fronius-read/fronius-read.js +1466 -0
- package/fronius-read/lib/inverter-profiles.js +711 -0
- package/fronius-read/lib/sunspec-core.js +303 -0
- package/fronius-read/lib/sunspec-model-parsers.js +845 -0
- package/fronius-write/fronius-write.html +1789 -0
- package/fronius-write/fronius-write.js +2531 -0
- package/influxdb/influxdb.html +1077 -0
- package/influxdb/influxdb.js +1487 -0
- package/janitza/janitza.html +2562 -0
- package/janitza/janitza.js +1500 -0
- package/janitza/lib/janitza-profiles.js +371 -0
- package/lib/a3-test-server.js +199 -0
- package/lib/adam-6000-profiles.js +173 -0
- package/lib/licensing/constants.js +103 -0
- package/lib/licensing/device-id.js +251 -0
- package/lib/licensing/errors.js +21 -0
- package/lib/licensing/license-client.js +377 -0
- package/lib/licensing/license-state.js +50 -0
- package/lib/licensing/node-guard.js +150 -0
- package/lib/licensing/package-integrity.js +167 -0
- package/lib/licensing/package-integrity.json +513 -0
- package/lib/licensing/public-key.pem +3 -0
- package/lib/licensing/public-key.pem.example +2 -0
- package/lib/licensing/public-keys.js +18 -0
- package/lib/licensing/storage.js +63 -0
- package/lib/licensing/token-verifier.js +84 -0
- package/lib/numbers.js +26 -0
- package/license/atelier-license.html +88 -0
- package/license/atelier-license.js +170 -0
- package/modbus-tcp-discover/modbus-tcp-discover.html +190 -0
- package/modbus-tcp-discover/modbus-tcp-discover.js +706 -0
- package/multi-battery/multi-battery.html +649 -0
- package/multi-battery/multi-battery.js +668 -0
- package/multi-grid/multi-grid.html +745 -0
- package/multi-grid/multi-grid.js +1392 -0
- package/multi-inverter/lib/engine-core.js +237 -0
- package/multi-inverter/multi-inverter.html +2674 -0
- package/multi-inverter/multi-inverter.js +1393 -0
- package/package.json +109 -0
- package/phoenix-contact/lib/eem-ma370-advanced.js +175 -0
- package/phoenix-contact/lib/eem-ma370.js +138 -0
- package/phoenix-contact/lib/integrations.js +149 -0
- package/phoenix-contact/lib/phoenix-contact-profile.js +35030 -0
- package/phoenix-contact/phoenix-contact.html +840 -0
- package/phoenix-contact/phoenix-contact.js +679 -0
- package/pv-generator/pv-generator.html +284 -0
- package/pv-generator/pv-generator.js +387 -0
- package/pylontech-read/pylontech-read.html +763 -0
- package/pylontech-read/pylontech-read.js +518 -0
- package/pylontech-write/pylontech-write.html +405 -0
- package/pylontech-write/pylontech-write.js +546 -0
- package/scada-aggregator/lib/aggregate.js +72 -0
- package/scada-aggregator/lib/groups.js +39 -0
- package/scada-aggregator/scada-aggregator.html +108 -0
- package/scada-aggregator/scada-aggregator.js +117 -0
- package/scada-server/lib/aggregator-map.js +58 -0
- package/scada-server/lib/encoding.js +235 -0
- package/scada-server/lib/modbus-server.js +492 -0
- package/scada-server/lib/object-path.js +18 -0
- package/scada-server/lib/register-map.js +288 -0
- package/scada-server/scada-server.html +1419 -0
- package/scada-server/scada-server.js +704 -0
- package/scripts/check-syntax.js +66 -0
- package/scripts/package-integrity.js +46 -0
- package/scripts/package-release.js +116 -0
- package/scripts/run-tests.js +2178 -0
- package/scripts/tgz-integrity.js +74 -0
- package/sma-read/lib/sunspec-core.js +83 -0
- package/sma-read/lib/sunspec-model-parsers.js +853 -0
- package/sma-read/sma-read.html +659 -0
- package/sma-read/sma-read.js +458 -0
- package/sma-write/sma-write.html +105 -0
- package/sma-write/sma-write.js +270 -0
- package/victron-read/lib/mqtt-state.js +176 -0
- package/victron-read/victron-read.html +1074 -0
- package/victron-read/victron-read.js +1222 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function () {
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const FIELDS = [
|
|
6
|
+
{ group: "Invertor AC", value: "summary.ac.power_w", label: "Putere activa AC (W)" },
|
|
7
|
+
{ group: "Invertor AC", value: "summary.ac.current_total_a", label: "Curent total (A)" },
|
|
8
|
+
{ group: "Invertor AC", value: "summary.ac.current_l1_a", label: "Curent L1 (A)" },
|
|
9
|
+
{ group: "Invertor AC", value: "summary.ac.current_l2_a", label: "Curent L2 (A)" },
|
|
10
|
+
{ group: "Invertor AC", value: "summary.ac.current_l3_a", label: "Curent L3 (A)" },
|
|
11
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_l1_v", label: "Tensiune L1 (V)" },
|
|
12
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_l2_v", label: "Tensiune L2 (V)" },
|
|
13
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_l3_v", label: "Tensiune L3 (V)" },
|
|
14
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_ll_ab_v", label: "Tensiune AB (V)" },
|
|
15
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_ll_bc_v", label: "Tensiune BC (V)" },
|
|
16
|
+
{ group: "Invertor AC", value: "summary.ac.voltage_ll_ca_v", label: "Tensiune CA (V)" },
|
|
17
|
+
{ group: "Invertor AC", value: "summary.ac.frequency_hz", label: "Frecventa (Hz)" },
|
|
18
|
+
{ group: "Invertor AC", value: "summary.ac.reactive_power_var", label: "Putere reactiva (var)" },
|
|
19
|
+
{ group: "Invertor AC", value: "summary.ac.apparent_power_va", label: "Putere aparenta (VA)" },
|
|
20
|
+
{ group: "Invertor AC", value: "summary.ac.power_factor", label: "Factor de putere" },
|
|
21
|
+
{ group: "Invertor DC", value: "summary.dc.dc_total_power_w", label: "Putere DC totala (W)" },
|
|
22
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.0.dc_power_w", label: "MPPT1 putere (W)" },
|
|
23
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.0.dc_current_a", label: "MPPT1 curent (A)" },
|
|
24
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.0.dc_voltage_v", label: "MPPT1 tensiune (V)" },
|
|
25
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.1.dc_power_w", label: "MPPT2 putere (W)" },
|
|
26
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.1.dc_current_a", label: "MPPT2 curent (A)" },
|
|
27
|
+
{ group: "Invertor DC", value: "summary.dc.mppts.1.dc_voltage_v", label: "MPPT2 tensiune (V)" },
|
|
28
|
+
{ group: "Smart Meter", value: "summary.meter.power_w", label: "Putere totala (W)" },
|
|
29
|
+
{ group: "Smart Meter", value: "summary.meter.power_l1_w", label: "Putere L1 (W)" },
|
|
30
|
+
{ group: "Smart Meter", value: "summary.meter.power_l2_w", label: "Putere L2 (W)" },
|
|
31
|
+
{ group: "Smart Meter", value: "summary.meter.power_l3_w", label: "Putere L3 (W)" },
|
|
32
|
+
{ group: "Smart Meter", value: "summary.meter.current_total_a", label: "Curent total (A)" },
|
|
33
|
+
{ group: "Smart Meter", value: "summary.meter.current_l1_a", label: "Curent L1 (A)" },
|
|
34
|
+
{ group: "Smart Meter", value: "summary.meter.current_l2_a", label: "Curent L2 (A)" },
|
|
35
|
+
{ group: "Smart Meter", value: "summary.meter.current_l3_a", label: "Curent L3 (A)" },
|
|
36
|
+
{ group: "Smart Meter", value: "summary.meter.voltage_l1_v", label: "Tensiune L1 (V)" },
|
|
37
|
+
{ group: "Smart Meter", value: "summary.meter.voltage_l2_v", label: "Tensiune L2 (V)" },
|
|
38
|
+
{ group: "Smart Meter", value: "summary.meter.voltage_l3_v", label: "Tensiune L3 (V)" },
|
|
39
|
+
{ group: "Smart Meter", value: "summary.meter.frequency_hz", label: "Frecventa (Hz)" },
|
|
40
|
+
{ group: "Smart Meter", value: "summary.meter.reactive_power_var", label: "Putere reactiva (var)" },
|
|
41
|
+
{ group: "Smart Meter", value: "summary.meter.apparent_power_va", label: "Putere aparenta (VA)" },
|
|
42
|
+
{ group: "Smart Meter", value: "summary.meter.power_factor", label: "Factor de putere" },
|
|
43
|
+
{ group: "Dispozitiv", value: "summary.common.manufacturer", label: "Producator" },
|
|
44
|
+
{ group: "Dispozitiv", value: "summary.common.model", label: "Model" },
|
|
45
|
+
{ group: "Dispozitiv", value: "summary.common.version", label: "Firmware" },
|
|
46
|
+
{ group: "Dispozitiv", value: "summary.common.serial", label: "Numar serial" }
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const PRESETS = {
|
|
50
|
+
multiInverter: ["summary.ac.power_w"],
|
|
51
|
+
controlExport: ["summary.ac.power_w"],
|
|
52
|
+
multiGrid: ["summary.meter.power_w", "summary.meter.power_l1_w", "summary.meter.power_l2_w", "summary.meter.power_l3_w"],
|
|
53
|
+
influxDb: ["summary.ac.power_w", "summary.dc.dc_total_power_w", "summary.ac.voltage_l1_v", "summary.ac.voltage_l2_v", "summary.ac.voltage_l3_v", "summary.ac.frequency_hz"]
|
|
54
|
+
};
|
|
55
|
+
const EXPERT_LONG_PRESS_MS = 5000;
|
|
56
|
+
let expertUnlocked = false;
|
|
57
|
+
let customSelection = false;
|
|
58
|
+
let longPressTimer = null;
|
|
59
|
+
let longPressTriggered = false;
|
|
60
|
+
let payloadSelectionState = ["full"];
|
|
61
|
+
let detected = false;
|
|
62
|
+
let detectedDeviceType = "unknown";
|
|
63
|
+
let detectedModelIds = [];
|
|
64
|
+
let detectedMpptCount = 0;
|
|
65
|
+
let probeTimer = null;
|
|
66
|
+
let probeRequestId = 0;
|
|
67
|
+
let probeRequest = null;
|
|
68
|
+
|
|
69
|
+
function parseFields(value) {
|
|
70
|
+
if (Array.isArray(value)) return value.length ? value : ["full"];
|
|
71
|
+
if (typeof value === "string" && value.trim()) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(value);
|
|
74
|
+
return Array.isArray(parsed) && parsed.length ? parsed : ["full"];
|
|
75
|
+
} catch (error) { return [value]; }
|
|
76
|
+
}
|
|
77
|
+
return ["full"];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function selectedFields() {
|
|
81
|
+
if ($("#node-input-payloadMode").val() === "full") return ["full"];
|
|
82
|
+
const values = $("#node-sma-read-fields input:checked").map(function () { return this.value; }).get();
|
|
83
|
+
if (values.length) return values;
|
|
84
|
+
const saved = parseFields(payloadSelectionState).filter(function (field) { return field !== "full"; });
|
|
85
|
+
return saved.length ? saved : [detectedDeviceType === "smart_meter" ? "summary.meter.power_w" : "summary.ac.power_w"];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function persistFields(fields) {
|
|
89
|
+
const normalized = parseFields(fields);
|
|
90
|
+
payloadSelectionState = normalized.slice();
|
|
91
|
+
$("#node-input-payloadFields").val(JSON.stringify(normalized));
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderFields(fields) {
|
|
96
|
+
const normalized = parseFields(fields);
|
|
97
|
+
const selected = new Set(normalized);
|
|
98
|
+
const container = $("#node-sma-read-fields").empty();
|
|
99
|
+
persistFields(normalized);
|
|
100
|
+
if (!detected) {
|
|
101
|
+
$("<div class='sma-fields-empty'></div>").text("Detectare in curs. Campurile apar dupa identificarea dispozitivului SunSpec.").appendTo(container);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let available;
|
|
105
|
+
if (detectedDeviceType === "smart_meter") {
|
|
106
|
+
available = FIELDS.filter(function (item) { return item.group === "Smart Meter" || item.group === "Dispozitiv"; });
|
|
107
|
+
} else {
|
|
108
|
+
const hasMpptModel = detectedModelIds.includes(160);
|
|
109
|
+
const deviceFields = FIELDS.filter(function (item) { return item.group === "Dispozitiv"; });
|
|
110
|
+
available = FIELDS.filter(function (item) {
|
|
111
|
+
if (item.group === "Smart Meter" || item.group === "Dispozitiv") return false;
|
|
112
|
+
if (item.value.indexOf(".mppts.") >= 0) return false;
|
|
113
|
+
return item.group !== "Invertor DC" || hasMpptModel;
|
|
114
|
+
});
|
|
115
|
+
if (hasMpptModel) {
|
|
116
|
+
for (let index = 0; index < detectedMpptCount; index += 1) {
|
|
117
|
+
const prefix = "summary.dc.mppts." + index;
|
|
118
|
+
const number = index + 1;
|
|
119
|
+
available.push(
|
|
120
|
+
{ group: "MPPT" + number, value: prefix + ".dc_power_w", label: "Putere (W)" },
|
|
121
|
+
{ group: "MPPT" + number, value: prefix + ".dc_current_a", label: "Curent (A)" },
|
|
122
|
+
{ group: "MPPT" + number, value: prefix + ".dc_voltage_v", label: "Tensiune (V)" }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
available = available.concat(deviceFields);
|
|
127
|
+
}
|
|
128
|
+
let group = "";
|
|
129
|
+
available.forEach(function (item) {
|
|
130
|
+
if (item.group !== group) {
|
|
131
|
+
group = item.group;
|
|
132
|
+
$("<div class='sma-field-group'></div>").text(group).appendTo(container);
|
|
133
|
+
}
|
|
134
|
+
const row = $("<label class='sma-field'></label>");
|
|
135
|
+
$("<input type='checkbox'>").val(item.value).prop("checked", selected.has(item.value)).appendTo(row);
|
|
136
|
+
$("<span></span>").text(item.label).appendTo(row);
|
|
137
|
+
row.appendTo(container);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function integrationEnabled() {
|
|
142
|
+
return ["multiInverter", "controlExport", "multiGrid", "influxDb"].some(function (name) {
|
|
143
|
+
return $("#node-input-" + name).is(":checked");
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function activeIntegration() {
|
|
148
|
+
return ["multiInverter", "controlExport", "multiGrid", "influxDb"].find(function (name) {
|
|
149
|
+
return $("#node-input-" + name).is(":checked");
|
|
150
|
+
}) || "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function enforceExclusive(active) {
|
|
154
|
+
["multiInverter", "controlExport", "multiGrid", "influxDb"].forEach(function (name) {
|
|
155
|
+
const keepInfluxPair = active === "influxDb" ? name !== "influxDb" : name === "influxDb";
|
|
156
|
+
if (name !== active && !keepInfluxPair) $("#node-input-" + name).prop("checked", false);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function persistIntegrationSelectionMode() {
|
|
161
|
+
const active = activeIntegration();
|
|
162
|
+
$("#node-input-multiInverterExpertMode").prop("checked", false);
|
|
163
|
+
$("#node-input-multiInverterUseSelectedFields").prop("checked", !!active && active !== "multiGrid" && customSelection);
|
|
164
|
+
$("#node-input-multiGridUseSelectedFields").prop("checked", active === "multiGrid" && customSelection);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function applyPreset(active) {
|
|
168
|
+
$("#node-input-payloadMode").val("selected");
|
|
169
|
+
renderFields(PRESETS[active] || ["summary.ac.power_w"]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function updateUi() {
|
|
173
|
+
const active = activeIntegration();
|
|
174
|
+
const compact = !!active;
|
|
175
|
+
if (compact) {
|
|
176
|
+
$("#node-input-payloadMode").val("selected").prop("disabled", true);
|
|
177
|
+
if (!customSelection) applyPreset(active);
|
|
178
|
+
} else {
|
|
179
|
+
$("#node-input-payloadMode").prop("disabled", false);
|
|
180
|
+
}
|
|
181
|
+
const full = $("#node-input-payloadMode").val() === "full";
|
|
182
|
+
const debug = $("#node-input-debugMode").is(":checked");
|
|
183
|
+
const primary = compact || full ? 1 : Math.max(1, $("#node-sma-read-fields input:checked").length);
|
|
184
|
+
const showFields = compact ? (customSelection && expertUnlocked) : !full;
|
|
185
|
+
$("#row-sma-read-fields").toggle(showFields);
|
|
186
|
+
$("#node-sma-read-fields input").prop("disabled", compact && !(customSelection && expertUnlocked));
|
|
187
|
+
$("#row-inverter-id").toggle(compact);
|
|
188
|
+
$("#row-influx-measurement,#row-influx-node").toggle($("#node-input-influxDb").is(":checked"));
|
|
189
|
+
$("#node-sma-output-info").text("Iesiri active: " + (primary + (debug ? 1 : 0)));
|
|
190
|
+
return primary + (debug ? 1 : 0);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function clearLongPress() {
|
|
194
|
+
if (longPressTimer) clearTimeout(longPressTimer);
|
|
195
|
+
longPressTimer = null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setProbeInfo(message, state) {
|
|
199
|
+
$("#node-sma-probe-info")
|
|
200
|
+
.text(message)
|
|
201
|
+
.removeClass("sma-probe-ok sma-probe-warn")
|
|
202
|
+
.addClass(state === "ok" ? "sma-probe-ok" : (state === "warn" ? "sma-probe-warn" : ""));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function syncDetectedIntegrationAvailability() {
|
|
206
|
+
const meter = detectedDeviceType === "smart_meter";
|
|
207
|
+
const inverter = detectedDeviceType === "inverter";
|
|
208
|
+
const unavailableForMeter = meter ? "Indisponibil pentru Smart Meter; foloseste Multi-grid sau InfluxDB." : "";
|
|
209
|
+
$("#node-input-multiInverter, #node-input-controlExport").prop("disabled", meter).attr("title", unavailableForMeter);
|
|
210
|
+
$("#node-input-multiGrid").prop("disabled", !meter).attr("title", meter ? "" : "Disponibil dupa detectarea unui Smart Meter SunSpec.");
|
|
211
|
+
if (meter) $("#node-input-multiInverter, #node-input-controlExport").prop("checked", false);
|
|
212
|
+
if (inverter) $("#node-input-multiGrid").prop("checked", false);
|
|
213
|
+
$("label[for='node-input-inverterId']").html('<i class="fa fa-id-badge"></i> ' + (meter ? "Meter ID" : "Inverter ID"));
|
|
214
|
+
$("#node-input-inverterId").attr("placeholder", meter ? "meter1" : "inv1");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function setDetectionUnavailable(message) {
|
|
218
|
+
detected = false;
|
|
219
|
+
detectedDeviceType = "unknown";
|
|
220
|
+
detectedModelIds = [];
|
|
221
|
+
detectedMpptCount = 0;
|
|
222
|
+
syncDetectedIntegrationAvailability();
|
|
223
|
+
renderFields(payloadSelectionState);
|
|
224
|
+
setProbeInfo(message, "warn");
|
|
225
|
+
updateUi();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function applyProbeData(data) {
|
|
229
|
+
if (data && Number(data.unit_id) >= 1 && Number(data.unit_id) <= 247) {
|
|
230
|
+
$("#node-input-unitId").val(Number(data.unit_id));
|
|
231
|
+
}
|
|
232
|
+
if (data && Array.isArray(data.payload_fields)) {
|
|
233
|
+
const runtimeFields = parseFields(data.payload_fields).filter(function (field) { return field !== "full"; });
|
|
234
|
+
if (runtimeFields.length && activeIntegration()) {
|
|
235
|
+
payloadSelectionState = runtimeFields.slice();
|
|
236
|
+
customSelection = true;
|
|
237
|
+
$("#node-input-expertPayloadFields").val(JSON.stringify(runtimeFields));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
detected = true;
|
|
241
|
+
detectedDeviceType = data.device_type || "unknown";
|
|
242
|
+
detectedModelIds = Array.isArray(data.model_ids) ? data.model_ids.map(Number) : [];
|
|
243
|
+
detectedMpptCount = Math.max(0, Number(data.mppt_count) || 0);
|
|
244
|
+
syncDetectedIntegrationAvailability();
|
|
245
|
+
renderFields(payloadSelectionState);
|
|
246
|
+
const common = data.common || {};
|
|
247
|
+
const label = common.model || data.meter_model || common.serial || (detectedDeviceType === "smart_meter" ? "Smart Meter" : "invertor SMA");
|
|
248
|
+
setProbeInfo("Model detectat: " + label, "ok");
|
|
249
|
+
persistIntegrationSelectionMode();
|
|
250
|
+
updateUi();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function runProbe() {
|
|
254
|
+
const host = String($("#node-input-host").val() || "").trim();
|
|
255
|
+
const port = String($("#node-input-port").val() || "").trim();
|
|
256
|
+
const unitId = String($("#node-input-unitId").val() || "").trim();
|
|
257
|
+
const requestId = ++probeRequestId;
|
|
258
|
+
if (probeRequest && probeRequest.abort) probeRequest.abort();
|
|
259
|
+
if (!host || !port || !unitId) {
|
|
260
|
+
setDetectionUnavailable("Completeaza IP / Port / Unit ID pentru detectare automata SunSpec.");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
setProbeInfo("Detectare SMA in curs...", "");
|
|
264
|
+
detected = false;
|
|
265
|
+
renderFields(payloadSelectionState);
|
|
266
|
+
probeRequest = $.getJSON("sma-read/probe", { host: host, port: port, unitId: unitId })
|
|
267
|
+
.done(function (data) {
|
|
268
|
+
if (requestId !== probeRequestId) return;
|
|
269
|
+
if (data && data.ok) applyProbeData(data);
|
|
270
|
+
else {
|
|
271
|
+
const reason = data && data.error ? String(data.error) : "raspuns necunoscut";
|
|
272
|
+
setDetectionUnavailable("Detectarea a esuat: " + reason + ". Selectia salvata este pastrata.");
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
.fail(function (xhr, status) {
|
|
276
|
+
if (requestId !== probeRequestId || status === "abort") return;
|
|
277
|
+
const serverError = xhr && xhr.responseJSON && xhr.responseJSON.error;
|
|
278
|
+
const reason = serverError || (xhr && xhr.status ? "HTTP " + xhr.status : status || "eroare de retea");
|
|
279
|
+
setDetectionUnavailable("Detectarea a esuat: " + reason + ". Selectia salvata este pastrata.");
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function scheduleProbe(delay) {
|
|
284
|
+
if (probeTimer) clearTimeout(probeTimer);
|
|
285
|
+
probeTimer = setTimeout(runProbe, delay === undefined ? 600 : delay);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function readCachedState(nodeId) {
|
|
289
|
+
if (!nodeId) {
|
|
290
|
+
scheduleProbe(50);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
$.getJSON("sma-read/state/" + encodeURIComponent(nodeId))
|
|
294
|
+
.done(function (data) {
|
|
295
|
+
const state = data && data.ok ? data.state : null;
|
|
296
|
+
const host = String($("#node-input-host").val() || "").trim();
|
|
297
|
+
const port = Number($("#node-input-port").val());
|
|
298
|
+
const unitId = Number($("#node-input-unitId").val());
|
|
299
|
+
if (state && state.status === "ok" && state.host === host && Number(state.port) === port && Number(state.unit_id) === unitId) {
|
|
300
|
+
applyProbeData(state);
|
|
301
|
+
} else {
|
|
302
|
+
scheduleProbe(50);
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
.fail(function () { scheduleProbe(50); });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function setupExpertLongPress() {
|
|
309
|
+
["multiInverter", "controlExport", "multiGrid", "influxDb"].forEach(function (name) {
|
|
310
|
+
const selector = "#node-input-" + name;
|
|
311
|
+
$(selector)
|
|
312
|
+
.off(".smaExpert")
|
|
313
|
+
.on("mousedown.smaExpert touchstart.smaExpert", function () {
|
|
314
|
+
if (!$(this).is(":checked")) return;
|
|
315
|
+
longPressTriggered = false;
|
|
316
|
+
clearLongPress();
|
|
317
|
+
longPressTimer = setTimeout(function () {
|
|
318
|
+
if ($(selector).is(":checked")) {
|
|
319
|
+
longPressTriggered = true;
|
|
320
|
+
expertUnlocked = true;
|
|
321
|
+
customSelection = true;
|
|
322
|
+
persistIntegrationSelectionMode();
|
|
323
|
+
updateUi();
|
|
324
|
+
}
|
|
325
|
+
}, EXPERT_LONG_PRESS_MS);
|
|
326
|
+
})
|
|
327
|
+
.on("mouseup.smaExpert mouseleave.smaExpert touchend.smaExpert touchcancel.smaExpert", clearLongPress)
|
|
328
|
+
.on("click.smaExpert", function (event) {
|
|
329
|
+
if (!longPressTriggered) return;
|
|
330
|
+
event.preventDefault();
|
|
331
|
+
event.stopImmediatePropagation();
|
|
332
|
+
$(this).prop("checked", true);
|
|
333
|
+
longPressTriggered = false;
|
|
334
|
+
updateUi();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
RED.nodes.registerType("sma-read", {
|
|
340
|
+
category: "Atelierul de Tehnologii",
|
|
341
|
+
color: "#9ebbd1",
|
|
342
|
+
defaults: {
|
|
343
|
+
license: { value: "", type: "atelier-license", required: true },
|
|
344
|
+
name: { value: "" },
|
|
345
|
+
host: { value: "", required: true },
|
|
346
|
+
port: { value: 502, required: true, validate: RED.validators.number() },
|
|
347
|
+
unitId: { value: 126, required: true, validate: RED.validators.number() },
|
|
348
|
+
pollInterval: { value: 5000, required: true, validate: function (value) {
|
|
349
|
+
const text = String(value || "").trim().toLowerCase();
|
|
350
|
+
return text === "off" || (Number.isFinite(Number(text)) && Number(text) >= 0);
|
|
351
|
+
} },
|
|
352
|
+
timeout: { value: 3000 },
|
|
353
|
+
payloadMode: { value: "full" },
|
|
354
|
+
payloadFields: { value: ["full"] },
|
|
355
|
+
expertPayloadFields: { value: "[]" },
|
|
356
|
+
debugMode: { value: false },
|
|
357
|
+
multiInverter: { value: false },
|
|
358
|
+
controlExport: { value: false },
|
|
359
|
+
multiGrid: { value: false },
|
|
360
|
+
influxDb: { value: false },
|
|
361
|
+
multiInverterExpertMode: { value: false },
|
|
362
|
+
multiInverterUseSelectedFields: { value: false },
|
|
363
|
+
multiGridUseSelectedFields: { value: false },
|
|
364
|
+
inverterId: { value: "", validate: function (value) {
|
|
365
|
+
const enabled = $("#node-input-multiInverter").length
|
|
366
|
+
? integrationEnabled()
|
|
367
|
+
: !!(this.multiInverter || this.controlExport || this.multiGrid || this.influxDb);
|
|
368
|
+
return !enabled || !!String(value || "").trim();
|
|
369
|
+
} },
|
|
370
|
+
influxMeasurement: { value: "sma_inverter", validate: function (value) {
|
|
371
|
+
const enabled = $("#node-input-influxDb").length
|
|
372
|
+
? $("#node-input-influxDb").is(":checked")
|
|
373
|
+
: !!this.influxDb;
|
|
374
|
+
return !enabled || !!String(value || "").trim();
|
|
375
|
+
} },
|
|
376
|
+
influxNode: { value: "" }
|
|
377
|
+
},
|
|
378
|
+
inputs: 0,
|
|
379
|
+
outputs: 1,
|
|
380
|
+
icon: "font-awesome/fa-sun-o",
|
|
381
|
+
label: function () { return this.name || this.host || "sma-read"; },
|
|
382
|
+
oneditprepare: function () {
|
|
383
|
+
const node = this;
|
|
384
|
+
const influxSelect = $("#node-input-influxNode").empty().append($("<option></option>").val("").text("Selecteaza adt-influxdb"));
|
|
385
|
+
RED.nodes.eachNode(function (candidate) {
|
|
386
|
+
if (candidate.type === "adt-influxdb") influxSelect.append($("<option></option>").val(candidate.id).text(candidate.name || "adt-influxdb"));
|
|
387
|
+
});
|
|
388
|
+
influxSelect.val(node.influxNode || "");
|
|
389
|
+
detected = false;
|
|
390
|
+
detectedDeviceType = "unknown";
|
|
391
|
+
detectedModelIds = [];
|
|
392
|
+
detectedMpptCount = 0;
|
|
393
|
+
const savedPayloadFields = parseFields(node.payloadFields);
|
|
394
|
+
const savedExpertFields = parseFields(node.expertPayloadFields).filter(function (field) { return field !== "full"; });
|
|
395
|
+
expertUnlocked = false;
|
|
396
|
+
const activeOnLoad = activeIntegration();
|
|
397
|
+
payloadSelectionState = activeOnLoad && savedExpertFields.length ? savedExpertFields.slice() : savedPayloadFields;
|
|
398
|
+
const savedNonFull = payloadSelectionState.filter(function (field) { return field !== "full"; });
|
|
399
|
+
const presetOnLoad = PRESETS[activeOnLoad] || [];
|
|
400
|
+
const differsFromPreset = !!activeOnLoad && savedNonFull.length > 0 && (
|
|
401
|
+
savedNonFull.length !== presetOnLoad.length ||
|
|
402
|
+
savedNonFull.some(function (field) { return !presetOnLoad.includes(field); })
|
|
403
|
+
);
|
|
404
|
+
customSelection = savedExpertFields.length > 0 || differsFromPreset || !!(
|
|
405
|
+
(node.multiGrid && node.multiGridUseSelectedFields) ||
|
|
406
|
+
((node.multiInverter || node.controlExport || node.influxDb) && (node.multiInverterUseSelectedFields || node.multiInverterExpertMode))
|
|
407
|
+
);
|
|
408
|
+
persistIntegrationSelectionMode();
|
|
409
|
+
const savedAutoName = node.host && node.inverterId ? node.host + " [" + node.inverterId + "]" : "";
|
|
410
|
+
let autoName = !node.name || node.name === node.host || node.name === savedAutoName || node.name === "sma-read";
|
|
411
|
+
renderFields(payloadSelectionState);
|
|
412
|
+
$("#node-input-payloadMode").val(node.payloadMode || (payloadSelectionState.includes("full") ? "full" : "selected"));
|
|
413
|
+
syncDetectedIntegrationAvailability();
|
|
414
|
+
|
|
415
|
+
function buildAutoName() {
|
|
416
|
+
const host = String($("#node-input-host").val() || "").trim();
|
|
417
|
+
const inverterId = String($("#node-input-inverterId").val() || "").trim();
|
|
418
|
+
return integrationEnabled() && inverterId ? (host ? host + " [" + inverterId + "]" : inverterId) : host;
|
|
419
|
+
}
|
|
420
|
+
function refreshAutoName() {
|
|
421
|
+
if (autoName) $("#node-input-name").val(buildAutoName());
|
|
422
|
+
}
|
|
423
|
+
refreshAutoName();
|
|
424
|
+
|
|
425
|
+
$("#node-input-host").on("input.smaRead change.smaRead", function () {
|
|
426
|
+
autoName = true;
|
|
427
|
+
refreshAutoName();
|
|
428
|
+
scheduleProbe();
|
|
429
|
+
});
|
|
430
|
+
$("#node-input-port, #node-input-unitId").on("input.smaProbe change.smaProbe", function () { scheduleProbe(); });
|
|
431
|
+
$("#node-input-inverterId").on("input.smaRead change.smaRead", refreshAutoName);
|
|
432
|
+
$("#node-input-name").on("input.smaRead", function () {
|
|
433
|
+
const value = String(this.value || "").trim();
|
|
434
|
+
autoName = !value || value === buildAutoName();
|
|
435
|
+
});
|
|
436
|
+
$("#node-input-payloadMode").on("change.smaRead", updateUi);
|
|
437
|
+
$("#node-sma-read-fields").on("change.smaRead", "input", function () {
|
|
438
|
+
if (activeIntegration()) customSelection = true;
|
|
439
|
+
persistFields(selectedFields());
|
|
440
|
+
if (customSelection) $("#node-input-expertPayloadFields").val(JSON.stringify(payloadSelectionState));
|
|
441
|
+
persistIntegrationSelectionMode();
|
|
442
|
+
updateUi();
|
|
443
|
+
});
|
|
444
|
+
$("#node-sma-select-all").on("click.smaRead", function () {
|
|
445
|
+
if (activeIntegration()) customSelection = true;
|
|
446
|
+
$("#node-sma-read-fields input").prop("checked", true);
|
|
447
|
+
persistFields(selectedFields());
|
|
448
|
+
if (customSelection) $("#node-input-expertPayloadFields").val(JSON.stringify(payloadSelectionState));
|
|
449
|
+
updateUi();
|
|
450
|
+
});
|
|
451
|
+
$("#node-sma-clear-all").on("click.smaRead", function () {
|
|
452
|
+
if (activeIntegration()) customSelection = true;
|
|
453
|
+
$("#node-sma-read-fields input").prop("checked", false);
|
|
454
|
+
persistFields(selectedFields());
|
|
455
|
+
if (customSelection) $("#node-input-expertPayloadFields").val(JSON.stringify(payloadSelectionState));
|
|
456
|
+
updateUi();
|
|
457
|
+
});
|
|
458
|
+
$("#node-input-debugMode").on("change.smaRead", updateUi);
|
|
459
|
+
["multiInverter", "controlExport", "multiGrid", "influxDb"].forEach(function (name) {
|
|
460
|
+
$("#node-input-" + name).on("change.smaRead", function () {
|
|
461
|
+
expertUnlocked = false;
|
|
462
|
+
customSelection = false;
|
|
463
|
+
if (this.checked) {
|
|
464
|
+
enforceExclusive(name);
|
|
465
|
+
applyPreset(name);
|
|
466
|
+
}
|
|
467
|
+
persistIntegrationSelectionMode();
|
|
468
|
+
refreshAutoName();
|
|
469
|
+
updateUi();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
setupExpertLongPress();
|
|
473
|
+
updateUi();
|
|
474
|
+
readCachedState(node.id);
|
|
475
|
+
},
|
|
476
|
+
oneditsave: function () {
|
|
477
|
+
const host = String($("#node-input-host").val() || "").trim();
|
|
478
|
+
const inverterId = String($("#node-input-inverterId").val() || "").trim();
|
|
479
|
+
const active = activeIntegration();
|
|
480
|
+
if (!String($("#node-input-name").val() || "").trim()) {
|
|
481
|
+
$("#node-input-name").val(active && inverterId ? (host ? host + " [" + inverterId + "]" : inverterId) : host);
|
|
482
|
+
}
|
|
483
|
+
if (active) {
|
|
484
|
+
const customFields = payloadSelectionState.filter(function (field) { return field !== "full"; });
|
|
485
|
+
this.payloadFields = customSelection && customFields.length ? customFields : (PRESETS[active] || ["summary.ac.power_w"]).slice();
|
|
486
|
+
this.expertPayloadFields = customSelection && customFields.length ? customFields.slice() : [];
|
|
487
|
+
this.payloadMode = "selected";
|
|
488
|
+
this.multiInverterExpertMode = false;
|
|
489
|
+
this.multiInverterUseSelectedFields = active !== "multiGrid" && customSelection;
|
|
490
|
+
this.multiGridUseSelectedFields = active === "multiGrid" && customSelection;
|
|
491
|
+
} else {
|
|
492
|
+
this.payloadFields = selectedFields();
|
|
493
|
+
this.expertPayloadFields = [];
|
|
494
|
+
this.payloadMode = $("#node-input-payloadMode").val() || "full";
|
|
495
|
+
this.multiInverterExpertMode = false;
|
|
496
|
+
this.multiInverterUseSelectedFields = false;
|
|
497
|
+
this.multiGridUseSelectedFields = false;
|
|
498
|
+
}
|
|
499
|
+
persistIntegrationSelectionMode();
|
|
500
|
+
this.outputs = updateUi();
|
|
501
|
+
persistFields(this.payloadFields);
|
|
502
|
+
$("#node-input-expertPayloadFields").val(JSON.stringify(this.expertPayloadFields));
|
|
503
|
+
const poll = String($("#node-input-pollInterval").val() || "").trim().toLowerCase();
|
|
504
|
+
this.inputs = poll === "off" || poll === "0" ? 1 : 0;
|
|
505
|
+
clearLongPress();
|
|
506
|
+
if (probeTimer) clearTimeout(probeTimer);
|
|
507
|
+
if (probeRequest && probeRequest.abort) probeRequest.abort();
|
|
508
|
+
},
|
|
509
|
+
oneditcancel: function () {
|
|
510
|
+
clearLongPress();
|
|
511
|
+
if (probeTimer) clearTimeout(probeTimer);
|
|
512
|
+
if (probeRequest && probeRequest.abort) probeRequest.abort();
|
|
513
|
+
$(".sma-read-editor").find("*").off(".smaRead").off(".smaExpert").off(".smaProbe");
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
})();
|
|
517
|
+
</script>
|
|
518
|
+
|
|
519
|
+
<script type="text/html" data-template-name="sma-read">
|
|
520
|
+
<div class="form-row"><label for="node-input-license"><i class="fa fa-key"></i> License</label><input type="text" id="node-input-license"></div>
|
|
521
|
+
<div class="sma-read-editor">
|
|
522
|
+
<input type="hidden" id="node-input-timeout">
|
|
523
|
+
<input type="hidden" id="node-input-payloadFields">
|
|
524
|
+
<input type="hidden" id="node-input-expertPayloadFields">
|
|
525
|
+
<input type="checkbox" id="node-input-multiInverterExpertMode" style="display:none">
|
|
526
|
+
<input type="checkbox" id="node-input-multiInverterUseSelectedFields" style="display:none">
|
|
527
|
+
<input type="checkbox" id="node-input-multiGridUseSelectedFields" style="display:none">
|
|
528
|
+
|
|
529
|
+
<div class="pr-section">
|
|
530
|
+
<div class="pr-section-title"><i class="fa fa-plug"></i> Connection</div>
|
|
531
|
+
<p class="pr-section-note">Configuratia de citire SunSpec (Modbus TCP) pentru invertor sau Smart Meter SMA.</p>
|
|
532
|
+
<div class="pr-grid pr-grid-2">
|
|
533
|
+
<div class="form-row"><label for="node-input-name"><i class="fa fa-tag"></i> Name</label><input type="text" id="node-input-name" placeholder="192.168.0.101"></div>
|
|
534
|
+
<div class="form-row"><label for="node-input-host"><i class="fa fa-globe"></i> IP</label><input type="text" id="node-input-host" placeholder="192.168.0.xxx"></div>
|
|
535
|
+
<div class="form-row"><label for="node-input-port"><i class="fa fa-plug"></i> Port</label><input type="text" id="node-input-port" placeholder="502"></div>
|
|
536
|
+
<div class="form-row"><label for="node-input-unitId"><i class="fa fa-hashtag"></i> Unit ID</label><input type="text" id="node-input-unitId" placeholder="126"></div>
|
|
537
|
+
<div class="form-row"><label for="node-input-pollInterval"><i class="fa fa-clock-o"></i> Poll</label><input type="text" id="node-input-pollInterval" placeholder="5000 sau off"></div>
|
|
538
|
+
<div class="form-row" id="row-inverter-id"><label for="node-input-inverterId"><i class="fa fa-id-badge"></i> Inverter ID</label><input type="text" id="node-input-inverterId" placeholder="inv1"></div>
|
|
539
|
+
<div class="form-row pr-span-2 sma-probe-row"><label><i class="fa fa-search"></i> Detectare</label><div id="node-sma-probe-info" class="sma-probe-info">Detectare in curs...</div></div>
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
|
|
543
|
+
<div class="pr-section">
|
|
544
|
+
<div class="pr-section-title"><i class="fa fa-filter"></i> Payload</div>
|
|
545
|
+
<p class="pr-section-note">Alege payload complet sau selecteaza campurile SunSpec care trebuie emise pe iesiri.</p>
|
|
546
|
+
<div class="form-row"><label for="node-input-payloadMode"><i class="fa fa-list"></i> Payload mode</label><select id="node-input-payloadMode"><option value="full">Full payload</option><option value="selected">Selected payload</option></select></div>
|
|
547
|
+
<div class="form-row pr-checkbox-row"><label for="node-input-multiInverter"><i class="fa fa-sitemap"></i> Multi-inverter</label><input type="checkbox" id="node-input-multiInverter"><span class="pr-inline-note">Fixeaza automat payload-ul pe Putere AC (W) pentru <code>multi-inverter</code>.</span></div>
|
|
548
|
+
<div class="form-row pr-checkbox-row"><label for="node-input-controlExport"><i class="fa fa-exchange"></i> Control-export</label><input type="checkbox" id="node-input-controlExport"><span class="pr-inline-note">Fixeaza automat payload-ul pe Putere AC (W) pentru <code>control-export</code>.</span></div>
|
|
549
|
+
<div class="form-row pr-checkbox-row"><label for="node-input-multiGrid"><i class="fa fa-sitemap"></i> Multi-grid</label><input type="checkbox" id="node-input-multiGrid"><span class="pr-inline-note">Trimite puterile SunSpec ale Smart Meter-ului catre <code>multi-grid</code>.</span></div>
|
|
550
|
+
<div class="form-row pr-checkbox-row"><label for="node-input-influxDb"><i class="fa fa-database"></i> InfluxDB</label><input type="checkbox" id="node-input-influxDb"><span class="pr-inline-note">Grupeaza valorile in <code>msg.influx</code> pentru <code>adt-influxdb</code>.</span></div>
|
|
551
|
+
<div class="form-row" id="row-influx-measurement"><label for="node-input-influxMeasurement"><i class="fa fa-line-chart"></i> Measurement</label><input type="text" id="node-input-influxMeasurement" placeholder="SMA Inverter 1"></div>
|
|
552
|
+
<div class="form-row" id="row-influx-node"><label for="node-input-influxNode"><i class="fa fa-plug"></i> Nod InfluxDB</label><select id="node-input-influxNode"></select></div>
|
|
553
|
+
<div class="form-row pr-checkbox-row"><label for="node-input-debugMode"><i class="fa fa-bug"></i> Debug mode</label><input type="checkbox" id="node-input-debugMode"><span class="pr-inline-note">Adauga o iesire suplimentara cu modelele SunSpec brute.</span></div>
|
|
554
|
+
<div id="row-sma-read-fields">
|
|
555
|
+
<div class="pr-toolbar"><button type="button" class="red-ui-button" id="node-sma-select-all">Select all</button><button type="button" class="red-ui-button" id="node-sma-clear-all">Clear all</button></div>
|
|
556
|
+
<div id="node-sma-read-fields" class="sma-fields"></div>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="sma-output"><span id="node-sma-output-info">Iesiri active: 1</span></div>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<style>
|
|
563
|
+
.sma-read-editor{--pr-border:#d7dde5;--pr-muted:#667085;--pr-surface:#f8fafc;--sma-accent:#344054}
|
|
564
|
+
.sma-read-editor .pr-section{margin:0 0 18px;padding:14px 16px 16px;border:1px solid var(--pr-border);border-radius:10px;background:var(--pr-surface)}
|
|
565
|
+
.sma-read-editor .pr-section-title{display:flex;align-items:center;gap:8px;margin:0 0 4px;font-size:15px;font-weight:600;color:#243447}.sma-read-editor .pr-section-title i{color:var(--sma-accent)}
|
|
566
|
+
.sma-read-editor .pr-section-note{margin:0 0 14px;color:var(--pr-muted);font-size:12px;line-height:1.45}
|
|
567
|
+
.sma-read-editor .pr-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 16px}.sma-read-editor .pr-grid-2{grid-template-columns:repeat(2,minmax(320px,1fr))}
|
|
568
|
+
.sma-read-editor .pr-span-2{grid-column:1/-1}.sma-read-editor .sma-probe-row{display:flex;align-items:flex-start}.sma-read-editor .sma-probe-info{color:var(--pr-muted);font-size:12px;padding-top:3px}.sma-read-editor .sma-probe-ok{color:#267a3e}.sma-read-editor .sma-probe-warn{color:#667085}
|
|
569
|
+
.sma-read-editor .form-row{margin:0}.sma-read-editor .pr-section>.form-row{margin-bottom:12px}.sma-read-editor .form-row label{width:140px}.sma-read-editor .form-row input:not([type=checkbox]),.sma-read-editor .form-row select{width:calc(100% - 150px)}
|
|
570
|
+
.sma-read-editor .pr-checkbox-row{display:flex;align-items:center;min-height:28px}.sma-read-editor .pr-checkbox-row input{width:auto!important;margin:0 12px 0 0;accent-color:var(--sma-accent)}.sma-read-editor .pr-inline-note{color:var(--pr-muted);font-size:12px}
|
|
571
|
+
.sma-read-editor code{color:#344054}
|
|
572
|
+
.sma-read-editor .pr-toolbar{display:flex;gap:8px;margin:8px 0}.sma-read-editor .sma-fields{max-height:270px;overflow:auto;padding:8px 12px;border:1px solid var(--pr-border);border-radius:8px;background:#fff}.sma-read-editor .sma-field-group{margin:8px 0 4px;font-weight:600;color:var(--sma-accent)}.sma-read-editor .sma-field{display:block;padding:3px 0}.sma-read-editor .sma-field input{margin-right:8px;accent-color:var(--sma-accent)}
|
|
573
|
+
.sma-read-editor .sma-fields-empty{padding:18px 10px;color:#8a8f98;font-style:italic}
|
|
574
|
+
.sma-read-editor .sma-output{margin-top:12px;padding-top:10px;color:var(--pr-muted);font-size:12px}
|
|
575
|
+
@media(max-width:700px){.sma-read-editor .pr-grid-2{grid-template-columns:1fr}}
|
|
576
|
+
</style>
|
|
577
|
+
</script>
|
|
578
|
+
|
|
579
|
+
<script type="text/html" data-help-name="sma-read">
|
|
580
|
+
<p><b>sma-read</b> citeste invertoare si contoare SMA care publica modele standard SunSpec prin Modbus TCP.</p>
|
|
581
|
+
|
|
582
|
+
<h3>Conexiune</h3>
|
|
583
|
+
<ul>
|
|
584
|
+
<li><b>IP</b>: adresa dispozitivului SMA.</li>
|
|
585
|
+
<li><b>Port</b>: portul Modbus TCP, implicit <code>502</code>.</li>
|
|
586
|
+
<li><b>Unit ID</b>: adresa Modbus, implicit <code>1</code>.</li>
|
|
587
|
+
<li><b>Poll</b>: intervalul dintre citiri, in milisecunde. Valoarea minima efectiva este 1000 ms.</li>
|
|
588
|
+
<li><b>Name</b>: se completeaza automat din IP. Cand este activa o integrare, numele automat include si identificatorul dispozitivului.</li>
|
|
589
|
+
</ul>
|
|
590
|
+
<p>Daca Poll este <code>off</code> sau <code>0</code>, citirea periodica este oprita si nodul primeste o intrare. Orice mesaj primit declanseaza o citire imediata.</p>
|
|
591
|
+
|
|
592
|
+
<h3>Detectare SunSpec</h3>
|
|
593
|
+
<p>La conectare, nodul cauta automat semnatura <code>SunS</code>, identifica baza SunSpec si scaneaza lista modelelor. Sunt interpretate:</p>
|
|
594
|
+
<ul>
|
|
595
|
+
<li>modelul comun <code>1</code>, cu producator, model, versiune si serie;</li>
|
|
596
|
+
<li>modelele de invertor <code>101-103</code> si <code>111-113</code>;</li>
|
|
597
|
+
<li>modelul MPPT <code>160</code>;</li>
|
|
598
|
+
<li>modelele de contor integer si float <code>201-204</code> si <code>211-214</code>;</li>
|
|
599
|
+
<li>modelele de control <code>120-123</code>, atunci cand sunt publicate de dispozitiv.</li>
|
|
600
|
+
</ul>
|
|
601
|
+
<p>Editorul executa automat aceeasi detectare cand este deschis sau cand se modifica IP, Port ori Unit ID. Lista valorilor nu este construita pana cand dispozitivul nu a fost identificat. Pentru invertor sunt afisate numai grupurile si MPPT-urile detectate; pentru Smart Meter sunt afisate numai valorile de contor si identificare.</p>
|
|
602
|
+
<p>Descriptorii modelelor sunt memorati dupa prima citire. Dupa o eroare de comunicatie, conexiunea si detectarea sunt refacute automat.</p>
|
|
603
|
+
|
|
604
|
+
<h3>Full payload</h3>
|
|
605
|
+
<p>Full payload foloseste o singura iesire si publica in <code>msg.payload</code> un obiect cu:</p>
|
|
606
|
+
<ul>
|
|
607
|
+
<li><code>status</code>, <code>ip</code>, <code>unit_id</code>, <code>profile</code> si <code>device_type</code>;</li>
|
|
608
|
+
<li><code>sunspec_base</code> si lista <code>models</code> cu ID, adresa si lungimea fiecarui model;</li>
|
|
609
|
+
<li><code>summary.common</code> pentru identificarea dispozitivului;</li>
|
|
610
|
+
<li><code>summary.ac</code> si <code>summary.dc</code> pentru invertor;</li>
|
|
611
|
+
<li><code>summary.meter</code> pentru Smart Meter;</li>
|
|
612
|
+
<li><code>summary.controls</code> pentru modelele de control disponibile;</li>
|
|
613
|
+
<li><code>ts</code>, marca temporala ISO a citirii.</li>
|
|
614
|
+
</ul>
|
|
615
|
+
<p>Mesajul mai contine <code>msg.payload_field = "full"</code>, <code>msg.payload_fields</code> si <code>msg.full_payload</code>.</p>
|
|
616
|
+
|
|
617
|
+
<h3>Selected payload</h3>
|
|
618
|
+
<p>Selected payload permite alegerea valorilor AC, DC, MPPT, Smart Meter si de identificare. Pentru un singur camp se emite o singura iesire. Pentru mai multe campuri, fiecare camp primeste propria iesire, in ordinea afisata in editor.</p>
|
|
619
|
+
<ul>
|
|
620
|
+
<li><code>msg.payload</code>: valoarea selectata;</li>
|
|
621
|
+
<li><code>msg.topic</code>: eticheta campului;</li>
|
|
622
|
+
<li><code>msg.payload_field</code>: calea completa, de exemplu <code>summary.ac.power_w</code>;</li>
|
|
623
|
+
<li><code>msg.payload_fields</code>: lista tuturor campurilor selectate;</li>
|
|
624
|
+
<li><code>msg.summary_fragment</code>: sumarul complet al citirii curente.</li>
|
|
625
|
+
</ul>
|
|
626
|
+
|
|
627
|
+
<h3>Moduri de integrare</h3>
|
|
628
|
+
<p>Poti activa o singura integrare principala dintre Multi-inverter, Control-export si Multi-grid, optional impreuna cu InfluxDB. Sunt permise maximum doua optiuni, iar una trebuie sa fie InfluxDB. <b>Inverter ID</b> este obligatoriu.</p>
|
|
629
|
+
|
|
630
|
+
<h4>Multi-inverter</h4>
|
|
631
|
+
<p>Emite o singura iesire compatibila cu <code>multi-inverter</code>. In configuratia standard, <code>msg.payload.power_w</code> contine puterea activa AC. <code>msg.topic</code> si <code>msg.inverter_id</code> primesc Inverter ID.</p>
|
|
632
|
+
|
|
633
|
+
<h4>Control-export</h4>
|
|
634
|
+
<p>Emite acelasi contract compact de putere, pentru conectare directa la <code>control-export</code>. Pentru invertor este folosita <code>summary.ac.power_w</code>; pentru un dispozitiv de tip meter este folosita puterea totala a contorului.</p>
|
|
635
|
+
|
|
636
|
+
<h4>Multi-grid</h4>
|
|
637
|
+
<p>Este destinat unui Smart Meter SunSpec si publica o singura iesire cu <code>msg.meter_id</code>. Payload-ul standard contine <code>power_w</code>, <code>power_l1_w</code>, <code>power_l2_w</code> si <code>power_l3_w</code>.</p>
|
|
638
|
+
|
|
639
|
+
<h4>InfluxDB</h4>
|
|
640
|
+
<p>Grupeaza valorile intr-o singura iesire pentru <code>adt-influxdb</code>:</p>
|
|
641
|
+
<ul>
|
|
642
|
+
<li><code>msg.influx.measurement</code>: numele measurement-ului configurat;</li>
|
|
643
|
+
<li><code>msg.influx.tags</code>: tip dispozitiv, Inverter ID, host si Unit ID;</li>
|
|
644
|
+
<li><code>msg.influx.fields</code>: valorile finite citite;</li>
|
|
645
|
+
<li><code>msg.influx.timestamp</code>: marca temporala ISO.</li>
|
|
646
|
+
</ul>
|
|
647
|
+
<p>Measurement si Inverter ID sunt obligatorii cand InfluxDB este activ.</p>
|
|
648
|
+
|
|
649
|
+
<h3>Debug mode</h3>
|
|
650
|
+
<p>Debug mode adauga ultima iesire a nodului. Mesajul are <code>msg.topic = "sma/raw"</code> si include descriptorii, sumarul si modelele SunSpec brute in <code>msg.payload.raw_models</code>. Iesirea este utila pentru diagnosticare si verificarea modelelor publicate de echipament.</p>
|
|
651
|
+
|
|
652
|
+
<h3>Stare si erori</h3>
|
|
653
|
+
<ul>
|
|
654
|
+
<li>status verde: dispozitiv conectat; daca este disponibila, este afisata puterea curenta;</li>
|
|
655
|
+
<li>status rosu: eroare de conectare, timeout, configuratie sau citire;</li>
|
|
656
|
+
<li>erorile sunt publicate cu <code>msg.topic = "sma/error"</code> si includ IP, Unit ID, mesajul erorii si timestamp;</li>
|
|
657
|
+
<li>dupa o eroare, clientul Modbus este inchis si recreat pentru urmatoarea incercare.</li>
|
|
658
|
+
</ul>
|
|
659
|
+
</script>
|