Skip to content
Permalink
Browse files

Version 0.0.11

  - Added is-pcap-encrypted.py for checking whether any encryption is already
    occurring in the data you captured.
  - No longer sending negative temperatures to PVOutput.
  - Add support for FS Forth-Systeme WiFi modules (in MAC address filter).
  - Added db_port configuration setting (default: 3306).
  • Loading branch information
Jerrythafast committed Jan 23, 2019
1 parent d50f189 commit 8f9812f6b8991f49c332f414048bab30ebaf1a0d
@@ -1,4 +1,4 @@
VERSION 0.0.10
VERSION 0.0.11

===============================================================================
INSTALLATION INSTRUCTIONS
@@ -86,6 +86,13 @@ commands used. Tested with Ubuntu 16.04, Python 2.7.11, MySQL 5.7.12, PHP 7.0.
===============================================================================
CHANGELOG

v0.0.11
- Added is-pcap-encrypted.py for checking whether any encryption is already
occurring in the data you captured.
- No longer sending negative temperatures to PVOutput.
- Add support for FS Forth-Systeme WiFi modules (in MAC address filter).
- Added db_port configuration setting (default: 3306).

v0.0.10
- Added support for inverters connected using WiFi modules (DigiBoard MACs).
- Fixed a minor issue in liveupdate.py that caused Python to display an extra
@@ -2,74 +2,74 @@ CREATE DATABASE solaredge;
USE solaredge;

CREATE TABLE telemetry_optimizers (
op_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
uptime SMALLINT UNSIGNED NOT NULL,
v_in SMALLINT UNSIGNED NOT NULL COMMENT '10bit steps of 0.125 V',
v_out SMALLINT UNSIGNED NOT NULL COMMENT '10bit steps of 0.125 V',
i_in SMALLINT UNSIGNED NOT NULL COMMENT '12bit steps of 0.00625 A',
e_day SMALLINT UNSIGNED NOT NULL COMMENT '16bit steps of 0.25 Wh',
temperature TINYINT NOT NULL COMMENT '8bit steps of 2 deg C',
PRIMARY KEY (timestamp, op_id),
INDEX (op_id, timestamp)
op_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
uptime SMALLINT UNSIGNED NOT NULL,
v_in SMALLINT UNSIGNED NOT NULL COMMENT '10bit steps of 0.125 V',
v_out SMALLINT UNSIGNED NOT NULL COMMENT '10bit steps of 0.125 V',
i_in SMALLINT UNSIGNED NOT NULL COMMENT '12bit steps of 0.00625 A',
e_day SMALLINT UNSIGNED NOT NULL COMMENT '16bit steps of 0.25 Wh',
temperature TINYINT NOT NULL COMMENT '8bit steps of 2 deg C',
PRIMARY KEY (timestamp, op_id),
INDEX (op_id, timestamp)
);

CREATE TABLE telemetry_inverter (
inv_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
temperature FLOAT NOT NULL,
e_day FLOAT NOT NULL,
de_day FLOAT NOT NULL,
v_ac FLOAT NOT NULL,
i_ac FLOAT NOT NULL,
frequency 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 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,
PRIMARY KEY (timestamp, inv_id)
inv_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
temperature FLOAT NOT NULL,
e_day FLOAT NOT NULL,
de_day FLOAT NOT NULL,
v_ac FLOAT NOT NULL,
i_ac FLOAT NOT NULL,
frequency 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 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,
PRIMARY KEY (timestamp, inv_id)
);

