def test_edge_finding(self): if ripyl.config.settings.cython_active: iterations = 100 cy_status = 'enabled' else: iterations = 10 cy_status = 'disabled' print('\nDetermining edge processing rate (Cython is {}, {} iterations)...'.format(cy_status, iterations)) edge_count = 1000 states = [0, 1] * (edge_count // 2) period = 1.0 intervals = [random.randint(1,15) * period for _ in xrange(len(states))] t = 0.0 edges = [(t, states[0])] for i in xrange(len(states)): t += intervals[i] edges.append((t, states[i])) sample_rate = 20 / period samples = list(sigp.synth_wave(iter(edges), sample_rate, sigp.min_rise_time(sample_rate) * 6.0, chunk_size=10000)) self._t_start = time.time() for _ in xrange(iterations): d_edges = list(decode.find_edges(iter(samples), (0.0, 1.0))) #print('### found edges:', len(d_edges), d_edges[:10]) samples_processed = iterations * (t * sample_rate) #print('### samples:', samples_processed, int(t * sample_rate)) return (iterations, samples_processed, 'samples')
def test_rc6_decode(self): self.test_name = 'RC-6 IR protocol' self.trial_count = 20 carrier_freq = 36.0e3 sample_rate = 10.0 * carrier_freq rise_time = sigp.min_rise_time(sample_rate) * 1.01 for i in xrange(self.trial_count): self.update_progress(i + 1) msg_count = randint(1, 10) msgs = [] for i in xrange(msg_count): do_mode6 = choice([True, False]) if do_mode6: msg = rc6.RC6Message(addr=randint(0,255), cmd=randint(0,255), toggle=randint(0,1), \ mode=6, customer=randint(0,2**15-1)) else: msg = rc6.RC6Message(addr=randint(0, 255), cmd=randint(0, 255), toggle=randint(0, 1), mode=randint(0, 5)) msgs.append(msg) do_modulation = choice((True, False)) edges = rc6.rc6_synth(msgs, message_interval=5.0e-3) if do_modulation: duty = uniform(0.1, 0.9) edges = ir.modulate(edges, carrier_freq, duty_cycle=duty) samples = sigp.synth_wave(edges, sample_rate, rise_time) #samples = sigp.dropout(samples, 2.6e-4, 3.8e-4) #samples = sigp.capacify(samples, 4.0e-7, 50.0) noisy = sigp.amplify(sigp.noisify(samples, snr_db=30), gain=5.0) waveform = list(noisy) #wave_samples = stream.sample_stream_to_samples(waveform) #plt.plot(wave_samples) #plt.show() records = list(rc6.rc6_decode(iter(waveform))) #print('\nDecoded {} records'.format(len(records))) #for m in msgs: # print(m) #for r in records: # for s in r.summary(): # print(s) self.assertEqual(len(msgs), len(records), 'Mismatch in decoded record count') for rec, msg in zip(records, msgs): self.assertEqual(rec.data, msg, 'Mismatched messages')
def test_rc6_decode(self): self.test_name = 'RC-6 IR protocol' self.trial_count = 20 carrier_freq = 36.0e3 sample_rate = 10.0 * carrier_freq rise_time = sigp.min_rise_time(sample_rate) * 1.01 for i in xrange(self.trial_count): self.update_progress(i+1) msg_count = randint(1,10) msgs = [] for i in xrange(msg_count): do_mode6 = choice([True, False]) if do_mode6: msg = rc6.RC6Message(addr=randint(0,255), cmd=randint(0,255), toggle=randint(0,1), \ mode=6, customer=randint(0,2**15-1)) else: msg = rc6.RC6Message(addr=randint(0,255), cmd=randint(0,255), toggle=randint(0,1), mode=randint(0,5)) msgs.append(msg) do_modulation = choice((True, False)) edges = rc6.rc6_synth(msgs, message_interval=5.0e-3) if do_modulation: duty = uniform(0.1, 0.9) edges = ir.modulate(edges, carrier_freq, duty_cycle=duty) samples = sigp.synth_wave(edges, sample_rate, rise_time) #samples = sigp.dropout(samples, 2.6e-4, 3.8e-4) #samples = sigp.capacify(samples, 4.0e-7, 50.0) noisy = sigp.amplify(sigp.noisify(samples, snr_db=30), gain=5.0) waveform = list(noisy) #wave_samples = stream.sample_stream_to_samples(waveform) #plt.plot(wave_samples) #plt.show() records = list(rc6.rc6_decode(iter(waveform))) #print('\nDecoded {} records'.format(len(records))) #for m in msgs: # print(m) #for r in records: # for s in r.summary(): # print(s) self.assertEqual(len(msgs), len(records), 'Mismatch in decoded record count') for rec, msg in zip(records, msgs): self.assertEqual(rec.data, msg, 'Mismatched messages')
def test_find_multi_edges(self): self.test_name = 'find_multi_edges() test' self.trial_count = 100 for i in xrange(self.trial_count): self.update_progress(i+1) bit_period = 1.0 sample_rate = bit_period * 1000 rt = sigp.min_rise_time(sample_rate) * random.uniform(20.0, 200.0) num_states = random.randint(2, 7) offset = (2 * (num_states - 1)) // 4 logic_states = (0 - offset, num_states - 1 - offset) #print('\nlogic states:', logic_states, num_states) # Generate random edges prev_state = 0 state = 0 edges = [] t = 0.0 for _ in xrange(10): while state == prev_state: # Guarantee that each edge is different from the previous state = random.randint(logic_states[0], logic_states[-1]) prev_state = state edges.append((t, state)) t += bit_period # Duplicate the last edge so that it will be decoded edges = edges + [(edges[-1][0] + bit_period, edges[-1][1])] #print('## edges:', edges) samples = sigp.synth_wave(iter(edges), sample_rate, rt, logic_states=logic_states) # Generate logic levels in range [0.0, 1.0] logic_levels = [float(level) / (num_states-1) for level in xrange(num_states)] #print('## logic_levels:', logic_levels) found_edges = list(decode.find_multi_edges(samples, decode.gen_hyst_thresholds(logic_levels))) #print('## found:', found_edges) # Remove brief transitional states that last less than half the bit period rts_edges = list(decode.remove_transitional_states(iter(found_edges), bit_period * 0.5)) #print('\n## RTS edges:', rts_edges) edges = edges[:-1] # Trim off last (duplicate) edge self.assertEqual(len(edges), len(rts_edges), msg='Mismatch in found edge count {} != {}'.format(len(edges), len(rts_edges))) for i, (e, f) in enumerate(zip(edges, rts_edges)): self.assertRelativelyEqual(e[0], f[0], epsilon=0.5, msg='Edge times not close enough {} != {}'.format(e[0], f[0])) self.assertEqual(e[1], f[1], msg='Edges not the same index={}, edge={}, found={}'.format(i, e[1], f[1]))
def test_uart_decode(self): self.test_name = 'UART message' self.trial_count = 10 for i in xrange(self.trial_count): self.update_progress(i + 1) msg = [] for _ in xrange(20): msg.append(chr(random.choice(xrange(ord('0'), ord('z'))))) msg = ''.join(msg) baud = random.choice((110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, \ 56000, 57600, 115200, 128000, 153600, 230400, 256000, 460800, 921600)) sample_rate = baud * 100.0 rise_time = sigp.min_rise_time( sample_rate) * 10.0 # 10x min rise time parity = random.choice((None, 'even', 'odd')) bits = random.choice((7, 8, 9)) #print('\nTRIAL {}: msg="{}", baud={}, parity={}, bits={}'.format(i, msg, baud, parity, bits)) edges = uart.uart_synth(bytearray(msg.encode('utf-8')), bits, baud, parity=parity, idle_start=100.0 / sample_rate) samples = sigp.synth_wave(edges, sample_rate, rise_time, ripple_db=60) noisy = sigp.amplify(sigp.noisify(samples, snr_db=20), gain=-15.0) waveform = list(noisy) frames = uart.uart_decode(iter(waveform), bits=bits, polarity=uart.UARTConfig.IdleLow, parity=parity, baud_rate=None) frames = list(frames) #print(''.join(str(d) for d in frames)) decoded_msg = ''.join(str(d) for d in frames) self.assertEqual(msg, decoded_msg, \ "Message not decoded successfully msg:'{0}', baud:{1}, parity:{2}, bits:{3}".format(msg, \ baud, parity, bits))
def test_sirc_decode(self): self.test_name = 'SIRC IR protocol' self.trial_count = 20 carrier_freq = 40.0e3 sample_rate = 10.0 * carrier_freq rise_time = sigp.min_rise_time(sample_rate) * 1.01 for i in xrange(self.trial_count): self.update_progress(i+1) msg_count = randint(1,10) msgs = [] for i in xrange(msg_count): if choice((True, False)): msg = sirc.SIRCMessage(cmd=randint(0, 127), device=randint(0, 255)) else: msg = sirc.SIRCMessage(cmd=randint(0, 127), device=randint(0, 31), \ extended=randint(0,255)) msgs.append(msg) do_modulation = choice((True, False)) edges = sirc.sirc_synth(msgs) if do_modulation: duty = uniform(0.1, 0.9) edges = ir.modulate(edges, carrier_freq, duty_cycle=duty) samples = sigp.synth_wave(edges, sample_rate, rise_time) #samples = sigp.dropout(samples, 2.6e-4, 3.8e-4) noisy = sigp.amplify(sigp.noisify(samples, snr_db=30), gain=5.0) waveform = list(noisy) #wave_samples = stream.sample_stream_to_samples(waveform) #plt.plot(wave_samples) #plt.show() records = list(sirc.sirc_decode(iter(waveform))) #print('\nDecoded {} records'.format(len(records))) self.assertEqual(len(msgs), len(records), 'Mismatch in decoded record count') for rec, msg in zip(records, msgs): self.assertEqual(rec.data, msg, 'Mismatched messages')
def test_nec_decode(self): self.test_name = 'NEC IR protocol' self.trial_count = 20 carrier_freq = 38.0e3 for i in xrange(self.trial_count): self.update_progress(i+1) msg_count = randint(1,10) msgs = [] for i in xrange(msg_count): msg = nec.NECMessage(cmd=randint(0,255), addr_low=randint(0,255), \ addr_high=choice((None, randint(0,255))) ) msgs.append(msg) sample_rate = uniform(8.0, 15.0) * carrier_freq rise_time = sigp.min_rise_time(sample_rate) * 1.01 do_modulation = choice((True, False)) edges = nec.nec_synth(msgs) if do_modulation: duty = uniform(0.2, 0.8) edges = ir.modulate(edges, carrier_freq, duty_cycle=duty) samples = sigp.synth_wave(edges, sample_rate, rise_time) #samples = sigp.dropout(samples, 2.6e-4, 3.8e-4) noisy = sigp.quantize(sigp.amplify(sigp.noisify(samples, snr_db=20), gain=5.0), 70.0) waveform = list(noisy) #wave_samples = stream.sample_stream_to_samples(waveform) #plt.plot(wave_samples) #plt.show() records = list(nec.nec_decode(iter(waveform))) #print('\nDecoded {} records'.format(len(records))) self.assertEqual(len(msgs), len(records), 'Mismatch in decoded record count') for rec, msg in zip(records, msgs): self.assertEqual(rec.data, msg, 'Mismatched messages')
def test_uart_decode(self): self.test_name = 'UART message' self.trial_count = 10 for i in xrange(self.trial_count): self.update_progress(i+1) msg = [] for _ in xrange(20): msg.append(chr(random.choice(xrange(ord('0'), ord('z')) ))) msg = ''.join(msg) baud = random.choice((110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, \ 56000, 57600, 115200, 128000, 153600, 230400, 256000, 460800, 921600)) sample_rate = baud * 100.0 rise_time = sigp.min_rise_time(sample_rate) * 10.0 # 10x min rise time parity = random.choice((None, 'even', 'odd')) bits = random.choice((7, 8, 9)) #print('\nTRIAL {}: msg="{}", baud={}, parity={}, bits={}'.format(i, msg, baud, parity, bits)) edges = uart.uart_synth(bytearray(msg.encode('utf-8')), bits, baud, parity=parity, idle_start=100.0 / sample_rate) samples = sigp.synth_wave(edges, sample_rate, rise_time, ripple_db=60) noisy = sigp.amplify(sigp.noisify(samples, snr_db=20), gain=-15.0) waveform = list(noisy) frames = uart.uart_decode(iter(waveform), bits=bits, polarity=uart.UARTConfig.IdleLow, parity=parity, baud_rate=None) frames = list(frames) #print(''.join(str(d) for d in frames)) decoded_msg = ''.join(str(d) for d in frames) self.assertEqual(msg, decoded_msg, \ "Message not decoded successfully msg:'{0}', baud:{1}, parity:{2}, bits:{3}".format(msg, \ baud, parity, bits))
def test_find_logic_levels(self): # test with clean samples # with noise # with bandwidth limited (slow and fast) # with noise and bandwidth limit # with first edge before 200th sample, just before 200th, just after 200th, after 200th sample_rate = 1000.0 sample_period = 1.0 / sample_rate rise_time = sigp.min_rise_time(sample_rate) * 50.0 edge_vectors = { 'flat_line': (((0.0,0), (3.0,0)), False), 'rising_edge_100': (((0.0,0), (0.10,1), (3.0,1)), False), 'rising_edge_190': (((0.0,0), (0.19,1), (3.0,1)), True), 'rising_edge_200': (((0.0,0), (0.2,1), (3.0,1)), True), 'rising_edge_400': (((0.0,0), (0.40,1), (3.0,1)), True), 'pulse_100': (((0.0,0), (0.10,1), (0.5,0), (3.0,0)), True), 'multi_pulse': (((0.0,0), (0.7, 1), (0.8, 0), (0.9, 1), (1.0, 0), \ (1.1,1), (1.2,0), (1.3,1), (1.4,0), \ (1.5,1), (1.6,0), (3.0,1)), True), } no_noise_vectors = ('rising_edge_190', 'rising_edge_200') sample_vectors = {} for name, vector in edge_vectors.iteritems(): samples = list( sigp.edges_to_sample_stream(iter(vector[0]), sample_period)) sample_vectors[name] = (samples, vector[1]) base_names = sample_vectors.keys() for name in base_names: if name in no_noise_vectors: continue noisy = list( sigp.noisify(iter(sample_vectors[name][0]), snr_db=20.0)) sample_vectors['noisy_' + name] = (noisy, sample_vectors[name][1]) for name in base_names: bwlim = list( sigp.filter_waveform(iter(sample_vectors[name][0]), sample_rate, rise_time)) sample_vectors['bwlim_' + name] = (bwlim, sample_vectors[name][1]) for name in base_names: if name in no_noise_vectors: continue bwlim_noisy = list(sigp.noisify(sigp.filter_waveform(iter(sample_vectors[name][0]), \ sample_rate, rise_time), snr_db=20.0)) sample_vectors['bwlim_noisy_' + name] = (bwlim_noisy, sample_vectors[name][1]) #sample_vectors.pop('rising_edge_400', None) for name, vector in sample_vectors.iteritems(): samples = vector[0] expect_success = vector[1] logic_levels = decode.find_logic_levels(samples) if not expect_success: continue # We expected find_logic_levels() to fail if logic_levels is None: #print('### Fail:', name) self.fail('No logic levels found') #print('####', name, logic_levels) self.assertRelativelyEqual(logic_levels[0], 0.0, epsilon=0.16, msg='Bad logic 0: {}'.format( logic_levels[0])) self.assertRelativelyEqual(logic_levels[1], 1.0, epsilon=0.1, msg='Bad logic 1: {}'.format( logic_levels[1]))
def test_find_multi_edges(self): self.test_name = 'find_multi_edges() test' self.trial_count = 100 for i in xrange(self.trial_count): self.update_progress(i + 1) bit_period = 1.0 sample_rate = bit_period * 1000 rt = sigp.min_rise_time(sample_rate) * random.uniform(20.0, 200.0) num_states = random.randint(2, 7) offset = (2 * (num_states - 1)) // 4 logic_states = (0 - offset, num_states - 1 - offset) #print('\nlogic states:', logic_states, num_states) # Generate random edges prev_state = 0 state = 0 edges = [] t = 0.0 for _ in xrange(10): while state == prev_state: # Guarantee that each edge is different from the previous state = random.randint(logic_states[0], logic_states[-1]) prev_state = state edges.append((t, state)) t += bit_period # Duplicate the last edge so that it will be decoded edges = edges + [(edges[-1][0] + bit_period, edges[-1][1])] #print('## edges:', edges) samples = sigp.synth_wave(iter(edges), sample_rate, rt, logic_states=logic_states) # Generate logic levels in range [0.0, 1.0] logic_levels = [ float(level) / (num_states - 1) for level in xrange(num_states) ] #print('## logic_levels:', logic_levels) found_edges = list( decode.find_multi_edges( samples, decode.gen_hyst_thresholds(logic_levels))) #print('## found:', found_edges) # Remove brief transitional states that last less than half the bit period rts_edges = list( decode.remove_transitional_states(iter(found_edges), bit_period * 0.5)) #print('\n## RTS edges:', rts_edges) edges = edges[:-1] # Trim off last (duplicate) edge self.assertEqual( len(edges), len(rts_edges), msg='Mismatch in found edge count {} != {}'.format( len(edges), len(rts_edges))) for i, (e, f) in enumerate(zip(edges, rts_edges)): self.assertRelativelyEqual( e[0], f[0], epsilon=0.5, msg='Edge times not close enough {} != {}'.format( e[0], f[0])) self.assertEqual( e[1], f[1], msg='Edges not the same index={}, edge={}, found={}'. format(i, e[1], f[1]))
def test_kline_decode(self): self.test_name = 'K-line message' self.trial_count = 10 for i in xrange(self.trial_count): self.update_progress(i + 1) msg_count = random.randint(1, 6) messages = [] for i in xrange(msg_count): data_bytes = random.randint(0, 6) protocol = random.choice((kline.KLineProtocol.ISO9141, kline.KLineProtocol.ISO14230)) if protocol == kline.KLineProtocol.ISO9141: # ISO9141 request msg = [0x68, 0x6A, 0xF1] else: header_size = random.choice((3, 4)) if header_size == 3: msg = [0x80 + data_bytes + 1, 0xD1, 0xF1] else: msg = [0x80, data_bytes + 1, 0xD1, 0xF1] sid = random.randint(0, 0x3F) msg.append(sid) msg.extend( [random.randint(0, 0xFF) for b in xrange(data_bytes)]) cs = sum(msg) % 256 msg.append(cs) messages.append(msg) data_bytes = random.randint(0, 6) if protocol == kline.KLineProtocol.ISO9141: # ISO9141 response msg = [0x48, 0x6B, 0xD1] else: if header_size == 3: msg = [0x80 + data_bytes + 1, 0xF1, 0xD1] else: msg = [0x80, data_bytes + 1, 0xF1, 0xD1] msg.append(sid + 0x40) msg.extend( [random.randint(0, 0xFF) for b in xrange(data_bytes)]) cs = sum(msg) % 256 msg.append(cs) messages.append(msg) baud = 10400 sample_rate = baud * 100.0 rise_time = sigp.min_rise_time( sample_rate) * 10.0 # 10x min rise time edges = kline.iso_k_line_synth(messages, idle_start=8.0 / baud, idle_end=8.0 / baud) samples = sigp.synth_wave(edges, sample_rate, rise_time, ripple_db=60) noisy = sigp.amplify(sigp.noisify(samples, snr_db=20), gain=12.0) waveform = list(noisy) records_it = kline.iso_k_line_decode(iter(waveform)) records = list(records_it) raw_decode = [] for r in records: msg = r.msg.header.bytes() + r.msg.data + [r.msg.checksum] raw_decode.append([b.data for b in msg]) msg_match = True for msg, omsg in zip(raw_decode, messages): if msg != omsg: msg_match = False break self.assertTrue(msg_match, "Messages not decoded successfully")
def test_kline_decode(self): self.test_name = 'K-line message' self.trial_count = 10 for i in xrange(self.trial_count): self.update_progress(i+1) msg_count = random.randint(1, 6) messages = [] for i in xrange(msg_count): data_bytes = random.randint(0,6) protocol = random.choice((kline.KLineProtocol.ISO9141, kline.KLineProtocol.ISO14230)) if protocol == kline.KLineProtocol.ISO9141: # ISO9141 request msg = [0x68, 0x6A, 0xF1] else: header_size = random.choice((3, 4)) if header_size == 3: msg = [0x80 + data_bytes+1, 0xD1, 0xF1] else: msg = [0x80, data_bytes+1, 0xD1, 0xF1] sid = random.randint(0, 0x3F) msg.append(sid) msg.extend([random.randint(0,0xFF) for b in xrange(data_bytes)]) cs = sum(msg) % 256 msg.append(cs) messages.append(msg) data_bytes = random.randint(0,6) if protocol == kline.KLineProtocol.ISO9141: # ISO9141 response msg = [0x48, 0x6B, 0xD1] else: if header_size == 3: msg = [0x80 + data_bytes+1, 0xF1, 0xD1] else: msg = [0x80, data_bytes+1, 0xF1, 0xD1] msg.append(sid + 0x40) msg.extend([random.randint(0,0xFF) for b in xrange(data_bytes)]) cs = sum(msg) % 256 msg.append(cs) messages.append(msg) baud = 10400 sample_rate = baud * 100.0 rise_time = sigp.min_rise_time(sample_rate) * 10.0 # 10x min rise time edges = kline.iso_k_line_synth(messages, idle_start=8.0 / baud, idle_end=8.0 / baud) samples = sigp.synth_wave(edges, sample_rate, rise_time, ripple_db=60) noisy = sigp.amplify(sigp.noisify(samples, snr_db=20), gain=12.0) waveform = list(noisy) records_it = kline.iso_k_line_decode(iter(waveform)) records = list(records_it) raw_decode = [] for r in records: msg = r.msg.header.bytes() + r.msg.data + [r.msg.checksum] raw_decode.append([b.data for b in msg]) msg_match = True for msg, omsg in zip(raw_decode, messages): if msg != omsg: msg_match = False break self.assertTrue(msg_match, "Messages not decoded successfully")
def test_find_logic_levels(self): # test with clean samples # with noise # with bandwidth limited (slow and fast) # with noise and bandwidth limit # with first edge before 200th sample, just before 200th, just after 200th, after 200th sample_rate = 1000.0 sample_period = 1.0 / sample_rate rise_time = sigp.min_rise_time(sample_rate) * 50.0 edge_vectors = { 'flat_line': (((0.0,0), (3.0,0)), False), 'rising_edge_100': (((0.0,0), (0.10,1), (3.0,1)), False), 'rising_edge_190': (((0.0,0), (0.19,1), (3.0,1)), True), 'rising_edge_200': (((0.0,0), (0.2,1), (3.0,1)), True), 'rising_edge_400': (((0.0,0), (0.40,1), (3.0,1)), True), 'pulse_100': (((0.0,0), (0.10,1), (0.5,0), (3.0,0)), True), 'multi_pulse': (((0.0,0), (0.7, 1), (0.8, 0), (0.9, 1), (1.0, 0), \ (1.1,1), (1.2,0), (1.3,1), (1.4,0), \ (1.5,1), (1.6,0), (3.0,1)), True), } no_noise_vectors = ('rising_edge_190', 'rising_edge_200') sample_vectors = {} for name, vector in edge_vectors.iteritems(): samples = list(sigp.edges_to_sample_stream(iter(vector[0]), sample_period)) sample_vectors[name] = (samples, vector[1]) base_names = sample_vectors.keys() for name in base_names: if name in no_noise_vectors: continue noisy = list(sigp.noisify(iter(sample_vectors[name][0]), snr_db=20.0)) sample_vectors['noisy_' + name] = (noisy, sample_vectors[name][1]) for name in base_names: bwlim = list(sigp.filter_waveform(iter(sample_vectors[name][0]), sample_rate, rise_time)) sample_vectors['bwlim_' + name] = (bwlim, sample_vectors[name][1]) for name in base_names: if name in no_noise_vectors: continue bwlim_noisy = list(sigp.noisify(sigp.filter_waveform(iter(sample_vectors[name][0]), \ sample_rate, rise_time), snr_db=20.0)) sample_vectors['bwlim_noisy_' + name] = (bwlim_noisy, sample_vectors[name][1]) #sample_vectors.pop('rising_edge_400', None) for name, vector in sample_vectors.iteritems(): samples = vector[0] expect_success = vector[1] logic_levels = decode.find_logic_levels(samples) if not expect_success: continue # We expected find_logic_levels() to fail if logic_levels is None: #print('### Fail:', name) self.fail('No logic levels found') #print('####', name, logic_levels) self.assertRelativelyEqual(logic_levels[0], 0.0, epsilon=0.16, msg='Bad logic 0: {}'.format(logic_levels[0])) self.assertRelativelyEqual(logic_levels[1], 1.0, epsilon=0.1, msg='Bad logic 1: {}'.format(logic_levels[1]))