taptap 0.1.1

An implementation of the Tigo TAP protocol
Documentation
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
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
# Tigo TAP protocol

This document describes the wired protocol used by Tigo TAPs.

## Background

Tigo makes solar power equipment, including a [system of module-level power electronics](https://www.tigoenergy.com/ts4)
that attach to each solar panel to provide functions like DC optimization, monitoring, and/or rapid shutdown. The full
system involves "smart" devices attached to solar panels, a gateway ("TAP") near those modules, and a cable between that
gateway and a controller.

```text
                       ┌───────────┬───────────┬───────────┬───────────┐
                       │   Solar   │   Solar   │   Solar   │   Solar   │
                       │   Panel   │   Panel   │   Panel   │   Panel   │
┌───┐                  └───┬───┬───┴───┬───┬───┴───┬───┬───┴───┬───┬───┘
│TAP│◀ ─ ─ ─Wireless─ ─ ─ ▶│Opt│◀ ─ ─ ▶│Opt│◀ ─ ─ ▶│Opt│◀ ─ ─ ▶│Opt│
└───┘                      └───┘       └───┘       └───┘       └───┘
  ▲
  │
Wired
  │
  ▼
┌───┐
│CCA│
└───┘
```

The controller collects performance data about the associated solar array. Unfortunately, this information is only
accessible via the manufacturer's cloud services, limiting it to relatively low frequency, low fidelity, and low
availability compared to the hardware itself. Tigo considers this to be a closed system, making both the wired and
wireless components [proprietary protocols](https://en.wikipedia.org/wiki/Proprietary_protocol).

The wired protocol is especially interesting because understanding it would allow passive, external, local monitoring of
this system with no loss of fidelity or unnecessary delay.

```text
┌───┐
│TAP│◀ ─ ─ ─ …
└───┘
  ▲
  │
  │
  ├──────┐
  ▼      ▼
┌───┐  ┌───┐
│CCA│  │O_o│
└───┘  └───┘
```

This approach to monitoring is low risk because it would be completely transparent. The first-party controller remains
in command of all associated equipment at all times, and in fact it cannot even determine whether it is being
monitored. (A full controller replacement would be a much larger project with many more pitfalls.)

The information here is based on the author's observations from a 2024 solar installation consisting of a Tigo CCA, a
Tigo TAP, and 135x Tigo [TS4-A-O](https://www.tigoenergy.com/product/ts4-a-o) DC optimizers. The author developed this
document and the [`taptap` software](https://github.com/willglynn/taptap) for the purpose of interoperability with this
system, and the author is sharing this work so that others may interoperate with their own systems in a similar way.
This information is certainly both incorrect and incomplete, especially with respect to other devices in the product
family, to installations involving multiple TAPs, and to the older "star" systems circa 2018 rather than the newer
"mesh" systems.

This information is provided with no warranty, express or implied.

## Architecture

This system is mostly proprietary but can be described in terms of the [OSI
model](https://en.wikipedia.org/wiki/OSI_model). There are two different networks.

```text
                     Gateway                PV device
                   device (TAP)            (optimizer)
               ┌─────────────────┐     ┌─────────────────┐
       PV   ┌─▶│   Application   │     │   Application   │   Proprietary
  network   │  ├─────────────────┤     ├─────────────────┤    │
            │  │     Network     │     │     Network     │    │
            │  ├─────────────────┤     ├─────────────────┤
            │  │      Link       │     │      Link       │   802.15.4
            │  ├─────────────────┤     ├─────────────────┤    │
            │  │    Physical     │     │    Physical     │    │
            │  └─────────────────┘     └─────────────────┘
            │                  ▲         ▲
            │                  └ ─ ─ ─ ─ ┘
            │  ┌─────────────────┐
  Gateway   └─▶│    Transport    │                           Proprietary
  network      ├─────────────────┤                            │
               │      Link       │                            │
               ├─────────────────┤
               │    Physical     │                           RS-485
               └─────────────────┘
```

Gateways serve as bridges between the gateway network and the PV network. Remote devices are reachable via the PV
network – that is its purpose – but high level gateway functions are accessed indirectly via a command interface which
mostly interacts with the PV network too. The PV network is thus the primary interface for this system, and the gateway
network exists to provide a tunnel from the controller into the PV network.

This document is structured to match the diagram above.

* [Gateway physical layer]#gateway-physical-layer
* [Gateway link layer]#gateway-link-layer
* [Gateway transport layer]#gateway-transport-layer
* [PV physical layer]#pv-physical-layer
* [PV link layer]#pv-link-layer
* [PV network layer]#pv-network-layer
* [PV application layer]#pv-application-layer

This document is written from the perspective of an external device observing this system, so it begins at the bottom of
the diagram.

## Gateway physical layer

The gateway physical layer provides a shared, byte-oriented, half duplex serial bus. The wired components communicate
over 2-wire [RS-485](https://en.wikipedia.org/wiki/RS-485) at 38400 baud, 8 data bits, no parity, 1 stop bit.

RS-485 is designed for multi-access networks. The manufacturer uses this capability to support connecting multiple
gateways (i.e. TAPs) to the same cable. They intend customers to daisy-chain gateways as needed, they recommend using a
120Ω termination resistor on the far end of bus, and they integrated a termination resistor into the controller device
to ensure that the controller end is always terminated.

Terminating both ends is good practice for RS-485. The bus can be joined from any point along its length, and the bus
can be lengthened as required.

Having said that, while it is good practice to join the bus between the terminators, termination is comparatively
unimportant for receive-only interfaces, and 38400 not a fast baud rate. If your intent is to monitor, positioning
an RS-485 interface (such as a USB RS-485 adapter) a few inches from the controller and connecting it in parallel to the
controller's A and B lines ought to work fine – just take care not to terminate the bus a third time.

## Gateway link layer

The gateway link layer provides unreliable, addressed, checksummed datagram service over the gateway physical layer. Bus
arbitration is implicit, handled by the assumption that gateways reply only when asked, and there are no explicit bus
coordination or flow control mechanisms at this layer.

```text
  Bytes on the bus:  FF 7E 07 92 01 01 49 00 FF EC 66 40 D6 21 7E 08
          Preamble:  FF
    Start of frame:     7E 07
              Body:           92 01 01 49 00 FF EC 66 40 D6 21
      End of frame:                                            7E 08
```

Gateways precede their frames with `FF`, while controllers precede their frames with `00 FF FF`. The implicit bus
arbitration scheme means that it is possible for the controller to transmit request A, give up waiting for a response,
and then begin transmitting request B at the same moment that the gateway attempts to transmit response A. `00` and `FF`
are opposite bus states, so the preamble may be intended for collision detection.

### Escaping

Frames begin with the sequence `7E 07` and are terminated by the sequence `7E 08`. A delimited data format requires some
mechanism to escape the delimiter, and indeed `7E` is used as an escape character, with 7 escape sequences replacing
certain data bytes.

| Escaped | Raw  |
|---------|------|
| `7E 00` | `7E` |
| `7E 01` | `24` |
| `7E 02` | `23` |
| `7E 03` | `25` |
| `7E 04` | `A4` |
| `7E 05` | `A3` |
| `7E 06` | `A5` |

Bytes `23`…`25`, `A3`…`A5` never appear on the bus; they are always substituted with the associated escape sequence
prior to transmission. (Perhaps these bytes pose some backwards compatibility hazard?) Regardless, a receiver must
invert these escape sequences to recover the intended frame contents, since frames certainly _do_ contain these bytes.

### Contents

Gateway frames contain of an address, a type, zero or more payload bytes, and a 16-bit checksum.

```
    Bytes on the bus:  FF 7E 07 92 01 01 49 00 FF 7C DB C2 7E 05 85 7E 08
  Delimiters removed:           92 01 01 49 00 FF 7C DB C2 7E 05 85
   Escaping reversed:           92 01 01 49 00 FF 7C DB C2 A3 85
             Address:           92 01
                Type:                 01 49
             Payload:                       00 FF 7C DB C2
            Checksum:                                      A3 85
```

The address field is a 16-bit quantity. Link layers normally have a source and destination address, but this network
supports only a single controller with no gateway-to-gateway communication, so the address field serves as either "to:
this gateway" or "from: this gateway", indicated by clearing or setting the `80 00` bit respectively. The resulting
addresses (that is, gateway IDs) are therefore 15 bits.

### Checksum

The checksum algorithm is [CRC-16/CCITT](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) with the 0x8408 (0x1021)
polynomial, except that it selects 0x8408 as an initial value just to [avoid being
compatible](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Obfuscation). This checksum covers the address, type,
and payload together. A detailed checksum calculation for the frame above follows.

```text
                                                        0x8408
    (0x8408 >> 8) ^ CRC_TABLE[(0x8408 & 0xFF) ^ 0x92] = 0x3B57
    (0x3B57 >> 8) ^ CRC_TABLE[(0x3B57 & 0xFF) ^ 0x01] = 0x3788
    (0x3788 >> 8) ^ CRC_TABLE[(0x3788 & 0xFF) ^ 0x01] = 0x19FE
    (0x19FE >> 8) ^ CRC_TABLE[(0x19FE & 0xFF) ^ 0x49] = 0xC12D
    (0xC12D >> 8) ^ CRC_TABLE[(0xC12D & 0xFF) ^ 0x00] = 0xFA26
    (0xFA26 >> 8) ^ CRC_TABLE[(0xFA26 & 0xFF) ^ 0xFF] = 0x4BB6
    (0x4BB6 >> 8) ^ CRC_TABLE[(0x4BB6 & 0xFF) ^ 0x7C] = 0x691D
    (0x691D >> 8) ^ CRC_TABLE[(0x691D & 0xFF) ^ 0xDB] = 0xA353
    (0xA353 >> 8) ^ CRC_TABLE[(0xA353 & 0xFF) ^ 0xC2] = 0x85A3
```

The resulting 0x85A3 checksum is encoded as `A3 85`.

## Gateway transport layer

The gateway transport layer provides reliable exchange of [PV network packets](#pv-network-layer) between the gateways
and the controller, as well as enumeration of attached gateways.

### Receive request

The controller asks for packets a gateway has received by sending a receive request frame (gateway frame type `01 48`).

Receive request payloads consist of a packet number and some unknown fields.

```text
            00 FF FF 7E 07 12 01 01 48 00 01 18 83 04 17 44 7E 08
      Type:                      01 48
   Payload:                            00 01 18 83 04
       ???:                            00 01
  Packet #:                                  18 83
       ???:                                        04
```

Gateways autonomously receive packets over-the-air and record them into an internal buffer. There is no signal that a
gateway has received a PV network packet; the controller must poll each gateway, asking for any packets received after a
given packet number. The gateway will usually respond reporting no packets, but it may also respond with one or more
packets. If the controller successfully received that response, the subsequent request's packet number will be
incremented one or more times; if not, it will still have the earlier packet number and the gateway will retransmit.

```text
Receive request, packet number 0x1883
           00 FF FF 7E 07 12 01 01 48 00 01 18 83 04 17 44 7E 08
  Address:                12 01
     Type:                      01 48
  Payload:                            00 01 18 83 04
 Packet #:                                  18 83

Receive response, 1 packet
           FF 7E 07 92 01 01 49 00 FE 01 83 5A DE 07 00 0A 01 14 63 3A … 7E 08
  Address:          92 01
     Type:                01 49
  Payload:                      00 FE 01 83 5A DE 07 00 0A 01 14 63 3A …

Receive request, packet number 0x1884
           00 FF FF 7E 07 12 01 01 48 00 01 18 84 04 1F 09 7E 08
  Address:                12 01
     Type:                      01 48
  Payload:                            00 01 18 84 04
 Packet #:                                  18 84
```

### Receive response

The answers a receive request frame with a receive response frame (gateway frame type `01 49`). The receive response
frame contains status information followed by zero or more PV network packets.

Most status fields change relatively slowly compared to the frequency of receive polling. Transmitting the full set of
status fields each request/response cycle would be wasteful, so the gateway selectively reveals fields which it believes
are interesting to the controller. It uses one of several status structures depending on which fields it wishes to
disclose.

The largest receive response status structure is `00 E0`, while the smallest is `00 FF`. This 7 byte difference in size
adds up to 1.8 milliseconds, and avoiding this delay is the only reason to introduce this complexity. Gateways
select `00 E0` every 16 receive requests to ensure all fields are regularly synchronized even if some receive responses
are garbled or lost.

The receive response begins with a 16-bit bitfield. The bitfield is read right-to-left. A zero indicates the field is
included. After the indicated optional fields, the Rx response has the low half of the packet number, the slot counter,
and zero or more PV network packets.

```text
Status type: 00 E0
             0    0    E    0
             0000 0000 1110 0000
                               0   Rx buffers used (1 byte)
                              0    Tx buffers free (1 byte)
                             0     ??? A (2 bytes)
                            0      ??? B (2 bytes)
                          0        Packet # high (1 byte)
```

```text
        Payload: 00 E0 04 0E 00 01 02 00 40 FB 21 1B …
    Status type: 00 E0
Rx buffers used:       04
Tx buffers free:          0E
          ??? A:             00 01
          ??? B:                   02 00
  Packet # high:                         40
   Packet # low:                            FB
   Slot counter:                               21 1B
        Packets:                                     …

        Payload: 00 FE 02 FF 21 22 …
    Status type: 00 FE
Rx buffers used:       02
   Packet # low:          FF
   Slot counter:             21 22
        Packets:                   …

        Payload: 00 EE 00 41 01 21 27 …
    Status type: 00 EE
Rx buffers used:       00
  Packet # high:          41
   Packet # low:             01
   Slot counter:                21 27
        Packets:                      …

        Payload: 00 FF 03 21 31 …
    Status type: 00 FF
   Packet # low:       03
   Slot counter:          21 31
        Packets:                …
```

The slot counter seems to be known by remote devices at the [PV link layer](#pv-link-layer), so the slot counter is
documented there.

Each received packet is preceded by a packet header. The author belives this conceptually belongs to the [PV network
layer](#pv-network-layer) and documented it there.

### Command request

The controller can issue a command to the gateway application by sending a command request (gateway frame type `0B 0F`).

```text
                00 FF FF 7E 07 12 01 0B 0F 00 00 00 06 66 00 09 5E 30 30 49 6E 66 6F 0D 3F 8C 7E 08
          Type:                      0B 0F
       Payload:                            00 00 00 06 66 00 09 5E 30 30 49 6E 66 6F 0D
           ???:                            00 00 00
PV packet type:                                     06
    Sequence #:                                        66
  Command data:                                           00 09 5E 30 30 49 6E 66 6F 0D
```

Commands may be answered by the gateway itself immediately via a [command response](#command-response), or the command
may cause a PV network transmission for execution on a remote PV node with the answer delivered sometime later via a
[receive response](#receive-response).

PV packet types and associated command data conceptually belong to the [PV application layer](#pv-application-layer).

The sequence # is chosen by the controller. The value normally increments each command, though the controller avoids
using sequence numbers `FF` or `00`.

### Command response

The gateway responds to a command request frame with a command response frame (gateway frame type `0B 10`).

```text
                 FF 7E 07 92 01 0B 10 00 0D 00 07 66 75 77 7E 08
           Type:                0B 10
        Payload:                      00 0D 00 07 66
            ???:                      00
Tx buffers free:                         0D
            ???:                            00
 PV packet type:                               07
     Sequence #:                                  66
           Data:
```

Some commands return data immediately, with the response data in the command response immediately following the 5-byte
header. This data depends on the command's packet type and is documented at the [PV application
layer](#pv-application-layer).

The author has not observed failed commands. Presumably the controller resends its command request if no command
response is received within some timeout (handling a lost command request), and presumably the gateway buffers command
responses and replays the response to provide idempotence (handling a lost command response).

If the command transmits a packet, the transmitted packet will use a Tx buffer at least until the packet has entered
the gateway's [PV link layer](#pv-link-layer), but more likely until it has been received and acknowledged by a remote
node's PV link layer. (This remote node may or may not be the destination, but as soon as it's received an 802.15.4
acknowledgment, the gateway will not need to retransmit it again and can therefore reclaim that buffer.) Presumably the
controller avoids transmitting when there are no Tx buffers available.

### Ping request

The controller occasionally sends ping requests (gateway frame type `0B 00`).

```text
             00 FF FF 7E 07 12 01 0B 00 01 FE 83 7E 08
       Type:                      0B 00
       Data:                            01
```

The gateway replies with a ping response (gateway frame type `0B 01`).

```text
             FF 7E 07 92 01 0B 01 01 73 10 7E 08
       Type:                0B 01
       Data:                      01
```

### Enumeration start request

The controller sends an enumeration start request (gateway frame type `00 14`) to the broadcast address (`00 00`)
containing unknown data. This is how the controller identifies which gateway(s) are attached to the cable. When the
controller wishes to enumerate gateways, it sends this several times.

```text
                 00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
     Unescaped:                 00 00 00 14 37 24 92 66 12 35 06 1A
       Address:                 00 00
          Type:                       00 14
           ???:                             37 24 92 66
Enumeration ID:                                         12 35
```

The "Enumerate at:" address is a temporary gateway address used for the enumeration process. All gateways receiving an
enumeration start request cease responding at their current gateway ID and begin responding at the requested enumeration
ID instead.

The 32-bit `???` field above appears repeatedly and unchanged during the enumeration process.

<details><summary>Example enumeration sequence</summary>
<p>The enumeration sequence is quite long, but it is illustrative, particularly with respect to the changing addresses.
A full example for a single gateway follows.</p>

```text
Ping request
              00 FF FF 7E 07 12 01 0B 00 01 FE 83 7E 08
    Address:                 12 01
       Type:                       0B 00

Ping response
              FF 7E 07 92 01 0B 01 01 73 10 7E 08
    Address:           92 01
       Type:                 0B 01


Enumeration start request #1
              00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
    Address:                 00 00
       Type:                       00 14

Enumeration start response #1
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15

Enumeration start request #2
              00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
    Address:                 00 00
       Type:                       00 14

Enumeration start response #2
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15

Enumeration start request #3
              00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
    Address:                 00 00
       Type:                       00 14

Enumeration start response #3
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15

Enumeration start request #4
              00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
    Address:                 00 00
       Type:                       00 14

Enumeration start response #4
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15

Enumeration start request #5
              00 FF FF 7E 07 00 00 00 14 37 7E 01 92 66 12 35 06 1A 7E 08
    Address:                 00 00
       Type:                       00 14

Enumeration start response #5
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15


Enumeration request #1
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38

Enumeration response
              FF 7E 07 92 35 00 39 04 C0 5B 30 00 02 BE 16 12 35 A7 83 7E 08
    Address:           92 35
       Type:                 00 39

Assign gateway ID request #1
              00 FF FF 7E 07 12 35 00 3C 37 7E 01 92 66 04 C0 5B 30 00 02 BE 16 12 01 58 0B 7E 08
    Address:                 12 35
       Type:                       00 3C

Assign gateway ID response #1
              FF 7E 07 92 35 00 3D 99 08 7E 08
    Address:           92 35
       Type:                 00 3D


Enumeration request #2
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38

Enumeration request #3
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38

Enumeration request #4
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38

Enumeration request #5
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38

Enumeration request #6
              00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
    Address:                 12 35
       Type:                       00 38


Identify request #1
              00 FF FF 7E 07 12 01 00 3A 87 B4 7E 08
    Address:                 12 01
       Type:                       00 3A

Identify response #1
              FF 7E 07 92 01 00 3B 04 C0 5B 30 00 02 BE 16 12 01 E6 A6 7E 08
    Address:           92 01
       Type:                 00 3B

Assign gateway ID request #2
              00 FF FF 7E 07 12 01 00 3C 37 7E 01 92 66 04 C0 5B 30 00 02 BE 16 12 02 DC 60 7E 08
    Address:                 12 01
       Type:                       00 3C

Assign gateway ID response #2
              FF 7E 07 92 01 00 3D 56 ED 7E 08
    Address:           92 01
       Type:                 00 3D

Identify request #2
              00 FF FF 7E 07 12 02 00 3A E3 5B 7E 08
    Address:                 12 02
       Type:                       00 3A

Identify response #2
              FF 7E 07 92 02 00 3B 04 C0 5B 30 00 02 BE 16 12 02 8A 9A 7E 08
    Address:           92 02
       Type:                 00 3B


Unknown 0010
              00 FF FF 7E 07 00 00 00 10 37 7E 01 92 66 C3 27 7E 08
    Address:                 00 00
       Type:                       00 10

Unknown 0011
              FF 7E 07 80 00 00 11 33 A6 7E 08
    Address:           80 00
       Type:                 00 11


Identify request #3
              00 FF FF 7E 07 12 01 00 3A 87 B4 7E 08
    Address:                 12 01
       Type:                       00 3A

Identify response #3
              FF 7E 07 92 01 00 3B 04 C0 5B 30 00 02 BE 16 12 01 E6 A6 7E 08
    Address:           92 01
       Type:                 00 3B

Version request
              00 FF FF 7E 07 12 01 00 0A 04 85 7E 08
    Address:                 12 01
       Type:                       00 0A

Version response
              FF 7E 07 92 01 00 0B 4D 67 61 74 65 20 56 65 72 73 69 6F 6E 20 47 38 2E 35 39 0D 4A 75 6C 20 20 36 20 32 30 32 30 0D 31 36 3A 35 31 3A 35 31 0D 47 57 2D 48 31 35 38 2E 34 2E 33 53 30 2E 31 32 0D 8A E2 7E 08
    Address:           92 01
       Type:                 00 0B


Enumeration end request
              00 FF FF 7E 07 12 01 0E 02 5C 93 7E 08
    Address:                 12 01
       Type:                       0E 02

Enumeration end response
              FF 7E 07 92 01 00 06 06 62 7E 08
    Address:           92 01
       Type:                 00 06


Ping request
              00 FF FF 7E 07 12 01 0B 00 01 FE 83 7E 08
    Address:                 12 01
       Type:                       0B 00

Ping response
              FF 7E 07 92 01 0B 01 01 73 10 7E 08
    Address:           92 01
       Type:                 0B 01
```

</details>

### Enumeration start response

The gateway responds to the enumeration start request by sending an enumeration start response (gateway frame type
`00 15`) from the broadcast address (`80 00`).

```text
              FF 7E 07 80 00 00 15 17 E0 7E 08
    Address:           80 00
       Type:                 00 15
```

### Enumeration request

After starting enumeration, the controller sends an enumeration request (gateway frame type `00 38`) to the enumeration
address (here `12 35`).

```text
               00 FF FF 7E 07 12 35 00 38 5A 72 7E 08
     Address:                 12 35
        Type:                       00 38
```

### Enumeration response

The gateway responds with an enumeration response (gateway frame type `00 39`) from the enumeration address (here
`92 35`). The response contains the [PV link layer hardware address](#pv-link-layer) of the gateway and its current
gateway ID, which was reset to the enumeration ID.

```text
               FF 7E 07 92 35 00 39 04 C0 5B 30 00 02 BE 16 12 35 A7 83 7E 08
     Address:           92 35
        Type:                 00 39
PV long addr:                       04 C0 5B 30 00 02 BE 16
  Gateway ID:                                               12 35
```

The author has exactly one gateway and is unclear about how this works for multiple gateways. Perhaps they take a random
delay and use collision detection such that _a_ gateway will respond, responding with _which_ gateway did so.

### Assign gateway ID request

The controller sends an assign gateway ID request (gateway frame type `00 3C`) to the enumeration ID (here `12 35`).
This request payload includes the hardware address of the intended recipient, likely to disambiguate in the case of
multiple gateways, since at this moment multiple gateways may be configured to use the same gateway ID.

```text
               00 FF FF 7E 07 12 35 00 3C 37 7E 01 92 66 04 C0 5B 30 00 02 BE 16 12 01 58 0B 7E 08
   Unescaped:                 12 35 00 3C 37 24 92 66 04 C0 5B 30 00 02 BE 16 12 01
     Address:                 12 35
        Type:                       00 3C
         ???:                             37 24 92 66
PV long addr:                                         04 C0 5B 30 00 02 BE 16
  Gateway ID:                                                                 12 01
```

Gateway ID assignment is complicated by [unknown 0011](#unknown-00100011).

### Assign gateway ID response

The gateway replies with an assign gateway ID response (gateway frame type `00 3D`) from the enumeration ID (here
`93 35`).

```text
              FF 7E 07 92 35 00 3D 99 08 7E 08
    Address:           92 35
       Type:                 00 3D
```

The gateway then continues enumerating by sending more [enumeration requests](#enumeration-request) until it is
satisfied that no more unidentified gateways are waiting.

### Identify request

The controller sends an identify request (gateway frame type `00 3A`). This is identical to an [enumeration
request](#enumeration-request), except directed at a particular gateway.

```text
              00 FF FF 7E 07 12 01 00 3A 87 B4 7E 08
    Address:                 12 01
       Type:                       00 3A
```

### Identify response

The gateway responds to an identify request by sending an identify response (gateway frame type `00 3B`). This is
identical to an [enumeration response](#enumeration-response), except directed from a particular gateway.

```
               FF 7E 07 92 01 00 3B 04 C0 5B 30 00 02 BE 16 12 01 E6 A6 7E 08
     Address:           92 01
        Type:                 00 3B
PV long addr:                       04 C0 5B 30 00 02 BE 16
  Gateway ID:                                               12 01
```

### Unknown 0010/0011

The controller sends gateway frame type `00 10` to address `00 00` containing unknown data.

```text
              00 FF FF 7E 07 00 00 00 10 37 7E 01 92 66 C3 27 7E 08
  Unescaped:                 00 00 00 10 37 24 92 66 C3 27
    Address:                 00 00
       Type:                       00 10
        ???:                             37 24 92 66
```

The gateway replies with gateway frame type `00 3D` from address `00 00`.

```text

              FF 7E 07 80 00 00 11 33 A6 7E 08
    Address:           80 00
       Type:                 00 11
```

In the example exchange, the controller starts enumerating, assigns gateway ID `12 01` via the enumeration address,
identifies the gateway at `12 01`, then assigns gateway ID `12 02` via address `12 01`, identifies the gateway at
`12 02`, then sends unknown gateway frame type `00 11` to the broadcast address, then identifies the gateway at `12 01`
again. After this, the controller does not address the gateway at `12 02` again.

### Version request

The controller sends a version request (gateway frame type `00 0A`) to a gateway.

```text
              00 FF FF 7E 07 12 01 00 0A 04 85 7E 08
    Address:                 12 01
       Type:                       00 0A
```

### Version response

The gateway responds to a version request by sending a version response (gateway frame type `00 0B`) containing a
version string.

```text
              FF 7E 07 92 01 00 0B 4D 67 61 … 7E 08
    Address:           92 01
       Type:                 00 0B
    Version:                       4D 67 61 …
```

In the author's case, the gateway version string is `"Mgate Version G8.59\rJul  6 2020\r16:51:51\rGW-H158.4.3S0.12\r"`.

### Enumeration end request

The controller ends the enumeration process by sending an enumeration end request (gateway frame type `0E 02`).

```text
              00 FF FF 7E 07 12 01 0E 02 5C 93 7E 08
    Address:                 12 01
       Type:                       0E 02
```

### Enumeration end response

The gateway responds to an enumeration end request with an enumeration end response (gateway frame type `00 06`).

```text
              FF 7E 07 92 01 00 06 06 62 7E 08
    Address:           92 01
       Type:                 00 06
```

## PV physical layer

The PV network uses [IEEE 802.15.4](https://en.wikipedia.org/wiki/IEEE_802.15.4) to exchange data between the gateways
and devices affixed to photovoltaic modules (providing functions like rapid shutdown, monitoring, and/or optimization),
here described as "PV devices", or simply "nodes".

The PV physical layer provides unreliable, low power, short range wireless data exchange. PV devices use the 802.15.4
physical layer over 2.4 GHz at 250 kbps using O-QPSK. In general the PV physical layer is not directly observable from
the gateway network, but some of it is, such as the gateway's radio channel and a received signal strength indicator
(RSSI).

802.15.4 PHYs typically support frames up to 127 bytes long. It is not clear whether this is the precise limit here, but
[node table responses](#node-table) support the idea that the maximum permitted packet body length is >= 124 bytes and
< 134 bytes.

### Transceiver identification

One complication for identifying specific components is that all devices containing a transceiver are designed for
installation outdoors and ought to be potted in epoxy for durability. (Another complication is that the author's modules
are all installed outdoors and doing something useful.) Even so, enough information is available to make some guesses.

According to [a 2015 FCC filing](https://fcc.report/FCC-ID/XMYSILICONAIR2/), Tigo TS4 modules contain the
[NXP JN5148](https://www.nxp.com/products/no-longer-manufactured/32-bit-mcu-and-ieee802-15-4-transceiver:JN5148)
microcontroller, for which [the datasheet](https://www.mouser.com/datasheet/2/302/JN-DS-JN5148-1v7-254806.pdf) provides
detailed capabilities. This chip was discontinued and replaced by the
similar [NXP JN5168](https://www.nxp.com/part/JN5168) ([datasheet](https://www.nxp.com/docs/en/data-sheet/JN516X.pdf),
[802.15.4 stack documentation](https://www.nxp.com/docs/en/user-guide/JN-UG-3024.pdf)). It is likely that the RSSI
values reported from such devices correspond to the graph in the datasheet – roughly linear from 255 meaning -9 dBm to
20 meaning -90 dBm.

According to [a 2022 case
study](https://www.silabs.com/applications/case-studies/bluetooth-low-energy-connectivity-enhances-safety-of-solar-pv-systems),
Tigo TS4 modules contain [Silicon Labs EFR32BG21](https://www.silabs.com/wireless/bluetooth/efr32bg21-series-2-socs)
chips. That particular part number implies using Bluetooth, a suggestion which disagrees with all the other evidence, so
it is more likely that Tigo is using the multiprotocol
[EFR32MG21](https://www.silabs.com/wireless/zigbee/efr32mg21-series-2-socs)
([datasheet](https://www.silabs.com/documents/public/data-sheets/efr32mg21-datasheet.pdf),
[reference](https://www.silabs.com/documents/public/reference-manuals/efr32xg21-rm.pdf)) in ["proprietary" 802.15.4
mode](https://www.silabs.com/documents/public/application-notes/an1365-15-4-over-rail.pdf)
([docs](https://docs.silabs.com/connect-stack/latest/connect-start/)) instead. RSSI values reported from these devices
may use a different (presently unknown) scale.

Circumstantial evidence such as the relative processing capabilities of these NXP and Silicon Labs platforms, the [2016
announcement](https://news.silabs.com/2016-06-29-Silicon-Labs-Multiband-Wireless-Gecko-SoCs-Break-New-Ground-in-the-IoT)
of the Silicon Labs EFR32 platform, the known breaks between Tigo's older "star" and newer "mesh" hardware, and the fact
that [Tigo rolled out "mesh" in
2018](https://www.businesswire.com/news/home/20180718005827/en/Tigo-Releases-State-of-the-art-Wireless-Technology-–-Mesh-–-as-the-New-Solar-Communication-Architecture-for-the-TS4-Platform)
supports the supposition that "star" systems use NXP JN51 chips while "mesh" systems use Silicon Labs EFR32 chips.

## PV link layer

The PV link layer provides reliable, addressed, checksummed, possibly-authenticated-and-maybe-encrypted wireless data
exchange over the PV physical layer. PV devices use the 802.15.4 link layer. In general, the PV link layer is not
directly observable from the gateway network.

### Addressing

The 802.15.4 link layer includes a 64-bit "long address" (EUI-64). This value works like an 802.3 Ethernet MAC address:
it's stamped into a device at the factory, it's stamped onto the packaging outside, etc. All PV network nodes, including
gateways, have fixed 64-bit long addresses built into the hardware.

The 802.15.4 link layer includes a 16-bit "short address". These short addresses are used instead of the long address
in most frame formats. They are dynamically assigned (when the device associates with a PAN coordinator, see
802.15.4-2020 § 6.4.1) and ephemeral. Given that most of the PV network shuts down when it gets dark outside, it's best
to assume that these addresses are not stable. Short addressees leak out in various data structures, presumably places
where the link layer data is known but where other context (like knowledge of which device has this link layer
identifier) is not guaranteed.

Gateways maintain a (persistent?) [node table](#node-table) relating 16-bit PV node IDs to 64-bit long addresses. These
PV node IDs are used instead of either the short or long addresses for most gateway-facing operations. If this was
intended to contain the complexity of the two address formats above, it failed, because it isn't used _everywhere_. The
controller – and any system which wishes to monitor the controller – has to collate all three kinds of addresses.

### Barcodes

Tigo barcodes are an alternate representation of the 802.15.4 long address. `4-9A57A2L` corresponds to
`04:C0:5B:40:00:9A:57:A2`. Tigo's 24-bit vendor prefix is implied (`04:C0:5B`), the `4` indicates the following hex
nibble, the `-` indicates an arbitrary number of zeroes, and all but the final character are the remaining portion of
the address. The final character is a CRC.

```text
  Barcode:          4  -  9A 57 A2 L
   Vendor: 04:C0:5B:
   Nibble:          4
   Zeroes:           0:00:
   Digits:                9A:57:A2
    CRC-4:                         L
```

Tigo selected a CRC-4 using polynomial 0x3 and an initial value of 0x2. (The CRC is similar to [this
one](https://stackoverflow.com/questions/54507106/how-to-classify-following-crc4-implementation), where a Stack Overflow
user notes "it seems unlikely that anyone would use a CRC with so few bytes in a practical application.") The CRC covers
the entire address. The calculation for this example is:

```text
                                   0x2
    CRC_TABLE[0x04 ^ (0x2 << 4)] = 0x6
    CRC_TABLE[0xC0 ^ (0x6 << 4)] = 0x4
    CRC_TABLE[0x5B ^ (0x4 << 4)] = 0xB
    CRC_TABLE[0x40 ^ (0xB << 4)] = 0x6
    CRC_TABLE[0x00 ^ (0x6 << 4)] = 0xD
    CRC_TABLE[0x9A ^ (0xD << 4)] = 0xA
    CRC_TABLE[0x57 ^ (0xA << 4)] = 0xF
    CRC_TABLE[0xA2 ^ (0xF << 4)] = 0x4
```

The resulting CRC nibble is stringified using an alternate set of 16 digits: `GHJKLMNPRSTVWXYZ`. `0x4` is `L`.

### Beacons

The 802.15.4 standard specifies several operating modes and the appropriate modes here involve gateways sending beacons.
These beacons establish a common timebase for all participants, and this shared clock is used to mediate access to the
channel, i.e. who is allowed to transmit when (see 802.15.4-2020 § 6.2). The standard also includes a guaranteed
timeslot mechanism (GTS, see § 6.8) and there is some indication that this is in use. The [gateway radio
configuration](#gateway-radio-configuration) message seems very related.

Gateway beacons likely include the "PV on"/"PV off" signal as part of their [broadcasts](#broadcast), as this would be a
reasonable way to implement the "shut down on command" feature. PV modules also support a separate autonomous "shut down
on controller power failure" feature, which could be triggered by loss of beacons in particular or by disassociation in
general.

### Slot counter

Gateways maintain a slot counter whose current value is known by all network participants. (It may be an 802.15.4-2020
§ 6.2.6.2 absolute slot number, or it may be communicated to listening nodes by some other means.) The slot counter is
read back to the controller via the gateway transport layer [receive responses](#receive-response). Other PV nodes use
the slot counter to synchronize measurements ([power reports](#power-report)) and to agree on the timing of these
reports ([PV configuration](#pv-configuration)). Each slot takes 5 ± 1% milliseconds, varying with the gateway's
internal oscillator.

The slot counter increments autonomously but the values are discontinuous. The slot counter starts at 0x0000 and
increments through 0x2EDF (11999), then jumps to 0x4000 and increments through 0x6EDF (0x4000 + 11999), etc. Put another
way: the top two bits count epochs, while the lower 14 bits count the interval [0, 12000) within each epoch. With 12,000
slots per epoch and 5 ± 1% milliseconds per slot, one epoch is 60 ± 1% seconds.

## PV network layer

The PV network layer provides unreliable, addressed, routed wireless packet service for the PV devices. Note that this
is still an unreliable network, meaning the possibility of PV packet drops requires the controller to explicitly
retransmit packets as needed, even though the reliability of the [gateway transport layer](#gateway-transport-layer)
means the controller can be certain that the gateway received the original transmission.

PV nodes in a "mesh" system have some explicit routing behavior whereby they choose another node as an intermediary to
reach the gateway. This selection is described by [topology reports](#topology-report).

The author belives it is likely that PV network packets share the header returned by the [receive
response](#receive-response) frame, and therefore describes this header as part of the PV network layer. A PV network
packet has the following structure:

```text
         Packet: 07 00 0A 01 14 63 3A …
           Type: 07
     PV node ID:    00 0A
  Short address:          01 14
            DSN:                63
    Data length:                   3A
           Data:                      …58 bytes…
```

The DSN appears to be sourced from the gateway's 802.15.4 MAC state and not from the corresponding PV link layer frame
header. When a PV node aggregates two packets for transmission – say, sending two separate power reports together – the
gateway describes both packets as having the same DSN, even though their sequence numbers would have differed over the
air.

## PV application layer

The author captured PV packets midday to midday on a system using a Tigo Cloud Connect Advanced controller and 135x Tigo
TS4-A-O optimizers. The packet counts below are illustrative of the directions and frequencies despite methodological
flaws.

| Packet type                                                               | Tx   | Rx from gateway | Rx from optimizer |
|---------------------------------------------------------------------------|------|-----------------|-------------------|
| `06` [String request]#string-requestresponse                            | 2943 | 0               | 0                 |
| `07` [String response]#string-requestresponse                           | 0    | 0               | 132               |
| `09` [Topology report]#topology-report                                  | 0    | 0               | 265               |
| `0D` [Gateway radio configuration request]#gateway-radio-configuration  | 36   | 0               | 0                 |
| `0E` [Gateway radio configuration response]#gateway-radio-configuration | 0    | 36              | 0                 |
| `13` [PV configuration request]#pv-configuration                        | 134  | 0               | 0                 |
| `18` [PV configuration response]#pv-configuration                       | 0    | 0               | 129               |
| `22` [Broadcast]#broadcast                                              | 58   | 0               | 0                 |
| `23` [Broadcast ack]#broadcast                                          | 0    | 58              | 0                 |
| `26` [Node table request]#node-table                                    | 810  | 0               | 0                 |
| `27` [Node table response]#node-table                                   | 0    | 810             | 0                 |
| `2D` [Long network status request]#network-status                       | 30   | 0               | 0                 |
| `2E` [Network status request]#network-status                            | 1401 | 0               | 0                 |
| `2F` [Network status response]#network-status                           | 0    | 1431            | 0                 |
| `31` [Power report]#power-report                                        | 0    | 0               | 695057            |
| `41` [Unknown 0x41]#unknown-0x41                                        | 887  | 0               | 0                 |

### Node table

Gateways maintain a table of mesh nodes mapping 64-bit 802.15.4 long addresses to and from 16-bit PV node IDs. (Note
that these 16-bit PV node IDs are _not_ the same as the 802.15.4 16-bit short addresses.) The gateway itself is also a
mesh node and is implicitly PV node ID `00 01`.

The controller can access this by sending a node table request command (PV packet type `26`).

```text
             00 00
      Index: 00 00
```

The gateway responds with a node table response (PV packet type `27`).

```text
                  00 02 00 0C 04 C0 5B 40 00 A2 34 6F 00 02 04 C0 5B 40 00 A2 34 71 00 03 …
  Starting index: 00 02
       # entries:       00 0C
           Entry:             04 C0 5B 40 00 A2 34 6F 00 02
    Long address:             04 C0 5B 40 00 A2 34 6F
      PV node ID:                                     00 02
           Entry:                                           04 C0 5B 40 00 A2 34 71 00 03
           Entry:                                                                         …
```

Each entry consists of a 64-bit 802.15.4 long address and a 16-bit PV node ID. The table can contain gaps, e.g. this
request for index 0 responded by starting at index 2. (The gateway does not report itself as PV node ID `00 01`, even
though it is.) The end of the table is indicated by the response returning zero entries.

The controller occasionally sends node table requests to other nodes, particularly when they are offline, but they do
not respond even when they are online. This behavior may relate to [unknown 0x41](#unknown-0x41) packets.

### Network status

The controller sends a network status request (PV packet type `2E`) to the gateway (PV node ID `00 01`). This request
has no payload.

The gateway responds with a network status response (PV packet type `2F`). This request describes the number of mesh
nodes, 135 for the system below, three times. These numbers likely diverge when the gateway cannot directly receive
transmissions from every node, but when they are all equal, it is unclear which fields have which meanings.

```
               00 01 01 03 18 00 87 00 87 00 87
   PV node ID: 00 01
          ???:       01
     Counter?:          03 18
  Node count?:                00 87
  Node count?:                      00 87
  Node count?:                            00 87
```

The controller also sends a long network status request (packet type `2D`) containing a node count approximately every
15 minutes, but only overnight when all the nodes except the gateway are offline.

```text
               BA BE 02 03 84 00 87 01
          ???: BA BE 02
     Counter?:          03 84
  Node count?:                00 87
          ???:                      01
```

The gateway responds with packet type `2F`, suggesting that `2D` is an alternate or legacy network status request. It is
also possible that `2D` sets rather than retrieves the second unknown field in the response, given that the request
above produced the response below.

```text
               01 03 84 00 87 00 87 00 87
          ???: 01
      Counter:    03 84
  Node count?:          00 87
  Node count?:                00 87
  Node count?:                      00 87
```

### Gateway radio configuration

The controller can retrieve some gateway radio configuration data by sending a gateway radio configuration request (PV
packet type `0D`).

```text
       00 01
  ???: 00 01
```

The gateway responds with configuration data (PV packet type `0E`):

```text
                  00 15 24 F6 18 04 02 01 00 00 00 00 1C 01 5A (…16 bytes…) 00 00 02 00 3C 00
             ???: 00
   Radio channel:    15
          PAN ID:       24 F6
            CAP?:             18
            CFP?:                04
            BOP?:                   02
            IAP?:                      01
             ???:                         00 00 00 00 1C 01 5A
  Encryption key:                                              (…16 bytes…)
             ???:                                                           00 00 02 00 3C 00
```

The positively identified fields concern the [PV link layer](#pv-link-layer) or [PV physical layer](#pv-physical-layer),
but without more precise measurements from the radio side, it's difficult to be more specific.

802.15.4 devices communicate as part of a "Personal Area Network", or PAN, and all link-layer frames include the PAN ID.
Gateways ought to perform standard PAN ID conflict resolution per 802.15.4-2020 § 6.3.2, meaning this value is subject
to change without notice.

The encryption key matches the length expected for AES-128. It is unclear exactly how this is used, but the obvious
candidates are MIC-128 or ENC-MIC-128 per 802.15.4-2020 § 9.3.

The CAP?, CFP?, BOP?, and IAP? values could plausibly describe the structure of the gateway's 802.15.4 superframe,
specifying the durations of a contention access period, a contention free period, a beacon-only period, and an inactive
period per 802.15.4-2020 § 6.2.1. This hypothesis could be supported or falsified by radio observations which the author
has not yet conducted.

### PV configuration

The controller sends modules a PV configuration request (PV packet type `13`). This is done at low frequency, i.e. less
than once per module per day.

This packet type assigns the power report cycle's period and phase with reference to the gateway's slot counter. The
period `0F A0` is 4000 in decimal, and 4000 slots at 5±1% milliseconds per slot corresponds to a measurement interval of
20±1% seconds. Each module is assigned a different reporting phase, spreading the load and minimizing retransmissions at
the [PV link layer](#pv-link-layer).

```text
            00 39 03 00 31 02 0F A0 09 7D 00 09 02 00 00 00 00 00 30 02 00 00 00 00
PV node ID: 00 39
       ???:       03 00
      Type:             31
       ???:                02
    Period:                   0F A0
     Phase:                         09 7D
       ???:                               00 09 02 00 00 00 00 00 30 02 00 00 00 00
```

The PV node returns a configuration request (PV packet type `18`). The response includes both radio parameters (like the
802.15.4 PAN ID and radio channel) and reporting parameters (echoed from the request). Both groups are separately
duplicated, possibly indicating an alternate or backup configuration for each.

```text
              0F 24 F6 15 6C 00 (5 bytes) 03 00 30 00 00 00 00 00 31 0F A0 09 7D 00 09 00 00 00 00 (19 bytes)
         ???: 0F
      PAN ID:    24 F6
     Channel:          15
         ???:             6C 00
  Alternate?:                   (repeat)
         ???:                             03 00 30 00 00 00 00 00
        Type:                                                     31
      Period:                                                        0F A0
       Phase:                                                              09 7D
         ???:                                                                    00 09 00 00 00 00
  Alternate?:                                                                                      (repeat)
```

### Broadcast

The controller sends broadcast data (packet type `22`) to PV node ID `00 00` (which is presumably a PV node broadcast
address).

```text
          00 01
     ???: 00 0
  PV off:     1
```

When "PV off" is maintained for extended periods, the controller de-asserts and re-asserts this a few milliseconds
later at one hour intervals. (Why? Does this have an effect over-the-air?)

The gateway replies (packet type `23`) to acknowledge.

When PV off is not asserted, the controller occasionally sends empty packets of type `22` to the broadcast address and
to the gateway.

### Topology report

PV devices send topology reports (PV packet type `09`).

```text
                 00 02 00 58 00 01 00 02 04 C0 5B 40 00 9A 57 BB 9F 01 E9 E1 08 95 27
  Short address: 00 02
     PV node ID:       00 58
       Next-hop:             00 01
            ???:                   00 02
   Long address:                         04 C0 5B 40 00 9A 57 BB
           RSSI:                                                 9F
            ???:                                                    01 E9 E1 08 95 27
```

These messages are the first received by the gateway as a node joins the network at power-on, but they are also received
unsolicited during normal operation.

### Power report

PV devices periodically measure their operating parameters and send power reports (PV packet type `31`).

```text
                    2B 61 58 FF 03 21 58 81 00 6E 8F A0 7E
        Voltage in: 2B 6                                     0x2B6 = 694 * 0.05V = 34.7V
       Voltage out:     1 58                                 0x158 = 344 * 0.10V = 34.4V
  DC-DC duty cycle:          FF                              0xFF = 255 = 100%
        Current in:             03 2                         0x032 = 50 * 0.005A = 0.025A
       Temperature:                 1 58                     0x158 = 344 * 0.1ºC = 34.4ºC
               ???:                      81 00 6E
      Slot counter:                               8F A0      timestamp referenced to gateway
              RSSI:                                     7E   0x7E = 126
```

PV devices take measurements simultaneously at intervals synchronized to the gateway's slot counter. They transmit these
measurements asynchronously, often with significant latency, including delaying them more than one measurement window.
Sometimes PV devices bundle multiple power reports together into a single transmission. The slot counter field allows
the receiver to temporally correlate the power report with the gateway's slot counter when the measurement was
performed.

### String request/response

PV modules support string-based requests (PV packet type `06`) and responses (PV packet type `07`). Requests are
preceded by a PV node ID. Requests appear to be terminated by ASCII carriage return (0x0D), while responses may or may
not have such termination. The packet data for known string requests/responses are described below, using C-style string
notation.

```text
            00 3D 5E 30 30 54 65 73 74 73 0D
PV node ID: 00 2D
    String:       5E 30 30 54 65 73 74 73 0D    "^00Tests\r"
```

#### Info

Request: `"^00Info\r"`

Response: `"!Info 0000 15 0000 0981 00 0000 0000 FF 00 0000 0FFF 000 2"`

#### MPPT

Request: `"^00Mppt_1.1\r"`

Response: none
s

#### Tests

Request: `"^00Tests\r"`

Response: `"!Tests 0 0 2 0 00 0000 00\r"`

#### Smrt

Request: `"^00Smrt\r"`

Response: `"!Smrt 0FFF 0000 0FFF 0008 00C8 00C8 000A 0154 0154 S 00 01"`

#### Version

Request: `"^00Version\r"`

Response: `"Mnode Version K8.0120 (2D)\r"`

#### `w`

Request: `#00w255\r`

Response: none

### Unknown 0x41

The controller sends an unknown packet (packet type `41`) to the gateway and to other PV nodes.

```text
             00 00 00 01 00 AA 00 00 00 00 04 05 0F E8 08 95 09 FD 8F F6 05 C7
        ???: 00 00
PV node ID?:       00 01
        ???:             00 AA 00 00 00 00 04 05 0F E8 08 95 09 FD 8F F6 05 C7
```

An example sent to another node:

```text
             00 00 00 02 00 AA 00 00 00 00 04 05 0F E8 08 95 09 FD 8F F6 05 C7
        ???: 00 00
PV node ID?:       00 02
        ???:             00 AA 00 00 00 00 04 05 0F E8 08 95 09 FD 8F F6 05 C7
```

These packets do not elicit replies.

The timing is unusual: the controller sends these to the gateway when overnight it sees no other devices, it sends them
to the first couple devices immediately after sunrise when they come online, and it sends them to the last couple
devices immediately after sunset when they've already gone offline.