Skip to content
Permalink
Browse files

Version 0.0.5

 - Added support for CPU version 3.1651.
 - Added find-key-in-pcap.py to scan for the encryption key in PCAP files.
 - get-key-by-rs232.py will now print an informative error message if PySerial
   is not installed.
  • Loading branch information
Jerrythafast committed Jan 23, 2019
1 parent 1453a05 commit 4a7fddbf9305463d35985dca064d760426822582
@@ -1,12 +1,11 @@
VERSION 0.0.4
VERSION 0.0.5

===============================================================================
INSTALLATION INSTRUCTIONS

These instructions may assume working knowledge of managing Linux systems and
MySQL databases. You may need to be root (i.e., 'sudo') for some of the
commands used. Tested with Ubuntu 16.04, MySQL 5.7.12, PHP 7.0.

commands used. Tested with Ubuntu 16.04, Python 2.7.11, MySQL 5.7.12, PHP 7.0.

1. Install the required system packages:
mysql-server
@@ -22,18 +21,32 @@ commands used. Tested with Ubuntu 16.04, MySQL 5.7.12, PHP 7.0.
to get this running from a different directory, make sure to edit all
scripts accordingly.)

4. Open 'liveupdate.py' in a text editor and enter your database username and
4. Inverters with recent firmware (presumably CPU version 3 or higher) employ
data encryption when communicating with the SE servers. Newly installed
inverters send this key once within the first 2 days after connecting them
to the internet. They will not employ encryption until after they have
sent the key, so you can continue with #5 now. If your inverter is already
connected to the internet, the encryption key has to be obtained from the
inverter first over an RS232 or USB serial connection with the
'get-key-by-rs232.py' script. To use it, enter your inverter's serial
number and the name of the serial port in the SETTINGS area of the script
and run it. You may need to install the python-serial system package
(PySerial) first.

5. Open 'liveupdate.py' in a text editor and enter your database username and
password as well as the encryption key of your inverter in the SETTINGS
area at the top of the script. The encryption key can be obtained from the
inverter using an RS232 or USB connection with the 'getkey.py' script. To
use it, enter your inverter's serial number and the name of the serial port
in the SETTINGS area of 'getkey.py' and run it.
area at the top of the script. If your inverter is newly installed and
the solaredge-logger will be running from the first day it is connected to
the internet, you may not need an encryption key the first day - just leave
it at the default value for now. You'll know that it started encrypting
the data when the data stops updating. Refer to #11 when that happens.

5. Open 'pvo-upload.php' in a text editor and enter your database username and
6. Open 'pvo-upload.php' in a text editor and enter your database username and
password as well as the PVOutput API key and System ID in the SETTINGS area
at the top of the script.
at the top of the script. You may skip this step if you don't want to set
up automatic uploading to PVOutput.

6. Open 'se-logger-service.sh' in a text editor and enter the interface name
7. Open 'se-logger-service.sh' in a text editor and enter the interface name
of the network device that connects to the inverter in the SETTINGS area at
the top of the script. If traffic from multiple devices is routed over
this interface, the value of FILTER may need to be changed from 'tcp' to a
@@ -42,25 +55,42 @@ commands used. Tested with Ubuntu 16.04, MySQL 5.7.12, PHP 7.0.
If you decided not to use the default directory, make sure to update the
value of CAPTDIR accordingly.

7. Copy and/or move the 'se-logger.service' file to the '/etc/systemd/system'
8. Copy and/or move the 'se-logger.service' file to the '/etc/systemd/system'
directory. If you decided not to use the default directory, make sure to
update the path to 'se-logger-service.sh' accordingly.

8. Run the following command to enable the new service:
9. Run the following command to enable the new service:
systemctl --now enable se-logger

9. Set up cronjobs for daily restarting the service and updating PVOutput
10. Set up cronjobs for daily restarting the service and updating PVOutput
every two minutes. Run 'crontab -e' and copy the contents of 'crontab.txt'
into your crontab. If you decided not to install the logger in the default
directory, make sure to update the paths to '/opt/se-logger' accordingly.

10. If anything fails to work, you may find error messages in various log files
If you don't want to set up automatic uploading to PVOutput, remove the
line that contains 'pvo-upload.php'.

11. If your inverter was newly installed and you had not yet entered your
inverter's encryption key in the 'liveupdate.py' script, you'll know that
it started encrypting the data when the data stops updating. Then, you
can find the encryption key in the 'solaredge-###.pcap' files in the
'/opt/se-logger' directory by using the 'find-key-in-pcap.py' script. When
found, update 'liveupdate.py' with your encryption key. Then, run
'python liveupdate.py *.pcap' to update the database and run
'systemctl restart se-logger' to restart the service to keep it updated.

12. If anything fails to work, you may find error messages in various log files
that appear in the '/opt/se-logger' directory.


===============================================================================
CHANGELOG

v0.0.5
- Added support for CPU version 3.1651.
- Added find-key-in-pcap.py to scan for the encryption key in PCAP files.
- get-key-by-rs232.py will now print an informative error message if PySerial
is not installed.

