mirror of
https://github.com/sudoxnym/hacs-connectd.git
synced 2026-04-14 03:26:36 +00:00
connectd HACS integration v1.1.0
This commit is contained in:
commit
5b258b62bc
9 changed files with 674 additions and 0 deletions
88
README.md
Normal file
88
README.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# connectd home assistant integration
|
||||
|
||||
monitor your connectd daemon from home assistant.
|
||||
|
||||
## installation
|
||||
|
||||
### HACS (recommended)
|
||||
|
||||
1. open HACS in home assistant
|
||||
2. click the three dots menu → custom repositories
|
||||
3. add `https://github.com/sudoxnym/connectd` with category "integration"
|
||||
4. search for "connectd" and install
|
||||
5. restart home assistant
|
||||
6. go to settings → devices & services → add integration → connectd
|
||||
|
||||
### manual
|
||||
|
||||
1. copy `custom_components/connectd` to your HA `config/custom_components/` directory
|
||||
2. restart home assistant
|
||||
3. go to settings → devices & services → add integration → connectd
|
||||
|
||||
## configuration
|
||||
|
||||
enter the host and port of your connectd daemon:
|
||||
- **host**: IP or hostname where connectd is running (e.g., `192.168.1.8`)
|
||||
- **port**: API port (default: `8099`)
|
||||
|
||||
## sensors
|
||||
|
||||
the integration creates these sensors:
|
||||
|
||||
### stats
|
||||
- `sensor.connectd_total_humans` - total discovered humans
|
||||
- `sensor.connectd_high_score_humans` - humans with high values alignment
|
||||
- `sensor.connectd_total_matches` - total matches found
|
||||
- `sensor.connectd_total_intros` - total intro drafts
|
||||
- `sensor.connectd_sent_intros` - intros successfully sent
|
||||
- `sensor.connectd_active_builders` - active builder count
|
||||
- `sensor.connectd_lost_builders` - lost builder count
|
||||
- `sensor.connectd_recovering_builders` - recovering builder count
|
||||
- `sensor.connectd_lost_outreach_sent` - lost builder outreach count
|
||||
|
||||
### state
|
||||
- `sensor.connectd_intros_today` - intros sent today
|
||||
- `sensor.connectd_lost_intros_today` - lost builder intros today
|
||||
- `sensor.connectd_status` - daemon status (running/dry_run/stopped)
|
||||
|
||||
### per-platform
|
||||
- `sensor.connectd_github_humans`
|
||||
- `sensor.connectd_mastodon_humans`
|
||||
- `sensor.connectd_reddit_humans`
|
||||
- `sensor.connectd_lemmy_humans`
|
||||
- `sensor.connectd_discord_humans`
|
||||
- `sensor.connectd_lobsters_humans`
|
||||
|
||||
## example dashboard card
|
||||
|
||||
```yaml
|
||||
type: entities
|
||||
title: connectd
|
||||
entities:
|
||||
- entity: sensor.connectd_status
|
||||
- entity: sensor.connectd_total_humans
|
||||
- entity: sensor.connectd_intros_today
|
||||
- entity: sensor.connectd_lost_intros_today
|
||||
- entity: sensor.connectd_active_builders
|
||||
- entity: sensor.connectd_lost_builders
|
||||
```
|
||||
|
||||
## automations
|
||||
|
||||
example: notify when an intro is sent:
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
- alias: "connectd intro notification"
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: sensor.connectd_intros_today
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.to_state.state | int > trigger.from_state.state | int }}"
|
||||
action:
|
||||
- service: notify.mobile_app
|
||||
data:
|
||||
title: "connectd"
|
||||
message: "sent intro #{{ states('sensor.connectd_intros_today') }} today"
|
||||
```
|
||||
117
custom_components/connectd/__init__.py
Normal file
117
custom_components/connectd/__init__.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""connectd integration for home assistant."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import aiohttp
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "connectd"
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
SCAN_INTERVAL = timedelta(minutes=1)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""set up connectd from a config entry."""
|
||||
host = entry.data["host"]
|
||||
port = entry.data["port"]
|
||||
|
||||
coordinator = ConnectdDataUpdateCoordinator(hass, host, port)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
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."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
return unload_ok
|
||||
|
||||
|
||||
class ConnectdDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""class to manage fetching connectd data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, host: str, port: int) -> None:
|
||||
"""initialize."""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.base_url = f"http://{host}:{port}"
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""fetch data from connectd api."""
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# get stats
|
||||
async with session.get(f"{self.base_url}/api/stats") as resp:
|
||||
if resp.status != 200:
|
||||
raise UpdateFailed(f"error fetching stats: {resp.status}")
|
||||
stats = await resp.json()
|
||||
|
||||
# get state
|
||||
async with session.get(f"{self.base_url}/api/state") as resp:
|
||||
if resp.status != 200:
|
||||
raise UpdateFailed(f"error fetching state: {resp.status}")
|
||||
state = await resp.json()
|
||||
|
||||
# get priority matches (optional)
|
||||
priority_matches = {}
|
||||
try:
|
||||
async with session.get(f"{self.base_url}/api/priority_matches") as resp:
|
||||
if resp.status == 200:
|
||||
priority_matches = await resp.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# get top humans (optional)
|
||||
top_humans = {}
|
||||
try:
|
||||
async with session.get(f"{self.base_url}/api/top_humans") as resp:
|
||||
if resp.status == 200:
|
||||
top_humans = await resp.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# get user info (optional)
|
||||
user = {}
|
||||
try:
|
||||
async with session.get(f"{self.base_url}/api/user") as resp:
|
||||
if resp.status == 200:
|
||||
user = await resp.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"stats": stats,
|
||||
"state": state,
|
||||
"priority_matches": priority_matches,
|
||||
"top_humans": top_humans,
|
||||
"user": user,
|
||||
}
|
||||
|
||||
except aiohttp.ClientError as err:
|
||||
raise UpdateFailed(f"error communicating with connectd: {err}")
|
||||
except Exception as err:
|
||||
raise UpdateFailed(f"unexpected error: {err}")
|
||||
BIN
custom_components/connectd/branding/icon.png
Normal file
BIN
custom_components/connectd/branding/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
custom_components/connectd/branding/icon@2x.png
Normal file
BIN
custom_components/connectd/branding/icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
71
custom_components/connectd/config_flow.py
Normal file
71
custom_components/connectd/config_flow.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""config flow for connectd integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "connectd"
|
||||
DEFAULT_PORT = 8099
|
||||
|
||||
|
||||
class ConnectdConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""handle a config flow for connectd."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
"""handle the initial step."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
port = user_input.get(CONF_PORT, DEFAULT_PORT)
|
||||
|
||||
# test connection
|
||||
try:
|
||||
timeout = aiohttp.ClientTimeout(total=10)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
url = f"http://{host}:{port}/api/health"
|
||||
async with session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
# connection works
|
||||
await self.async_set_unique_id(f"{host}:{port}")
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"connectd ({host})",
|
||||
data={
|
||||
"host": host,
|
||||
"port": port,
|
||||
},
|
||||
)
|
||||
else:
|
||||
_LOGGER.error("connectd api returned status %s", resp.status)
|
||||
errors["base"] = "cannot_connect"
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.error("connectd connection error: %s", err)
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception as err:
|
||||
_LOGGER.exception("connectd unexpected error: %s", err)
|
||||
errors["base"] = "unknown"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default="192.168.1.8"): str,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
11
custom_components/connectd/manifest.json
Normal file
11
custom_components/connectd/manifest.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"domain": "connectd",
|
||||
"name": "connectd",
|
||||
"codeowners": ["@sudoxnym"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://github.com/sudoxnym/connectd",
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/sudoxnym/connectd/issues",
|
||||
"requirements": [],
|
||||
"version": "1.1.0"
|
||||
}
|
||||
363
custom_components/connectd/sensor.py
Normal file
363
custom_components/connectd/sensor.py
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
"""sensor platform for connectd."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DOMAIN, ConnectdDataUpdateCoordinator
|
||||
|
||||
|
||||
def get_device_info(entry_id: str, host: str) -> DeviceInfo:
|
||||
"""return device info for connectd daemon."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, entry_id)},
|
||||
name="connectd daemon",
|
||||
manufacturer="sudoxnym",
|
||||
model="connectd",
|
||||
sw_version="1.1.0",
|
||||
configuration_url=f"http://{host}:8099",
|
||||
)
|
||||
|
||||
SENSORS = [
|
||||
# stats sensors
|
||||
("total_humans", "total humans", "mdi:account-group", "stats"),
|
||||
("high_score_humans", "high score humans", "mdi:account-star", "stats"),
|
||||
("total_matches", "total matches", "mdi:handshake", "stats"),
|
||||
("total_intros", "total intros", "mdi:email-outline", "stats"),
|
||||
("sent_intros", "sent intros", "mdi:email-check", "stats"),
|
||||
("active_builders", "active builders", "mdi:hammer-wrench", "stats"),
|
||||
("lost_builders", "lost builders", "mdi:account-question", "stats"),
|
||||
("recovering_builders", "recovering builders", "mdi:account-heart", "stats"),
|
||||
("lost_outreach_sent", "lost outreach sent", "mdi:heart-pulse", "stats"),
|
||||
|
||||
# state sensors
|
||||
("intros_today", "intros today", "mdi:email-fast", "state"),
|
||||
("lost_intros_today", "lost intros today", "mdi:heart-outline", "state"),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""set up connectd sensors."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
host = entry.data.get("host", "localhost")
|
||||
device_info = get_device_info(entry.entry_id, host)
|
||||
|
||||
entities = []
|
||||
for sensor_key, name, icon, data_source in SENSORS:
|
||||
entities.append(
|
||||
ConnectdSensor(coordinator, sensor_key, name, icon, data_source, device_info)
|
||||
)
|
||||
|
||||
# add status sensor
|
||||
entities.append(ConnectdStatusSensor(coordinator, device_info))
|
||||
|
||||
# add priority matches sensor
|
||||
entities.append(ConnectdPriorityMatchesSensor(coordinator, device_info))
|
||||
|
||||
# add top humans sensor
|
||||
entities.append(ConnectdTopHumansSensor(coordinator, device_info))
|
||||
|
||||
# add countdown sensors
|
||||
entities.append(ConnectdCountdownSensor(coordinator, device_info, "scout", "mdi:radar"))
|
||||
entities.append(ConnectdCountdownSensor(coordinator, device_info, "match", "mdi:handshake"))
|
||||
entities.append(ConnectdCountdownSensor(coordinator, device_info, "intro", "mdi:email-fast"))
|
||||
|
||||
# add personal score sensor
|
||||
entities.append(ConnectdUserScoreSensor(coordinator, device_info))
|
||||
|
||||
# add platform sensors (by_platform dict)
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "github", device_info))
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "mastodon", device_info))
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "reddit", device_info))
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "lemmy", device_info))
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "discord", device_info))
|
||||
entities.append(ConnectdPlatformSensor(coordinator, "lobsters", device_info))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ConnectdSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd sensor entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ConnectdDataUpdateCoordinator,
|
||||
sensor_key: str,
|
||||
name: str,
|
||||
icon: str,
|
||||
data_source: str,
|
||||
device_info: DeviceInfo,
|
||||
) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._sensor_key = sensor_key
|
||||
self._attr_name = f"connectd {name}"
|
||||
self._attr_unique_id = f"connectd_{sensor_key}"
|
||||
self._attr_icon = icon
|
||||
self._data_source = data_source
|
||||
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return the state."""
|
||||
if self.coordinator.data:
|
||||
data = self.coordinator.data.get(self._data_source, {})
|
||||
return data.get(self._sensor_key, 0)
|
||||
return None
|
||||
|
||||
|
||||
class ConnectdStatusSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd daemon status sensor."""
|
||||
|
||||
def __init__(self, coordinator: ConnectdDataUpdateCoordinator, device_info: DeviceInfo) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = "connectd status"
|
||||
self._attr_unique_id = "connectd_status"
|
||||
self._attr_icon = "mdi:connection"
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return the state."""
|
||||
if self.coordinator.data:
|
||||
state = self.coordinator.data.get("state", {})
|
||||
if state.get("running"):
|
||||
return "running" if not state.get("dry_run") else "dry_run"
|
||||
return "stopped"
|
||||
return "unavailable"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""return extra attributes."""
|
||||
if self.coordinator.data:
|
||||
state = self.coordinator.data.get("state", {})
|
||||
return {
|
||||
"last_scout": state.get("last_scout"),
|
||||
"last_match": state.get("last_match"),
|
||||
"last_intro": state.get("last_intro"),
|
||||
"last_lost": state.get("last_lost"),
|
||||
"started_at": state.get("started_at"),
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
class ConnectdPlatformSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd per-platform sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ConnectdDataUpdateCoordinator,
|
||||
platform: str,
|
||||
device_info: DeviceInfo,
|
||||
) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._platform = platform
|
||||
self._attr_name = f"connectd {platform} humans"
|
||||
self._attr_unique_id = f"connectd_platform_{platform}"
|
||||
self._attr_icon = self._get_platform_icon(platform)
|
||||
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||
self._attr_device_info = device_info
|
||||
|
||||
def _get_platform_icon(self, platform: str) -> str:
|
||||
"""get icon for platform."""
|
||||
icons = {
|
||||
"github": "mdi:github",
|
||||
"mastodon": "mdi:mastodon",
|
||||
"reddit": "mdi:reddit",
|
||||
"lemmy": "mdi:alpha-l-circle",
|
||||
"discord": "mdi:discord",
|
||||
"lobsters": "mdi:web",
|
||||
"bluesky": "mdi:cloud",
|
||||
"matrix": "mdi:matrix",
|
||||
}
|
||||
return icons.get(platform, "mdi:web")
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return the state."""
|
||||
if self.coordinator.data:
|
||||
stats = self.coordinator.data.get("stats", {})
|
||||
by_platform = stats.get("by_platform", {})
|
||||
return by_platform.get(self._platform, 0)
|
||||
return 0
|
||||
|
||||
|
||||
class ConnectdPriorityMatchesSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd priority matches sensor."""
|
||||
|
||||
def __init__(self, coordinator: ConnectdDataUpdateCoordinator, device_info: DeviceInfo) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = "connectd priority matches"
|
||||
self._attr_unique_id = "connectd_priority_matches"
|
||||
self._attr_icon = "mdi:account-star"
|
||||
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return count of new priority matches."""
|
||||
if self.coordinator.data:
|
||||
pm = self.coordinator.data.get("priority_matches", {})
|
||||
return pm.get("new_count", 0)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""return top matches as attributes."""
|
||||
if self.coordinator.data:
|
||||
pm = self.coordinator.data.get("priority_matches", {})
|
||||
top = pm.get("top_matches", [])
|
||||
attrs = {
|
||||
"total_matches": pm.get("count", 0),
|
||||
"new_matches": pm.get("new_count", 0),
|
||||
}
|
||||
for i, m in enumerate(top[:3]):
|
||||
attrs[f"match_{i+1}_username"] = m.get("username")
|
||||
attrs[f"match_{i+1}_platform"] = m.get("platform")
|
||||
attrs[f"match_{i+1}_score"] = m.get("overlap_score")
|
||||
attrs[f"match_{i+1}_reasons"] = ", ".join(m.get("reasons", []))
|
||||
return attrs
|
||||
return {}
|
||||
|
||||
|
||||
class ConnectdTopHumansSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd top humans sensor."""
|
||||
|
||||
def __init__(self, coordinator: ConnectdDataUpdateCoordinator, device_info: DeviceInfo) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = "connectd top human"
|
||||
self._attr_unique_id = "connectd_top_human"
|
||||
self._attr_icon = "mdi:account-check"
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return top human username."""
|
||||
if self.coordinator.data:
|
||||
th = self.coordinator.data.get("top_humans", {})
|
||||
top = th.get("top_humans", [])
|
||||
if top:
|
||||
return top[0].get("username", "none")
|
||||
return "none"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""return top humans as attributes."""
|
||||
if self.coordinator.data:
|
||||
th = self.coordinator.data.get("top_humans", {})
|
||||
top = th.get("top_humans", [])
|
||||
attrs = {"total_high_score": th.get("count", 0)}
|
||||
for i, h in enumerate(top[:5]):
|
||||
attrs[f"human_{i+1}_username"] = h.get("username")
|
||||
attrs[f"human_{i+1}_platform"] = h.get("platform")
|
||||
attrs[f"human_{i+1}_score"] = h.get("score")
|
||||
attrs[f"human_{i+1}_signals"] = ", ".join(h.get("signals", [])[:3])
|
||||
attrs[f"human_{i+1}_contact"] = h.get("contact_method")
|
||||
return attrs
|
||||
return {}
|
||||
|
||||
|
||||
class ConnectdCountdownSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd countdown timer sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ConnectdDataUpdateCoordinator,
|
||||
device_info: DeviceInfo,
|
||||
cycle_type: str,
|
||||
icon: str,
|
||||
) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._cycle_type = cycle_type
|
||||
self._attr_name = f"connectd next {cycle_type}"
|
||||
self._attr_unique_id = f"connectd_countdown_{cycle_type}"
|
||||
self._attr_icon = icon
|
||||
self._attr_device_info = device_info
|
||||
self._attr_native_unit_of_measurement = "min"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return minutes until next cycle."""
|
||||
if self.coordinator.data:
|
||||
state = self.coordinator.data.get("state", {})
|
||||
secs = state.get(f"countdown_{self._cycle_type}", 0)
|
||||
return int(secs / 60)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""return detailed countdown info."""
|
||||
if self.coordinator.data:
|
||||
state = self.coordinator.data.get("state", {})
|
||||
secs = state.get(f"countdown_{self._cycle_type}", 0)
|
||||
return {
|
||||
"seconds": secs,
|
||||
"hours": round(secs / 3600, 1),
|
||||
f"last_{self._cycle_type}": state.get(f"last_{self._cycle_type}"),
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
class ConnectdUserScoreSensor(CoordinatorEntity, SensorEntity):
|
||||
"""connectd personal score sensor."""
|
||||
|
||||
def __init__(self, coordinator: ConnectdDataUpdateCoordinator, device_info: DeviceInfo) -> None:
|
||||
"""initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = "connectd my score"
|
||||
self._attr_unique_id = "connectd_user_score"
|
||||
self._attr_icon = "mdi:star-circle"
|
||||
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""return user's personal score."""
|
||||
if self.coordinator.data:
|
||||
user = self.coordinator.data.get("user", {})
|
||||
return user.get("score", 0)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""return user profile details."""
|
||||
if self.coordinator.data:
|
||||
user = self.coordinator.data.get("user", {})
|
||||
signals = user.get("signals", [])
|
||||
interests = user.get("interests", [])
|
||||
return {
|
||||
"configured": user.get("configured", False),
|
||||
"name": user.get("name"),
|
||||
"github": user.get("github"),
|
||||
"mastodon": user.get("mastodon"),
|
||||
"reddit": user.get("reddit"),
|
||||
"lobsters": user.get("lobsters"),
|
||||
"matrix": user.get("matrix"),
|
||||
"lemmy": user.get("lemmy"),
|
||||
"discord": user.get("discord"),
|
||||
"bluesky": user.get("bluesky"),
|
||||
"location": user.get("location"),
|
||||
"bio": user.get("bio"),
|
||||
"match_count": user.get("match_count", 0),
|
||||
"new_matches": user.get("new_match_count", 0),
|
||||
"signals": ", ".join(signals[:5]) if signals else "",
|
||||
"interests": ", ".join(interests[:5]) if interests else "",
|
||||
}
|
||||
return {}
|
||||
18
custom_components/connectd/strings.json
Normal file
18
custom_components/connectd/strings.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "connectd daemon",
|
||||
"description": "connect to your connectd daemon for monitoring.",
|
||||
"data": {
|
||||
"host": "host",
|
||||
"port": "port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "failed to connect to connectd api",
|
||||
"unknown": "unexpected error"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
hacs.json
Normal file
6
hacs.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "connectd",
|
||||
"render_readme": true,
|
||||
"domains": ["sensor"],
|
||||
"homeassistant": "2023.1.0"
|
||||
}
|
||||
Loading…
Reference in a new issue