diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 381b649..e42e7d3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,6 +8,7 @@ + - fixes: ## How has this been tested? @@ -37,4 +38,4 @@ - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. \ No newline at end of file +- [ ] I have updated the documentation accordingly. diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 8704a79..ac358c6 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -58,7 +58,7 @@ exlude-contributors: - "dependabot" template: | - # Adaptive Cover ⛅ v$RESOLVED_VERSION + # Adaptive Cover ⛅ v$RESOLVED_VERSION ## Changes $CHANGES diff --git a/.ruff.toml b/.ruff.toml index b8b5067..eb68b8c 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,8 +1,8 @@ # The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml -target-version = "py310" +target-version = "py312" -select = [ +lint.select = [ "B007", # Loop control variable {name} not used within loop body "B014", # Exception handler with duplicate exception "C", # complexity @@ -26,7 +26,7 @@ select = [ "W", # pycodestyle ] -ignore = [ +lint.ignore = [ "D202", # No blank lines allowed after function docstring "D203", # 1 blank line required before class docstring "D213", # Multi-line docstring summary should start at the second line @@ -38,11 +38,11 @@ ignore = [ "E731", # do not assign a lambda expression, use a def ] -[flake8-pytest-style] +[lint.flake8-pytest-style] fixture-parentheses = false -[pyupgrade] +[lint.pyupgrade] keep-runtime-typing = true -[mccabe] +[lint.mccabe] max-complexity = 25 diff --git a/LICENSE b/LICENSE index 7c83bdd..7b6004a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2019 - 2023 Joakim Sørensen @ludeeus - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +MIT License + +Copyright (c) 2019 - 2023 Joakim Sørensen @ludeeus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 5d1d93c..4155f2e 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,6 @@ This mode is split up in two types of strategies; [Presence](https://github.com/ ### Climate - | Variables | Default | Range | Example | Description | | ----------------------------- | ------- | ----- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Indoor Temperature Entity | `None` | | `climate.living_room` \| `sensor.indoor_temp` | | @@ -243,7 +242,6 @@ This mode is split up in two types of strategies; [Presence](https://github.com/ | Irradiance Entity | `None` | | `sensor.irradiance` | Returns measured irradiance | | Irradiance Threshold | `300` | | | "In non-summer, above threshold, use optimal position. Otherwise, default position or fully open in winter." | - ### Blindspot | Variables | Default | Range | Example | Description | diff --git a/custom_components/adaptive_cover/blueprints/auto_sun_blind.yaml b/custom_components/adaptive_cover/blueprints/auto_sun_blind.yaml index fec011d..270957b 100644 --- a/custom_components/adaptive_cover/blueprints/auto_sun_blind.yaml +++ b/custom_components/adaptive_cover/blueprints/auto_sun_blind.yaml @@ -252,13 +252,13 @@ variables: {# default height, when automatic control is off. #} {%- set def_h = def / 100 * h_max -%} {%- set alpha = deg2rad * sun_ele -%} - {% set gamma = deg2rad * ((win_azi - sun_azi + 180) % 360 - 180) %} + {% set gamma = deg2rad * ((win_azi - sun_azi + 180) % 360 - 180) %} {%- set h = (d / cos(gamma)) * tan(alpha) -%} {# gamma is outside of FOV #} {%- if gamma < azi_left or gamma > azi_right or alpha < elev_low or alpha > elev_high -%} {{ clipv(h2perc(def_h) | round(0) | int , 0, 100) }} {# gamma is inside of FOV #} - {%- else -%} + {%- else -%} {{ clipv(h2perc(h) | round(0) | int , min_pos, 100) }} {%- endif -%} change_threshold: !input change_threshold diff --git a/custom_components/adaptive_cover/calculation.py b/custom_components/adaptive_cover/calculation.py index d13754c..8ddb878 100644 --- a/custom_components/adaptive_cover/calculation.py +++ b/custom_components/adaptive_cover/calculation.py @@ -32,6 +32,8 @@ class AdaptiveGeneralCover(ABC): h_def: int max_pos: int min_pos: int + max_pos_bool: bool + min_pos_bool: bool blind_spot_left: int blind_spot_right: int blind_spot_elevation: int @@ -155,6 +157,29 @@ def fov(self) -> list: """Return field of view.""" return [self.azi_min_abs, self.azi_max_abs] + @property + def apply_min_position(self) -> bool: + """Check if min position is applied.""" + if self.min_pos is not None and self.min_pos != 0: + if self.min_pos_bool: + return self.direct_sun_valid + return True + return False + + @property + def apply_max_position(self) -> bool: + """Check if max position is applied.""" + if self.max_pos is not None and self.max_pos != 100: + if self.max_pos_bool: + return self.direct_sun_valid + return True + return False + + @property + def direct_sun_valid(self) -> bool: + """Check if sun is directly in front of window.""" + return (self.valid) & (not self.sunset_valid) & (not self.is_sun_in_blind_spot) + @abstractmethod def calculate_position(self) -> float: """Calculate the position of the blind.""" @@ -173,16 +198,14 @@ class NormalCoverState: def get_state(self) -> int: """Return state.""" state = np.where( - (self.cover.valid) - & (not self.cover.sunset_valid) - & (not self.cover.is_sun_in_blind_spot), + self.cover.direct_sun_valid, self.cover.calculate_percentage(), self.cover.default, ) result = np.clip(state, 0, 100) - if result > self.cover.max_pos: + if self.cover.apply_max_position and result > self.cover.max_pos: return self.cover.max_pos - if result < self.cover.min_pos: + if self.cover.apply_min_position and result < self.cover.min_pos: return self.cover.min_pos return result diff --git a/custom_components/adaptive_cover/config_flow.py b/custom_components/adaptive_cover/config_flow.py index 11c0a84..077de48 100644 --- a/custom_components/adaptive_cover/config_flow.py +++ b/custom_components/adaptive_cover/config_flow.py @@ -73,6 +73,8 @@ DOMAIN, SensorType, CONF_MIN_POSITION, + CONF_ENABLE_MAX_POSITION, + CONF_ENABLE_MIN_POSITION, ) # DEFAULT_NAME = "Adaptive Cover" @@ -109,14 +111,14 @@ min=0, max=100, step=1, mode="slider", unit_of_measurement="%" ) ), - vol.Optional(CONF_MAX_POSITION, default=100): selector.NumberSelector( - selector.NumberSelectorConfig( - min=1, max=100, step=1, mode="slider", unit_of_measurement="%" - ) + vol.Optional(CONF_MAX_POSITION): vol.All( + vol.Coerce(int), vol.Range(min=1, max=100) ), + vol.Optional(CONF_ENABLE_MAX_POSITION, default=False): bool, vol.Optional(CONF_MIN_POSITION): vol.All( - vol.Coerce(int), vol.Range(min=0, max=90) + vol.Coerce(int), vol.Range(min=0, max=99) ), + vol.Optional(CONF_ENABLE_MIN_POSITION, default=False): bool, vol.Optional(CONF_MIN_ELEVATION): vol.All( vol.Coerce(int), vol.Range(min=0, max=90) ), diff --git a/custom_components/adaptive_cover/const.py b/custom_components/adaptive_cover/const.py index a04f1fb..b80d380 100644 --- a/custom_components/adaptive_cover/const.py +++ b/custom_components/adaptive_cover/const.py @@ -40,6 +40,8 @@ CONF_WEATHER_STATE = "weather_state" CONF_MAX_POSITION = "max_position" CONF_MIN_POSITION = "min_position" +CONF_ENABLE_MAX_POSITION = "enable_max_position" +CONF_ENABLE_MIN_POSITION = "enable_min_position" CONF_OUTSIDETEMP_ENTITY = "outside_temp" CONF_ENABLE_BLIND_SPOT = "blind_spot" CONF_BLIND_SPOT_RIGHT = "blind_spot_right" diff --git a/custom_components/adaptive_cover/coordinator.py b/custom_components/adaptive_cover/coordinator.py index 2cc14a5..921c08b 100644 --- a/custom_components/adaptive_cover/coordinator.py +++ b/custom_components/adaptive_cover/coordinator.py @@ -85,6 +85,8 @@ CONF_OUTSIDE_THRESHOLD, DOMAIN, LOGGER, + CONF_ENABLE_MAX_POSITION, + CONF_ENABLE_MIN_POSITION, ) from .helpers import get_datetime_from_str, get_last_updated, get_safe_state @@ -544,6 +546,8 @@ def common_data(self, options): options.get(CONF_DEFAULT_HEIGHT), options.get(CONF_MAX_POSITION), options.get(CONF_MIN_POSITION), + options.get(CONF_ENABLE_MAX_POSITION, False), + options.get(CONF_ENABLE_MIN_POSITION, False), options.get(CONF_BLIND_SPOT_LEFT), options.get(CONF_BLIND_SPOT_RIGHT), options.get(CONF_BLIND_SPOT_ELEVATION), diff --git a/custom_components/adaptive_cover/manifest.json b/custom_components/adaptive_cover/manifest.json index e0908f5..5380ff0 100644 --- a/custom_components/adaptive_cover/manifest.json +++ b/custom_components/adaptive_cover/manifest.json @@ -3,7 +3,14 @@ "name": "Adaptive Cover", "codeowners": ["@basbruss"], "config_flow": true, - "dependencies": ["sun","device_tracker","zone","climate","sensor","weather"], + "dependencies": [ + "sun", + "device_tracker", + "zone", + "climate", + "sensor", + "weather" + ], "documentation": "https://github.com/basbruss/adaptive-cover", "iot_class": "calculated", "issue_tracker": "https://github.com/basbruss/adaptive-cover/issues", diff --git a/custom_components/adaptive_cover/translations/en.json b/custom_components/adaptive_cover/translations/en.json index 92f0dca..8b5f93a 100644 --- a/custom_components/adaptive_cover/translations/en.json +++ b/custom_components/adaptive_cover/translations/en.json @@ -42,6 +42,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", @@ -120,6 +122,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", @@ -161,6 +165,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", @@ -269,6 +275,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", @@ -347,6 +355,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", @@ -388,6 +398,8 @@ "default_percentage": "Default Position", "min_position": "Minimum Position", "max_position": "Maximum Position", + "enable_min_position": "Only force the minimum position when the sun is in front of the window", + "enable_max_position": "Only force the maximum position when the sun is in front of the window", "fov_left": "Field of view left", "fov_right": "Field of view right", "group": "Cover Entities", diff --git a/custom_components/adaptive_cover/translations/nl.json b/custom_components/adaptive_cover/translations/nl.json index a6b63e4..c2c360a 100644 --- a/custom_components/adaptive_cover/translations/nl.json +++ b/custom_components/adaptive_cover/translations/nl.json @@ -42,6 +42,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entiteiten", @@ -119,6 +121,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entities", @@ -159,6 +163,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entities", @@ -266,6 +272,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entiteiten", @@ -335,6 +343,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entities", @@ -375,6 +385,8 @@ "default_percentage": "Standaard positie", "min_position": "Minimale positie", "max_position": "Maximale positie", + "enable_min_position": "Forceer alleen de minimale positie wanneer de zon voor het raam staat", + "enable_max_position": "Forceer alleen de maximale positie wanneer de zon voor het raam staat", "fov_left": "Links gezichtsveld", "fov_right": "Rechts gezichtsveld", "group": "Cover Entities", diff --git a/notebooks/test_env.ipynb b/notebooks/test_env.ipynb index dcac6f3..7e636e8 100644 --- a/notebooks/test_env.ipynb +++ b/notebooks/test_env.ipynb @@ -8,15 +8,16 @@ "source": [ "# Allow to load the `adaptive cover` module from this notebook\n", "import sys\n", + "\n", "sys.path.append(\"../custom_components\")\n", "\n", - "from datetime import date, timedelta\n", - "from pvlib import solarposition\n", + "from datetime import date, timedelta # noqa: F401 # noqa: F401\n", + "from pvlib import solarposition # noqa: F401\n", "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from adaptive_cover.calculation import AdaptiveVerticalCover, AdaptiveHorizontalCover\n", - "from adaptive_cover.sun import SunData" + "import numpy as np # noqa: F401\n", + "import matplotlib.pyplot as plt # noqa: F401\n", + "from adaptive_cover.calculation import AdaptiveVerticalCover, AdaptiveHorizontalCover # type: ignore\n", + "from adaptive_cover.sun import SunData # type: ignore" ] }, { @@ -57,8 +58,9 @@ "\n", "default_height = 60\n", "\n", - "class MockedHass:\n", - " class MockedConfig:\n", + "\n", + "class MockedHass: # noqa: D101\n", + " class MockedConfig: # noqa: D106\n", " latitude = lat\n", " longitude = lon\n", " time_zone = timezone\n", @@ -67,6 +69,7 @@ " config = MockedConfig()\n", " data = {}\n", "\n", + "\n", "mocked_hass = MockedHass()\n", "\n", "sun_data = SunData(hass=MockedHass, timezone=timezone)\n", @@ -82,7 +85,7 @@ "sun_df.set_index(sun_data.times, inplace=True)\n", "\n", "\n", - "def compute_cover_position(sun_data: pd.Series):\n", + "def compute_cover_position(sun_data: pd.Series): # noqa: D103\n", " # print(sun_data)\n", " vertical_cover = AdaptiveVerticalCover(\n", " hass=mocked_hass,\n", @@ -110,33 +113,75 @@ " return vertical_cover.calculate_percentage(), vertical_cover.valid\n", "\n", "\n", - "sun_df[[\"opening state\", \"sun_is_on\"]] = sun_df.apply(compute_cover_position, axis=1, result_type=\"expand\")\n", + "sun_df[[\"opening state\", \"sun_is_on\"]] = sun_df.apply(\n", + " compute_cover_position, axis=1, result_type=\"expand\"\n", + ")\n", "\n", - "sun_in_front_begin=sun_df[sun_df[\"sun_is_on\"]].iloc[0].name.to_pydatetime()\n", - "sun_in_front_end=sun_df[sun_df[\"sun_is_on\"]].iloc[-1].name.to_pydatetime()\n", + "sun_in_front_begin = sun_df[sun_df[\"sun_is_on\"]].iloc[0].name.to_pydatetime()\n", + "sun_in_front_end = sun_df[sun_df[\"sun_is_on\"]].iloc[-1].name.to_pydatetime()\n", "\n", "\n", - "def plot(suptitle: str):\n", - " ax = sun_df.plot(secondary_y=[\"opening state\"], title=f\"window height: {window_height}m, azimuth: {windown_azimuth}°, distance: {window_distance}m\")\n", + "def plot(suptitle: str): # noqa: D103\n", + " ax = sun_df.plot(\n", + " secondary_y=[\"opening state\"],\n", + " title=f\"window height: {window_height}m, azimuth: {windown_azimuth}°, distance: {window_distance}m\",\n", + " )\n", "\n", " ax.set_ylabel(\"sun elevation/azimuth (degree)\")\n", " ax.right_ax.set_ylabel(\"cover opening state (%)\")\n", "\n", " ax.figure.suptitle(suptitle)\n", "\n", - " ax.axvline(sunrise_time, color='r', ls=':')\n", - " ax.text(sunrise_time, 0.99, 'sunrise', color='r', ha='right', va='top', rotation=90, transform=ax.get_xaxis_transform())\n", + " ax.axvline(sunrise_time, color=\"r\", ls=\":\")\n", + " ax.text(\n", + " sunrise_time,\n", + " 0.99,\n", + " \"sunrise\",\n", + " color=\"r\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " rotation=90,\n", + " transform=ax.get_xaxis_transform(),\n", + " )\n", "\n", - " ax.axvline(sun_in_front_begin, color='y', ls=':')\n", - " ax.text(sun_in_front_begin, 0.5, 'sun in front begin', color='y', ha='right', va='top', rotation=90, transform=ax.get_xaxis_transform())\n", + " ax.axvline(sun_in_front_begin, color=\"y\", ls=\":\")\n", + " ax.text(\n", + " sun_in_front_begin,\n", + " 0.5,\n", + " \"sun in front begin\",\n", + " color=\"y\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " rotation=90,\n", + " transform=ax.get_xaxis_transform(),\n", + " )\n", "\n", - " ax.axvline(sunset_time, color='r', ls=':')\n", - " ax.text(sunset_time, 0.99, 'sunset', color='r', ha='right', va='top', rotation=90, transform=ax.get_xaxis_transform())\n", + " ax.axvline(sunset_time, color=\"r\", ls=\":\")\n", + " ax.text(\n", + " sunset_time,\n", + " 0.99,\n", + " \"sunset\",\n", + " color=\"r\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " rotation=90,\n", + " transform=ax.get_xaxis_transform(),\n", + " )\n", "\n", - " ax.axvline(sun_in_front_end, color='y', ls=':')\n", - " ax.text(sun_in_front_end, 0.5, 'sun in front end', color='y', ha='right', va='top', rotation=90, transform=ax.get_xaxis_transform())\n", + " ax.axvline(sun_in_front_end, color=\"y\", ls=\":\")\n", + " ax.text(\n", + " sun_in_front_end,\n", + " 0.5,\n", + " \"sun in front end\",\n", + " color=\"y\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " rotation=90,\n", + " transform=ax.get_xaxis_transform(),\n", + " )\n", "\n", - "plot(\"vertical cover\")\n" + "\n", + "plot(\"vertical cover\")" ] }, { @@ -163,7 +208,8 @@ "cover_awning_height = 0\n", "cover_awning_angle = 0\n", "\n", - "def compute_cover_position(sun_data: pd.Series):\n", + "\n", + "def compute_cover_position(sun_data: pd.Series): # noqa: D103\n", " # print(sun_data)\n", " horizontal_cover = AdaptiveHorizontalCover(\n", " hass=mocked_hass,\n", @@ -192,12 +238,16 @@ " return horizontal_cover.calculate_percentage(), horizontal_cover.valid\n", "\n", "\n", - "sun_df[[\"opening state\", \"sun_is_on\"]] = sun_df.apply(compute_cover_position, axis=1, result_type=\"expand\")\n", + "sun_df[[\"opening state\", \"sun_is_on\"]] = sun_df.apply(\n", + " compute_cover_position, axis=1, result_type=\"expand\"\n", + ")\n", "\n", - "sun_in_front_begin=sun_df[sun_df[\"sun_is_on\"]].iloc[0].name.to_pydatetime()\n", - "sun_in_front_end=sun_df[sun_df[\"sun_is_on\"]].iloc[-1].name.to_pydatetime()\n", + "sun_in_front_begin = sun_df[sun_df[\"sun_is_on\"]].iloc[0].name.to_pydatetime()\n", + "sun_in_front_end = sun_df[sun_df[\"sun_is_on\"]].iloc[-1].name.to_pydatetime()\n", "\n", - "plot(f\"horizontal cover length: {cover_awning_length}m, angle:{cover_awning_angle}°, height:{cover_awning_height}m\")" + "plot(\n", + " f\"horizontal cover length: {cover_awning_length}m, angle:{cover_awning_angle}°, height:{cover_awning_height}m\"\n", + ")" ] }, { @@ -213,19 +263,34 @@ "metadata": {}, "outputs": [], "source": [ - "# start_date = '2022-06-05'\n", - "# end_date = '2022-06-06'\n", - "start_date = date.today()\n", - "end_date = start_date + timedelta(days=1)\n", - "\n", - "times = pd.date_range(start=start_date, end=end_date, freq='5min',tz=timezone)\n", - "solpos = solarposition.get_solarposition(times, lat, lon)\n", - "\n", - "slat_distance = 4\n", - "depth = 6\n", - "\n", - "plot_calc = AdaptiveCoverCalculator(timezone,lat,lon,solpos['azimuth'],solpos['elevation'],win_azi,\n", - " h_win,distance,fov_left,fov_right,def_height, angle, l_awn, slat_distance, depth)" + "# # start_date = '2022-06-05'\n", + "# # end_date = '2022-06-06'\n", + "# start_date = date.today()\n", + "# end_date = start_date + timedelta(days=1)\n", + "\n", + "# times = pd.date_range(start=start_date, end=end_date, freq=\"5min\", tz=timezone)\n", + "# solpos = solarposition.get_solarposition(times, lat, lon)\n", + "\n", + "# slat_distance = 4\n", + "# depth = 6\n", + "\n", + "# plot_calc = AdaptiveCoverCalculator(\n", + "# timezone,\n", + "# lat,\n", + "# lon,\n", + "# solpos[\"azimuth\"],\n", + "# solpos[\"elevation\"],\n", + "# win_azi,\n", + "# h_win,\n", + "# distance,\n", + "# fov_left,\n", + "# fov_right,\n", + "# def_height,\n", + "# angle,\n", + "# l_awn,\n", + "# slat_distance,\n", + "# depth,\n", + "# )" ] }, { @@ -241,7 +306,7 @@ "metadata": {}, "outputs": [], "source": [ - "blind_vertical = plot_calc.blind_state_perc()\n", + "# blind_vertical = plot_calc.blind_state_perc()\n", "# print(blind_vertical)\n", "# plt.plot(blind_vertical, label='blind vertical', color='red')" ] @@ -259,7 +324,7 @@ "metadata": {}, "outputs": [], "source": [ - "blind_horizontal = plot_calc.awn_state_perc()\n", + "# blind_horizontal = plot_calc.awn_state_perc()\n", "# print(blind_horizontal)\n", "# plt.plot(blind_horizontal, label='blind horizontal', color='green')" ] @@ -342,9 +407,9 @@ } ], "source": [ - "blind_tilt = plot_calc.calculate_tilt_angle\n", - "print(blind_tilt.values)\n", - "blind_tilt_per = plot_calc.tilt_state_perc()" + "# blind_tilt = plot_calc.calculate_tilt_angle\n", + "# print(blind_tilt.values)\n", + "# blind_tilt_per = plot_calc.tilt_state_perc()" ] }, { @@ -362,24 +427,25 @@ } ], "source": [ - "from astral import LocationInfo # pylint: disable=import-outside-toplevel\n", - "from astral.location import Location\n", - "from astral.sun import sun\n", - "info = LocationInfo('','',\"CET\",lat,lon)\n", - "astral_location = Location(info)\n", - "#print(times.to_list)\n", - "print(astral_location.solar_azimuth(times[83]))\n", - "print((astral_location.sunset(start_date,local=False)))\n", - "test_astral = sun(info.observer, date=date.today())\n", - "#print(test_astral)\n", - "index = 0\n", - "azi_list = []\n", - "for i in times:\n", - " azi_list.append(Location(info).solar_azimuth(times[index]))\n", - " index += 1\n", - "df_azi = pd.DataFrame(azi_list)\n", - "df_azi = df_azi.set_index(times)\n", - "#print(df_azi)" + "# from astral import LocationInfo # pylint: disable=import-outside-toplevel\n", + "# from astral.location import Location\n", + "# from astral.sun import sun\n", + "\n", + "# info = LocationInfo(\"\", \"\", \"CET\", lat, lon)\n", + "# astral_location = Location(info)\n", + "# # print(times.to_list)\n", + "# print(astral_location.solar_azimuth(times[83]))\n", + "# print(astral_location.sunset(start_date, local=False))\n", + "# test_astral = sun(info.observer, date=date.today())\n", + "# # print(test_astral)\n", + "# index = 0\n", + "# azi_list = []\n", + "# for i in times:\n", + "# azi_list.append(Location(info).solar_azimuth(times[index]))\n", + "# index += 1\n", + "# df_azi = pd.DataFrame(azi_list)\n", + "# df_azi = df_azi.set_index(times)\n", + "# # print(df_azi)" ] }, { @@ -396,27 +462,30 @@ } ], "source": [ - "elevation = 36\n", + "# elevation = 36\n", + "\n", + "\n", + "# def get_astral_location(\n", + "# lat, lon, timezone, elevation\n", + "# ) -> tuple[astral.location.Location, astral.Elevation]:\n", + "# \"\"\"Get an astral location for the current Home Assistant configuration.\"\"\"\n", + "# from astral import LocationInfo # pylint: disable=import-outside-toplevel\n", + "# from astral.location import Location # pylint: disable=import-outside-toplevel\n", "\n", - "def get_astral_location(lat, lon, timezone, elevation\n", - ") -> tuple[astral.location.Location, astral.Elevation]:\n", - " \"\"\"Get an astral location for the current Home Assistant configuration.\"\"\"\n", - " from astral import LocationInfo # pylint: disable=import-outside-toplevel\n", - " from astral.location import Location # pylint: disable=import-outside-toplevel\n", + "# latitude = lat\n", + "# longitude = lon\n", + "# timezone = str(timezone)\n", + "# elevation = elevation\n", + "# info = (\"\", \"\", timezone, latitude, longitude)\n", "\n", - " latitude = lat\n", - " longitude = lon\n", - " timezone = str(timezone)\n", - " elevation = elevation\n", - " info = (\"\", \"\", timezone, latitude, longitude)\n", + "# # Cache astral locations so they aren't recreated with the same args\n", + "# dict = {}\n", + "# dict[info] = Location(LocationInfo(*info))\n", "\n", - " # Cache astral locations so they aren't recreated with the same args\n", - " dict = {}\n", - " dict[info] = Location(LocationInfo(*info))\n", + "# return dict[info], elevation\n", "\n", - " return dict[info], elevation\n", "\n", - "print(get_astral_location(lat,lon,timezone,elevation))" + "# print(get_astral_location(lat, lon, timezone, elevation))" ] }, { @@ -436,30 +505,30 @@ } ], "source": [ - "time = solpos.index\n", - "\n", - "ax, fig = plt.subplots(figsize=(15, 5))\n", - "plt.title(f\"Window azimuth: {win_azi}°, azi_min: {fov_left}°, azi_max: {fov_right}°\")\n", - "plt.plot(solpos['elevation'].values, label='elevation')\n", - "plt.plot(solpos['azimuth'].values, label='azimuth')\n", - "plt.plot(blind_tilt.values, label=\"tilt\", color='purple')\n", - "plt.legend(loc='upper left')\n", - "plt.ylabel('Angle [°]')\n", - "\n", - "ax2 = fig.twinx()\n", - "ax2.plot(np.clip(blind_vertical, 0, 100), label='blind vertical', color='red')\n", - "ax2.plot(np.clip(blind_horizontal, 0, 100), label='blind horizontal', color='green')\n", - "ax2.plot(np.clip(blind_tilt_per, 0, 100), label='blind horizontal', color='brown')\n", - "ax2.set_ylim(0, 100 + 0.1)\n", - "ax2.legend(loc='upper right')\n", - "\n", - "plt.xticks(np.arange(0, len(time), 12), time[::12].strftime('%H:%M'), rotation=90)\n", - "\n", - "# plt labels\n", - "plt.xlabel('Time [HH:MM]')\n", - "plt.ylabel('Blind open [%]')\n", - "plt.savefig('simulation/sim_plot.png')\n", - "plt.show()" + "# time = solpos.index\n", + "\n", + "# ax, fig = plt.subplots(figsize=(15, 5))\n", + "# plt.title(f\"Window azimuth: {win_azi}°, azi_min: {fov_left}°, azi_max: {fov_right}°\")\n", + "# plt.plot(solpos[\"elevation\"].values, label=\"elevation\")\n", + "# plt.plot(solpos[\"azimuth\"].values, label=\"azimuth\")\n", + "# plt.plot(blind_tilt.values, label=\"tilt\", color=\"purple\")\n", + "# plt.legend(loc=\"upper left\")\n", + "# plt.ylabel(\"Angle [°]\")\n", + "\n", + "# ax2 = fig.twinx()\n", + "# ax2.plot(np.clip(blind_vertical, 0, 100), label=\"blind vertical\", color=\"red\")\n", + "# ax2.plot(np.clip(blind_horizontal, 0, 100), label=\"blind horizontal\", color=\"green\")\n", + "# ax2.plot(np.clip(blind_tilt_per, 0, 100), label=\"blind horizontal\", color=\"brown\")\n", + "# ax2.set_ylim(0, 100 + 0.1)\n", + "# ax2.legend(loc=\"upper right\")\n", + "\n", + "# plt.xticks(np.arange(0, len(time), 12), time[::12].strftime(\"%H:%M\"), rotation=90)\n", + "\n", + "# # plt labels\n", + "# plt.xlabel(\"Time [HH:MM]\")\n", + "# plt.ylabel(\"Blind open [%]\")\n", + "# plt.savefig(\"simulation/sim_plot.png\")\n", + "# plt.show()" ] } ], diff --git a/scripts/setup b/scripts/setup index 7261532..4d86e2c 100755 --- a/scripts/setup +++ b/scripts/setup @@ -8,4 +8,4 @@ cd "$(dirname "$0")/.." python3 -m pip install --requirement requirements-dev.txt # Setup git commit hooks -pre-commit install \ No newline at end of file +pre-commit install