Skip to content

Commit bbaf27e

Browse files
author
Drew Chaplin
committed
Merge branch 'main' into HeightAdjustment
2 parents 50c90de + b40b4a1 commit bbaf27e

File tree

14 files changed

+2188
-105
lines changed

14 files changed

+2188
-105
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ jobs:
3333
zip adaptive_cover.zip -r ./
3434
3535
- name: "Upload the ZIP file to the release"
36-
uses: softprops/action-gh-release@v2.0.8
36+
uses: softprops/action-gh-release@v2.2.1
3737
with:
3838
files: ${{ github.workspace }}/custom_components/adaptive_cover/adaptive_cover.zip

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,20 +188,20 @@ This mode is split up in two types of strategies; [Presence](https://github.com/
188188

189189
### Vertical
190190

191-
| Variables | Default | Range | Description |
192-
| ---------------------- | ------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
193-
| Window Height | 2.1 | 0.1-6 | Length of fully extended cover/window |
194-
| Workarea Distance | 0.5 | 0.1-10 | The distance to the workarea on equal height to the bottom of the cover when fully extended |
195-
| Area Height Adjustment | 0.5 | -6 - 6 | The vertical distance from the bottom of the closed blind to the shaded area. Will be negative value for 2nd story windows in tall rooms and postive value when shaded area is higher than base of window. |
191+
| Variables | Default | Range | Description |
192+
| ----------------------- | ------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
193+
| Window Height | 2.1 | 0.1-6 | Length of fully extended cover/window |
194+
| Glare Zone | 0.5 | 0.1-10 | Objects within this distance of the cover recieve direct sunlight. Measured horizontally from the bottom of the cover when fully extended | extended |
195+
| Glare Height Adjustment | 0.5 | -6 - 6 | The vertical distance from the bottom of the closed blind to the shaded area. Will be negative value for 2nd story windows in tall rooms and postive value when shaded area is higher than base of window. |
196196

197197
### Horizontal
198198

199-
| Variables | Default | Range | Description |
200-
| -------------------------- | ------- | ----- | ---------------------------------------------- |
201-
| Awning Height | 2 | 0.1-6 | Height from work area to awning mounting point |
202-
| Awning Length (horizontal) | 2.1 | 0.3-6 | Length of the awning when fully extended |
203-
| Awning Angle | 0 | 0-45 | Angle of the awning from the wall |
204-
| Workarea Distance | 0.5 | 0.1-2 | Distance to the work area |
199+
| Variables | Default | Range | Description |
200+
| -------------------------- | ------- | ----- | ----------------------------------------------------------------- |
201+
| Awning Height | 2 | 0.1-6 | Height from work area to awning mounting point |
202+
| Awning Length (horizontal) | 2.1 | 0.3-6 | Length of the awning when fully extended |
203+
| Awning Angle | 0 | 0-45 | Angle of the awning from the wall |
204+
| Glare Zone | 0.5 | 0.1-2 | Objects within this distance of the cover recieve direct sunlight |
205205

206206
### Tilt
207207

custom_components/adaptive_cover/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
CONF_TEMP_ENTITY,
1717
CONF_WEATHER_ENTITY,
1818
DOMAIN,
19+
_LOGGER,
1920
)
2021
from .coordinator import AdaptiveDataUpdateCoordinator
2122

@@ -48,6 +49,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4849
if entity is not None:
4950
_entities.append(entity)
5051

