<-[[.:]] ====== 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}}