-[[.:]]
====== Fldigi Parrot ======
=== Experimental HF Digi-mode message-relay system using Fldigi and Python==
===== Work in progress =====
Uses ''xml-rpc'' in ''python'' to communicate with **fldigi** and read the incoming RX Text.
When a particular text sequence is received (''ZCZC CALL CALL'') to trigger the "parrot" function, the RX text is then read searching for an ''end of message'' string (''...end''). When this is found the full message is then re-transmitted, with self-identification added to ensure it is clear which station is the source of the message and which is the source of the //relay//.
Transmitter control via RS232 / NMEA (for an IC-M710 marine HF transceiver) is also included to allow for remote commands to change frequency to be handled. **Fldigi** does the rig control (via its own ''hamlib'' configuration). **Fldigi** is told what we want the radio to do via ''xml-rpc'' rather than have direct control ourselves.
===== Monitoring =====
A web-page is provided that shows the Fldigi receive text, in //almost real time// at:
[[https://gm4slv.plus.com/pages/fldigi.html|Fldigi RX Text]]
and another shows the //parrot// activity:
[[https://gm4slv.plus.com/pages/parrot.html|Fldigi Parrot]]
===== Software =====
The ''python'' scripts to run these functions are run within a ''tmux'' session which is set up with the following script:
==== tmux script ====
++++ fldigi_xml.sh |
#!/bin/sh
session="Shack"
tmux start-server
tmux new-session -d -s $session
tmux rename-window "fldigi"
tmux selectp -t 0
tmux send-keys "python2 ./rx_buf.py" C-m
tmux selectp -t 0
tmux splitw -h -p 50
tmux send-keys "python2 ./parrot_flmsg.py" C-m
tmux selectp -t 1
tmux attach-session -t $session
++++
The ''python'' scripts
* rx_buf.py
* parrot_flmsg.py
* flfn.py
are as follows:
==== rx_buf.py ====
rx_buf.py reads the Fldigi RX Buffer and provides the online web page to allow remote observation of what is being received.
++++ rx_buf.py |
import xmlrpclib
import time
import os
s = xmlrpclib.ServerProxy("http://192.168.21.107:7362")
rx_text = ""
n = 0
index = 0
count = 0
def get_status():
try:
status = s.main.get_status1()
except:
status = "Fldigi Offline"
return status
def get_mode():
try:
mode = s.modem.get_name()
except:
mode = "Fldigi Offline"
return mode
def get_freq():
try:
freq = s.main.get_frequency()
carrier = s.modem.get_carrier()
centre = (float(freq) + int(carrier))/1000
except:
centre = 0.0
return centre
def write_file(text):
print "in write_file() with ", text
text_asc = "".join(x for x in text if (ord(x) > 31 and ord(x) < 128) or ord(x) == 13 or ord(x) == 10 or ord(x) == 9)
filename = r'/var/www/html/pages/gm4slv_fldigi.txt'
f = open(filename, 'a+') # a+ is "append to file, create it if it doesn't exist"
f.write(text_asc)
f.close()
return
def stamp_file():
mode = get_mode()
centre = get_freq()
status = get_status()
timenow = time.strftime("*** Time Stamp : %Y-%m-%d %H:%M : ", time.gmtime(time.time()))
#print mode
write_file("\r\n\n%s *** Modem : %s : Centre Freq : %.3f *** SNR : %s \r\n\n" % (timenow, mode, centre, status))
def tail(n):
filename = r'/var/www/html/pages/gm4slv_fldigi.txt'
tailfile = r'/var/www/html/pages/gm4slv_tail.txt'
fin = open(filename, 'r')
list = fin.readlines()
fin.close()
tail_list = list[-n:]
fout = open(tailfile, "w")
fout.writelines(tail_list)
fout.close()
while True:
'''
try:
rx_length = s.text.get_rx_length()
rx_text = str(s.text.get_rx(index,rx_length - index))
index = rx_length
except:
rx_text = ""
'''
rx_length = s.text.get_rx_length()
print "rxl ", rx_length
print "index ", index
print "getting %d, %d" % (index, rx_length - index)
rx_text = str(s.text.get_rx(index,rx_length - index))
index = rx_length
if rx_text == "empty rx buffer!":
rx_text = ""
#s.text.clear_rx()
#os.system('cls' if os.name == 'nt' else 'clear')
print rx_text
write_file(rx_text)
tail(100)
count += 1
if count == 180:
# hourly 180 * 20 secs = 3600 secs = 60 mins
stamp_file()
count = 0
time.sleep(20)
++++
==== parrot_flmsg.py ====
''parrot_flmsg.py'' handles the message-relay functions.
Recent changes were made to handle the longer //formal message// formats of ''flmsg''.
The //parrot// also provides some simple remote control of the HF transceiver's frequency and power level via //QSY?// and //PWR?// commands.
++++ parrot_flmsg.py |
#
#######
## test file
#
import xmlrpclib
import time
import os
#import subprocess
import flfn
s = xmlrpclib.ServerProxy("http://shack:7362")
rx_text = []
zc_flag = 0
nn_flag = 0
message_time = 0
qsy_flag = 0
qsy_time = 0
mycall = "GM4SLV"
info = "GM4SLV INFO\nTest Relay\nIC-M710 50W\nInverted-L\n"
help_text = "Usage : \nZCZC\nRELAY_CALL FROM_CALL [ INFO? | HELP? | TO_CALL ]\nNNNN"
### remove old crud
#
s.text.clear_rx()
flfn.do_qsy(5366.5)
def write_file(text):
filename = r'/var/www/html/pages/parrot_log.txt'
f = open(filename, 'a+')
f.write(text)
f.close()
def parse_message(text):
global qsy_flag
global qsy_time
#print "Parsing : ", text
header_line=text.split('\n')[0]
message_text = "\n".join(text.split('\n')[1:])
print "Header Line :\n", header_line
print "Message Text : \n", message_text
calls_list = header_line.split()
if len(calls_list) > 1:
if len(calls_list) < 5:
qsy_time = time.time()
print "Calls List ", calls_list
relay_call = calls_list[0].upper()
print "Relay call ", relay_call
from_call = calls_list[1].upper()
if len(calls_list) > 2:
to_call = calls_list[2].upper()
else:
to_call = "ECHO"
print "From ",from_call
print "To ",to_call
user = call_asc(from_call)
target = call_asc(to_call)
if len(calls_list) == 4:
command = calls_list[3]
message = "\n".join(text.split('\n')[1:])
if target == "INFO?":
target = user
user = "ECHO"
message = info
if target == "PWR?":
pwr = flfn.do_pwr(command)
target = "BOGUS"
user = "BOGUS"
message = "New Power preset %s" % (command)
if target == "QSY?":
qsy = flfn.do_qsy(command)
target = "BOGUS"
user = "BOGUS"
message = "New Freq %s" % (command)
#print "command was ", command
if command != "HOME":
qsy_flag = 1
print "set qsy_flag ", qsy_flag
#print qsy_time
else:
qsy_flag = 0
print "reset qsy_flag ", qsy_flag
if from_call == "HELP?" or target == "HELP?":
target = user
user = "ECHO"
message = help_text
if relay_call == mycall:
return(user,target,message)
else:
print "Wrong Relay Call"
return("BOGUS","BOGUS","Not for Me")
else:
print "Header too long"
return("BOGUS", "BOGUS", "Too Long")
else:
print "Header too short"
return("BOGUS", "BOGUS","Too Short")
def call_asc(call):
call_asc = "".join(x for x in call if (ord(x) > 46 and ord(x) < 58) or (ord(x) > 62 and ord(x) < 91))
return(call_asc)
def qsp(text, snr):
print "\nQSP triggered"
timenow = time.strftime("*** Time Stamp : %Y-%m-%d %H:%M:%S : ", time.gmtime(time.time()))
user,target,message=parse_message(text)
print "Message : \n",message
if user == "BOGUS":
print "Ignoring"
write_file("\n%s\nNOT RELAYED\n%s" % (timenow, text))
#subprocess.call("./log.sh")
s.text.clear_rx()
return
time.sleep(10)
s.text.clear_rx()
print "PTT ON"
timemsg = time.strftime("%H:%M:%S ", time.gmtime(time.time()))
s.main.tx()
s.text.add_tx("\n"+timemsg+"\nTo "+target+" from "+user+" via GM4SLV ("+snr+")\n\n"+message+"\n\n\n"+target+" de "+user+" via GM4SLV kn \n ^r")
timenow = time.strftime("*** Time Stamp : %Y-%m-%d %H:%M:%S : ", time.gmtime(time.time()))
timemsg = time.strftime("%H:%M:%S ", time.gmtime(time.time()))
modem = s.modem.get_name()
car = s.modem.get_carrier()
f = s.main.get_frequency()
rfc = (f + car) / 1000
write_file("\n%s\n%s (%s) Message Relayed on %0.1f for : %s\n%s\n====================\n" % (timenow, modem, snr, rfc, user, text))
#subprocess.call("./log.sh")
return
def watchdog():
#print "In Watchdog ", qsy_flag
if qsy_flag == 1:
print "We were QSY'd"
delta_t = time.time() - qsy_time
print delta_t
if delta_t > 300:
print "Timeout"
qsy_end = "GM4SLV GM4SLV QSY? HOME"
parse_message(qsy_end)
timenow = time.strftime("*** Timeout : %Y-%m-%d %H:%M:%S : ", time.gmtime(time.time()))
write_file("\n%s : Returning to HOME channel\n" % (timenow))
else:
return
while True:
try:
rx_length = s.text.get_rx_length()
# print "rx_length ",rx_length
for l in range(rx_length-2000, rx_length):
rx = str(s.text.get_rx(l,1))
rx_text.append(rx)
for i in range(len(rx_text)):
if rx_text[i] == "C" and rx_text[i-1] == "Z" and rx_text[i-2] == "C" and rx_text[i-3] == "Z":
if not zc_flag:
print "Found ZCZC...."
modem = s.modem.get_name()
if modem != "RTTY":
stat1 = s.main.get_status1()
else:
stat1 = s.main.get_status2()
zc_flag = 1
zc_index = i+2
if rx_text[i] == "d" and rx_text[i-1] == "n" and rx_text[i-2] == "e" and rx_text[i-3] == " " and rx_text[i-4] == "." and rx_text[i-5] == "." and rx_text[i-6] == ".":
if not nn_flag and zc_flag:
print "Found end"
nn_flag = 1
nn_index = i-3
text = ''.join(rx_text)
if zc_flag == 1:
print "found start... waiting for end ", message_time
if message_time < 300:
message_time += 1
else:
print "Timeout waiting"
s.text.clear_rx()
message_time = 0
rx_text= []
zc_flag = 0
nn_flag = 0
else:
rx_text=[]
if zc_flag == 1 and nn_flag == 1 : # message complete
print "Message complete"
#print "zc_index %d, nn_index %d" % (zc_index, nn_index)
#print "Text %s " % (rx_text[zc_index:nn_index])
if zc_index < nn_index:
message_time = 0
print "Index correct"
message = ''.join(rx_text[zc_index:nn_index+8])
print "Raw Message ", message
zc_flag = 0
nn_flag = 0
message_asc = "".join(x for x in message if (ord(x) > 31 and ord(x) < 128) or ord(x) == 13 or ord(x) == 10 or ord(x) == 9)
rx_text = []
qsp(message_asc, stat1)
else:
rx_text = []
zc_flag = 0
nn_flag = 0
except:
rx_text = []
watchdog()
time.sleep(2)
++++
==== flfn.py ====
''flfn.py'' is a helper module of functions to communicate with **fldigi** using ''xml-rpc''.
++++ flfn.py |
import xmlrpclib
import time
s = xmlrpclib.ServerProxy("http://shack:7362")
def do_qsy_tune(ttime):
while s.main.get_trx_status() != "rx":
trx = s.main.get_trx_status()
print "starting while ",trx
#s.main.rx()
time.sleep(1)
s.main.tune()
time.sleep(ttime)
trx = s.main.get_trx_status()
print "tune? ",trx
while s.main.get_trx_status() != "rx":
trx = s.main.get_trx_status()
print "ending while ",trx
s.main.rx()
time.sleep(1)
trx = s.main.get_trx_status()
print "final status ",trx
return
def fld_qsy():
cur_modem_carrier = s.modem.get_carrier()
cur_dial_freq = s.main.get_frequency()
new_dial_freq = (cur_dial_freq + cur_modem_carrier - 1500)
s.modem.set_carrier(1500)
s.main.set_frequency(new_dial_freq)
return
def do_qsy(freq):
if freq == "HOME":
print "in do_qsy with HOME"
do_home()
time.sleep(2)
do_qsy_tune(3)
do_home()
return freq
else:
print "in do_qsy with ", freq
s.main.set_frequency(float(freq)*1000)
s.modem.set_carrier(1500)
do_qsy_tune(3)
return freq
def do_reverse():
s.main.toggle_reverse()
return
def do_rxid():
s.main.toggle_rsid()
return
def do_txid():
s.main.toggle_txid()
return
def do_home():
s.main.run_macro(14)
return
def do_pwr(pwr):
if pwr == "1":
s.main.run_macro(20)
elif pwr == "2":
s.main.run_macro(21)
elif pwr == "3":
s.main.run_macro(22)
elif pwr == "4":
s.main.run_macro(23)
else:
pass
return
++++
===== Parrot Operation =====
==== Basic overview ====
To trigger a message relay you need to send the following format of text:
''ZCZC GM4SLV '' (replacing '''' with your actual callsign)
This is the ''start of message'' trigger.
Then type/paste/otherwise send your message-to-be-relayed.
To end the message send:
''... end''
If you are using ''flmsg'' to to create and send the message it will send the correct ''end of message'' string ''... end'' itself, otherwise you need to add it manually.
After a short delay, if successful, you should hear/see your message being relayed.
==== Further examples of operation ====
add screenshots of relaying in operation
FIXME
A simple "ECHO" relay
{{:public:radio:screenshot.jpg?400|}}
Page Updated: ~~LASTMOD~~
{{tag>radio fldigi python}}