CREATE TABLE telemetry_inverter_3phase (
inv_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
temperature FLOAT NOT NULL,
e_day FLOAT NOT NULL,
de_day FLOAT NOT NULL,
v_ac1 FLOAT NOT NULL,
v_ac2 FLOAT NOT NULL,
v_ac3 FLOAT NOT NULL,
i_ac1 FLOAT NOT NULL,
i_ac2 FLOAT NOT NULL,
i_ac3 FLOAT NOT NULL,
frequency1 FLOAT NOT NULL,
frequency2 FLOAT NOT NULL,
frequency3 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 COMMENT '1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 8 STANDBY',
v_1to2 FLOAT NOT NULL,
v_2to3 FLOAT NOT NULL,
v_3to1 FLOAT NOT NULL,
p_active1 FLOAT NOT NULL,
p_active2 FLOAT NOT NULL,
p_active3 FLOAT NOT NULL,
p_apparent1 FLOAT NOT NULL,
p_apparent2 FLOAT NOT NULL,
p_apparent3 FLOAT NOT NULL,
p_reactive1 FLOAT NOT NULL,
p_reactive2 FLOAT NOT NULL,
p_reactive3 FLOAT NOT NULL,
PRIMARY KEY (timestamp, inv_id)
inv_id INT UNSIGNED NOT NULL,
timestamp INT UNSIGNED NOT NULL,
temperature FLOAT NOT NULL,
e_day FLOAT NOT NULL,
de_day FLOAT NOT NULL,
v_ac1 FLOAT NOT NULL,
v_ac2 FLOAT NOT NULL,
v_ac3 FLOAT NOT NULL,
i_ac1 FLOAT NOT NULL,
i_ac2 FLOAT NOT NULL,
i_ac3 FLOAT NOT NULL,
frequency1 FLOAT NOT NULL,
frequency2 FLOAT NOT NULL,
frequency3 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 COMMENT '1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 8 STANDBY',
v_1to2 FLOAT NOT NULL,
v_2to3 FLOAT NOT NULL,
v_3to1 FLOAT NOT NULL,
p_active1 FLOAT NOT NULL,
p_active2 FLOAT NOT NULL,
p_active3 FLOAT NOT NULL,
p_apparent1 FLOAT NOT NULL,
p_apparent2 FLOAT NOT NULL,
p_apparent3 FLOAT NOT NULL,
p_reactive1 FLOAT NOT NULL,
p_reactive2 FLOAT NOT NULL,
p_reactive3 FLOAT NOT NULL,
PRIMARY KEY (timestamp, inv_id)
);

CREATE TABLE live_update (
pvo_last_live INT UNSIGNED NOT NULL,
last_0503 BINARY(34) NOT NULL,
last_telemetry INT UNSIGNED NOT NULL
pvo_last_live INT UNSIGNED NOT NULL,
last_0503 BINARY(34) NOT NULL,
last_telemetry INT UNSIGNED NOT NULL
);
INSERT INTO live_update (pvo_last_live, last_0503, last_telemetry) VALUES (0, "\0", 0);
@@ -1,4 +1,25 @@
#!/usr/bin/env python

#
# Copyright (C) 2019 Jerrythafast
#
# This file is part of se-logger, which captures telemetry data from
# the TCP traffic of SolarEdge PV inverters.
#
# se-logger is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# se-logger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with se-logger. If not, see <http://www.gnu.org/licenses/>.
#

