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