1 ###############################################################################
3 # The MIT License (MIT)
5 # Copyright (c) Tavendo GmbH
6 # Modified by Russell Handorf
8 # Permission is hereby granted, free of charge, to any person obtaining a copy
9 # of this software and associated documentation files (the "Software"), to deal
10 # in the Software without restriction, including without limitation the rights
11 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 # copies of the Software, and to permit persons to whom the Software is
13 # furnished to do so, subject to the following conditions:
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 ###############################################################################
29 import sys, math, ctypes, numpy
32 from itertools import *
33 from radio_math import *
36 from numpy import mean
37 from random import randint
38 from twisted.internet import reactor
39 from autobahn.twisted.websocket import WebSocketClientFactory, \
40 WebSocketClientProtocol, \
43 class SdrWrap(object):
44 "wrap sdr and try to manage tuning"
47 self.read_samples = self.sdr.read_samples
52 def tune(self, fc, fs, g):
53 if fc == self.prev_fc and fs == self.prev_fs and g == self.prev_g:
55 if fc != self.prev_fc:
56 self.sdr.center_freq = fc
57 if fs != self.prev_fs:
58 self.sdr.sample_rate = fs
64 time.sleep(0.04) # wait for settle
65 self.sdr.read_samples(2**11) # clear buffer
67 def gain_change(self, x):
68 # the whole 10x gain number is annoying
69 real_g = int(self.prev_g * 10)
70 i = self.sdr.GAIN_VALUES.index(real_g)
72 i = min(len(self.sdr.GAIN_VALUES) -1, i)
74 new_g = self.sdr.GAIN_VALUES[i]
75 self.sdr.gain = new_g / 10.0
76 self.prev_g = new_g / 10.0
80 class Stateful(object):
83 self.freq_lower = None
84 self.freq_upper = None
85 self.vertexes = [] # (timestamp, vertex_list)
87 self.time_start = None
89 self.history = 60 # seconds
93 self.highlight = False
94 self.hl_mode = None # set this to a function!
100 self.shiftfreq = time.time() +10
104 #state.freq_lower = float(929e6)
105 #state.freq_upper = float(930e6)
106 state.freq_lower = float(100e6)
107 state.freq_upper = float(101e6)
108 state.time_start = time.time()
109 state.viewport = (0,0,1,1)
113 delta = state.freq_upper - state.freq_lower
114 return delta * x / state.width + state.freq_lower
117 return math.log(x)/math.log(2)
119 def acquire_sample(center, bw, detail, samples=8, relay=None):
120 "collect a single frequency"
124 sdr.tune(center, bw, sdr.prev_g)
125 detail = 2**int(math.ceil(log2(detail)))
126 sample_count = samples * detail
127 data = sdr.read_samples(sample_count)
128 ys,xs = psd(data, NFFT=detail, Fs=bw/1e6, Fc=center/1e6)
129 ys = 10 * numpy.log10(ys)
135 "assumes -50 to 0 range, returns color"
136 r = int((x+50) * 255 // 50)
141 def render_sample(now, dt, freqs, powers):
145 #interval = int(round(4096/state.width)+1)
146 interval = int(round(4096/state.width))
150 avgrgb=tuple([0,0,0])
152 #server side centering
153 #pad = (((4096-state.width)/interval)/interval)/interval
154 #for temp in range(0,pad):
155 # row[temp]={"r": 0, "g": 0, "b": 100}
159 for i,f in enumerate(freqs):
160 rgb = mapping(powers[i])
161 if (counter < interval):
162 avgrgb=tuple(map(operator.add, avgrgb, rgb))
163 #print "added {0}".format(rgb)
164 #print "sum {0}".format(avgrgb)
167 #print "sum {0}".format(avgrgb)
168 #avgrgb=tuple(map(mean, zip(avgrgb)))
169 avgrgb=tuple(x/interval for x in avgrgb)
170 avgrgb=tuple(map(int, avgrgb))
171 #print "average {0}".format(avgrgb)
172 if (temp<(state.width+1)):
173 row[temp]={"r": avgrgb[0], "g": avgrgb[1], "b": avgrgb[2]}
176 avgrgb=tuple([0,0,0])
178 #print "reset {0}".format(avgrgb)
179 #row[i]={"r": rgb[0], "g": rgb[1], "b": rgb[2]}
181 #server side centering
182 #for tmp in range(0,(state.width-temp)):
183 # row[temp+tmp]={"r": 0, "g": 0, "b": 100}
185 return(json.dumps(row).encode('utf8'))
186 #self.sendMessage(json.dumps(row).encode('utf8'))
187 #return(json.dumps(row))
189 def acquire_range(lower, upper):
190 "automatically juggles frequencies"
191 delta = upper - lower
192 center = (upper+lower)/2
196 return acquire_sample(center, 2.8e6,
197 detail=state.width*2.8e6/delta,
199 xs2 = numpy.array([])
200 ys2 = numpy.array([])
201 detail = state.width // ((delta)/(2.8e6))
202 for f in range(int(lower), int(upper), int(2.8e6)):
203 xs,ys = acquire_sample(f+1.4e6, 2.8e6, detail=detail)
204 xs2 = numpy.append(xs2, xs)
205 ys2 = numpy.append(ys2, ys)
208 def configure_highlight():
209 if not state.highlight:
211 pass_fc = (state.hl_lo + state.hl_hi) / 2
212 pass_bw = state.hl_hi - state.hl_lo
215 state.hl_filter = Bandpass(sdr.prev_fc, sdr.prev_fs,
218 class BroadcastClientProtocol(WebSocketClientProtocol):
221 Simple client that connects to a WebSocket server, send a HELLO
222 message every 2 seconds and print everything it receives.
227 row=[None]*self.canvasWidth
228 for x in range (0,self.canvasWidth):
232 row[x]={"r": r, "g": g, "b": b}
233 #print(json.dumps(row).encode('utf8'))
234 self.sendMessage(json.dumps(row).encode('utf8'))
235 reactor.callLater(1, self.randomrow)
236 #return(json.dumps(row))
239 now = time.time() - state.time_start
240 if (state.shiftfreq < time.time()):
241 state.shiftfreq = time.time()+20
242 #state.freq_lower = randint(24e6,1766e6)
243 state.freq_lower = randint(80e6,105e6)
244 state.freq_upper = state.freq_lower + 1e6
245 #print "jumping frequency to {0}".format(state.freq_lower)
247 freqs,power = acquire_range(state.freq_lower, state.freq_upper)
248 self.sendMessage(render_sample(now, dt, freqs, power))
249 reactor.callLater(0.1, self.update)
256 if __name__ == '__main__':
258 if len(sys.argv) < 2:
259 print("Need the WebSocket server address, i.e. ws://localhost:9000")
262 factory = WebSocketClientFactory(sys.argv[1])
263 #factory = WebSocketClientFactory("ws://localhost:9000")
264 factory.protocol = BroadcastClientProtocol