def calcrxwaveform(self, p, bandwidth=None): """Pre-compute receive waveform and set 'dot11n-rxwaveform'. Use 'dot11n-channel' and 'dot11n-txwaveform' annotations to pre-compute the received waveform (modulo intereference and noise). """ if bandwidth is None: bandwidth = DOT11N_BANDWIDTH # check for annotations for a in ['rxpower', 'dot11n-txwaveform', 'dot11n-channel']: errmsg = "[DOT11N_DSP]: cannot find '%s' annotation!"%(a) assert ANNO.supports(p,a), errmsg A = db2linear(p.getanno('rxpower')) H = p.getanno('dot11n-channel') x = p.getanno('dot11n-txwaveform') # check that channel dimensions match Ntx nrx = H.rows() ntx, hcols = x.rows(), H.cols() assert (hcols == ntx), "[DOT11N]: Got channel with invalid dimensions!" z = sqrt(A)*H.conv(x) # apply local frequency offset (and doppler) if annotations are found cfo = 0 if p.hasanno('tx-cfo') and p.hasanno('rx-cfo'): cfo += p.getanno('tx-cfo') - p.getanno('rx-cfo') if p.hasanno('doppler'): cfo += p.getanno('doppler') z.apply_offset(cfo, bandwidth) # set rxwaveform annotation p.setanno('dot11n-rxwaveform', z) return p
def sinr_heap(self, p, force=True): """Calculate signal-to-interference-and noise ratio (SINR) for each partition created by `interval_heap()`. :param p: Packet to inspect. :param force: If true, recalculate interval heap, else use existing annotation if it exists. :return: SINR heap. SINR heap has looks like this: [(t0, t1, sinr0), (t1, t2, sinr1), ... ] Note: This method uses the 'rxpower' and 'noisepower' annotations. """ # check packet errmsg = "[CHANNELIF]: sinr_heap() cannot process non-Packet!" assert ANNO.supported(p), errmsg for a in ['rxpower', 'noisepower']: errmsg = "[CHANNELIF]: sinr_heap() cannot find '%s' annotation!"%(a) assert ANNO.supports(p, a), errmsg # get parameters rxpower = p.getanno('rxpower') # in dBm noisepower = p.getanno('noisepower') # in dBm npow = db2linear(noisepower) # get interval heap if p.hasanno('cif-iheap') and not force: iheap = p.getanno('cif-iheap') else: iheap = self.interval_heap(p) # start creating sinr heap sinrheap = [] #print "%s: SINR heap for %s @ %.8f"%(self.traceid, p.traceid, now()) for ta,tb,coll in iheap: ipow = 0 for c in coll: errmsg = "[CHANNELIF]: sinr_heap() cannot find 'rxpower' " + \ "annotation in collision list!" assert ANNO.supports(c, 'rxpower'), errmsg ipow += db2linear(c.getanno('rxpower') ) sinr = rxpower - linear2db(ipow + npow) sinrheap.append((ta,tb,sinr) ) #print " --> (%.8f, %.8f): %.3f dB, coll = %s"%(ta,tb, sinr, [c.traceid for c in coll]) return sinrheap
def calcber(cls, snr, mtype=None, nbps=None, order=None): """Calculate bit-error rate (BER) corresponding to the signal-to-noise ratio (SNR) provided in an AWGN channel. :param snr: Signal-to-noise ratio (in dB). :param mtype: Modulation type enumeration (see `mtype`). :param nbps: Number of bits per symbol (see `nbps`). :param order: Modulation order (see `order`). :return: Bit-error rate (BER) in [0,1]. By default, this class uses BPSK modulation. Use any of the optional parameters `mtype`, `nbps`, or `order` to specify a different modulation type. The bit-error rate (BER) calculated in this method assumes an additive white gaussian noise (AWGN) channel. See `M-QAM Performance`_ for more on this calculation. :note: If `snr` is a `numpy` array, this method will return an array of values corresponding to the specified parameters. .. _`M-QAM Performance`: http://en.wikipedia.org/wiki/Quadrature_amplitude_modulation#Quantized_QAM_performance """ snrdb = snr # determine modulation type if (order is not None): nbps = log2(order) if (mtype is not None): assert (mtype.upper() in cls.Nbps.keys() ), \ "[MQAM]: Modulation type (%s) is not supported!"%(mtype) mtype = mtype.upper() nbps = cls.Nbps[mtype] elif (nbps is not None): mtype = cls.nbps2mtype(nbps) else: mtype, nbps = "BPSK", 1 assert (mtype in cls.Nbps) and (nbps==cls.Nbps[mtype]), \ "[MQAM]: Unsupported modultation (%s, nbps=%s)!"(mtype,nbps) # calculate BER snr = db2linear(snrdb) M = 2**nbps if (M == 2): x = sqrt(2.0*snr) ser = 0.5*erfc(x/sqrt(2) ) ber = ser else: x = sqrt(3.0*snr/(M-1)) ter = 2.0*(1 - sqrt(1.0/M))*0.5*erfc(x/sqrt(2) ) ser = 1 - (1-ter)**2.0 ber = ser/log2(M) return ber
def rxenergy(self): """Calculate total receive energy of packets in `rxbuffer`. :return: Receive power (in dBm), or -Inf if nothing is in `rxbuffer`. This method uses the 'rxpower' annotation of packets in `rxbuffer` to determine the total receive power. Any packets that do not support this annotation will not contribute to the total power calculated. """ tot = 0 # sum total in milliwatts for p in self.rxbuffer: if ANNO.supports(p, 'rxpower'): prx = p.getanno('rxpower') # rxpower in dBm tot += db2linear(prx) tot_dbm = linear2db(tot) return tot_dbm
def calcper_data(self, p, **kwargs): """Calculate probability of error for data decoding. :param p: Packet being decoded. :param kwargs: Additional keywords arguments passed to `sinr_heap()` (or `sinr()`). :return: PER for decoding packet payload. This method sets the 'dot11a-sinr' and 'dot11a-per' annotations. The operation of this method depends on `DOT11A_USE_PIECEWISE_PER`. """ for a in ["cif-rxts", "cif-duration"]: errmsg = "[DOT11APHY]: calcper_data() cannot find " + "'%s' annotation!" % (a) assert ANNO.supports(p, a), errmsg # verify header parameters plen = len(p.payload) rate, length = p.rate, p.length assert 0 <= rate < len(DOT11A_DATARATE), "[DOT11A]: Invalid rate option (%s)!" % (rate) assert p.length == plen, "[DOT11A]: Header length reported " + "does not equal payload length; %s!=%s" % ( p.length, plen, ) # calculate PER using appropriate method if DOT11A_USE_PIECEWISE_PER: sinrheap = self.sinr_heap(p, **kwargs) t1 = p.getanno("cif-rxts") + p.getanno("cif-duration") t0 = t1 - self.calcnofdm(plen, rate) * DOT11A_TSYM xheap = [(max(ta, t0), min(tb, t1), sinr) for (ta, tb, sinr) in sinrheap if (ta < t1) and (tb > t0)] errmsg = "[DOT11APHY]: Unable to find valid data from SINR heap!" assert len(xheap) > 0, errmsg # calculate piecewise PER and average SINR psuccess, stot = 1.0, 0.0 for ta, tb, sinr in xheap: alpha = (tb - ta) / (t1 - t0) dlen = plen * alpha stot += db2linear(sinr) * alpha psuccess *= 1.0 - self.calcper(dlen, rate, sinr) per = 1.0 - psuccess sinr = linear2db(stot) else: # configure modulation and coding to calculate PER sinr, plen = self.sinr(p, **kwargs), length per = self.calcper(plen, rate, sinr) # set annotations and return PER p.setanno("dot11a-sinr", sinr) p.setanno("dot11a-per", per) return per
def calcper_header(self, p, **kwargs): """Calculate probability of error for header decoding. :param p: Packet being decoded. :param kwargs: Additional keywords arguments passed to `sinr_heap()` (or `sinr()`). :return: PER for header decoding. This method sets the 'dot11a-sinr' and 'dot11a-per' annotations. The operation of this method depends on `DOT11A_USE_PIECEWISE_PER`. """ for a in ["cif-rxts"]: errmsg = "[DOT11APHY]: calcper_header() cannot find " + "'%s' annotation!" % (a) assert ANNO.supports(p, a), errmsg # calculate PER using appropriate method plen = len(p.payload) if DOT11A_USE_PIECEWISE_PER: sinrheap = self.sinr_heap(p, **kwargs) t0 = p.getanno("cif-rxts") + DOT11A_TSHORT + DOT11A_TLONG t1 = t0 + DOT11A_TSIGNAL xheap = [(max(ta, t0), min(tb, t1), sinr) for (ta, tb, sinr) in sinrheap if (ta < t1) and (tb > t0)] errmsg = "[DOT11APHY]: Unable to find valid data from SINR heap!" assert len(xheap) > 0, errmsg # calculate piecewise PER and average SINR psuccess, stot = 1.0, 0.0 for ta, tb, sinr in xheap: alpha = (tb - ta) / (t1 - t0) hlen = len(Dot11A()) * alpha stot += db2linear(sinr) * alpha psuccess *= 1.0 - self.calcper(hlen, 0, sinr) per = 1.0 - psuccess sinr = linear2db(stot) else: sinr, hlen = self.sinr(p, **kwargs), len(p) - plen # configure modulation and coding to calculate PER per = self.calcper(hlen, 0, sinr) # set annotations and return PER p.setanno("dot11a-sinr", sinr) p.setanno("dot11a-per", per) return per
def addrxinput(self, p, **kwargs): """Update local waveform with new packet. :param p: Packet to add to `rxinput`. :param kwargs: Keyword arguments passed to `calcrxwaveform()`. """ # check if waveform has been calculated or already been added to input if p.hasanno('dot11n-rxadded'): return if not p.hasanno('dot11n-rxwaveform'): self.calcrxwaveform(p, **kwargs) # check other annotations for a in ['cif-rxts', 'dot11n-rxwaveform', 'noisepower']: errmsg = "[DOT11N_DSP]: cannot find '%s' annotation!"%(a) assert ANNO.supports(p,a), errmsg # get parameters y = p.getanno('dot11n-rxwaveform') npow = db2linear(p.getanno('noisepower')) nrx = y.rows() yts = p.getanno('cif-rxts') errmsg = "[DOT11N]: Cannot add waveform with %d "%(y.cols() ) + \ "(> %d) samples in addrxinput()!"%(self.MAXINPUT) assert (y.cols()<self.MAXINPUT), errmsg # apply pad to front and back of new input pad = Waveform.zeros(nrx,2*self.MINPAD) y = pad.concat_horizontal(y.concat_horizontal(pad) ) yts -= pad.cols()/DOT11N_BANDWIDTH # initialize rxinput if it is not already set if self.rxinput is None: #self.log("rxadded", p) noise = Waveform.randn(nrx, y.cols(), 0, npow) self.rxinput = (yts, y+noise) p.setanno('dot11n-rxadded', True) return p # update rxinput (rxts, w) = self.rxinput rxend = rxts + w.cols()/DOT11N_BANDWIDTH assert (y.rows() == nrx), "[DOT11N]: Invalid row size in addrxinput()!" assert (w.rows() == nrx), "[DOT11N]: Invalid row size in addrxinput()!" # get indices for begin/end of rxinput and new input rxa, rxb = 0, w.cols()-1 ya = rxa + int((yts-rxts)*DOT11N_BANDWIDTH) yb = ya + y.cols() - 1 istart, istop = min(rxa, ya), max(rxb, yb) errmsg = "[DOT11N]: Cannot use addrxinput() to add " + \ "waveform that starts prior to current input stream!" assert (istart==rxa), errmsg # new waveform starts after current input ends -> reinitialize input if ya>rxb: self.rxinput = None return self.addrxinput(p) # add pads to y as needed if ya>istart: pad = Waveform.zeros(nrx, ya - istart) y = pad.concat_horizontal(y) self.log("-DSP.PAD.IN", p, npad=ya-istart) if yb<istop: pad = Waveform.zeros(nrx, istop - yb) y = y.concat_horizontal(pad) self.log("+DSP.PAD.IN", p, npad=istop-yb) # add pads to (end of) rxinput as needed if rxb<istop: noise = Waveform.randn(nrx, istop-rxb, 0, npow) w = w.concat_horizontal(noise) self.log("+DSP.PAD.RX", p, npad=istop-rxb) # combine waveforms errmsg = "[DOT11N]: Incompatible waveform dimensions, " + \ "y ~ %d x %d, "%(y.rows(),y.cols()) + \ "w ~ %d x %d, "%(w.rows(),w.cols()) + \ "y: [%d, %d], w: [%d, %d]"%(ya,yb,rxa,rxb) assert (y.cols() == w.cols() ), errmsg assert (y.rows() == w.rows() ), errmsg assert (y.cols()>0), errmsg assert (y.rows()>0), errmsg z = y + w # trim input if necessary if z.cols()> self.MAXINPUT: trimidx = z.cols() - self.MAXINPUT z = z.get_cols(trimidx, z.cols()-1) rxts += trimidx/DOT11N_BANDWIDTH self.rxinput = (rxts, z) p.setanno('dot11n-rxadded', True)