52+
_LOGGER.debug("Setting up entry %s", entry.data.get("name"))
53+
5154
entry.async_on_unload(
5255
async_track_state_change_event(
5356
hass,

custom_components/adaptive_cover/calculation.py

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
from .helpers import get_domain, get_safe_state
1515
from .sun import SunData
16+
from .config_context_adapter import ConfigContextAdapter
1617

1718

1819
@dataclass
1920
class AdaptiveGeneralCover(ABC):
2021
"""Collect common data."""
2122

2223
hass: HomeAssistant
24+
logger: ConfigContextAdapter
2325
sol_azi: float
2426
sol_elev: float
2527
sunset_pos: int
@@ -88,6 +90,7 @@ def is_sun_in_blind_spot(self) -> bool:
8890
blindspot = (self.gamma <= left_edge) & (self.gamma >= right_edge)
8991
if self.blind_spot_elevation is not None:
9092
blindspot = blindspot & (self.sol_elev <= self.blind_spot_elevation)
93+
self.logger.debug("Is sun in blind spot? %s", blindspot)
9194
return blindspot
9295
return False
9396

@@ -119,7 +122,9 @@ def valid_elevation(self) -> bool:
119122
return self.sol_elev <= self.max_elevation
120123
if self.max_elevation is None:
121124
return self.sol_elev >= self.min_elevation
122-
return self.min_elevation <= self.sol_elev <= self.max_elevation
125+
within_range = self.min_elevation <= self.sol_elev <= self.max_elevation
126+
self.logger.debug("elevation within range? %s", within_range)
127+
return within_range
123128

124129
@property
125130
def valid(self) -> bool:
@@ -132,6 +137,7 @@ def valid(self) -> bool:
132137
valid = (
133138
(self.gamma < azi_min) & (self.gamma > -azi_max) & (self.valid_elevation)
134139
)
140+
self.logger.debug("Sun in front of window (ignoring blindspot)? %s", valid)
135141
return valid
136142

137143
@property
@@ -143,6 +149,9 @@ def sunset_valid(self) -> bool:
143149
before_sunrise = datetime.utcnow() < (
144150
sunrise + timedelta(minutes=self.sunrise_off)
145151
)
152+
self.logger.debug(
153+
"After sunset plus offset? %s", (after_sunset or before_sunrise)
154+
)
146155
return after_sunset or before_sunrise
147156

148157
@property
@@ -197,11 +206,20 @@ class NormalCoverState:
197206

198207
def get_state(self) -> int:
199208
"""Return state."""
200-
state = np.where(
201-
self.cover.direct_sun_valid,
202-
self.cover.calculate_percentage(),
203-
self.cover.default,
209+
self.cover.logger.debug("Determining normal position")
210+
dsv = self.cover.direct_sun_valid
211+
self.cover.logger.debug(
212+
"Sun directly in front of window & before sunset + offset? %s", dsv
204213
)
214+
if dsv:
215+
state = self.cover.calculate_percentage()
216+
self.cover.logger.debug(
217+
"Yes sun in window: using calculated percentage (%s)", state
218+
)
219+
else:
220+
state = self.cover.default
221+
self.cover.logger.debug("No sun in window: using default value (%s)", state)
222+
205223
result = np.clip(state, 0, 100)
206224
if self.cover.apply_max_position and result > self.cover.max_pos:
207225
return self.cover.max_pos
@@ -215,6 +233,7 @@ class ClimateCoverData:
215233
"""Fetch additional data."""
216234

217235
hass: HomeAssistant
236+
logger: ConfigContextAdapter
218237
temp_entity: str
219238
temp_low: float
220239
temp_high: float
@@ -289,8 +308,17 @@ def is_presence(self):
289308
def is_winter(self) -> bool:
290309
"""Check if temperature is below threshold."""
291310
if self.temp_low is not None and self.get_current_temperature is not None:
292-
return self.get_current_temperature < self.temp_low
293-
return False
311+
is_it = self.get_current_temperature < self.temp_low
312+
else:
313+
is_it = False
314+
315+
self.logger.debug(
316+
"is_winter(): current_temperature < temp_low: %s < %s = %s",
317+
self.get_current_temperature,
318+
self.temp_low,
319+
is_it,
320+
)
321+
return is_it
294322

295323
@property
296324
def outside_high(self) -> bool:
@@ -306,8 +334,18 @@ def outside_high(self) -> bool:
306334
def is_summer(self) -> bool:
307335
"""Check if temperature is over threshold."""
308336
if self.temp_high is not None and self.get_current_temperature is not None:
309-
return self.get_current_temperature > self.temp_high and self.outside_high
310-
return False
337+
is_it = self.get_current_temperature > self.temp_high and self.outside_high
338+
else:
339+
is_it = False
340+
341+
self.logger.debug(
342+
"is_summer(): current_temp > temp_high and outside_high?: %s > %s and %s = %s",
343+
self.get_current_temperature,
344+
self.temp_high,
345+
self.outside_high,
346+
is_it,
347+
)
348+
return is_it
311349

312350
@property
313351
def is_sunny(self) -> bool:
@@ -316,9 +354,12 @@ def is_sunny(self) -> bool:
316354
if self.weather_entity is not None:
317355
weather_state = get_safe_state(self.hass, self.weather_entity)
318356
else:
357+
self.logger.debug("is_sunny(): No weather entity defined")
319358
return True
320359
if self.weather_condition is not None:
321-
return weather_state in self.weather_condition
360+
matches = weather_state in self.weather_condition
361+
self.logger.debug("is_sunny(): Weather: %s = %s", weather_state, matches)
362+
return matches
322363

323364
@property
324365
def lux(self) -> bool:
@@ -349,6 +390,9 @@ class ClimateCoverState(NormalCoverState):
349390

350391
def normal_type_cover(self) -> int:
351392
"""Determine state for horizontal and vertical covers."""
393+
394+
self.cover.logger.debug("Is presence? %s", self.climate_data.is_presence)
395+
352396
if self.climate_data.is_presence:
353397
return self.normal_with_presence()
354398

@@ -357,23 +401,32 @@ def normal_type_cover(self) -> int:
357401
def normal_with_presence(self) -> int:
358402
"""Determine state for horizontal and vertical covers with occupants."""
359403

404+
is_summer = self.climate_data.is_summer
405+
360406
# Check if it's not summer and either lux, irradiance or sunny weather is present
361-
if not self.climate_data.is_summer and (
407+
if not is_summer and (
362408
self.climate_data.lux
363409
or self.climate_data.irradiance
364410
or not self.climate_data.is_sunny
365411
):
366412
# If it's winter and the cover is valid, return 100
367413
if self.climate_data.is_winter and self.cover.valid:
414+
self.cover.logger.debug(
415+
"n_w_p(): Winter and sun is in front of window = use 100"
416+
)
368417
return 100
369418
# Otherwise, return the default cover state
419+
self.cover.logger.debug(
420+
"n_w_p(): it's not summer and sunny weather is not present = use default"
421+
)
370422
return self.cover.default
371423

372424
# If it's summer and there's a transparent blind, return 0
373-
if self.climate_data.is_summer and self.climate_data.transparent_blind:
425+
if is_summer and self.climate_data.transparent_blind:
374426
return 0
375427

376428
# If none of the above conditions are met, get the state from the parent class
429+
self.cover.logger.debug("n_w_p(): None of the climate conditions are met")
377430
return super().get_state()
378431

379432
def normal_without_presence(self) -> int:
@@ -426,8 +479,18 @@ def get_state(self) -> int:
426479
if self.climate_data.blind_type == "cover_tilt":
427480
result = self.tilt_state()
428481
if self.cover.apply_max_position and result > self.cover.max_pos:
482+
self.cover.logger.debug(
483+
"Climate state: Max position applied (%s > %s)",
484+
result,
485+
self.cover.max_pos,
486+
)
429487
return self.cover.max_pos
430488
if self.cover.apply_min_position and result < self.cover.min_pos:
489+
self.cover.logger.debug(
490+
"Climate state: Min position applied (%s < %s)",
491+
result,
492+
self.cover.min_pos,
493+
)
431494
return self.cover.min_pos
432495
return result
433496

@@ -453,7 +516,11 @@ def calculate_position(self) -> float:
453516

454517
def calculate_percentage(self) -> float:
455518
"""Convert blind height to percentage or default value."""
456-
result = self.calculate_position() / self.h_win * 100
519+
position = self.calculate_position()
520+
self.logger.debug(
521+
"Converting height to percentage: %s / %s * 100", position, self.h_win
522+
)
523+
result = position / self.h_win * 100
457524
return round(result)
458525

459526

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""This module provides a logging adapter that adds a configuration name to log messages."""
2+
3+
import logging
4+
5+
6+
class ConfigContextAdapter(logging.LoggerAdapter):
7+
"""A logging adapter that adds a configuration name to log messages."""
8+
9+
def __init__(self, logger, extra=None):
10+
"""Initialize the ConfigContextAdapter.
11+
12+
Args:
13+
logger (logging.Logger): The logger instance to which this adapter is attached.
14+
extra (dict, optional): Additional context information. Defaults to None.
15+
16+
"""
17+
super().__init__(logger, extra or {})
18+
self.config_name = None
19+
20+
def set_config_name(self, config_name):
21+
"""Set the configuration name.
22+
23+
Args:
24+
config_name (str): The name of the configuration to set.
25+
26+
"""
27+
self.config_name = config_name
28+
29+
def process(self, msg, kwargs):
30+
"""Process the log message and add the configuration name if set.
31+
32+
Args:
33+
msg (str): The log message.
34+
kwargs (dict): Additional keyword arguments.
35+
36+
Returns:
37+
tuple: The processed log message and keyword arguments.
38+
39+
"""
40+
if self.config_name:
41+
return f"[{self.config_name}] {msg}", kwargs
42+
else:
43+
return f"[Unknown] {msg}", kwargs

0 commit comments

Comments
 (0)