first commit
This commit is contained in:
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!../.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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user