v0.0.4
- liveupdate.py will no longer try to fill gaps in TCP streams that have been
terminated by the RST flag.
@@ -23,10 +23,10 @@ CREATE TABLE telemetry_inverter (
v_ac FLOAT NOT NULL,
i_ac FLOAT NOT NULL,
frequency FLOAT NOT NULL,
v_dc FLOAT NOT NULL,
v_dc FLOAT NOT NULL COMMENT 'steps of 0.0625 V',
e_total INT UNSIGNED NOT NULL,
i_rcd FLOAT NOT NULL,
mode TINYINT UNSIGNED NOT NULL,
mode TINYINT UNSIGNED NOT NULL COMMENT '1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 8 STANDBY',
p_active FLOAT NOT NULL,
p_apparent FLOAT NOT NULL,
p_reactive FLOAT NOT NULL,
@@ -0,0 +1,96 @@
#!/usr/bin/env python
"""
Use this script to find your SolarEdge inverter's encryption key in
PCAP network traffic capture files.
"""
import sys, glob

if len(sys.argv) < 2:
print("%s\n\nTry running: python %s *.pcap" %
(__doc__.strip("\r\n").replace("\r", "").replace("\n", " "), sys.argv[0]))
sys.exit(1)


def iglob(pathname):
success = False
for result in glob.iglob(pathname):
success = True
yield result
if not success:
yield pathname


"""
Finding: (** = checksum of packet, __ = bytes of key)
0 12 34 56 79 02 00 fd ff qq rr fd ff ff ff ss tt uu vv 12 00 39 02 ** **
1 12 34 56 79 06 00 f9 ff qq rr ss tt uu vv fd ff ff ff 90 00 __ __ __ __ 00 00 ** **
2 12 34 56 79 02 00 fd ff ww xx fd ff ff ff ss tt uu vv 12 00 3a 02 ** **
3 12 34 56 79 06 00 f9 ff ww xx ss tt uu vv fd ff ff ff 90 00 __ __ __ __ 00 00 ** **
4 12 34 56 79 02 00 fd ff yy zz fd ff ff ff ss tt uu vv 12 00 3b 02 ** **
5 12 34 56 79 06 00 f9 ff yy zz ss tt uu vv fd ff ff ff 90 00 __ __ __ __ 00 00 ** **
6 12 34 56 79 02 00 fd ff mm nn fd ff ff ff ss tt uu vv 12 00 3c 02 ** **
7 12 34 56 79 06 00 f9 ff mm nn ss tt uu vv fd ff ff ff 90 00 __ __ __ __ 00 00 ** **
"""
BARKER = "\x12\x34\x56\x79"
LEN1 = "\x02\x00\xfd\xff"
LEN2 = "\x06\x00\xf9\xff"
ID1 = "\xfd\xff\xff\xff"
CMD1 = "\x12\x00"
CMD2 = "\x90\x00"
PARAMS = ("\x39\x02", "\x3a\x02", "\x3b\x02", "\x3c\x02")
PTYPE = "\x00\x00"

seq = ""
id2 = ""
key = ""
state = 0

for filename in (x for x in sys.argv[1:] for x in iglob(x)):
print("Reading from %s" % filename)
if filename == "-":
f = sys.stdin
else:
f = open(filename, 'rb')
data = f.read()
f.close()

pos = -1
while True:
if state == 0:
pos = data.find(BARKER + LEN1, pos + 1)
if pos == -1:
break
if data[pos+10:pos+14] != ID1 or data[pos+18:pos+22] != CMD1 + PARAMS[0]:
continue
seq = data[pos+8:pos+10]
id2 = data[pos+14:pos+18]
state = 1
pos += 23
elif state in (1, 3, 5, 7):
pos = data.find(BARKER + LEN2 + seq + id2 + ID1 + CMD2, pos + 1)
if pos == -1:
break
if data[pos+24:pos+26] != PTYPE:
continue
key += data[pos+20:pos+24]
state += 1
pos += 27
elif state in (2, 4, 6):
pos = data.find(BARKER + LEN1, pos + 1)
if pos == -1:
break
if data[pos+10:pos+22] != ID1 + id2 + CMD1 + PARAMS[state/2]:
continue
seq = data[pos+8:pos+10]
state += 1
pos += 23
elif state == 8:
print("Found it! Your key is '%s'" % ("".join("\\x%02x" % ord(x) for x in key)))
sys.exit(0)

if state < 2:
print("Sorry, your key is not in the given input file(s).")
else:
print("Sorry, only the first part of your key is in the given input file(s).")
print("Your key starts with '%s'" % ("".join("\\x%02x" % ord(x) for x in key)))

@@ -1,12 +1,25 @@
#!/usr/bin/env python
import struct, serial, time

# SETTINGS
serial_port = "/dev/ttyUSB0"
inverter_id = 0x7f101234



import struct, time
try:
import serial
except ImportError:
import sys
sys.stderr.write(
"Error: This script requires the 'serial' (PySerial) package.\n")
sys.stderr.write(
"Install the 'python-serial' system package or try running 'pip install pyserial'.\n")
sys.stderr.write(
"Alternatively, download PySerial from https://pypi.python.org/pypi/pyserial.\n")
sys.exit(1)


crcTable = (
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
@@ -1,12 +1,25 @@
#!/usr/bin/env python
import struct, serial

# SETTINGS
serial_port = "/dev/ttyUSB0"
inverter_id = 0x7f101234



import struct
try:
import serial
except ImportError:
import sys
sys.stderr.write(
"Error: This script requires the 'serial' (PySerial) package.\n")
sys.stderr.write(
"Install the 'python-serial' system package or try running 'pip install pyserial'.\n")
sys.stderr.write(
"Alternatively, download PySerial from https://pypi.python.org/pypi/pyserial.\n")
sys.exit(1)


crcTable = (
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
@@ -331,7 +331,7 @@ def parse0500(data):
'e_day': bytes[6] | (bytes[7] << 8),
'temperature': (bytes[8] | ~0xFF) if bytes[8] & 0x80 else bytes[8]
}
elif type == 0x0010 and length == 124: # Inverter data
elif type == 0x0010 and length in (124, 174): # Inverter data
inv = SEDataInverter.parse(data, pos + 12)
yield {
'inv_id': id & ~0x00800000,

0 comments on commit 4a7fddb

Please sign in to comment.
You can’t perform that action at this time.