-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathwsjtx_listener.py
236 lines (206 loc) · 9.62 KB
/
wsjtx_listener.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import pywsjtx.extra.simple_server
import threading
from pyhamtools.utils import freq_to_band
import requests
import re
import random
from datetime import datetime,timedelta
import pandas
from termcolor import colored
from logger import LOGGER as log
class Listener:
def __init__(self,q,config,ip_address,port,timeout=2.0):
log.debug('new listener: '+str(q))
self.config = config
self.call = None
self.band = None
self.lastReport = datetime.now()
self.lastScan = None
self.q = q
self.unseen = []
self.stopped = False
self.ip_address = ip_address
self.port = port
self.initAdif()
self.s = pywsjtx.extra.simple_server.SimpleServer(ip_address, port)
def initAdif(self):
filePaths = self.config.get('ADIF_FILES','paths').splitlines()
if self.config.get('OPTS','load_adif_files_on_start'):
for filepath in filePaths:
self.q.addAdifFile(filepath,True)
self.loadLotw()
def loadLotw(self):
if self.config.get('LOTW','enable'):
username = self.config.get('LOTW','username')
password = self.config.get('LOTW','password')
if username and password:
self.q.loadLotw(username,password)
def webhook_event(self,event):
events = self.config.get('WEBHOOKS','events').split(',')
for webhook in self.config.get('WEBHOOKS','hooks').splitlines():
try:
for sendEvent in events:
if event[sendEvent]:
requests.post(webhook, data=event)
break
except Exception as e:
log.warn('webhook {} failed: event {} error {}'.format(webhook,event,e))
def print_line(self):
now = datetime.now()
newLastReport = datetime(now.year, now.month, now.day, now.hour, now.minute, 15*(now.second // 15),0)
if (newLastReport-self.lastReport).total_seconds() >= 15:
log.info("------- "+str(newLastReport)+" -------")
self.lastReport = newLastReport
def send_reply(self,data):
packet = pywsjtx.ReplyPacket.Builder(data['packet'])
self.s.send_packet(data['addr_port'], packet)
def parse_packet(self):
if self.q.defered:
return
print('decode packet ',self.the_packet)
try:
m = re.match(r"^CQ\s(\w{2,3}\b)?\s?([A-Z0-9/]+)\s([A-Z0-9/]+)?\s?([A-Z]{2}[0-9]{2})", self.the_packet.message)
if m:
#print("Callsign {}".format(m.group(1)))
directed = m.group(1)
callsign = m.group(2)
grid = m.group(4)
#print("CALL ",callsign,' on ',self.band)
self.print_line()
msg = callsign
print("D1")
needData = self.q.needDataByBandAndCall(self.band,callsign,grid)
print("D2")
needData['cuarto'] = 15 * (datetime.now().second // 15)
needData['directed'] = directed
needData['call'] = callsign
needData['grid'] = grid
needData['cq'] = True
needData['packet'] = self.the_packet
needData['addr_port'] = self.addr_port
log.debug("listener needData {}".format(needData))
self.unseen.append(needData)
threading.Thread(target=self.webhook_event,args=(needData),daemon=True)
if needData['newState'] == True:
log.info(colored("NEW STATE {} {}".format(callsign,needData['state']), 'magenta', 'on_white'))
bg=pywsjtx.QCOLOR.RGBA(255,255,0,0)
fg=pywsjtx.QCOLOR.Black()
elif needData['newDx'] == True:
log.info(colored("NEW DX {} {} {}".format(callsign,needData['dx'],needData['country']), 'red', 'on_white'))
bg=pywsjtx.QCOLOR.Red()
fg=pywsjtx.QCOLOR.White()
elif needData['newGrid'] == True:
log.info(colored("NEW GRID {} {} {}".format(callsign,needData['grid'],needData['country']), 'white', 'on_blue'))
bg=pywsjtx.QCOLOR.RGBA(255,0,0,255)
fg=pywsjtx.QCOLOR.White()
msg = msg + ' NEW CALL'
elif needData['newCall'] == True:
log.info(colored("NEW CALL {} {} {}".format(callsign,needData['state'],needData['country']), 'white', 'on_blue'))
bg=pywsjtx.QCOLOR.RGBA(255,0,0,255)
fg=pywsjtx.QCOLOR.White()
msg = msg + ' NEW CALL'
else:
bg=pywsjtx.QCOLOR.Uncolor()
fg=pywsjtx.QCOLOR.Uncolor()
msg = msg + '_'
color_pkt = pywsjtx.HighlightCallsignPacket.Builder(self.the_packet.wsjtx_id, callsign, bg, fg, True)
self.s.send_packet(self.addr_port, color_pkt)
else:
log.debug("checking for a dual-call message")
m = re.match(r"([A-Z0-9/]+) ([A-Z0-9/]+) ([A-Z0-9-]+)", self.the_packet.message)
if m:
call1 = m.group(1)
call2 = m.group(2)
msg = m.group(3)
grid = False
m2 = re.match(r"([A-Z]{2}[0-9]{2})", msg)
if m2:
g = m2.group(1)
if g != "RR73":
grid = g
log.debug("found two calls: {} and {}; grid {}".format(call1,call2,grid))
log.debug("get needData for call1 {}, {}, {}".format(self.band,call1,False))
needData1 = self.q.needDataByBandAndCall(self.band,call1,False)
log.debug("needData for call1 retrieved")
needData1['call'] = call1
log.debug("needData for call1 assigned")
needData1['cq'] = False
log.debug("needData for call1 cq assigned")
log.debug("needData1 {}".format(needData1))
needData2 = self.q.needDataByBandAndCall(self.band,call2,grid)
needData2['call'] = call2
needData2['cq'] = False
log.debug("needData2 {}".format(needData2))
if needData1['call'] and needData2['call']:
cuarto = 15 * (datetime.now().second // 15)
log.debug("cuarto {}".format(cuarto))
needData2['cuarto'] = cuarto
try:
self.unseen.append(needData2)
except Exception as e:
log.error("failed to append unseen: {}".format(needData2))
raise e
pass
except TypeError as e:
log.error("Caught a type error in parsing packet: {}; error {}".format(self.the_packet.message,e))
except Exception as e:
log.error("caught an error parsing packet: {}; error {}".format(self.the_packet.message,e))
def stop(self):
log.debug("stopping wsjtx listener")
self.stopped = True
#self.t.join()
def doListen(self):
while True:
if self.stopped:
break
(self.pkt, self.addr_port) = self.s.rx_packet()
if (self.pkt != None):
self.the_packet = pywsjtx.WSJTXPacketClassFactory.from_udp_packet(self.addr_port, self.pkt)
self.handle_packet()
self.pkt = None
self.the_packet = None
self.addr_port = None
def listen(self):
self.t = threading.Thread(target=self.doListen, daemon=True)
log.info("Listener started "+self.ip_address+":"+str(self.port))
self.t.start()
def heartbeat(self):
max_schema = max(self.the_packet.max_schema, 3)
reply_beat_packet = pywsjtx.HeartBeatPacket.Builder(self.the_packet.wsjtx_id,max_schema)
self.s.send_packet(self.addr_port, reply_beat_packet)
def update_status(self):
#log.debug('wsjt-x status {}'.format(self.the_packet))
try:
bandinfo = freq_to_band(self.the_packet.dial_frequency/1000)
self.call = self.the_packet.de_call
self.band = str(bandinfo['band'])+'M'
except Exception as e:
pass
def update_log(self):
log.debug("update log".format(self.the_packet))
nd = self.q.needDataByBandAndCall(self.band,self.the_packet.call,self.the_packet.grid)
log.debug("update_log call {} grid {} needData {}".format(self.the_packet.call,self.the_packet.grid,nd))
try:
qso = {
'CALL': self.the_packet.call,
'BAND': self.band,
'DXCC': nd.get('dx'),
'STATE': nd.get('state'),
'GRID': nd.get('grid')
}
self.q.addQso(qso)
except Exception as e:
log.error("Failed to update log for call {}, data {}: {}".format(self.the_packet.call,nd,e))
pass
def handle_packet(self):
if type(self.the_packet) == pywsjtx.HeartBeatPacket:
self.heartbeat()
elif type(self.the_packet) == pywsjtx.StatusPacket:
self.update_status()
elif type(self.the_packet) == pywsjtx.QSOLoggedPacket:
self.update_log()
elif self.band != None:
if type(self.the_packet) == pywsjtx.DecodePacket:
self.parse_packet()
else:
log.debug('unknown packet type {}; {}'.format(type(self.the_packet),self.the_packet))