
    j*                         d Z ddlZddlmZ ddlmZ dZdZdZd	Z	d
Z
dZdZdZdZdZdZdZdZdZdZdZdZdZdZdZd Zd Zd Z G d de      Zy)a  
TinyTuya - Contrib - SoriaInverterDevice
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for SORIA solar micro-inverter (product ID: 5l1ht8jygsyr1wn1)

Protocol:
    DPS values are Base64-encoded binary frames using a repeating TLV structure:
        [PREFIX: 3 bytes] [TAG: 1 byte] [VALUE: 2 bytes big-endian]
    The prefix is detected dynamically (most frequent 3-byte sequence in the frame).

DPS keys:
    '21' : Full report  - voltages, currents, power, temperature, energy (~60s)
    '22' : Config       - firmware versions and device identifiers (read-only)
    '23' : Event status - device state flags
    '24' : Circuit status - on/off per circuit (prefix 01 01 80)
    '25' : Real-time    - active and apparent power only (~2s)
    '29' : Raw binary   - not yet decoded

Author  : Markourai (https://github.com/Markourai)
Issue   : https://github.com/jasonacox/tinytuya/issues/658
    N)Counter   )Device212223242529         #   '   1   2   3   I   J   W   X   c                     t               }t        t        |       dz
        D ]&  }| ||dz    }|t        d      k7  s||xx   dz  cc<   ( |st        j	                  d      S |j                  d      d   d   S )us   Return the most frequent 3-byte sequence — that is the TLV prefix.
    Falls back to 01 01 10 if detection fails.         010110r   )r   rangelenbytesfromhexmost_common)datacountsi	candidates       Q/DATA/.local/lib/python3.12/site-packages/tinytuya/Contrib/SoriaInverterDevice.py_detect_prefixr'   <   s|     YF3t9q=!1Q3K	a 9"	 "
 }}X&&a #A&&    c                     t        |       }i }d}|t        |       dz
  k  rI| ||dz    |k(  r'| |dz      }| |dz      dz  | |dz      z  }|||<   |dz  }n|dz  }|t        |       dz
  k  rI|S )z7Extract all {tag: value} pairs from a binary TLV frame.r   r   r            r   )r'   r   )r"   prefixtagsr$   tagvals         r&   
_parse_tlvr1   J   s    D!FD	A
c$i!m
!A#;& qs)C!9>T!A#Y.CDIFAFA c$i!m
 Kr(   c                 b    	 t        t        j                  |             S # t        $ r i cY S w xY w)z@Decode a Base64 DPS string into a tag dict. Returns {} on error.)r1   base64	b64decode	Exception)values    r&   _decode_b64_tlvr7   Z   s1    &**5122 	s     ..c                   Z     e Zd ZdZ fdZd ZddZd Zd Zd Z	d Z
d	 Zd
 Zd Z xZS )SoriaInverterDevicea  Support for SORIA solar inverter (product ID: 5l1ht8jygsyr1wn1)

    Usage::

        from tinytuya.Contrib import SoriaInverterDevice

        d = SoriaInverterDevice(
            dev_id='abcdefghijklmnop123456',
            address='10.2.3.4',
            local_key='1234567890123abc'
        )
        d.receive()  # initial handshake

        while True:
            data = d.receive_and_update()
            print(d.get_realtime_power())
            print(d.get_full_report())
    c                 v    d|vs|d   sd|d<   d|vrd|d<   t        t        | 
  |i | i | _        i | _        y )Nversiong      @persistT)superr9   __init___cached_dps_decoded)selfargskwargs	__class__s      r&   r>   zSoriaInverterDevice.__init__z   sR    F"&*; #F9F" $F9!414B6Br(   c                 :    d| j                   j                         iS )u   Override status — this device does not support status queries.
        Returns the last cached DPS data received asynchronously.dps)r?   copyrA   s    r&   statuszSoriaInverterDevice.status   s     t'',,.//r(   c                 x   | j                   }| j                  |       	 | j                         }| j                  |       |rbd|v r^| j                  j	                  |d          t
        t        t        t        t        fD ]"  }||d   v s| j                  ||d   |          $ |S # | j                  |       w xY w)zReceive one async update, cache raw DPS values and decode them.

        Returns the raw tinytuya message dict, or None.
        rF   )connection_timeoutset_socketTimeoutreceiver?   updateDPS_FULLDPS_REALTIME	DPS_EVENT
