Add HACS integration, 8 Lovelace cards, and pairing page
CI / Identify a merged version PR (push) Canceled after 0s
CI / Rust checks, rendering, and native package (push) Canceled after 0s
CI / Publish release and Homebrew feed (push) Canceled after 0s
Release PR / Maintain Version Packages PR (push) Canceled after 0s

New HA integration (integration/custom_components/luma_home/) with:
- WS API: luma/get_config (Lovelace→v2 bridge), luma/pair, luma/pair_status
- Config flow with dashboard URL path setting

8 HACS frontend cards (cards/) matching TV app visual spec:
- toggle, range, climate, media, curtain, action, vacuum, weather
- Glass card design: rgba(36,40,48,0.75) bg, 22px radius, accent glows

Pairing page (pairing/luma-pair.html) for TV token provisioning.
Web dashboard (web-dashboard/) — legacy config editor (deprecated).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kaloyan DanchevandClaude Opus 4.6 committed 2026-10-25 13:19:26 +02:00
1 parent a890ba6baf
commit d681f20884
18 files changed
+2708

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
const LUMA_WEATHER_VERSION = '0.1.0';
class LumaWeatherCard extends HTMLElement {
set hass(hass) { this._hass = hass; this._render(); }
setConfig(config) {
if (!config.entity) throw new Error('Entity is required');
this._config = { entity: config.entity, name: config.name || '', size: config.size || '2x2',
icon: config.icon || '', tap_action: config.tap_action || { action: 'more-info' },
hold_action: config.hold_action || { action: 'more-info' }, ...config };
this._render();
}
getCardSize() { return 4; }
static getStubConfig() { return { entity: '', name: '', size: '2x2' }; }
connectedCallback() { if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); this._render(); }
_getState() { return this._hass?.states?.[this._config?.entity]; }
_getWeatherIcon(condition) {
const map = {
'sunny': 'mdi:weather-sunny', 'clear-night': 'mdi:weather-night',
'cloudy': 'mdi:weather-cloudy', 'fog': 'mdi:weather-fog',
'partlycloudy': 'mdi:weather-partly-cloudy',
'rainy': 'mdi:weather-rainy', 'pouring': 'mdi:weather-pouring',
'snowy': 'mdi:weather-snowy', 'lightning': 'mdi:weather-lightning',
'lightning-rainy': 'mdi:weather-lightning-rainy', 'hail': 'mdi:weather-hail',
'windy': 'mdi:weather-windy', 'windy-variant': 'mdi:weather-windy-variant',
'exceptional': 'mdi:alert-circle',
};
return map[condition] || 'mdi:weather-sunny';
}
_handleClick() {
if (!this._hass || !this._config) return;
const ev = new Event('hass-more-info', { composed: true });
ev.detail = { entityId: this._config.entity };
this.dispatchEvent(ev);
}
_render() {
if (!this.shadowRoot || !this._config) return;
const s = this._getState();
const condition = s?.state || 'sunny';
const temp = s?.attributes?.temperature ?? '--';
const humidity = s?.attributes?.humidity ?? '--';
const wind = s?.attributes?.wind_speed ?? '--';
const icon = this._config.icon || this._getWeatherIcon(condition);
const conditionLabel = condition.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
this.shadowRoot.innerHTML = `
<style>
:host { display: block; }
.card {
position: relative; overflow: hidden; background: rgba(36,40,48,0.75);
border: 1px solid rgba(255,255,255,0.10); border-radius: 22px; padding: 16px;
cursor: pointer; transition: border-color .15s, box-shadow .15s;
height: 100%; min-height: 200px; display: flex; flex-direction: column;
align-items: center; justify-content: center; text-align: center;
box-shadow: 0 2px 6px rgba(0,0,0,.06);
}
.card:hover { border-color: rgba(255,255,255,0.18); box-shadow: 0 8px 28px rgba(0,0,0,.28); }
.icon { position: relative; z-index: 1; }
.icon ha-icon { --mdc-icon-size: 64px; color: #f0d080; }
.temp { font-size: 40px; font-weight: 600; letter-spacing: -0.04em; color: #fff; margin-top: 12px; position: relative; z-index: 1; }
.condition { font-size: 15px; font-weight: 400; color: rgba(232,232,232,0.60); margin-top: 4px; position: relative; z-index: 1; }
.details { display: flex; gap: 16px; margin-top: 16px; position: relative; z-index: 1; }
.detail { text-align: center; }
.detail-label { font-size: 11px; font-weight: 400; letter-spacing: 0.04em; color: rgba(232,232,232,0.45); text-transform: uppercase; }
.detail-value { font-size: 14px; font-weight: 600; color: #fff; margin-top: 2px; }
</style>
<div class="card">
<div class="icon"><ha-icon icon="${icon}"></ha-icon></div>
<div class="temp">${temp}°</div>
<div class="condition">${conditionLabel}</div>
<div class="details">
<div class="detail">
<div class="detail-label">Humidity</div>
<div class="detail-value">${humidity}%</div>
</div>
<div class="detail">
<div class="detail-label">Wind</div>
<div class="detail-value">${wind} km/h</div>
</div>
</div>
</div>`;
this.shadowRoot.querySelector('.card').addEventListener('click', () => this._handleClick());
}
}
customElements.define('luma-weather-card', LumaWeatherCard);
window.customCards = window.customCards || [];
window.customCards.push({ type: 'luma-weather-card', name: 'Luma Weather', description: 'Luma-style weather hero card with temperature and conditions.', preview: true });
console.info(`%c LUMA-WEATHER-CARD %c v${LUMA_WEATHER_VERSION} `, 'background:#f0d080;color:#111;font-weight:bold', 'background:#161a22;color:#f0d080');