Compare commits
10 Commits
305055936d
...
3c43576b57
Author | SHA1 | Date | |
---|---|---|---|
3c43576b57 | |||
bcfbefb4bf | |||
b060a4adc6 | |||
7eaa80ce37 | |||
a9c72e4ebc | |||
95af086efa | |||
208bf88539 | |||
179da1b033 | |||
2b2b121751 | |||
b5f7f5d43f |
@ -1,5 +1,7 @@
|
|||||||
# Infoscreen
|
# Infoscreen
|
||||||
|
|
||||||
|
[![Read the blog entry at c0ntroller.de](https://c0ntroller.de/img/read-blog.svg)](https://c0ntroller.de/blog/project/infoscreen)
|
||||||
|
|
||||||
This is a project for personal use. An infoscreen that shows:
|
This is a project for personal use. An infoscreen that shows:
|
||||||
- time
|
- time
|
||||||
- weather & weather radar
|
- weather & weather radar
|
||||||
|
@ -26,10 +26,11 @@ interface EventList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
||||||
const [token, setToken] = React.useState("")
|
const [calendarToken, setToken] = React.useState("")
|
||||||
const [events, setEvents] = React.useState<EventList>({})
|
const [events, setEvents] = React.useState<EventList>({})
|
||||||
|
|
||||||
const mergeEvents = (currentList: EventList, toAdd: Event[], ownerIdx: number = 0) => {
|
const mergeEvents = (currentList: EventList, toAdd: Event[], ownerIdx: number = 0) => {
|
||||||
|
if (!toAdd || toAdd.length === 0) return;
|
||||||
for(const event of toAdd) {
|
for(const event of toAdd) {
|
||||||
const startDate = new Date(event.start.date ? event.start.date : event.start.dateTime)
|
const startDate = new Date(event.start.date ? event.start.date : event.start.dateTime)
|
||||||
const startDateString = startDate.toDateString()
|
const startDateString = startDate.toDateString()
|
||||||
@ -103,45 +104,6 @@ const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*let lastDate = "";
|
|
||||||
let i = 0;
|
|
||||||
for (const event of events) {
|
|
||||||
const startDate = new Date(event.start.date ? event.start.date : event.start.dateTime)
|
|
||||||
const startDateString = `${startDate.getFullYear()}${startDate.getMonth()}${startDate.getDate()}`
|
|
||||||
if (startDateString !== lastDate) {
|
|
||||||
lastDate = startDateString;
|
|
||||||
const dayDiff = daysDifference(new Date(), startDate);
|
|
||||||
let dayDiffString: string;
|
|
||||||
switch (dayDiff) {
|
|
||||||
case 0: dayDiffString = "Heute"
|
|
||||||
break;
|
|
||||||
case 1: dayDiffString = "Morgen"
|
|
||||||
break;
|
|
||||||
default: dayDiffString = `${dayDiff} Tage`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
eventTable.push(
|
|
||||||
<tr key={startDateString}><td colSpan={2} className={styles.calendarDateHeader}>
|
|
||||||
{dowToString(startDate.getDay())}, {startDate.getDate()}. {startDate.getMonth() + 1}
|
|
||||||
<span className={styles.calendarDateHeaderSub}>({dayDiffString})</span>
|
|
||||||
</td></tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.start.date) {
|
|
||||||
eventTable.push(
|
|
||||||
<tr key={++i} className={styles.calendarEntry}><td colSpan={2}>{event.summary}</td></tr>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
eventTable.push(
|
|
||||||
<tr key={++i} className={styles.calendarEntry}>
|
|
||||||
<td>{event.summary}</td>
|
|
||||||
<td className={styles.entryTime}>{startDate.getHours()}:{startDate.getMinutes().toString().padStart(2, "0")}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return eventTable;
|
return eventTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +122,8 @@ const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
|||||||
return token.access_token;
|
return token.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pullCalendar = async (provToken?: string, calendarIndex: number = 0) => {
|
const pullCalendar = async (calendarToken: string, calendarIndex: number = 0) => {
|
||||||
const correctToken = provToken || token;
|
const correctToken = calendarToken;
|
||||||
if (!correctToken || correctToken === "") return;
|
if (!correctToken || correctToken === "") return;
|
||||||
|
|
||||||
const timeMin = new Date();
|
const timeMin = new Date();
|
||||||
@ -183,7 +145,7 @@ const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
|||||||
//setEvents([...processEventData(events.items)]);
|
//setEvents([...processEventData(events.items)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pullAll = async (provToken?: string) => {
|
const pullAll = async (provToken: string) => {
|
||||||
const eventList: EventList = {}
|
const eventList: EventList = {}
|
||||||
for(let i = 0; i < secrets.calendarIds.length; i++) {
|
for(let i = 0; i < secrets.calendarIds.length; i++) {
|
||||||
const events = await pullCalendar(provToken, i);
|
const events = await pullCalendar(provToken, i);
|
||||||
@ -194,8 +156,13 @@ const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
requestToken().then(pullAll)
|
requestToken().then(pullAll)
|
||||||
const calendarInterval = setInterval(pullAll, CALENDAR_REFRESH_INTERVAL);
|
const calendarInterval = setInterval(() => {
|
||||||
const calendarTokenInterval = setInterval(requestToken, CALENDAR_TOKEN_REFRESH_INTERVAL);
|
setToken(token => {
|
||||||
|
pullAll(token);
|
||||||
|
return token;
|
||||||
|
})
|
||||||
|
}, CALENDAR_REFRESH_INTERVAL);
|
||||||
|
const calendarTokenInterval = setInterval(requestToken.bind(this), CALENDAR_TOKEN_REFRESH_INTERVAL);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(calendarInterval);
|
clearInterval(calendarInterval);
|
||||||
|
@ -54,7 +54,7 @@ const DVB = ({ stopId }: { stopId: number }) => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
pullDepartures();
|
pullDepartures();
|
||||||
const dvbInterval = setInterval(pullDepartures, DVB_REFRESH_INTERVAL);
|
const dvbInterval = setInterval(pullDepartures.bind(this), DVB_REFRESH_INTERVAL);
|
||||||
|
|
||||||
return () => clearInterval(dvbInterval);
|
return () => clearInterval(dvbInterval);
|
||||||
}, [])
|
}, [])
|
||||||
|
60
src/components/HAssOverview.tsx
Normal file
60
src/components/HAssOverview.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { HAssStates } from "../lib/interfaces";
|
||||||
|
import * as styles from "../styles/containers/HomeAssistant.module.css";
|
||||||
|
|
||||||
|
const HASS_REFRESH_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
const HomeAssistant = ({ hassUrl, token }: { hassUrl: string, token: string }) => {
|
||||||
|
const [states, setStates] = React.useState<HAssStates>({
|
||||||
|
daniel: false,
|
||||||
|
vicki: false,
|
||||||
|
nextbikes: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchState = async (entityId: string) => {
|
||||||
|
const response = await fetch(`${hassUrl}/api/states/${entityId}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pullStates = async () => {
|
||||||
|
const daniel = await fetchState("person.daniel")
|
||||||
|
const vicki = await fetchState("person.vicki")
|
||||||
|
const nextbikes = await fetchState("sensor.nextbikes")
|
||||||
|
|
||||||
|
const location = (l: string|undefined) => { switch (l) {
|
||||||
|
case "home": return "Zuhause";
|
||||||
|
case "not_home": return "Unterwegs";
|
||||||
|
case undefined:
|
||||||
|
case "unknown": return "Unbekannt";
|
||||||
|
default: return l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStates({
|
||||||
|
daniel: location(daniel?.state),
|
||||||
|
vicki: location(vicki?.state),
|
||||||
|
nextbikes: nextbikes?.state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
pullStates()
|
||||||
|
const statesInterval = setInterval(pullStates.bind(this), HASS_REFRESH_INTERVAL);
|
||||||
|
|
||||||
|
return () => clearInterval(statesInterval);
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div className={`container ${styles.container}`}>
|
||||||
|
<span>Daniel: {states.daniel}</span>
|
||||||
|
<span>Vicki: {states.vicki}</span>
|
||||||
|
<span>NextBikes: {states.nextbikes}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeAssistant;
|
@ -15,7 +15,7 @@ const News = () => {
|
|||||||
for (const n of news) {
|
for (const n of news) {
|
||||||
if (!n.title || n.title === "") continue;
|
if (!n.title || n.title === "") continue;
|
||||||
|
|
||||||
const updated = new Date(n.updated);
|
const updated = new Date(n["dc:date"]);
|
||||||
newsTable.push(
|
newsTable.push(
|
||||||
<tr key={++i}>
|
<tr key={++i}>
|
||||||
<td>{n.title}</td>
|
<td>{n.title}</td>
|
||||||
@ -31,7 +31,7 @@ const News = () => {
|
|||||||
const updated = new Date(n.pubDate);
|
const updated = new Date(n.pubDate);
|
||||||
newsTable.splice(randTablePos, 0,
|
newsTable.splice(randTablePos, 0,
|
||||||
<tr key={n.title}>
|
<tr key={n.title}>
|
||||||
<td>{n.title}</td>
|
<td>{n.title.replace(/&/g, "&").replace(/"/g, "\"")}</td>
|
||||||
<td>{updated.getHours()}:{updated.getMinutes().toString().padStart(2, "0")}</td>
|
<td>{updated.getHours()}:{updated.getMinutes().toString().padStart(2, "0")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
@ -43,7 +43,7 @@ const News = () => {
|
|||||||
const pullNews = async () => {
|
const pullNews = async () => {
|
||||||
const xml = new XMLParser();
|
const xml = new XMLParser();
|
||||||
const response = await fetch("https://www.tagesschau.de/xml/atom/");
|
const response = await fetch("https://www.tagesschau.de/xml/atom/");
|
||||||
const feed: { title: string; updated: string; }[] = xml.parse(await response.text()).feed.entry;
|
const feed: { title: string; "dc:date": string; }[] = xml.parse(await response.text()).feed.entry;
|
||||||
|
|
||||||
// Feedburner does not allow cors but at least we get JSON
|
// Feedburner does not allow cors but at least we get JSON
|
||||||
const postResponse = await fetch("https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Ffeeds.feedburner.com%2Fblogspot%2FrkEL");
|
const postResponse = await fetch("https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Ffeeds.feedburner.com%2Fblogspot%2FrkEL");
|
||||||
@ -54,7 +54,7 @@ const News = () => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
pullNews()
|
pullNews()
|
||||||
const newsInterval = setInterval(pullNews, NEWS_REFRESH_INTERVAL);
|
const newsInterval = setInterval(pullNews.bind(this), NEWS_REFRESH_INTERVAL);
|
||||||
|
|
||||||
return () => clearInterval(newsInterval);
|
return () => clearInterval(newsInterval);
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -29,7 +29,7 @@ const PlantState = ({ hassUrl, token, plants }: { hassUrl: string, token: string
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
pullPlants()
|
pullPlants()
|
||||||
const plantInterval = setInterval(pullPlants, PLANT_REFRESH_INTERVAL);
|
const plantInterval = setInterval(pullPlants.bind(this), PLANT_REFRESH_INTERVAL);
|
||||||
|
|
||||||
return () => clearInterval(plantInterval);
|
return () => clearInterval(plantInterval);
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -67,7 +67,7 @@ const WeatherAndTime = ({ secrets }: { secrets: SecretsWeather }) => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
pullWeather()
|
pullWeather()
|
||||||
const weatherInterval = setInterval(pullWeather, WEATHER_REFRESH_INTERVAL);
|
const weatherInterval = setInterval(pullWeather.bind(this), WEATHER_REFRESH_INTERVAL);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(dateInterval);
|
clearInterval(dateInterval);
|
||||||
|
@ -41,7 +41,7 @@ export interface Event {
|
|||||||
|
|
||||||
export interface News {
|
export interface News {
|
||||||
title: string;
|
title: string;
|
||||||
updated: string;
|
"dc:date": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostillonNews {
|
export interface PostillonNews {
|
||||||
@ -73,6 +73,12 @@ export interface PlantState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HAssStates {
|
||||||
|
daniel: string|number|boolean;
|
||||||
|
vicki: string|number|boolean;
|
||||||
|
nextbikes: string|number|boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type SongInfo = {
|
export type SongInfo = {
|
||||||
playbackState: "PLAYING" | "PAUSE" | "STOPPED";
|
playbackState: "PLAYING" | "PAUSE" | "STOPPED";
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -7,6 +7,7 @@ import Spotify from "../components/Spotify";
|
|||||||
import PlantState from "../components/PlantState";
|
import PlantState from "../components/PlantState";
|
||||||
import WeatherAndTimeContainer from "../components/WeatherAndTime"
|
import WeatherAndTimeContainer from "../components/WeatherAndTime"
|
||||||
import WeatherRadar from "../components/WeatherRadar";
|
import WeatherRadar from "../components/WeatherRadar";
|
||||||
|
import HomeAssistant from "../components/HAssOverview";
|
||||||
|
|
||||||
function importAll(r) {
|
function importAll(r) {
|
||||||
return r.keys().map(r);
|
return r.keys().map(r);
|
||||||
@ -40,7 +41,7 @@ const IndexPage = () => {
|
|||||||
<WeatherRadar />
|
<WeatherRadar />
|
||||||
<News />
|
<News />
|
||||||
<DVB stopId={secrets.dvb.stopId} />
|
<DVB stopId={secrets.dvb.stopId} />
|
||||||
<Spotify mqtt={secrets.mqtt} Alternative={<PlantState hassUrl={secrets.hass.url} token={secrets.hass.token} plants={["Basilikum", "Chili"]} />} />
|
<Spotify mqtt={secrets.mqtt} Alternative={<HomeAssistant hassUrl={secrets.hass.url} token={secrets.hass.token} />} />
|
||||||
</main>)
|
</main>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,9 +37,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color0 {
|
.color0 {
|
||||||
background: rgba(255, 170, 0, 0.2);
|
background: rgba(185, 43, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color1 {
|
.color1 {
|
||||||
background: rgba(255, 0, 0, 0.2);
|
background: rgba(0, 93, 255, 0.2);
|
||||||
}
|
}
|
10
src/styles/containers/HomeAssistant.module.css
Normal file
10
src/styles/containers/HomeAssistant.module.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user