DPS_STATUSDPS_RAW
_decode_dp)rA   timeoutold_timeoutr"   dp_ids        r&   receive_and_updatez&SoriaInverterDevice.receive_and_update   s    
 --w'	0<<>D"";/ETM##DK0"L)ZQDK'OOE4;u+=> R  "";/s   B& &B9c                    |t         k(  r#| j                  |      | j                  t         <   y|t        k(  r#| j	                  |      | j                  t        <   y|t
        k(  r#| j                  |      | j                  t
        <   y|t        t        fv r4	 dt        j                  |      j                         i| j                  |<   yy# t        $ r Y yw xY w)z=Decode a Base64-encoded DPS value and store physical results.raw_hexN)rP   _decode_realtimer@   rO   _decode_fullrR   _decode_statusrQ   rS   r3   r4   hexr5   )rA   rW   r6   s      r&   rT   zSoriaInverterDevice._decode_dp   s    L *.*?*?*FDMM,'h&*&7&7&>DMM(#j (,(;(;E(BDMM*% y'**(163C3CE3J3N3N3P'Qe$ +  s   2C 	CCc                     t        |      }|sy|j                  t              }|j                  t              }|y||dS )u8   Decode DPS '25' — real-time active and apparent power.N)W_PVW_AC)r7   getTAG_W_PVTAG_W_AC)rA   r6   r.   r`   ra   s        r&   r[   z$SoriaInverterDevice._decode_realtime   sF    u%xx!xx!<
 	
r(   c                 8   t        |      syfd} |t              y |t              rt         |t              dz  d      nd |t              rt         |t              dz  d      nd |t               |t
              rt         |t
              dz  d      nd |t              rt         |t              dz  d      nd |t               |t              rt         |t              dz  d      nd |t              rt         |t              dz  d      nd |t              rt         |t              dz  d      nd |t              rt         |t              dz  d      nd |t              rt         |t              dz  d      nd |t              dS )u/   Decode DPS '21' — complete electrical report.Nc                 &    j                  |       S )N)rb   )tag_idr.   s    r&   tz+SoriaInverterDevice._decode_full.<locals>.t   s    88F##r(   
   r   d   r   )V1_volts
A1_amperesW1_wattsV2_volts
A2_amperesW2_wattsHzcos_phitemp1_Ctemp2_C
energy_kwhwifi_signal)r7   TAG_W1_WATTSTAG_V1_VOLTSroundTAG_A1_AMPERESTAG_V2_VOLTSTAG_A2_AMPERESTAG_W2_WATTSTAG_HZTAG_COS_PHI	TAG_TEMP1	TAG_TEMP2TAG_ENERGY_KWHTAG_WIFI_SIGNAL)rA   r6   rh   r.   s      @r&   r\   z SoriaInverterDevice._decode_full   sM   u%	$ \?" AB,5<R!7!<W[AB>AR5>!2c!91=X\\?@A,5<R!7!<W[AB>AR5>!2c!91=X\\?AB656c!91=X\AB;5;c!91=X\AB959b!81=X\AB959b!81=X\AB>AR5>!2c!91=X\_-%
 	
r(   c                    	 t        j                  |      }t        g d      }i }d}|t        |      dz
  k  rI|||dz    |k(  r'||dz      }||dz      dz  ||dz      z  }|||<   |dz  }n|dz  }|t        |      dz
  k  rIt	        |j                               D 	ci c]  \  }}	d	| |	dk7   c}	}S c c}	}w # t        $ r Y y
w xY w)uG   Decode DPS '24' — circuit on/off status (uses fixed prefix 01 01 80).)r   r      r   r   r   r*   r+   r,   r   circuit_N)r3   r4   r   r   sorteditemsr5   )
rA   r6   rawr-   r.   r$   r/   r0   kvs
             r&   r]   z"SoriaInverterDevice._decode_status   s    	%%e,C-.FDAc#hl"q1:'ac(Cqs8q=C!H4C #DIFAFA c#hl" :@

9MN9MAhqcNQ!V+9MNNN 		s*   A?B; B; B51B; 5B; ;	CCc                 @    | j                   j                  t              S )zReturn last decoded real-time power (DPS '25').

        Returns::

            {
                'W_PV':   int,   # active power in watts
                'W_AC':   int,   # apparent power in VA
            }
        )r@   rb   rP   rH   s    r&   get_realtime_powerz&SoriaInverterDevice.get_realtime_power   s     }}  ..r(   c                 @    | j                   j                  t              S )a9  Return last decoded full electrical report (DPS '21').

        Returns::

            {
                'V1_volts':    float,  # DC voltage (V)
                'A1_amperes':  float,  # DC current (A)
                'W1_watts':    int,    # DC power (W)
                'V2_volts':    float,  # Grid voltage (V)
                'A2_amperes':  float,  # Grid current (A)
                'W2_watts':    int,    # Grid power (W)
                'Hz':          float,  # Grid frequency (Hz)
                'cos_phi':     float,  # Power factor
                'temp1_C':     float,  # Temperature 1 (C) display by Tuya app
                'temp2_C':     float,  # Temperature 2 (C)
                'energy_kwh':  float,  # Cumulated energy (kWh)
                'wifi_signal': int,    # WiFi signal level
            }
        )r@   rb   rO   rH   s    r&   get_full_reportz#SoriaInverterDevice.get_full_report  s    ( }}  **r(   c                 @    | j                   j                  t              S )zReturn last decoded circuit on/off status (DPS '24').

        Returns::

            {'circuit_0': bool, 'circuit_1': bool, ...}
        )r@   rb   rR   rH   s    r&   get_circuit_statusz&SoriaInverterDevice.get_circuit_status  s     }}  ,,r(   )r   )__name__
__module____qualname____doc__r>   rI   rX   rT   r[   r\   r]   r   r   r   __classcell__)rD   s   @r&   r9   r9   f   s;    &	0
,$

B.
/+,-r(   r9   )r   r3   collectionsr   corer   rO   
DPS_CONFIGrQ   rR   rP   rS   r   r   r{   r|   r}   r~   rd   rw   rx   rz   rc   r   r   r   r'   r1   r7   r9    r(   r&   <module>r      s   ,   
 
	

 		' -& -r(   