"""
Use this script to find your SolarEdge inverter's encryption key in
PCAP network traffic capture files.
@@ -1,5 +1,25 @@
#!/usr/bin/env python

#
# Copyright (C) 2019 Jerrythafast
#
# This file is part of se-logger, which captures telemetry data from
# the TCP traffic of SolarEdge PV inverters.
#
# se-logger is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# se-logger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with se-logger. If not, see <http://www.gnu.org/licenses/>.
#

# SETTINGS
serial_port = "/dev/ttyUSB0"
inverter_id = 0x7f101234
@@ -78,4 +98,4 @@ def getKeyPart(conn, seq, inverter_id):
connection = serial.Serial(port=serial_port, baudrate=115200, timeout=0)
print(" | BARKER | LEN |LEN_I| SEQ | SOURCE | DEST | CMD |DATA")
print("Your key is '" + "".join("\\x%02x" % ord(x) for i in range(4) for x in
getKeyPart(connection, i+1, inverter_id)) + "'")
getKeyPart(connection, i+1, inverter_id)) + "'")
@@ -1,5 +1,25 @@
#!/usr/bin/env python

#
# Copyright (C) 2019 Jerrythafast
#
# This file is part of se-logger, which captures telemetry data from
# the TCP traffic of SolarEdge PV inverters.
#
# se-logger is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# se-logger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with se-logger. If not, see <http://www.gnu.org/licenses/>.
#

# SETTINGS
serial_port = "/dev/ttyUSB0"
inverter_id = 0x7f101234
@@ -72,4 +92,4 @@ def getKeyPart(conn, seq, inverter_id):

connection = serial.Serial(serial_port, 115200)
print("Your key is '" + "".join("\\x%02x" % ord(x) for i in range(4) for x in
getKeyPart(connection, i+1, inverter_id)) + "'")
getKeyPart(connection, i+1, inverter_id)) + "'")
@@ -0,0 +1,68 @@
#!/usr/bin/env python

#
# Copyright (C) 2019 Jerrythafast
#
# This file is part of se-logger, which captures telemetry data from
# the TCP traffic of SolarEdge PV inverters.
#
# se-logger is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# se-logger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with se-logger. If not, see <http://www.gnu.org/licenses/>.
#

"""
Use this script to quickly check whether captured data is encrypted.
"""
import sys, glob

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


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)

packets = 0
encrypted = 0

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

pos = -1
while True:
pos = data.find("\x12\x34\x56\x79", pos + 1)
if pos == -1 or pos > len(data) - 20:
break
packets += 1
if ord(data[pos+18]) + (ord(data[pos+19]) << 8) in (0x0503, 0x003d):
encrypted += 1

if not packets:
print("No SolarEdge data found in input.")
elif not encrypted:
print("None of the SolarEdge data found appears to be encrypted.")
elif packets == encrypted:
print("All SolarEdge data found is encrypted.")
else:
print("%.1f%% of SolarEdge data found is encrypted." %
(100. * encrypted / packets))
@@ -1,15 +1,37 @@
#!/usr/bin/env python

#
# Copyright (C) 2019 Jerrythafast
#
# This file is part of se-logger, which captures telemetry data from
# the TCP traffic of SolarEdge PV inverters.
#
# se-logger is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# se-logger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with se-logger. If not, see <http://www.gnu.org/licenses/>.
#

import struct, sys, MySQLdb, time
from collections import namedtuple

__version__ = "0.0.10"
__version__ = "0.0.11"

# SETTINGS
inverter_private_key = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
db_user = "dbuser"
db_pass = "dbpassword"
db_name = "solaredge"
db_host = "localhost"
db_port = 3306



@@ -312,7 +334,7 @@ def get_data_from_pcap(self, f):
packet_offset -= tcpheaderlen-tcphdrlen
data = f.read(ipdatalen-ipheaderlen-tcpheaderlen) # This is the actual data.
packet_offset -= ipdatalen-ipheaderlen-tcpheaderlen
if etherhdr[6:9] in ("\x00\x27\x02", "\x00\x40\x9d"): # Inverter speaking.
if etherhdr[6:9] in ("\x00\x27\x02", "\x00\x40\x9d", "\x00\x04\xf3"): # Inverter speaking.

# Treat data gaps as loss if not filled within 60 seconds.
self.give_up_gaps(pcaptime)
@@ -354,11 +376,11 @@ def get_data_from_pcap(self, f):
#############################################################################################

class DBManager:
def __init__(self, user, passwd, db, host, retries=5):
def __init__(self, user, passwd, db, host, port, retries=5):
self.retries = retries
while retries:
try:
self.conn = MySQLdb.connect(user=user, passwd=passwd, db=db, host=host)
self.conn = MySQLdb.connect(user=user, passwd=passwd, db=db, host=host, port=port)
self.cursor = self.conn.cursor()
retries = 0
except MySQLdb.Error as e:
@@ -479,7 +501,7 @@ def eprint(message):


# Connect to database and get last 0503 message.
db = DBManager(db_user, db_pass, db_name, db_host)
db = DBManager(db_user, db_pass, db_name, db_host, db_port)
db.execute("SELECT last_0503 FROM live_update")
last_0503 = db.fetchone()[0]

0 comments on commit 8f9812f

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