mirror of
https://github.com/acamarata/fajr-watch.git
synced 2026-07-01 03:04:28 +00:00
Complete project structure for an automated Fajr/Isha observation appliance: Hardware: - BOM for 3 build tiers ($270, $465, $1000) - 20-unit bulk order spec (~$9,900 for fleet) - Solar power, weatherproof enclosure, GPS timing Software: - Detection engine: multi-channel color analysis + temporal derivative tracking on horizon ROI, no fixed brightness threshold - Capture scheduler: computes twilight windows from coordinates, captures every 10s during window, runs detection, uploads results - Solar position via PyEphem for depression angle at each frame - Upload sync: hourly cron to central server + CSV export for offline Provisioning: - first-boot.sh: one-shot setup script installs all deps, configures systemd service, sets up WiFi from station.yaml on boot partition - Flash SD card, edit station.yaml, plug in, forget Docs: - Volunteer host guide: 10-minute installation, plug-and-play - Hardware BOM with sourcing links
190 lines
5.3 KiB
Bash
Executable file
190 lines
5.3 KiB
Bash
Executable file
#!/bin/bash
|
|
# fajr-watch first-boot provisioning script
|
|
#
|
|
# This runs once on first boot of a fresh Raspberry Pi OS image.
|
|
# It installs all dependencies, configures the system, and enables
|
|
# the fajr-watch service.
|
|
#
|
|
# Usage: placed in /boot/fajr-watch/first-boot.sh by the image builder,
|
|
# triggered by a systemd oneshot service on first boot.
|
|
|
|
set -euo pipefail
|
|
|
|
LOG="/var/log/fajr-watch-provision.log"
|
|
exec &> >(tee -a "$LOG")
|
|
|
|
echo "=== fajr-watch first-boot provisioning ==="
|
|
echo "Date: $(date -u)"
|
|
|
|
# ── System setup ──
|
|
|
|
echo "[1/8] Updating system packages..."
|
|
apt-get update -qq
|
|
apt-get upgrade -y -qq
|
|
|
|
echo "[2/8] Installing system dependencies..."
|
|
apt-get install -y -qq \
|
|
python3-pip python3-venv python3-numpy python3-opencv \
|
|
python3-yaml python3-scipy python3-astropy \
|
|
gpsd gpsd-clients libgps-dev \
|
|
libusb-1.0-0-dev libudev-dev \
|
|
ntp ntpdate \
|
|
jq curl
|
|
|
|
# ── GPS setup ──
|
|
|
|
echo "[3/8] Configuring GPS..."
|
|
if [ -e /dev/ttyUSB0 ] || [ -e /dev/ttyACM0 ]; then
|
|
systemctl enable gpsd
|
|
systemctl start gpsd
|
|
echo "GPS device detected"
|
|
else
|
|
echo "No GPS device found. Using NTP for time sync."
|
|
fi
|
|
|
|
# ── Python environment ──
|
|
|
|
echo "[4/8] Setting up Python environment..."
|
|
VENV="/opt/fajr-watch/venv"
|
|
python3 -m venv --system-site-packages "$VENV"
|
|
source "$VENV/bin/activate"
|
|
|
|
pip install --quiet \
|
|
ephem \
|
|
pyyaml \
|
|
requests \
|
|
Pillow
|
|
|
|
# Install ZWO ASI SDK if a ZWO camera is configured
|
|
STATION_CONFIG="/boot/fajr-watch/station.yaml"
|
|
if [ -f "$STATION_CONFIG" ]; then
|
|
CAMERA_TYPE=$(python3 -c "
|
|
import yaml
|
|
with open('$STATION_CONFIG') as f:
|
|
c = yaml.safe_load(f)
|
|
print(c.get('camera', {}).get('type', ''))
|
|
")
|
|
if [[ "$CAMERA_TYPE" == zwo_* ]]; then
|
|
echo "ZWO camera configured. Installing ASI SDK..."
|
|
pip install --quiet zwoasi
|
|
# Download ZWO SDK library
|
|
if [ ! -f /usr/lib/libASICamera2.so ]; then
|
|
ARCH=$(uname -m)
|
|
if [ "$ARCH" = "aarch64" ]; then
|
|
SDK_URL="https://github.com/stevemarple/python-zwoasi/raw/master/lib/armv8/libASICamera2.so"
|
|
else
|
|
SDK_URL="https://github.com/stevemarple/python-zwoasi/raw/master/lib/armv7/libASICamera2.so"
|
|
fi
|
|
curl -sL "$SDK_URL" -o /usr/lib/libASICamera2.so
|
|
chmod 644 /usr/lib/libASICamera2.so
|
|
ldconfig
|
|
fi
|
|
fi
|
|
|
|
if [[ "$CAMERA_TYPE" == pi_* ]]; then
|
|
echo "Pi camera configured. Installing picamera2..."
|
|
pip install --quiet picamera2
|
|
fi
|
|
fi
|
|
|
|
# ── Install fajr-watch ──
|
|
|
|
echo "[5/8] Installing fajr-watch software..."
|
|
INSTALL_DIR="/opt/fajr-watch/app"
|
|
if [ -d /boot/fajr-watch/src ]; then
|
|
cp -r /boot/fajr-watch/src "$INSTALL_DIR/src"
|
|
cp -r /boot/fajr-watch/config "$INSTALL_DIR/config"
|
|
else
|
|
# Clone from GitHub if not bundled on the SD card
|
|
git clone --depth 1 https://github.com/acamarata/fajr-watch.git "$INSTALL_DIR"
|
|
fi
|
|
|
|
# ── Data directories ──
|
|
|
|
echo "[6/8] Creating data directories..."
|
|
mkdir -p /var/lib/fajr-watch/data/{captures,results,upload-queue}
|
|
chown -R pi:pi /var/lib/fajr-watch
|
|
|
|
# ── Copy station config ──
|
|
|
|
if [ -f "$STATION_CONFIG" ]; then
|
|
cp "$STATION_CONFIG" "$INSTALL_DIR/config/station.yaml"
|
|
echo "Station config loaded from boot partition"
|
|
fi
|
|
|
|
# ── WiFi setup from station config ──
|
|
|
|
if [ -f "$STATION_CONFIG" ]; then
|
|
WIFI_SSID=$(python3 -c "
|
|
import yaml
|
|
with open('$STATION_CONFIG') as f:
|
|
c = yaml.safe_load(f)
|
|
print(c.get('network', {}).get('wifi_ssid', ''))
|
|
")
|
|
WIFI_PASS=$(python3 -c "
|
|
import yaml
|
|
with open('$STATION_CONFIG') as f:
|
|
c = yaml.safe_load(f)
|
|
print(c.get('network', {}).get('wifi_password', ''))
|
|
")
|
|
if [ -n "$WIFI_SSID" ] && [ -n "$WIFI_PASS" ]; then
|
|
echo "[6b] Configuring WiFi: $WIFI_SSID"
|
|
nmcli device wifi connect "$WIFI_SSID" password "$WIFI_PASS" || true
|
|
fi
|
|
fi
|
|
|
|
# ── Systemd service ──
|
|
|
|
echo "[7/8] Installing systemd service..."
|
|
cat > /etc/systemd/system/fajr-watch.service << 'UNIT'
|
|
[Unit]
|
|
Description=fajr-watch twilight observation station
|
|
After=network-online.target gpsd.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=pi
|
|
Group=pi
|
|
Environment=PYTHONPATH=/opt/fajr-watch/app
|
|
ExecStart=/opt/fajr-watch/venv/bin/python -m src.capture.scheduler
|
|
WorkingDirectory=/opt/fajr-watch/app
|
|
Restart=always
|
|
RestartSec=60
|
|
StandardOutput=append:/var/log/fajr-watch.log
|
|
StandardError=append:/var/log/fajr-watch.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable fajr-watch.service
|
|
systemctl start fajr-watch.service
|
|
|
|
# ── Upload cron ──
|
|
|
|
echo "[8/8] Setting up data upload cron..."
|
|
cat > /etc/cron.d/fajr-watch-upload << 'CRON'
|
|
# Upload completed detection results every hour
|
|
0 * * * * pi /opt/fajr-watch/venv/bin/python -m src.upload.sync 2>> /var/log/fajr-watch-upload.log
|
|
CRON
|
|
|
|
# ── Disable first-boot trigger ──
|
|
|
|
rm -f /boot/fajr-watch/first-boot-trigger
|
|
systemctl disable fajr-watch-provision.service 2>/dev/null || true
|
|
|
|
echo ""
|
|
echo "=== fajr-watch provisioning complete ==="
|
|
echo "Station ID: $(python3 -c "
|
|
import yaml
|
|
with open('$STATION_CONFIG') as f:
|
|
c = yaml.safe_load(f)
|
|
print(c.get('station', {}).get('id', 'unknown'))
|
|
" 2>/dev/null || echo 'unknown')"
|
|
echo "Service status:"
|
|
systemctl status fajr-watch.service --no-pager || true
|
|
echo ""
|
|
echo "Logs: journalctl -u fajr-watch -f"
|
|
echo "Data: /var/lib/fajr-watch/data/results/"
|