4444
4545#--------------------------------------------------------------------------------------------------------------#
4646
47- def getQRPLabs (bCfg : Dict , last_date : str ):
47+ def matchQRPRecords (jWSPRRec1 : List , jWSPRRec2 : List ) -> List :
48+ # determine if 2nd record avilable to process
49+ logging .info (f" Starting record matching process" )
50+
51+ print (f"jWSPRRec1 len = { len (jWSPRRec1 )} " )
52+ print (f"jWSPRRec2 len = { len (jWSPRRec2 )} " )
53+
54+ aDateTime = []
55+ aMatch = []
56+ for i in range (0 , len (jWSPRRec1 )):
57+ try :
58+ aDateTime .index (jWSPRRec1 [i ]['time' ])
59+ except ValueError :
60+ aDateTime .append (jWSPRRec1 [i ]['time' ])
61+ sDateTime = adjDateTime (jWSPRRec1 [i ]['time' ]) # find 2nd record time based on 1st record
62+ match = False
63+ for j , element in enumerate (jWSPRRec2 ):
64+ #for j in range(len(jWSPRRec2)):
65+ if element ['time' ] == sDateTime :
66+ match = True
67+ break
68+ # process both records
69+ if match == True :
70+ aMatch .append (jWSPRRec1 [i ])
71+ aMatch .append (jWSPRRec2 [j ])
72+ logging .debug (f" Found 1st record to process = { jWSPRRec1 [i ]['tx_sign' ]} , { jWSPRRec1 [i ]['time' ]} , { jWSPRRec1 [i ]['tx_loc' ]} , { jWSPRRec1 [i ]['band' ]} " )
73+ logging .debug (f" Found 2nd record to process = { jWSPRRec2 [j ]['tx_sign' ]} , { jWSPRRec2 [j ]['time' ]} , { jWSPRRec2 [j ]['tx_loc' ]} , { jWSPRRec2 [j ]['band' ]} " )
74+ else :
75+ logging .debug (f" Found 1st record to process but no match = { jWSPRRec1 [i ]['tx_sign' ]} , { jWSPRRec1 [i ]['time' ]} , { jWSPRRec1 [i ]['tx_loc' ]} , { jWSPRRec1 [i ]['band' ]} " )
76+ return aMatch
77+
78+ #--------------------------------------------------------------------------------------------------------------#
79+
80+ def decodeQRP (JSON1 : Dict , JSON2 : Dict ) -> Dict :
81+ pow2dec = {0 :0 ,3 :1 ,7 :2 ,10 :3 ,13 :4 ,17 :5 ,20 :6 ,23 :7 ,27 :8 ,30 :9 ,33 :10 ,37 :11 ,40 :12 ,43 :13 ,47 :14 ,50 :15 ,53 :16 ,57 :17 ,60 :18 }
82+
83+ spot_pos_time = JSON1 ['time' ]
84+ spot_pos_call = JSON1 ['tx_sign' ]
85+ spot_pos_loc = JSON1 ['tx_loc' ]
86+ #spot_pos_power = 17
87+ spot_tele_call = JSON2 ['tx_sign' ]
88+ spot_tele_loc = JSON2 ['tx_loc' ]
89+ spot_tele_power = int (JSON2 ['power' ])
90+
91+ # Convert call to numbers
92+ c1 = spot_tele_call [1 ]
93+ # print("C1=",c1)
94+ if c1 .isalpha ():
95+ c1 = ord (c1 ) - 55
96+ else :
97+ c1 = ord (c1 ) - 48
98+
99+ c2 = ord (spot_tele_call [3 ]) - 65
100+ c3 = ord (spot_tele_call [4 ]) - 65
101+ c4 = ord (spot_tele_call [5 ]) - 65
102+
103+ # Convert locator to numbers
104+ l1 = ord (spot_tele_loc [0 ]) - 65
105+ l2 = ord (spot_tele_loc [1 ]) - 65
106+ l3 = ord (spot_tele_loc [2 ]) - 48
107+ l4 = ord (spot_tele_loc [3 ]) - 48
108+
109+ #
110+ # Convert power
111+ #
112+ p = pow2dec [spot_tele_power ]
113+ sum1 = c1 * 26 * 26 * 26
114+ sum2 = c2 * 26 * 26
115+ sum3 = c3 * 26
116+ sum4 = c4
117+ sum1_tot = sum1 + sum2 + sum3 + sum4
118+
119+ sum1 = l1 * 18 * 10 * 10 * 19
120+ sum2 = l2 * 10 * 10 * 19
121+ sum3 = l3 * 10 * 19
122+ sum4 = l4 * 19
123+ sum2_tot = sum1 + sum2 + sum3 + sum4 + p
124+ # print("sum_tot1/2:", sum1_tot,sum2_tot)
125+
126+ # 24*1068
127+ lsub1 = int (sum1_tot / 25632 )
128+ lsub2_tmp = sum1_tot - lsub1 * 25632
129+ lsub2 = int (lsub2_tmp / 1068 )
130+ # print("lsub1/2",lsub1,lsub2)
131+ alt = (lsub2_tmp - lsub2 * 1068 ) * 20
132+
133+ # Handle bogus altitudes
134+ if alt > 14000 :
135+ # print("Bogus packet. Too high altitude!! locking to 9999")
136+ alt = 9999
137+
138+ if alt == 2760 :
139+ # print("Bogus packet. 2760 m locking to 9998")
140+ alt = 9998
141+
142+ if alt == 0 :
143+ # print("Zero alt detected. Locking to 10000")
144+ alt = 10000
145+
146+ # Sublocator
147+ lsub1 = lsub1 + 65
148+ lsub2 = lsub2 + 65
149+ subloc = (chr (lsub1 ) + chr (lsub2 )).lower ()
150+
151+ # Temperature
152+ # 40*42*2*2
153+ temp_1 = int (sum2_tot / 6720 )
154+ temp_2 = temp_1 * 2 + 457
155+ temp_3 = temp_2 * 5 / 1024
156+ temp = (temp_2 * 500 / 1024 ) - 273
157+ # print("Temp: %5.2f %5.2f %5.2f %5.2f" % (temp_1, temp_2, temp_3, temp))
158+
159+ #
160+ # Battery
161+ #
162+ # =I7-J7*(40*42*2*2)
163+ batt_1 = int (sum2_tot - temp_1 * 6720 )
164+ batt_2 = int (batt_1 / 168 )
165+ batt_3 = batt_2 * 10 + 614
166+ # 5*M8/1024
167+ batt = batt_3 * 5 / 1024
168+
169+ #
170+ # Speed / GPS / Sats
171+ #
172+ # =I7-J7*(40*42*2*2)
173+ # =INT(L7/(42*2*2))
174+ t1 = sum2_tot - temp_1 * 6720
175+ t2 = int (t1 / 168 )
176+ t3 = t1 - t2 * 168
177+ t4 = int (t3 / 4 )
178+ speed = t4 * 2
179+ r7 = t3 - t4 * 4
180+ gps = int (r7 / 2 )
181+ sats = r7 % 2
182+ # print("T1-4,R7:",t1, t2, t3, t4, r7)
183+
184+ #
185+ # Calc lat/lon from loc+subbloc
186+ #
187+ loc = spot_pos_loc + subloc
188+ lat , lon = GridtoLatLon (loc )
189+
190+ pstr = ("Spot %s Call: %6s Latlon: %10.5f %10.5f Grid: %6s Alt: %5d Temp: %4.1f Batt: %5.2f Speed: %3d GPS: %1d Sats: %1d" %
191+ ( spot_pos_time , spot_pos_call , lat , lon , loc , alt , temp , batt , speed , gps , sats ))
192+
193+ telemetry = {'time' :spot_pos_time , "call" :spot_pos_call , "lat" :round (lat ,3 ), "lon" :round (lon ,3 ), "grid" :loc , "alt" : alt ,
194+ "temp" :round (temp ,1 ), "batt" :round (batt ,2 ), "speed" :speed , "gps" :gps , "sats" :sats }
195+
196+ return telemetry
197+
198+ #--------------------------------------------------------------------------------------------------------------#
199+
200+ def getQRPLabs (bCfg : Dict , lastdate : str ):
48201 """
49202 Function to retrieve WSPR records, create data structure and then upload to APRS-IS or SondeHub
50203
@@ -61,7 +214,7 @@ def getQRPLabs(bCfg: Dict, last_date: str):
61214 timeslot = bCfg ['timeslot' ]
62215 band = bCfg ['band' ]
63216
64- query = "SELECT * FROM rx WHERE tx_sign='" + wCallsign + "' AND time > '" + last_date + "' ORDER BY time"
217+ query = "SELECT * FROM rx WHERE tx_sign='" + wCallsign + "' AND time > '" + lastdate + "' ORDER BY time"
65218 logging .info (" SQL query = " + query )
66219
67220 url = "https://db1.wspr.live/?query=" + urllib .parse .quote_plus (query + " FORMAT JSON" )
@@ -90,8 +243,84 @@ def getQRPLabs(bCfg: Dict, last_date: str):
90243 logging .warning (" Exit function, insufficient WSPR records to process" )
91244 return 0 , None , None
92245
93- # eliminate duplicates
246+ # remove any duplicate first packets
247+ print (40 * "-" )
248+ logging .debug (f" starting record count = { len (jWsprData )} " )
94249 jWsprData = deldupWspr (jWsprData )
95- logging .info (f" WSPR Live records after removing duplicates = { len (jWsprData )} " )
250+ logging .debug (f" ending record count after removing duplicates = { len (jWsprData )} " )
251+
252+ # process CFG values to search for 2nd packets
253+ ch1 = channel [0 ]
254+ ch3 = channel [1 ]
255+ ts = str (int (timeslot )+ 2 )
256+ sSign = f"{ ch1 } _{ ch3 } %"
257+ #sTime = '____-__-__ __:_' + ts + '%'
258+ logging .info (f" Values to use for 2nd packet: ch1 = { ch1 } , ch3 = { ch3 } , ts = { ts } , band = { band } , sSign = { sSign } " )
259+
260+ # build query for 2nd packet
261+ query = "SELECT * FROM rx WHERE tx_sign LIKE '" + sSign + "' AND band=" + band + " AND time > '" + lastdate + "' ORDER BY time"
262+ logging .info (" SQL query = " + query )
263+ url = "https://db1.wspr.live/?query=" + urllib .parse .quote_plus (query + " FORMAT JSON" )
264+
265+ # download contents from wspr.live
266+ try :
267+ contents = urllib .request .urlopen (url ).read ()
268+ except urllib .error .URLError as erru :
269+ logging .critical (f" URL error - { erru .reason } " )
270+ return - 1 , None , None
271+ except urllib .error .HTTPError as errh :
272+ logging .critical (f" HTTP error - { errh } " )
273+ return - 1 , None , None
274+ except socket .timeout as errt :
275+ logging .critical (f" Connection timeout - { errt } " )
276+ return - 1 , None , None
277+ except :
278+ logging .critical (f" Unexpected error calling URL - { traceback .format_exc ()} " )
279+ return - 1 , None , None
280+
281+ jWsprData2 = json .loads (contents .decode ("UTF-8" ))["data" ]
282+ record_count = len (jWsprData2 )
283+ logging .info (f" WSPR Live records downloaded = { record_count } " )
284+ if record_count < 1 :
285+ logging .warning (" Exit function, insufficient matching WSPR records to process" )
286+ return 0 , None , None
287+ #pprint.pp(jWsprData2[len(jWsprData2)-1], indent=2)
288+
289+ # process records downloaded and match
290+ aMatch = matchQRPRecords (jWsprData , jWsprData2 )
291+ logging .info (f" Number of matched records = { len (aMatch )} " )
292+ if len (aMatch ) < 2 :
293+ # no matches to process
294+ logging .warning (f" Insuficient number of records to process" )
295+ return 0 , None , None
296+
297+ # decode each pair of matches and build upload data list
298+ logging .info (f" Starting decoding process" )
299+ jDecodedData = {}
300+ jUploadData = []
301+ for i in range (0 , len (aMatch ), 2 ):
302+ jDecodedData [i ] = decodeQRP (aMatch [i ], aMatch [i + 1 ])
303+
304+ # reformat time from WSPR format to Zulu
305+ datetime1 = reformatDateTime (aMatch [i ]['time' ], 0 )
306+ datetime2 = reformatDateTime (aMatch [i ]['time' ], 10 )
307+
308+ # add telemetry data
309+ # build strComment grid, temp, volt, To:?, Up:?, V?, Sun:?, comment
310+ strComment = jDecodedData [i ]['grid' ] + " " + str (jDecodedData [i ]['temp' ]) + "C " + str (jDecodedData [i ]['batt' ]) + "V "
311+ strComment += "To:?? Up:??m/s V:??Km/h Sun:?? " + bCfg ['comment' ]
312+
313+ # put data into jUploadData format for uploading
314+ lat , lon = GridtoLatLon (jDecodedData [i ]['grid' ])
315+ JSON = {"software_name" :SOFTWARE_NAME , "software_version" : __version__ , "uploader_callsign" : bCfg ['uploadcallsign' ], "time_received" : datetime1 ,
316+ "payload_callsign" :BalloonCallsign , "datetime" :datetime2 , "lat" :round (lat ,3 ), "lon" :round (lon ,3 ), "alt" :jDecodedData [i ]['alt' ],
317+ "sats" :jDecodedData [i ]['sats' ], "temp" :jDecodedData [i ]['temp' ], "batt" :jDecodedData [i ]['batt' ], "grid" :jDecodedData [i ]['grid' ], "comment" :strComment }
318+ jUploadData .append (JSON )
319+
320+ logging .info (f" Decoding completed, record count = { len (jUploadData )} " )
321+ pprint .pp (jUploadData )
322+
323+
324+
96325
97- return
326+ return 0 , None , None
0 commit comments