11from __future__ import annotations
22
33from abc import ABCMeta , abstractmethod
4+ import dataclasses
45from typing import TYPE_CHECKING , Final , override
56
67from exceptions import MqttGatewayException
1415 from vehicle import VehicleState
1516
1617
18+ @dataclasses .dataclass (kw_only = True , frozen = True )
19+ class CommandProcessingResult :
20+ force_refresh : bool
21+ clear_command : bool
22+
23+
24+ RESULT_DO_NOTHING : Final [CommandProcessingResult ] = CommandProcessingResult (
25+ force_refresh = False , clear_command = False
26+ )
27+ RESULT_REFRESH_AND_CLEAR : Final [CommandProcessingResult ] = CommandProcessingResult (
28+ force_refresh = True , clear_command = True
29+ )
30+ RESULT_CLEAR_ONLY : Final [CommandProcessingResult ] = CommandProcessingResult (
31+ force_refresh = False , clear_command = True
32+ )
33+ RESULT_REFRESH_ONLY : Final [CommandProcessingResult ] = CommandProcessingResult (
34+ force_refresh = True , clear_command = False
35+ )
36+
37+
1738class CommandHandlerBase (metaclass = ABCMeta ):
1839 def __init__ (self , saic_api : SaicApi , vehicle_state : VehicleState ) -> None :
1940 self .__saic_api : Final [SaicApi ] = saic_api
@@ -29,7 +50,7 @@ def topic(cls) -> str:
2950 raise NotImplementedError
3051
3152 @abstractmethod
32- async def handle (self , payload : str ) -> bool :
53+ async def handle (self , payload : str ) -> CommandProcessingResult :
3354 raise NotImplementedError
3455
3556 @property
@@ -50,23 +71,32 @@ def publisher(self) -> Publisher:
5071
5172
5273class MultiValuedCommandHandler [T ](CommandHandlerBase , metaclass = ABCMeta ):
53- async def should_refresh (self , _action_result : T ) -> bool :
54- return True
74+ @abstractmethod
75+ async def _get_action_result (self , _action_result : T ) -> CommandProcessingResult :
76+ pass
5577
5678 @abstractmethod
5779 def options (self ) -> dict [str , Callable [[], Awaitable [T ]]]:
5880 raise NotImplementedError
5981
82+ @property
83+ def supports_empty_payload (self ) -> bool :
84+ return False
85+
6086 @override
61- async def handle (self , payload : str ) -> bool :
87+ async def handle (self , payload : str ) -> CommandProcessingResult :
6288 normalized_payload = payload .strip ().lower ()
89+
90+ if len (normalized_payload ) == 0 and not self .supports_empty_payload :
91+ return RESULT_DO_NOTHING
92+
6393 options = self .options ()
6494 option_handler = options .get (normalized_payload )
6595 if option_handler is None :
6696 msg = f"Unsupported payload { payload } for command { self .name ()} "
6797 raise MqttGatewayException (msg )
6898 response = await option_handler ()
69- return await self .should_refresh (response )
99+ return await self ._get_action_result (response )
70100
71101
72102class BooleanCommandHandler [T ](CommandHandlerBase , metaclass = ABCMeta ):
@@ -78,20 +108,26 @@ async def handle_true(self) -> T:
78108 async def handle_false (self ) -> T :
79109 raise NotImplementedError
80110
81- async def should_refresh (self , _action_result : T ) -> bool :
82- return True
111+ @abstractmethod
112+ async def _get_action_result (self , _action_result : T ) -> CommandProcessingResult :
113+ pass
83114
84115 @override
85- async def handle (self , payload : str ) -> bool :
86- match payload .strip ().lower ():
116+ async def handle (self , payload : str ) -> CommandProcessingResult :
117+ normalized_payload = payload .strip ().lower ()
118+
119+ if len (normalized_payload ) == 0 :
120+ return RESULT_DO_NOTHING
121+
122+ match normalized_payload :
87123 case "true" | "1" | "on" :
88124 response = await self .handle_true ()
89125 case "false" | "0" | "off" :
90126 response = await self .handle_false ()
91127 case _:
92128 msg = f"Unsupported payload { payload } for command { self .name ()} "
93129 raise MqttGatewayException (msg )
94- return await self .should_refresh (response )
130+ return await self ._get_action_result (response )
95131
96132
97133class PayloadConvertingCommandHandler [T ](CommandHandlerBase , metaclass = ABCMeta ):
@@ -101,11 +137,18 @@ def convert_payload(payload: str) -> T:
101137 raise NotImplementedError
102138
103139 @abstractmethod
104- async def handle_typed_payload (self , payload : T ) -> bool :
140+ async def handle_typed_payload (self , payload : T ) -> CommandProcessingResult :
105141 raise NotImplementedError
106142
143+ @property
144+ def supports_empty_payload (self ) -> bool :
145+ return False
146+
107147 @override
108- async def handle (self , payload : str ) -> bool :
148+ async def handle (self , payload : str ) -> CommandProcessingResult :
149+ if len (payload .strip ()) == 0 and not self .supports_empty_payload :
150+ return RESULT_DO_NOTHING
151+
109152 try :
110153 converted_payload = self .convert_payload (payload )
111154 except Exception as e :
@@ -119,3 +162,9 @@ class IntCommandHandler(PayloadConvertingCommandHandler[int], metaclass=ABCMeta)
119162 @staticmethod
120163 def convert_payload (payload : str ) -> int :
121164 return int (payload .strip ().lower ())
165+
166+
167+ class FloatCommandHandler (PayloadConvertingCommandHandler [float ], metaclass = ABCMeta ):
168+ @staticmethod
169+ def convert_payload (payload : str ) -> float :
170+ return float (payload .strip ().lower ())
0 commit comments