const DEFAULT_ENTITIES = {
roof: "cover.sternwarte_dach",
power: "switch.sternwarte_dachsteuerung",
roofClosed: "binary_sensor.sternwarte_dach_zu",
roofOpen: "binary_sensor.sternwarte_dach_offen",
roofMoving: "binary_sensor.sternwarte_dach_bewegt",
roofStatus: "sensor.sternwarte_dachstatus",
roofPosition: "sensor.sternwarte_dachposition",
rain: "binary_sensor.sternwarte_regen",
wind: "sensor.sternwarte_windgeschwindigkeit",
scheduleOpen: "datetime.sternwarte_offnen_geplant",
scheduleClose: "datetime.sternwarte_schliessen_geplant",
astroLat: 50.2984230,
astroLon: 11.0481766,
};
class SternwarteCard extends HTMLElement {
constructor() {
super();
this._scheduleInputState = {
open: { draft: "", editing: false, pending: false },
close: { draft: "", editing: false, pending: false },
};
this._pendingRender = false;
}
connectedCallback() {
this._ensureClockTimer();
}
disconnectedCallback() {
if (this._clockTimer) {
window.clearInterval(this._clockTimer);
this._clockTimer = null;
}
}
setConfig(config) {
this.config = { ...DEFAULT_ENTITIES, ...config };
if (!this.shadowRoot) {
this.attachShadow({ mode: "open" });
}
}
set hass(hass) {
this._hass = hass;
this._ensureClockTimer();
this._requestRender();
}
getCardSize() {
return 8;
}
getGridOptions() {
return {
rows: 8,
columns: 18,
min_rows: 7,
};
}
_state(entityId) {
return this._hass?.states?.[entityId];
}
_ensureClockTimer() {
if (this._clockTimer) return;
this._clockTimer = window.setInterval(() => {
if (this._hass) this._requestRender();
}, 30000);
}
_requestRender() {
if (this._isScheduleEditing()) {
this._pendingRender = true;
return;
}
this._pendingRender = false;
this.render();
}
_isScheduleEditing() {
return Object.values(this._scheduleInputState).some((field) => field.editing);
}
_scheduleFieldState(key) {
return this._scheduleInputState[key];
}
_scheduleInputValue(key, fallbackState) {
const field = this._scheduleFieldState(key);
if (field.draft) return field.draft;
return this._isoForInput(fallbackState);
}
_resetScheduleDrafts() {
for (const field of Object.values(this._scheduleInputState)) {
field.draft = "";
field.editing = false;
field.pending = false;
}
}
_beginScheduleEdit(key, fallbackState) {
const field = this._scheduleFieldState(key);
field.editing = true;
field.pending = false;
if (!field.draft) {
field.draft = this._isoForInput(fallbackState);
}
}
_updateScheduleDraft(key, value) {
const field = this._scheduleFieldState(key);
field.draft = value;
field.pending = false;
}
_endScheduleEdit(key, event) {
const field = this._scheduleFieldState(key);
field.editing = false;
if (!this._isScheduleEditing() && this._pendingRender) {
this._requestRender();
}
}
_bindScheduleInput(key, inputId, fallbackState) {
const input = this.shadowRoot.getElementById(inputId);
if (!input) return;
input.addEventListener("focus", () => this._beginScheduleEdit(key, fallbackState));
input.addEventListener("input", (event) => this._updateScheduleDraft(key, event.target.value));
input.addEventListener("change", (event) => this._updateScheduleDraft(key, event.target.value));
input.addEventListener("blur", (event) => this._endScheduleEdit(key, event));
}
_firstExistingEntityId(entityIds) {
for (const entityId of entityIds) {
if (entityId && this._state(entityId)) return entityId;
}
return entityIds.find(Boolean) || null;
}
_scheduleOpenEntityId() {
return this._firstExistingEntityId([
this.config.scheduleOpen,
"datetime.sternwarte_offnen_geplant",
"datetime.sternwarte_oeffnen_geplant",
]);
}
_scheduleCloseEntityId() {
return this._firstExistingEntityId([
this.config.scheduleClose,
"datetime.sternwarte_schliessen_geplant",
]);
}
_powerEntityId() {
const configured = this.config?.power;
if (configured && this._state(configured)) {
return configured;
}
const states = this._hass?.states || {};
const fallback = Object.keys(states).find((entityId) => {
if (!entityId.startsWith("switch.")) return false;
const normalized = entityId.toLowerCase();
const friendlyName = String(states[entityId]?.attributes?.friendly_name || "").toLowerCase();
return (
normalized.includes("dachsteuer")
|| (normalized.includes("sternwarte") && normalized.includes("power"))
|| friendlyName.includes("dachsteuerung")
);
});
return fallback || configured || "switch.sternwarte_dachsteuerung";
}
async _call(domain, service) {
try {
const result = this._hass.callService(domain, service, {});
Promise.resolve(result).catch(() => {});
} catch (error) {}
}
async _callEntity(domain, service, entityId) {
if (!entityId) return;
try {
const result = this._hass.callService(domain, service, { entity_id: entityId });
Promise.resolve(result).catch(() => {});
} catch (error) {}
}
_bindClick(id, handler) {
this.shadowRoot.getElementById(id)?.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
handler();
});
}
_showMoreInfo(entityId) {
if (!entityId) return;
const event = new Event("hass-more-info", {
bubbles: true,
composed: true,
});
event.detail = { entityId };
this.dispatchEvent(event);
}
_bindMoreInfo(id, entityId) {
const element = this.shadowRoot.getElementById(id);
if (!element || !entityId) return;
const openMoreInfo = (event) => {
event.preventDefault();
event.stopPropagation();
this._showMoreInfo(entityId);
};
element.addEventListener("click", openMoreInfo);
element.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
openMoreInfo(event);
}
});
}
_setDateTime(entityId, inputId, key) {
const input = this.shadowRoot.getElementById(inputId);
const field = this._scheduleFieldState(key);
const value = field?.draft || input?.value || "";
if (!value) return;
if (!entityId) return;
field.pending = true;
field.editing = false;
field.draft = value;
this._hass.callService("datetime", "set_value", {
entity_id: entityId,
datetime: value,
});
if (this._pendingRender) {
this._requestRender();
}
}
_isoForInput(state) {
if (!state || !state.state || state.state === "unknown" || state.state === "unavailable") {
return "";
}
const value = new Date(state.state);
if (Number.isNaN(value.getTime())) return "";
const pad = (n) => String(n).padStart(2, "0");
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`;
}
_formatDateTime(state) {
if (!state || !state.state || state.state === "unknown" || state.state === "unavailable") {
return "\u2014";
}
const value = new Date(state.state);
if (Number.isNaN(value.getTime())) return "\u2014";
const pad = (n) => String(n).padStart(2, "0");
return `${pad(value.getDate())}.${pad(value.getMonth() + 1)}. ${pad(value.getHours())}:${pad(value.getMinutes())}`;
}
_roofStatusText() {
const status = this._state(this.config.roofStatus)?.state;
switch (status) {
case "offen":
return "Offen";
case "geschlossen":
return "Geschlossen";
case "in_bewegung":
return "In Bewegung";
case "sensorfehler":
return "Sensorfehler";
case "unbekannt":
return "Unbekannt";
default:
return this._labelForRoof();
}
}
_roofPositionValue() {
const value = Number(this._state(this.config.roofPosition)?.state);
if (Number.isNaN(value)) return null;
return Math.max(0, Math.min(100, Math.round(value)));
}
_labelForRoof() {
const roof = this._state(this.config.roof);
const moving = this._state(this.config.roofMoving);
const closed = this._state(this.config.roofClosed);
const open = this._state(this.config.roofOpen);
if (moving?.state === "on") return "In Bewegung";
if (open?.state === "on" && closed?.state !== "on") return "Offen";
if (closed?.state === "on" && open?.state !== "on") return "Geschlossen";
if (roof?.state === "opening") return "\u00d6ffnet";
if (roof?.state === "closing") return "Schlie\u00dft";
return "Unbekannt";
}
_boolBadge(label, entityId, onText = "Aktiv", offText = "Aus", extraClass = "") {
const state = this._state(entityId);
const on = state?.state === "on";
return `
${label}
${on ? onText : offText}
`;
}
_openBlockedReason() {
const rain = this._state(this.config.rain)?.state === "on";
const moving = this._state(this.config.roofMoving)?.state === "on";
const open = this._state(this.config.roofOpen)?.state === "on";
if (rain) return "\u00d6ffnen gesperrt: Regen erkannt";
if (moving) return "\u00d6ffnen gesperrt: Dach ist in Bewegung";
if (open) return "Dach ist bereits offen";
return "";
}
_powerIndicatorState() {
const state = this._state(this._powerEntityId())?.state;
if (state === "on") {
return {
className: "on",
label: "Ein",
lightStyle: "background:#4caf50; box-shadow:0 0 12px rgba(76, 175, 80, 0.45);",
};
}
if (state === "off") {
return {
className: "off",
label: "Aus",
lightStyle: "background:#c62828; box-shadow:0 0 10px rgba(198, 40, 40, 0.25);",
};
}
return {
className: "unknown",
label: "?",
lightStyle: "background:#d1a94a; box-shadow:0 0 10px rgba(209, 169, 74, 0.3);",
};
}
_windIndicatorState() {
const wind = this._state(this.config.wind);
const value = Number(wind?.state);
const display = wind?.state && wind.state !== "unknown" && wind.state !== "unavailable"
? `${wind.state} ${wind.attributes?.unit_of_measurement ?? "km/h"}`
: "\u2014";
if (Number.isNaN(value)) {
return {
display,
className: "",
};
}
if (value <= 10) {
return {
display,
className: "wind-good",
};
}
if (value <= 15) {
return {
display,
className: "wind-warn",
};
}
return {
display,
className: "wind-danger",
};
}
_isMESZ(date) {
const year = date.getUTCFullYear();
const lastSunday = (month, hour) => {
const event = new Date(Date.UTC(year, month + 1, 0, hour));
while (event.getUTCDay() !== 0) event.setUTCDate(event.getUTCDate() - 1);
return event;
};
return date >= lastSunday(2, 1) && date < lastSunday(9, 1);
}
_calcTwilight(date) {
const lat = Number(this.config.astroLat);
const lon = Number(this.config.astroLon);
const jd = date.getTime() / 86400000 + 2440587.5;
const n = jd - 2451545.0;
const L = ((280.460 + 0.9856474 * n) % 360 + 360) % 360;
const g = ((357.528 + 0.9856003 * n) % 360 + 360) % 360;
const gR = g * Math.PI / 180;
const lam = L + 1.915 * Math.sin(gR) + 0.020 * Math.sin(2 * gR);
const lamR = lam * Math.PI / 180;
const eps = 23.439 - 0.0000004 * n;
const epsR = eps * Math.PI / 180;
const dec = Math.asin(Math.sin(epsR) * Math.sin(lamR));
const RA = Math.atan2(Math.cos(epsR) * Math.sin(lamR), Math.cos(lamR)) * 180 / Math.PI / 15;
const GMST0h = ((L + 180) % 360 + 360) % 360 / 15;
const solarNoon = (12 - lon / 15 - (RA - GMST0h + 12 + 720) % 24 + 24) % 24;
const latR = lat * Math.PI / 180;
const eventUTC = (depDeg) => {
const cosH = (Math.sin(-depDeg * Math.PI / 180) - Math.sin(latR) * Math.sin(dec))
/ (Math.cos(latR) * Math.cos(dec));
if (cosH < -1 || cosH > 1) return null;
const H = Math.acos(cosH) * 180 / Math.PI / 15;
return { dawn: (solarNoon - H + 24) % 24, dusk: (solarNoon + H) % 24 };
};
return {
sun: eventUTC(0.833),
civil: eventUTC(6),
naut: eventUTC(12),
astro: eventUTC(18),
};
}
_julianDate(date) {
return date.getTime() / 86400000 + 2440587.5;
}
_gmstHours(jd) {
const T = (jd - 2451545) / 36525;
const gmst = 280.46061837 + 360.98564736629 * (jd - 2451545) + 0.000387933 * T * T
- (T * T * T) / 38710000;
return (((gmst % 360) + 360) % 360) / 15;
}
_moonStateAt(date) {
const jd = this._julianDate(date);
const d = jd - 2451543.5;
const lat = Number(this.config.astroLat) * Math.PI / 180;
const lonHours = Number(this.config.astroLon) / 15;
const toRad = (deg) => deg * Math.PI / 180;
const normalizeDeg = (deg) => ((deg % 360) + 360) % 360;
const sunMeanLongitude = normalizeDeg(280.460 + 0.9856474 * (jd - 2451545.0));
const sunMeanAnomaly = normalizeDeg(357.528 + 0.9856003 * (jd - 2451545.0));
const sunLongitude = normalizeDeg(
sunMeanLongitude + 1.915 * Math.sin(toRad(sunMeanAnomaly)) + 0.020 * Math.sin(2 * toRad(sunMeanAnomaly)),
);
const N = normalizeDeg(125.1228 - 0.0529538083 * d);
const i = toRad(5.1454);
const w = normalizeDeg(318.0634 + 0.1643573223 * d);
const a = 60.2666;
const e = 0.0549;
const M = normalizeDeg(115.3654 + 13.0649929509 * d);
const E = M + (180 / Math.PI) * e * Math.sin(toRad(M)) * (1 + e * Math.cos(toRad(M)));
const xv = a * (Math.cos(toRad(E)) - e);
const yv = a * (Math.sqrt(1 - e * e) * Math.sin(toRad(E)));
const v = Math.atan2(yv, xv);
const r = Math.sqrt(xv * xv + yv * yv);
const xh = r * (Math.cos(toRad(N)) * Math.cos(v + toRad(w)) - Math.sin(toRad(N)) * Math.sin(v + toRad(w)) * Math.cos(i));
const yh = r * (Math.sin(toRad(N)) * Math.cos(v + toRad(w)) + Math.cos(toRad(N)) * Math.sin(v + toRad(w)) * Math.cos(i));
const zh = r * Math.sin(v + toRad(w)) * Math.sin(i);
const moonLon = Math.atan2(yh, xh);
const moonLat = Math.atan2(zh, Math.sqrt(xh * xh + yh * yh));
const obliquity = toRad(23.4393 - 0.0000004 * d);
const xe = r * Math.cos(moonLon) * Math.cos(moonLat);
const ye = r * Math.sin(moonLon) * Math.cos(moonLat);
const ze = r * Math.sin(moonLat);
const xeq = xe;
const yeq = ye * Math.cos(obliquity) - ze * Math.sin(obliquity);
const zeq = ye * Math.sin(obliquity) + ze * Math.cos(obliquity);
const raHours = (((Math.atan2(yeq, xeq) * 180 / Math.PI) / 15) + 24) % 24;
const dec = Math.atan2(zeq, Math.sqrt(xeq * xeq + yeq * yeq));
const lstHours = (this._gmstHours(jd) + lonHours + 24) % 24;
const hourAngle = toRad((lstHours - raHours) * 15);
const altitude = Math.asin(
Math.sin(dec) * Math.sin(lat) + Math.cos(dec) * Math.cos(lat) * Math.cos(hourAngle),
);
const elongation = Math.acos(
Math.cos(moonLat) * Math.cos(moonLon - toRad(sunLongitude)),
);
const illumination = Math.max(0, Math.min(1, (1 - Math.cos(elongation)) / 2));
return { altitude, illumination };
}
_moonTrackState() {
const now = new Date();
const cycleStart = new Date(now);
cycleStart.setHours(12, 0, 0, 0);
if (now.getHours() < 12) {
cycleStart.setDate(cycleStart.getDate() - 1);
}
const stepMinutes = 10;
const totalSteps = Math.round((24 * 60) / stepMinutes);
const segments = [];
let segmentStart = null;
let currentStep = 0;
const currentMoon = this._moonStateAt(now);
for (; currentStep <= totalSteps; currentStep += 1) {
const sample = new Date(cycleStart.getTime() + currentStep * stepMinutes * 60000);
const visible = this._moonStateAt(sample).altitude > 0;
if (visible && segmentStart === null) {
segmentStart = currentStep;
}
if ((!visible || currentStep === totalSteps) && segmentStart !== null) {
const endStep = visible && currentStep === totalSteps ? currentStep : currentStep - 1;
const startHour = (segmentStart * stepMinutes) / 60;
const endHour = ((endStep + 1) * stepMinutes) / 60;
segments.push({
left: `${(startHour / 24) * 100}%`,
width: `${Math.max(((endHour - startHour) / 24) * 100, 0.5)}%`,
});
segmentStart = null;
}
}
return {
opacity: (0.18 + currentMoon.illumination * 0.82).toFixed(2),
segments,
};
}
_formatHourValue(hourValue) {
if (hourValue === null || hourValue === undefined) return "\u2014";
const hours = ((hourValue % 24) + 24) % 24;
const h = Math.floor(hours);
const m = Math.floor((hours - h) * 60);
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}
_astroState() {
const now = new Date();
const tw = this._calcTwilight(now);
const offset = this._isMESZ(now) ? 2 : 1;
const shiftHour = (hourValue) => ((hourValue - 12 + 24) % 24);
const localize = (event) => event ? {
dawn: (event.dawn + offset + 24) % 24,
dusk: (event.dusk + offset + 24) % 24,
} : null;
const localTw = {
sun: localize(tw.sun),
civil: localize(tw.civil),
naut: localize(tw.naut),
astro: localize(tw.astro),
};
const nowLocal = shiftHour(now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600);
const segments = [];
const colors = {
night: "#16202f",
astro: "#2a4978",
naut: "#4a76bf",
civil: "#c68c50",
day: "#eed06f",
};
const addSegment = (start, end, className) => {
if (start === null || end === null) return;
const shiftedStart = shiftHour(start);
const shiftedEnd = shiftHour(end);
if (shiftedEnd < shiftedStart) {
segments.push({
start: shiftedStart,
end: 24,
color: colors[className],
});
segments.push({
start: 0,
end: shiftedEnd,
color: colors[className],
});
return;
}
if (shiftedEnd <= shiftedStart) return;
segments.push({
start: shiftedStart,
end: shiftedEnd,
color: colors[className],
});
};
addSegment(0, localTw.astro?.dawn ?? null, "night");
addSegment(localTw.astro?.dawn ?? null, localTw.naut?.dawn ?? null, "astro");
addSegment(localTw.naut?.dawn ?? null, localTw.civil?.dawn ?? null, "naut");
addSegment(localTw.civil?.dawn ?? null, localTw.sun?.dawn ?? null, "civil");
addSegment(localTw.sun?.dawn ?? null, localTw.sun?.dusk ?? null, "day");
addSegment(localTw.sun?.dusk ?? null, localTw.civil?.dusk ?? null, "civil");
addSegment(localTw.civil?.dusk ?? null, localTw.naut?.dusk ?? null, "naut");
addSegment(localTw.naut?.dusk ?? null, localTw.astro?.dusk ?? null, "astro");
addSegment(localTw.astro?.dusk ?? null, 24, "night");
if (!segments.length) addSegment(0, 24, "night");
segments.sort((a, b) => a.start - b.start);
const gradientStops = segments.flatMap((segment) => {
const start = `${(segment.start / 24) * 100}%`;
const end = `${(segment.end / 24) * 100}%`;
return [`${segment.color} ${start}`, `${segment.color} ${end}`];
});
const gradient = gradientStops.length
? `linear-gradient(90deg, ${gradientStops.join(", ")})`
: colors.night;
const toLocalHour = (state) => {
if (!state?.state || state.state === "unknown" || state.state === "unavailable") return null;
const value = new Date(state.state);
if (Number.isNaN(value.getTime())) return null;
return shiftHour(value.getHours() + value.getMinutes() / 60);
};
return {
now: now.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }),
gradient,
markerLeft: `${(nowLocal / 24) * 100}%`,
openMarkerLeft: toLocalHour(this._state(this._scheduleOpenEntityId())),
closeMarkerLeft: toLocalHour(this._state(this._scheduleCloseEntityId())),
};
}
render() {
if (!this.shadowRoot || !this._hass) return;
const powerEntityId = this._powerEntityId();
const powerIndicator = this._powerIndicatorState();
const rain = this._state(this.config.rain);
const openScheduleEntityId = this._scheduleOpenEntityId();
const closeScheduleEntityId = this._scheduleCloseEntityId();
const openSchedule = this._state(openScheduleEntityId);
const closeSchedule = this._state(closeScheduleEntityId);
const openInputValue = this._scheduleInputValue("open", openSchedule);
const closeInputValue = this._scheduleInputValue("close", closeSchedule);
if (openInputValue === this._isoForInput(openSchedule)) {
this._scheduleFieldState("open").draft = "";
this._scheduleFieldState("open").pending = false;
}
if (closeInputValue === this._isoForInput(closeSchedule)) {
this._scheduleFieldState("close").draft = "";
this._scheduleFieldState("close").pending = false;
}
const roofPosition = this._roofPositionValue();
const windIndicator = this._windIndicatorState();
const rainOn = rain?.state === "on";
const movingOn = this._state(this.config.roofMoving)?.state === "on";
const openOn = this._state(this.config.roofOpen)?.state === "on";
const closedOn = this._state(this.config.roofClosed)?.state === "on";
const openBlockedReason = this._openBlockedReason();
const powerOn = this._state(powerEntityId)?.state === "on";
let openButtonDisabled;
let closeButtonDisabled;
let stopButtonDisabled;
if (!powerOn) {
openButtonDisabled = true;
closeButtonDisabled = true;
stopButtonDisabled = true;
} else if (movingOn) {
openButtonDisabled = true;
closeButtonDisabled = true;
stopButtonDisabled = false;
} else if (openOn && !closedOn) {
openButtonDisabled = true;
closeButtonDisabled = false;
stopButtonDisabled = true;
} else if (closedOn && !openOn) {
openButtonDisabled = false;
closeButtonDisabled = true;
stopButtonDisabled = true;
} else if (closedOn && openOn) {
openButtonDisabled = true;
closeButtonDisabled = true;
stopButtonDisabled = false;
} else {
openButtonDisabled = false;
closeButtonDisabled = false;
stopButtonDisabled = false;
}
const astro = this._astroState();
const moonTrack = this._moonTrackState();
this.shadowRoot.innerHTML = `
${rainOn ? `
Regen erkanntDas Dach wird bei Regen nicht \u00f6ffnet und bei Bedarf automatisch geschlossen.
` : ""}
\u00d6ffnen geplant
${this._formatDateTime(openSchedule)}
Schlie\u00dfen geplant
${this._formatDateTime(closeSchedule)}
Wind
${windIndicator.display}
${this._boolBadge("Dach zu", this.config.roofClosed, "Ja", "Nein")}
${this._boolBadge("Dach offen", this.config.roofOpen, "Ja", "Nein")}
${this._boolBadge("Dach bewegt", this.config.roofMoving, "Ja", "Nein", "moving")}
${this._boolBadge("Regen", this.config.rain, "Ja", "Nein", "rain")}
12
18
0
6
12
${moonTrack.segments.map((segment) => `
`).join("")}
${astro.openMarkerLeft === null ? "" : `
`}
${astro.closeMarkerLeft === null ? "" : `
`}
Dachposition
${roofPosition === null ? "\u2014" : `${roofPosition} %`}
Steuerung
${powerIndicator.label}
${openBlockedReason ? `
${openBlockedReason}
` : ""}
Zeitplanung
\u00d6ffnen geplant
${this._formatDateTime(openSchedule)}
Schlie\u00dfen geplant
${this._formatDateTime(closeSchedule)}
`;
this._bindClick("open-btn", () => this._callEntity("cover", "open_cover", this.config.roof));
this._bindClick("stop-btn", () => this._callEntity("cover", "stop_cover", this.config.roof));
this._bindClick("close-btn", () => this._callEntity("cover", "close_cover", this.config.roof));
this._bindClick("power-on-btn", () => this._callEntity("switch", "turn_on", powerEntityId));
this._bindClick("power-off-btn", () => this._callEntity("switch", "turn_off", powerEntityId));
this._bindClick("nautical-btn", () => this._call("sternwarte", "set_nautical_schedule"));
this._bindClick("clear-schedule-btn", () => {
this._resetScheduleDrafts();
this._call("sternwarte", "clear_schedule");
this._requestRender();
});
this._bindClick("open-schedule-btn", () => this._setDateTime(openScheduleEntityId, "open-schedule-input", "open"));
this._bindClick("close-schedule-btn", () => this._setDateTime(closeScheduleEntityId, "close-schedule-input", "close"));
this._bindMoreInfo("wind-stat", this.config.wind);
this._bindScheduleInput("open", "open-schedule-input", openSchedule);
this._bindScheduleInput("close", "close-schedule-input", closeSchedule);
}
}
if (!customElements.get("sternwarte-card")) {
customElements.define("sternwarte-card", SternwarteCard);
}
window.customCards = window.customCards || [];
if (!window.customCards.some((card) => card?.type === "sternwarte-card")) {
window.customCards.push({
type: "sternwarte-card",
name: "Sternwarte Card",
description: "Eigene Card f\u00fcr die Sternwarte",
});
}