Shabbos Board (Shabbat board)
A full-screen, bilingual Hebrew/English board for a kitchen TV (designed for 1920x1080, works in any browser), plus an optional daemon that casts it to a Google Cast TV. It is made for Yom Tov / Yamim Tovim as well as Shabbos: holiday blocks take over the board for each chag, multi-day Yom Tov and Yom Tov running into Shabbos count as one period, and the Israel or diaspora schedule is a config setting.
Accuracy: check before you rely on it. Its times are known to be a minute or two off from shul calendars, which use their own calculations, elevation and rounding. Use it only after checking that it matches your own synagogue's calendar for your location (see Calibration), and always follow your shul's published times and your rabbi.
On the board
- Parasha, Hebrew and Gregorian date, a large clock
- Zmanim rows you choose (latest Shema, chatzot, sunset, havdalah, ...)
- Your shul's minyan times, fixed or relative to candle lighting, sunset or havdalah
- Times that have passed dim one by one
- Optional holiday schedule (
holidays.json) that takes over the board on the days it covers - Weather from Open-Meteo, with a dew-point comfort band
- Optional Hebrew word of the hour from your own word list
- Israel only, optional: Home Front Command alert banner with a soft chime
Why a server at all. A TV stick often has no battery clock and can come up with the wrong date. A screen that is a week out draws a completely plausible board for the wrong Shabbat. The server, whose clock is kept by NTP, decides which Shabbat it is and tells the page the correct time; the page shows a warning banner if the screen's own clock is more than ten minutes out.
Files
| File | Purpose |
|---|---|
board_server.py | Web server: page, /zmanim.json, /config.json, media with byte ranges |
cast_daemon.py | Optional: casts the board and your media to a Google Cast TV |
castv2.py | Minimal Google Cast sender (standard library only) |
shabbat_common.py | Configuration, Hebcal calls |
static/board.html | The board |
config.example.json | Annotated configuration |
holidays.example.json | Holiday schedule schema with fictional example times |
words.example.json | Word-list format with three sample entries |
systemd/ | User units for the server and the daemon |
Requirements
- Python 3.9+, internet access to hebcal.com.
- An always-on device on your local network to run the server: a computer that does not sleep, a home file server/NAS, or a Raspberry Pi or similar mini-PC. The TV or browser points at it (
http://<device>:8760/), and casting to the TV also happens from it. Disable sleep/suspend and automatic updates or reboots over Shabbos and Yom Tov, keep it on wired power, and enable the systemd units below so it starts at boot. Set it up and test it before Shabbos.
Install
mkdir -p ~/.local/share/shabbat-tools ~/.config/shabbat-tools
cp -r board ~/.local/share/shabbat-tools/
cp board/config.example.json ~/.config/shabbat-tools/board.json
$EDITOR ~/.config/shabbat-tools/board.json
python3 ~/.local/share/shabbat-tools/board/board_server.py
# open http://127.0.0.1:8760/
Until the location section is filled in, the server prints what is missing and exits. Then open http://<device>:8760/setup (or run board_server.py --calibrate) and enter this week's times from your shul calendar.
Calibration
The board computes future weeks from one week of your shul's own calendar:
- Open
/setupfrom a computer or phone on your network, or runpython3 board_server.py --calibrate. - Enter this Shabbos's (or erev Yom Tov's) date, candle lighting and havdalah / Motzei Shabbos from the shul calendar. Optionally add the printed shkia, Friday Mincha, Shabbos Mincha and Shacharis times.
- The server fetches Hebcal's sunset for those dates at your location and stores the differences, e.g. "your shul: candle lighting 22 min before sunset", Friday Mincha as
candles+12, Shabbos Mincha assunset-30, Shacharis as fixed times. The config is written atomically and the previous version kept asboard.json.bak-<timestamp>.
The board then shows "Calibrated from Shabbos <date> (<parasha>)" in small print.
Inputs are checked: the date must be a Friday or a day Hebcal lists candle lighting for, candle lighting must be 0-60 minutes before sunset and havdalah 15-90 minutes after it.
Scripted use:
python3 board_server.py --calibrate --non-interactive --friday 2030-01-04 \
--candles 16:28 --havdalah 17:30 --fri-mincha 16:40 --shabbat-mincha 16:20 --shacharit "7:00,8:30"
Protect the setup page. It answers only loopback and private (LAN) addresses. Set setup.pin (or setup.pin_env) so people who can see the TV cannot change the times, or setup.enabled: false once you are done.
Offsets drift. A fixed number of minutes from sunset is an approximation of how a luach is calculated, and the gap changes slightly across the year (and the luach may use elevation or its own rounding). Re-check against the shul calendar when the clocks change and every few months, and calibrate again if it has moved.
Defaults before calibration
Until you calibrate, the board shows times from general defaults, with a banner saying they have not been checked against your shul: candle lighting 18 minutes before sunset outside Israel, 40 minutes in Jerusalem (set location.jerusalem: true; it is never guessed from coordinates) and 15 minutes elsewhere in Israel; havdalah 50 minutes after sunset. Communities differ (some Israeli cities use 20 or 30 minutes, for example), which is exactly why the shul's schedule is what counts: calibration always overrides these defaults. The setup page pre-fills them so you can see the difference when you type in your shul's times.
Location
"location": {
"name_en": "Jerusalem", "name_he": "ירושלים",
"latitude": 31.778, "longitude": 35.235,
"tzid": "Asia/Jerusalem",
"israel": true
}
(Jerusalem is only an illustration.) Instead of coordinates you can give a GeoNames geonameid; weather is then disabled because it needs coordinates. israel: false selects the diaspora holiday schedule.
times.candle_minutes (minutes before sunset) and times.havdalah_minutes (minutes after sunset) are normally written by calibration; leave them null until then. Hebcal treats b=18 as "use the default", which in Israel silently becomes 20; the server works around this, so 18 means 18.
Rows and time formats
zmanim_rows and minyanim.rows are lists of {"he", "en", "time" or "times", "hot"}. hot: true highlights the row. A time is one of:
| Format | Meaning |
|---|---|
"8:00" | that time on Shabbat day (Saturday); printed exactly as written |
"fri 19:30" | that time on Friday |
"candles", "candles+15" | candle lighting, plus or minus whole minutes |
"sunset-30" | Saturday sunset minus 30 minutes |
"havdalah", "fri_sunset", "chatzot", "sofZmanShma", "sofZmanShmaMGA", "minchaGedola", "plagHaMincha", "sunrise", "tzeit42min", "tzeit72min" | Hebcal zmanim for Shabbat day |
Set minyanim.rows to [] to hide the minyanim column.
Holiday schedule
Set "holidays_file": "holidays.json" (relative to the config file) to show a typed-in schedule for Yom Tov, fast days and similar. The schema is documented inside holidays.example.json; all names and times there are fictional. Blocks take over the board between their from and to times. Preview any moment with http://127.0.0.1:8760/?at=2099-09-18T09:00.
Word of the hour
Set "words_file" to a JSON list of {"word", "translit", "gloss", "example", "example_translit", "example_translation"} objects. words.example.json has three entries; the same format is used by cosmic-tools panel-bar word lists. Eight words rotate hourly for a week, then the next eight, so each word is seen many times, spaced out.
Alerts (Israel)
alerts.enabled: true proxies the Home Front Command feed through /alerts.json. The feed normally answers only Israeli IP addresses. home_area_he is your area's name exactly as the feed spells it; an alert naming it shows a red banner, alerts elsewhere an amber one. Test with ?testalert=here or ?testalert=other. This is a convenience display, not a replacement for the official app or sirens.
Casting to a TV (optional)
The daemon runs on the same always-on device as the server.
- Run the server bound to your LAN:
"server": {"host": "0.0.0.0"}. - Give the TV a fixed address in your router and set
cast.host. - Set
cast.board_urlto the server as the TV sees it, e.g.http://<server-ip>:8760/. - Check the plan:
python3 cast_daemon.py --print-schedule(casts nothing). - Run
python3 cast_daemon.py, or enable the systemd unit.
events decides what happens. Each event is anchor (candles or havdalah), offset (-4h, -60m, +20s, -5m17s) and action:
| Action | Does |
|---|---|
board | shows the board page |
media | plays file from media.dir (a countdown video, an announcement you recorded) |
music | plays one track from your own music, see below |
stop | ends the cast; the TV returns to its idle screen |
Candle-lighting events fire only at the start of a Shabbat/Yom Tov period (a candle lighting within period_gap_hours of another is part of the same period). During a period a watchdog re-casts the board if the TV shows something else. Media and music are never started more than max_late_s late (default 90 seconds), so nothing begins playing after candle lighting because the daemon was restarted.
Proof that a cast worked is a request from the TV's address in the server's access log. A Cast app can report "launched" while the page never loads.
Your own music
The board plays only media you provide:
media.music_dir: a folder of audio files you own (.mp3,.ogg,.opus,.m4a,.flac,.wav). All files below it are used, shuffled unless"shuffle": false.media.playlist(optional): an.m3ufile listing files insidemusic_dir, one per line, and/orhttp(s)URLs you are entitled to stream. Other paths are refused.media.dir: files referenced bymediaevents.
Files are served only from these folders, with byte-range support that Cast receivers require. Nothing is downloaded from anywhere.
Run as systemd user services
cp board/systemd/*.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now shabbat-board # the web server
systemctl --user enable --now shabbat-cast # optional: the casting daemon
loginctl enable-linger "$USER" # keep running while logged out
The units expect the code in ~/.local/share/shabbat-tools/board and the config in ~/.config/shabbat-tools/board.json; edit INSTALL_DIR if yours differ.
Tests
python3 -m unittest discover -s tests -v
Uses a mock Hebcal server; no network access.