Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sniffer mode to capture firmware updates #1650

Open
broth-itk opened this issue Jan 15, 2024 · 31 comments
Open

Sniffer mode to capture firmware updates #1650

broth-itk opened this issue Jan 15, 2024 · 31 comments
Labels
enhancement New feature or request

Comments

@broth-itk
Copy link
Contributor

Is your feature request related to a problem? Please describe.

OpenDTU team want captures of firmware upgrades of the inverters.

Describe the solution you'd like

Switch OpenDTU in "sniffer" mode to listen, receive and store all communication from Hoymiles DTU to the inverters.

The captured data should be send over the network to syslog server for further analysis.

Wishlist:

  • Listen mode
  • Syslog implementation (for debug output on console)

Once we have (unencrypted?) firmware dumps of the inverters, we can start disassembling ;-)

Comments are welcome. Thanks!

Describe alternatives you've considered

No response

Additional context

No response

@broth-itk broth-itk added the enhancement New feature or request label Jan 15, 2024
@stefan123t
Copy link

stefan123t commented Jan 18, 2024

Promiscuous mode would be possible as you can set both DTU and inverter Serial ID on the current menus. Those could be added both/all to the maximum five NRF pipes.

Difficulty would be to guess the channels used by both Hoymiles DTU and inverter. But switching between the five NRF channels might be sufficient as the messages/packets are usually repeated 15 times/transmitted 16 times. So switching channel after 2-4ms may be sufficient, especially if configurable.

Payload parsing may not be needed in the first place. Though that and a pass-through repeater mode may be added at a later stage ;)

@broth-itk
Copy link
Contributor Author

I don't like the idea of channel hopping for captures.
As OpenDTU has the same inverters configured as the Hoymiles DTU, we can scan all channels until we see active communication with matching serials.
That channel is locked in and dumped to syslog.

@stefan123t
Copy link

The typical WR <-> DTU messages are repeated by the NRF radio 15 times, i.e. 16 packets as you can see in the following pictures.

One packet with 15 repeats takes about ~35 ms to be sent, i.e. a single packet will take about 2,5-3 ms with a ~1 ms gap between repeated packets.
(The larger gaps between consecutive packets / messages sent from the WR -> DTU are usually about 9-10 ms each.)

image

So switching the five channels after 2-5 ms listening on each should allow you to listen on each channel 2-3 times during the complete transmission period for a single packet with all its retransmits. And also the inverter is changing the channel from time to time, which has been known as channel hopping.

Please note that it may be easier (and has been done initially with the Hoymiles DTU Lite too) to simply hook two serial2usb converters onto the RX/TX pins of an original Hoymiles DTU: BAUD 125000, 8N1 should be the correct settings for the UART communication between the main cpu and the RF chips.

See #1540 (comment)

@broth-itk
Copy link
Contributor Author

@stefan123t OK, since I have an original DTU, it might be easier to make both signals available from the outside to capture data.

As long I don't have to open the lid of the transceiver, all is fine :)

@broth-itk
Copy link
Contributor Author

broth-itk commented Jan 19, 2024

@stefan123t

Here you have it, my new HMDUSI - HoyMiles Dual USB Sniffer Interface

IMG_5327
IMG_5328
IMG_5329

Now I need to do some software to capture data with exact time stamps.

Do you happen to know something which can be used?

@phol
Copy link

phol commented Jan 19, 2024

I think capturing the data should be very straightforward with a simple python script. If you run that twice in two separate terminal windows, I think you're good.

Make sure pyserial is installed: pip install pyserial

import serial
import datetime

# Replace baud_rate with the actual Hoymiles baud rate
def read_serial(port, baud_rate=9600, log_location='log.txt'):
    try:
        ser = serial.Serial(port, baud_rate, timeout=1)
        print(f"Connected to {port}")

        try:
            with open(log_location, "a") as file:
                while True:
                    if ser.in_waiting > 0:
                        data = ser.readline().decode('utf-8').rstrip()
                        timestamp = datetime.datetime.now()
                        log_entry = f"{timestamp}: {data}\n"
                        print(log_entry)
                        file.write(log_entry)

        except KeyboardInterrupt:
            print("Interrupted by user, exiting.")

        except Exception as e:
            print(f"Error: {e}")

    except serial.SerialException as e:
        print(f"Serial error: {e}")

    finally:
        try:
            ser.close()
            print("Serial port closed.")
        except:
            print("Error closing serial port.")

