|
|
|
# Copyright 2021 Marcel Konstapel
|
|
|
|
# https://www.meezenest.nl/mees
|
|
|
|
#
|
|
|
|
# This file is part of SolarGeneratorDatalogger.
|
|
|
|
# A set of programs and scripts to log all the available data from a self build solar generator.
|
|
|
|
# It runs on a Raspberry Pi Zero 1 W with Raspbian
|
|
|
|
#
|
|
|
|
# It logs data from a EPEVER Soalrge Charge Controller (via Bluetooth) and an EPEVER solar charger (via RS485-USB).
|
|
|
|
# The data is analyzed and stored in a file. The data is also available via a web browser over Wifi
|
|
|
|
# (Raspberry Pi acts as access point).
|
|
|
|
# Future plan is to implement LoRa so the data can be accessed remotely.
|
|
|
|
#
|
|
|
|
# Program is configured via the file "config.json"
|
|
|
|
#
|
|
|
|
# Example of config_file:
|
|
|
|
#
|
|
|
|
# {
|
|
|
|
# "epever_data_file": "epever_data.json",
|
|
|
|
# "RS485_port": "/dev/ttyXRUSB0",
|
|
|
|
# "RS485_address": 1
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# Usage of program:
|
|
|
|
#
|
|
|
|
# -c, --config_file : reads the most important registers of the charge
|
|
|
|
# controller and writes it to the file specified in the
|
|
|
|
# config_file
|
|
|
|
# -s, --set_date : sets the date and time from the given argument with
|
|
|
|
# format: YYYY-MM-DD HH:MM:SS
|
|
|
|
# -l, --load : switches load on or off as given by the argument")
|
|
|
|
# -d, --dump_file : dumps all the registers in the specified file
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# A lot of effort is made to catch all possible communication errors, so the program never hangs.
|
|
|
|
# When an error occurs, the program exits and has probably not written any data to 'epever_data_file'
|
|
|
|
# But at least we did not crash! Check the date of the data file to make sure the data is written or not.
|
|
|
|
#
|
|
|
|
# Example of epever_data_file (JSON format):
|
|
|
|
#
|
|
|
|
#{
|
|
|
|
# "solar_voltage": 0,
|
|
|
|
# "solar_current": 0,
|
|
|
|
# "solar_power": 0,
|
|
|
|
# "load_voltage": 13.11,
|
|
|
|
# "load_current": 0,
|
|
|
|
# "load_power": 0,
|
|
|
|
# "battery_voltage": 13.14,
|
|
|
|
# "battery_current": 0,
|
|
|
|
# "battery_power": 0,
|
|
|
|
# "controller_temp": 18.1,
|
|
|
|
# "ambient_temp": 16.42,
|
|
|
|
# "generated_energy_today": 0,
|
|
|
|
# "controller_date_time": "2021-11-12 15:56:52",
|
|
|
|
# "battery_status": {
|
|
|
|
# "wrong_identifaction_for_rated_voltage": false,
|
|
|
|
# "battery_inner_resistence_abnormal": false,
|
|
|
|
# "temperature_warning_status": "NORMAL",
|
|
|
|
# "battery_status": "NORMAL"
|
|
|
|
# },
|
|
|
|
# "controller_status": {
|
|
|
|
# "input_voltage_status": "NORMAL",
|
|
|
|
# "charging_mosfet_is_short_circuit": false,
|
|
|
|
# "charging_or_anti_reverse_mosfet_is_open_circuit": false,
|
|
|
|
# "anti_reverse_mosfet_is_short_circuit": false,
|
|
|
|
# "input_over_current": false,
|
|
|
|
# "load_over_current": false,
|
|
|
|
# "load_short_circuit": false,
|
|
|
|
# "load_mosfet_short_circuit": false,
|
|
|
|
# "disequilibrium_in_three_circuits": false,
|
|
|
|
# "pv_input_short_circuit": false,
|
|
|
|
# "charging_status": "NO_CHARGING",
|
|
|
|
# "fault": false,
|
|
|
|
# "running": true
|
|
|
|
# },
|
|
|
|
# "load_on": true
|
|
|
|
#}
|
|
|
|
#
|
|
|
|
# This code is based on:
|
|
|
|
# https://github.com/rosswarren/epevermodbus
|
|
|
|
# https://minimalmodbus.readthedocs.io/en/master/index.html
|
|
|
|
# https://github.com/kasbert/epsolar-tracer (Linux driver for CC-USB-RS485-150U USB to RS485 cable)
|
|
|
|
#
|
|
|
|
# Thanks for sharing your knowledge!
|
|
|
|
#
|
|
|
|
# V0.1 2021-11-12
|
|
|
|
# - First working program
|
|
|
|
# - Generic read works
|
|
|
|
# - Dump all registers works
|
|
|
|
# - Set date does not work yet
|
|
|
|
# - Make LOAD-switch availible through command line arguments
|
|
|
|
#
|
|
|
|
# V0.2 2021-11-15
|
|
|
|
# - Set date and time works
|
|
|
|
# - Load switch can now be set from command line
|
|
|
|
# - Started to implement programming of the controller, but stumbled across a problem: when the
|
|
|
|
# controller is in lithium mode, it seems like it is not possible to program some registers.
|
|
|
|
# This functionality is not documented by EPEVER. Bummer!
|
|
|
|
#
|
|
|
|
# This Python3 program gathers the data from the Liontron BMS over Bluetooth BLE.
|
|
|
|
#
|
|
|
|
# SolarGeneratorDatalogger 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.
|
|
|
|
#
|
|
|
|
# SolarGeneratorDatalogger 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 SolarGeneratorDatalogger. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
import json
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import getopt
|
|
|
|
|
|
|
|
from json.decoder import JSONDecodeError
|
|
|
|
from datetime import datetime
|
|
|
|
from itertools import count
|
|
|
|
from multiprocessing import Process
|
|
|
|
from epever_control import EpeverChargeController
|
|
|
|
|
|
|
|
# This function is very much a work in progress.
|
|
|
|
# It seems that is is not possible to program the registers when the controller is in lithium mode. This part of the protocol (lithium programming)
|
|
|
|
# is undocumented by EPEVER and no one in the community has figured it out yet. I could try to reverse engineer this, but for now I decided not to.
|
|
|
|
# The program as it is has all the functionality I need for my application, so that's all folk!
|
|
|
|
def dump_all_registers():
|
|
|
|
|
|
|
|
rawdat={}
|
|
|
|
#Address range 0x3000
|
|
|
|
rawdat['rated_solar_voltage'] = controller.get_rated_solar_voltage()
|
|
|
|
rawdat['rated_solar_current'] = controller.get_rated_solar_current()
|
|
|
|
rawdat['rated_solar_power'] = controller.get_rated_solar_power_l() + 256*controller.get_rated_solar_power_h()
|
|
|
|
rawdat['rated_charging_voltage'] = controller.get_rated_charging_voltage()
|
|
|
|
rawdat['rated_charging_current'] = controller.get_rated_charging_current()
|
|
|
|
rawdat['rated_charging_power'] = controller.get_rated_charging_power_l() + 256 * controller.get_rated_charging_power_h()
|
|
|
|
rawdat['charging_mode'] = controller.get_charging_mode()
|
|
|
|
rawdat['rated_load_current'] = controller.get_rated_load_current()
|
|
|
|
# Address range 0x3100
|
|
|
|
rawdat['solar_voltage'] = controller.get_solar_voltage()
|
|
|
|
rawdat['solar_current'] = controller.get_solar_current()
|
|
|
|
rawdat['solar_power'] = controller.get_solar_power_l() + 256 * controller.get_solar_power_h()
|
|
|
|
rawdat['battery_voltage'] = controller.get_battery_voltage()
|
|
|
|
rawdat['battery_current'] = controller.get_battery_current()
|
|
|
|
rawdat['battery_power'] = controller.get_battery_power_l() + 256 * controller.get_battery_power_h()
|
|
|
|
rawdat['load_voltage'] = controller.get_load_voltage()
|
|
|
|
rawdat['load_current'] = controller.get_load_current()
|
|
|
|
rawdat['load_power'] = controller.get_load_power_l() + 256 * controller.get_load_power_h()
|
|
|
|
rawdat['battery_temperature'] = controller.get_battery_temperature()
|
|
|
|
rawdat['controller_temperature'] = controller.get_controller_temperature()
|
|
|
|
rawdat['heatsink_temperature'] = controller.get_heatsink_temperature()
|
|
|
|
rawdat['battery_state_of_charge'] = controller.get_battery_state_of_charge()
|
|
|
|
rawdat['remote_battery_temperature'] = controller.get_remote_battery_temperature()
|
|
|
|
rawdat['battery_real_rated_voltage'] = controller.get_battery_real_rated_voltage()
|
|
|
|
# Address range 0x3200
|
|
|
|
rawdat['battery_status'] = controller.get_battery_status()
|
|
|
|
rawdat['charging_equipment_status'] = controller.get_charging_equipment_status()
|
|
|
|
# Address 0x3300
|
|
|
|
rawdat['maximum_solar_voltage_today'] = controller.get_maximum_solar_voltage_today()
|
|
|
|
rawdat['minimum_solar_voltage_today'] = controller.get_minimum_solar_voltage_today()
|
|
|
|
rawdat['maximum_battery_voltage_today'] = controller.get_maximum_battery_voltage_today()
|
|
|
|
rawdat['minimum_battery_voltage_today'] = controller.get_minimum_battery_voltage_today()
|
|
|
|
rawdat['consumed_energy_today'] = controller.get_consumed_energy_today_l() + 256 * controller.get_consumed_energy_today_h()
|
|
|
|
rawdat['consumed_energy_month'] = controller.get_consumed_energy_month_l() + 256 * controller.get_consumed_energy_month_h()
|
|
|
|
rawdat['consumed_energy_year'] = controller.get_consumed_energy_year_l() + 256 * controller.get_consumed_energy_year_h()
|
|
|
|
rawdat['consumed_energy_total'] = controller.get_consumed_energy_total_l() + 256 * controller.get_consumed_energy_total_h()
|
|
|
|
rawdat['generated_energy_today'] = controller.get_generated_energy_today_l() + 256 * controller.get_generated_energy_today_h()
|
|
|
|
rawdat['generated_energy_month'] = controller.get_generated_energy_month_l() + 256 * controller.get_generated_energy_month_h()
|
|
|
|
rawdat['generated_energy_year'] = controller.get_generated_energy_year_l() + 256 * controller.get_generated_energy_year_h()
|
|
|
|
rawdat['generated_energy_total'] = controller.get_generated_energy_total_l() + 256 * controller.get_generated_energy_total_h()
|
|
|
|
rawdat['carbon_reduction'] = controller.get_carbon_reduction_l() + 256 * controller.get_carbon_reduction_h()
|
|
|
|
rawdat['battery_net_current'] = controller.get_battery_net_current_l() + 256 * controller.get_battery_net_current_h()
|
|
|
|
rawdat['battery_temperature'] = controller.get_battery_temperature()
|
|
|
|
rawdat['ambient_temperature'] = controller.get_ambient_temperature()
|
|
|
|
# Address range 0x9000
|
|
|
|
rawdat['battery_type'] = controller.get_battery_type()
|
|
|
|
rawdat['battery_capacity'] = controller.get_battery_capacity()
|
|
|
|
rawdat['temperature_compensation_coefficient'] = controller.get_temperature_compensation_coefficient()
|
|
|
|
rawdat['over_voltage_disconnect_voltage'] = controller.get_over_voltage_disconnect_voltage()
|
|
|
|
rawdat['charging_limit_voltage'] = controller.get_charging_limit_voltage()
|
|
|
|
rawdat['over_voltage_reconnect_voltage'] = controller.get_over_voltage_reconnect_voltage()
|
|
|
|
rawdat['equalize_charging_voltage'] = controller.get_equalize_charging_voltage()
|
|
|
|
rawdat['boost_charging_voltage'] = controller.get_boost_charging_voltage()
|
|
|
|
rawdat['float_charging_voltage'] = controller.get_float_charging_voltage()
|
|
|
|
rawdat['boost_reconnect_charging_voltage'] = controller.get_boost_reconnect_charging_voltage()
|
|
|
|
rawdat['low_voltage_reconnect_voltage'] = controller.get_low_voltage_reconnect_voltage()
|
|
|
|
rawdat['under_voltage_recover_voltage'] = controller.get_under_voltage_recover_voltage()
|
|
|
|
rawdat['under_voltage_warning_voltage'] = controller.get_under_voltage_warning_voltage()
|
|
|
|
rawdat['low_voltage_disconnect_voltage'] = controller.get_low_voltage_disconnect_voltage()
|
|
|
|
rawdat['discharging_limit_voltage'] = controller.get_discharging_limit_voltage()
|
|
|
|
rawdat['controller_date_time'] = controller.get_datetime()
|
|
|
|
rawdat['equalization_charging_cycle'] = controller.get_equalization_charging_cycle()
|
|
|
|
rawdat['battery_temp_upper_warning_limit'] = controller.get_battery_temp_upper_warning_limit()
|
|
|
|
rawdat['battery_temp_lower_warning_limit'] = controller.get_battery_temp_lower_warning_limit()
|
|
|
|
rawdat['controller_inner_temp_upper_limit'] = controller.get_controller_inner_temp_upper_limit()
|
|
|
|
rawdat['controller_inner_temp_upper_limit_recover'] = controller.get_controller_inner_temp_upper_limit_recover()
|
|
|
|
rawdat['power_comp_temp_upper_limit'] = controller.get_power_comp_temp_upper_limit()
|
|
|
|
rawdat['power_comp_temp_upper_limit_recover'] = controller.get_power_comp_temp_upper_limit_recover()
|
|
|
|
rawdat['line_impedance'] = controller.get_line_impedance()
|
|
|
|
rawdat['night_time_threshold_volt'] = controller.get_night_time_threshold_volt()
|
|
|
|
rawdat['light_signal_startup_delay'] = controller.get_light_signal_startup_delay()
|
|
|
|
rawdat['day_time_threshold_volt'] = controller.get_day_time_threshold_volt()
|
|
|
|
rawdat['light_signal_turnoff_delay'] = controller.get_light_signal_turnoff_delay()
|
|
|
|
rawdat['controling_mode'] = controller.get_controling_mode()
|
|
|
|
rawdat['working_time_lenght_1'] = controller.get_working_time_lenght_1()
|
|
|
|
rawdat['working_time_lenght_2'] = controller.get_working_time_lenght_2()
|
|
|
|
rawdat['turn_on_time_1_sec'] = controller.get_turn_on_time_1_sec()
|
|
|
|
rawdat['turn_on_time_1_min'] = controller.get_turn_on_time_1_min()
|
|
|
|
rawdat['turn_on_time_1_hours'] = controller.get_turn_on_time_1_hours()
|
|
|
|
rawdat['turn_off_time_1_sec'] = controller.get_turn_off_time_1_sec()
|
|
|
|
rawdat['turn_off_time_1_min'] = controller.get_turn_off_time_1_min()
|
|
|
|
rawdat['turn_off_time_1_hours'] = controller.get_turn_off_time_1_hours()
|
|
|
|
rawdat['turn_on_time_2_sec'] = controller.get_turn_on_time_2_sec()
|
|
|
|
rawdat['turn_on_time_2_min'] = controller.get_turn_on_time_2_min()
|
|
|
|
rawdat['turn_on_time_2_hours'] = controller.get_turn_on_time_2_hours()
|
|
|
|
rawdat['turn_off_time_2_sec'] = controller.get_turn_off_time_2_sec()
|
|
|
|
rawdat['turn_off_time_2_min'] = controller.get_turn_off_time_2_min()
|
|
|
|
rawdat['turn_off_time_2_hours'] = controller.get_turn_off_time_2_hours()
|
|
|
|
rawdat['length_of_night'] = controller.get_length_of_night()
|
|
|
|
rawdat['battery_rated_voltage'] = controller.get_battery_rated_voltage()
|
|
|
|
rawdat['load_timing_control_selection'] = controller.get_load_timing_control_selection()
|
|
|
|
rawdat['default_load_on_off_in_manual_mode'] = controller.get_default_load_on_off_in_manual_mode()
|
|
|
|
rawdat['equalize_duration'] = controller.get_equalize_duration()
|
|
|
|
rawdat['boost_duration'] = controller.get_boost_duration()
|
|
|
|
rawdat['battery_discharge'] = controller.get_battery_discharge()
|
|
|
|
rawdat['battery_charge'] = controller.get_battery_charge()
|
|
|
|
rawdat['management_mode'] = controller.get_management_mode()
|
|
|
|
# Address range 0x0
|
|
|
|
rawdat['manual_control'] = controller.get_manual_control()
|
|
|
|
rawdat['enable_load_test_mode'] = controller.get_enable_load_test_mode()
|
|
|
|
rawdat['force_load_on'] = controller.get_force_load_on()
|
|
|
|
# Address range 0x2000
|
|
|
|
rawdat['is_device_over_temperature'] = controller.is_device_over_temperature()
|
|
|
|
rawdat['is_night'] = controller.is_night()
|
|
|
|
|
|
|
|
print (json.dumps(rawdat, indent=1, sort_keys=False))
|
|
|
|
with open(dump_file, 'w') as f:
|
|
|
|
json.dump(rawdat, f, indent=1)
|
|
|
|
|
|
|
|
# Reads the given config-file and programs all the registers defined in this file to the solar controller
|
|
|
|
def program_registers():
|
|
|
|
print("Program the registers specified in the register file:", register_file)
|
|
|
|
|
|
|
|
# read configuration file
|
|
|
|
try:
|
|
|
|
with open(register_file, 'r') as myfile:
|
|
|
|
register_data=myfile.read()
|
|
|
|
except FileNotFoundError:
|
|
|
|
print("Configuration file <"+ register_file +"> not found.")
|
|
|
|
sys.exit();
|
|
|
|
|
|
|
|
# parse configuration file
|
|
|
|
try:
|
|
|
|
register_obj = json.loads(register_data)
|
|
|
|
except JSONDecodeError as e:
|
|
|
|
print("Error decoding configuration file. Not JSON format?")
|
|
|
|
sys.exit()
|
|
|
|
except TypeError as e:
|
|
|
|
print("Error decoding configuration file: JSON objects not str, bytes or bytearray.")
|
|
|
|
sys.exit()
|
|
|
|
except KeyError:
|
|
|
|
print("Error reading configuration file: needed fields not found.")
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
if 'battery_type' in register_obj:
|
|
|
|
#controller.set_battery_type(register_obj['battery_type']):
|
|
|
|
print("battery_type =", register_obj['battery_type'])
|
|
|
|
|
|
|
|
if 'battery_capacity' in register_obj:
|
|
|
|
#controller.set_battery_capacity(register_obj['battery_capacity'])
|
|
|
|
print("battery_capacity =", register_obj['battery_capacity'])
|
|
|
|
|
|
|
|
if 'temperature_compensation_coefficient' in register_obj:
|
|
|
|
#controller.set_temperature_compensation_coefficient(register_obj['temperature_compensation_coefficient'])
|
|
|
|
print("temperature_compensation_coefficient =", register_obj['temperature_compensation_coefficient'])
|
|
|
|
|
|
|
|
if 'over_voltage_disconnect_voltage' in register_obj:
|
|
|
|
#controller.set_over_voltage_disconnect_voltage(register_obj['over_voltage_disconnect_voltage'])
|
|
|
|
print("over_voltage_disconnect_voltage =", register_obj['over_voltage_disconnect_voltage'])
|
|
|
|
|
|
|
|
if 'charging_limit_voltage' in register_obj:
|
|
|
|
#controller.set_charging_limit_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("charging_limit_voltage =", register_obj['charging_limit_voltage'])
|
|
|
|
|
|
|
|
if 'over_voltage_reconnect_voltage' in register_obj:
|
|
|
|
#controller.set_over_voltage_reconnect_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("over_voltage_reconnect_voltage =", register_obj['over_voltage_reconnect_voltage'])
|
|
|
|
|
|
|
|
if 'equalize_charging_voltage' in register_obj:
|
|
|
|
#controller.set_equalize_charging_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("equalize_charging_voltage =", register_obj['equalize_charging_voltage'])
|
|
|
|
|
|
|
|
if 'boost_charging_voltage' in register_obj:
|
|
|
|
#controller.set_boost_charging_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("boost_charging_voltage =", register_obj['boost_charging_voltage'])
|
|
|
|
|
|
|
|
if 'float_charging_voltage' in register_obj:
|
|
|
|
#controller.set_float_charging_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("float_charging_voltage =", register_obj['float_charging_voltage'])
|
|
|
|
|
|
|
|
if 'boost_reconnect_charging_voltage' in register_obj:
|
|
|
|
#controller.set_boost_reconnect_charging_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("boost_reconnect_charging_voltage =", register_obj['boost_reconnect_charging_voltage'])
|
|
|
|
|
|
|
|
if 'low_voltage_reconnect_voltage' in register_obj:
|
|
|
|
#controller.set_low_voltage_reconnect_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("low_voltage_reconnect_voltage =", register_obj['low_voltage_reconnect_voltage'])
|
|
|
|
|
|
|
|
if 'under_voltage_recover_voltage' in register_obj:
|
|
|
|
#controller.set_under_voltage_recover_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("under_voltage_recover_voltage =", register_obj['under_voltage_recover_voltage'])
|
|
|
|
|
|
|
|
if 'under_voltage_warning_voltage' in register_obj:
|
|
|
|
#controller.set_under_voltage_warning_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("under_voltage_warning_voltage =", register_obj['under_voltage_warning_voltage'])
|
|
|
|
|
|
|
|
if 'low_voltage_disconnect_voltage' in register_obj:
|
|
|
|
#controller.set_low_voltage_disconnect_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("low_voltage_disconnect_voltage =", register_obj['low_voltage_disconnect_voltage'])
|
|
|
|
|
|
|
|
if 'discharging_limit_voltage' in register_obj:
|
|
|
|
#controller.set_discharging_limit_voltage(register_obj['charging_limit_voltage'])
|
|
|
|
print("discharging_limit_voltage =", register_obj['discharging_limit_voltage'])
|
|
|
|
|
|
|
|
if 'datetime_sec_min' in register_obj:
|
|
|
|
#controller.set_datetime_sec_min(self, value):
|
|
|
|
print("datetime_sec_min =", register_obj['datetime_sec_min'])
|
|
|
|
|
|
|
|
if 'datetime_hour_day' in register_obj:
|
|
|
|
#controller.set_datetime_hour_day(self, value):
|
|
|
|
print("datetime_hour_day =", register_obj['datetime_hour_day'])
|
|
|
|
|
|
|
|
if 'datetime_month_year' in register_obj:
|
|
|
|
#controller.set_datetime_month_year(self, value):
|
|
|
|
print("datetime_month_year =", register_obj['datetime_month_year'])
|
|
|
|
|
|
|
|
if 'equalization_charging_cycle' in register_obj:
|
|
|
|
#controller.set_equalization_charging_cycle(self, value):
|
|
|
|
print("equalization_charging_cycle =", register_obj['equalization_charging_cycle'])
|
|
|
|
|
|
|
|
if 'battery_temp_upper_warning_limit' in register_obj:
|
|
|
|
#controller.set_battery_temp_upper_warning_limit(self, value):
|
|
|
|
print("battery_temp_upper_warning_limit =", register_obj['battery_temp_upper_warning_limit'])
|
|
|
|
|
|
|
|
if 'battery_temp_lower_warning_limit' in register_obj:
|
|
|
|
#controller.set_battery_temp_lower_warning_limit(self, value):
|
|
|
|
print("battery_temp_lower_warning_limit =", register_obj['battery_temp_lower_warning_limit'])
|
|
|
|
|
|
|
|
if 'controller_inner_temp_upper_limit' in register_obj:
|
|
|
|
#controller.set_controller_inner_temp_upper_limit(self, value):
|
|
|
|
print("controller_inner_temp_upper_limit =", register_obj['controller_inner_temp_upper_limit'])
|
|
|
|
|
|
|
|
if 'controller_inner_temp_upper_limit_recover' in register_obj:
|
|
|
|
#controller.set_controller_inner_temp_upper_limit_recover(self, value):
|
|
|
|
print("controller_inner_temp_upper_limit_recover =", register_obj['controller_inner_temp_upper_limit_recover'])
|
|
|
|
|
|
|
|
if 'power_comp_temp_upper_limit' in register_obj:
|
|
|
|
#controller.set_power_comp_temp_upper_limit(self, value):
|
|
|
|
print("equalization_charging_cycle =", register_obj['equalization_charging_cycle'])
|
|
|
|
|
|
|
|
if 'power_comp_temp_upper_limit_recover' in register_obj:
|
|
|
|
#controller.set_power_comp_temp_upper_limit_recover(self, value):
|
|
|
|
print("power_comp_temp_upper_limit_recover =", register_obj['power_comp_temp_upper_limit_recover'])
|
|
|
|
|
|
|
|
if 'line_impedance' in register_obj:
|
|
|
|
#controller.set_line_impedance(self, value):
|
|
|
|
print("line_impedance =", register_obj['line_impedance'])
|
|
|
|
|
|
|
|
if 'night_time_threshold_volt' in register_obj:
|
|
|
|
#controller.set_night_time_threshold_volt(self, value):
|
|
|
|
print("night_time_threshold_volt =", register_obj['night_time_threshold_volt'])
|
|
|
|
|
|
|
|
if 'light_signal_startup_delay' in register_obj:
|
|
|
|
#controller.set_light_signal_startup_delay(self, value):
|
|
|
|
print("light_signal_startup_delay =", register_obj['light_signal_startup_delay'])
|
|
|
|
|
|
|
|
if 'day_time_threshold_volt' in register_obj:
|
|
|
|
#controller.set_day_time_threshold_volt(self, value):
|
|
|
|
print("day_time_threshold_volt =", register_obj['day_time_threshold_volt'])
|
|
|
|
|
|
|
|
if 'light_signal_turnoff_delay' in register_obj:
|
|
|
|
#controller.set_light_signal_turnoff_delay(self, value)
|
|
|
|
print("light_signal_turnoff_delay =", register_obj['light_signal_turnoff_delay'])
|
|
|
|
|
|
|
|
if 'controling_mode' in register_obj:
|
|
|
|
#controller.set_controling_mode(self, value):
|
|
|
|
print("controling_mode =", register_obj['controling_mode'])
|
|
|
|
|
|
|
|
if 'working_time_lenght_1' in register_obj:
|
|
|
|
#controller.set_working_time_lenght_1(self, value):
|
|
|
|
print("working_time_lenght_1 =", register_obj['working_time_lenght_1'])
|
|
|
|
|
|
|
|
if 'working_time_lenght_2' in register_obj:
|
|
|
|
#controller.set_working_time_lenght_2(self, value):
|
|
|
|
print("working_time_lenght_2 =", register_obj['working_time_lenght_2'])
|
|
|
|
|
|
|
|
if 'turn_on_time_1_sec' in register_obj:
|
|
|
|
#controller.set_turn_on_time_1_sec(self, value):
|
|
|
|
print("turn_on_time_1_sec =", register_obj['turn_on_time_1_sec'])
|
|
|
|
|
|
|
|
if 'turn_on_time_1_min' in register_obj:
|
|
|
|
#controller.set_turn_on_time_1_min(self, value):
|
|
|
|
print("turn_on_time_1_min =", register_obj['turn_on_time_1_min'])
|
|
|
|
|
|
|
|
if 'turn_on_time_1_hours' in register_obj:
|
|
|
|
#controller.set_turn_on_time_1_hours(self, value):
|
|
|
|
print("turn_on_time_1_hours =", register_obj['turn_on_time_1_hours'])
|
|
|
|
|
|
|
|
if 'turn_off_time_1_sec' in register_obj:
|
|
|
|
#controller.set_turn_off_time_1_sec(self, value):
|
|
|
|
print("turn_off_time_1_sec =", register_obj['turn_off_time_1_sec'])
|
|
|
|
|
|
|
|
if 'turn_off_time_1_min' in register_obj:
|
|
|
|
#controller.set_turn_off_time_1_min(self, value):
|
|
|
|
print("turn_off_time_1_min =", register_obj['turn_off_time_1_min'])
|
|
|
|
|
|
|
|
if 'turn_off_time_1_hours' in register_obj:
|
|
|
|
#controller.set_turn_off_time_1_hours(self, value):
|
|
|
|
print("turn_off_time_1_hours =", register_obj['turn_off_time_1_hours'])
|
|
|
|
|
|
|
|
if 'turn_on_time_2_sec' in register_obj:
|
|
|
|
#controller.set_turn_on_time_2_sec(self, value):
|
|
|
|
print("turn_on_time_2_sec =", register_obj['turn_on_time_2_sec'])
|
|
|
|
|
|
|
|
if 'turn_on_time_2_min' in register_obj:
|
|
|
|
#controller.set_turn_on_time_2_min(self, value):
|
|
|
|
print("turn_on_time_2_min =", register_obj['turn_on_time_2_min'])
|
|
|
|
|
|
|
|
if 'turn_on_time_2_hours' in register_obj:
|
|
|
|
#controller.set_turn_on_time_2_hours(self, value):
|
|
|
|
print("turn_on_time_2_hours =", register_obj['turn_on_time_2_hours'])
|
|
|
|
|
|
|
|
if 'turn_off_time_2_sec' in register_obj:
|
|
|
|
#controller.set_turn_off_time_2_sec(self, value):
|
|
|
|
print("turn_off_time_2_sec =", register_obj['turn_off_time_2_sec'])
|
|
|
|
|
|
|
|
if 'turn_off_time_2_min' in register_obj:
|
|
|
|
#controller.set_turn_off_time_2_min(self, value):
|
|
|
|
print("turn_off_time_2_min =", register_obj['turn_off_time_2_min'])
|
|
|
|
|
|
|
|
if 'turn_off_time_2_hours' in register_obj:
|
|
|
|
#controller.set_turn_off_time_2_hours(self, value):
|
|
|
|
print("turn_off_time_2_hours =", register_obj['turn_off_time_2_hours'])
|
|
|
|
|
|
|
|
if 'length_of_night' in register_obj:
|
|
|
|
#controller.set_length_of_night(self, value):
|
|
|
|
print("length_of_night =", register_obj['length_of_night'])
|
|
|
|
|
|
|
|
if 'battery_rated_voltage' in register_obj:
|
|
|
|
#controller.set_battery_rated_voltage(self, value):
|
|
|
|
print("battery_rated_voltage =", register_obj['battery_rated_voltage'])
|
|
|
|
|
|
|
|
if 'load_timing_control_selection' in register_obj:
|
|
|
|
#controller.set_load_timing_control_selection(self, value):
|
|
|
|
print("load_timing_control_selection =", register_obj['load_timing_control_selection'])
|
|
|
|
|
|
|
|
if 'default_load_on_off_in_manual_mode' in register_obj:
|
|
|
|
#controller.set_default_load_on_off_in_manual_mode(self, value):
|
|
|
|
print("default_load_on_off_in_manual_mode =", register_obj['default_load_on_off_in_manual_mode'])
|
|
|
|
|
|
|
|
if 'equalize_duration' in register_obj:
|
|
|
|
#controller.set_equalize_duration(self, value):
|
|
|
|
print("equalize_duration =", register_obj['equalize_duration'])
|
|
|
|
|
|
|
|
if 'boost_duration' in register_obj:
|
|
|
|
#controller.set_boost_duration(self, value):
|
|
|
|
print("boost_duration =", register_obj['boost_duration'])
|
|
|
|
|
|
|
|
if 'battery_discharge' in register_obj:
|
|
|
|
#controller.set_battery_discharge(self, value):
|
|
|
|
print("battery_discharge =", register_obj['battery_discharge'])
|
|
|
|
|
|
|
|
if 'battery_charge' in register_obj:
|
|
|
|
#controller.set_battery_charge(self, value):
|
|
|
|
print("battery_charge =", register_obj['battery_charge'])
|
|
|
|
|
|
|
|
if 'management_mode' in register_obj:
|
|
|
|
#controller.set_management_mode(self, value):
|
|
|
|
print("management_mode =", register_obj['management_mode'])
|
|
|
|
|
|
|
|
print("This functionality is not implemented yet. Nothing is actually written to the solar charger.")
|
|
|
|
|
|
|
|
config_file =""
|
|
|
|
set_date =""
|
|
|
|
load_switch=""
|
|
|
|
register_file=""
|
|
|
|
dump_file=""
|
|
|
|
|
|
|
|
argv = sys.argv[1:]
|
|
|
|
try:
|
|
|
|
options, args = getopt.getopt(argv, "c:s:l:p:d:",
|
|
|
|
["config_file =",
|
|
|
|
"set_date =",
|
|
|
|
"load_switch =",
|
|
|
|
"register_file=",
|
|
|
|
"dump_file ="])
|
|
|
|
except:
|
|
|
|
print("epever-charge-controller can read and write the registers of a EPEVER solar")
|
|
|
|
print("charge controller using the Modbus protocol.")
|
|
|
|
print("")
|
|
|
|
print("-c, --config_file : reads the most important registers of the charge")
|
|
|
|
print(" controller and writes it to the file specified in the")
|
|
|
|
print(" config_file")
|
|
|
|
print("-s, --set_date : sets the date and time from the given argument with")
|
|
|
|
print(" format: YYYY-MM-DD HH:MM:SS")
|
|
|
|
print("-l, --load : switches load on or off as given by the argument")
|
|
|
|
print("-p, --program : programs all the registers of the solar charger given in")
|
|
|
|
print(" the specified register file.")
|
|
|
|
print("-d, --dump_file : dumps all the registers in the specified file")
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
for name, value in options:
|
|
|
|
if name in ['-c', '--config_file']:
|
|
|
|
config_file = value
|
|
|
|
elif name in ['-s', '--set_date']:
|
|
|
|
set_date = value
|
|
|
|
elif name in ['-l', '--load']:
|
|
|
|
load_switch = value
|
|
|
|
elif name in ['-p', '--program']:
|
|
|
|
register_file = value
|
|
|
|
elif name in ['-d', '--dump']:
|
|
|
|
dump_file = value
|
|
|
|
|
|
|
|
if not config_file:
|
|
|
|
print("No configuration file given.")
|
|
|
|
sys.exit();
|
|
|
|
|
|
|
|
# read configuration file
|
|
|
|
try:
|
|
|
|
with open(config_file, 'r') as myfile:
|
|
|
|
data=myfile.read()
|
|
|
|
except FileNotFoundError:
|
|
|
|
print("Configuration file <"+ config_file +"> not found.")
|
|
|
|
sys.exit();
|
|
|
|
|
|
|
|
# parse configuration file
|
|
|
|
try:
|
|
|
|
obj = json.loads(data)
|
|
|
|
tmp = obj['epever_data_file']
|
|
|
|
tmp1 = obj['RS485_port']
|
|
|
|
tmp2 = obj['RS485_address']
|
|
|
|
except JSONDecodeError as e:
|
|
|
|
print("Error decoding configuration file. Not JSON format?")
|
|
|
|
sys.exit()
|
|
|
|
except TypeError as e:
|
|
|
|
print("Error decoding configuration file: JSON objects not str, bytes or bytearray.")
|
|
|
|
sys.exit()
|
|
|
|
except KeyError:
|
|
|
|
print("Error reading configuration file: needed fields not found.")
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
# show values from config file
|
|
|
|
print("Using RRS485 port : " + str(obj['RS485_port']))
|
|
|
|
print("Using RRS485 address : " + str(obj['RS485_address']))
|
|
|
|
print("Saving EPEVER data to: " + str(obj['epever_data_file']))
|
|
|
|
|
|
|
|
controller = EpeverChargeController(obj['RS485_port'], obj['RS485_address'])
|
|
|
|
|
|
|
|
if set_date:
|
|
|
|
try:
|
|
|
|
iso_date = datetime.fromisoformat(set_date)
|
|
|
|
except:
|
|
|
|
print("Error in date and time. Date and time should be in ISO format.")
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
print("Setting the time and date to", iso_date)
|
|
|
|
datetime={}
|
|
|
|
datetime['year'] = int(iso_date.strftime('%Y')) - 2000
|
|
|
|
datetime['month'] = int(iso_date.strftime('%m'))
|
|
|
|
datetime['day'] = int(iso_date.strftime('%d'))
|
|
|
|
datetime['hours'] = int(iso_date.strftime('%H'))
|
|
|
|
datetime['minutes'] = int(iso_date.strftime('%M'))
|
|
|
|
datetime['seconds'] = int(iso_date.strftime('%S'))
|
|
|
|
|
|
|
|
controller.write_date(datetime)
|
|
|
|
|
|
|
|
elif load_switch:
|
|
|
|
if (load_switch.lower()=='on'):
|
|
|
|
print("Switching load ON.")
|
|
|
|
controller.switch_load_on()
|
|
|
|
elif (load_switch.lower()=='off'):
|
|
|
|
print("Switching load OFF.")
|
|
|
|
controller.switch_load_off()
|
|
|
|
else:
|
|
|
|
print("No argument given. Cannot switch load.")
|
|
|
|
|
|
|
|
elif register_file:
|
|
|
|
program_registers();
|
|
|
|
|
|
|
|
elif dump_file:
|
|
|
|
print("Dumping all registers to file: "+ dump_file)
|
|
|
|
dump_all_registers()
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
while (1):
|
|
|
|
time.sleep(3) # Sleep for 3 seconds
|
|
|
|
print ("Retrieving all known registers.")
|
|
|
|
rawdat={}
|
|
|
|
rawdat['ID'] = controller.get_id()
|
|
|
|
rawdat['Wind direction'] = controller.get_wind_direction()
|
|
|
|
rawdat['Wind speed'] = controller.get_wind_speedl()
|
|
|
|
rawdat['Wind gust'] = controller.get_wind_gust()
|
|
|
|
rawdat['Rain last hour'] = controller.get_rain()
|
|
|
|
rawdat['Rain last 24 hours'] = controller.get_rain_last24()
|
|
|
|
rawdat['Temperature'] = controller.get_temperature()
|
|
|
|
rawdat['Humidity'] = controller.get_humidity()
|
|
|
|
rawdat['Pressure'] = controller.get_pressure()
|
|
|
|
|
|
|
|
print (json.dumps(rawdat, indent=1, sort_keys=False))
|
|
|
|
|
|
|
|
|