From 0d6b860664555c933b768cc069b5f00b571c448d Mon Sep 17 00:00:00 2001 From: retr0 <42kdesigners@gmail.com> Date: Fri, 24 Apr 2026 22:30:45 +0200 Subject: [PATCH] HAOS Add-on: MVP + NuttX Binary + .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - haos-addon/: vollständiges HA Add-on (config.yaml, Dockerfile, build.yaml) - Python Backend: pymodbus Modbus TCP → paho-mqtt MQTT Discovery - Unterstützte Modelle: MIC 1500/2000 TL-X, SPH 5000 TL3, MOD 6000 TL3 - Web UI: Wechselrichter-Auswahl, Modbus/MQTT-Konfig, Live-Sensor-Grid (dark theme) - MQTT HA Discovery für alle Sensoren mit device_class, state_class, icon - ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin: NuttX Firmware (ohne DFU, 0x08000000) - .gitignore: Logs, MQTT-JSON, shinelanx-modbus/ ausgeschlossen Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 8 + ShineLAN-X/firmware/src/main.cpp | 196 ++++-- ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin | Bin 0 -> 95384 bytes haos-addon/Dockerfile | 14 + haos-addon/build.yaml | 7 + haos-addon/config.yaml | 40 ++ haos-addon/run.sh | 20 + haos-addon/src/inverters.py | 127 ++++ haos-addon/src/main.py | 221 +++++++ haos-addon/src/modbus_client.py | 77 +++ haos-addon/src/mqtt_publisher.py | 94 +++ haos-addon/src/web/index.html | 570 ++++++++++++++++++ 12 files changed, 1327 insertions(+), 47 deletions(-) create mode 100644 .gitignore create mode 100755 ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin create mode 100644 haos-addon/Dockerfile create mode 100644 haos-addon/build.yaml create mode 100644 haos-addon/config.yaml create mode 100644 haos-addon/run.sh create mode 100644 haos-addon/src/inverters.py create mode 100644 haos-addon/src/main.py create mode 100644 haos-addon/src/modbus_client.py create mode 100644 haos-addon/src/mqtt_publisher.py create mode 100644 haos-addon/src/web/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba32ed8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.log +*.txt +*.json +!haos-addon/**/*.json +shinelanx-modbus/ +__pycache__/ +*.pyc +.env diff --git a/ShineLAN-X/firmware/src/main.cpp b/ShineLAN-X/firmware/src/main.cpp index 6741c10..f865faa 100644 --- a/ShineLAN-X/firmware/src/main.cpp +++ b/ShineLAN-X/firmware/src/main.cpp @@ -63,16 +63,65 @@ extern "C" void USBD_reenumerate(void) { HardwareSerial DebugSerial(PA10, PA9); -// USBSerial::flush() wartet endlos wenn der Wechselrichter den IN-Endpoint nicht pollt. -// Dieser Wrapper ersetzt flush() durch eine No-Op. +// USB-Diagnose: was der Wechselrichter per Control-Transfer schickt +extern __IO bool dtrState; +extern __IO bool rtsState; +struct { uint32_t baud; uint8_t format; uint8_t parity; uint8_t bits; } + g_lineCoding = {115200, 0, 0, 8}; +volatile bool g_lineCodingUpdated = false; + +// Wird vom USB-Stack aufgerufen wenn Wechselrichter SET_LINE_CODING schickt +// usbd_cdc_if.c kann nicht geändert werden → wir lesen linecoding-Struct direkt +extern "C" { + typedef struct { uint32_t bitrate; uint8_t format; uint8_t paritytype; uint8_t datatype; } + USBD_CDC_LineCodingTypeDef; + extern USBD_CDC_LineCodingTypeDef linecoding; +} + +// CDC SERIAL_STATE Notification — NuttX sendet dies beim Öffnen von /dev/ttyACM0 +// signalisiert dem Wechselrichter DCD+DSR=1 → startet Modbus-Server im Inverter +#include "usbd_core.h" +extern USBD_HandleTypeDef hUSBD_Device_CDC; + +#define CDC_SERIAL_STATE_EP 0x82U + +void sendSerialStateNotification() { + static uint8_t notif[10] = { + 0xA1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00 + }; + USBD_LL_Transmit(&hUSBD_Device_CDC, CDC_SERIAL_STATE_EP, notif, sizeof(notif)); +} + +// ModbusMaster schreibt Byte-für-Byte. Würden wir sofort senden, käme Byte 1 als +// eigenes 1-Byte USB-Paket an — der Wechselrichter behandelt jedes Short Packet +// als Frame-Ende und sieht keinen gültigen Modbus-Frame. +// Lösung: Bytes puffern, dann in flush() als ein einziges USB-Paket senden. class ModbusStream : public Stream { public: - int available() override { return SerialUSB.available(); } - int read() override { return SerialUSB.read(); } + uint8_t txBuf[16]; uint8_t txLen = 0; + uint8_t rxBuf[32]; uint8_t rxLen = 0; + uint32_t rxTotal = 0; + void reset() { txLen = 0; rxLen = 0; } + + int available() override { return SerialUSB.available(); } + int read() override { + int b = SerialUSB.read(); + if (b >= 0) { rxTotal++; if (rxLen < sizeof(rxBuf)) rxBuf[rxLen++] = (uint8_t)b; } + return b; + } int peek() override { return SerialUSB.peek(); } - size_t write(uint8_t b) override { return SerialUSB.write(b); } - size_t write(const uint8_t* buf, size_t size) override { return SerialUSB.write(buf, size); } - void flush() override {} + size_t write(uint8_t b) override { + if (txLen < sizeof(txBuf)) txBuf[txLen++] = b; + return 1; + } + size_t write(const uint8_t* buf, size_t size) override { + for (size_t i = 0; i < size && txLen < sizeof(txBuf); i++) txBuf[txLen++] = buf[i]; + return size; + } + void flush() override { + if (txLen > 0) SerialUSB.write(txBuf, txLen); + // txLen nicht zurücksetzen — Debug-Code liest txBuf danach noch + } using Print::write; } modbusSerial; @@ -153,6 +202,7 @@ EthernetServer webServer(80); uint32_t g_okTotal = 0; uint32_t g_failTotal = 0; uint8_t g_lastErr = 0; +uint32_t g_rawTotal = 0; // ============================================================ // Web-Server @@ -524,6 +574,12 @@ void setup() { SerialUSB.begin(MODBUS_BAUD); delay(10000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); + // Warte bis Wechselrichter USB-CDC konfiguriert hat + delay(3000); + // SERIAL_STATE(DCD=1,DSR=1) senden — NuttX tut dies beim Öffnen von ttyACM0 + // signalisiert dem Wechselrichter dass das Device bereit ist + sendSerialStateNotification(); + delay(500); modbus.begin(MODBUS_ADDR, modbusSerial); DebugSerial.println("Setup done."); @@ -533,6 +589,7 @@ void setup() { // Loop // ============================================================ unsigned long lastUpdate = 0; +static bool g_usbInfoPublished = false; void loop() { handleWebRequest(); @@ -546,65 +603,110 @@ void loop() { mqtt.loop(); handleWebRequest(); + // usbinfo: initial publish + DTR-Änderungen + { + static bool lastDtr = false; + if (!g_usbInfoPublished || (bool)dtrState != lastDtr) { + g_usbInfoPublished = true; + lastDtr = (bool)dtrState; + char info[80]; + snprintf(info, sizeof(info), "baud=%lu fmt=%u par=%u bits=%u dtr=%d rts=%d", + (unsigned long)linecoding.bitrate, linecoding.format, + linecoding.paritytype, linecoding.datatype, + (int)dtrState, (int)rtsState); + mqtt.publish("growatt/shinelan/usbinfo", info, true); + } + } + + // SERIAL_STATE(DCD=1,DSR=1) alle 5s senden bis DTR vom Wechselrichter gesetzt wird + { + static uint32_t lastNotif = 0; + if (!dtrState && millis() - lastNotif > 5000) { + lastNotif = millis(); + sendSerialStateNotification(); + } + } + + // Raw-RX-Drain: jedes Byte das außerhalb von Modbus-Fenstern ankommt fangen + // Beantwortet: sendet der Inverter JEMALS irgendwas (spontan oder verspätet)? + { + static uint8_t rawBuf[32] = {}; + static uint8_t rawLen = 0; + static uint32_t rawPublish = 0; + while (SerialUSB.available()) { + uint8_t b = (uint8_t)SerialUSB.read(); + g_rawTotal++; + if (rawLen < sizeof(rawBuf)) rawBuf[rawLen++] = b; + } + if (rawLen > 0 && millis() - rawPublish > 1000) { + rawPublish = millis(); + char msg[80]; + size_t p = snprintf(msg, sizeof(msg), "raw=%lu: ", g_rawTotal); + for (uint8_t i = 0; i < rawLen && p < 74; i++) + p += snprintf(msg + p, sizeof(msg) - p, "%02X", rawBuf[i]); + mqtt.publish("growatt/shinelan/rawbytes", msg); + rawLen = 0; + } + } + if (millis() - lastUpdate < cfg.updateMs) return; lastUpdate = millis(); ledSet(LED_BLUE, true); - uint8_t avStart = SerialUSB.availableForWrite(); - bool usbReady = (avStart >= 8); - uint8_t okCount = 0, failCount = 0; - uint8_t lastErr = 0; + uint32_t okCnt = 0, failCnt = 0; + uint8_t lastErr = 0; uint16_t lastErrReg = 0; for (uint8_t i = 0; i < SENSOR_COUNT; i++) { - mqtt.loop(); - handleWebRequest(); - const Sensor& s = SENSORS[i]; + modbusSerial.reset(); - if (!usbReady) { - failCount++; lastErr = 0xFE; lastErrReg = s.address; + uint8_t res = modbus.readInputRegisters(s.address, s.isDword ? 2 : 1); + + char dbg[80]; + size_t pos = snprintf(dbg, sizeof(dbg), "ok=%lu fail=%lu err=0x%02X@%u avS=%d dtr=%d raw=%lu rx=%u tx=", + g_okTotal + okCnt, g_failTotal + failCnt, + lastErr, lastErrReg, + SerialUSB.availableForWrite(), + (int)dtrState, + g_rawTotal, + modbusSerial.rxLen); + for (uint8_t j = 0; j < modbusSerial.txLen && pos < 74; j++) + pos += snprintf(dbg + pos, sizeof(dbg) - pos, "%02X", modbusSerial.txBuf[j]); + pos += snprintf(dbg + pos, sizeof(dbg) - pos, " rx="); + for (uint8_t j = 0; j < modbusSerial.rxLen && pos < 78; j++) + pos += snprintf(dbg + pos, sizeof(dbg) - pos, "%02X", modbusSerial.rxBuf[j]); + mqtt.publish("growatt/shinelan/debug", dbg); + + delay(10); // mbusd -R10: 10ms Pause zwischen Requests + + if (res != modbus.ku8MBSuccess) { + failCnt++; + lastErr = res; + lastErrReg = s.address; continue; } - uint8_t result; - uint32_t raw = 0; - + okCnt++; + float val; if (s.isDword) { - result = modbus.readInputRegisters(s.address, 2); - if (result == ModbusMaster::ku8MBSuccess) - raw = ((uint32_t)modbus.getResponseBuffer(0) << 16) | modbus.getResponseBuffer(1); + uint32_t raw = ((uint32_t)modbus.getResponseBuffer(0) << 16) + | modbus.getResponseBuffer(1); + val = raw * s.scale; } else { - result = modbus.readInputRegisters(s.address, 1); - if (result == ModbusMaster::ku8MBSuccess) - raw = modbus.getResponseBuffer(0); + val = modbus.getResponseBuffer(0) * s.scale; } - if (result != ModbusMaster::ku8MBSuccess) { - failCount++; lastErr = result; lastErrReg = s.address; - continue; - } - - okCount++; - float value = raw * s.scale; - char valueStr[16]; - dtostrf(value, 1, (s.scale < 0.1f) ? 2 : 1, valueStr); - - char stateTopic[64]; - snprintf(stateTopic, sizeof(stateTopic), "growatt/shinelan/%s", s.id); - mqtt.publish(stateTopic, valueStr); + char topic[64], payload[24]; + snprintf(topic, sizeof(topic), "growatt/shinelan/%s", s.id); + snprintf(payload, sizeof(payload), "%.1f", (double)val); + mqtt.publish(topic, payload); } - g_okTotal += okCount; - g_failTotal += failCount; - if (lastErr) g_lastErr = lastErr; - - char dbg[64]; - snprintf(dbg, sizeof(dbg), "ok=%u fail=%u err=0x%02X@%u avS=%d avE=%d rx=%d", - okCount, failCount, lastErr, lastErrReg, - avStart, SerialUSB.availableForWrite(), SerialUSB.available()); - mqtt.publish("growatt/shinelan/debug", dbg); + g_okTotal += okCnt; + g_failTotal += failCnt; + g_lastErr = lastErr; ledSet(LED_BLUE, false); } diff --git a/ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin b/ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin new file mode 100755 index 0000000000000000000000000000000000000000..8e69b46c82d695bb9bf71fe7f4a80af7bd15bcc1 GIT binary patch literal 95384 zcmeFadwdgB-amdOGfC4nr70~?+5(ddXxnfJ6x}N7rfHepz*cZ~5!6j7x>Hbzz$z#! z!J-z~eMHLoAXOIyT@^2+P#z+b*2U|(?vrqFk>XN89iUz&Z6VWJ=J!662HpKW&+GO3 zzJCAyGw1p_pZoco<5zCNPqz{Cn%w{Y`F{fga|=<5RifP0N0k3oi+G~^vM;l8 zY}u)tky-DKw=1mSS$9Jlu@hT@k}~ARcd>6|W%~c|J+(YRDI6-s_iNFTWuOG9>3xiH zqb4FXxi1BO^ZJtT_o_aFGD7|8Cc<}egg=dk5W*jNiZK%*!=B!9{hV_ip=NQ-&YE*I zcFC~_y$yrYK2o}C^+5_)|?aj&mH@Z?uA zjZ719%sx&3P7PjNkuC{J0$Z$%WYayhNGohDefN6i$KIW_s976JuT3xRJ)d5S7K;_% zN&)XCX7B{9#i9j+UXxWUtl7~YB8s492(>jk>xz4K6c;NuvO8;NyFQdUhdjcf-QsgK zhCS9+LE6slKh88Uv)-0T8?hQ(7NRMCyTc3!7+egXY;NJoi1OEdwqpL$ri8&0=2p@+ z5#?xEVrMEBdc38CFs>;Vl;Wz3*0Ez1F0sAGkoL>aluDwc8t1Dvo=v!|ZK}9cG=w?J z%VD#L^pcx@=&^2CvH!G6oQK0Cl?k=?yqlML^?s7jZ&HPeX7iCGI-lZ;iR@li@kKNH zx8aiJP$@U=M25nolF;J)gl|2s)QiT{6KB-2cC0h|7MZVFI!%hj^k1&27ZVcvuIq}6 zRg0DMwioZc8D+CNEb;k&}Qy?kdJ?#(apfj<|Yx zmloO5<~Ctw=_8IORIpgFd8)vZJlck(EyHHDVV0!cwW-=$O6#(f$5fI#&opr2h{~A( z8zh!$C*jYN5CuD$nn^N!$sOtm0=aGqVsF&shq zn3{p#or~?jx+6%ty6?9iQO$ZecQ2XeRxElstO^}7`m(kJ)i+!_)NR(Y64M)8Y3~iL zNH0F;K`zfI3^Tt@z{tlI-%?|3wYJ1oL5qB)#Uc7*Zn>YqMVo+plPD~qMsKFf%l{G?7pt^s+z+Fm&^?^4j5$An03IE1>^PdxPR7WGdg8NIva zoAt3J*C3ZC`iNXB=MRsRj=C7Md(6WKyVPxDgZiA4!kZK_u1xS5ZwtA~C}MqF(?wpy zS5VL!QOrcgxs*x@r5SWpijo1PRcd_xWWJ!c8iU$s9cr!Ty)G?cJsfYlu!T%anEMNr zOSbwcZu_FlOylIsq1N+-E<>3X`B@ujyB%~*z!BHAq?Mgxk{=18Km;16n|lT)733PjrK4P(WJ?SPztX_P7ZYfw@(bUswOLoFIPo*k@D?W8*%z&CE+5&Bo!MxKT`1z zJx1Sng-bOAf9OeE{|9j?xG!%24c1S&Fj!S5z8Ickuqz3MKZ-<2?%Q*Iu!@$pwE23> z4ZfblhWZ|B1Lf?gxz?78X9mJNG~*-JIW$yh0(OaR{_e7eWl3fLF)$yL3i z;4=RDgdW*k$>k<{x`W2to}ekWH<+5sg?3)fm``hdC08k$^PD{KJJM;UJg9!fequ9J zm_?KFWIt)!0eJj-{v-cO{)+!4|L!aK=xaum(kUEKeYPX&2AfbJl!J#(sZxA1j;NVa zj;J|!_$iAdrl5l`v!)*<_MGx$a7%fR*t5!6q|PEWw%mZ!7f6jM=a4#y)aY^}Qpb?W zC^sSXccjwGMU+`}7VkH=7O9-$BPw%wVkTda&h-vr zPQ+u&Bj!8sem0+&U&i}7{I0`u3!d~ApHL?+PH{45?~LXY^CU)jaF7U-C5Ok{>BNYR59i)9NG5j&DTEdc@FKsrFK`s z=jDmabsL*E8j5;J5d#TE@&Dl0;4f1br*^+v;oq z>#)95*jia9)Ubit8X{EI#B=#IwU4+=til+RLY)#>yCZD0oDLJ>>aq||4`FK`AObH| z)>N$jMA}wb`^a5--buN5lW@@hU4pCF*j!ZKth$^X&0~vSQmNFY^NRB)Mo3y?P}Uh5 zU54MK7qh?pkxN;@^@KV$U~T2sjNKU1xvD)z#iK1(mN7iAYPAGDzN~Vz1BF!4fwY0GU5rkFN*1{HM4F}p;$5i!h@EVnt zC@|u&75gb%-P))tI&@*Hx5X$H1{l$GITO%-F-o;zZ!5Q^s&~DYQ`T`d%aZIO9$NP4MOj?#e(8xv$<@`*y1#wzajsT z3%+Oi49+bKaTvlbLssSJQ%f-4y!$CO@HSB8Pq2X(v%tN|hR0eRC44}hte0$R<_iAL zl4+2(RlTlTw8-_tb@HQMfo5L;@1eL*NM*L@>NfI5B_VolE#dF#DPqs9g6=}BEv8CW zH_Pu;%UrvfDf})Ez1NdOblHu%YD)?z6i`6j3qzwYW?OKSvO3imbX_)L4bBXaNyVLv z!1goYkDFtZEc`C*B5N`QTM{N6XY6A{W`;T69Ewx1>+)#NSy{84mlOI8m!`UZ2nrXC zY|bR(fMK@qTj`p3x!5Y!{_eJf@h3RmVBMBdtI5O;P^V6iH*Zb zFu>vYLqFp135^k|dfVsKu7b{SKkaG%P}OB^F;}@$E#ZHP-!VSRh}M=|9T$ztG)OsX zE3*b`WTg~hRIVDzH zNns8X!rHAZV=6P}>{MMmr7fad4>;{wLDME>^PnJ(tl-6BRdUpNN-sx?UG|-i62T^> z%<-xBIGF;oW5eA=zuBSQ$CGdl=?yV#6OaYCP=^Y*qto4!hzBh1|eD!8v6e?7a@ zYLC$16o1O|U?;5=oWA z__Ro9fYvZ+yVMd-u1;-Lz2CS&iA7o@{m_rNVfGOCfz%crsD#LQTU3rnUB z=mY20PI{DT1m{-K7Do>z3>e*8u=dO~f|AObr#yHVPR z48XK>ASGZFX06H`roQFMNMMXwpC<<3Uz!uF>U|8{W9C5ZBg=K|g|F%d1kt_B+sztH}zLsY+G&axUe$qO;a0R|^Wfx{@ROEiDr7+M^ zVk0x0vf8ez|BzZ!x15GwfYJu#KQ{j-Qo27`V*O=-I3M}AAM)B@#VRW6DMw2fhFoj^ zH&Tx@3lPfkOn(USALW)AF3K}1uU@m;6N7Z^BkAz$WCU$%T$i&wF;4Vif7NAWWuCZf zJuh})g_kR-hWLy>kug3irB9MaIuZ^NVOfn(w>L1U=B}Cx4W+$2)?z*8vM^!taSA!7 zSWy2f!0znCtys~@o&7y)I}#F4 zP$(9PVLA-A9L++e}uw>5SOyaZ6 zJ4$j-xVkOgt4oYZLI1(!2PqHfP60kl#u}0N=N>(`mcstrDhH)Fs$m$jQyji8p9Dlf z53}BJVcdI$FcOqKxSTjHhQ5%KP1z?L4X*Aauh9GD720z6j0)4w3q|1mLj6sc`3z8+ zZBwkJq<6GPXa8lHu2h`%Q|G$6GdwM7iL3Oo_CQQm=RlSF7 za$DNS)yowz!3F+jRQ}L+9oAQieAM?B8FanMwk}Qaq?$l$=kWEBz9&0x5_{#^bruKd z&R&1!e7cfC9wK9z-FS|{=>3YwBtlG#iAiT%-RbCaNuObwIfN1M!a9=Ao*LD=u8M@N zg=fZfnM0em_8EnJvhJyJl)v>D2cr^+=kiH`{eL2yy3-nF( zmWV2>qvJL>xFEX+>kqx)%R}%Lvge!M?@5?6u6Ru7rs7ed9D#vHc^z3$EMzvS>v_p} zcTbU_9w{CYNg5K?(h<#kn+Pp3g^Izm5b z?fQnw04fWpoFNH>pMwYWyT_sC9qJSESU_=%hk*^K7PsH*>K^0Sua;E5*-Y)Nx$3B1 z-)}O4(D@4J+K%?IwroBsccN&6l;6 zwDf+p%y9-}2QSId<$a3UDe=%) zv+I)m+sn6=R`psJQ-1xQ@YLO}qK6e*D~XNjqI;otZ7JK*OszSpgPmQwCE?d?dM@%f zJ&!2A>PJs2p3t>d+E<4$=LF_f`{>QYM#t*vPK7mgRI6Rf0E-NcJA*{1U6s_iA6i3G zbFpG&UqQ-1^MswNXbTg#b(bD~o5E%^YPM+LFb^)Ddq!piOYl zM+hH{tk!I6jd?Dm$>+K}(!(ihp|kqHGp&M9|K*dk#2+=n+crfbk7=#)&b8Fu+qsH3 zUJYRmJcXTIu^L8lBQ2N-Kf8=Vw02cA@>easUX8M&A=C71b23)Jh;dY696#m1|0SpO zk~&KeQV?HnpkJ@>b1kxT=yf@X6H(WMuT%czs{p7b2h){-)PhhRR`5Xoo_?R=O7sza z)l+f{IOjxN+xl!l6|~I{dJ;|3MNUc9TvK0db6rKhkBezz3v$Y4h#Q-u5eKv#N=sjP zgjKuLC4fh7REy*eA-#EnYTdj)L3n|KEYJD5tG z8kut0-w#e4=J%9lQlW2CTCsp`DD9Y$rWQ)tTw-H+PHBsNi(Nuq%oDRA!8x(eU*z7{ z?7EzSp8T+D3tP3wk$wRy^3Bjh{5?0sK%1s}3q7l+oiQQQBC75lYF zU;oItf|MsNLfhL)Y5jeAeg6>kfLm^!yuv0@EX>8<68Q1XsH{MLYmsOAzV(b5*3Nj? zlQH=hv+vM4{1Wg1qp!tL+UiEV)yId3UEtF&E*o}itaH-Di9AuV`_D^Sa#eo$m@1wmWmlNgt zK7;7;3*cX40jF_M>gR**(l(Lj@byfe*+0>y$aD|XR!Gd{v*5e67j-Ya<i6kUFfL?hGtiZ18kJw^9h&gp)|6^&dSfYk+$sQ;4d=f)5% z_o%`!fctQM@#O~8$Ouro71V-td1i>_ZO}_S0FA`jiWcDKcydp|t0rZj|9LgXX_><{ zKCg~*rr21#$2+lGf%gPwLIs02?%{_6zJtI-Ue-3)!;7 zPcM)(I#QF;tFn9UP`QDG7Pbzn)_6ps`J|B%vS0~+J^a<$Vu1C&x}Osl_!(Plz5;sD ztzEmJ+@O4anEGA-ZocU9)tK8W2bf&bY1m~Qv^>p84kYBNL5u-=6=XP%0X!I?WKPN4 z`j+S54d6uwBvWvZ6@&*}m0WnO>Aefa0+TSoep9(qJXdd2P7K~qVOw`Ym7(ihHhaxY zp6rC#ZNfeC)ag7PXHzDzFZN_fb`Qz_W$4HSQc_SUi8&sFz`RCVKBvK}58py~$f%5p za>C@B3p@o9<0$^ZDfx&|VWWAKnaJThFRmMp-LBf`PAR9XW9ft%lTt94g`6LTGV!-Q z3NN7TCCTAUUpcNK%|nz|`YlS?5K-;}2Hq4!T*N@4^47ozWk`)iHV+yUcRwd2l{vfw zd!I&uzKc|o{(iRKq}@jSGogQUh<&-XMiSP$C1y(AlH8mP`lSaG9l`~t}-89Z%GBDe$Io^u&EwROhCC z&;gm&rEOH^np-R<6D#JaPUj-Op{w0654_b>nI+-zfGgOIm6zo#aR7Xpmbc)c@Syeo zbw7(!Mi0gIxx6hSZ*JJbD-$@IGQs$=n>lI59^tt#ufx+LL8wmdLHyD+(17D&~1@ou25arsh?76`{=GD<}jedH>o6rIWTz0y5 zN|f88)Gx)`;dkqs432M9Rt%W^yuf;oUogxdjvIoDqlIkTD;?GwA_+ zKBc$ZoFf)UKKKa6h_CM<4x&8an_D?f$_dOZGb%Hpm2+6&L4iaZYeU~}GAXQS@Ay02 zcNXP|kHc>S{85d-yaxtT!cVh?#+2}#EQ7!0Ea7h|CymC}Q^I+yQE?6IuA5b1Xf%e` zve%c>5t7CO@Ev5d6^rR2nU>jhJ)kuOds-%CA2^FidAsk2Whc~`I}W%Lx{}PUW3JgZ z9X;=dr&1wU_56x_%x%=b^&;W(kg^WUL+Wazk_V_y!Wk+PCzadLCObf_Mab4Dt2FiB zw3qNaN~!(CHUSv^ipnaR`}0HtB;PMpDkF)qK?Me5*R-G4B8LYDH+QW_b^`aLrvYXg;D zq>aKWw%?gPce1qAKN9604#Z#ptbxxd=`?m{Hl!eb(G_@5Uc=1FZWcBWM{fx5Q<9@3 z_lKZO%$2B&yC}!orI^Kv&V6Pvjm`nS=bUxM$*}6~d3p7{3FZ^$q`3dtT;V*#Df3(7 zzjM}cl}RGzV|Bvrd^AAXPGXIntFi7_r)oY0nE1DT$gB^&VBGHGF!Fy;o}ZVl^HtR{8(22AP;zX|1x}oHjSD%qCLF*N*2y zJRjhBACDF}rWtJ1NA%ws^&HXtEb5z7T?Y6(-)QT_`hA7xG@j#lzQr@;f>vY{pW_>_ zKR5w1SP2cF)1T6`13Zjvv^0Tk8k3u5h)iQr6KR|U4w$bRp-a&?ow@zFkR6os@Z#4J zQFiuSMjTkOu}6l~naFNUc+RlZ^c?uW%ycnHq+|FaELG|iIRD%O&n$@w+KTo_)%mx2QR=M$<{&f_}jX6cj zebRqb=M3={e1|=%#lIWzJ>k^uWyJAqaJv5O*P1MsBbD5gTtS{8%7|{-MlEUo1S9$<9z-M(w(Ho+=C0aQ zt^-krR-w^+v(^Japl@d3R#ku(e|XcUG!nKGsLw{-p(oirBy@!|41v%S#ofa_*#xj zth%^9F`;WcG{O&}*h|f=43e(IP#o&Oep!2JM$=W zO^aIzEmurLmP=Lc?ORoD0wOS;rq=unu{ei$wpt{OoKmBartKw{vpxaM2qZ))hx}ch z1AgKKcL1)yqBB>H2VI?j%tXxHKqmMorMurnuB|>N+;jU15X697+>+lp?-UWt=dS>> zAxaN+)QrmOL$nX*MO9RT7$uGupV+f3Fb|&Dao7VizDATwpv~Ml;2PK;NjYcN7b!4JIXD@!OQ3Z7R7`Jsb+Cs*nA?q={!Hk1M&S2ID}Ex4VM)yE>P(TR}^4nS9}Bl$hS1$7f@;xQSUOJj9*HOttu zC6j)?$9gmOMuEuNQnB9%d%54RmHnd@SvN>^cg${ar!BEE3I38*n7gTCRgD%|uaDjW zJr;YPgMIQK`1CJ;7t3!Z=7o4ew>fBl9>El(d#KFJu~6TIQCtNpvr*$j^Ij`bk86C* zM}AJ=!Y;CN76wvQ*@Lwn_v zUu)<4onyL&2DQ5B|tBWAGXz9Uvi6T=fPq4#Sb&r zFJ2j^6{g^Qc^tesU2tOh2c{QD_x4!o%1T#_qbXQj6@+h_Kf~{Va4Y zg)OYjC?=O#luBS=a)_~6lw9~b4R}vfsgx~r!7e<+d|^@URhKKHNntCEQM9(0E7wce z5+`nxT+?07WJN@b3D+@fSu2wcS#=s-@1_wbDM6x``>Xv&Herw7pht8#{!-72*aKE> z1!so^uJrf!W7j4YuX6jqOHx94`oqPFnN44LR@wn>F>;Vbvius5r1tuSY66?4r)UIt zWTwAfO7@>xY7Y?J>D8s$`Xf4rU>qYtK}5BJ>Kp6pQYxIli_cV}=o$a2JGU(tyZ2<$ zn7T76@B5X?{hT8CqP*Bgxvq+N z-8#?^NCJo2?PfzXevyTD5%D34Hk~L_0m&Eye|3=XXVDhrIfyM4EaD%eqNUgQpOpUS zPLiyloq;CDl0@prNzP`^0Ka{<2ZNHN_Z z1rNn&+Wk*R3E>R0OJtSxQ5zyVDPLKta-!s?c^jiGedAqU6@Qhw#0|((9#Wqmel5eV z@&fbP-upjLCvJWWalfXp#h8oo%eBq`vz_FRJ(YPa)w-A&tWplWizs&vxUiSWyv8bb zX!O(qbBg442PyCPXCLk9)+qK2o~>L%9FZX9V^3&3I=$s0Vvtkb8sa@3Ke2a7X=1&H zmn?wTIEp)`cyOnuhDKBP+U(M9>3wwT$0B0GjFn%s zRPcP_-!47sMuai2C|kNL@9i6JJ++DCC3hHbDul^T2sL6>ivl!u#0sz7J8h%+3(%z7 zRcN%ia)`yc(3QPw>68HF7j(T?CD58HR;!t_Y8Mm5-e=TTV?5*sr`$Az+Q8FP=s(%g z-aeiu`AJ`7t!3A4cbkqT!5=tE1x$J2!K6;e8T?LB4Fbu%6lCr82klb&gA{)$9#Ea# z+LA08*6#J^tlf&*3#G-t0y1NP$LvY(%!bUNzpS1z@N!FMEWH4Fb_Jru!y)`Gc=rM# z{4sN-Wz^h!skx6Q)`Nzt(&q?Ldf+{LS6U}XW7lp`t?j>`Z8>_21g*zb7ntuE{UF1$ zc84eH!7OnW=wKvT>yj$xt23=^*L(19W#bEb&&Qb+&?4zL*(L0QPH2!9<%OP0XqWnh zSSp8LJ0@3HC18XjwId036LWo*i)vMw{=Z1u-I<8&j9ZkD36SrkEv`?|RqI5lhiJ47 z_$ZlV0Ua{4(p|}D$E^z0abCD{S-PLf{2=Iw%5y3o79xfdeqZQhv^^>hEaJ!h5op2c zKLw29LfLBt51APAiKk=jwa6=4uAkL&v3K7`Wr>07!{1RHV)XSjwIUw$bLi(r#1H&@ zaEY7qfD_E5Jd16}=5B98-$4oc+N1zyaTQ{V-OrwnKsy^Qxpk1_3}pta|4Bd0jqpEm>HnlGYOFq zMETQTFUpMq9gUMn;9Kk*Xpu|Nc7NBfcE<%{T-@MD3wB96-3HJ&B1r~_vRz#epc3^B zm0axfQps+Bma|;35a0(-<7Z>~dU?YVk-ss(dY zKGFF!BCwRl-}zZf@@gZ-YQ~8S4w0Pc!OGQ4qI#OA2*HBVW1&q^+5)7 z2&=ObdQZSQ&kqMP!50yqAMKV4{p{){6L*3cN9-(5lm!Q=wqOF5kx4h~z2=~>#^6W_ zvW^i!#xXhM6kn8*mHI)WI8pim`#SKtjZ9WdgO}wOC8wASFV*o2tN{5H&R{Hxq$Z3X z)$x+_pTN?I5}AB5L?iL&u6wfb?Z9;+pz>RqUNra*ik-$EgS zs1|e{_FZPNbS#jea2yK`&P-&btOqGC+!Anj8Ust?{O!R+a1Oyg)~`jb8{&$;YNmXH zOuQhcBVKSJo*2)gyp+;ais<)8Bj=;Z@PEzp9|3l=K?={`4i)_r(T0Qt7;MD_oQLAPYd0(F|g&CC>Odhy}$e)CTFe~cF zIcW|MrEyS;bU-s~2Q-W5ZX~q!bI>w9?78~5){fmySZqsRK|~`zABN~Ct{*)ZxCqvr z#v>z64^W9lr>^4QE72mUgBvaq;|BM4&4|mu$|hr7=MLuT=N9OAl3^LddW&)7kK5RW znLCTOB?0#<|XkFcuQma2sL1LX^}?Tz#$gf}< z#x_6gHidqqs0vUNgZ3c-e4|ct(4209U(4rDhCO{`X}X`%%XXZ;K#b86E%N5DWz8wU zE0#5lB9D!w5c21Hby)uB5=x7-Lvr;&a-CJFj9JBXh z@Fb9kCO~(3Ak(wpisj`ky-hNEF3Z%{2f0{!bLG!-v*G(s0j<9vk?t)%2HcPCD|2GR zKWkir-wlss@PdJJ9N#mHz7Ny&n}^}(4%@X!E!1`GqCPfOS=Bd1B2zAfI%GjAs9m{ir~9)I5mwe7 zllM@M?ncDIm$AyP`Z#gyu*d3*x_R?1KM^*lnLfc&0Ds-Jp_#R-YJcP5!e;iw1x6ob zmn76U>S}8a%3hvRMyflgUIX}(u-8JpYOLr|b1GjCz!&E9%>EcLOg*9aTA<{UEB2RR zblK=PYq;O{4{vFi3aB$+X48QOQwX33$TC`Bu zI#IfWae+sRODUuQ8-phW+T-d#I<(d}EWD+L{6uOwQ`p%J9%IFKa<#;IzLS}JCIqVm zc02VEQ=jp=mV3oZQB zJ*t7!s>L}*(Hf*)Csvu%m#1T425d~HpBYCnaxtuEoGFKf!74S7I_HAX$lw4oOoxRn z^^}j$CEA_3{Y6kR5@4?6QwYs+X~ep<(B` z`YF$NDX?DmJ+IS!&oQZ>MJoC-A&scUe(zv>{_T(w7v=b`KTFeBqhE0TgV6Ic*EG13 zLvhYq?4Kbm^sJD!dzz(Fh`P&`zLO>Jb9k16zd;1W2F)nu2JS{`9a1E>F<2lO!<>1U zXQi~;vsdb(@v!t9qCMa9&N7leE!0{&REyYH{hWE;njMfVFJn*U=e-ZU&DM=It-G}H z?czFEx1M4NUiUTIN|#bD3jg>V^f4EG#J$)g_v26o&nn|}|LxCvfAu!>{#9*q|FTh@ zPvvOjRR8qVwDkv~)Vnw}Sd1|{;BV;HJr8Sw4@<=!c<$aHKU$c?_vSwa2zGjUB@gC@&sX(a`R*V3N!iG3qgZ$@U z+;;iwo0#6N!XBf0WpUgruO;T4_m1=I1fNJYzK*s3Kt9k$_1e~NsQ>m4d!QXE4MVK7 zug|`jY2?DUVm}ww3d=U4Kk&t~n@0AM2Q0xjUo3(FqUr6MQ*>{tVVgmDL|=(- zgJ(D44E>6;^(N2RdrAKMQ0HB{Mbq1p?oJ6?&9Hk+&wwi!!)8MR-gGC`08d`FYMnq+ zChb3w`#7F>U&MZ!xJ=4jyN!BKH&N|mI->W<%5(MCi{@7iIIA{YG{2sN)O5}KdXZ>F zD-)odCB1Hb&7jQCzPVTQzvWI3Iiz!U3HR-$o(@5{J9`)dwa5!YRA2e+&_>yQpVjjk z;xH)3bc*b=XeWBDM5bN}$+8i8CQi|;V;#6tLGqUB+IeXV-judeuurxPCyz)jJem2o z21(w$4vZJFjQZAyvRu8o^1Tbpo}lwG2MWJ^h6c_$K?JDQkDsFxfa-sMEN;% zCNg;%bbxOL;vS3KPLi7!lJM0@zvg~FEKk!aNRw5XC$S}nv}#H(VH)F7*HXbWHM-4T zB$tuxjRQ)U45Sq#m5?n=BWZGYn5JbOIx{D%nNi%wat7d{eY|}Z`Rn#sOKc|0ZSO$1 zn;95cVDPXN^NYdT^nG)>2jF{V8!@*q%B22SeBAT{)aPYDooz0r&|5kgWz4X4BZukk zcd{bmoD{Vfla-@UwvsE)M9BAOF(}-WQo;H-T6791|$}A;Q zrZX{{kKD)DSqagb(--Nr^Kf3KsgBk~bf%s%ib(&Y%=u<|3X_9PJ4*cb+S9Mp{z*G_ zs$0kV$SkEkBx)y){y%D8KingKUq0F)wlj4F;sH7mw4fCI*a9KxE!9$nSdS6Is|o5O zo8hH~7vf-F?9PEV?S@$JpNSajZ}_t3jBEFx=f(=DuLwTXVa_$U2@ zR-<+gd^d$H#j8`qcYBJ`Ho0*sHK;{ojsG_|Vc(32!N&oQ%8$_cG4(&ejoJw@Z@@N@ z#&ST1-a$1yE%LsGw5!nNn!T$li8BVd$=#nXSMG%;3)(+V=#3)1`9EZ&vg3W!g`7@;crsXI7Lm$_Nngtx7A75xa zcm(Yi@%@1|=y~^Xq61i)4y?^ZoQ(r<>gmARSQ`9bQj=7SsP%te18o>z4E#`JbN|0$ zDY5@r@AdU)gZ)lD^>Q>{G$VDF5Bq*vzL==zu0_hCryfKqMNh5JG<%Ysx>w7wC+jH* zUt`)qtv1e%v!#Iko!SchEk~Kc_U577X(wN_7wS2;Xest~JvB!wu>VC*m1-mHALuEZ zXtuwnr*6~^+5f7i3N?fM6FpTxBhgpT*f%Zm3qUJhv;t1m*yo^rmyz&I;Y=Fn*E@^@ z8a-zU4aOr;dVuvta9%_AD$dh59mi=)S7FzUvyanzI22{YvVhqh!3uTW>J`Y8OLr=nl$>3XD(N6+f%4M_hzdQMM2 zhV)0#PCflN(tnA5si&Vn`Ve|Qj9>Kb5Jnh>cv_lX79DBl!8vJr58Dsv-`^kVurJb6 z?bKUt{}Z5caL6EdB*9y(^Dg*!FkWgKP@cGCrdgSey*b3v1BzPYIs9eI&B_|^Y^%$x z}JBRju3ttRfT`l583YHpD%k(@Jsk@{5)EzwJ_NDXl{@84?9mT1n+wnC& zDFI)975yoG9#^+{P})ne_Mu@sT{br1kQ%lEb8{& zPx&8Hp+#0=%pr-GS7Lr<~hVaH>&E4S6IA8=t*N z&liU1s-7OAJCa_&;Lq>8M!DB8gfoUNn)nmx_TzflABGtFQ9X5Wh*IvjC}StWMLnl! z$RI8V#J+Y6oz(M|41KP@JwuL!ktfW+JUY6IgE5TUHh?n-I!6lhnTPv3J@{(Eh!Z}{ zmXSkri%o_N#tQaGJ$YTo1?bCSD$3w)qh8S5^rBlg_}k#;t1G&}6D3@Nwl zQL$!Dx-+9idZLss(EgU9Kgt1~;m z4~I_5l+T)5eD{J6;y!^?LT|sKx^4R;x{ESGTn}FnvHhahB_6UHl>Q-C_hEZ_rLoJ* z83qyx-oxn9D&JE*o8MFSI8!U{_PnR=bB?IA!#g>3CeABL7H~z9o6upz=v^hm2J5%j zr1%a|zaO=Posb#Wg5a|u^Zd(*`OP{!z#q!1QveAc;@+E<(l*o}{YAMB#fJGkA3SO;k|S9!vQkY+7WBxdH0Fnhcdb92f7BPZ}B&Y zd_>dmg)diF@)!8O=!yjOwzBIQpam4~CE+rCJsyj4k@U=c)H@h+w z_%>P;{JU50_ERhw-qDWYKugHsCJ`1Wqaho~>az#EcFL_^ba?slB1JJuOcAFu|1<>uy*&5ydV zTkHE=UCT4m$J`$1TTK$18z?T?;KL=$a;&|b>s~{7>fd0vnZcxKBXGxui;M|P^L~z- z3=wHwL{mH2r`cyo-tiD(Kme~d2J>J|UJl(TlDxl$lBS^-d!9(k&Wh3< zs&+Zn5*J!D?H$M@=!8rr7lfMOg;*Y(ArxtWa6`sX$D2Jko=$bPJ7!Dpjb{h zoG}=yUnb65y1A65%djK4rEas12dAI|8g)++tv}BnZ;#fdIVArS_6rhm=SDP=4vRYe z9nst!vG4B&FUs-Ucu%zT7SQ4xt`&cixOZekjJ`(i?%9DTKWcsP*wgyDpJ`f{gE*Wm zkuQ36{-73vBbYe&qEk`!AwG!>+(KvEw}NpR?Xak+^jI}G3z2hH zGs=MP#c@APmsxS1<`Rb@Z$mewJdJ7!lpe^GuTRC>JEPvpHqp-5JvfQfa(*tTMh4Ayx$*yehHqNhE^5avWA;B^fc9u3h}*VKmaeG zl9$@}OnH*xY~3|%+cWkCCud7iI7DFOK&sP-q1kO5Ze76{O0mJ2gcIAe?bw&XHWmK+ z1jxn_IwVGc-zFd2e`(V?>#6;h9$)9`IqX~B`}oS9Of$~9AMl+y&nj>1-B15}{AGf( zs0A;$OM?3^xz{bi$y5`5Wd-TYnQH5{Qfqi@>gPe|3OK*cq`v^}(B#{HY5h6{G)I)@ zeGaMCvrO9Q>5|TQMt1`H5di~fi4|Kl%&k&R(=^S?1_d-_#iL;y@U46*50|y>>syWT ziOut}zN1`PZ)Kq_?}m9ET7lG77owF8)Y8A9*6n(&b64v86j#irDBe)~iS^9PYDZ1I zgq8M1YtPHl_ZMMH%?{9U?t-L`TSITjhAGLRcrNXq7b&*l#wG1rnYodfa^Yl;Or@2I zHGJrPE%Mmmd_>R3amf)p$-`Wpf6e8XzmDs#ZbyQ=(oq8tj zcvbI5`t3zysJ9T~9Ht{^Zs%4Df!Hsq$HhlS_2}mz!*M1;3+Yk1oH$h+hWjv1WB{r^ zT`6OTwiBn|H=8Hzjp?KmqOl&Dbh=EOYB=LU`)`^Y0G2)q;1%PHNzXnVm+HA?1tg|bhZBjpA(+iS$(`GH{iexIJsg6)`Z55c@bSy zFX6_h>hf@zqWozYZ=@GMFWC$FCC&f5 zQfC3`EF7+5J~do!xL;=0-%wL;GmhQSOV`1I7_B?BnYgL-6pR91N7{$de|QHx z#PlApcQC)rxZ9Q!cf%*=Lyz=U8G1V{Zao7o(OZ4vsRKG+p}#Uqu}=@(yr;;5vqiC6 z`e?gS^8XU0I2ntM#tmCq4kBV*mrdTV%uHs3w3N|%1{UEq z&~T?I`=JxGe@9vyvE$#?0x_qpKqOnJm*^@Qtt*=Kd#lpW@*m*`0_V~G8@2a14Yub! zw3Y0KSVuDA9Z|cHY%$T=_5$&QenYWQZ-I^7d28E>fb+CZ-}$R`cP|`o$}c;4=Db1S3f-lpQwqssM1)O* zhl+%)hHBg;yxCohzkQ*R6AcYnzCYsznyW=d_`&D6-wP}pp85D!Pt|~@C4#3B<;W0j zWLlpmdaT`USmzlUvX7aj1_~m3AGGxqG z&h2hPkXrqnxC{E9I2)lwHb7RP1hD-%;AWG&KJTbrgE;>FFE#2=<2PtK(|c5|!5AiZ z56OqZeComwVCdW#<}HW9Y-$xCW>GQ>Gu$ucRSee`@qUSSNLikJ>MQ(q-&XZiK4Iea z`|px%d4-{bY1a;yn-QXxV=^>+;(Ski$r*r0YnS?p^I6s8+yEYno1+m!_Xhj`+e%+U z-K^nyqw&TU*yS749fUZVf(-v1z9$WTH^sg?o(dZHFZDZX#(VF?{WvQ;i@^PO?jLmj zTHl`mx6=8<4}+Zlnqi!yen_Hxb7&V%G*eo&crQWc(IV%EX7B^>_ku%Mr~4f^+eEnp z5vbn^xMxDA#i6W zGiw9>o}}KEsuY~O!T+ZL_eU^*-2;R0yj3rgT(;*tblg>gjEHd)C%7&HY`YtFK{7Y0 zb8aPLKjNVn)@vOv72z&-R>&3il+x0BNOhp9_kIsis!)0)N6%Ej`qUom+@RM`hil+A z1$(^CK4L`=Dc-N-SeZeTfe-phn+Nposovu5kI+OgNM8H2JUAh_s1X^ z8Sr}eK-J~m`*FLa_Z9gS)x5`q*bB?GH_ygh{W~roiiRYeQP*zfE^T-qR*%A^<0tiO z-X~>R2jw>d@`z;l0DDz{yz`U5dw@TM;Ct$E4^u#)7>{i+Un|c340N#w`!|%6#k3CS z#7`Ypk2}YE&cT24*u%Ih8hkDC1ocBwYYMuIj%n`zVm!&^ui1!O8oktdD#yJuh&4S(&&Vp^)94G)1|-~$6D-ll&(y8zi=eq_@HL)H zI=1vx8F(Qf+f9?&l}Ud;Qm}zXtm9Nt}au53odRm3rDk zZ~KjV9Msn1>S^Fi7*V{qT@K@Vy&rXTx_!afCD%ccjMgQ|3;nSfrf8j@=1A3}S9A~k zOi>k4{y=Ahn5iJR1Ercs!<(wX>6up8=DC`LgSchklp3s_viBZ|r?GEz)ot&n3)9i^m39}U1p+ma$_l8Rkc6G=CfWb@Oaj&Z{{P?q zlh4i0+_`h_o%=lJ+;hICC$(YQ^pUYO^37qynkcqkf};+f1^2;NDijF}$z%${H^u}? zxUQ9@)Z<0Gcl506?avX?FZbP))8X!ijGp_c35ScOktJD8mc@-Fy z@x=A=*zL43?T}xCw28fS6Mk9io3h3~dY*N8KBOqAklZl#O|Y$419YWKv?mvP7nG?P zy_X))2aMJKnRh14yAIN4#Jq=qS|8d-w{)Fc@6 zb`Ma}*t9q0RZgtlE87p-VN)W3n8-WG;k1p>h#^B;MS$PUaBIb;srG zf?~{-QN@gOxW^)h7y}3U)FTXMX+C5r^I<63odtd*GwMC;6f+((3-PEBQ-k`VG&&`X zs{0MB#it=-(me@GUHmHigWU<51ym$I#ypC-X(r|KcX_1U-aHbc{GDuT#x^(K&hbOT zyv@aM&Z7->UyV=G%G*?Ck56yY`8NAL_W7FP(bBj|_#&d6c^;u9k}k6~yv%0vGPi`z za)vP904JQ)l_L}&Fs0C@#xDyW4rw9IZV6Q!(b|kYH6&`4)dp*ow3^@S<87b#T(;8X zTC;Karsbw4ty$+&n@fEst2g;L>tB3X&}RnXvGv=C3)XJ2h;~$xPd#d;Z)^3(zWG3P z-{kuioYIAe)nH%{mxz9w@Y_iG%Z;5Ssf@EE zy`gP+zHOI}NqxlE>RX4saP0DRvf95+*6g>fSSRbzJBIx=qRG;>ViYiP)z-Gh=KaL> zxW;08g17B{?2X4SJU(XSI(d-)H!CCfHTfTTY|KyA$wQIDP5T!;zD^$QuYaNjTxM}e z+Y_pGGwTt^D&q0PI(aPewj*zPwocA*8gQ=VWY+eXZJb1~!x`voILEM1D7&qT0oY%3C=3hx9}R z85oV!xN_bIz5PkA(~gD}vEa#b4XmwT`E{zNC#W6xuEE{v5veV)>K%awXgubNqk^${ zd~3Rp3N3M~mteUkTHxcIx{Q% zSS}4PqdKa2uQcy#FFpGPy4x~pXT*sZwXa_=1LK61${@$r9nc7Zob;d*={Cwl7o)Yt zdcD8FI`xh?8Fw}y{k5!c3Hma<)Pouu&-SOK*`A?TSW~@>>TcL*MI%d;F_MniqPdIG zy(*JLiIo=h(%o-R6JOJ(kYDM9?r8h_kWJa*(I;UMx1x8XU3Gp}`)Yo!d+sz~lR>^k zxN_h1=>qAnE@Vo3A_r3#vm$MQFB0t1eP*nlLl|KDB^Vp5;equq#5xF?OgAI$nZ6`X z0Z`S9o}_Z1I<%KFMh_zu#fDf7NbxhISPHJ6WHU_j9@Kds!k-XA2p=FcBAi2juK}Q= zL9<#az$jD$P2pfdC6WblQ4_Sw3sj&juXrKT>F!eGHa*c5I89U`9qiOamm5B0kgCla z!_(tpZ**tOmPucT^#~^+ndZW+8z6RY> z)MOjp5lV0WS+W$0BF|&6VCM=3hp3ce-k#`6NEkF{d!vhDt(ez89;gG^LtyOitmT+j zFD(b67dI~veW{tG!HH-+q#18wX4>ZUOO*(95E__x0^|WZdlqsUv#pvfu+8g6iF`HA zZ>I3`rU1{O8PB=1{m&>>4@qsu4EEA$nTS4{C=J*k4|aIRpfn&E+TU+>z(#~*OGL@% zz$WQ=#4;`8<`p&Rgq{e=rlBcmMjd;Bxd^8g#uX#afaD*^FM?1yj7Uy}lWh z@DCG<@S@R69!Q|SC)xuXcx5g~*e^Wd3?jwO36uuPVG#>jj?*-Iyf|0Eo7})VuyX+4 zWC_Bu*a}Nut8WH;sK7>>VHQ}o#IJU6@ifNhj3PFK18_ z8o^{~O>In8<>~PLq~dw+9MOX-jxR=g()d)KxOkkS%@NaFKZq?Jh%NqpY}G*8%7L_y z83kQpKL#p#6QxbfdA-?#F9h}tzVKrg4CAv*gIPG`0TJz{slDhm@2dI$4fkrwUQ1p@L98Q|MCDUaC;c3DQ2IxjN>=WBZYFwZ+O^UE^RQRw2sf46msSnLvOV^`IZLwZ@OBz z)?lR+&@{S*Z7m1In*}P3$vYX=NMG>v?bA6f*LC%Ee--Mr$6x7iNgw$Sc#u2N=$c0e znVtUC>CMyd4T*`g6Z|Fa7k-X>I77LlGMtW-OFF|T{5Kg+MhYg-ithintZwDu{=X) zab8+Dx6{9PWNcHcywkk+4LrMw)XZu|3L!PK`1;drktJv|F;7oWj0t1h5T*9$>(LZaKCuBtTX0H8h&ox&IU$Ot$C|`8TW8( zZO{AddPqrCYxKFyRTh}xsgj00&m;!^K30A&F|#DCPFpxi(yA8Tr7O&objd@)hlN3{ z^rb{!z90i_V&Bz>8M_YkwV^$6YiN4(vtY@6!rm(ey&IMcv~O>pacKGwnt@+Ir zo%Rml&Mz0_v7zd``Y`HUbpZ9A5WZgL2m0#_JYy!;28-%RXK_AV>2d=Fd7lC3ijaOw zCEflkX6Hu@XVtFugMJ%&e?K_fO)*!;et*eT6Fbah%6T}p;p&*MO-kWM#%g00sN~9Y^yKQke!OWNcodlgOSu-kw24gcXx4w^c#hTT{6 z7(+j4MY+o}AQ@JvSkUZwRAyC-YE#9A{S94_qYa}X25YV~lydC&r@H7A)#v6gTU>xI zVwErYUF2)f1FZ;Wg-n@s+?$&ef`tQDnX7C54Cpi_{;Y;Qg|;g;Sf|~V~j9PP@i)aG`Ja=opSYzuSp-cr63x)ub>Om)bYhw zdu}G&heR|T_7Pt_a{SVg0zxu^2Zs1@xfR-epW=EsuJ4|$>e>YuJKe*~=!dhh=Df8MD)Vh8$)Y6>K~6BE&yak5fb8*7WOz-Ep|fr@v7 z&glhe?FLwFFj6zPlXz4c!)jl=em~hufX>=t4_*o;K_aInnL#a!UE-?D?i{&l`~)`S zn2-v-imd~E4N7t<@c)vqH!X+_wE^W1J49P(!Vz|70puvm*l!~E`l3VeE?FJd!M=6b zWxbvSieSbn9+(ij<+*{9>HKMa=aBF#)6{d0Cl{i}$im2l1!hRI!(`aE1R9}*~D`;Egg#BF=H0ZwI=fd*ZWZ8wYCZBAE7CBpJ zg0DUJ4YG2DpeF75Fs1Iik^0|ZuJGGP?T8G=_v&CaX! zsy*&`#}q2j2nvX4er*S%TZWLblhN@&@FMCFC_MsZaY-hwRi0w6%2SKb{}#P-9_hat zc!%f+LyqCx1pA2d6DF>4XSM9Y-pQJ|0AYcR%1Jv%_j*Ui8LwS^{$8XoAjN$=qz|~C zgnJJ6xA0#@7+Z!wd?&Eb#5@5wmNyetD(TUT@%oxTE0)wLUhyCy$s#17%vBk}CdgS> zo`dCEG)z`OM&Jw052CjmOl~}^YQ>cYbHbAzoSVrOnVQ&JyTY9BJ9=A4fp#vPgqboy zh2TB5WRp8h;6vG~u-=T5!x@>TCPT?fZU%i$-uO(7Dah$1H_D&Y9R}6UXn|3ewTg!< zfIkI{@VE7l6%Mp{^S>CzJoq5r$iEHE+AMY}PJe(ENK_9Gst0(KW_B-h)4{bI1pmWr zSJSgNajC%9gB3E#R+v$=_f7bjX1Fe<>KN(Ph3mWV!i!-Fo-kB7iRn(gY4zDNZ+n9ke#c}@B*5qJzQU_P4A+A6_-S@3~^ zBS7O2ZH7~DrwsWrFnp+YiViSifG*O_f(tc)pR~$!wGLOql`9!n8SvS1) z_@G;>SiIdLFIjx0`d^vE*T1v)Rri?iq^x{k1MaET!CsxQ4?6p@{HT*WV64Y4& zyAIKRE~o=PZiIgY9jI{PV$||upvY9r-Dn-+0y4mDlFqsTTn;PnrHu5V%mLLKan1k6 zzUdr;9`>+8h6wr^IIZ8mzxTiHe=~4D*kfw60V}b-J&8*yB@EBg4u!$he>eDI*%MydyHOF(~(qQBcZ=r5xzlq6ZedQ=A^^-{Ok~&C(-_V z0df#~5Eik}P4HpGf9%l3>M?=OF9CNS+{ykK#0l3YE}_?xz`>C$0JKf}O{_)IvV|qN z)o-BxiDt89W&-b3W!77Dli>$-oN0>3YqaTZdv{)y_nqo;&;prWV4W#46FRH9^N`&5 zi9$KiZG;R zady5VaKAV#U;%{xB0+1tXG-PH2QaBf0sWQobO3Sub1}2{z=!A3Ryuv zn$VMw0=!E#JBvAsQR*Uv-bCbajsH`{eUNacC*8D4_>6B&!ekhb9S zQjWK5k=Xxfs60sp!pJ zUR~f*@whjwq+H~1pIKb)odWqAPWTo5dl_TmiDtLNaf`hZgik8YqUHlw%Zg=v2z5P* zT3@g4@nBNkM4_lT$-=f9HDe^LCg4swO(U%c`2i&+fp#;MEPg0RafJRTH;q*^uV7P@ zxJ~$Hq)OZ^(y0HHTXQB|xE!g(9miLC(_6n3H+c<#FU7ETdlq~TU*S3F%p1c>z4Mit z=NHfSwpLKCV-r=Pu8_Px;oEQf3wBVF$kYQvCL@pw>H~II(D*HCIy#@Na#z(>9hGlG ztGQZ_$|canMePl6p9!@3G-4Y={V4$n{ikLRiz&(mO!I;Z-- z-`CxEUWex=|HpIFjpu2L@jk3mepo)8`uTyr$Bex~2-y`7Mm6wxt-JY3#06TGeF@T1JNrt<*$&K`>))4bGXOvN z(`LO*jo*)(>HGjVxv=U`<-H_mfjYchFudj}SH(1)vo3#>H1@QneW;OxS6V)A1f<&< zDW4lMjI|X3?|)JS;x)z{E4=p{V61b6SX3v^6=YDXGvFi0*~;3SWzN#ez+9u>i^^|X z#93;{>!I1GL$t=S44D6EN=tWr5TE=D>h|B1T^an=~|P5+5YLtI8| zIxJ=xU~`yomjZ23I$Owv1dGPP?YrR@8leQ&Kf~`#{Jw_Y;q{DeG{SIP>r|{O2?5-j zi$QP!t7m5e$%Kr>`a(HdlSN}k7+jG2(p>seqR2|f*04oPFjkim z*Od6xgOW#Yo23e?`n=FGTEPTO-b~|BRR*CEN%P$~u*G!&Yt2 zsaMt%i@0MA8&2YG`^Kz|{st4Ip);=?fsevz%dxADPDEc!=m-I6y{2sR{6ut1LTjx8 zwp`{~mn9dc0K}6#qu8~WoO-P4SKi0D?)yd1z{_#=w?bNo&q+B=wFmc-INr{4B!k}y z`@{J+>i?s}dO62c1iY~=vK^Xxo83Snf~WhTAyZCTm>CNh)+3Yy?R_(V#@v5q?pEHX zxZ82kWUr9nA)Q&ojJbprail;wX#|}k3B1dY)4vaV8UQ_!MJwLZi9rFi%o^FL9g&9u z@QJO&`Vc!><`BzF=l}*gd_pb13Qw<~ffZaPtUu7+VC}=gnRhy#b_6m?uWM}hnxGL8j}fB>(e z9n*mSZ}aVp)h4YjE|!?$0dkjqPThM2j zu}m6`HMM`=E5G)(MO~iaC+z(Xz1Q~=;wu;@<05-*4$^P_r@C^k!04?NQ$>&WsF<2* zFuRL)xVJU$%xuYI%$vQw8H(-qPd^Dxzk1J{Bu}Q_{nH(O_omD>D8ZUZ2sb#DntAQh zX~X=4TX4)vxGmRDr@mjFaCPH+8NSrn$uGIpg7viOr`bo`;#=9)nC9ABah~1Z_0-7a z?TnVk!~%j)VLLR#o?5}!v{GLmS!)#+ABbP;5Z5y1cf3m;lc6D;@R8Q61t^DP-`ItI zhHd@KB##=@3o{WX5Raixdxfi;=Fu2y23o!ymaCujsLLxAtBFcwk2#1n;8@eM1JA6+9BA9zqGC@0J2nP1HGeOFv_v}Wd%jFOqwU5n6`avS2CVIFqT2=Q5WW;g@BG}40`W)v>+HO;iX3A`eU@Yo`|HxU(Z7R$HK z4LNep!FY`ekY*W&N@Ty!Fc>3hoN^ zb>JP+olEwp&Do+fX5q5#=y$PaJ5n~xhsSi=G6mNMbZphqG>qDv>h@DOO*PvVbw?vH z)(mU6FVG_cxX<8wlyEhfTr{X63|65GEcQAhc8rN90dy{bW<8Jb(_Ik{KQ_4@f^<`Q!&XuvM z^!xoP#y69%GI($rIS<#=23=_Xx!6-2Ii#IUXQMX>i_h~v;~b0NLt4kGtQk(d^pXr5 zcKeURpUWjyRafeAXl*ch=~>wo)Cg6JrQ20qYae^sJG$i8ZtgT$1?>Dd%8v?Y}r3V*Z}%ueM!)t(oR%e)u;J^!n#h@vD&>EsLeL`61&C9 zT+y+NtTTIjpZGL3LC{@w6{Hpx0|nEM`&&szP-@;}I& zIp4Zzd7H>uH+yflp%!zD06z4r+_aKyPv*aEI0DOI6*Rhavu*PyDPKiah^niDQjg2} ziFDl%>5Wz;uAds4h>KZs1Y__$1dd^fNwTID;JNIo$4oNjzUUp`2`diF>P$l)VNUD% zq9=L@-@=8RcT2a*uEX5xzuSnr>}IQ{-kS@0a4hfzO6H&seSOh?;| zYR{5nLJ!Fu`8}gLn5@j`A)Y~3I2InVZsw$2u|017tQTH@-jTnK@HY6&x>;vq>I^_K z@`^PjoHPXXx!<*nhSa1l`c5yi#UZ39&p1hkCnwLgMt=LxR2#?W7<^X=*^v+(kfG4* zebJiUwx`BGCe#LcG9(`2}w!6f_n9Z7&~HmORkc7SRZFk3TdT8++f{xMaNebyRQ8{zs`N}3_JA% zEM?(k8rq?4-q|<>;K$nLG16zy2rhyrvOk3Rff8#_;zuo7={(LtzhBP%C}+|@IbZdv z?NdVWzxHQaKt!uYvN_g{{>NppUCU zs)4d@?$b(p|EsRYQP+d;Il>jKM^2?mPFIjqv69op5K6d?qkC*c&xo*BVezTto70sl(7eOy3GyM+7nntGb-&O7`ibSAY!4*uOyyulB02 za&-h4`0oPGvmg5FINO2OAkyct5}vYFnh1@kj+Rj1Z-!*S6`So2Pq$PmxN6>@-)C&kI8{b>p;G0MuiFgK0#&x9Jefj3s_X6wQFxE$v>Z)>Du_*z|1 zi}M@unUn!-<^R2A8tD*pIQ~7|F_Kheo*GCC=+|OlUjGk z@)L5V^PLS;_HW^V6J8+)O5faf_W#?;4yBc^kA)w_ueWzi96rhi+S`cswg=Cnlmc%P zN;u%Bc7Lev9}%Xp4hW6V-H2tEXP2IoGYV7SA?B%-rn-}|xiGoTebuybFs`%HPh3ix zcoLE#U`p0O3WPXQp}x*<)8Q(qP+P}c)fd1!WEZzmjTlwF3fF5^@^xvez}401JS+lJ zXT!?75Vo4&?YmvMjQL}%-J1|TM7VzqtDBENr-aGE`u%?B);$gVxn;52#0lWmb<(^Z zCQy?7h5Yr_!*}tm_l+#Tc1=gP5r(TcvXm+)RY!Us!PX?TpWGgAAS2G4=JGFLIV z8PL<{jh6I4iZ7{q@-ag#!f#7;@cCmGYFnq?o-w}VG+SROkCvF`=_59n@$L~I!K7Q9gsc#@IjB@`xQ1v8(;)#sN3<8 zah?$3hEFker?L4*bJH$>O$YVT8dO7Yu8J~Cac0L5&y*#J%*~l+sowX>J!~K}j)wt7h zT4`?YuJ*fl*1-v?Gt=$#%v`!0&kWYx?Gq8d`r2>yS%mD;MaX~1K>nGf&*N_JK+9r> zM;((?TYLhef!#^a4`8F_5x1 z+L!o#N;6WL2U3nJJ%N zOklJ97#{8YD~6S+y4HX?fS38sUqHLYzhVetINx!suR3X_k|wnb)V~vB6?gHK$FUZh zp~qn=f#^T62WQaD5s+w-4dum)+YN9g^_jL)N%d*JBPqG~+J*wLCHld_^ zJJJ**&4Wm@Y9P(_zI^1At)dal7HxO0zV_&QHepKnTBN-dX{UcL?b^PvNNfIH+PO%Z zU%musCnD{H?hUXbb>eE9ToJGcdANE}?$_kZz?-8~QzJN;Lh2km$@t#)+|y_B&`ddo zdD5?EU5Zp@q*7zo2fCqjq1%czoDsF4dw@0U%ZxaLVdWDd*U;azR`lYR#sUvNTC}EE z@JnmQ82sLWKx>A8Uk^eh!cv5vAiRk1GQw{W8W8@B@G$1VS^WMJ;UdDn5$KGF8RhW! zg^$hI;HXwY_kppOhu(k|+{ZX^&`R-MT~H+yw9+}mBYkUe3I#pT{r3v@3D5vF9KpHE zrO;gT-4>jswug_j+mgQw-6{Z=YDzqCyQ3S3%4>mU1KJ($l67fGpGLSv7z*iS2Il!s z@K!WDS*XC17r~btgPs9)9L#Duttp^+w_H`uW2F8f201S@gW4n14(ew~W~c+_mkPgp zML5gd0e+I|I{}g!MMu66zDb&|z}GIUb;@{1OLF>BL4A}!zQRf`b|>RM1N$EzYN3&- z2mX{WLY4?A3(}?$zpd~Ete`?lTQ_f3L;j-e>x^Wg?9U+!rv4d+>$wP!v6;z-RkYVI z({QKpu0-^Bf(a7Vy-rH$cHq1bzM>Ngn1ishEEN77S%i0XJK&5mdMC zZ2WJ;I_V4aM2|qPK?^UD8K_l;Qmc=9|5GdameX=tDQn#?C#5r3XQ1nqfKLf~hSzkq z2S}21%Y9zPlgxmDrb(_Oj5wAlaBP+yl%?s-Yt>3ad1=+N$1h;98RQG|A2ipxnuMHWvQW zs8_<>iv`Y{h4LTiO^nl>Un~qOWAVnr=q)uo@?MAxEgkAvD}IL65f<;^#X@=+`R)C4 zZw)jb)&a3Soipsq7WhvOx=ql+w+S4O#G#1*i)fR#euG`8^Rv*C`2gQT_5T8O=byv) zMxbw??|K5)BtNITlCb|UlDT)6sD_r4MWELB0wd{Nw+<+F7D0{s1HEbwbSK2!h#QU* zfKLbV67$Fx4WUOc?T_#;$+OKF!kme{(LJ!(o^1juKm7vFfh)MOkZe$J0_DlZ`}7`Z z@Q^-!zChf+TKWm-;=yRiKPtR$N2n)yRN;ro6L`Y8Oj0u}5T!d>eDG8TpLNjBy9p9; zvsJ{5ySZ2Qc2x=ooIuv9Qgq~tW%IcHGc&dHEF_rFAV=Q6RPw$Wntu5!pB@<5py6lD zf`0ajSv&Wyz}`z^)7Zz3GJ|I|?CFi_dmr^=zC9eiXb-+oqvQ`9VNMm}t}>Ql*)=Y8 zkJ;KAz1A~XJS%5x`v>f{X%$!vdu=vp-b{;VUJ8A7E-A+Kcv3RorutRltry&fngzNF-Bx`atZg;g#uonqL8*_j-@9Ju!t9?u&f?eX~-B>+hRy`)5QCJK&7Mw7?13yu}wW zfXiDNQRXSu5*qt7)@e?=+{p~Pfz{Et+vibPhryEsv_AV-f%vZ%{co2hqC5L2ulm3q z&&MU(r<=~O1&87t9sj@zkAB_4)zMkIOFHX+!-M#%d^~l-E>Gs(Eb)J~&>8sl1ky;a zWhs|)2j*qw+r`S*LI2*yU_5;0I;+h;r3HQ{+R=1j>5HXvGFcm2Vg`1>ORxMCVX?=Y^$?k`~ycufuh6c$@r)iOF$E2mMcCp5B9R7?u9GhYhGL zq~kTd52f!dMjt98k4k4hakXfzOI_{j*6_J5>8mx{*^HH=*wg`6jkX>dA#}L< zg{hGi*<|K+p3G!&KfzVTxfY-u8nO1NaggJC>{ z@n(l9)V5UDx=mhe-X_;L`74}FThL-s7aZvFuins%J$ENWlNRhrGhPBF~wa2H(8vkyp^5Ynz2QGG(pRq){!5B|F^7A*$W#&e88xL|Ejs+5JJw$B_?!UGull!?EW( z!V#5aD!y`V&*g|J_m$A(GK{Gez#s2fGh8i9?d0BL?B~zS!8pkglm>)9j)hImhXnAABmN6 zOz@$U49(cPoo3s5d5&ehTcwd1=pyd|yG z?1+aiMQE&V99Yk3KJ3BuhX`GuOD^JC&*bt@~EncE7sB^D9OID_gv>syxvXRcG-^&q3 zV9!_+{|OMG?nT}2^J=c}W3-d5nY5GshPeB@k3-t}{n>PV?Qa1J^@HzG$*15QUXzbG z1$#yEUNR5!>ZLf^FM9VrYFR2@T%aic&(`UFk5Ot+62EPLPK6!lDwQz7F(}L`Jb6l~ zR{BLOr7Rg|wu2If0vc&Tf@{qR8q3psq<2l@!ce9|s@j+aT*QZ<^+=Y*Bc`qUf}(#x zJ_bpC|78=T#X*!A;oDRC!3Uja~65lH@XbiB>Yk$aiW% zko*I@*xg^6OE#49tI`5$=usR>XmFC{bdlaV(@cB&60C=LD0?1cMkH&Zx|5Eywzr=r z)hVbGggDWM(+BT8WGUX=Gp>X&c;PqV*puuELMk}69JZ>wQOD0xX_)6nkR3mvEA&;5&F>ho}>nT>c!Z=#t#i zY!KH5hlypdmSqD5uN&Td?-%vr{kSXl?tqV)9p2vP&|sN3+bcy_F~!>%@hSP}1EuhY zngKl*T_d$;L}o$BYozT7x@(m+lDV%Zx~h+9-iq}7X-*BKc~(jDSELz?G>;%n1JaOQ z4XngtM8V@UshRjSCu}pH+t2M$ZR?4)#EcehPfzqvOso7Jj3q0-`(v!~`)&+;0G{uO z4N`vJjHM{QyJCZt-&bP><#&55N%`FtGXpuD-D@o6Xl1A40FqSk{H7u)Gi(1+iBffGFIF>uW zk87j^cvf(%W>_)}aN|XgQzfE*OR!EEsT@kGZxZk}1Aj>98u*FEA1i*As2)X+gW{H?ukcVJ*T@gboB}<8Z76%*yBX^IQ41 z)mYhzm0nCoeh)$dVT682JbJHkm5r;}N*Sr>*=(g~*1qp;7q}6Iuu(~bK*lHK(Jb&;4!@Q86r)%1R9ZXZu35%-Y1y_|F2AoY#|JvQ} z&q$SXM}HoJQ^vwyJx=gh7VAM(yA`ThRgQDR zQQ&?HP7Ks48mIllzixy=SF=_YXHvcyk711~f_`bDAn8utyB?^N+(ea7>^0bqd0&>b ztVTL0-vx^fgUx7bLyXRnpQ~zy&I4o|Boo`;HE$LNTb$*gTh8t88oJsS8g}kk`!e9T zdhCye9!1DUV9a-7HF>y)cAB1OL;Pd=Bcc6WzgWF6^b5NL+WqcU+&%0K8IZ@9%YKY! zLzn%CQY`y2tT#`mr?c*(*je|||A+q`9%R0?b+Tu>yCRfzt_Z)gLPoqhZ|Yrm{^bPa z|I}mQX;(RB4BnnMp)}+~s?yK|{Qo`THB4sfcwD>j-Z=bD3-$kZ;QBXsZeR?pIk=t@ zqW`}_4EXETX;)cB*QA48i3;c$9IUi>Gc^6ooe>T2PPJw!l4SlaVt^)t(R?{F*!*=Q z*?bY!ta_#m|J6(@{`1Uv{8uq&@n6gQ1OIi*=lDN}3FE(!`78b>F`wZ7V0bk+jPc8C zOJU4!XqA3r{-R?@hDiOmYxEe#+cuPGT?8}|{q68Y3K|9+_RR_CI%&zu$80SEqQFf( zu-(C0Qv{joSn%7nhFMP!| z$-}qJt4Ex<*w*>b%HmGl{N3|@i8Hrba;Juha$TWGxm>uoQ(sk&SUsnfb|j|Ti$b&P zQ$oL#4f)L2e21>Nz8d~knu{eDUq26a0nm{e11+P`K8@6@AXRAK1wgSfLQTI0R|hRj z3)T8GV(CgH?UA_=LtD(Ep1g{<+}KxC(ytB`cN(fbj1+eotC}PA)zHFSSwHWXa^+gt zt&Dx>&nvW48Z11q!#i#|$5;n@lFKr^$v6oQU1IY&tbnF?v1J!ZSpsg&?A60&>AE&+ zWixq|;Gkhb<2M$eIdcmALb7>#W^KUfAYz%CmRaoRP)-)CUZyRr=TCHzG9$Q zd@xW8|5gtY+B594=8Ki9izPE(2|swtr2Bv{1oTp9P_M-9&h8zGRrxK@$MzseGTH6$ z*8}-GGfsUrjye4w1Q2M=Dpq^f^WRzh!~9JH`4fUsfBuS(y}c^w3FK}Dq9Wz}&l`D9 z{9fL4Zj}aF44TQ{s4X(}hN*GW{u7tj)IqN%?cle*8L)~fScZbS0J_DAOPW+*rCIsC znb`H9>0swtk^-uoHZSQ=TH#+&YFrPhfydX4#P>}?4Jv}ho%sS=XMoKzxW?^Mup^wf zQJc#*{Ql7?q}1eW+`K;>wyozc4X)$^>jTiyt7P9nebUv~#TjUuY0~(;s6zoGmCK|x zMEWVQ|0*}u`+qMtbfesT|5Ywo2>v0kSv(Mc=5W9WiLM7Hs?g_in8I7ov$e1)R4uH9 ztrvL{sHz?_@7v6Uz{BZBQXxbZ(u~(ZGv0J?cHnujBrpoz)k^~<;%yicruTQkmWk4u z-Y44u9cm0)rsDImeG7S#P6t96<5&`6>`Oy4;46A@=s9uqk}rbn_}s8@95e@gBhNA8 zMzy%E?c;y#8t$G9&)g|Mp*4)l1b(b8`JONbElps-_4*r%YO`BUKq7JoIQ|bMpAQk5 ze^yx2nldgg#Ge~HP8CWXcYA1=^dMturFU-`cy|ciT~*84o~o{QIRn ztW7I^dY&8!LRMi87m6%q(9_kU0uIE|^_7V2vYMyo@PQI4Sd|uJY*U_q9Sz3FyEyNw zeI|;w8N{4cT8L1(hA=1bKb860fA!l4^qVVaz<6Qma)c=YkC9uW^qCg3;`7*bKhZQ& z$Xnj{O9|k2GIq3F&f;+KGsfUxTk+%do6~T9I$>pfEi<~R`uHW&J;j}_%Bp#zAOH0N zR?6xff7f`AlTaRtXJ@OLIbk^Y`sNEl(9;{u7MLBHFe?r2T_$BQD*F28)G@w2#oZ2z z{rbmkPqM`m;X7MpxDp(GC!svu+RcppLSC|ES39Qzim&(Y!6`znU;oJ5kM%^=-Ajct@B)(~IEQPbyJAd`y=9Vk7U!7b z#V?iCIt<$Dr&y0xAtbOy`teIVq$x;xnng{lw7*9Si-hzbD+PKwBX+@poF0praKE?b z+X#7argzAf<%Ae3RRW`FSU37kCcl=f)OCpTn`>VT$<8e~j#(1Jzapz$e(YySs+lFV z&+(_J&q#Cm!iM_l2FRpTAs`*Cz9Yn71#~ao;f{szMn&;6&_mVa3rRq07|ec~3G70d zXxlb_y{NyU)*hEro%I{r)}DrzR=xL-Y<1SJq3b`%8YkU74NTNjXPK;awrx7%Z`)|A zwQZ`ZJp@bfwl#;av)R_p>ik31ku|xtJ@ReNqe{t5*n4#xmB&c6^6v<7phhKB1|E2F z+A~S{b26vbq?Hs`C89r1EUZFH!JA=`i-jlE2Vu{1(^*D0$Rjnr3M;pyy9Uh~Ui|{> zw1F1AcgK4yJk8 z$(3HdUFNTbolQ1HE^&GyV-6wE-WZafXEah3BpJu$=ba|lu8}91d*cUzkN==ne+u?M z?H{vhDFxn}i3(w*yN(cFbt?7-uwRnYG^)tA*ES%Q-Rk`1uzAaFP(^8z2nqP7k zHD?R|iew8H6rAo^2^Gp;it;i4W6Zb@pw~=&L3MOuemwAGQhr*=;PUKh6Gk}`bb{k5 zbk+v$+^`?A)z{#~d=Reg#T7il!xHyZoF^A!mR}ArVr1Z3O+g?8Ku$z9QIs!ny!|L< zBJhL@an2`)C^xvYMjq*6y&DKVj_*K2F|ZLFc~`oSzhRJYAxJxy8u)&s!_8TpG+w2N z(DsSBVF%be)Kjp5JoH{|=it?MX9LbZ9xiI;yZn^}{%yfRoLaMmZ#Eq4ip{dqsmR@h z{so8MY_MP){2qD{-@(q_*93hw+`mV964>|VVUIqJ`qJE$8?}&EkT!iHYQsL`4@?bUj}lMW3#G#9Am4k*<4lYSZ#aaoz?YETv^@r z#Fa^7* z=bq#6D*~T|702awD^`jw**+DRx3UrbuWk3xs62jY^gXQ$kIS!CjP}m=PQ(c1F|Ho+ z0%xN80LCG$0n`_yyR1gv|GJwquh=jV*k%IcDg0Sh+6{C8U(>JUyIm`xpYOo8y$7x6 z4}c(qIHzO&w*wlvjD2>9dXaV9iQRBb_d)q?cp3oDfSGCrHp>X;a=-{p83xX6CV)KUKRb{%8Y)<%SzLLsOf8>Z&hQ>TP9TlS93MPu=L%=!Mx9y zRO~YvYJGq3(b!}TlO!_{jU{v?B-KsB`fC->232S`wFC?wWz_ZGU6WN9amI2hEHsUt zhImIiA5XFA%j?{~Y}S|CyeBiPUhJ#`zB91CHgBKYKaQAfHv_u6-={PO+j)3RBss70 zoIHrYvy;FsE(lHzGOIJNA~f|L$9jVK0_;Rqdb9ryzR8wJ3vixy{f%jud6cFsK&f6D zNY$mJ>cE#j3SZx}x?<0V&T%3-sm~SIg`F6BCOFwbsaUaQ_mb@(E4_7SCG3S~yGY}r zNOLYwyX0xmNU@a<6&q~&!h`XS&WVBpGuT+pY*R_bUgA2Opx{63$+r!2lAL6h`yxE4 zku@=Ka6Xqxy0z(VUotvB)}TZ*)Qo(=38|zBnBTIZ+o2Pn+2MnHB~EMkN8K9CA0NDt zT&lu4j@68j{^-}`vLQ`w3Rd>n&Li%jr%95-Kqrnk+1VHsG(Y|rV{Gp{H=&ih?vQo^ zc~hX&{F#<4O7-sumy2K1B;+y&=ZGWE;5`$@DT7gKVk8-70%mw2WLD5w%&&kZ-_ek6 zrBy#9GnjenWG7HX8iA@ndks+_M*Z*s8{Q5TH!IVR4U)T(j}6L3nz*_%}swqTQM;F&WO_#eEw?9JsywtdPu|}x#ezG`8sPqP~A`B7I`~l4H z_4_ORFQFw`H2I&iKqMd!dln!f5RK2owIxS8w6@5?qYcd1cUFGAa@pFzi_RL2^icfo zvh(Pu!dGQA!~Zf}*4&olo&62<)PbN(&ELkU8%(c%|VAV7`LHBUJ_Y={#ntkA?q(*p~gN5q){Y=gS zp>phQ&u46tEl%%-G_)&Z2DVKPxeoKNO-knKudLt)BnPyvOH1VOec`#`3sE&Yti}16jrYy_cmg9|LbT zz4eXi1 zHC9Ht3%;J;S_kPdzIoR=`ev8)79a`LZgxLopRzsE-+(W}_YV=04pcokS`Uks>0aCD_0&$aFG}yL3TmHAL{pk2 zv{d+#j)gAp5j&_+(%#iUA9g6dD4MwPVqM^917AYpa$I4Cbtg~4F78A6ybqcY*R@Mop7#E?_+YYXB%xc&8m}?+{g2gx;ZD< e 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 kV@V(B&-^&d6n9+X{ej8-X($g2);X9 List[Sensor]: + return [ + Sensor("pv_voltage", "PV Spannung", 3, 1, 0.1, "V", "voltage", "measurement", "mdi:solar-panel"), + Sensor("pv_current", "PV Strom", 4, 1, 0.1, "A", "current", "measurement", "mdi:solar-panel"), + Sensor("pv_power", "PV Leistung", 5, 2, 0.1, "W", "power", "measurement", "mdi:solar-panel"), + Sensor("grid_frequency", "Netz-Frequenz", 37, 1, 0.01, "Hz", "frequency", "measurement", "mdi:sine-wave"), + Sensor("grid_voltage", "Netz-Spannung", 38, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current", "Netz-Strom", 39, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("ac_power", "AC Ausgangsleistung", 40, 2, 0.1, "W", "power", "measurement", "mdi:flash"), + Sensor("energy_today", "Energie Heute", 53, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("energy_total", "Energie Gesamt", 55, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("inverter_temp", "Wechselrichter Temp.", 93, 1, 0.1, "°C", "temperature", "measurement", "mdi:thermometer"), + ] + + +def _sph_sensors() -> List[Sensor]: + return [ + Sensor("pv1_voltage", "PV1 Spannung", 3, 1, 0.1, "V", "voltage", "measurement", "mdi:solar-panel"), + Sensor("pv1_current", "PV1 Strom", 4, 1, 0.1, "A", "current", "measurement", "mdi:solar-panel"), + Sensor("pv1_power", "PV1 Leistung", 5, 2, 0.1, "W", "power", "measurement", "mdi:solar-panel"), + Sensor("pv2_voltage", "PV2 Spannung", 7, 1, 0.1, "V", "voltage", "measurement", "mdi:solar-panel"), + Sensor("pv2_current", "PV2 Strom", 8, 1, 0.1, "A", "current", "measurement", "mdi:solar-panel"), + Sensor("pv2_power", "PV2 Leistung", 9, 2, 0.1, "W", "power", "measurement", "mdi:solar-panel"), + Sensor("ac_power_total", "AC Gesamtleistung", 35, 2, 0.1, "W", "power", "measurement", "mdi:flash"), + Sensor("grid_frequency", "Netz-Frequenz", 37, 1, 0.01, "Hz", "frequency", "measurement", "mdi:sine-wave"), + Sensor("grid_voltage_l1", "Netz-Spannung L1", 38, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l1", "Netz-Strom L1", 39, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("grid_voltage_l2", "Netz-Spannung L2", 42, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l2", "Netz-Strom L2", 43, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("grid_voltage_l3", "Netz-Spannung L3", 46, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l3", "Netz-Strom L3", 47, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("energy_today", "Energie Heute", 53, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("energy_total", "Energie Gesamt", 55, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("inverter_temp", "Wechselrichter Temp.", 93, 1, 0.1, "°C", "temperature", "measurement", "mdi:thermometer"), + Sensor("bat_discharge_power", "Batterie Entladeleistung", 1009, 2, 0.1, "W", "power", "measurement", "mdi:battery-minus"), + Sensor("bat_charge_power", "Batterie Ladeleistung", 1011, 2, 0.1, "W", "power", "measurement", "mdi:battery-plus"), + Sensor("bat_voltage", "Batterie Spannung", 1013, 1, 0.1, "V", "voltage", "measurement", "mdi:battery"), + Sensor("bat_soc", "Batterie Ladezustand", 1014, 1, 1.0, "%", "battery", "measurement", "mdi:battery"), + Sensor("bat_temperature", "Batterie Temp.", 1040, 1, 0.1, "°C", "temperature", "measurement", "mdi:thermometer"), + Sensor("power_to_grid", "Einspeisung", 1021, 2, 0.1, "W", "power", "measurement", "mdi:transmission-tower-export"), + Sensor("power_to_user", "Netzbezug", 1029, 2, 0.1, "W", "power", "measurement", "mdi:transmission-tower-import"), + Sensor("energy_import_total", "Netzbezug Gesamt", 1046, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:transmission-tower-import"), + Sensor("energy_export_total", "Einspeisung Gesamt", 1050, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:transmission-tower-export"), + Sensor("bat_discharge_total", "Batterie Entladung Ges.",1054, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:battery-minus"), + Sensor("bat_charge_total", "Batterie Ladung Ges.", 1058, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:battery-plus"), + ] + + +def _mod_sensors() -> List[Sensor]: + return [ + Sensor("pv1_voltage", "PV1 Spannung", 3, 1, 0.1, "V", "voltage", "measurement", "mdi:solar-panel"), + Sensor("pv1_current", "PV1 Strom", 4, 1, 0.1, "A", "current", "measurement", "mdi:solar-panel"), + Sensor("pv1_power", "PV1 Leistung", 5, 2, 0.1, "W", "power", "measurement", "mdi:solar-panel"), + Sensor("pv2_voltage", "PV2 Spannung", 7, 1, 0.1, "V", "voltage", "measurement", "mdi:solar-panel"), + Sensor("pv2_current", "PV2 Strom", 8, 1, 0.1, "A", "current", "measurement", "mdi:solar-panel"), + Sensor("pv2_power", "PV2 Leistung", 9, 2, 0.1, "W", "power", "measurement", "mdi:solar-panel"), + Sensor("ac_power_total", "AC Gesamtleistung", 35, 2, 0.1, "W", "power", "measurement", "mdi:flash"), + Sensor("grid_frequency", "Netz-Frequenz", 37, 1, 0.01, "Hz", "frequency", "measurement", "mdi:sine-wave"), + Sensor("grid_voltage_l1","Netz-Spannung L1", 38, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l1","Netz-Strom L1", 39, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("grid_voltage_l2","Netz-Spannung L2", 42, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l2","Netz-Strom L2", 43, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("grid_voltage_l3","Netz-Spannung L3", 46, 1, 0.1, "V", "voltage", "measurement", "mdi:flash"), + Sensor("grid_current_l3","Netz-Strom L3", 47, 1, 0.1, "A", "current", "measurement", "mdi:flash"), + Sensor("energy_today", "Energie Heute", 53, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("energy_total", "Energie Gesamt", 55, 2, 0.1, "kWh", "energy", "total_increasing", "mdi:solar-power"), + Sensor("inverter_temp", "Wechselrichter Temp.",93, 1, 0.1, "°C", "temperature","measurement", "mdi:thermometer"), + ] + + +INVERTERS = { + "MIC_1500_TL_X": Inverter( + id="MIC_1500_TL_X", + name="Growatt MIC 1500 TL-X", + manufacturer="Growatt", + sensors=_mic_sensors(), + read_ranges=[(3, 91), (93, 1)], # regs 3-93 + ), + "MIC_2000_TL_X": Inverter( + id="MIC_2000_TL_X", + name="Growatt MIC 2000 TL-X", + manufacturer="Growatt", + sensors=_mic_sensors(), + read_ranges=[(3, 91), (93, 1)], + ), + "SPH_5000_TL3": Inverter( + id="SPH_5000_TL3", + name="Growatt SPH 5000 TL3-BH-UP", + manufacturer="Growatt", + sensors=_sph_sensors(), + read_ranges=[(3, 91), (93, 1), (1009, 52)], # regs 3-93 + 1009-1060 + ), + "MOD_6000_TL3": Inverter( + id="MOD_6000_TL3", + name="Growatt MOD 6000 TL3-XH", + manufacturer="Growatt", + sensors=_mod_sensors(), + read_ranges=[(3, 91), (93, 1)], + ), +} diff --git a/haos-addon/src/main.py b/haos-addon/src/main.py new file mode 100644 index 0000000..42b583e --- /dev/null +++ b/haos-addon/src/main.py @@ -0,0 +1,221 @@ +import json +import logging +import os +import threading +import time +from typing import Any, Dict, Optional + +from flask import Flask, jsonify, request, send_from_directory + +from inverters import INVERTERS, Inverter +from modbus_client import ModbusReader +from mqtt_publisher import MqttPublisher + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s: %(message)s", +) +log = logging.getLogger(__name__) + +CONFIG_PATH = "/data/config.json" +WEB_DIR = os.path.join(os.path.dirname(__file__), "web") + +app = Flask(__name__, static_folder=WEB_DIR) + + +class State: + lock = threading.Lock() + config: Dict[str, Any] = {} + last_values: Dict[str, float] = {} + last_update: Optional[float] = None + modbus_ok: bool = False + mqtt_ok: bool = False + poll_count: int = 0 + error_count: int = 0 + + +def load_config() -> Dict[str, Any]: + cfg: Dict[str, Any] = { + "modbus_ip": os.environ.get("MODBUS_IP", "10.10.20.190"), + "modbus_port": int(os.environ.get("MODBUS_PORT", "502")), + "modbus_address": int(os.environ.get("MODBUS_ADDRESS", "1")), + "inverter_model": os.environ.get("INVERTER_MODEL", "MIC_1500_TL_X"), + "mqtt_broker": os.environ.get("MQTT_BROKER", "core-mosquitto"), + "mqtt_port": int(os.environ.get("MQTT_PORT", "1883")), + "mqtt_user": os.environ.get("MQTT_USER", ""), + "mqtt_pass": os.environ.get("MQTT_PASS", ""), + "mqtt_topic_prefix": os.environ.get("MQTT_TOPIC_PREFIX", "growatt/shinelanx"), + "update_interval": int(os.environ.get("UPDATE_INTERVAL", "30")), + } + if os.path.exists(CONFIG_PATH): + try: + with open(CONFIG_PATH) as f: + saved = json.load(f) + cfg.update(saved) + except Exception as e: + log.warning("Config-Datei konnte nicht gelesen werden: %s", e) + return cfg + + +def save_config(cfg: Dict[str, Any]): + os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) + with open(CONFIG_PATH, "w") as f: + json.dump(cfg, f, indent=2) + + +_reader: Optional[ModbusReader] = None +_publisher: Optional[MqttPublisher] = None +_poll_thread: Optional[threading.Thread] = None +_stop_event = threading.Event() + + +def _build_reader(cfg: Dict[str, Any]) -> ModbusReader: + return ModbusReader( + host=cfg["modbus_ip"], + port=cfg["modbus_port"], + slave=cfg["modbus_address"], + ) + + +def _build_publisher(cfg: Dict[str, Any]) -> MqttPublisher: + return MqttPublisher( + broker=cfg["mqtt_broker"], + port=cfg["mqtt_port"], + user=cfg["mqtt_user"], + password=cfg["mqtt_pass"], + topic_prefix=cfg["mqtt_topic_prefix"], + ) + + +def _poll_loop(cfg: Dict[str, Any]): + global _reader, _publisher + + inverter_id = cfg.get("inverter_model", "MIC_1500_TL_X") + inverter: Inverter = INVERTERS.get(inverter_id, INVERTERS["MIC_1500_TL_X"]) + interval = max(5, int(cfg.get("update_interval", 30))) + + _reader = _build_reader(cfg) + _publisher = _build_publisher(cfg) + _publisher.connect() + time.sleep(2) + _publisher.setup_inverter(inverter) + + log.info("Poll-Loop gestartet: %s alle %ds", inverter.name, interval) + + while not _stop_event.is_set(): + t_start = time.time() + values = _reader.read(inverter) + with State.lock: + if values is not None: + State.last_values = values + State.last_update = time.time() + State.modbus_ok = True + State.poll_count += 1 + _publisher.publish_data(values) + _publisher.publish_status("online") + else: + State.modbus_ok = False + State.error_count += 1 + _publisher.publish_status("offline") + State.mqtt_ok = _publisher.connected + + elapsed = time.time() - t_start + wait = max(0.0, interval - elapsed) + _stop_event.wait(wait) + + _reader.close() + _publisher.publish_status("offline") + _publisher.disconnect() + log.info("Poll-Loop beendet") + + +def start_poll_thread(cfg: Dict[str, Any]): + global _poll_thread + _stop_event.clear() + _poll_thread = threading.Thread(target=_poll_loop, args=(cfg,), daemon=True, name="poll") + _poll_thread.start() + + +def stop_poll_thread(): + _stop_event.set() + if _poll_thread: + _poll_thread.join(timeout=15) + + +# ── REST API ────────────────────────────────────────────────── + +@app.get("/api/config") +def api_get_config(): + cfg = State.config.copy() + cfg.pop("mqtt_pass", None) # Passwort nie zurückgeben + return jsonify(cfg) + + +@app.post("/api/config") +def api_save_config(): + data = request.get_json(force=True) or {} + with State.lock: + # Passwort nur überschreiben wenn mitgesendet und nicht leer + if not data.get("mqtt_pass"): + data["mqtt_pass"] = State.config.get("mqtt_pass", "") + State.config.update(data) + save_config(State.config) + cfg_snapshot = State.config.copy() + + stop_poll_thread() + start_poll_thread(cfg_snapshot) + return jsonify({"ok": True}) + + +@app.get("/api/data") +def api_get_data(): + with State.lock: + inverter_id = State.config.get("inverter_model", "MIC_1500_TL_X") + inverter = INVERTERS.get(inverter_id, INVERTERS["MIC_1500_TL_X"]) + sensors_meta = [ + { + "id": s.id, + "name": s.name, + "unit": s.unit, + "icon": s.icon, + "device_class": s.device_class, + } + for s in inverter.sensors + ] + return jsonify({ + "values": State.last_values, + "sensors": sensors_meta, + "last_update": State.last_update, + "modbus_ok": State.modbus_ok, + "mqtt_ok": State.mqtt_ok, + "poll_count": State.poll_count, + "error_count": State.error_count, + }) + + +@app.get("/api/inverters") +def api_get_inverters(): + return jsonify({ + k: {"id": v.id, "name": v.name, "sensor_count": len(v.sensors)} + for k, v in INVERTERS.items() + }) + + +@app.get("/") +def index(): + return send_from_directory(WEB_DIR, "index.html") + + +@app.get("/") +def static_files(filename): + return send_from_directory(WEB_DIR, filename) + + +if __name__ == "__main__": + cfg = load_config() + with State.lock: + State.config = cfg + start_poll_thread(cfg) + port = int(os.environ.get("INGRESS_PORT", "8099")) + log.info("Web UI startet auf Port %d", port) + app.run(host="0.0.0.0", port=port, threaded=True) diff --git a/haos-addon/src/modbus_client.py b/haos-addon/src/modbus_client.py new file mode 100644 index 0000000..857c1cf --- /dev/null +++ b/haos-addon/src/modbus_client.py @@ -0,0 +1,77 @@ +import logging +import time +from typing import Dict, Optional + +from pymodbus.client import ModbusTcpClient +from pymodbus.exceptions import ModbusException + +from inverters import Inverter, Sensor + +log = logging.getLogger(__name__) + + +class ModbusReader: + def __init__(self, host: str, port: int, slave: int, timeout: float = 10.0): + self.host = host + self.port = port + self.slave = slave + self.timeout = timeout + self._client: Optional[ModbusTcpClient] = None + + def _connect(self) -> bool: + if self._client and self._client.connected: + return True + self._client = ModbusTcpClient(self.host, port=self.port, timeout=self.timeout) + if not self._client.connect(): + log.error("Modbus TCP Verbindung fehlgeschlagen: %s:%d", self.host, self.port) + self._client = None + return False + log.info("Modbus TCP verbunden: %s:%d", self.host, self.port) + return True + + def _disconnect(self): + if self._client: + self._client.close() + self._client = None + + def read(self, inverter: Inverter) -> Optional[Dict[str, float]]: + if not self._connect(): + return None + + # Batch-read aller Register-Bereiche + reg_cache: Dict[int, int] = {} + for start, length in inverter.read_ranges: + try: + result = self._client.read_input_registers(start, length, slave=self.slave) + if result.isError(): + log.error("FC04 Fehler bei Reg %d+%d: %s", start, length, result) + self._disconnect() + return None + for i, val in enumerate(result.registers): + reg_cache[start + i] = val + except ModbusException as e: + log.error("Modbus Ausnahme: %s", e) + self._disconnect() + return None + + return _extract_sensors(inverter.sensors, reg_cache) + + def close(self): + self._disconnect() + + +def _extract_sensors(sensors: list, regs: Dict[int, int]) -> Dict[str, float]: + values: Dict[str, float] = {} + for s in sensors: + if s.reg not in regs: + log.warning("Register %d fehlt in Antwort (%s)", s.reg, s.id) + continue + if s.count == 2: + if s.reg + 1 not in regs: + log.warning("Register %d (high word) fehlt (%s)", s.reg + 1, s.id) + continue + raw = (regs[s.reg] << 16) | regs[s.reg + 1] + else: + raw = regs[s.reg] + values[s.id] = round(raw * s.scale, 3) + return values diff --git a/haos-addon/src/mqtt_publisher.py b/haos-addon/src/mqtt_publisher.py new file mode 100644 index 0000000..23839fc --- /dev/null +++ b/haos-addon/src/mqtt_publisher.py @@ -0,0 +1,94 @@ +import json +import logging +import time +from typing import Dict, Optional + +import paho.mqtt.client as mqtt + +from inverters import Inverter + +log = logging.getLogger(__name__) + +DEVICE_ID = "growatt_shinelanx" + + +class MqttPublisher: + def __init__(self, broker: str, port: int, user: str, password: str, topic_prefix: str): + self.topic_prefix = topic_prefix.rstrip("/") + self._client = mqtt.Client(client_id=DEVICE_ID, clean_session=True) + if user: + self._client.username_pw_set(user, password) + self._client.on_connect = self._on_connect + self._client.on_disconnect = self._on_disconnect + self._broker = broker + self._port = port + self._connected = False + self._inverter: Optional[Inverter] = None + + def _on_connect(self, client, userdata, flags, rc): + if rc == 0: + self._connected = True + log.info("MQTT verbunden: %s:%d", self._broker, self._port) + if self._inverter: + self._publish_discovery(self._inverter) + else: + log.error("MQTT Verbindungsfehler rc=%d", rc) + + def _on_disconnect(self, client, userdata, rc): + self._connected = False + log.warning("MQTT getrennt rc=%d", rc) + + def connect(self): + try: + self._client.connect_async(self._broker, self._port, keepalive=60) + self._client.loop_start() + except Exception as e: + log.error("MQTT connect fehlgeschlagen: %s", e) + + def disconnect(self): + self._client.loop_stop() + self._client.disconnect() + + @property + def connected(self) -> bool: + return self._connected + + def setup_inverter(self, inverter: Inverter): + self._inverter = inverter + if self._connected: + self._publish_discovery(inverter) + + def _publish_discovery(self, inverter: Inverter): + device_payload = { + "identifiers": [DEVICE_ID], + "name": "Growatt ShineLAN-X", + "manufacturer": inverter.manufacturer, + "model": inverter.name, + } + for sensor in inverter.sensors: + config = { + "name": sensor.name, + "unique_id": f"{DEVICE_ID}_{sensor.id}", + "state_topic": f"{self.topic_prefix}/state", + "value_template": f"{{{{ value_json.{sensor.id} }}}}", + "unit_of_measurement": sensor.unit, + "state_class": sensor.state_class, + "icon": sensor.icon, + "device": device_payload, + } + if sensor.device_class: + config["device_class"] = sensor.device_class + + topic = f"homeassistant/sensor/{DEVICE_ID}/{sensor.id}/config" + self._client.publish(topic, json.dumps(config), retain=True, qos=1) + log.info("MQTT Discovery für %d Sensoren veröffentlicht", len(inverter.sensors)) + + def publish_data(self, values: Dict[str, float]): + if not self._connected: + log.warning("MQTT nicht verbunden, Daten verworfen") + return + payload = json.dumps(values) + self._client.publish(f"{self.topic_prefix}/state", payload, retain=True, qos=0) + + def publish_status(self, status: str): + self._client.publish(f"{self.topic_prefix}/status", status, retain=True, qos=1) diff --git a/haos-addon/src/web/index.html b/haos-addon/src/web/index.html new file mode 100644 index 0000000..4f10bc5 --- /dev/null +++ b/haos-addon/src/web/index.html @@ -0,0 +1,570 @@ + + + + + +Growatt ShineLAN-X + + + + +
+ + + + +
+

Growatt ShineLAN-X

+
Lade...
+
+
+
Modbus
+
MQTT
+
+
+ +
+
+
Live-Daten
+
Konfiguration
+
+ + +
+
+
+
+ + + + +

Warte auf erste Messung...

+
+
+
+ + +
+
+
+

Wechselrichter-Modell

+
+ +
+ +
+
+

Modbus TCP

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

MQTT

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+ + + +