# Replace with your port, e.g., 'COM3' for Windows, '/dev/ttyUSB0' for Linux/macOS
read_serial('/dev/tty.usbserial-0001', 9600, "log_1.txt")

Use Ctrl + C to interrupt and close the script.
Make sure NOT to call the script serial.py, as that will cause conflicts with the serial library. Call it something else instead like serial_log.py.

@stefan123t
Copy link

stefan123t commented Jan 19, 2024

According to other users info the BAUD rate is 125000 and 8N1 for parity, start/stop bits.

The Grid Profile update has been traced #900 (comment) by now, but you can still contribute the firmware update if you want. Eventuall they both use the 0x0A DOWN_DAT main command, though that ist just a quick guess.

To get a firmware dump you would need access to the JTAG/SWD port on the inverter. Some other users do have that at the moment and have contributed a firmware dump already.

@broth-itk
Copy link
Contributor Author

broth-itk commented Jan 28, 2024

I modified the code a bit to provide a more or less usable output.
Attached some files with the outputs (oringial towards this final version)-

Before doing any more steps with the DTU and updates, can you please take a look to the dumps and check if this is usable at all?

Thanks!

log_1.txt
log_2.txt

#!/usr/bin/python3

import serial
import datetime
import binascii

# Replace baud_rate with the actual Hoymiles baud rate


def read_serial(port, baud_rate=9600, log_location='log.txt'):
    try:
        ser = serial.Serial(port, baud_rate, timeout=1)
        print(f"Connected to {port}")

        try:
            with open(log_location, "a") as file:
                while True:
                    if ser.in_waiting > 0:
                        data = ser.read(ser.in_waiting)
                        h = binascii.hexlify(data)
                        timestamp = datetime.datetime.now()
                        log_entry = f"A: {timestamp}: {h}\n"
                        print(log_entry)
                        file.write(log_entry)
                        file.flush()

        except KeyboardInterrupt:
            print("Interrupted by user, exiting.")

        except Exception as e:
            print(f"Error: {e}")

    except serial.SerialException as e:
        print(f"Serial error: {e}")

    finally:
        try:
            ser.close()
            print("Serial port closed.")
        except:
            print("Error closing serial port.")


# Replace with your port, e.g., 'COM3' for Windows, '/dev/ttyUSB0' for Linux/macOS
read_serial('/dev/ttyUSB0', 125000, "log_1.txt")

@phol
Copy link

phol commented Jan 28, 2024

Nice that you got it working! I'm glad I could contribute this small bit.
I'm unfortunately not the right person to answer your question, but hopefully this will get us closer to supporting firmware updates and setting grid profiles.

PS:
If you add the file extension to the start of a markdown code block, you enable syntax highlighting. :)

