first commit

This commit is contained in:
2022-08-24 08:18:01 +02:00
commit d3831d09c4
1124 changed files with 6738 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
*.pyc
.DS_Store
status.wav
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Armel FAUVEAU
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+997
View File
@@ -0,0 +1,997 @@
###############################################################################
#
# Generic Logic event handlers
#
###############################################################################
#
# This is the namespace in which all functions and variables below will exist.
#
namespace eval Logic {
#
# A variable used to store a timestamp for the last identification.
#
variable prev_ident 0;
#
# A constant that indicates the minimum time in seconds to wait between two
# identifications. Manual and long identifications is not affected.
#
variable min_time_between_ident 120;
#
# Short and long identification intervals. They are setup from config
# variables below.
#
variable short_ident_interval 0;
variable long_ident_interval 0;
variable short_voice_id_enable 1
variable short_cw_id_enable 0
variable short_announce_enable 0
variable short_announce_file ""
variable long_voice_id_enable 1
variable long_cw_id_enable 0
variable long_announce_enable 0
variable long_announce_file ""
#
# The ident_only_after_tx variable indicates if identification is only to
# occur after the node has transmitted. The variable is setup below from the
# configuration variable with the same name.
# The need_ident variable indicates if identification is needed.
#
variable ident_only_after_tx 0;
variable need_ident 0;
#
# List of functions that should be called periodically. Use the
# addMinuteTickSubscriber and addSecondTickSubscriber functions to
# add subscribers.
#
variable minute_tick_subscribers [list];
variable second_tick_subscribers [list];
#
# Contains the ID of the last receiver that indicated squelch activity
#
variable sql_rx_id "?";
#
# Executed when the SvxLink software is started
#
proc startup {} {
#playMsg "Core" "online"
#send_short_ident
variable room;
set fp [open "/etc/spotnik/network" "r"];
set room [string tolower [gets $fp]]
puts "**** Salon: $room ****";
if {"$room" != "default"} {
playMsg "RRF" "S$room"
}
}
#
# Executed when a specified module could not be found
# module_id - The numeric ID of the module
#
proc no_such_module {module_id} {
playMsg "Core" "no_such_module";
playNumber $module_id;
}
#
# Executed when a manual identification is initiated with the * DTMF code
#
proc manual_identification {} {
global mycall;
global report_ctcss;
global active_module;
global loaded_modules;
variable CFG_TYPE;
variable prev_ident;
set epoch [clock seconds];
set hour [clock format $epoch -format "%k"];
regexp {([1-5]?\d)$} [clock format $epoch -format "%M"] -> minute;
set prev_ident $epoch;
playMsg "Core" "online";
spellWord $mycall;
if {$CFG_TYPE == "Repeater"} {
playMsg "Core" "repeater";
}
playSilence 250;
playMsg "Core" "the_time_is";
playTime $hour $minute;
playSilence 250;
if {$report_ctcss > 0} {
playMsg "Core" "pl_is";
playFrequency $report_ctcss
playSilence 300;
}
if {$active_module != ""} {
playMsg "Core" "active_module";
playMsg $active_module "name";
playSilence 250;
set func "::";
append func $active_module "::status_report";
if {"[info procs $func]" ne ""} {
$func;
}
} else {
foreach module [split $loaded_modules " "] {
set func "::";
append func $module "::status_report";
if {"[info procs $func]" ne ""} {
$func;
}
}
}
playMsg "Default" "press_0_for_help"
playSilence 250;
}
#
# Executed when a short identification should be sent
# hour - The hour on which this identification occur
# minute - The minute on which this identification occur
#
proc send_short_ident {{hour -1} {minute -1}} {
global mycall;
global report_ctcss;
variable CFG_TYPE;
variable short_announce_file
variable short_announce_enable
variable short_voice_id_enable
variable short_cw_id_enable
# Play voice id if enabled
if {$short_voice_id_enable} {
puts "Playing short voice ID"
spellWord $mycall;
if {$CFG_TYPE == "Repeater"} {
playMsg "Core" "repeater";
}
playSilence 500;
if {$report_ctcss > 0} {
playMsg "Core" "pl_is";
playFrequency $report_ctcss
playSilence 300;
}
}
# Play announcement file if enabled
if {$short_announce_enable} {
puts "Playing short announce"
if [file exist "$short_announce_file"] {
playFile "$short_announce_file"
playSilence 500
}
}
# Play CW id if enabled
if {$short_cw_id_enable} {
puts "Playing short CW ID"
if {$CFG_TYPE == "Repeater"} {
set call "$mycall/R"
CW::play $call
} else {
CW::play $mycall
}
playSilence 500;
}
}
#
# Executed when a long identification (e.g. hourly) should be sent
# hour - The hour on which this identification occur
# minute - The minute on which this identification occur
#
proc send_long_ident {hour minute} {
global mycall;
global loaded_modules;
global active_module;
global report_ctcss;
variable CFG_TYPE;
variable long_announce_file
variable long_announce_enable
variable long_voice_id_enable
variable long_cw_id_enable
# Play the voice ID if enabled
if {$long_voice_id_enable} {
puts "Playing Long voice ID"
spellWord $mycall;
if {$CFG_TYPE == "Repeater"} {
playMsg "Core" "repeater";
}
playSilence 500;
if {$report_ctcss > 0} {
playMsg "Core" "pl_is";
playFrequency $report_ctcss
playSilence 300;
}
playMsg "Core" "the_time_is";
playSilence 100;
playTime $hour $minute;
playSilence 500;
# Call the "status_report" function in all modules if no module is active
if {$active_module == ""} {
foreach module [split $loaded_modules " "] {
set func "::";
append func $module "::status_report";
if {"[info procs $func]" ne ""} {
$func;
}
}
}
playSilence 500;
}
# Play announcement file if enabled
if {$long_announce_enable} {
puts "Playing long announce"
if [file exist "$long_announce_file"] {
playFile "$long_announce_file"
playSilence 500
}
}
# Play CW id if enabled
if {$long_cw_id_enable} {
puts "Playing long CW ID"
if {$CFG_TYPE == "Repeater"} {
set call "$mycall/R"
CW::play $call
} else {
CW::play $mycall
}
playSilence 100
}
}
#
# Executed when the squelch have just closed and the RGR_SOUND_DELAY timer has
# expired.
#
proc send_rgr_sound {} {
variable sql_rx_id
if {$sql_rx_id != "?"} {
# 150 CPM, 1000 Hz, -4 dBFS
CW::play $sql_rx_id 150 1000 -4
set sql_rx_id "?"
} else {
CW::play " K" 150 600 -8
}
playSilence 100
}
#
# Executed when an empty macro command (i.e. D#) has been entered.
#
proc macro_empty {} {
playMsg "Core" "operation_failed";
}
#
# Executed when an entered macro command could not be found
#
proc macro_not_found {} {
playMsg "Core" "operation_failed";
}
#
# Executed when a macro syntax error occurs (configuration error).
#
proc macro_syntax_error {} {
playMsg "Core" "operation_failed";
}
#
# Executed when the specified module in a macro command is not found
# (configuration error).
#
proc macro_module_not_found {} {
playMsg "Core" "operation_failed";
}
#
# Executed when the activation of the module specified in the macro command
# failed.
#
proc macro_module_activation_failed {} {
playMsg "Core" "operation_failed";
}
#
# Executed when a macro command is executed that requires a module to
# be activated but another module is already active.
#
proc macro_another_active_module {} {
global active_module;
playMsg "Core" "operation_failed";
playMsg "Core" "active_module";
playMsg $active_module "name";
}
#
# Executed when an unknown DTMF command is entered
# cmd - The command string
#
proc unknown_command {cmd} {
spellWord $cmd;
playMsg "Core" "unknown_command";
}
#
# Executed when an entered DTMF command failed
# cmd - The command string
#
proc command_failed {cmd} {
spellWord $cmd;
playMsg "Core" "operation_failed";
}
#
# Executed when a link to another logic core is activated.
# name - The name of the link
#
proc activating_link {name} {
if {[string length $name] > 0} {
playMsg "Core" "activating_link_to";
spellWord $name;
}
}
#
# Executed when a link to another logic core is deactivated.
# name - The name of the link
#
proc deactivating_link {name} {
if {[string length $name] > 0} {
playMsg "Core" "deactivating_link_to";
spellWord $name;
}
}
#
# Executed when trying to deactivate a link to another logic core but the
# link is not currently active.
# name - The name of the link
#
proc link_not_active {name} {
if {[string length $name] > 0} {
playMsg "Core" "link_not_active_to";
spellWord $name;
}
}
#
# Executed when trying to activate a link to another logic core but the
# link is already active.
# name - The name of the link
#
proc link_already_active {name} {
if {[string length $name] > 0} {
playMsg "Core" "link_already_active_to";
spellWord $name;
}
}
#
# Executed each time the transmitter is turned on or off
# is_on - Set to 1 if the transmitter is on or 0 if it's off
#
proc transmit {is_on} {
#puts "Turning the transmitter $is_on";
variable prev_ident;
variable need_ident;
if {$is_on && ([clock seconds] - $prev_ident > 5)} {
set need_ident 1;
}
}
#
# Executed each time the squelch is opened or closed
# rx_id - The ID of the RX that the squelch opened/closed on
# is_open - Set to 1 if the squelch is open or 0 if it's closed
#
proc squelch_open {rx_id is_open} {
variable sql_rx_id;
#puts "The squelch is $is_open on RX $rx_id";
set sql_rx_id $rx_id;
}
#
# Executed when a DTMF digit has been received
# digit - The detected DTMF digit
# duration - The duration, in milliseconds, of the digit
#
# Return 1 to hide the digit from further processing in SvxLink or
# return 0 to make SvxLink continue processing as normal.
#
proc dtmf_digit_received {digit duration} {
#puts "DTMF digit \"$digit\" detected with duration $duration ms";
return 0;
}
#
# Executed when a DTMF command has been received
# cmd - The command
#
# Return 1 to hide the command from further processing is SvxLink or
# return 0 to make SvxLink continue processing as normal.
#
# This function can be used to implement your own custom commands or to disable
# DTMF commands that you do not want users to execute.
proc dtmf_cmd_received {cmd} {
#global active_module
# Example: Ignore all commands starting with 3 in the EchoLink module.
# Allow commands that have four or more digits.
#if {$active_module == "EchoLink"} {
# if {[string length $cmd] < 4 && [string index $cmd 0] == "3"} {
# puts "Ignoring random connect command for module EchoLink: $cmd"
# return 1
# }
#}
# Handle the "force core command" mode where a command is forced to be
# executed by the core command processor instead of by an active module.
# The "force core command" mode is entered by prefixing a command by a star.
#if {$active_module != "" && [string index $cmd 0] != "*"} {
# return 0
#}
#if {[string index $cmd 0] == "*"} {
# set cmd [string range $cmd 1 end]
#}
# Example: Custom command executed when DTMF 99 is received
#if {$cmd == "99"} {
# puts "Executing external command"
# playMsg "Core" "online"
# exec ls &
# return 1
#}
proc sayIP {} {
set result [exec /etc/spotnik/getIP]
puts "$result"
regexp "(\[0-9]{1,3})\.(\[0-9]{1,3})\.(\[0-9]{1,3})\.(\[0-9]{1,3})" $result all first second third fourth
playSilence 100
playNumber $first
playSilence 100
playMsg "default" "dash"
playSilence 100
playNumber $second
playSilence 100
playMsg "default" "dash"
playSilence 100
playNumber $third
playSilence 100
playMsg "default" "dash"
playSilence 100
playNumber $fourth
playSilence 500;
}
# internet test
proc sayInternetStatus {} {
if {[catch {exec ping -c 1 google.com} result] == 0} {
puts "Internet Online Passed"
playSilence 100
playMsg "Core" "online"
} else {
puts "Internet Disconnected"
#playMsg "MetarInfo" "not"
#playMsg "Core" "online"
#playSilence 500;
playSilence 100
playMsg "EchoLink" "link"
playMsg "EchoLink" "disconnected"
}
}
# Speak network IPs
if {$cmd == "93"} {
sayIP
return 1
}
# Say if online or offline
if {$cmd == "939"} {
sayInternetStatus
return 1
}
if {$cmd == "94"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /usr/bin/nmcli con up SPOTNIK &
exec nohup /usr/bin route del default &
return 1
}
# Mode Déconnecté du réseau, Autonome Relais seulement
if {$cmd == "95"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.default &
return 1
}
# 96 SvxReflector RRF
if {$cmd == "96"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.rrf &
return 1
}
# 97 SvxReflector FON
if {$cmd == "97"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.fon &
return 1
}
# 98 Salon Technique
if {$cmd == "98"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.tec &
return 1
}
# 99 Salon International
if {$cmd == "99"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.int &
return 1
}
# 100 Salon Bavardage
if {$cmd == "100"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.bav &
return 1
}
# 101 Salon Local
if {$cmd == "101"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.loc &
return 1
}
# 102 salon Sat
if {$cmd == "102"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.sat &
return 1
}
# 103 Echolink
if {$cmd == "103"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.el &
return 1
}
# 104 Regional a creer
if {$cmd == "104"} {
puts "Executing external command"
playMsg "Core" "online"
exec nohup /etc/spotnik/restart.reg &
return 1
}
# 200 Raptor start and stop
if {$cmd == "200"} {
puts "Executing external command"
exec nohup /opt/RRFRaptor/RRFRaptor.sh &
return 1
}
# 201 Raptor quick scan
if {$cmd == "201"} {
puts "Executing external command"
exec /opt/RRFRaptor/RRFRaptor.sh scan
return 1
}
# 202 Raptor sound
if {$cmd == "202"} {
if { [file exists /tmp/RRFRaptor_status.tcl] } {
source "/tmp/RRFRaptor_status.tcl"
if {$RRFRaptor == "ON"} {
playSilence 1500
playFile /opt/RRFRaptor/sounds/active.wav
} else {
playSilence 1500
playFile /opt/RRFRaptor/sounds/desactive.wav
}
}
return 1
}
# 203 Raptor quick scan sound
if {$cmd == "203"} {
if { [file exists /tmp/RRFRaptor_scan.tcl] } {
source "/tmp/RRFRaptor_scan.tcl"
if {$RRFRaptor == "None"} {
playSilence 1500
playFile /opt/RRFRaptor/sounds/qso_ko.wav
} else {
playSilence 1500
playFile /opt/RRFRaptor/sounds/qso_ok.wav
if {$RRFRaptor == "RRF"} {
playFile /etc/spotnik/Srrf.wav
} elseif {$RRFRaptor == "FON"} {
playFile /etc/spotnik/Sfon.wav
} elseif {$RRFRaptor == "TECHNIQUE"} {
playFile /etc/spotnik/Stec.wav
} elseif {$RRFRaptor == "INTERNATIONAL"} {
playFile /etc/spotnik/Sint.wav
} elseif {$RRFRaptor == "LOCAL"} {
playFile /etc/spotnik/Sloc.wav
} elseif {$RRFRaptor == "BAVARDAGE"} {
playFile /etc/spotnik/Sbav.wav
}
}
}
return 1
}
# 1000 Reboot
if {$cmd == "1000"} {
puts "Executing external command"
playMsg "Core" "online"
exec reboot &
return 1
}
# 1001 halt
if {$cmd == "1001"} {
puts "Executing external command"
playMsg "Core" "online"
exec halt &
return 1
}
return 0
}
#
# Executed once every whole minute. Don't put any code here directly
# Create a new function and add it to the timer tick subscriber list
# by using the function addMinuteTickSubscriber.
#
proc every_minute {} {
variable minute_tick_subscribers;
#puts [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"];
foreach subscriber $minute_tick_subscribers {
$subscriber;
}
}
#
# Executed once every whole minute. Don't put any code here directly
# Create a new function and add it to the timer tick subscriber list
# by using the function addSecondTickSubscriber.
#
proc every_second {} {
variable second_tick_subscribers;
#puts [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"];
foreach subscriber $second_tick_subscribers {
$subscriber;
}
}
#
# Deprecated: Use the addMinuteTickSubscriber function instead
#
proc addTimerTickSubscriber {func} {
puts "*** WARNING: Calling deprecated TCL event handler addTimerTickSubcriber."
puts " Use addMinuteTickSubscriber instead"
addMinuteTickSubscriber $func;
}
#
# Use this function to add a function to the list of functions that
# should be executed once every whole minute. This is not an event
# function but rather a management function.
#
proc addMinuteTickSubscriber {func} {
variable minute_tick_subscribers;
lappend minute_tick_subscribers $func;
}
#
# Use this function to add a function to the list of functions that
# should be executed once every second. This is not an event
# function but rather a management function.
#
proc addSecondTickSubscriber {func} {
variable second_tick_subscribers;
lappend second_tick_subscribers $func;
}
#
# Should be executed once every whole minute to check if it is time to
# identify. Not exactly an event function. This function handle the
# identification logic and call the send_short_ident or send_long_ident
# functions when it is time to identify.
#
proc checkPeriodicIdentify {} {
variable prev_ident;
variable short_ident_interval;
variable long_ident_interval;
variable min_time_between_ident;
variable ident_only_after_tx;
variable need_ident;
global logic_name;
if {$short_ident_interval == 0} {
return;
}
set now [clock seconds];
set hour [clock format $now -format "%k"];
regexp {([1-5]?\d)$} [clock format $now -format "%M"] -> minute;
set short_ident_now \
[expr {($hour * 60 + $minute) % $short_ident_interval == 0}];
set long_ident_now 0;
if {$long_ident_interval != 0} {
set long_ident_now \
[expr {($hour * 60 + $minute) % $long_ident_interval == 0}];
}
if {$long_ident_now} {
puts "$logic_name: Sending long identification...";
send_long_ident $hour $minute;
set prev_ident $now;
set need_ident 0;
} else {
if {$now - $prev_ident < $min_time_between_ident} {
return;
}
if {$ident_only_after_tx && !$need_ident} {
return;
}
if {$short_ident_now} {
puts "$logic_name: Sending short identification...";
send_short_ident $hour $minute;
set prev_ident $now;
set need_ident 0;
}
}
}
#
# Executed when the QSO recorder is being activated
#
proc activating_qso_recorder {} {
playMsg "Core" "activating";
playMsg "Core" "qso_recorder";
}
#
# Executed when the QSO recorder is being deactivated
#
proc deactivating_qso_recorder {} {
playMsg "Core" "deactivating";
playMsg "Core" "qso_recorder";
}
#
# Executed when trying to deactivate the QSO recorder even though it's
# not active
#
proc qso_recorder_not_active {} {
playMsg "Core" "qso_recorder";
playMsg "Core" "not_active";
}
#
# Executed when trying to activate the QSO recorder even though it's
# already active
#
proc qso_recorder_already_active {} {
playMsg "Core" "qso_recorder";
playMsg "Core" "already_active";
}
#
# Executed when the timeout kicks in to activate the QSO recorder
#
proc qso_recorder_timeout_activate {} {
playMsg "Core" "timeout"
playMsg "Core" "activating";
playMsg "Core" "qso_recorder";
}
#
# Executed when the timeout kicks in to deactivate the QSO recorder
#
proc qso_recorder_timeout_deactivate {} {
playMsg "Core" "timeout"
playMsg "Core" "deactivating";
playMsg "Core" "qso_recorder";
}
#
# Executed when the user is requesting a language change
#
proc set_language {lang_code} {
global logic_name;
puts "$logic_name: Setting language $lang_code (NOT IMPLEMENTED)";
}
#
# Executed when the user requests a list of available languages
#
proc list_languages {} {
global logic_name;
puts "$logic_name: Available languages: (NOT IMPLEMENTED)";
}
#
# Executed when the node is being brought online or offline
#
proc logic_online {online} {
global mycall
variable CFG_TYPE
if {$online} {
playMsg "Core" "online";
spellWord $mycall;
if {$CFG_TYPE == "Repeater"} {
playMsg "Core" "repeater";
}
}
}
##############################################################################
#
# Main program
#
##############################################################################
if [info exists CFG_SHORT_IDENT_INTERVAL] {
if {$CFG_SHORT_IDENT_INTERVAL > 0} {
set short_ident_interval $CFG_SHORT_IDENT_INTERVAL;
}
}
if [info exists CFG_LONG_IDENT_INTERVAL] {
if {$CFG_LONG_IDENT_INTERVAL > 0} {
set long_ident_interval $CFG_LONG_IDENT_INTERVAL;
if {$short_ident_interval == 0} {
set short_ident_interval $long_ident_interval;
}
}
}
if [info exists CFG_IDENT_ONLY_AFTER_TX] {
if {$CFG_IDENT_ONLY_AFTER_TX > 0} {
set ident_only_after_tx $CFG_IDENT_ONLY_AFTER_TX;
}
}
if [info exists CFG_SHORT_ANNOUNCE_ENABLE] {
set short_announce_enable $CFG_SHORT_ANNOUNCE_ENABLE
}
if [info exists CFG_SHORT_ANNOUNCE_FILE] {
set short_announce_file $CFG_SHORT_ANNOUNCE_FILE
}
if [info exists CFG_SHORT_VOICE_ID_ENABLE] {
set short_voice_id_enable $CFG_SHORT_VOICE_ID_ENABLE
}
if [info exists CFG_SHORT_CW_ID_ENABLE] {
set short_cw_id_enable $CFG_SHORT_CW_ID_ENABLE
}
if [info exists CFG_LONG_ANNOUNCE_ENABLE] {
set long_announce_enable $CFG_LONG_ANNOUNCE_ENABLE
}
if [info exists CFG_LONG_ANNOUNCE_FILE] {
set long_announce_file $CFG_LONG_ANNOUNCE_FILE
}
if [info exists CFG_LONG_VOICE_ID_ENABLE] {
set long_voice_id_enable $CFG_LONG_VOICE_ID_ENABLE
}
if [info exists CFG_LONG_CW_ID_ENABLE] {
set long_cw_id_enable $CFG_LONG_CW_ID_ENABLE
}
# end of namespace
}
#
# This file has not been truncated
#
+191
View File
@@ -0,0 +1,191 @@
# RRFRaptor
Le RRFRaptor analyse le trafic sur le réseau [RRF](https://f5nlg.wordpress.com/2015/12/28/nouveau-reseau-french-repeater-network/) (Réseau des Répéteurs Francophones) et gère automatiquement les QSY de votre Spotnik afin de lui faire rejoindre automatiquement le salon sur lequel il y a de l'activité. Attention, __il n'est pas recommandé de l'installer sur un relais__ en permanence. Par contre, il est parfaitement adapté à un usage personnel.
> Pour ma part, j'active néanmoins le RRFRaptor sur le F1ZPX situé en JN18du, entre 22h05 et 06h05, période généralement assez calme sur le RRF.
# Principe de fonctionnement
Une fois le RRFRaptor lancé, tant qu'il y a de l'activité sur le salon sur lequel vous êtes, rien ne se passe. Le RRFRaptor reste en sommeil.
Si l'activité retombe, au bout d'une certaine temporisation paramétrable, le RRFRaptor va s'activer et commencer à analyser le trafic sur l'ensemble du réseau RRF à la recherche de QSO sur les autres salons.
Si le trafic reprend entre temps sur le salon sur lequel vous étiez, évidemment, la temporisation redémarre à zéro et le RRFRaptor retombe en sommeil.
Par contre, si le trafic ne reprend pas et que le RRFRaptor détecte de l'activité sur un autre salon, alors il va automatiquement faire basculer votre Spotnik sur celui ci.
# Installation
## Installation du RRFRaptor
Commencez par ouvrir une connexion SSH sur votre Spotnik.
### Etape 1 - Récupération du code
Depuis votre connexion SSH, lancez les commandes suivantes :
`cd /opt`
Puis,
`git clone https://github.com/armel/RRFRaptor.git`
### Etape 2 - Installation des dépendances
Si nécessaire, il faut également procéder à l'installation de quelques paquets complémentaires. Rassurez-vous, ce sera rapide. Toujours depuis votre connexion SSH, lancez les commandes suivantes :
`sudo apt-get install python-pip`
`sudo pip install requests`
### Etape 3 - Ajout des codes DTMF
Il est possible d'activer et de désactiver le RRFRaptor par un simple code DTMF.
Si vous n'êtes pas familier avec les fichiers de paramétrages de __SvxLink__, il vous suffit de copier le fichier `Logic.tcl` que j'ai déjà modifié pour vous. Donc, depuis votre connexion SSH, lancer les commandes suivantes :
`mv /usr/share/svxlink/events.d/local/Logic.tcl /usr/share/svxlink/events.d/local/Logic.tcl.bak`
`cp /opt/RRFRaptor/Logic.tcl /usr/share/svxlink/events.d/local/Logic.tcl`
La première va faire une sauvegarde de votre fichier `Logic.tcl` (renommé en `Logic.tcl.bak` au cas ou). Et la seconde va copier le fichier `Logic.tcl` modifié afin de prendre en charge le RRFRaptor.
Le RRFRaptor pourra désormais être activé ou désactivé en envoyant le code DTMF __200__.
### Etape 4 - Redémarrage de SvxLink
Enfin, pour finir, redémarrez __SvxLink__ à l'aide de la commande suivante :
`/etc/spotnik/restart`
Et voilà, c'est tout ;) Vous êtes pret à utiliser le RRFRaptor !
## Lancement du RRFRaptor
Le plus simple est de lancer le RRFRaptor en CLI (ligne de commande). Toujours depuis une connexion SSH,
- pour activer le RRFRaptor : `/opt/RRFRaptor/RRFRaptor.sh start`
- pour désactiver le RRFRaptor : `/opt/RRFRaptor/RRFRaptor.sh stop`
Sinon, vous pouvez également activer ou désactiver le RRFRaptor à l'aide du code DTMF __200__.
Dans tous les cas, une annonce vocale vous informera de l'activation ou de la désactivation du RRFRaptor.
Une fois activé, en l'absence d'activité durant 1 minute (par défaut), le RRFRaptor va commencer à analyser le trafic sur l'ensemble du réseau RRF à la recherche de QSO sur les autres salons et gérer lui même les QSY.
## Mode scan rapide
Le RRFRaptor dispose également d'une fonctionnalité de _scan rapide_. Cela permet de savoir si un QSO est en cours sur un salon, via une annonce vocale. __Il n'est pas nécessaire que le RRFRaptor soit activé pour que cette fonctionnalité soit utilisable__. On peut donc lancer un _scan rapide_ à tous moments.
En CLI (ligne de commande), depuis une connexion SSH, lancez la commande suivante :
`/opt/RRFRaptor/RRFRaptor.sh scan`
Sinon, vous pouvez également utiliser le code DTMF __201__.
> S'il n'est pas forcément recommandé d'activer en permanence le RRFRaptor sur un relais, cette fonctionnalité de __scan rapide__ peut s'avérer très intéressante partout. Un seul code DTMF peut renseigner les OM sur les QSO en cours sur les autres salons. À eux, si le RRFRaptor n'est pas activé, de gérer les QSY.
# Paramétrages fins
## Changer les paramétrages par défaut
Vous pouvez évidemment éditer le fichier `/opt/RRFRaptor/RRFRaptor.sh` afin de changer la durée de la temporisation par défaut (option `--sleep`).
>L'option `--debug` présente juste un intérêt en phase de développement. Inutile de l'activer.
## Ne pas prendre en compte certains salons
Vous pouvez éditer le fichier `/opt/RRFRaptor/settings.py` et modifier la variable `valid_room` (ligne 20) avec la liste des salons que vous voulez surveiller.
## Lancer le RRFRaptor au démarrage du Spotnik
Il est évidemment possible de lancer le RRFRaptor au démarrage du Spotnik. Il suffit d'éditer le fichier `/etc/rc.local` et d'y ajouter les lignes suivantes, juste __avant__ le `exit 0` qui termine le script :
```
## demarrage Raptor
/opt/RRFRaptor/RRFRaptor.sh start
```
## Mettre à jour la version du RRFRaptor
Depuis votre connexion SSH, lancez les commandes suivantes :
`cd /opt/RRFRaptor`
`git pull`
`mv /usr/share/svxlink/events.d/local/Logic.tcl /usr/share/svxlink/events.d/local/Logic.tcl.bak`
`cp /opt/RRFRaptor/Logic.tcl /usr/share/svxlink/events.d/local/Logic.tcl`
`/etc/spotnik/restart`
Et voilà, votre version est à jour.
## Modifier les codes DTMF par défaut
Si vous le souhaitez, vous pouvez modifier les codes DTMF par défaut et les adapter suivant vos besoins. Pour se faire, éditer le fichier `/usr/share/svxlink/events.d/local/Logic.tcl` à l'aide de votre éditeur préféré. Recherchez les blocs concernant les codes DTMF (vers les lignes 600...). Ajoutez et / ou modifiez les 4 nouveaux blocs ci dessous en les adaptant à votre convenance :
```
# 200 Raptor start and stop
if {$cmd == "200"} {
puts "Executing external command"
exec nohup /opt/RRFRaptor/RRFRaptor.sh &
return 1
}
# 201 Raptor quick scan
if {$cmd == "201"} {
puts "Executing external command"
exec /opt/RRFRaptor/RRFRaptor.sh scan
return 1
}
# 202 Raptor sound
if {$cmd == "202"} {
if { [file exists /tmp/RRFRaptor_status.tcl] } {
source "/tmp/RRFRaptor_status.tcl"
if {$RRFRaptor == "ON"} {
playSilence 1500
playFile /opt/RRFRaptor/sounds/active.wav
} else {
playSilence 1500
playFile /opt/RRFRaptor/sounds/desactive.wav
}
}
return 1
}
# 203 Raptor quick scan sound
if {$cmd == "203"} {
if { [file exists /tmp/RRFRaptor_scan.tcl] } {
source "/tmp/RRFRaptor_scan.tcl"
if {$RRFRaptor == "None"} {
playSilence 1500
playFile /opt/RRFRaptor/sounds/qso_ko.wav
} else {
playSilence 1500
playFile /opt/RRFRaptor/sounds/qso_ok.wav
if {$RRFRaptor == "RRF"} {
playFile /etc/spotnik/Srrf.wav
} elseif {$RRFRaptor == "FON"} {
playFile /etc/spotnik/Sfon.wav
} elseif {$RRFRaptor == "TECHNIQUE"} {
playFile /etc/spotnik/Stec.wav
} elseif {$RRFRaptor == "INTERNATIONAL"} {
playFile /etc/spotnik/Sint.wav
} elseif {$RRFRaptor == "LOCAL"} {
playFile /etc/spotnik/Sloc.wav
} elseif {$RRFRaptor == "BAVARDAGE"} {
playFile /etc/spotnik/Sbav.wav
}
}
}
return 1
}
```
>Attention, si vous modifiez également les codes __202__ et __203__ qui servent aux annonces vocales, vous devrez les modifiez également dans le script `/opt/RRFRaptor/RRFRaptor.sh`. Ce changement n'est pas recommandé.
# That's all
Bon trafic à tous, 88 & 73 de Armel F4HWN !
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
RRFRaptor
Learn more about RRF on https://f5nlg.wordpress.com
73 & 88 de F4HWN Armel
'''
import settings as s
import lib as l
import getopt
import sys
import time
import datetime
import os
def main(argv):
# Check et capture des arguments
try:
options, remainder = getopt.getopt(argv, '', ['help', 'version', 'sleep=', 'scan=', 'debug='])
except getopt.GetoptError:
l.usage()
sys.exit(2)
for opt, arg in options:
if opt == '--help':
l.usage()
sys.exit()
elif opt == '--version':
print s.version
sys.exit()
elif opt in ('--sleep'):
s.sleep = float(arg)
elif opt in ('--scan'):
if arg in ['True', 'true']:
s.scan = True
else:
s.scan = False
elif opt in ('--debug'):
if arg in ['True', 'true']:
s.debug = True
else:
s.debug = False
if s.scan is True: # Si scan simple
l.where_is()
while(True):
if l.read_log() is True:
file = open('/tmp/RRFRaptor_scan.tcl', 'w')
file.write('set RRFRaptor "' + l.scan() + '"\n')
file.close()
sys.exit()
else: # Sinon, boucle principale
while(True):
# Lecture du salon courant
l.where_is()
now = datetime.datetime.now()
if s.current_room != 'PARROT': # Si pas sur le perroquet
# Lecture de l'activité
l.read_log()
# Gestion de la temporisation
s1 = s.room[s.current_room]['last']
s2 = time.time()
if (s2 - s1) > s.sleep * 60: # Si la limite de temporisation atteinte, on scan
if s.debug is True or s.scan is True: # Attention, on réutilise ici la variable s.scan mais ne pas la confondre avec l'option --scan
s.scan = False
print now.strftime('%H:%M:%S'), '-', 'Scan en cours...'
l.qsy()
else: # Sinon, on affiche éventuellement une trace
s.scan = True # Attention, on réutilise ici la variable s.scan mais ne pas la confondre avec l'option --scan
if s.debug is True:
print now.strftime('%H:%M:%S'), '-', 'Standby sur ' + s.current_room + ' depuis ' + str(int(s2 - s1)) + ' secondes'
else: # Sinon on ne fait rien sur le perroquet
if s.debug is True:
print now.strftime('%H:%M:%S'), '-', 'Perroquet...'
# On controle toutes les 2 secondes, c'est suffisant...
time.sleep(5)
sys.stdout.flush()
if __name__ == '__main__':
try:
main(sys.argv[1:])
except KeyboardInterrupt:
pass
+46
View File
@@ -0,0 +1,46 @@
#!/bin/sh
PATH_SCRIPT='/opt/RRFRaptor/RRFRaptor.py'
PATH_LOG='/tmp'
PATH_PID='/tmp'
# Si pas d'argument, on gere tout seul
if [ -z "$1" ]; then
/usr/bin/pgrep -f 'python /opt/RRFRaptor/RRFRaptor.py'
pid=$?
if [ $pid != 1 ]; then
set -- 'stop'
else
set -- 'start'
fi
fi
case "$1" in
start)
echo "Starting RRFRaptor"
search="python ${PATH_SCRIPT}"
pkill -f "${search}"
nohup python $PATH_SCRIPT --sleep 1 --scan False --debug False > $PATH_LOG/RRFRaptor.log 2>&1 &
echo 'set RRFRaptor "ON"' > /tmp/RRFRaptor_status.tcl
echo "202#"> /tmp/dtmf_uhf
echo "202#"> /tmp/dtmf_vhf
;;
stop)
echo "Stopping RRFRaptor"
search="python ${PATH_SCRIPT}"
pkill -f "${search}"
echo 'set RRFRaptor "OFF"' > /tmp/RRFRaptor_status.tcl
echo "202#"> /tmp/dtmf_uhf
echo "202#"> /tmp/dtmf_vhf
;;
scan)
echo "Simple Scan RRFRaptor"
python $PATH_SCRIPT --scan True --debug False
echo "203#"> /tmp/dtmf_uhf
echo "203#"> /tmp/dtmf_vhf
;;
version)
echo "Version RRFRaptor"
python $PATH_SCRIPT --version
;;
esac
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
'''
RRFRaptor
Learn more about RRF on https://f5nlg.wordpress.com
73 & 88 de F4HWN Armel
'''
import settings as s
import requests
import os
import time
import datetime
import json
# Usage
def usage():
print 'Usage: RRFRaptor.py [options ...]'
print
print '--help cet aide'
print '--version numéro de version'
print
print 'Parametrages:'
print
print ' --sleep nombre Nombre de minutes avant scanning (3 minutes par défaut)'
print ' --scan booléen Mode scan [True, False (défaut)]'
print ' --debug booléen Mode debug [True, False (défaut)]'
print
print '88 & 73 from F4HWN Armel'
# Lecture du flux Json
def read_log():
# Requete HTTP vers le flux json du salon produit par le RRFTracker
try:
r = requests.get(s.room[s.current_room]['url'], verify=False, timeout=10)
except requests.exceptions.ConnectionError as errc:
print ('Error Connecting:', errc)
except requests.exceptions.Timeout as errt:
print ('Timeout Error:', errt)
# Controle de la validité du flux json
rrf_data = ''
try:
rrf_data = r.json()
except:
pass
if rrf_data != '': # Si le flux est valide
# On récupère le TOT du salon en court
current_tot = rrf_data['abstract'][0]['TOT']
s.room[s.current_room]['tot'] = current_tot
if current_tot != 0: # Si le TOT tourne encore
s.room[s.current_room]['last'] = time.time()
else: # Sinon, on commence à regarder ailleurs
try:
for data in rrf_data['elsewhere'][6]:
if data in s.valid_room:
tmp = rrf_data['elsewhere'][6][data]
if tmp != 0:
s.room[data]['tot'] = tmp
else:
s.room[data]['tot'] = 0
except:
if s.debug is True:
print 'KeyError: \'elsewhere\''
return False
else: # Si le flux est invalide
if s.debug is True:
print 'Failed to read...'
return False
return True
# Gestion des QSY
def qsy(new_room = ''):
cmd = ''
old_room = s.current_room
if new_room != '': # Si une room est passée en argument
cmd = '/etc/spotnik/restart.' + new_room[:3].lower()
else: # Sinon
for data in s.valid_room:
if data != s.current_room:
if s.room[data]['tot'] >= 3:
s.current_room = data
s.room[s.current_room]['last'] = time.time()
cmd = '/etc/spotnik/restart.' + data[:3].lower()
break
# Si une commande est en attente... on la joue !
if cmd != '':
now = datetime.datetime.now()
print now.strftime('%H:%M:%S'), '- Execute', cmd, '(', old_room, ' -> ', s.current_room, ')'
os.system(cmd)
time.sleep(5) # Petite temporisation avant de killer le timersalon éventuel
cmd = '/usr/bin/pkill -f timersalon'
os.system(cmd)
return True
# Gestion du scan simple
def scan():
for data in s.valid_room:
if s.room[data]['tot'] >= 3:
return data
break
return 'None'
# Detection salon
def where_is():
detect_room = ''
with open('/etc/spotnik/network', 'r') as content_file:
content = content_file.read()
content = content.strip()
if content == 'int':
detect_room = 'INTERNATIONAL'
elif content == 'bav':
detect_room = 'BAVARDAGE'
elif content == 'loc':
detect_room = 'LOCAL'
elif content == 'tec':
detect_room = 'TECHNIQUE'
elif content == 'default':
detect_room = 'PARROT'
else:
detect_room = content.upper()
# QSY sur le salon RRF si perdu...
if detect_room not in s.valid_room:
s.current_room = 'RRF'
qsy(s.current_room)
s.room[s.current_room]['last'] = time.time()
elif detect_room != s.current_room: # Si changement de salon...
if s.scan is False:
now = datetime.datetime.now()
print now.strftime('%H:%M:%S'), '- QSY manuel', '(', s.current_room, ' -> ', detect_room, ')'
s.current_room = detect_room
s.room[s.current_room]['last'] = time.time()
return True
# Trace debugage
def trace():
for data in s.room:
print data,
if s.room[data]['tot'] == '':
print s.room[data]['last']
else:
print s.room[data]['tot']
print '-----'
return True
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
'''
RRFRaptor
Learn more about RRF on https://f5nlg.wordpress.com
73 & 88 de F4HWN Armel
'''
# Version
version = '1.3.3'
# Variables par defaut
sleep = 1 # Durée en minutes avant QSY
scan = False # Mode scan
debug = False # Mode debug
current_room = '' # Salon courant
valid_room = ['PARROT', 'RRF', 'TECHNIQUE', 'LOCAL', 'BAVARDAGE', 'INTERNATIONAL'] # Ajoutez le 'FON' si vous le souhaitez
room = {
'PARROT': {
'url': '',
'tot': 0,
'last': ''
},
'RRF': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/RRF-today/rrf_tiny.json',
'tot': 0,
'last': ''
},
'TECHNIQUE': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/TECHNIQUE-today/rrf_tiny.json',
'tot': 0,
'last': ''
},
'INTERNATIONAL': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/INTERNATIONAL-today/rrf_tiny.json',
'tot': 0,
'last': ''
},
'LOCAL': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/LOCAL-today/rrf_tiny.json',
'tot': 0,
'last': ''
},
'BAVARDAGE': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/BAVARDAGE-today/rrf_tiny.json',
'tot': 0,
'last': ''
},
'FON': {
'url': 'http://rrf.f5nlg.ovh:8080/RRFTracker/FON-today/rrf_tiny.json',
'tot': 0,
'last': ''
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.