Coverage for apps/garage_door.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-03-12 12:23 +0000

1# The MIT License (MIT) 

2# 

3# Copyright © 2023 Xavier Berger 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy of this software 

6# and associated documentation files (the “Software”), to deal in the Software without restriction, 

7# including without limitation the rights to use, copy, modify, merge, publish, distribute, 

8# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 

9# furnished to do so, subject to the following conditions: 

10# 

11# The above copyright notice and this permission notice shall be included in all copies or 

12# substantial portions of the Software. 

13# 

14# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 

15# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 

16# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 

17# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

19import appdaemon.plugins.hass.hassapi as hass 

20 

21# 

22# Garage door App 

23# 

24# Args: 

25# sun: sun.sun 

26# notification_delay: 600 

27# door_state: binary_sensor.porte_garage_opening 

28 

29# 

30# Send a notification when sun is below horizon and garage door is style open 

31# based on following conditions: 

32# - When door is open when sun is passing below horizon 

33# - When door is open during the night and not closed after 10 minutes 

34# 

35 

36 

37class GarageDoor(hass.Hass): 

38 def initialize(self): 

39 """ 

40 Initialize the GarageDoor application. 

41 

42 This method sets up the necessary listeners and handles for the GarageDoor application. 

43 It registers callbacks for starting and stopping automation based on sun position. 

44 

45 Returns: 

46 None 

47 """ 

48 self.log("Starting GarageDoor") 

49 

50 # Handles to register / unregister callbacks 

51 self.state_handles = [] 

52 self.delayed_notification_handle = None 

53 

54 # Activate/deactivate automation based on sun 

55 self.listen_state( 

56 self.callback_start_automation, 

57 self.args["sun"], 

58 new="below_horizon", 

59 immediate=True, 

60 ) 

61 self.listen_state( 

62 self.callback_stop_automation, 

63 self.args["sun"], 

64 new="above_horizon", 

65 immediate=True, 

66 ) 

67 

68 def callback_start_automation(self, entity, attribute, old, new, kwargs): 

69 """ 

70 Callback for starting the garage door automation. 

71 

72 This method is called when conditions are met to start the garage door automation. 

73 It sets up listeners for garage door state changes. 

74 

75 Args: 

76 Arguments as define into Appdaemon callback documentation. 

77 

78 Returns: 

79 None 

80 """ 

81 # Start automation 

82 self.log("Garage door Automation is started") 

83 # Start listening for garage states 

84 self.state_handles.append( 

85 self.listen_state( 

86 self.callback_garage_door_open, 

87 self.args["door_state"], 

88 new="on", 

89 immediate=True, 

90 ) 

91 ) 

92 self.state_handles.append( 

93 self.listen_state( 

94 self.callback_garage_door_close, 

95 self.args["door_state"], 

96 new="off", 

97 immediate=True, 

98 ) 

99 ) 

100 

101 def callback_stop_automation(self, entity, attribute, old, new, kwargs): 

102 """ 

103 Callback for stopping the garage door automation. 

104 

105 This method is called when conditions are met to stop the garage door automation. 

106 It deregisters all the garage door state change callbacks. 

107 

108 Args: 

109 Arguments as define into Appdaemon callback documentation. 

110 

111 Returns: 

112 None 

113 """ 

114 # Deregister garage door state change callbacks 

115 while len(self.state_handles) >= 1: 

116 handle = self.state_handles.pop() 

117 self.cancel_listen_state(handle) 

118 self.log("Garage door Automation is stopped") 

119 

120 def callback_garage_door_open(self, entity, attribute, old, new, kwargs): 

121 """ 

122 Callback for handling the garage door opening. 

123 

124 This method is called when the garage door opens. It determines whether to send an immediate 

125 notification or schedule a delayed notification based on the sun's position. 

126 

127 Args: 

128 Arguments as define into Appdaemon callback documentation. 

129 

130 Returns: 

131 None 

132 """ 

133 # Automation is triggered only when sun is below horizon and door is open (new = on) 

134 if old in ["on", None]: 

135 # Door is open during sunset 

136 self.log("Door is open during sunset => send notification") 

137 self.send_notification(None) 

138 else: 

139 # Door was closed and is opening. 

140 self.log("Door is open while sun is below horizon => trigger delayed notification") 

141 self.delayed_notification_handle = self.run_in(self.send_notification, self.args["notification_delay"]) 

142 

143 def callback_garage_door_close(self, entity, attribute, old, new, kwargs): 

144 """ 

145 Callback for handling the garage door closing. 

146 

147 This method is called when the garage door closes. If there is a pending delayed notification, 

148 it cancels the notification timer. 

149 

150 Args: 

151 Arguments as define into Appdaemon callback documentation. 

152 

153 Returns: 

154 None 

155 """ 

156 # Automation is triggered only when sun is below horizon and door is closed (new = off) 

157 if self.delayed_notification_handle is not None: 

158 self.log("Cancel delayed notification") 

159 self.cancel_timer(self.delayed_notification_handle) 

160 self.delayed_notification_handle = None 

161 

162 def send_notification(self, kwargs): 

163 """ 

164 Send a notification about the open garage door. 

165 

166 This method sends a notification saying tha the garage door is open during nighttime. 

167 

168 Args: 

169 kwargs: Additional keyword arguments (not used in this method). 

170 

171 Returns: 

172 None 

173 """ 

174 # Send notification 

175 self.log("Send notification") 

176 self.fire_event( 

177 "NOTIFIER", 

178 action="send_to_present", 

179 title=self.args["notification_title"], 

180 message=self.args["notification_message"], 

181 icon="mdi-garage-open", 

182 color="deep-orange", 

183 tag="garage_open", 

184 until=[ 

185 {"entity_id": self.args["door_state"], "new_state": "off"}, 

186 {"entity_id": self.args["sun"], "new_state": "above_horizon"}, 

187 ], 

188 ) 

189 self.delayed_notification_handle = None