+
    ~i<                     L   R t ^ RIt^ RIt^ RIt^ RIHtHtHt ^ RIH	t	H
t
HtHtHt ]! R]4      t]P                   ! 4       tRsRs/ tRs/ tR]
R]R]R	]R
]	R]P.                  P0                  /tR tR tR R ltR R ltR tR tR t ]PC                  RR.R7      R 4       t"]PC                  RR.R7      R 4       t#]PC                  RR.R7      R 4       t$]PC                  RR.R7      R 4       t%]PC                  RR.R7      R  4       t&]PC                  R!R.R7      R" 4       t'R# )#a#  
WifiSwitch: relay + temperature/humidity node.

Device reports to:
  POST /wifiswitch/reading

Device polls server for desired relay state and reboot:
  POST /wifiswitch/api/poll

SolarMon dashboard can control the device via:
  POST /wifiswitch/api/set_relay
  POST /wifiswitch/api/reboot
N)	BlueprintResponserequest)MYSQL_DATABASE
MYSQL_HOSTMYSQL_PASSWORD
MYSQL_PORT
MYSQL_USER
wifiswitchFhostportuserpassworddatabasecursorclassc                  6    \         P                  ! R/ \        B # )N )pymysqlconnect	DB_CONFIGr       blueprints/wifiswitch.pyget_dbr   0   s    ??'Y''r   c           	      D   V P                  4       ;_uu_ 4       pVP                  R 4       RRR4       V P                  4        \        ;_uu_ 4        \        '       d    RRR4       R# V P                  4       ;_uu_ 4       pVP                  R\
        34       VP                  4       RJpV'       g"   VP                  R4       V P                  4        RRR4       RsRRR4       R#   + '       g   i     L; i  + '       g   i     L/; i  + '       g   i     R# ; i)a]  
            CREATE TABLE IF NOT EXISTS WifiSwitchStatus (
                id INT AUTO_INCREMENT PRIMARY KEY,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

                unit_id VARCHAR(16) NOT NULL,
                node_name VARCHAR(50) DEFAULT NULL,

                temp FLOAT NULL,
                humid FLOAT NULL,
                relay_on TINYINT(1) NOT NULL,
                temp_rule_enabled TINYINT(1) NOT NULL DEFAULT 0,

                mac VARCHAR(24) NULL,
                ip VARCHAR(45) NULL,

                firmware_version VARCHAR(24) NULL
            )
            Na  
                SELECT 1
                FROM information_schema.columns
                WHERE table_schema=%s
                  AND table_name='WifiSwitchStatus'
                  AND column_name='temp_rule_enabled'
                LIMIT 1
                zWALTER TABLE WifiSwitchStatus ADD COLUMN temp_rule_enabled TINYINT(1) NOT NULL DEFAULT 0T)cursorexecutecommit_state_lock_wifi_switch_schema_checkedr   fetchone)conncurhas_cols   &  r   ensure_wifi_switch_tabler#   4   s    	#	
 
, 	KKM 
&& 
 [[]]cKK  !
 llnD0GQ % & '+#- 
3 
8 ] 
s6   C(D)DAC;	
D(C8	;DDD	c                $    V ^8  d   QhR\         /#    unit_idstr)formats   "r   __annotate__r+   h   s      C r   c                     \         ;_uu_ 4        V \        9   d)   \        \        P                  V 4      4      uuR R R 4       #  R R R 4       R #   + '       g   i     R # ; iN)r   _temp_rule_enabled_pendingboolpopr'   s   &r   _pop_temp_rule_enabledr2   h   s<    	00266w?@ 
  
s   )AAA	c                $    V ^8  d   QhR\         /# r%   r(   )r*   s   "r   r+   r+   o   s      # r   c                    \         ;_uu_ 4        V \        9   d    \        P                  V 4      uuRRR4       #  RRR4       R#   + '       g   i     R# ; i)z+Pop one-time identity update for a unit_id.N)r   _identity_pending_by_unit_idr0   r1   s   &r   _pop_identity_updater6   o   s6    {..)--g6 {  {{{s    AAA	c                     \        V \        4      '       d   V # \        V \        \        34      '       d   V ^ 8g  # \        V \        4      '       d$   V P                  4       P                  4       pVR9   # R# )    N)1trueonyes)
isinstancer/   intfloatr)   striplower)vss   & r   _parse_boolrD   w   s\    !T!c5\""Av!SGGIOO...r   c                     \         ;_uu_ 4        \        '       d   Rs RRR4       R#  RRR4       R#   + '       g   i     R# ; i)z6Pop pending reboot once; return 'reboot' or 'nosleep'.FNrebootnosleep)r   _switch_reboot_pendingr   r   r   _pop_reboot_commandrI      s0     
!!%*" 
 	 
