Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pnbruckner committed Dec 11, 2023
0 parents commit b4c337e
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Validate

on:
pull_request:
push:

jobs:
validate-hassfest:
runs-on: ubuntu-latest
name: With hassfest
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@v3

- name: 🏃 Hassfest validation
uses: "home-assistant/actions/hassfest@master"

validate-hacs:
runs-on: ubuntu-latest
name: With HACS Action
steps:
- name: 🏃 HACS validation
uses: hacs/action@main
with:
category: integration
ignore: brands
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

.vscode/
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

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 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.

For more information, please refer to <http://unlicense.org>
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# <img src="https://brands.home-assistant.io/entity_tz/icon.png" alt="Entity Time Zone Sensor" width="50" height="50"/> Entity Time Zone Sensor

Creates a sensor that indicates the time zone in which another entity is located.
The entity must have `latitude` and `longitude` attributes.

Follow the installation instructions below.

## Installation
### With HACS
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://hacs.xyz/)

You can use HACS to manage the installation and provide update notifications.

1. Add this repo as a [custom repository](https://hacs.xyz/docs/faq/custom_repositories/):

```text
https://github.com/pnbruckner/ha-entity-tz
```

2. Install the integration using the appropriate button on the HACS Integrations page. Search for "entity time zone".

### Manual

Place a copy of the files from [`custom_components/entity_tz`](custom_components/entity_tz)
in `<config>/custom_components/entity_tz`,
where `<config>` is your Home Assistant configuration directory.

>__NOTE__: When downloading, make sure to use the `Raw` button from each file's page.
### Versions

This custom integration supports HomeAssistant versions 2023.4.0 or newer.
59 changes: 59 additions & 0 deletions custom_components/entity_tz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Entity Time Zone Sensor."""
from __future__ import annotations

import asyncio
from collections.abc import Coroutine
from typing import Any

from timezonefinder import TimezoneFinder
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_UNIQUE_ID,
EVENT_CORE_CONFIG_UPDATE,
SERVICE_RELOAD,
Platform,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.sun import get_astral_location
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN

PLATFORMS = [Platform.SENSOR]


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up composite integration."""

def create_timefinder() -> None:
"""Create timefinder object."""

# This must be done in an executor since the timefinder constructor
# does file I/O.

hass.data[DOMAIN] = TimezoneFinder()

await hass.async_add_executor_job(create_timefinder)
return True


# async def entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
# """Handle config entry update."""
# await hass.config_entries.async_reload(entry.entry_id)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up config entry."""
# entry.async_on_unload(entry.add_update_listener(entry_updated))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
46 changes: 46 additions & 0 deletions custom_components/entity_tz/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Config flow for Illuminance integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ENTITY_ID
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.schema_config_entry_flow import wrapped_entity_config_entry_title
from homeassistant.helpers.selector import EntitySelector, EntitySelectorConfig

from .const import DOMAIN


class EntityTimeZoneConfigFlow(ConfigFlow, domain=DOMAIN):
"""Entity Time Zone config flow."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Start user config flow."""
if user_input is not None:
title = wrapped_entity_config_entry_title(self.hass, user_input[CONF_ENTITY_ID])
return self.async_create_entry(title=title, data=user_input)

entity_ids = [
state.entity_id
for state in self.hass.states.async_all()
if state.domain != "zone"
and all(
attr in state.attributes
for attr in (ATTR_LATITUDE, ATTR_LONGITUDE)
)
]
data_schema = vol.Schema(
{
vol.Required(
CONF_ENTITY_ID
): EntitySelector(EntitySelectorConfig(include_entities=entity_ids)),
}
)
return self.async_show_form(step_id="user", data_schema=data_schema)
4 changes: 4 additions & 0 deletions custom_components/entity_tz/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Constants for Entity Time Zone integration."""
DOMAIN = "entity_tz"

ATTR_UTC_OFFSET = "utc_offset"
12 changes: 12 additions & 0 deletions custom_components/entity_tz/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "entity_tz",
"name": "Entity Time Zone",
"codeowners": ["@pnbruckner"],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/pnbruckner/ha-entity-tz/blob/master/README.md",
"iot_class": "calculated",
"issue_tracker": "https://github.com/pnbruckner/ha-entity-tz/issues",
"requirements": ["timezonefinder==5.2.0"],
"version": "1.0.0b0"
}
133 changes: 133 additions & 0 deletions custom_components/entity_tz/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Entity Time Zone Sensor."""
from __future__ import annotations

from dataclasses import dataclass
import logging
from typing import cast

from homeassistant.components.sensor import (
DOMAIN as S_DOMAIN,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)

# SensorDeviceClass.SPEED was new in 2022.10
speed_sensor_device_class: str | None
try:
from homeassistant.components.sensor import SensorDeviceClass

speed_sensor_device_class = SensorDeviceClass.SPEED
except AttributeError:
speed_sensor_device_class = None
from homeassistant.const import (
EVENT_CORE_CONFIG_UPDATE,
LENGTH_KILOMETERS,
LENGTH_METERS,
LENGTH_MILES,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
)
from homeassistant.util.distance import convert
from homeassistant.util.unit_system import METRIC_SYSTEM

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_ENTITY_ID, CONF_NAME

# UnitOfSpeed was new in 2022.11
meters_per_second: str
try:
from homeassistant.const import UnitOfSpeed

meters_per_second = UnitOfSpeed.METERS_PER_SECOND
except ImportError:
from homeassistant.const import SPEED_METERS_PER_SECOND

meters_per_second = SPEED_METERS_PER_SECOND

from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event

from .const import ATTR_UTC_OFFSET, DOMAIN

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
async_add_entities([EntityTimeZoneSensor(entry)], True)


class EntityTimeZoneSensor(SensorEntity):
"""Entity Time Zone Sensor Entity."""

# _attr_has_entity_name = True
# _attr_extra_state_attributes = {}
_attr_icon = "mdi:map-clock"
_attr_should_poll = False
# _attr_native_value = ""

def __init__(
self, entry: ConfigEntry
) -> None:
"""Initialize composite sensor entity."""
self._attr_name = f"{entry.title} Time Zone"
self._attr_unique_id = entry.entry_id
self._entity_id = entry.data[CONF_ENTITY_ID]

async def async_update(self) -> None:
"""Update sensor."""
self._update(self.hass.states.get(self._entity_id))

async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass.
To be extended by integrations.
"""

@callback
def sensor_state_listener(event: Event) -> None:
"""Process input entity state update."""
new_state: State | None = event.data["new_state"]
old_state: State | None = event.data["old_state"]
if (
not old_state
or not new_state
or new_state.attributes.get(ATTR_LATITUDE) != old_state.attributes.get(ATTR_LATITUDE)
or new_state.attributes.get(ATTR_LONGITUDE) != old_state.attributes.get(ATTR_LONGITUDE)
):
self._update(new_state)
self.async_write_ha_state()

self.async_on_remove(async_track_state_change_event(self.hass, self._entity_id, sensor_state_listener))

def _update(self, state: State | None) -> None:
"""Perform state update."""
_LOGGER.debug("Updating: %s", state)
self._attr_available = False

if state is None:
return
latitude = state.attributes.get(ATTR_LATITUDE)
longitude = state.attributes.get(ATTR_LONGITUDE)
if latitude is None or longitude is None:
return

_LOGGER.debug("Updating 2: lat=%s, lng=%s", latitude, longitude)
tz = self.hass.data[DOMAIN].timezone_at(lat=latitude, lng=longitude)
_LOGGER.debug("Updating 3: %s", tz)
if tz is None:
return

self._attr_native_value = tz
# offset = 0
# self._attr_extra_state_attributes = {ATTR_UTC_OFFSET: offset}
self._attr_available = True
_LOGGER.debug("Updating 4: %s, %s", self._attr_native_value, self._attr_available)
13 changes: 13 additions & 0 deletions custom_components/entity_tz/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "Entity Time Zone",
"config": {
"step": {
"user": {
"title": "Select Entity with Location",
"data": {
"entity_id": "Entity"
}
}
}
}
}
4 changes: 4 additions & 0 deletions hacs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Entity Time Zone",
"homeassistant": "2023.4.0"
}
4 changes: 4 additions & 0 deletions info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# <img src="https://brands.home-assistant.io/entity_tz/icon.png" alt="Entity Time Zone Sensor" width="50" height="50"/> Entity Time Zone Sensor

Creates a sensor that indicates the time zone in which another entity is located.
The entity must have `latitude` and `longitude` attributes.

0 comments on commit b4c337e

Please sign in to comment.