diff --git a/.gitignore b/.gitignore
index a20bf3960..a1710c8d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,4 +115,6 @@ Catalogs/GMN_StarCatalog_LM12.0.bin
# Agents
.agents/
-skills-lock.json
\ No newline at end of file
+skills-lock.json
+# Station list for Scripts/MultiCamLinux/add_GStation.sh
+stations.csv
diff --git a/Scripts/MultiCamLinux/GRMSUpdater.sh b/Scripts/MultiCamLinux/GRMSUpdater.sh
index 51246d6ba..c9e148c31 100755
--- a/Scripts/MultiCamLinux/GRMSUpdater.sh
+++ b/Scripts/MultiCamLinux/GRMSUpdater.sh
@@ -527,7 +527,9 @@ fi
mapfile -t RunList < <(
pgrep -f "Scripts/MultiCamLinux/StartCapture.sh" | while read -r pid; do
cmdline=$(ps -p "$pid" -o args= 2>/dev/null || continue)
- if [[ "$cmdline" =~ Scripts/MultiCamLinux/StartCapture\.sh[[:space:]]+([[:alnum:]]{6}) ]]; then
+ # Station IDs are normally 6 alphanumerics, but add_Station.sh
+ # also accepts underscores/hyphens and other lengths on request
+ if [[ "$cmdline" =~ Scripts/MultiCamLinux/StartCapture\.sh[[:space:]]+([[:alnum:]_-]+) ]]; then
echo "${BASH_REMATCH[1]}"
fi
done | sort -u
diff --git a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
index 1911bbb25..a105b5dbc 100755
--- a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
+++ b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
@@ -1,19 +1,6 @@
#!/bin/bash
-seconds=70
-echo " Starting all configured stations post-update..."
-
-loop=0
-for Dir in ~/source/Stations/*
- do
- Station=$(basename $Dir)
- echo " Starting camera ${Station}"
- lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
- echo " waiting $seconds seconds..."
- sleep ${seconds}
- if [[ $loop = 0 ]] ; then
- seconds=10
- fi
- let loop++
- done
-echo " All cameras started"
+# Kept for compatibility with Desktop shortcuts created by older
+# installs - RMS_StartCapture.sh now handles the multi-camera data
+# structure directly
+exec ~/source/RMS/Scripts/RMS_StartCapture.sh "$@"
diff --git a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
index 40eeb88fd..3670ebe85 100755
--- a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+++ b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
@@ -1,307 +1,4 @@
#!/bin/bash
-# This software is part of the Linux port of RMS
-# Copyright (C) 2023 Ed Harman
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# Version 1.8 - replaced Desktop link with one that starts all captures
-#
-# Version 1.7 - changed desktop launcher display names and added conkyrc1 mod
-#
-# Version 1.6 - added support for RPi-5 platforms that can run max 4 cameras
-#
-# Version 1.5 - added support for non English locales where user user directories may not include a directory named Desktop
-# i.e. this enables support of RMS on a non English distro install
-#
-# Version 1.4 - moved codebase into RMS/Scripts/MultiCamLinux
-#
-# Version 1.3 - fixed path to CMN desktop shortcut
-#
-# Version 1.2 - added a change to the flag reboot_after_processing from true to false
-#
-# Version 1.1
-# Changes - added station arguments to Launch scripts
-# - changed desktop links for StartCapture to symbolic links of the scripts within .config/autostart
-#
-# Version 1.2 - Peter E. took over dev of this script and any blame going forward
-# - white space and indents added
-#
-
-MaxStation=4 # maximum number of stations allowed on a Pi5
-
-if [[ $(uname -m ) == aarch64 ]]
-then
- Model=$(lscpu| grep -o 'Cortex.*'|grep -o '[0-9]*')
- if [[ $Model -lt 76 ]] # RPi5 uses Cortex A76 - use 72 for script testing on an RPi4
- then
- echo "This platform will not support multiple cameras, an RPi5 or similar is required"
- exit
- fi
-
- DefStation=$(awk ' /stationID:/ {print toupper($2)}' ~/source/RMS/.config) # force uppercase
- if [[ $DefStation == XX0001 && ! -d ~/source/Stations/ ]]
- then
- echo "Please run RMS_Firstrun and configure your 1st station"
- echo "then if you wish to configure additional stations execute add_Pi_Station again"
- exit
- fi
- if [[ ! -d ~/source/Stations/ ]]
- then
-
-cat <
-
-It appears you have already configured your first station - id ${DefStation}
-so its config files will now be relocated to -
-~/source/Stations/${DefStation}
-Captured data from your default station are stored by default in
-
-~/RMS_data
-
-This data will be moved to -
-~/RMS_data/${DefStation}
-EOF
-
- # First station has been configured, so move it to ~/source/Stations/,
- # and move any captured data to ~/RMS_data/.
-
- # tweak conky station title
- sed -i 's/\(source\)\/RMS/\1\/Stations\/'"$DefStation"'/g' /home/rms/.conkyrc1
- # tweak conky data source path for log data
- sed -i "s/\(.*RMS_data\)/\1\/${DefStation}/g" /home/rms/.conkyrc1
-
- mkdir ~/source/Stations/
- mkdir ~/source/Stations/${DefStation}
- cp ~/source/RMS/.config ~/source/Stations/${DefStation}
- cd ~/source/RMS
- sed -i "s,data_dir.*$,data_dir: ~/RMS_data/${DefStation},g" ~/source/Stations/${DefStation}/.config
- if [[ -e ~/source/RMS/platepar_cmn2010.cal ]]
- then
- mv ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
- fi
- if [[ -e ~/source/RMS/mask.bmp ]]
- then
- mv ~/source/RMS/mask.bmp ~/source/Stations/${DefStation}/
- #replace with copy from master
- cd ~/source/RMS
- wget -q https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/mask.bmp
- fi
- mkdir ~/RMS_data/${DefStation}
- cd ~/RMS_data/${DefStation}
- mv ../ArchivedFiles .
- mv ../CapturedFiles .
- mv ../logs .
-
- cat <<- EOF > ~/Desktop/${DefStation}_StartCapture.desktop
- [Desktop Entry]
- Name=${DefStation}-StartCapture
- Type=Application
- Exec=lxterminal --title=${DefStation} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${DefStation}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${DefStation}_StartCapture.desktop
-
- cat <<- EOF > ~/Desktop/${DefStation}-Show_LiveStream.desktop
- [Desktop Entry]
- Name=${DefStation}-ShowLiveStream
- Type=Application
- Exec=lxterminal --title=${DefStation}-LiveStream -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${DefStation}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${DefStation}-Show_LiveStream.desktop
-
- # Check if the user's desktop directory environment variable is set
- if [ -n "$xdg-user-dir DESKTOP" ]
- then
- Desktop=`xdg-user-dir DESKTOP`
- fi
-
- # cleanup erroneous Desktop shortcuts
- if [[ -f ~/Desktop/RMS_FirstRun.sh ]]
- then
- rm ~/Desktop/CMNbinViewer.sh ~/Desktop/RMS_ShowLiveStream.sh ~/Desktop/RMS_StartCapture.sh
- rm ~/Desktop/RMS_config.txt ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
- fi
-
- # remove comment from last line of wayfire.ini to enable window cascade
- sed -i s/#mode/mode/ ~/.config/wayfire.ini
-
- fi # if [[ ! -d ~/source/Stations/ ]]
-
-fi # if [[ $(uname -m ) == aarch64 ]]
-
-# need to count the number of directories under ~/source/Stations and only
-# prompt for adding the correct number of new cameras, so subtract that number
-# from MaxStation defined above
-
-numdirs=$(find ~/source/Stations/ -maxdepth 1 -type d | wc -l)
-numdirs=$((numdirs-1))
-Remaining=$((MaxStation-numdirs))
-#echo MaxStation:$MaxStation NumDirs:$numdirs Remaining:$Remaining
-
-declare Station
-while :
-do
- if [[ ${#Station[@]} == $Remaining ]]
- then
- echo ""
- echo "Done, This platform has a maximum of $numdirs cameras"
- break
- fi
- read -p "Enter station ID, to end: " this_Station
- this_Station="${this_Station^^}" #uppercase it..
- if [[ -z $this_Station ]]
- then
- break
- fi
- Station+=("$this_Station")
- # echo $Station
-done
-
-echo -e "\nNew stations to add -"
-printf '%s\n' "${Station[@]}"
-
-# check if ~/${RMS_data} exists and if not create it....
-if [[ ! -d "${RMS_data}/" ]]
-then
- mkdir ${RMS_data}
-fi
-
-No_Stations=${#Station[@]}
-for item in "${Station[@]}"
-do
- if [[ -d ~/source/Stations/${item} ]]
- then
- echo -e "\n\nNot creating station ${item} - it already exists\n"
- exit
- else
- echo "making dir Stations/${item}"
- mkdir ~/source/Stations/${item}
- if [[ ! -d ${RMS_data}/$item ]]
- then
- mkdir ~/RMS_data/${item}
- fi
- fi
-
- cp ~/source/RMS/.config ~/source/Stations/${item}
-
- # create autostart entry for the station
- cat <<- EOF > ~/Desktop/${item}_StartCapture.desktop
- [Desktop Entry]
- Name=${item}-StartCapture
- Type=Application
- Exec=lxterminal --title=${item} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${item}_StartCapture.desktop
-
- if [[ -e ~/source/RMS/mask.bmp ]]
- then
- cp ~/source/RMS/mask.bmp ~/source/Stations/${item}/
- fi
-
- # create a ShowLiveStream- ~/Desktop/${item}-Show_LiveStream.desktop
- [Desktop Entry]
- Name=${item}-ShowLiveStream
- Type=Application
- Exec=lxterminal --title=Stream-${item} -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${item}-Show_LiveStream.desktop
-
- # customise each .config
- # set the station_id
- sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
- # set the ${RMS_data} dir
- sed -i "s,data_dir.*$,data_dir: ~/RMS_data/${item},g" ~/source/Stations/${item}/.config
- echo -e "\n\nAdded station $item\n\n"
- # disable daily post processing reboot
- sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
-done
-
-# check if keys are already present
-if [[ -f ~/.ssh/id_rsa ]]
-then
- if [ ! -z ${Configured} ]
- then
- echo "SSH keys already exist, not overwriting them"
- fi
-else
- ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
- cp ~/.ssh/id_rsa.pub ~/Desktop
- echo "SSH keys successfully generated in ~/.ssh"
- echo "your new id_rsa.pub public key file now placed on the Desktop"
- echo "Be sure to send a copy of this file to Denis"
-fi
-
-if [[ ! -v Model ]]
-then
- timedatectl set-timezone UTC
-fi
-
-echo -e "\n\nStation configuration complete\n\n"
-user=`id -u -n`
-
-# Set up shortcut for CMNbinViewer
-cat <<- EOF > ~/Desktop/CMNbinViewer.desktop
-[Desktop Entry]
-Name=CMNbinViewer
-Type=Application
-Exec=/home/${user}/source/RMS/Scripts/CMNbinViewer_env.sh
-Hidden=false
-NoDisplay=false
-Icon=/home/${user}/source/RMS/Scripts/MultiCamLinux/icon.png
-EOF
-chmod +x ~/Desktop/CMNbinViewer.desktop
-
-# set the option to allow launching of desktop shortcuts
-#sed -i s/quick_exec=0/quick_exec=1/ ~/.config/libfm/libfm.conf
-
-if grep -Fqv "UTC" /etc/timezone
-then
- sudo timedatectl set-timezone UTC
-fi
-
-# set the extra_space for all configured stations -
-mult=$(ls -1 /home/${USER}/source/Stations/|wc -l)
-for Dir in /home/${USER}/source/Stations/*
- do
- sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${mult} * 30 ))/g" ${Dir}/.config
-done
-
-# set up so RMS_FirstRun can autostart all captures
-rm -f /home/${USER}/Desktop/RMS_StartCapture.sh
-ln -s /home/${USER}/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh /home/${USER}/Desktop/RMS_StartCapture.sh
-
-# get a copy of the default .config
-cd ~/source/RMS
-rm .config #delete potentially modified .config and replace with new..
-wget -q https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/.config
+# Kept for compatibility with existing documentation and older images.
+# The station tooling is now universal - see add_Station.sh.
+exec "$(dirname "$0")/../../add_Station.sh" "$@"
diff --git a/Scripts/MultiCamLinux/StartCapture.sh b/Scripts/MultiCamLinux/StartCapture.sh
index 066c5730c..2f34bdf34 100755
--- a/Scripts/MultiCamLinux/StartCapture.sh
+++ b/Scripts/MultiCamLinux/StartCapture.sh
@@ -25,19 +25,6 @@
# second command line arg is optional, can be used to adjust the sleep time in seconds
# so that there can be a delay between starting each station
-delay_by_station_sequence() {
-
-# implement a delay based on position of station in directory
-# this is not used presently
-
-delay=$(ls /home/$(whoami)/source/Stations/ | grep -n $1 | cut -d':' -f1)
-delay=$((delay*30))
-echo Additional $delay second delay will be added.
-sleep $delay
-
-}
-
-
if [[ -z "$1" ]] # called with no args
then
echo " No Station directory specified, quitting now"
diff --git a/Scripts/MultiCamLinux/add_GStation.sh b/Scripts/MultiCamLinux/add_GStation.sh
index 4b26275a6..03cbb7674 100755
--- a/Scripts/MultiCamLinux/add_GStation.sh
+++ b/Scripts/MultiCamLinux/add_GStation.sh
@@ -1,340 +1,4 @@
#!/bin/bash
-# This software is part of the Linux port of RMS
-# Copyright (C) 2023 Ed Harman
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# Version 1.6 - added logic to prevent running this script on a Raspberry Pi
-# added by Peter E., June 2024
-#
-# Version 1.5 - added support for non English locales where user user directories may not include a directory named Desktop
-# i.e. this enables support of RMS on a non English distro install
-#
-# Version 1.4 - moved codebase into RMS/Scripts/MultiCamLinux
-#
-# Version 1.3 - fixed path to CMN desktop shortcut
-#
-# Version 1.2 - added a change to the flag reboot_after_processing from true to false
-#
-# Version 1.1
-# Changes - added station arguments to Launch scripts
-# - changed desktop links for StartCapture to symbolic links of the scripts within .config/autostart
-#
-#
-# Prevent running this script on a Raspberry Pi, because
-# add_Pi_Station.sh should be used to add cameras on a Pi.
-file=/sys/firmware/devicetree/base/model
-if [[ -f "$file" ]]; then
- echo ""
- contents=$(tr -d '\0' < $file)
- echo "The file $file reads: $contents"
- if [[ $contents == 'Raspberry'* ]]; then
- echo "The add_GStation.sh script should not be used on Raspberry Pi."
- echo "Please use add_Pi_Station.sh to add cameras on a Pi5."
- echo ""
- exit 1
- fi
-fi
-
-# Check if the user's desktop directory environment variable is set
-if [ -n "$xdg-user-dir DESKTOP" ]; then
- Desktop=`xdg-user-dir DESKTOP`
-fi
-#
-if [[ "$#" -eq 0 ]] #called with no arg
-then
- RMS_data=~/RMS_data
- else
- RMS_data=${1}
-fi
-# cleanup erroneous Desktop shortcuts
-if [[ -f ~/Desktop/RMS_FirstRun.sh ]]
- then
- rm ~/Desktop/RMS_FirstRun.sh ~/Desktop/RMS_StartCapture.sh ~/Desktop/RMS_ShowLiveStream.sh ~/Desktop/TunnelIPCamera.sh ~/Desktop/CMNbinViewer.sh
-fi
-
-# Install the Buster RMS default terminal - lxterminal
-if ! command -v lxterminal &> /dev/null
-then
- sudo apt-get install -y lxterminal &
- wait %1
- echo -e "\n\nlaunching lxterminal\n\n"
-# we need to launch lxterminal at least once for it to write its default preferences file
- echo -e "\n\nclosed lxterminal\n\n"
- # now lxterminal is installed and written it's config we can customise it
- sed -i "s/scrollback=.*/scrollback=10000/g" ~/.config/lxterminal/lxterminal.conf
- sed -i "s/geometry_columns=.*/geometry_columns=120/g" ~/.config/lxterminal/lxterminal.conf
- sed -i "s/geometry_rows=.*/geometry_rows=25/g" ~/.config/lxterminal/lxterminal.conf
- echo -e "\n\nlxterminal installed and preferences set\n\n"
-fi
-if [[ ! -d ~/source/Stations ]]
- then
- mkdir ~/source/Stations
-fi
-declare Station
-while :
-do
- read -p "Enter station ID, to end: " this_Station
- if [[ -z $this_Station ]]
- then
- break
- fi
- Station+=("$this_Station")
-# echo $Station
-done
-
-echo -e "\nNew stations to add -"
-printf '%s\n' "${Station[@]}"
-
-# check if ~/${RMS_data} exists and if not create it....
-if [[ ! -d "${RMS_data}/" ]]
- then
- mkdir ${RMS_data}
-fi
-
-# check if user has an autostart dir in their .config directory and if not create it...
-if [[ ! -d ~/.config/autostart ]]
- then
- mkdir ~/.config/autostart
-fi
-
-No_Stations=${#Station[@]}
-for item in "${Station[@]}"
-do
- if [[ -d ~/source/Stations/${item} ]]
- then
- echo -e "\n\nNot creating station ${item} - it already exists\n"
- exit
- else
- echo "making dir Stations/${item}"
- mkdir ~/source/Stations/${item}
- if [[ ! -d ${RMS_data}/$item ]]
- then
- mkdir ${RMS_data}/${item}
- fi
-
- fi
- cp ~/source/RMS/.config ~/source/Stations/${item}
- # create autostart entry for the station
- cat <<- EOF > ~/.config/autostart/${item}_StartCap.desktop
- [Desktop Entry]
- Name=${item}-Startcapture
- Type=Application
- Exec=lxterminal --title=${item} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- # create Desktop softlink for StartCapture
- chmod +x ~/.config/autostart/${item}_StartCap.desktop
- ln -s ~/.config/autostart/${item}_StartCap.desktop ~/Desktop/${item}_StartCap.desktop
- chmod -x ~/Desktop/${item}_StartCap.desktop
- gio set ~/Desktop/${item}_StartCap.desktop metadata::trusted true
- chmod +x ~/Desktop/${item}_StartCap.desktop
- cp ~/source/RMS/mask.bmp ~/source/Stations/${item}
- # create a ShowLiveStream- ~/Desktop/Show_LiveStream-${item}.desktop
- [Desktop Entry]
- Name=${item}-ShowLiveStream
- Type=Application
- Exec=lxterminal --title=Stream-${item} -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod -x ~/Desktop/Show_LiveStream-${item}.desktop
- gio set ~/Desktop/Show_LiveStream-${item}.desktop metadata::trusted true
- chmod +x ~/Desktop/Show_LiveStream-${item}.desktop
- #
- # customise each .config
- #
- # set the station_id
- sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
- # set the ${RMS_data} dir
- sed -i "s,data_dir.*$,data_dir: ${RMS_data}/${item},g" ~/source/Stations/${item}/.config
- # update the free space in ratio of number of stations being added, using a 20Gb factor for now
- sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${No_Stations} * 20 ))/g" ~/source/Stations/${item}/.config
- echo -e "\n\nAdded station $item\n\n"
- # disable daily post processing reboot
- sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
-done
-# check if keys are already present
-if [[ -f ~/.ssh/id_rsa ]]
- then
- if [ ! -z ${Configured} ]
- then
- echo "SSH keys already exist, not overwriting them"
- fi
- else
- ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
- echo "SSH keys successfully generated in ~/.ssh"
-fi
-timedatectl set-timezone UTC
-gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' # disables inactivity lock
-gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0 # sleep inactivity 0=='never'
-gsettings set org.gnome.desktop.session idle-delay 600 # screen blank after xxx secs, adjust to taste 0=='never'
-gsettings set org.gnome.desktop.screensaver lock-enabled false # disable screensaver lock
-
-# only prompt for these optional installs if the user has run this for the 1st time
-
-# launch the vRMS environment at logon
-if ( ! grep -q RMS ~/.bashrc)
-then
- cat <>~/.bashrc
- # Activate RMS
- cd ~/source/RMS
- source ~/vRMS/bin/activate
-EOF
-fi
-if ( ! command -v pcmanfm &> /dev/null )
-then
- echo -e "Install pcmanfm file manager ?\n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* ) sudo apt install -y pcmanfm; break;;
- [nN] ) break ;;
- esac
- done
-fi
-if (! command -v mousepad &> /dev/null )
-then
- echo -e "Install mousepad ?\n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* ) sudo apt install -y mousepad; break;;
- [nN] ) break ;;
- esac
- done
-
-fi
-
-if ( ! command -v anydesk &> /dev/null )
-then
- echo -e "Install AnyDesk ?"
- echo -e "This will require sudo privileges \n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* )
- wget -qO - https://keys.anydesk.com/repos/DEB-GPG-KEY | sudo apt-key add -
- echo "deb http://deb.anydesk.com/ all main" > anydesk-stable.list
- sudo mv anydesk-stable.list /etc/apt/sources.list.d/
- sudo apt update
- sudo apt install -y anydesk
- break;;
- [nN] ) break ;;
- esac
- done
-fi
-
-echo -e "\n\nStation configuration complete\n\n"
-user=`id -u -n`
-
-# Optionaly create a crontab entry for the user to automate regular RMS updates I've chosen midday as a default but this can easily be changed
-echo -e "Do you wish to schedule a regular cron job to stop all running instances,"
-echo -e "perform an update of RMS, and finally restart all configured instances? "
-while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* )
- echo " The following template entry will schedule the action to run @3pm every Sunday"
- echo " feel free to edit the line if you want to alter the timing and/or frequency, when you are happy with"
- echo "the schedule, just hit return"
- echo " If you want some examples of the required format check out https://crontab.guru/examples.html"
- echo "0 15 * * SUN /home/${user}/source/RMS/Scripts/MultiCamLinux/GRMSUpdate.sh" | read Schedule
- if sudo test -f "/var/spool/cron/crontabs/$user" # does user have an existing crontab?
- then
- if sudo grep -q RMSUpdater /var/spool/cron/crontabs/$user # check there isn't an existing entry
- then
- echo -e "\nNot modifying ${user}'s crontab -that user has an existing entry\n"
- else
- read -e -i "0 15 * * SUN" Schedule
- # can't edit users crontab in-place without messing with dir permissions...
- sudo mv /var/spool/cron/crontabs/$user $user.tmp
- set -o noglob
- sudo echo "$Schedule /home/$user/source/RMS/Scripts/MultiCamLinux/GRMSUpdater.sh" >> ${user}.tmp
- set +o noglob
- sudo mv ${user}.tmp /var/spool/cron/crontabs/${user}
- echo -e "\n User ${user}'s crontab has been updated, if you subsequently wish to edit it you can do so"
- echo -e "by issuing the following cmd in a terminal- crontab -e\n"
- fi
-else
- read -e -i "0 15 * * SUN" Schedule
- set -o noglob
- # user does not have a crontab so create one with the standard boilerplate stuff
- touch ${user}.tmp
- cat <<- EOF >${user}.tmp
- # Edit this file to introduce tasks to be run by cron.
- #
- # Each task to run has to be defined through a single line
- # indicating with different fields when the task will be run
- # and what command to run for the task
- #
- # To define the time you can provide concrete values for
- # minute (m), hour (h), day of month (dom), month (mon),
- # and day of week (dow) or use '*' in these fields (for 'any').
- #
- # Notice that tasks will be started based on the cron's system
- # daemon's notion of time and timezones.
- #
- # Output of the crontab jobs (including errors) is sent through
- # email to the user the crontab file belongs to (unless redirected).
- #
- # For example, you can run a backup of all your user accounts
- # at 5 a.m every week with:
- # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
- #
- # For more information see the manual pages of crontab(5) and cron(8)
- #
- # m h dom mon dow command
- $Schedule /home/$user/source/RMS/Scripts/MultiCamLinux/GRMSUpdater.sh
- EOF
-
- set -o noglob
- sudo mv ${user}.tmp /var/spool/cron/crontabs/${user}
- echo -e "\nA new crontab has been installed for ${user}, if you subsequently wish to change it in any way"
- echo -e "you can edit it in a terminal with the cmd - crontab -e\n"
- fi
- break ;;
- [nN]* ) break ;;
- esac
- done
-
-# Set up shortcut for CMNbinViewer
-cat <<- EOF > ~/Desktop/CMNbinViewer.desktop
-[Desktop Entry]
-Name=CMNbinViewer
-Type=Application
-Exec=/home/${user}/source/RMS/Scripts/CMNbinViewer_env.sh
-Hidden=false
-NoDisplay=false
-Icon=/home/${user}/source/cmn_binviewer/icon.png
-EOF
-# set the 'allow launching' feature
-chmod -x ~/Desktop/CMNbinViewer.desktop
-gio set ~/Desktop/CMNbinViewer.desktop metadata::trusted true
-chmod +x ~/Desktop/CMNbinViewer.desktop
-if grep -Fqv "UTC" /etc/timezone
-then
-sudo timedatectl set-timezone UTC
-fi
-# Fix AnyDesk issue with Wayland ... requires sudo
-if grep -q '#Way' /etc/gdm3/custom.conf
-then
- sudo sed -i 's/#\(WaylandEnable.*\)/\1/g' /etc/gdm3/custom.conf
-fi
-exit
+# Kept for compatibility with existing documentation.
+# The station tooling is now universal - see add_Station.sh.
+exec "$(dirname "$0")/../add_Station.sh" "$@"
diff --git a/Scripts/README.md b/Scripts/README.md
index ecd4c6308..baa6d028a 100644
--- a/Scripts/README.md
+++ b/Scripts/README.md
@@ -1 +1,7 @@
-These scripts are usually kept on the desktop of the Pi and assume that RMS is installed in ~/source/RMS.
+Helper scripts for running a meteor station. All of them assume that RMS is installed in `~/source/RMS`.
+
+- Most scripts here (`RMS_FirstRun.sh`, `RMS_StartCapture.sh`, `RMS_Update.sh`, ...) are linked on the desktop of a station and run from there.
+- `RMS_Installer.sh` installs RMS and everything it needs on a generic Linux machine (Ubuntu LTS or Debian).
+- `add_Station.sh` creates camera stations under `~/source/Stations/` — interactively, or in bulk from a CSV file (see `stations_template.csv` in the repository root). It also converts a legacy single-camera setup to that layout (by copying — nothing in the RMS root is modified). Works on any Linux platform, Raspberry Pi included.
+- `RMS_StartCapture.sh` starts capture on either data structure: every station under `~/source/Stations/` in its own terminal, or the single legacy camera if there are none.
+- `MultiCamLinux/` holds the per-station runtime launchers (`StartCapture.sh`, `LiveStream.sh`) plus compatibility wrappers for the older `add_GStation.sh`, `add_Pi_Station.sh` and `Pi/RMS_StartCapture_MCP.sh` entry points.
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 7787ab169..b30fa90ae 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-echo "Waiting 10 seconds to init the Pi..."
+echo "Waiting a few seconds for the system to initialize..."
echo ""
echo "IMPORTANT: RMS will first update itself."
echo "Do not touch any file during the update and do not close this window."
@@ -18,26 +18,32 @@ RMSCONFIG=~/source/RMS/.config
# Auto run enable flag file
RMSAUTORUNFILE=~/.rmsautorunflag
-RMSSTARTCAPTURE=~/Desktop/RMS_StartCapture.sh
-RMSUPDATESCRIPT=~/Desktop/RMS_Update.sh
+# RMS_StartCapture.sh handles both the legacy and the multi-camera data
+# structure, so the same launcher is used before and after a conversion
+RMSSTARTCAPTURE=~/source/RMS/Scripts/RMS_StartCapture.sh
+RMSUPDATESCRIPT=~/source/RMS/Scripts/RMS_Update.sh
-# Function for editing the RMS config
-editRMSConfig () {
-
- # If leafpad is not available (Raspbian Jessie), use mousepad (Raspbian Buster)
- if [ $( command -v leafpad ) ]; then
-
- # Open the config file
- leafpad $RMSCONFIG
-
- else
+# Check that a value is a number within the given range
+isNumberInRange () { # $1 = value, $2 = min, $3 = max
+ [[ "$1" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] || return 1
+ awk -v v="$1" -v lo="$2" -v hi="$3" 'BEGIN { exit !(v >= lo && v <= hi) }'
+}
- # Open the config file
- mousepad $RMSCONFIG
-
- fi
+# Strip leading/trailing whitespace. Used instead of xargs, which aborts
+# with an error when the input contains a stray quote or backslash
+trimSpaces () {
+ local s="$*"
+ s="${s#"${s%%[![:space:]]*}"}"
+ s="${s%"${s##*[![:space:]]}"}"
+ printf '%s' "$s"
+}
+# Discard buffered keypresses (e.g. the tail of an arrow key escape
+# sequence left behind by a single-key read) so they cannot pollute the
+# next typed entry
+flushInput () {
+ while read -t 0.01; do :; done
}
@@ -57,7 +63,7 @@ else
# If the configuration was done, run recording
bash $RMSSTARTCAPTURE
- exit 1
+ exit 0
fi
fi
@@ -112,44 +118,117 @@ read -p ""
echo ""
echo "This is a brief overview of this guide:"
-echo " 0. Expand the file system (if you flashed this SD card yourself)."
+echo " 0. Expand the file system (checked automatically)."
echo " 1. Connect your Pi to the Internet. "
echo " 2. Change the default password for security reasons."
echo " 3. Generate a new SSH key."
-echo " 4. Edit the RMS config file. "
-
+echo " 4. Enter the station ID and the geo coordinates of your camera."
+echo " 5. Convert to the multi-camera data structure (recommended)."
echo ""
+echo "At the end of these steps the first data capture session will start."
+echo ""
+
+# Expanding the file system only applies to Raspberry Pi SD card images
+if command -v raspi-config >/dev/null 2>&1; then
echo "
0) Expanding the file system
----------------------------
-If you have bought a system that was already assembled, or the file system
-has already been expanded, PRESS Q to skip this step.
-"
-
-read -n1 -r -p 'If you have flashed this SD card yourself, press ENTER.' key
+Checking if the file system needs to be expanded..."
+
+# Compare the root file system to the partition and the device it lives
+# on. A freshly flashed image is much smaller than the SD card it was
+# written to: expanding first grows the partition to fill the device
+# (raspi-config + reboot), then a boot-time resize2fs grows the file
+# system to fill the partition. On large cards that second step can still
+# be running in the background when this guide starts again after the
+# reboot, so the two are checked separately.
+fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+root_part=$(findmnt -n -o SOURCE / 2>/dev/null)
+root_dev=$(lsblk -n -o PKNAME "$root_part" 2>/dev/null | head -1)
+part_gb=$(( $(lsblk -b -d -n -o SIZE "$root_part" 2>/dev/null || echo 0) / 1000000000 ))
+if (( part_gb == 0 )); then
+ part_gb=$fs_gb
+fi
+if [[ -n "$root_dev" ]]; then
+ dev_gb=$(( $(lsblk -b -d -n -o SIZE "/dev/$root_dev") / 1000000000 ))
+else
+ dev_gb=$part_gb
+fi
-if [[ "$key" = "" ]]; then
- lxterminal -e "sudo raspi-config"
-
- echo "
- Another window has opened where you can expand the file system.
-
- Go to:
-
- 7 ADVANCED OPTIONS -->
- A1 EXPAND FILE SYSTEM -->
- < OK >
+# The file system reported by df is always a few percent smaller than its
+# partition (ext4 keeps inode tables and a journal), and that overhead
+# grows with the card size - so the comparison slack must scale too, or
+# large cards get flagged as unexpanded forever
+slack_gb=$(( part_gb / 20 + 2 ))
- < FINISH > (at the bottom) -->
- < YES > (reboot)
+# clear the input buffer
+while read -t 0.01; do :; done
- Your Raspberry Pi will reboot.
- "
+if (( dev_gb - part_gb >= 2 )); then
- read -p "Press ENTER to continue..."
+ echo "
+The file system takes up ${fs_gb} GB of the ${dev_gb} GB storage device.
+RMS needs the whole device for data capture: 64 GB at minimum, 128 GB or
+more is recommended. The usable size is always a bit smaller than the
+rated size, e.g. a 64 GB card gives about 62 GB.
+"
+ read -n1 -r -p 'Press ENTER to expand the file system to the full device (recommended), or any other key to skip this step...' key
+
+ if [[ "$key" = "" ]]; then
+ echo ""
+ echo "Expanding the file system, then the system will reboot."
+ echo "This guide will start again after the reboot."
+ sudo raspi-config --expand-rootfs
+ echo ""
+ flushInput
+ read -n1 -s -r -p 'Take your time to review the output above, then press any key to reboot... '
+ echo ""
+ echo "Rebooting..."
+ sleep 2
+ sudo reboot
+ exit 0
+ else
+ echo ""
+ echo "The file system was not expanded, so part of the storage device"
+ echo "will stay unused and there may not be enough room for data capture!"
+ fi
+
+elif (( part_gb - fs_gb >= slack_gb )); then
+
+ # The partition already fills the device but the file system does not,
+ # so the resize scheduled by raspi-config is still running (or failed)
+ echo "
+The file system is still being expanded to fill the ${dev_gb} GB storage
+device - on large cards this takes a few minutes after the resize reboot.
+Waiting for it to finish..."
+ while pgrep -x resize2fs >/dev/null 2>&1; do
+ printf '.'
+ sleep 2
+ done
+ echo ""
+ fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+ if (( part_gb - fs_gb >= slack_gb )); then
+ # The scheduled resize never ran or did not finish the job -
+ # resize directly, which is safe online on a mounted ext4 root
+ sudo resize2fs "$root_part"
+ fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+ fi
+ echo "${fs_gb} GB of space is now available."
+
+elif (( fs_gb < 61 )); then
+ echo ""
+ echo "The whole ${dev_gb} GB storage device is already in use, but it is smaller"
+ echo "than the 64 GB minimum RMS needs for data capture (128 GB or more is"
+ echo "recommended). Please consider moving to a larger storage device."
+ read -p "Press ENTER to continue anyway..."
+else
+ echo ""
+ echo "${fs_gb} GB of space is available, no expansion needed."
fi
+fi # raspi-config available
+
echo ""
echo ""
@@ -157,11 +236,13 @@ echo ""
echo "1) Internet connection"
echo "----------------------"
echo "Checking if connected to the Internet..."
-if ping -c 1 $IP &> /dev/null
+# Some networks block ping, so fall back to an HTTP check of the server
+# the update needs to reach anyway
+if ping -c 1 $IP &> /dev/null || wget -q --spider --timeout=10 https://github.com
then
echo "Success!"
else
- echo "The device is not connected to the internet! Please connect the device to the Internet to proceed!"
+ echo "The device is not connected to the internet! Please connect the device to the Internet to proceed!"
read -p "Press ENTER to continue..."
exit 1
fi
@@ -172,7 +253,7 @@ echo "--------------------------------"
echo "The default password is either 'raspberry' or 'rmsraspberry'. Please change it so nobody can connect to your Raspberry Pi and hack the computers on your network!"
echo ""
-read -n1 -r -p 'Press ENTER to change the password (recommended), or Q to skip this step...' key
+read -n1 -r -p 'Press ENTER to change the password (recommended), or any other key to skip this step...' key
if [[ "$key" = "" ]]; then
passwd
@@ -183,17 +264,27 @@ echo ""
echo "3) Generating a new SSH key"
echo "---------------------------"
-read -n1 -r -p 'Press ENTER to generate the SSH key (recommended), or Q to skip this step...' key
+read -n1 -r -p 'Press ENTER to generate the SSH key (recommended), or any other key to skip this step...' key
if [[ "$key" = "" ]]; then
- echo ""
- echo "Generating a new SSH key..."
- # Generate an SSH key without a passphrase
- yes y | ssh-keygen -t rsa -m PEM -N "" -f ~/.ssh/id_rsa >/dev/null
+ if [[ -f ~/.ssh/id_rsa ]]; then
+
+ # Never overwrite an existing key - GMN already has its public half on
+ # file, and replacing it would break the data uploads
+ echo ""
+ echo "An SSH key already exists in ~/.ssh, not overwriting it."
+
+ else
+ echo ""
+ echo "Generating a new SSH key..."
+
+ # Generate an SSH key without a passphrase
+ ssh-keygen -t rsa -m PEM -N "" -f ~/.ssh/id_rsa >/dev/null
+ fi
# Link the public SSH key to desktop
- ln -s ~/.ssh/id_rsa.pub ~/Desktop/id_rsa.pub
+ ln -sf ~/.ssh/id_rsa.pub ~/Desktop/id_rsa.pub
echo ""
echo "A file called id_rsa.pub appeared on Desktop, please send this file to Denis "
@@ -208,50 +299,148 @@ echo "Updating to the latest version of RMS..."
bash $RMSUPDATESCRIPT
echo ""
-echo "4) Editing the configuration file"
-echo "---------------------------------"
-echo "Finally, edit the RMS configuration file."
-echo "Put in the station code/ID and the geo coordinates of your camera, save the"
-echo "file and you are good to go!"
+echo "4) Station configuration"
+echo "------------------------"
+echo "Enter the station ID and the geo coordinates of your camera."
echo ""
echo "
-If you need to make changes to the configuration file in the future, you can
-find a shortcut to it on desktop (RMS_config), or open it directly in
-$RMSCONFIG.
+If you need to change any other setting in the future, you can find a
+shortcut to the configuration file on desktop (RMS_config), or open it
+directly in $RMSCONFIG.
"
-read -p "The configuration file will open for editing after you press ENTER..."
-
-
-editRMSConfig
-
+while true; do
-echo ""
+ # Station ID
+ while true; do
+ flushInput
+ read -r -p "Station ID (e.g. US01AB): " statID || exit 1
+ statID=$(trimSpaces "$statID" | tr '[:lower:]' '[:upper:]')
+ if [[ "$statID" =~ ^[A-Z]{2}[A-Z0-9]{4}$ && "$statID" != "XX0001" ]]; then
+ break
+ fi
+ echo " A station code has 2 country letters followed by 4 characters, e.g. US01AB."
+ # Only offer to keep a nonstandard entry when it is still safe to use
+ # as a directory name and a .config value
+ if [[ "$statID" =~ ^[A-Z0-9_-]+$ ]]; then
+ read -n1 -r -p " Use '$statID' anyway? Press Y to accept it, any other key to retype... " key
+ echo ""
+ if [[ "$key" = "y" || "$key" = "Y" ]]; then
+ break
+ fi
+ fi
+ done
+
+ # Latitude
+ while true; do
+ flushInput
+ read -r -p "Latitude (+N, in degrees, e.g. 40.689298): " lat || exit 1
+ lat=$(trimSpaces "$lat")
+ isNumberInRange "$lat" -90 90 && break
+ echo " The latitude must be a number between -90 and 90."
+ done
+
+ # Longitude
+ while true; do
+ flushInput
+ read -r -p "Longitude (+E, in degrees, NEGATIVE in the western hemisphere, e.g. -74.044479): " lon || exit 1
+ lon=$(trimSpaces "$lon")
+ isNumberInRange "$lon" -180 180 && break
+ echo " The longitude must be a number between -180 and 180."
+ done
+
+ # Elevation
+ while true; do
+ flushInput
+ read -r -p "Elevation (mean sea level, in meters, NOT feet): " elev || exit 1
+ elev=$(trimSpaces "$elev")
+ isNumberInRange "$elev" -500 9000 && break
+ echo " The elevation must be a number in meters, e.g. 95.3."
+ done
+
+ echo "
+Station ID: $statID
+Latitude: $lat
+Longitude: $lon
+Elevation: $elev m
+"
+ read -n1 -r -p 'Press ENTER to save these values, or R to re-enter them... ' key
+ echo ""
+ if [[ "$key" = "" ]]; then
+ break
+ fi
+done
+# Write the values into the config file
+sed -i "s/^stationID:.*$/stationID: $statID/" $RMSCONFIG
+sed -i "s/^latitude:.*$/latitude: $lat/" $RMSCONFIG
+sed -i "s/^longitude:.*$/longitude: $lon/" $RMSCONFIG
+sed -i "s/^elevation:.*$/elevation: $elev/" $RMSCONFIG
-while true; do
+echo "Configuration saved to $RMSCONFIG"
-# Check if the config file was changed
-statID=$(grep stationID $RMSCONFIG | cut -d ":" -f 2 | xargs)
+# Offer the conversion to the multi-camera data structure, unless the
+# system already uses it
+if [[ ! -d ~/source/Stations ]]; then
+echo "
+5) Converting the data structure
+--------------------------------
+The new recommended data structure keeps important camera files in a safer
+place. Converting to this multi-camera data structure is recommended for
+both single and multiple camera systems.
+
+The important files (.config, mask.bmp and platepar_cmn2010.cal) will be
+copied to ~/source/Stations/, and captured data will be stored
+in ~/RMS_data/, where is the unique station code
+you were given by GMN. Nothing in ~/source/RMS is modified.
+"
+sleep 1
-if [ "$statID" = "XX0001" ]; then
-
- echo "The config file was not changed!"
- echo "Please change the station ID and the geo coordinates in the config file!"
+# clear the input buffer
+while read -t 0.01; do :; done
- editRMSConfig
+read -n1 -r -p 'Press ENTER to convert to the new data structure, or any other key to stay with the legacy structure... ' key
+if [[ "$key" != "" ]]; then
+ echo ""
+ echo ""
+ echo "Are you sure you want to stay with the legacy structure?"
+ read -n1 -r -p 'Press any key to confirm, or ENTER to convert to the new data structure... ' key
+ echo ""
+fi
+if [[ "$key" = "" ]]; then
+ echo ""
+ echo "Next, this script will run to convert to the recommended data structure"
+ echo "(it will show the details and ask you to confirm):"
+ echo " ~/source/RMS/Scripts/add_Station.sh"
+ echo ""
+ echo "Note: you can run the same script later to add more cameras."
+ echo ""
+ sleep 2
+ bash ~/source/RMS/Scripts/add_Station.sh
else
- break
+ echo ""
+ echo "You have chosen to keep the legacy data structure."
+ echo "If you decide to convert in the future, run this command from a terminal:"
+ echo " ~/source/RMS/Scripts/add_Station.sh"
fi
-done
-# Enable autorun
+fi # if no ~/source/Stations yet
+
+# Enable autorun. This is done only after the optional data structure
+# conversion so an interrupted conversion does not get skipped over by
+# the autorun path on the next boot.
echo "1" > $RMSAUTORUNFILE
+sleep 2
+echo ""
+echo "Configuration is done"
+echo ""
+read -n1 -r -p 'Press ENTER when you are ready to start camera data capture... '
+echo ""
# If the configuration was done, run recording
bash $RMSSTARTCAPTURE
diff --git a/Scripts/RMS_Installer.sh b/Scripts/RMS_Installer.sh
index 4dde5a263..03cc8c12a 100755
--- a/Scripts/RMS_Installer.sh
+++ b/Scripts/RMS_Installer.sh
@@ -59,6 +59,14 @@ sudo systemctl enable chrony
sudo systemctl start ssh
sudo systemctl enable ssh
+# On GNOME desktops, prevent sleep and screen lock so captures are not interrupted
+if command -v gsettings >/dev/null 2>&1; then
+ gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' 2>/dev/null
+ gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0 2>/dev/null
+ gsettings set org.gnome.desktop.session idle-delay 600 2>/dev/null
+ gsettings set org.gnome.desktop.screensaver lock-enabled false 2>/dev/null
+fi
+
# Clone repositories
git clone https://github.com/CroatianMeteorNetwork/RMS.git
git clone https://github.com/CroatianMeteorNetwork/cmn_binviewer.git
diff --git a/Scripts/RMS_StartCapture.sh b/Scripts/RMS_StartCapture.sh
index 677e6e5b3..b501737a2 100755
--- a/Scripts/RMS_StartCapture.sh
+++ b/Scripts/RMS_StartCapture.sh
@@ -1,26 +1,74 @@
#!/bin/bash
+
+# Starts capture on either supported data structure:
+# - multi-camera (~/source/Stations has station directories): every
+# configured station is started in its own terminal, staggered so
+# concurrent first-start updates cannot collide
+# - legacy: the single camera is started in this terminal
+
echo "Starting RMS..."
sleep 10
-source ~/vRMS/bin/activate
-cd ~/source/RMS
-# Init log file
-LOGPATH=~/RMS_data/logs/
-LOGDATE=$(date +"%Y%m%d_%H%M%S")
-LOGSUFFIX="_log.txt"
-LOGFILE=$LOGPATH$LOGDATE$LOGSUFFIX
+dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
+
+if [[ $dircount -gt 0 ]]; then
+
+ # On a single camera system concurrent first-start updates are no
+ # risk, so the long stagger delay is skipped
+ if [[ $dircount -le 1 ]]; then
+ seconds=2
+ else
+ seconds=70
+ fi
+
+ # Detect the terminal emulator
+ if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+ elif command -v lxterminal >/dev/null 2>&1; then
+ TERMINAL=lxterminal
+ else
+ TERMINAL=gnome-terminal
+ fi
+
+ echo " Starting all configured stations..."
+
+ loop=0
+ for Dir in ~/source/Stations/*/
+ do
+ Station=$(basename "$Dir")
+ echo " Starting camera ${Station}"
+ if [[ "$TERMINAL" == "gnome-terminal" ]]; then
+ gnome-terminal --profile=StartCapture --title=${Station} -- bash -c "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ else
+ lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ fi
+ echo " waiting $seconds seconds..."
+ sleep ${seconds}
+ if [[ $loop = 0 ]] ; then
+ seconds=10
+ fi
+ let loop++
+ done
+ echo " All cameras started"
+
+else
+
+ # Legacy single-camera data structure
+ source ~/vRMS/bin/activate
+ cd ~/source/RMS
-mkdir -p $LOGPATH
+ mkdir -p ~/RMS_data/logs/
-echo ""
-echo ""
-echo "If you need to update the RMS config file, you can do it now."
-echo "Any changes to the config file will be read only after this script is started again or the Pi is rebooted."
-echo ""
-sleep 5
+ echo ""
+ echo ""
+ echo "If you need to update the RMS config file, you can do it now."
+ echo "Any changes to the config file will be read only after this script is started again or the Pi is rebooted."
+ echo ""
+ sleep 5
-python -m RMS.StartCapture "$@"
+ python -m RMS.StartCapture "$@"
-read -p "Press any key to continue... "
+ read -p "Press any key to continue... "
-$SHELL
+ $SHELL
+fi
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
new file mode 100755
index 000000000..2fad718e8
--- /dev/null
+++ b/Scripts/add_Station.sh
@@ -0,0 +1,539 @@
+#!/bin/bash
+# This software is part of the Linux port of RMS
+# Copyright (C) 2023 Ed Harman
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# Universal station tool for any Linux platform, Raspberry Pi included.
+# Merged from add_Pi_Station.sh and add_GStation.sh, which are kept as
+# compatibility wrappers around this script.
+#
+# What it does:
+# - converts a legacy single-camera setup (config files in ~/source/RMS,
+# data directly in ~/RMS_data) into the per-station layout under
+# ~/source/Stations/ and ~/RMS_data/
+# - creates new stations, either interactively or in bulk from a CSV file
+#
+# Usage: add_Station.sh [RMS_data_path] [stations.csv]
+#
+# The CSV header row names the columns: station_id (required), camera_ip
+# (spliced into the rtsp device URL), and any other .config key, which is
+# applied verbatim to that station's config. With no CSV argument the
+# script uses ~/source/RMS/stations.csv if present, otherwise it prompts
+# for station IDs interactively. A sample to copy and edit is provided in
+# ~/source/RMS/stations_template.csv.
+#
+# On Raspberry Pi platforms a recommended camera limit applies (1 camera,
+# or 4 on a Pi5 or later). The limit is advisory - it reflects what has
+# been tested - and can be overridden at the prompt.
+
+# ---------------------------------------------------------------- helpers
+
+# Strip leading/trailing whitespace (including CR from Windows-edited CSVs)
+trim() {
+ local s="$*"
+ s="${s#"${s%%[![:space:]]*}"}"
+ s="${s%"${s##*[![:space:]]}"}"
+ printf '%s' "$s"
+}
+
+uppercase() {
+ printf '%s' "$*" | tr '[:lower:]' '[:upper:]'
+}
+
+count_stations() {
+ find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l
+}
+
+# Build the Exec= line for a .desktop entry using the detected terminal.
+# The gnome-terminal StartCapture profile is optional - create one to
+# customise the capture windows; without it the default profile is used.
+term_exec() { # $1 = window title, $2 = command
+ if [[ "$TERMINAL" == "gnome-terminal" ]]; then
+ printf 'gnome-terminal --profile=StartCapture --title=%s -- bash -c "%s"' "$1" "$2"
+ else
+ printf 'lxterminal --title=%s -e "%s"' "$1" "$2"
+ fi
+}
+
+# Create a station directory with config, mask and launch entries.
+# Returns 1 if the station already exists.
+create_station() {
+ local item="$1"
+ local config_file=~/source/Stations/${item}/.config
+ local entry
+
+ if [[ -d ~/source/Stations/${item} ]]; then
+ echo -e "\nNot creating station ${item} - it already exists"
+ return 1
+ fi
+
+ echo "Creating station ${item}..."
+ mkdir -p ~/source/Stations/${item}
+ mkdir -p "${RMS_data}/${item}"
+ mkdir -p ~/.config/autostart "${Desktop}"
+
+ # Copy config template
+ cp ~/source/RMS/.config ~/source/Stations/${item}
+
+ # Copy mask file
+ cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
+
+ # Launch entry for the station. On the Pi images, FirstRun runs at boot
+ # and starts captures through RMS_StartCapture.sh, so only a Desktop
+ # launcher is created there; elsewhere the entry goes into XDG autostart
+ # with a Desktop symlink, so captures start at login. (A PC may carry the
+ # autorun flag from running FirstRun, but nothing launches FirstRun at
+ # boot on a PC, hence the additional Pi check.)
+ if [[ -f ~/.rmsautorunflag && $RECOMMENDED -gt 0 ]]; then
+ entry="${Desktop}/${item}_StartCapture.desktop"
+ else
+ entry=~/.config/autostart/${item}_StartCapture.desktop
+ fi
+
+ cat <<- EOF > "$entry"
+ [Desktop Entry]
+ Name=${item}-StartCapture
+ Type=Application
+ Exec=$(term_exec "${item}" "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}")
+ Hidden=false
+ NoDisplay=false
+ Icon=${TERMINAL}
+ EOF
+ chmod +x "$entry"
+
+ if [[ "$entry" != "${Desktop}/"* ]]; then
+ ln -sf "$entry" "${Desktop}/${item}_StartCapture.desktop"
+ gio set "${Desktop}/${item}_StartCapture.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/${item}_StartCapture.desktop"
+ fi
+
+ # ShowLiveStream desktop shortcut
+ cat <<- EOF > "${Desktop}/${item}-ShowLiveStream.desktop"
+ [Desktop Entry]
+ Name=${item}-ShowLiveStream
+ Type=Application
+ Exec=$(term_exec "Stream-${item}" "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}")
+ Hidden=false
+ NoDisplay=false
+ Icon=${TERMINAL}
+ EOF
+ gio set "${Desktop}/${item}-ShowLiveStream.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/${item}-ShowLiveStream.desktop"
+
+ # Customise the config
+ sed -i "s/^stationID:.*$/stationID: $item/g" "${config_file}"
+ sed -i "s,^data_dir:.*$,data_dir: ${RMS_data}/${item},g" "${config_file}"
+
+ return 0
+}
+
+# Convert a legacy single-camera setup to the per-station layout by copying
+migrate_legacy() {
+
+cat <.
+
+Your configured station ${DefStation} will get copies of:
+- .config
+- platepar_cmn2010.cal
+- mask.bmp
+in ~/source/Stations/${DefStation}
+
+Nothing in ~/source/RMS is modified, and camera_settings.json is shared
+by all stations from there.
+
+Any previously captured data will be moved from
+${RMS_data}
+to
+${RMS_data}/${DefStation}
+EOF
+
+ read -n1 -r -p 'Press ENTER to continue with the conversion, or any other key to skip it: ' key
+ echo ""
+ if [[ "$key" != "" ]]; then
+ echo "Skipping the conversion"
+ return
+ fi
+
+ create_station "${DefStation}" || return
+
+ # the .config and mask copies are made by create_station
+ if [[ -e ~/source/RMS/platepar_cmn2010.cal ]]; then
+ cp ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
+ fi
+
+ # move any existing captured data into the station folder
+ find "${RMS_data}" -mindepth 1 -maxdepth 1 ! -name "${DefStation}" -exec mv -t "${RMS_data}/${DefStation}/" {} +
+
+ # Pi image cosmetics - skipped when the files are not present
+ if [[ -f ~/.conkyrc1 ]]; then
+ # tweak conky station title and data source path for log data
+ sed -i 's/\(source\)\/RMS/\1\/Stations\/'"$DefStation"'/g' ~/.conkyrc1
+ sed -i "s/\(.*RMS_data\)/\1\/${DefStation}/g" ~/.conkyrc1
+ fi
+
+ # cleanup single-camera Desktop shortcuts from the Pi image
+ if [[ -f ~/Desktop/RMS_FirstRun.sh ]]; then
+ rm -f ~/Desktop/CMNbinViewer.sh ~/Desktop/RMS_ShowLiveStream.sh
+ rm -f ~/Desktop/RMS_StartCapture.sh ~/Desktop/RMS_config.txt
+ rm -f ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
+ fi
+
+ echo ""
+ echo "Conversion of station ${DefStation} complete"
+}
+
+# ------------------------------------------------------------------ main
+
+# Parse arguments: [RMS_data_path] [stations.csv]
+# Smart detection: if single arg ends in .csv, treat as CSV file
+RMS_data=~/RMS_data
+CSV_FILE=""
+
+if [[ "$#" -eq 0 ]]; then
+ # No args: use the default CSV if one exists
+ if [[ -f ~/source/RMS/stations.csv ]]; then
+ CSV_FILE=~/source/RMS/stations.csv
+ fi
+elif [[ "$#" -eq 1 ]]; then
+ # Single arg: check if it's a CSV file or a directory
+ if [[ "${1}" == *.csv ]]; then
+ CSV_FILE="${1}"
+ else
+ RMS_data="${1}"
+ if [[ -f ~/source/RMS/stations.csv ]]; then
+ CSV_FILE=~/source/RMS/stations.csv
+ fi
+ fi
+else
+ # Two args: first is RMS_data, second is CSV
+ RMS_data="${1}"
+ CSV_FILE="${2}"
+fi
+
+# A CSV that was explicitly requested must exist
+if [[ -n "${CSV_FILE}" && ! -f "${CSV_FILE}" ]]; then
+ echo "Error: CSV file not found: ${CSV_FILE}"
+ echo "Usage: $0 [RMS_data_path] [stations.csv]"
+ echo "A sample to copy and edit is provided in ~/source/RMS/stations_template.csv"
+ exit 1
+fi
+
+# Detect the platform. On Raspberry Pi a recommended camera limit applies:
+# 1 camera, or 4 on a Pi5 or later (the tested ceiling). The limit is
+# advisory and can be overridden at the prompt.
+PI_MODEL_FILE="${RMS_PI_MODEL_FILE:-/sys/firmware/devicetree/base/model}"
+RECOMMENDED=0 # 0 = no recommendation (generic Linux)
+if [[ -f "$PI_MODEL_FILE" ]]; then
+ model=$(tr -d '\0' < "$PI_MODEL_FILE")
+ if [[ "$model" == *'Raspberry'* ]]; then
+ pi_num=$(printf '%s' "$model" | grep -oE '[0-9]+' | head -1)
+ if [[ -n "$pi_num" && "$pi_num" -ge 5 ]]; then
+ RECOMMENDED=4
+ else
+ RECOMMENDED=1
+ fi
+ echo "Detected: ${model} (recommended maximum: ${RECOMMENDED} camera(s))"
+ fi
+fi
+
+# Detect the terminal emulator for launch entries
+if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+elif command -v lxterminal >/dev/null 2>&1; then
+ TERMINAL=lxterminal
+elif command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+else
+ TERMINAL=lxterminal
+ echo "Warning: neither lxterminal nor gnome-terminal was found, launch entries will use lxterminal"
+fi
+
+# Get user's desktop directory
+Desktop=$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")
+
+DefStation=$(uppercase "$(awk '/^stationID:/ {print $2}' ~/source/RMS/.config 2>/dev/null)")
+
+# A configured legacy single-camera setup is offered conversion first
+if [[ ! -d ~/source/Stations && -n "$DefStation" && "$DefStation" != "XX0001" ]]; then
+ migrate_legacy
+elif [[ -f ~/.rmsautorunflag && "$DefStation" == "XX0001" && ! -d ~/source/Stations ]]; then
+ echo "Please run RMS_FirstRun and configure your 1st station,"
+ echo "then run add_Station again to convert it or add more cameras."
+ exit
+fi
+
+# Ensure required directories exist
+mkdir -p ~/source/Stations
+mkdir -p "${RMS_data}"
+mkdir -p ~/.config/autostart
+
+if [[ -n "${CSV_FILE}" ]]; then
+
+ echo "Reading stations from: ${CSV_FILE}"
+
+ # Read CSV header to get column names
+ IFS=',' read -r -a HEADERS < "${CSV_FILE}"
+
+ # Trim whitespace from headers
+ for i in "${!HEADERS[@]}"; do
+ HEADERS[$i]=$(trim "${HEADERS[$i]}")
+ done
+
+ # Find required column indices
+ station_id_col=-1
+ camera_ip_col=-1
+ for i in "${!HEADERS[@]}"; do
+ case "${HEADERS[$i]}" in
+ station_id|stationID) station_id_col=$i ;;
+ camera_ip) camera_ip_col=$i ;;
+ esac
+ done
+
+ if [[ $station_id_col -eq -1 ]]; then
+ echo "Error: CSV must have a 'station_id' or 'stationID' column"
+ exit 1
+ fi
+
+ No_Stations=$(tail -n +2 "${CSV_FILE}" | grep -c -v '^[[:space:]]*$')
+ echo -e "\nStations to add from CSV: ${No_Stations}"
+
+ # Warn when the CSV would exceed the recommended camera limit
+ if [[ $RECOMMENDED -gt 0 && $(( $(count_stations) + No_Stations )) -gt $RECOMMENDED ]]; then
+ echo ""
+ echo "This would exceed the recommended maximum of ${RECOMMENDED} camera(s) for this platform"
+ echo "(the tested limit - more cameras may drop frames during capture and processing)."
+ read -n1 -r -p 'Press Y to continue anyway, any other key to quit: ' key
+ echo ""
+ if [[ "$key" != "y" && "$key" != "Y" ]]; then
+ exit
+ fi
+ fi
+
+ # Process each data row (the || keeps a final line without a trailing newline)
+ tail -n +2 "${CSV_FILE}" | while IFS=',' read -r -a VALUES || [[ ${#VALUES[@]} -gt 0 ]]; do
+ # Skip empty lines
+ [[ ${#VALUES[@]} -eq 0 ]] && continue
+
+ # Trim whitespace from values
+ for i in "${!VALUES[@]}"; do
+ VALUES[$i]=$(trim "${VALUES[$i]}")
+ done
+
+ item="${VALUES[$station_id_col]}"
+ [[ -z "$item" ]] && continue
+
+ # reject IDs that would break directory names or the sed edits
+ if [[ ! "$item" =~ ^[A-Za-z0-9_-]+$ ]]; then
+ echo "Skipping invalid station ID from CSV: ${item}"
+ continue
+ fi
+
+ create_station "$item" || continue
+
+ config_file=~/source/Stations/${item}/.config
+
+ # Apply camera_ip to device URL if column exists
+ if [[ $camera_ip_col -ne -1 && -n "${VALUES[$camera_ip_col]}" ]]; then
+ camera_ip="${VALUES[$camera_ip_col]}"
+ # Replace IP address in the device URL (matches IP pattern in rtsp:// URL)
+ sed -i -E "s|^(device:.*rtsp://)[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|\1${camera_ip}|g" "${config_file}"
+ echo " Set camera IP: ${camera_ip}"
+ fi
+
+ # Apply any additional CSV columns that match .config keys
+ for i in "${!HEADERS[@]}"; do
+ header="${HEADERS[$i]}"
+ value="${VALUES[$i]}"
+
+ # Skip station_id, camera_ip (already handled), and empty values
+ [[ "$header" == "station_id" || "$header" == "stationID" || "$header" == "camera_ip" ]] && continue
+ [[ -z "$value" ]] && continue
+
+ # Check if this header exists as a key in .config
+ if grep -q "^${header}:" "${config_file}"; then
+ sed -i "s|^${header}:.*$|${header}: ${value}|g" "${config_file}"
+ echo " Set ${header}: ${value}"
+ fi
+ done
+
+ echo "Added station ${item}"
+ done
+
+else
+
+ # No CSV available - prompt for station IDs interactively
+ declare -a Station
+ while :
+ do
+ # Advise when the recommended camera limit is reached, allow override
+ if [[ $RECOMMENDED -gt 0 && $(( $(count_stations) + ${#Station[@]} )) -ge $RECOMMENDED ]]; then
+ echo ""
+ echo "The recommended maximum of ${RECOMMENDED} camera(s) for this platform is configured"
+ echo "(the tested limit - more cameras may drop frames during capture and processing)."
+ read -n1 -r -p 'Press ENTER to finish, or A to add another camera anyway: ' key
+ echo ""
+ if [[ "$key" != "a" && "$key" != "A" ]]; then
+ break
+ fi
+ fi
+ # drop buffered keypresses (e.g. the tail of an arrow key escape
+ # sequence left by a single-key read) before taking a typed entry
+ while read -t 0.01; do :; done
+ read -r -p "Enter station ID, to end: " this_Station
+ this_Station=$(uppercase "$(trim "$this_Station")")
+ if [[ -z "$this_Station" ]]; then
+ break
+ fi
+ if [[ ! "$this_Station" =~ ^[A-Z]{2}[A-Z0-9]{4}$ ]]; then
+ echo " A station code has 2 country letters followed by 4 characters, e.g. US01AB."
+ # Only offer to keep a nonstandard entry when it is still safe
+ # to use as a directory name and a .config value
+ if [[ "$this_Station" =~ ^[A-Z0-9_-]+$ ]]; then
+ read -n1 -r -p " Use '${this_Station}' anyway? Press Y to accept it, any other key to retype: " key
+ echo ""
+ if [[ "$key" != "y" && "$key" != "Y" ]]; then
+ continue
+ fi
+ else
+ continue
+ fi
+ fi
+ Station+=("$this_Station")
+ done
+
+ if [[ ${#Station[@]} -gt 0 ]]; then
+ echo -e "\nNew stations to add -"
+ printf '%s\n' "${Station[@]}"
+ fi
+
+ for item in "${Station[@]}"; do
+ create_station "$item" && echo "Added station ${item}"
+ done
+
+fi
+
+# -------------------------------------------------- per-platform settings
+
+total=$(count_stations)
+
+# Recompute the per-station settings from the current station count on
+# every run, so cameras added (or removed) since the last run are
+# accounted for
+for dir in ~/source/Stations/*/; do
+ [[ -f "${dir}/.config" ]] || continue
+ station=$(basename "$dir")
+
+ # Remove launch entries left over from the pre-merge per-platform
+ # scripts - their different file names would otherwise double-launch
+ # the station at login
+ rm -f ~/.config/autostart/"${station}_StartCap.desktop" \
+ "${Desktop}/${station}_StartCap.desktop" \
+ "${Desktop}/Show_LiveStream-${station}.desktop" \
+ "${Desktop}/${station}-Show_LiveStream.desktop"
+
+ if [[ $total -gt 1 ]]; then
+ # scale the reserved free space with the number of stations
+ sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( total * 20 ))/g" "${dir}/.config"
+ # disable the daily post processing reboot, it would kill the other captures
+ sed -i "s/^\(reboot_after_processing:\).*/\1 false/g" "${dir}/.config"
+ else
+ # a single camera keeps the standard daily post processing reboot
+ sed -i "s/^\(reboot_after_processing:\).*/\1 true/g" "${dir}/.config"
+ fi
+done
+
+# remove comment from last line of wayfire.ini to enable window cascade (Pi image)
+if [[ $total -gt 1 && -f ~/.config/wayfire.ini ]]; then
+ sed -i s/#mode/mode/ ~/.config/wayfire.ini
+fi
+
+# Make sure the Desktop StartCapture entry points at RMS_StartCapture.sh,
+# which handles both the legacy and the multi-camera data structure (the
+# entry may be a stale copy rather than a symlink on older installs).
+# On the Pi images FirstRun also runs this launcher at boot.
+ln -sf ~/source/RMS/Scripts/RMS_StartCapture.sh "${Desktop}/RMS_StartCapture.sh"
+
+# Generate SSH keys if not present
+if [[ ! -f ~/.ssh/id_rsa ]]; then
+ ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
+ cp ~/.ssh/id_rsa.pub "${Desktop}/"
+ echo "SSH keys generated in ~/.ssh"
+ echo "Your new id_rsa.pub public key file is now placed on the Desktop."
+ echo "Be sure to send a copy of this file to Denis"
+fi
+
+# Add RMS activation to .bashrc if not present
+if ! grep -q "source ~/vRMS/bin/activate" ~/.bashrc; then
+ cat <> ~/.bashrc
+
+# Activate RMS
+cd ~/source/RMS
+source ~/vRMS/bin/activate
+EOF
+fi
+
+# Create CMNbinViewer desktop shortcut
+cat <<- EOF > "${Desktop}/CMNbinViewer.desktop"
+[Desktop Entry]
+Name=CMNbinViewer
+Type=Application
+Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
+Hidden=false
+NoDisplay=false
+Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
+EOF
+gio set "${Desktop}/CMNbinViewer.desktop" metadata::trusted true 2>/dev/null
+chmod +x "${Desktop}/CMNbinViewer.desktop"
+
+# Create Desktop shortcut for editing the station .config files
+cat << 'EOF' > "${Desktop}/Edit_configs"
+#!/bin/bash
+
+echo ""
+echo "One by one, each config file under ~/source/Stations will open for editing..."
+read -n1 -r -p 'Press ENTER to proceed, any other key to quit: ' key
+if [[ "${key}" != "" ]]; then
+ exit
+fi
+
+editor=""
+for e in mousepad leafpad gedit gnome-text-editor nano; do
+ if command -v "$e" >/dev/null 2>&1; then
+ editor="$e"
+ break
+ fi
+done
+
+for Dir in ~/source/Stations/*/
+do
+ Station=$(basename "${Dir}")
+ echo ""
+ echo "Editing .config file for ${Station}"
+ read -n1 -r -p 'Press ENTER to edit, any other key to skip: ' key
+ if [[ "${key}" = "" ]]; then
+ "$editor" ~/source/Stations/${Station}/.config
+ fi
+done
+
+echo ""
+echo "Done editing .config files"
+sleep 2
+EOF
+chmod +x "${Desktop}/Edit_configs"
+
+echo -e "\nStation configuration complete"
diff --git a/stations_template.csv b/stations_template.csv
new file mode 100644
index 000000000..24345f151
--- /dev/null
+++ b/stations_template.csv
@@ -0,0 +1,7 @@
+station_id,camera_ip,capture_wait_seconds
+XX0001,192.168.42.101,0
+XX0002,192.168.42.102,30
+XX0003,192.168.42.103,60
+XX0004,192.168.42.104,90
+XX0005,192.168.42.105,120
+XX0006,192.168.42.106,150