s   44A	c                  z    \         ;_uu_ 4        \        \        4      uuR R R 4       #   + '       g   i     R # ; ir-   )r   r/   _relay_desiredr   r   r   _get_desired_relayrL      s    	N# 
s   ):	z/readingPOST)methodsc                     \         P                  ! RRR7      p V '       g   \        RRRR7      # V P                  R4      pV P                  R4      pV P                  R	4      pV P                  R
4      pV P                  R4      pV P                  R4      pV P                  R4      pV P                  R4      pV P                  R4      p	Ve   Ve	   Ve   Vf   \        RRRR7      # \	        V\
        4      '       d   VP                  4       '       g   \        RRRR7      # VP                  4       R,          p\	        V\
        4      '       d   TMRP                  4       pV'       d
   VR,          MRp \        V4      p
 \        T4      p\        T4      pTf   \        RRRR7      # \        T4      pTf   Rp\	        T\
        4      '       d   TMRP                  4       R,          ;'       g    Rp\	        T\
        4      '       d   TMRP                  4       R,          ;'       g    Rp\	        T	\
        4      '       d   T	MRP                  4       R,          ;'       g    Rp	\        4       p \        T4       TP                  4       ;_uu_ 4       pTP                  RTTT
TT'       d   ^M^ T'       d   ^M^ TTT	3	4       RRR4       TP                  4        TP!                  4        \#        4       p\%        4       p\&        P(                  ! RRRTRT/R#R7      p\        T^RR7      #   \        \        3 d    \        RRRR7      u # i ; i  \        \        3 d    \        RRRR7      u # i ; i  + '       g   i     L; i  TP!                  4        i ; i  \*        P,                   d    \        R R!RR7      u # \.         d<   p\        \&        P(                  ! RRR"\        T4      /R#R7      R!RR7      u Rp?# Rp?ii ; i)$z;Accept relay + AHT10 reading and store in WifiSwitchStatus.Tforcesilentz.{"ok":false,"error":"Invalid or missing JSON"}  application/jsonstatusmimetyper'   	node_nametemphumidrelay_ontemp_rule_enabledmacipfirmware_versionNz@{"ok":false,"error":"Missing unit_id, temp, humid, or relay_on"}z9{"ok":false,"error":"unit_id must be a non-empty string"}N   N N2   Nz,{"ok":false,"error":"temp must be a number"}z-{"ok":false,"error":"humid must be a number"}z4{"ok":false,"error":"relay_on must be boolean-like"}F:N   N:N-   Na  
                    INSERT INTO WifiSwitchStatus
                        (unit_id, node_name, temp, humid, relay_on, temp_rule_enabled, mac, ip, firmware_version)
                    VALUES
                        (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                    okcommand
separatorsz%{"ok":false,"error":"Database error"}  error,:)r   get_jsonr   getr=   r)   r@   r?   	TypeError
ValueErrorrD   r   r#   r   r   r   closerI   rL   jsondumpsr   Error	Exception)datar'   rX   rY   rZ   r[   r\   r]   r^   r_   temp_fhumid_f
relay_on_btemp_rule_enabled_br    r!   rh   desired_relaybodyes                       r   readingr      s   lDd48LUXcuvv((9%HH[)	xx!88J' HH%89hhuoXXd^88$67?dlemx?OR+  '3''w}}K+ 
 --/#&",Y"<"<Y"KKM	&/IcNT		4[F	ElG !*
