Skip to content

Commit

Permalink
Merge pull request #35 from jlapenna/dev
Browse files Browse the repository at this point in the history
Add Photos entities for each activity.
  • Loading branch information
craibo authored Oct 16, 2022
2 parents 276e8a4 + 5675877 commit 850d993
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 318 deletions.
6 changes: 4 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
"ms-python.vscode-pylance",
"foxundermoon.shell-format"
],
"settings": {
"editor.tabSize": 4,
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black"
"python.formatting.provider": "black",
"ha_strava.remote_host": "home.local"
}
}
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ repos:
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shfmt
- repo: local
hooks:
- id: black
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"type": "python",
"request": "attach",
"port": 5678,
"host": "homeassistant.local",
"host": "${config:ha_strava.remote_host}",
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
Expand Down
6 changes: 6 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
"type": "shell",
"command": "pre-commit",
"problemMatcher": []
},
{
"label": "Push Component to Remote Host",
"type": "shell",
"command": "tools/push_remote.sh ${config:ha_strava.remote_host}",
"problemMatcher": []
}
]
}
616 changes: 319 additions & 297 deletions custom_components/ha_strava/__init__.py

Large diffs are not rendered by default.

108 changes: 99 additions & 9 deletions custom_components/ha_strava/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,48 @@
CONF_PHOTOS_ENTITY,
CONFIG_URL_DUMP_FILENAME,
DOMAIN,
EVENT_ACTIVITY_IMAGES_UPDATE,
MAX_NB_ACTIVITIES,
)

_LOGGER = logging.getLogger(__name__)

_DEFAULT_IMAGE_URL = (
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/"
"No_image_available_600_x_450.svg/1280px-No_image_available_600_x_450.svg.png"
)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""
Set up the Camera that displays images from Strava.
Works via image-URLs, not via local file storage
"""

if not config_entry.data.get(CONF_PHOTOS, False):
camera = UrlCam(default_enabled=False)
else:
camera = UrlCam(default_enabled=True)

async_add_entities([camera])
default_enabled = config_entry.data.get(CONF_PHOTOS, False)
cameras = [UrlCam(default_enabled=default_enabled)]
for i in range(MAX_NB_ACTIVITIES):
cameras.append(
ActivityCamera(
device_info={
"identifiers": {(DOMAIN, f"strava_activity_{i}")},
"name": f"Strava Activity {i}",
"manufacturer": "Strava",
"model": "Activity",
},
activity_index=0,
default_enabled=default_enabled,
)
)
async_add_entities(cameras)

def image_update_listener( # pylint: disable=inconsistent-return-statements
now,
): # pylint: disable=unused-argument
if len(ha_strava_config_entries) != 1:
return -1

camera.rotate_img()
for camera in cameras:
camera.rotate_img()

ha_strava_config_entries = hass.config_entries.async_entries(domain=DOMAIN)
img_update_interval_seconds = int(
Expand All @@ -59,7 +76,74 @@ def image_update_listener( # pylint: disable=inconsistent-return-statements
hass, image_update_listener, timedelta(seconds=img_update_interval_seconds)
)

return

class ActivityCamera(Camera): # pylint: disable=too-many-instance-attributes
"""
Rotates through all images for an activity.
Up to 100 URLs are stored in the Camera object.
"""

_attr_should_poll = False

def __init__(self, device_info, activity_index, default_enabled=True):
"""Initialize Camera component."""
super().__init__()
self._attr_device_info = device_info
self._attr_name = f"{device_info['name']} Photos"
self._attr_unique_id = f"strava_{activity_index}_photos"
self._attr_entity_registry_enabled_default = default_enabled

self._device_id = (
list(device_info["identifiers"])[0][1] if device_info else None
)
self._activity_index = int(activity_index)

self._url_index = 0
self._urls = []

@property
def state(self): # pylint: disable=overridden-final-method
if len(self._urls) == 0:
return _DEFAULT_IMAGE_URL
return self._urls[self._url_index]["url"]

def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return image response."""
if len(self._urls) == 0:
_LOGGER.debug(f"{self._device_id}: serving default image")
return _return_default_img()

url = self._urls[self._url_index]["url"]
response = requests.get(url=url) # pylint: disable=missing-timeout
if response.status_code != 200:
_LOGGER.error(
f"{self._device_id}: Invalid Image: {response.status_code}: {url}"
)
return _return_default_img()
return response.content

def rotate_img(self): # pylint: disable=missing-function-docstring
_LOGGER.debug(f"{self._device_id}: Strava Image Count: {len(self._urls)}")
if len(self._urls) == 0:
return
self._url_index = (self._url_index + 1) % len(self._urls)
self.async_write_ha_state()

def img_update_handler(self, event):
"""handle new urls of Strava images"""
_LOGGER.debug(f"{self._device_id}: Received image update: {event}")
if event.data["activity_index"] != self._activity_index:
return
self._urls = event.data["img_urls"]
self._url_index = self._url_index if self._url_index < len(self._urls) else 0

