-
-
Notifications
You must be signed in to change notification settings - Fork 219
/
__init__.py
818 lines (717 loc) · 26.6 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Hoymiles Micro-Inverters decoder library
"""
import struct
from datetime import datetime, timedelta
import crcmod
import logging
f_crc_m = crcmod.predefined.mkPredefinedCrcFun('modbus')
f_crc8 = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0)
def g_unpack(s_fmt, s_buf):
"""Chunk unpack helper
:param s_fmt: struct format string
:type s_fmt: str
:param s_buf: buffer to unpack
:type s_buf: bytes
:return: decoded data iterator
:rtype: generator object
"""
cs = struct.calcsize(s_fmt)
s_exc = len(s_buf) % cs
return struct.iter_unpack(s_fmt, s_buf[:len(s_buf) - s_exc])
def print_table_unpack(s_fmt, payload, cw=6):
"""
Print table of decoded numbers with different offsets
Helps recognizing values in unknown payloads
:param s_fmt: struct format string
:type s_fmt: str
:param payload: bytes data
:type payload: bytes
:param cw: cell width
:type cw: int
:return: None
"""
l_hexlified = [f'{byte:02x}' for byte in payload]
dbg = f'{"Pos": <{cw}}'
dbg += ''.join([f'{num: >{cw}}' for num in range(0, len(payload))])
logging.debug(dbg)
dbg = f'{"Hex": <{cw}}'
dbg += ''.join([f'{byte: >{cw}}' for byte in l_hexlified])
logging.debug(dbg)
l_fmt = struct.calcsize(s_fmt)
if len(payload) >= l_fmt:
for offset in range(0, l_fmt):
dbg = f'{s_fmt: <{cw}}'
dbg += ' ' * cw * offset
dbg += ''.join([f'{num[0]: >{cw*l_fmt}}' for num in g_unpack(s_fmt, payload[offset:])])
logging.debug(dbg)
class Response:
""" All Response Shared methods """
inverter_ser = None
inverter_name = None
dtu_ser = None
response = None
def __init__(self, *args, **params):
"""
:param bytes response: response payload bytes
"""
self.inverter_ser = params.get('inverter_ser', None)
self.inverter_name = params.get('inverter_name', None)
self.dtu_ser = params.get('dtu_ser', None)
self.response = args[0]
strings = params.get('strings', None)
self.inv_strings = strings
if isinstance(params.get('time_rx', None), datetime):
self.time_rx = params['time_rx']
else:
self.time_rx = datetime.now()
def __dict__(self):
""" Base values, availabe in each __dict__ call """
return {
'inverter_ser': self.inverter_ser,
'inverter_name': self.inverter_name,
'dtu_ser': self.dtu_ser}
class StatusResponse(Response):
"""Inverter StatusResponse object"""
phase_keys = ['voltage','current','power','reactive_power','frequency']
string_keys = ['voltage','current','power','energy_total','energy_daily', 'irradiation']
temperature = None
frequency = None
powerfactor = None
event_count = None
unpack_error = False
def unpack(self, fmt, base):
"""
Data unpack helper
:param str fmt: struct format string
:param int base: unpack base position from self.response bytes
:return: unpacked values
:rtype: tuple
"""
size = struct.calcsize(fmt)
if (len(self.response) < base+size):
self.unpack_error = True
logging.error(f'base: {base} size: {size} len: {len(self.response)} fmt: {fmt} rep: {self.response}')
return [0]
return struct.unpack(fmt, self.response[base:base+size])
@property
def phases(self):
"""
AC power data
:retrun: list of dict's
:rtype: list
"""
phases = []
p_exists = True
while p_exists:
p_exists = False
phase_id = len(phases)
phase = {}
for key in self.phase_keys:
prop = f'ac_{key}_{phase_id}'
if hasattr(self, prop):
p_exists = True
phase[key] = getattr(self, prop)
if p_exists:
phases.append(phase)
return phases
@property
def strings(self):
"""
DC PV-string data
:retrun: list of dict's
:rtype: list
"""
strings = []
s_exists = True
while s_exists and len(strings) < len(self.inv_strings):
s_exists = False
string_id = len(strings)
if string_id < len(self.inv_strings):
string = {}
string['name'] = self.inv_strings[string_id]['s_name']
for key in self.string_keys:
prop = f'dc_{key}_{string_id}'
if hasattr(self, prop):
s_exists = True
string[key] = getattr(self, prop)
if s_exists:
strings.append(string)
return strings
def __dict__(self):
"""
Get all known data
:return: dict of properties
:rtype: dict
"""
data = super().__dict__()
data['phases'] = self.phases
data['strings'] = self.strings
data['temperature'] = self.temperature
data['powerfactor'] = self.powerfactor
data['yield_total'] = 0.0
data['yield_today'] = 0.0
for string in data['strings']:
data['yield_total'] += string['energy_total']
data['yield_today'] += string['energy_daily']
ac_sum_power = 0.0
for phase in data['phases']:
ac_sum_power += phase['power']
dc_sum_power = 0.0
for string in data['strings']:
dc_sum_power += string['power']
if dc_sum_power != 0:
data['efficiency'] = round(ac_sum_power * 100 / dc_sum_power, 2)
else:
data['efficiency'] = 0.0
data['event_count'] = self.event_count
data['time'] = self.time_rx
if not self.unpack_error:
return data
class UnknownResponse(Response):
"""
Debugging helper for unknown payload format
"""
@property
def hex_ascii(self):
"""
Generate white-space separated byte representation
:return: hexlifierd byte string
:rtype: str
"""
return ' '.join([f'{byte:02x}' for byte in self.response])
def validate_crc8(self):
"""
Checks if self.response has valid CRC8
:return: if crc is available and correct
:rtype: bool
"""
# check crc
pcrc = struct.unpack('>B', self.response[-1:])[0]
return f_crc8(self.response[:-1]) == pcrc
def validate_crc_m(self):
"""
Checks if self.response has valid Modbus CRC
:return: if crc is available and correct
:rtype: bool
"""
# check crc
pcrc = struct.unpack('>H', self.response[-2:])[0]
return f_crc_m(self.response[:-2]) == pcrc
def unpack_table(self, *args):
"""Access shared debug function"""
print_table_unpack(*args)
class EventsResponse(UnknownResponse):
""" Hoymiles micro-inverter event log decode helper """
alarm_codes = {
# HM Error Codes
1: 'Inverter start', # 0x01
2: 'DTU command failed', # 0x02
121: 'Over temperature protection', # 0x79
125: 'Grid configuration parameter error', # 0x7D
126: 'Software error code 126', # 0x7E
127: 'Firmware error', # 0x7F
128: 'Software error code 128', # 0x80
129: 'Software error code 129', # 0x81
130: 'Offline', # 0x82
141: 'Grid overvoltage', # 0x8D
142: 'Average grid overvoltage', # 0x8E
143: 'Grid undervoltage', # 0x8F
144: 'Grid overfrequency', # 0x90
145: 'Grid underfrequency', # 0x91
146: 'Rapid grid frequency change', # 0x92
147: 'Power grid outage', # 0x93
148: 'Grid disconnection', # 0x94
149: 'Island detected', # 0x95
205: 'Input port 1 & 2 overvoltage', # 0xCD
206: 'Input port 3 & 4 overvoltage', # 0xCE
207: 'Input port 1 & 2 undervoltage', # 0xCF
208: 'Input port 3 & 4 undervoltage', # 0xD0
209: 'Port 1 no input', # 0xD1
210: 'Port 2 no input', # 0xD2
211: 'Port 3 no input', # 0xD3
212: 'Port 4 no input', # 0xD4
213: 'PV-1 & PV-2 abnormal wiring', # 0xD5
214: 'PV-3 & PV-4 abnormal wiring', # 0xD6
215: 'PV-1 Input overvoltage', # 0xD7
216: 'PV-1 Input undervoltage', # 0xD8
217: 'PV-2 Input overvoltage', # 0xD9
218: 'PV-2 Input undervoltage', # 0xDA
219: 'PV-3 Input overvoltage', # 0xDB
220: 'PV-3 Input undervoltage', # 0xDC
221: 'PV-4 Input overvoltage', # 0xDD
222: 'PV-4 Input undervoltage', # 0xDE
301: 'Hardware error code 301', # 0x012D
302: 'Hardware error code 302', # 0x012E
303: 'Hardware error code 303', # 0x012F
304: 'Hardware error code 304', # 0x0130
305: 'Hardware error code 305', # 0x0131
306: 'Hardware error code 306', # 0x0132
307: 'Hardware error code 307', # 0x0133
308: 'Hardware error code 308', # 0x0134
309: 'Hardware error code 309', # 0x0135
310: 'Hardware error code 310', # 0x0136
311: 'Hardware error code 311', # 0x0137
312: 'Hardware error code 312', # 0x0138
313: 'Hardware error code 313', # 0x0139
314: 'Hardware error code 314', # 0x013A
# MI Error Codes
5041: 'Error code-04 Port 1', # 0x13B1
5042: 'Error code-04 Port 2', # 0x13B2
5043: 'Error code-04 Port 3', # 0x13B3
5044: 'Error code-04 Port 4', # 0x13B4
5051: 'PV Input 1 Overvoltage/Undervoltage', # 0x13BB
5052: 'PV Input 2 Overvoltage/Undervoltage', # 0x13BC
5053: 'PV Input 3 Overvoltage/Undervoltage', # 0x13BD
5054: 'PV Input 4 Overvoltage/Undervoltage', # 0x13BE
5060: 'Abnormal bias', # 0x13C4
5070: 'Over temperature protection', # 0x13CE
5080: 'Grid Overvoltage/Undervoltage', # 0x13D8
5090: 'Grid Overfrequency/Underfrequency', # 0x13E2
5100: 'Island detected', # 0x13EC
5120: 'EEPROM reading and writing error', # 0x1400
5150: '10 min value grid overvoltage', # 0x141E
5200: 'Firmware error', # 0x1450
8310: 'Shut down', # 0x2076
9000: 'Microinverter is suspected of being stolen' # 0x2328
}
def __init__(self, *args, **params):
super().__init__(*args, **params)
crc_valid = self.validate_crc_m()
if crc_valid:
#logging.debug(' payload has valid modbus crc')
self.response = self.response[:-2]
self.status = struct.unpack('>H', self.response[:2])[0]
self.a_text = self.alarm_codes.get(self.status, 'N/A')
logging.info (f'Inverter status: {self.a_text} ({self.status})')
chunk_size = 12
for i_chunk in range(2, len(self.response), chunk_size):
chunk = self.response[i_chunk:i_chunk+chunk_size]
logging.debug(' '.join([f'{byte:02x}' for byte in chunk]) + ': ')
if (len(chunk[0:6]) < 6):
logging.error(f'length of chunk must be greater or equal 6 bytes: {chunk}')
return
opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6])
a_text = self.alarm_codes.get(a_code, 'N/A')
logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}')
dbg = ''
for fmt in ['BBHHHHH']:
dbg += f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk))
logging.debug(dbg)
def __dict__(self):
""" Base values, availabe in each __dict__ call """
data = super().__dict__()
data['inv_stat_num'] = self.status
data['inv_stat_txt'] = self.a_text
return data
class HardwareInfoResponse(UnknownResponse):
def __init__(self, *args, **params):
super().__init__(*args, **params)
"""
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_Build_Hour_Minute, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 },
{ FLD_unknown, UNIT_NONE, CH0, 10, 2, 1 },
{ FLD_unknown, UNIT_NONE, CH0, 12, 2, 1 },
{ FLD_CRC-M, UNIT_NONE, CH0, 14, 2, 1 }
};
self.response = bytes('\x27\x1a\x07\xe5\x04\x4d\x03\x4a\x00\x68\x00\x00\x00\x00\xe6\xfb', 'latin1')
"""
def __dict__(self):
""" Base values, availabe in each __dict__ call """
data = super().__dict__()
if (len(self.response) != 16):
logging.error(f'HardwareInfoResponse: data length should be 16 bytes - measured {len(self.response)} bytes')
logging.error(f'HardwareInfoResponse: data: {self.response}')
return data
logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", self.response[0:16])}')
fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10])
fw_version_maj = int((fw_version / 10000))
fw_version_min = int((fw_version % 10000) / 100)
fw_version_pat = int((fw_version % 100))
fw_build_mm = int(fw_build_mmdd / 100)
fw_build_dd = int(fw_build_mmdd % 100)
fw_build_HH = int(fw_build_hhmm / 100)
fw_build_MM = int(fw_build_hhmm % 100)
logging.info(f'Firmware: {fw_version_maj}.{fw_version_min}.{fw_version_pat} '\
f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\
f'HW revision {hw_id}')
data['FW_ver_maj'] = fw_version_maj
data['FW_ver_min'] = fw_version_min
data['FW_ver_pat'] = fw_version_pat
data['FW_build_yy'] = fw_build_yyyy
data['FW_build_mm'] = fw_build_mm
data['FW_build_dd'] = fw_build_dd
data['FW_build_HH'] = fw_build_HH
data['FW_build_MM'] = fw_build_MM
data['FW_HW_ID'] = hw_id
return data
class DebugDecodeAny(UnknownResponse):
"""Default decoder"""
def __init__(self, *args, **params):
super().__init__(*args, **params)
crc8_valid = self.validate_crc8()
if crc8_valid:
logging.debug(' payload has valid crc8')
self.response = self.response[:-1]
crc_valid = self.validate_crc_m()
if crc_valid:
logging.debug(' payload has valid modbus crc')
self.response = self.response[:-2]
l_payload = len(self.response)
logging.debug(f' payload has {l_payload} bytes')
logging.debug('')
logging.debug('Field view: int')
print_table_unpack('>B', self.response)
logging.debug('')
logging.debug('Field view: shorts')
print_table_unpack('>H', self.response)
logging.debug('')
logging.debug('Field view: longs')
print_table_unpack('>L', self.response)
try:
if len(self.response) > 2:
logging.debug(' type utf-8 : ' + self.response.decode('utf-8'))
except UnicodeDecodeError:
logging.debug(' type utf-8 : utf-8 decode error')
try:
if len(self.response) > 2:
logging.debug(' type ascii : ' + self.response.decode('ascii'))
except UnicodeDecodeError:
logging.debug(' type ascii : ascii decode error')
# 1121-Series Intervers, 1 MPPT, 1 Phase
class Hm300Decode01(HardwareInfoResponse):
""" 1121-series Firmware version / date """
class Hm300Decode02(EventsResponse):
""" 1121-series Inverter generic events log """
class Hm300Decode0B(StatusResponse):
""" 1121-series mirco-inverters status data """
@property
def dc_voltage_0(self):
""" String 1 VDC """
return self.unpack('>H', 2)[0]/10
@property
def dc_current_0(self):
""" String 1 ampere """
return self.unpack('>H', 4)[0]/100
@property
def dc_power_0(self):
""" String 1 watts """
return self.unpack('>H', 6)[0]/10
@property
def dc_energy_total_0(self):
""" String 1 total energy in Wh """
return self.unpack('>L', 8)[0]
@property
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 12)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[0]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
""" Phase 1 VAC """
return self.unpack('>H', 14)[0]/10
@property
def ac_current_0(self):
""" Phase 1 ampere """
return self.unpack('>H', 22)[0]/100
@property
def ac_power_0(self):
""" Phase 1 watts """
return self.unpack('>H', 18)[0]/10
@property
def ac_frequency_0(self):
""" Grid frequency in Hertz """
return self.unpack('>H', 16)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 20)[0]/10
@property
def powerfactor(self):
""" Powerfactor """
return self.unpack('>H', 24)[0]/1000
@property
def temperature(self):
""" Inverter temperature in °C """
return self.unpack('>h', 26)[0]/10
@property
def event_count(self):
""" Event counter """
return self.unpack('>H', 28)[0]
class Hm300Decode0C(Hm300Decode0B):
""" 1121-series mirco-inverters status data """
class Hm300Decode11(EventsResponse):
""" 1121-series Inverter generic events log """
class Hm300Decode12(EventsResponse):
""" 1121-series Inverter major events log """
# 1141-Series Inverters, 2 MPPT, 1 Phase
class Hm600Decode01(HardwareInfoResponse):
""" 1141-Series Firmware version / date """
class Hm600Decode02(EventsResponse):
""" 1141-Series Inverter generic events log """
class Hm600Decode0B(StatusResponse):
""" 1141-series mirco-inverters status data """
@property
def dc_voltage_0(self):
""" String 1 VDC """
return self.unpack('>H', 2)[0]/10
@property
def dc_current_0(self):
""" String 1 ampere """
return self.unpack('>H', 4)[0]/100
@property
def dc_power_0(self):
""" String 1 watts """
return self.unpack('>H', 6)[0]/10
@property
def dc_energy_total_0(self):
""" String 1 total energy in Wh """
return self.unpack('>L', 14)[0]
@property
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 22)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[0]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def dc_voltage_1(self):
""" String 2 VDC """
return self.unpack('>H', 8)[0]/10
@property
def dc_current_1(self):
""" String 2 ampere """
return self.unpack('>H', 10)[0]/100
@property
def dc_power_1(self):
""" String 2 watts """
return self.unpack('>H', 12)[0]/10
@property
def dc_energy_total_1(self):
""" String 2 total energy in Wh """
return self.unpack('>L', 18)[0]
@property
def dc_energy_daily_1(self):
""" String 2 daily energy in Wh """
return self.unpack('>H', 24)[0]
@property
def dc_irradiation_1(self):
""" String 2 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[1]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
""" Phase 1 VAC """
return self.unpack('>H', 26)[0]/10
@property
def ac_current_0(self):
""" Phase 1 ampere """
return self.unpack('>H', 34)[0]/100
@property
def ac_power_0(self):
""" Phase 1 watts """
return self.unpack('>H', 30)[0]/10
@property
def ac_frequency_0(self):
""" Grid frequency in Hertz """
return self.unpack('>H', 28)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 32)[0]/10
@property
def powerfactor(self):
""" Powerfactor """
return self.unpack('>H', 36)[0]/1000
@property
def temperature(self):
""" Inverter temperature in °C """
return self.unpack('>h', 38)[0]/10
@property
def event_count(self):
""" Event counter """
return self.unpack('>H', 40)[0]
class Hm600Decode0C(Hm600Decode0B):
""" 1141-series mirco-inverters status data """
class Hm600Decode11(EventsResponse):
""" 1141-Series Inverter generic events log """
class Hm600Decode12(EventsResponse):
""" 1141-Series Inverter major events log """
# 1161-Series Inverters, 2 MPPT, 1 Phase
class Hm1200Decode01(HardwareInfoResponse):
""" 1161-Series Firmware version / date """
class Hm1200Decode02(EventsResponse):
""" 1161-Series Inverter generic events log """
class Hm1200Decode0B(StatusResponse):
""" 1161-series mirco-inverters status data """
@property
def dc_voltage_0(self):
""" String 1 VDC """
return self.unpack('>H', 2)[0]/10
@property
def dc_current_0(self):
""" String 1 ampere """
return self.unpack('>H', 4)[0]/100
@property
def dc_power_0(self):
""" String 1 watts """
return self.unpack('>H', 8)[0]/10
@property
def dc_energy_total_0(self):
""" String 1 total energy in Wh """
return self.unpack('>L', 12)[0]
@property
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 20)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[0]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def dc_voltage_1(self):
""" String 2 VDC """
return self.unpack('>H', 2)[0]/10
@property
def dc_current_1(self):
""" String 2 ampere """
return self.unpack('>H', 6)[0]/100
@property
def dc_power_1(self):
""" String 2 watts """
return self.unpack('>H', 10)[0]/10
@property
def dc_energy_total_1(self):
""" String 2 total energy in Wh """
return self.unpack('>L', 16)[0]
@property
def dc_energy_daily_1(self):
""" String 2 daily energy in Wh """
return self.unpack('>H', 22)[0]
@property
def dc_irradiation_0(self):
""" String 2 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[1]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
@property
def dc_voltage_2(self):
""" String 3 VDC """
return self.unpack('>H', 24)[0]/10
@property
def dc_current_2(self):
""" String 3 ampere """
return self.unpack('>H', 26)[0]/100
@property
def dc_power_2(self):
""" String 3 watts """
return self.unpack('>H', 30)[0]/10
@property
def dc_energy_total_2(self):
""" String 3 total energy in Wh """
return self.unpack('>L', 34)[0]
@property
def dc_energy_daily_2(self):
""" String 3 daily energy in Wh """
return self.unpack('>H', 42)[0]
@property
def dc_irradiation_0(self):
""" String 3 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[2]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3)
@property
def dc_voltage_3(self):
""" String 4 VDC """
return self.unpack('>H', 24)[0]/10
@property
def dc_current_3(self):
""" String 4 ampere """
return self.unpack('>H', 28)[0]/100
@property
def dc_power_3(self):
""" String 4 watts """
return self.unpack('>H', 32)[0]/10
@property
def dc_energy_total_3(self):
""" String 4 total energy in Wh """
return self.unpack('>L', 38)[0]
@property
def dc_energy_daily_3(self):
""" String 4 daily energy in Wh """
return self.unpack('>H', 44)[0]
@property
def dc_irradiation_0(self):
""" String 4 irratiation in percent """
if self.inv_strings is None:
return None
if self.inv_strings[3]['s_maxpower'] == 0:
return 0.00
return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
""" Phase 1 VAC """
return self.unpack('>H', 46)[0]/10
@property
def ac_current_0(self):
""" Phase 1 ampere """
return self.unpack('>H', 54)[0]/100
@property
def ac_power_0(self):
""" Phase 1 watts """
return self.unpack('>H', 50)[0]/10
@property
def ac_frequency_0(self):
""" Grid frequency in Hertz """
return self.unpack('>H', 48)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 52)[0]/10
@property
def powerfactor(self):
""" Powerfactor """
return self.unpack('>H', 56)[0]/1000
@property
def temperature(self):
""" Inverter temperature in °C """
return self.unpack('>h', 58)[0]/10
@property
def event_count(self):
""" Event counter """
return self.unpack('>H', 60)[0]
class Hm1200Decode0C(Hm1200Decode0B):
""" 1161-series mirco-inverters status data """
class Hm1200Decode11(EventsResponse):
""" 1161-Series Inverter generic events log """
class Hm1200Decode12(EventsResponse):
""" 1161-Series Inverter major events log """