F+  **;<&"' c**s99;C@HHDr3''bR668=EE!+,<c!B!BQQSTWX``\` 	 x	$T*#  !'Q0a(
 ( KKMJJL%'*,zz4GZG!
 S3EFFI :& 	>+ 	 :& 	?+ 	: , JJL == j?Vhii D

D%#a&#AjYbe  qC  D  	DDs   N N B6N '+N N !AN (N 4L*  M N (AN /8N (8N !N /#N #M46M4N AN *M	N MN M1.N 0M11N 4N	?N NN #PP
P0P;PPz	/api/pollc                 6    \         P                  ! RRR7      ;'       g    / p V P                  R4      pV P                  R4      pV P                  R4      pVe-   \        V\        4      '       d   VP                  4       '       g   \        RRR	R
7      # VP                  4       R,          pTpTp\        4       p\        4       p\        V4      p\        V4      pRRRVRV/p	Ve   WyR&   Ve)   VP                  R4      V	R&   VP                  R4      V	R&   \        P                  ! V	RR7      p
\        RV RV RV 2RR7       \        V
^R	R
7      #   \         d<   p\        \        P                  ! RRR\	        T4      /RR7      RR	R
7      u Rp?# Rp?ii ; i)z7Return desired relay state and one-time reboot command.TrP   r'   r[   r_   N'{"ok":false,"error":"unit_id required"}rS   rT   rU   r`   rg   rh   r\   new_unit_idnew_node_nameri   z[WifiSwitch] api_poll unit_id=z desired_relay=z cmd=flushFrl   rk   rm   )r   rp   rq   r=   r)   r@   r   rI   rL   r2   r6   ru   rv   printrx   )ry   r'   r[   r_   _rh   r~   r\   identity_updateresp_objr   r   s               r   api_pollr     s   Dd48>>B((9%88J'88$67?:gs#;#;GMMOOEc\noo--/#& %'*,27;.w7$	7JN(,=()&&5&9&9-&HH]#(7(;(;O(LH_%zz(z:.wi}oUZ[bZcdlpqS3EFF D

D%#a&#AjYbe  qC  D  	DDs0   E A%E E B<E F0FFFz/api/set_relayc                     \         P                  ! RRR7      ;'       g    / p V P                  R4      p\        V4      pVf   \	        RRRR7      # \
        ;_uu_ 4        \        \        4      p\        V4      s\        \        4      pRRR4       \         P                  ! R	R
7      pV'       d   VR,          P                  RRR7      MRp\        RV  RX RX R\         P                   RV: 2
RR7       \	        \        P                  ! RRRV/RR7      ^RR7      #   + '       g   i     L; i  \         d<   p\	        \        P                  ! RR	R\        T4      /RR7      RRR7      u Rp?# Rp?ii ; i)z-Set desired relay on/off (active-high relay).TrP   r[   Nz5{"ok":false,"error":"relay_on required (true/false)"}rS   rT   rU   F)cache:N   Nzutf-8replace)errorsrb   z$[WifiSwitch] api_set_relay received=z parsed_relay_on=z	 (before=z) ct=z raw_preview=r   rg   ri   rl   rk   rm   )r   rp   rq   rD   r   r   r/   rK   get_datadecoder   content_typeru   rv   rx   r)   )ry   r[   r|   beforeafterrawraw_previewr   s           r   api_set_relayr   (  se   Dd48>>B88J' *
S\_j|}} [.)F!*-N(E  U+EHc$i&&wy&Ab24&8I%PYZ`Ya b&&'}[OE	
 JJdJ6:N'
 	
 [(  D

D%#a&#AjYbe  qC  D  	DDsG   D3 0D3 D3  *D 
&D3 1A.D3  D0	+D3 3E9>0E4.E94E9z/api/set_temp_rule_enabledc                     \         P                  ! RRR7      ;'       g    / p V P                  R4      pV P                  R4      pVe-   \        V\        4      '       d   VP                  4       '       g   \        RRRR	7      # VP                  4       R
,          p\        V4      pVf   \        RRRR	7      # \        ;_uu_ 4        \        V4      \        V&   RRR4       \        R^RR	7      #   + '       g   i     L; i  \         d<   p\        \        P                  ! RRR\	        T4      /RR7      RRR	7      u Rp?# Rp?ii ; i)z}Set a pending temp_rule_enabled flag for the device.

Device will consume it once on the next poll and persist it to EEPROM.
TrP   r'   r\   Nr   rS   rT   rU   r`   z={"ok":false,"error":"temp_rule_enabled must be boolean-like"}{"ok":true}rg   Frl   ri   rk   rm   )r   rp   rq   r=   r)   r@   r   rD   r   r/   r.   rx   ru   rv   )ry   r'   r\   	enabled_br   s        r   api_set_temp_rule_enabledr   L  s   
d48>>B((9% HH%89?:gs#;#;GMMOO9+ 
 --/#& 12	O+  [26y/&w/  c<NOO [
  
JJeWc!f5*M'
 	

sM   D AD 6D 3D 8D C/D /C?	:D E0E=EEz/api/set_node_id_namec                     \         P                  ! RRR7      ;'       g    / p V P                  R4      pV P                  R4      pV P                  R4      pVe-   \        V\        4      '       d   VP                  4       '       g   \        RRR	R
7      # Ve-   \        V\        4      '       d   VP                  4       '       g   \        RRR	R
7      # Ve-   \        V\        4      '       d   VP                  4       '       g   \        RRR	R
7      # VP                  4       R,          pVP                  4       R,          pVP                  4       R,          p\        ;_uu_ 4        RVRV/\        V&   RRR4       \        R^R	R
7      #   + '       g   i     L; i  \         d<   p\        \        P                  ! RRR\	        T4      /RR7      RR	R
7      u Rp?# Rp?ii ; i)zSet pending node identity for this WifiSwitch.

Expects JSON:
  { "unit_id": "<current_id>", "new_unit_id": "<new_id>", "new_node_name": "<name>" }

Device will consume it once on the next poll and persist it to EEPROM.
TrP   r'   r   r   Nr   rS   rT   rU   z+{"ok":false,"error":"new_unit_id required"}z-{"ok":false,"error":"new_node_name required"}r`   rc   r   rg   Frl   ri   rk   rm   )r   rp   rq   r=   r)   r@   r   r   r5   rx   ru   rv   )ry   r'   r   r   r   s        r   api_set_node_id_namer   t  s   *
d48>>B((9%hh}-1?:gs#;#;GMMOO9+ 
 z+s'C'CKL]L]L_L_=+ 
  M3)G)GP]PcPcPePe?+  --/#&!'')#.%++-c2[{5(1  c<NOO [  
JJeWc!f5*M'
 	

s`   F A%F F /F F /F F AF $F2F F	F G!&0GG!G!z/api/rebootc                     \         ;_uu_ 4        RsRRR4       \        RRR7       \        R^RR7      #   + '       g   i     L+; i)z<Set pending reboot; device will reboot on next poll/reading.TNz#[WifiSwitch] api_reboot pending setr   r   rT   rU   )r   rH   r   r   r   r   r   
api_rebootr     s7     
!% 
	
/t<M#8JKK 
s	   5A	)(__doc__ru   	threadingr   flaskr   r   r   configr   r   r   r   r	   __name__bpLockr   rK   rH   r.   r   r5   cursors
DictCursorr   r   r#   r2   r6   rD   rI   rL   router   r   r   r   r   r   r   r   r   <module>r      s      . .  |X&nn  # !  J
J
J7??--	(1+h$
 *vh'nD (nDb +x(!D )!DH 
VH- D . DF 
&9$
 :$
N 
!F842
 52
j -&*L +Lr   