async def async_added_to_hass(self):
self.hass.bus.async_listen(
EVENT_ACTIVITY_IMAGES_UPDATE, self.img_update_handler
)


class UrlCam(Camera):
Expand Down Expand Up @@ -187,3 +271,9 @@ async def async_added_to_hass(self):

async def async_will_remove_from_hass(self):
await super().async_will_remove_from_hass()


def _return_default_img():
return requests.get( # pylint: disable=unused-argument,missing-timeout
url=_DEFAULT_IMAGE_URL
).content
5 changes: 4 additions & 1 deletion custom_components/ha_strava/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
CONF_STRAVA_RELOAD_EVENT = "ha_strava_reload"
CONF_IMG_UPDATE_EVENT = "ha_strava_new_images"
CONF_IMG_ROTATE_EVENT = "ha_strava_rotate_images"

EVENT_ACTIVITIES_UPDATE = "ha_strava_activities_update"
EVENT_ACTIVITY_IMAGES_UPDATE = "ha_strava_activity_images_update"
EVENT_SUMMARY_STATS_UPDATE = "ha_strava_stats_update"

# Sensor Specs
CONF_SENSOR_ID = "id"
CONF_SENSOR_DATE = "date"
CONF_SENSOR_DURATION = "duration"
CONF_SENSOR_ACTIVITY_COUNT = "activity_count"
Expand Down
14 changes: 10 additions & 4 deletions custom_components/ha_strava/sensor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Sensor platform for HA Strava"""
import logging

# generic imports
from datetime import datetime as dt

Expand Down Expand Up @@ -34,13 +35,14 @@
CONF_SENSOR_SPEED,
CONF_SENSOR_TITLE,
CONF_SENSORS,
CONF_STRAVA_DATA_UPDATE_EVENT,
CONF_STRAVA_RELOAD_EVENT,
CONF_SUMMARY_ALL,
CONF_SUMMARY_RECENT,
CONF_SUMMARY_YTD,
DEFAULT_NB_ACTIVITIES,
DOMAIN,
EVENT_ACTIVITIES_UPDATE,
EVENT_SUMMARY_STATS_UPDATE,
FACTOR_METER_TO_FEET,
FACTOR_METER_TO_MILE,
MAX_NB_ACTIVITIES,
Expand Down Expand Up @@ -72,7 +74,11 @@ async def async_setup_entry(
CONF_SENSOR_MOVING_TIME,
CONF_SENSOR_ACTIVITY_COUNT,
]:
for summary_type in [CONF_SUMMARY_RECENT, CONF_SUMMARY_YTD, CONF_SUMMARY_ALL]:
for summary_type in [
CONF_SUMMARY_RECENT,
CONF_SUMMARY_YTD,
CONF_SUMMARY_ALL,
]:
entries.append(
StravaSummaryStatsSensor(
activity_type=activity_type,
Expand Down Expand Up @@ -202,7 +208,7 @@ def strava_data_update_event_handler(self, event):

async def async_added_to_hass(self):
self.hass.bus.async_listen(
CONF_STRAVA_DATA_UPDATE_EVENT, self.strava_data_update_event_handler
EVENT_SUMMARY_STATS_UPDATE, self.strava_data_update_event_handler
)

async def async_will_remove_from_hass(self):
Expand Down Expand Up @@ -397,7 +403,7 @@ def strava_data_update_event_handler(self, event):

async def async_added_to_hass(self):
self.hass.bus.async_listen(
CONF_STRAVA_DATA_UPDATE_EVENT, self.strava_data_update_event_handler
EVENT_ACTIVITIES_UPDATE, self.strava_data_update_event_handler
)

async def async_will_remove_from_hass(self):
Expand Down
3 changes: 2 additions & 1 deletion tools/post_create_command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

set -e

pip install pre-commit
pip install -r requirements_dev.txt
pip install -r requirements_test.txt

pre-commit install
17 changes: 17 additions & 0 deletions tools/push_remote.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

set -e

function main() {
local remote_host="$1"

echo "Pushing to: ${remote_host}"
rsync -avz -e ssh --exclude=__pycache__ \
/workspaces/ha_strava/custom_components/ha_strava/ ${remote_host}:/root/config/custom_components/ha_strava &&
echo "Restarting home assistant on ${remote_host}" &&
ssh "${remote_host}" 'source /etc/profile.d/homeassistant.sh >/dev/null && ha core restart' &&
echo "Push and Restart Complete!" \
;
}

main "$@"
3 changes: 0 additions & 3 deletions tools/setup_virtualenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@ set -e
python -m virtualenv env
source env/bin/activate

pip install -r requirements_dev.txt
pip install -r requirements_test.txt

tools/post_create_command.sh

0 comments on commit 850d993

Please sign in to comment.