Files
pi-solarmax/solarmax/solarmaxd.py
T
2024-09-23 15:10:59 +02:00

234 lines
8.7 KiB
Python
Executable File

#!../.venv/bin/python
##!/opt/lib/python/venv_app/bin/python
'''
Created on 7 nov. 2023
@author: denis
'''
import threading, logging, argparse
from SolarMax.solarmax_fr import SolarMax, get_status_code
from contrib.mqttc import MqttBase
from contrib import utils
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
INVERTERS = {
'192.168.1.123': [1,],
#'192.168.0.202': [2,],
#'192.168.0.203': [3,],
#'192.168.0.204': [4,],
}
ACCESS = [
(1, "Pub only"),
(2, "/set only"),
(5, "/get, pub"),
(7, "/get, /set, pub"),
]
class SolarmaxMqttWorker(MqttBase):
def __init__(self, parent=None, **p):
super().__init__(**p)
self.parent = parent
def makeReport(self):
data = dict(
name = 'Onduleur Solarmax',
uuid = self.parent.uuid,
sensor = 'solarmax',
vendor = 'Solarmax',
model_id = 'SM2000S',
description = "Onduleur Solarmax SM2000S, 1980 W",
ip = self.parent.ip,
org = self.parent.org,
datas = [
{"access":1,"description":"Linux timestamp en secondes","label":"Timestamp","name":"time","property":"time","type":"numeric","unit":"s"},
{"access":1,"description":"Numéro d'onduleur","label":"Inverter","name":"inv","property":"range","type":"numeric","unit":""},
{"access":1,"description":"Intensité max","label":"Ivmax","name":"ivmax","property":"intensity","type":"numeric","unit":"A"},
{"access":1,"description":"Production AC","label":"PAC","name":"pac","property":"power","type":"numeric","unit":"W"},
{"access":1,"description":"Rendement AC","label":"Eac","name":"eac","property":"efficiency","type":"numeric","unit":""},
{"access":1,"description":"Production DC.","label":"PDC","name":"pdc","property":"power","type":"numeric","unit":"W"},
{"access":1,"description":"Efficacity DC","label":"Edc","name":"edc","property":"efficiency","type":"numeric","unit":""},
{"access":1,"description":"Production du jour","label":"Qday","name":"qdy","property":"days","type":"numeric","unit":"kWh"},
{"access":1,"description":"Production totale","label":"Qtotal","name":"qt0","property":"days","type":"numeric","unit":"kWh"},
{"access":1,"description":"Status onduleur","label":"Status","name":"stat","property":"state","type":"text","unit":""},
{"access":1,"description":"Température des panneaux","label":"Temperature","name":"tmpr","property":"temperature","type":"numeric","unit":"°C"},
],
)
return data
def publish_to_client(self, evt, **payload):
#logger.info(f"{self.topic_base}/{utils.ts_now()}/{evt}\n{payload}")
self._publish_message(f'{self.topic_base}/{evt}', **payload)
def _on_stop_mqtt(self):
self.publish_to_client('stop', alive=False)
logger.info(f'WAITING 1s for last message')
threading.Event().wait(1)
def _on_connect_info(self, info):
logger.info(f"{info}\n subs: {self.subscriptions}")
self.publish_to_client('report', retain=True, **self.makeReport())
def _on_message_callback(self, topic, payload):
try:
pass
#if topic.endswith('registry'):
# self.publish_to_client('report', **self.makeReport())
except Exception as e:
logger.error(e)
class SolarmaxDaemon():
def __init__(self, conf_file, **settings):
super().__init__()
self.conf_file = conf_file
self.settings = settings
topic_subs = self.settings['solarmax']['topic_subs']
topic_base = self.settings['solarmax']['topic_base']
inverters = self.settings['solarmax']['inverters']
self.timeout = self.settings['solarmax']['loop_timeout']
self.uuid = hex(self.settings['solarmax']['uuid'])
self.ip = self.settings['solarmax']['ip']
self.org = self.settings['solarmax']['origine']
self.solar_stop = threading.Event()
self.smlist = []
for host in inverters.keys():
sm = SolarMax(host, 12345)
sm.use_inverters(inverters[host])
self.smlist.append(sm)
self.allinverters = []
for host in inverters.keys():
self.allinverters.extend(inverters[host])
self.inverters_size = len(self.allinverters)
self.mqtt = SolarmaxMqttWorker(parent=self, topic_base=topic_base ,topic_subs=topic_subs, **settings['mqtt'])
self.mqtt.connectMQTT()
def start(self):
self.mqtt.client.loop_start()
self.run_forever()
def stop(self):
self.mqtt.client.loop_stop()
self.solar_stop.set()
def run_forever(self):
logger.info(f'Module SolarmaxDaemon::run_forever is started')
while not self.solar_stop.is_set():
try:
count = 0
for sm in self.smlist:
for (no, ivdata) in sm.inverters().items():
try:
(inverter, current) = sm.query(no, ['PAC', 'TKK', 'KDY', 'KT0', 'IDC', 'UDC', 'IL1', 'UL1', 'FDAT', 'SYS'])
count += 1
except:
logger.info(f'Erreur de communication, éventuellement onduleur éteint, WR {no}')
threading.Event().wait(self.timeout)
continue
ivmax = ivdata['installed']
ivname = ivdata['desc']
UAC = current['UL1']
IAC = current['IL1']
PAC = UAC * IAC
IDC = current['IDC']
UDC = current['UDC']
tmpr = current['TKK']
eac = int((PAC/ivmax) * 100) # rendement AC
PDC = UDC * IDC
edc = int((float(PAC)/PDC) * 100) # efficacity DC
(status, errors) = sm.status(no)
if errors:
logger.error(f'WR {no}: {status} ({errors})')
continue
logger.debug(
f'''
Onduleur..............: n° {inverter} ({ivname})
Status................: {status}
Température panneaux..: {tmpr} °C
Tension CC............: {UDC} V
Intensité CC..........: {IDC} A
Tension AC............: {UAC} V
Intensité AC..........: {IAC} A
Production AC.........: {current['PAC']:9.1f} Watt / calculée: {PAC:9.1f} W rendement: ({eac}% de {ivmax} Watt)
Production DC.........: {PDC:9.1f} Watt (Efficacité: {edc}%)
Total aujourd'hui.....: {current['KDY']:9.1f} kWh
Total jusqu'à présent.: {current['KT0']:9.1f} kWh (depuis le {current['FDAT'].date()})
'''
)
if count < self.inverters_size:
raise Exception(f"({count} < {self.inverters_size} => Erreur de communication, éventuellement onduleur éteint")
payload = dict(
time=utils.ts_now(),
inv=inverter,
ivmax=ivmax,
tmpr=tmpr,
pac=round(PAC, 1),
eac=eac,
pdc=round(PDC, 1),
edc=edc,
qdy=current['KDY'],
qt0=current['KT0'],
stat=get_status_code(status),
)
self.mqtt.publish_to_client('production', **payload)
except Exception as e:
logger.error(e)
threading.Event().wait(self.timeout)
def load_configuration(conf_file):
settings = utils.yaml_load(conf_file)
if not settings['solarmax']['uuid']:
uuid = f'0x{utils.gen_device_uuid(19)}'
origine = settings['solarmax']['origine']
settings['solarmax']['uuid'] = uuid
settings['solarmax']['inverters'] = INVERTERS
settings['solarmax']['topic_base'] = f"{origine}/{uuid}"
settings['solarmax']['topic_subs']= [[f"{origine}/{uuid}/#", 0], ]
utils.yaml_save(conf_file, settings)
return settings
def main(conf_file):
daemon = None
try:
config = load_configuration(conf_file)
daemon = SolarmaxDaemon(conf_file, **config)
daemon.start()
except Exception as e:
print(f'\n SolarmaxDaemon error {e}')
finally:
if daemon:
daemon.stop()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Solarmax device")
parser.add_argument("--config", default='config.yaml', help="Config yaml file path", required=False)
args = parser.parse_args()
if args.config:
main(args.config)