```py

@broth-itk
Copy link
Contributor Author

broth-itk commented Jan 28, 2024

Thanks for your hint, slways something new to learn ;-)

I will keep the logging running for a while.
Did "grid profile update" already and some other maintenance tasks.
Maybe we catch something of interest here.

@broth-itk
Copy link
Contributor Author

broth-itk commented Jan 28, 2024

Here we go, these logs were collected during 5 to 6 hours until night came to send the inverters asleep.

log_1.txt.gz
log_2.txt.gz

I can't tell which of these logs are RX or TX but following log has above combined and sorted by time:

log_combined_sorted.txt.gz

This should make analysis easier.
Maybe someone can spot a new command or something we haven't found yet.

I'll detach the DTU and use OpenDTU instead now.

@broth-itk
Copy link
Contributor Author

broth-itk commented Jan 31, 2024

Any feedback about the logs? Are the captures usable?

I plan to perform firmware updates on all inverters on friday.

@stefan123t
Copy link

stefan123t commented Feb 1, 2024

Hi @broth-itk, I did not have time to look into the logs in detail.
The hex format is a bit difficult to read, so I will have to post-process them to understand what is going on.
Before that I can not say what the content / commands are that are used in the logs.

A: 2024-01-28 11:46:54.330986: b'~\xd6\x80\x16F3\x80\x16F3\x01\x00\x15!\xe3\x7f~\xd6\x80\x16F3\x80\x16F3\x01\x00\x15!\xe3\x7f~\xd6\x80\x16F3\x80\x16F3\x01\x00\x15!\xe3\x7f~\xd6\x80\x16F3\x80\x16F3\x01\x00\x15!\xe3\x7f'
...
A: 2024-01-28 11:53:58.630405: b'7ed6801646338016463301001521e37f'
B: 2024-01-28 11:53:58.684149: b'7e5683199047831990470215212114557f'
B: 2024-01-28 11:53:58.828190: b'7e5683199047831990470215212114557f'
B: 2024-01-28 11:53:58.988290: b'7e5683199047831990470215212114557f'
A: 2024-01-28 11:53:59.078706: b'7ed6801646338016463301001521e37f'
B: 2024-01-28 11:53:59.084321: b'7e5680164633801646330115212114567f'
B: 2024-01-28 11:53:59.132321: b'7e5680164633801646330115212114567f'
A: 2024-01-28 11:53:59.142698: b'7ed6801646338016463301001521e37f'
B: 2024-01-28 11:53:59.196374: b'7e5683199047831990470215212114557f'
B: 2024-01-28 11:53:59.340453: b'7e5683199047831990470215212114557f'
B: 2024-01-28 11:53:59.484530: b'7e5683199047831990470215212114557f'
A: 2024-01-28 11:53:59.591014: b'7ed6801646338016463301001521e37f'
B: 2024-01-28 11:53:59.596584: b'7e5680164633801646330115212114567f'
B: 2024-01-28 11:53:59.644618: b'7e158319904783199047800b0065b6437c000000000000000015a7c07f'
B: 2024-01-28 11:54:00.092834: b'7e15831990478319904780120065b6437c0000000000000000ccbf187f'
B: 2024-01-28 11:54:00.845265: b'7e15813130708131307080120065b6437d5d00000000000000005cb2847f'
A: 2024-01-28 11:54:01.592603: b'7ed6801646338016463301001521e37f'
B: 2024-01-28 11:54:01.597569: b'7e5680164633801646330115212114567f'
B: 2024-01-28 11:54:01.645613: b'7e5683199047831990470215212114557f'
B: 2024-01-28 11:54:01.789726: b'7e5683199047'
B: 2024-01-28 11:54:01.805766: b'831990470215212114557f'

I just looked into the combined output again and found that after several minutes of the \xd6 chatter the signal improves and I can actually read part of the data being sent.
The last two lines are obviously separated, though they belong to the same frame starting with 7e and ending with 7f.
The SPI communication uses 7e SOF (Start of Frame) and 7f EOF (End of Frame) and some escape mechanism to replace these two bytes in case they occur otherwise in the data.

Given the above communication B: are the TX messages from the DTU to the inverter (e.g. Main command 0x56)
whereas A: are the RX responses from the inverter to the DTU (e.g. reply to command 0x56 | 0x80 = 0xd6).

@broth-itk
Copy link
Contributor Author

Thanks for your feedback!
Well, the proposed python script gave weird outputs and I don't think the DTU is using UTF-8 to communicate to its radio module.
The script can still be improved by

  • reading the commands one by one
  • parsing/formatting the output at your convinience

But as time is precious, if the collected dumps are complete, postprocessing can always be done.

@broth-itk
Copy link
Contributor Author

Here you go, these logs include the firmware update of two -4T inverters to 1.0.27 (Firmware Build Date 2023-06-05 10:24:00):

log_1.txt.fwupd.gz
log_2.txt.fwupd.gz

@stefan123t
Copy link

@broth-itk I have downloaded them but I will not be able to analyse them in the coming weeks.
But many thanks for making the traces, I am already excited to learn some new "commands"
which we can add as a new feature to OpenDTU/AhoyDTU at a later stage.

@broth-itk
Copy link
Contributor Author

@stefan123t Thanks! The logs contain all data since startup of the DTU. Maybe there is something interesting - indeed.
Good luck!
Cheers!

@stefan123t
Copy link

stefan123t commented Oct 20, 2024

Huch ich sollte mal die Logs vom Firmware Update von @broth-itk auswerten und wie versprochen dokumentieren.

@broth-itk ich habe das log_1.txt ein wenig aufgeräumt, die Kommandos auf dem UART / der Seriellen Schnittstelle zwischen MCU und NRF / CMT Modul beginnen immer mit 7e und enden auf 7f, wenn das nicht der Fall ist, dann wurde beim Logging etwas umgebrochen, was nicht sein muß / sein sollte. Ich habe den folgenden Regex dazu verwendet (?<!7f)'\n[AB]: [0-9-]{10} [0-9:.]{15}: b'(?!7e), vielleicht willst Du das noch in Dein Python Script einarbeiten ?

@broth-itk
Copy link
Contributor Author

@stefan123t ist das tatsächlich so, dass die Frames stets mit 7e beginnen und mit 7f enden? Ist sichergestellt, dass ein 7e innerhalb eines Pakets nicht auftritt ;-)

Das Script kann ich gerne anpassen und demnächst einen neuen Trace (wegen Power Distribution Logic) ziehen.
Das wird sicherlich einige interessieren und wir sollten sicherstellen, dass die Tracedaten vollständig und verwendbar sind.

@stefan123t
Copy link

Hallo Bernhard @broth-itk ja die 7e/7f werden durch 7d5e/7d5f escaped, 7d selbst durch 7d5d. Ich habe es hier detailliert beschrieben: #1421 (comment)

@broth-itk
Copy link
Contributor Author

Danke @stefan123t, habe das Script angepasst und werde das morgen tagsüber ausprobieren.
Bevor ich das Firmware/Grid Profile Update laufen lasse, würde ich Dir einen kurzen Trace zur Verfügung stellen.
Nicht, dass am Ende die Traces nicht passen :)

@stefan123t
Copy link

@broth-itk Danke die Traces passen prinzipiell schon so wie sie jetzt sind.
Die Umbrüche und Ersetzungen der Escapes kann man ja leicht auch nachträglich machen.
Ich habe lediglich für die einfachere Lesbarkeit ein paar Leerzeichen eingefügt.

@broth-itk
Copy link
Contributor Author

Ich habe das Script so erweitert, dass nun alles in einer Datei (Timestamp, Hex-Dump), nach Frames getrennt und mit Prefix RX/TX versehen ist.
So ist es für den menschlichen Betrachter besser sichtbar und der zeitliche Zusammenhang erkennbar.
Das davor war eher Q&D

@broth-itk
Copy link
Contributor Author

So sieht das nun aus, denke damit kann man etwas besser arbeiten:

image

Die Frames werden einzeln aus dem Datenstrom herausgesucht und ins Log geschrieben.
Anstelle von RX/TX habe ich schlicht << und >> ins Log geschrieben da ich schlicht nicht weiß welcher USB/Serial Adapter auf welcher Leitung hängt.
Sollte aber so passen.

Ich lasse die DTU morgen mal den ganzen Tag online und beobachte.

@stefan123t
Copy link

stefan123t commented Oct 21, 2024

@broth-itk ja sieht gut aus die Richtung passt.

Z.B. das MainCmd 0x15 REQ_ARW_DAT_ALL mit dem SubCmd RealTimeRunData_Debug [sic] 0x0B ist die Anfrage nach den aktuellen Werten. Die geht von der DTU zum WR >>.

Die Antwort wäre dann 0x95 (immer MainCmd | 0x80) vom WR zur DTU <<.


Die DTU stellt aber nebenher auch ganz viele 0x56 Anfragen und bekommt 0xD6 Antworten zurück.

Weiß jemand zufällig wie das 0x56 genau aufgebaut ist ?
Es ist wohl das ChannelChangeCommand bei den HMS/HMT mit CMT2300A RF Modul.

Siehe auch hier die Dokumentation im Source Code:

Command structure:
* ID: fixed identifier and everytime 0x56
* CH: Channel to which the inverter will be switched to
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
-----------------------------------------------------------------------------------------------------------------
56 71 60 35 46 80 12 23 04 02 15 21 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (860 MHz band)
56 71 60 35 46 80 12 23 04 03 17 3c 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (900 MHz band)
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^
ID Target Addr Source Addr ? ? ? CH ? CRC8

Die Diskussion zum ChannelChangeCommand sollten wir m.E. hier weiterführen: #2137 (comment)

@broth-itk
Copy link
Contributor Author

@stefan123t

Anbei ein großer Trace von heute morgen.
Darin sind folgende Updates enthalten:

  • HMS-1800-4T/HMS-2000-4T von V01.00.27 auf V01.01.12
  • HMS-350-1T von V01.00.14 auf V01.01.00

update_communication_log.txt.gz

@AlexJacu
Copy link

AlexJacu commented Oct 28, 2024

@stefan123t
Nach dem update meines 1600 bekomme ich keine Verbindung mehr mit der Opendtu. Wele FW der opendtu hast du?

Update nachdem ich beide Geräte einmal vom Strom getrennt habe und sie wieder angeschlossen habe hat es nach einer knappen halben Stunde wieder funktioniert sie haben sich synchronisiert und jetzt läuft es wieder

@stefan123t
Copy link

@AlexJacu bitte ein eigenes Issue aufmachen oder Deinen Kommentar löschen. Hier geht es nur um die Implementierung / Dokumentation des Sniffer Modus. Danke.

@DanielR92
Copy link

So sieht das nun aus, denke damit kann man etwas besser arbeiten:

image

Die Frames werden einzeln aus dem Datenstrom herausgesucht und ins Log geschrieben. Anstelle von RX/TX habe ich schlicht << und >> ins Log geschrieben da ich schlicht nicht weiß welcher USB/Serial Adapter auf welcher Leitung hängt. Sollte aber so passen.

Ich lasse die DTU morgen mal den ganzen Tag online und beobachte.

Ich schaue mir gerade alles an um zu verstehen wie die Daten fließen.
Könntest du biutte noch die DTU-ID und den entsprechenden WR-ID mitteilen? :)

Danke.

@stefan123t
Copy link

stefan123t commented Nov 15, 2024

@DanielR92 schau mal hier sind die beiden Firmware Updates von @broth-itk

#1421 (comment)

Die sind für die beiden Inverter mit den Serial IDs 80729785 und 81313070.

Er hatte hier und weiter oben in diesem issue seine Inverter genauer beschrieben:

#2137 (comment)

These are my IDs and SW

  • DTU: 80164633 (v00.02.23)
  • HMS 1: 80729785 (1800-4T, v1.00.27)
  • HMS 2: 81313070 (2000-4T, v1.00.27)

update of two -4T inverters to 1.0.27 (Firmware Build Date 2023-06-05 10:24:00)

  • HMS 3: 83199047 (350-1T, v1.00.14)

Anbei ein großer Trace von heute morgen.
Darin sind folgende Updates enthalten:

  • HMS-1800-4T/HMS-2000-4T von V01.00.27 auf V01.01.12
  • HMS-350-1T von V01.00.14 auf V01.01.00

#2137 (comment)

Serial 1164807xxxxx
Production Year 2022
Production Week 7
Model HMS-1800-4T
Detected max. Power 1,800 W
Bootloader Version 0.0.1
Firmware Version 1.0.27
Firmware Build Date 2023-06-05 10:24:00
Hardware Part Number 269635841
Hardware Version 01.00

Serial 1164813xxxxx
Production Year 2022
Production Week 13
Model HMS-2000-4T
Detected max. Power 2,000 W
Bootloader Version 0.0.1
Firmware Version 1.0.27
Firmware Build Date 2023-06-05 10:24:00
Hardware Part Number 269644033
Hardware Version 01.00

Serial 1124831xxxxx
Production Year 2022
Production Week 31
Model HMS-350-1T
Detected max. Power 350 W
Bootloader Version 0.1.2
Firmware Version 1.0.14
Firmware Build Date 2021-12-09 12:46:00
Hardware Part Number 270541056
Hardware Version 02.00

@stefan123t
Copy link

Ach ja und die letzten Logs habe ich mir noch nicht genauer angesehen. Das müsste mW die Version mit der „Power Distribution Logic“ aus #2362 sein

#1650 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants