Skip to content

Commit

Permalink
Multiple Fixes (#30)
Browse files Browse the repository at this point in the history
Fixed:
- Storage key for data to valid Windows file path, migration on first run, resolves
  [#26](#26)
- Last Rain sensor now behaving correctly, was previously updating to "Unknown" when it had no data,
  resolves [#28](#28)
- Keys for lightning data storage are converted to strings by the storage provider, perform
  conversions when data is accessed, resolves
  [#29](#29)
  • Loading branch information
tlskinneriv committed Jun 17, 2023
1 parent bce0f17 commit b95ef18
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 49 deletions.
66 changes: 35 additions & 31 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,49 @@
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10-bullseye",
"VARIANT": "3.11-bullseye",
// Options
"NODE_VERSION": "lts/*"
}
},

// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/srv/homeassistant/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.formatting.provider": "black",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
"python.linting.pylintArgs": [
"--init-hook",
"import sys; sys.path.append('/srv/homeassistant/lib/python3.10/site-packages/')"
]
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/srv/homeassistant/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.formatting.provider": "black",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
"python.linting.pylintArgs": [
"--init-hook",
"import sys; sys.path.append('/srv/homeassistant/lib/python3.10/site-packages/')"
]
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"rlnt.keep-a-changelog",
"esbenp.prettier-vscode",
"GitHub.vscode-pull-request-github",
"streetsidesoftware.code-spell-checker",
"ms-python.pylint"
]
}
},

// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"rlnt.keep-a-changelog",
"esbenp.prettier-vscode",
"GitHub.vscode-pull-request-github",
"streetsidesoftware.code-spell-checker"
],

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].

## [1.1.2] - 2023-06-16

### Fixed

- Storage key for data to valid Windows file path, migration on first run, resolves
[#26](https://github.com/tlskinneriv/awnet_local/issues/26)
- Last Rain sensor now behaving correctly, was previously updating to "Unknown" when it had no data,
resolves [#28](https://github.com/tlskinneriv/awnet_local/issues/28)
- Keys for lightning data storage are converted to strings by the storage provider, perform
conversions when data is accessed, resolves
[#29](https://github.com/tlskinneriv/awnet_local/issues/29)

## [1.1.1] - 2023-04-06

### Fixed
Expand Down
24 changes: 23 additions & 1 deletion custom_components/awnet_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
self.station.setdefault(ATTR_LIGHTNING_DATA, {})
self.station[ATTR_SENSOR_UPDATE_IN_PROGRESS] = False
self.station[ATTR_STATIONTYPE] = ""
self._storage_key = f"{STORAGE_KEY}_{self.station[ATTR_MAC]}"
self._old_storage_key = f"{STORAGE_KEY}_{self.station[ATTR_MAC]}"
self._storage_key = f"{STORAGE_KEY}_{self.station[ATTR_MAC].replace(':','')}"
self._old_store: Store = Store(hass, STORAGE_VERSION, self._old_storage_key)
self._store: Store = Store(hass, STORAGE_VERSION, self._storage_key)
self._update_event_handle = f"{DOMAIN}_data_update_{self.station[ATTR_MAC]}"

Expand All @@ -147,6 +149,26 @@ def update_event_handle(self) -> str:
async def async_load(self) -> None:
"""Load data for station from datastore"""
_LOGGER.info("Loading data for integration")
if (data := await self._old_store.async_load()) is not None:
_LOGGER.info("Data being migrated to new storage key: %s", data)
if isinstance(data, list):
self.station[ATTR_KNOWN_SENSORS] = data
self.station[ATTR_LIGHTNING_DATA] = {}
elif isinstance(data, dict):
self.station[ATTR_KNOWN_SENSORS] = data.get(ATTR_KNOWN_SENSORS, None)
self.station[ATTR_LIGHTNING_DATA] = data.get(ATTR_LIGHTNING_DATA, None)
else:
self.station[ATTR_KNOWN_SENSORS] = {}
self.station[ATTR_LIGHTNING_DATA] = {}
_LOGGER.info("Data being saved into new storage key")
await self._store.async_save(
{
ATTR_KNOWN_SENSORS: self.station[ATTR_KNOWN_SENSORS],
ATTR_LIGHTNING_DATA: self.station[ATTR_LIGHTNING_DATA],
}
)
_LOGGER.info("Old storage key being removed")
await self._old_store.async_remove()
if (data := await self._store.async_load()) is not None:
_LOGGER.info("Data being restored: %s", data)
# handle old style data if that's what we have saved
Expand Down
2 changes: 1 addition & 1 deletion custom_components/awnet_local/const_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
# Each sensor listed here is calculated server-side and depends on the list of
# sensors it is a key for
CALCULATED_SENSOR_TYPES = {
TYPE_LASTRAIN: [TYPE_HOURLYRAININ],
TYPE_LASTRAIN: [TYPE_DATEUTC, TYPE_HOURLYRAININ],
TYPE_FEELSLIKE: [TYPE_TEMPF, TYPE_WINDSPEEDMPH, TYPE_HUMIDITY],
TYPE_DEWPOINT: [TYPE_TEMPF, TYPE_HUMIDITY],
TYPE_SOLARRADIATION_LX: [TYPE_SOLARRADIATION],
Expand Down
49 changes: 35 additions & 14 deletions custom_components/awnet_local/helpers_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def calculate(entity_key: str, station_data: dict[str, object]) -> object:
)
if entity_key == TYPE_LASTRAIN:
return AmbientSensorCalculations.last_rain(
float(station_values.get(TYPE_HOURLYRAININ))
str(station_values.get(TYPE_DATEUTC)),
float(station_values.get(TYPE_HOURLYRAININ)),
)
if entity_key == TYPE_FEELSLIKE:
return AmbientSensorCalculations.feels_like(
Expand Down Expand Up @@ -102,7 +103,7 @@ def solar_rad_wm2_to_lux(solar_rad_wm2: float) -> float:
return float(round(solar_rad_wm2 * 126.7, 0))

@staticmethod
def last_rain(hourly_rain_in: float) -> any:
def last_rain(lightning_time: str, hourly_rain_in: float) -> any:
"""Calculates the last rain timestamp from the last time that houlry rain had a value
greater than 0 per https://github.com/ambient-weather/api-docs/wiki/Device-Data-Specs
Expand All @@ -113,8 +114,10 @@ def last_rain(hourly_rain_in: float) -> any:
Returns:
any: timestamp if there is data to report; None if it's not raining
"""
print("we are calculating last rain")
print(hourly_rain_in)
if hourly_rain_in > 0:
return datetime.now(timezone.utc)
return AmbientSensorConversions.mysql_timestamp_to_datetime(lightning_time)
return None

@staticmethod
Expand Down Expand Up @@ -167,7 +170,7 @@ def wind_chill(tempf: float, wind_mph: float) -> float:
def lightning_hour(
lightning_time: str,
lightning_current_count: int,
lightning_data: dict[float, int],
lightning_data: dict[str, int],
) -> int:
"""Calculates lighting strikes in the last hour based on lightning_data collected in the
last hour
Expand All @@ -183,15 +186,15 @@ def lightning_hour(
lightning_datetime = AmbientSensorConversions.mysql_timestamp_to_datetime(
lightning_time
)
lightning_data[lightning_datetime.timestamp()] = lightning_current_count
lightning_data[str(lightning_datetime.timestamp())] = lightning_current_count

# find the time closest to an hour ago to get the count of lightning strikes from
lightning_data_times = list(lightning_data.keys())
search_datetime = lightning_datetime - timedelta(hours=1)
lightning_data_closest_times = {
abs(search_datetime.timestamp() - test_datetime): datetime.fromtimestamp(
test_datetime
)
abs(
search_datetime.timestamp() - float(test_datetime)
): datetime.fromtimestamp(float(test_datetime))
for test_datetime in lightning_data_times
}
lightning_data_closest_time = lightning_data_closest_times[
Expand All @@ -205,7 +208,7 @@ def lightning_hour(

# calculate the value of the number of strikes that happened in the last hour
lightning_previous_count = lightning_data[
lightning_data_closest_time.timestamp()
str(lightning_data_closest_time.timestamp())
]
if lightning_previous_count > lightning_current_count:
_LOGGER.debug(
Expand All @@ -221,7 +224,7 @@ def lightning_hour(
for date in [
timestamp
for timestamp in lightning_data.keys()
if timestamp < search_datetime.timestamp()
if float(timestamp) < search_datetime.timestamp()
]:
_LOGGER.debug("Removing old entry for %s", date)
del lightning_data[date]
Expand Down Expand Up @@ -312,17 +315,35 @@ def convert(entity_key: str, value: object) -> object:
raise NotImplementedError(f"Conversion for {entity_key} is not implemented")

@staticmethod
def epoch_to_datetime(epoch: int) -> str:
def epoch_to_datetime(epoch: int) -> datetime:
"""Converts epoch time to a datetime object in UTC
Args:
epoch (int): epoch time
Returns:
datetime: datetime object representing the epoch time in UTC
"""
return datetime.fromtimestamp(epoch, timezone.utc)

@staticmethod
def mysql_timestamp_to_datetime(mysql_timestamp: str) -> datetime:
"""Converts a MySQL timestamp string into a datetime object in UTC
Args:
mysql_timestamp (str): MySQL timestamp string
Returns:
datetime: datetime object representing the MySQL timestamp string in UTC
"""
try:
return datetime.strptime(mysql_timestamp, "%Y-%m-%d %H:%M:%S").replace(
tzinfo=timezone.utc
)
except Exception as e:
except ValueError as error:
_LOGGER.error(
'Failed to convert timestamp "%s" to datetime: %s', mysql_timestamp, e
'Failed to convert timestamp "%s" to datetime: %s',
mysql_timestamp,
error,
)
return None
return datetime.fromtimestamp(0, timezone.utc)
2 changes: 1 addition & 1 deletion custom_components/awnet_local/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/tlskinneriv/awnet_local/issues",
"requirements": [],
"version": "1.0.1"
"version": "1.1.2"
}
6 changes: 5 additions & 1 deletion custom_components/awnet_local/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,13 @@ def update_from_latest_data(self) -> None:
for x in CALCULATED_SENSOR_TYPES[self.entity_description.key]
):
# calculation of sensor values
raw = AmbientSensorCalculations.calculate(
value = AmbientSensorCalculations.calculate(
self.entity_description.key, self._ambient.station
)
if value is not None:
raw = value
else:
raw = self._attr_native_value
else:
raw = None

Expand Down

0 comments on commit b95ef18

Please sign in to comment.