From e4efe7ada94afd49654293c128324722f60175f7 Mon Sep 17 00:00:00 2001 From: Gabriel Guerrer Date: Fri, 3 Nov 2023 21:17:13 -0300 Subject: [PATCH] v1.0.1 updates --- CHANGELOG.md | 10 + README.md | 30 +- examples/lamp_stats.py | 28 +- examples/led_test.py | 6 +- examples/rava_interactive.py | 88 ++-- examples/rng_async.py | 19 +- examples/rng_byte_stream.py | 7 +- examples/rng_bytes_to_file.py | 8 +- examples/rng_doubles.py | 56 +++ examples/rng_example.py | 29 +- examples/rng_pulse_counts.py | 17 +- examples/rng_throughput.py | 12 +- pyproject.toml | 8 +- src/rng_rava/rava_defs.py | 50 +- src/rng_rava/rava_rng.py | 898 ++++++++++++++++------------------ src/rng_rava/rava_rng_aio.py | 373 ++++++-------- src/rng_rava/rava_rng_led.py | 137 +++--- 17 files changed, 840 insertions(+), 936 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 examples/rng_doubles.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..448ee60 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## v1.0.1 + +* Removed numpy dependency +* Changed maximum line length from 80 to 120. This change does not apply to the code's documentation +* Using "not in" and "is not None" +* Correcting firmware version in eeprom_firmware +* Adding callbacks +* Setting logger name to 'rava' +* Changed health startup results format +* Including hardware float generation (and moved software double to examples) \ No newline at end of file diff --git a/README.md b/README.md index 04fe614..fa7d2b0 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ pip install rng_rava ``` Requirements: - * [pyserial](https://github.com/pyserial/pyserial) - * [numpy](https://github.com/numpy/numpy) + * [pyserial](https://github.com/pyserial/pyserial) ## Usage @@ -50,11 +49,11 @@ rng = rava.RAVA_RNG() rng.connect(serial_number=rava_sns[0]) ''' -The default PWM and RNG configuration parameters are stored in the EEPROM memory -and can be accessed with rng.get_eeprom_pwm() and rng.get_eeprom_rng(). If -desired, users can modify the default values using the respective snd_ functions. -Additionally, it is possible to make non-permanent configuration changes using -the following commands: +The default PWM and RNG configuration parameters are stored in the EEPROM memory +and can be accessed with rng.get_eeprom_pwm() and rng.get_eeprom_rng(). If +desired, users can modify the default values using the respective snd_ +functions. Additionally, it is possible to make non-permanent configuration +changes using the following commands: ''' # Configure PWM @@ -71,25 +70,22 @@ Next, the generation of various random data types is demonstrated. pc_a, pc_b = rng.get_rng_pulse_counts(n_counts=100) # Generate a random bit XORing both channels -bit = rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB_XOR']) +bit = rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB_XOR']) # Generate 100 random bytes en each channel without post-processing # Output as numpy array -bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=100, - postproc_id=rava.D_RNG_POSTPROC['NONE'], - out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) +bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=100, + postproc_id=rava.D_RNG_POSTPROC['NONE'], + list_output=True) # Generate 100 8-bit integers between 0 and 99 -ints8 = rng.get_rng_int8s(n_ints=100, int_max=100) +ints8 = rng.get_rng_int8s(n_ints=100, int_delta=100) -# Generate 100 16-bit integers between 0 and 999 -ints16 = rng.get_rng_int16s(n_ints=100, int_max=999) +# Generate 100 16-bit integers between 0 and 9999 +ints16 = rng.get_rng_int16s(n_ints=100, int_delta=10000) # Generate 100 32-bit floats ranging between 0 and 1 floats = rng.get_rng_floats(n_floats=100) - -# Generate 100 64-bit doubles ranging between 0 and 1 -doubles = rng.get_rng_doubles(n_doubles=100) ``` ## Associated projects diff --git a/examples/lamp_stats.py b/examples/lamp_stats.py index 23b7360..19e02b5 100644 --- a/examples/lamp_stats.py +++ b/examples/lamp_stats.py @@ -52,37 +52,29 @@ def measure_lamp_statistics(): # Print results print('\nLamp Statistics' - '\nLasted {:.0f}h {:.0f}m {:.0f}s, yielding {} experiments' - .format(delta_t_h, delta_t_min, delta_t_s, exp_n), + '\nLasted {:.0f}h {:.0f}m {:.0f}s, yielding {} experiments'.format(delta_t_h, delta_t_min, delta_t_s, exp_n), - '\n where {} ({:.2f}%) reached statistical significance' - .format(stats['exp_n_zsig'], - stats['exp_n_zsig']/exp_n*100), + '\n where {} ({:.2f}%) reached statistical significance'.format(stats['exp_n_zsig'], + stats['exp_n_zsig']/exp_n*100), '\nMeaning one should expect to find:', - '\n {:.2f} significant events per 1 min' - .format(stats['exp_n_zsig']/delta_t*60), + '\n {:.2f} significant events per 1 min'.format(stats['exp_n_zsig']/delta_t*60), - '\n {:.2f} significant events per 5 min' - .format(stats['exp_n_zsig']/delta_t*300), + '\n {:.2f} significant events per 5 min'.format(stats['exp_n_zsig']/delta_t*300), - '\n {:.2f} significant events per 10 min' - .format(stats['exp_n_zsig']/delta_t*600), + '\n {:.2f} significant events per 10 min'.format(stats['exp_n_zsig']/delta_t*600), - '\n {:.2f} significant events per 30 min' - .format(stats['exp_n_zsig']/delta_t*1800), + '\n {:.2f} significant events per 30 min'.format(stats['exp_n_zsig']/delta_t*1800), - '\n {:.2f} significant events per 1 h' - .format(stats['exp_n_zsig']/delta_t*3600), + '\n {:.2f} significant events per 1 h'.format(stats['exp_n_zsig']/delta_t*3600), '\nColor distribution (%):', '\n R={:.2f} O={:.2f} Y={:.2f} G={:.2f}' '\n C={:.2f} B={:.2f} PU={:.2f} PI={:.2f}' - .format(stats['red']/exp_n*100, stats['orange']/exp_n*100, - stats['yellow']/exp_n*100, stats['green']/exp_n*100, - stats['cyan']/exp_n*100, stats['blue']/exp_n*100, + .format(stats['red']/exp_n*100, stats['orange']/exp_n*100, stats['yellow']/exp_n*100, + stats['green']/exp_n*100, stats['cyan']/exp_n*100, stats['blue']/exp_n*100, stats['purple']/exp_n*100, stats['pink']/exp_n*100)) measure_lamp_statistics() \ No newline at end of file diff --git a/examples/led_test.py b/examples/led_test.py index 5257867..180d07e 100644 --- a/examples/led_test.py +++ b/examples/led_test.py @@ -12,7 +12,7 @@ rngled = rava.RAVA_RNG_LED() dev_sns = rava.find_rava_sns() if len(dev_sns): - rngled.connect(serial_number=dev_sns[0]) + rngled.connect(serial_number=dev_sns[0]) else: rava.lg.error('No device found') exit() @@ -21,8 +21,8 @@ rngled.snd_led_color(color_hue=rava.D_LED_COLOR['BLUE'], intensity=0) # Fade to full intensity -rngled.snd_led_intensity_fade(intensity_tgt=255, duration_ms=1000) -time.sleep(2) +rngled.snd_led_intensity_fade(intensity_tgt=255, duration_ms=2000) +time.sleep(3) # Oscilate colors rngled.snd_led_color_oscillate(n_cycles=3, duration_ms=4000) diff --git a/examples/rava_interactive.py b/examples/rava_interactive.py index 17cd1bd..a6640f3 100644 --- a/examples/rava_interactive.py +++ b/examples/rava_interactive.py @@ -1,8 +1,8 @@ ''' -This file should be opened in an interactive Python environment. In VSCode, the -users can utilize the F6 shortcut to execute the initial code, establishing a -connection with the device. Next, users can navigate the code and execute each -line by positioning the cursor and pressing Shift + Enter. This approach +This file should be opened in an interactive Python environment. In VSCode, the +users can utilize the F6 shortcut to execute the initial code, establishing a +connection with the device. Next, users can navigate the code and execute each +line by positioning the cursor and pressing Shift + Enter. This approach facilitates comprehensive testing of the RAVA's functionality. This example code is in the public domain. @@ -27,19 +27,15 @@ def DEVICE(): rng.connect(serial_number=rava_sns[0]) - rng.connected() - rng.close() rng.get_device_serial_number() - rng.get_device_temperature() - rng.get_device_free_ram() rng.snd_device_reboot() - + def EEPROM(): @@ -47,7 +43,7 @@ def EEPROM(): rng.get_eeprom_device() rng.snd_eeprom_device(temp_calib_slope=397, temp_calib_intercept=-280) - rng.snd_eeprom_device(temp_calib_slope=300, temp_calib_intercept=-200) + rng.snd_eeprom_device(temp_calib_slope=1, temp_calib_intercept=0) rng.get_eeprom_firmware() @@ -64,8 +60,8 @@ def EEPROM(): rng.snd_eeprom_rng(sampling_interval_us=15) rng.get_eeprom_led() - rng.snd_eeprom_led(led_attached=0) - rng.snd_eeprom_led(led_attached=1) + rng.snd_eeprom_led(led_attached=False) + rng.snd_eeprom_led(led_attached=True) rng.get_eeprom_lamp() rng.snd_eeprom_lamp(exp_dur_max_ms=5*60*1000, exp_z_significant=3.925, exp_mag_smooth_n_trials=20) @@ -93,44 +89,37 @@ def RNG(): rng.snd_rng_timing_debug_d1(on=True) rng.snd_rng_timing_debug_d1(on=False) - - rng.get_rng_pulse_counts(n_counts=100) - - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB']) - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['A']) - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['B']) - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB_XOR']) - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB_RND']) - - rng.get_rng_bytes(n_bytes=100, postproc_id=rava.D_RNG_POSTPROC['NONE'], out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) - rng.get_rng_bytes(n_bytes=100, postproc_id=rava.D_RNG_POSTPROC['XOR'], out_type=rava.D_RNG_BYTE_OUT['PY_LIST']) - rng.get_rng_bytes(n_bytes=100, postproc_id=rava.D_RNG_POSTPROC['XOR_DICHTL'], out_type=rava.D_RNG_BYTE_OUT['PY_BYTES']) - rng.get_rng_bytes(n_bytes=100, postproc_id=rava.D_RNG_POSTPROC['VON_NEUMANN'], out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) - - rng.get_rng_int8s(n_ints=100, int_max=99) - rng.get_rng_int16s(n_ints=100, int_max=999) - rng.get_rng_floats(n_floats=100) - rng.get_rng_doubles(n_doubles=100) - rng.get_rng_byte_stream_status() + rng.get_rng_pulse_counts(n_counts=15) - rng.snd_rng_byte_stream_start(n_bytes=1, stream_delay_ms=50, postproc_id=rava.D_RNG_POSTPROC['NONE']) - rng.snd_rng_byte_stream_start(n_bytes=10000, stream_delay_ms=0, postproc_id=rava.D_RNG_POSTPROC['NONE']) + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB']) + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['A']) + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['B']) + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB_XOR']) + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB_RND']) - rng.get_rng_byte_stream_data(out_type=rava.D_RNG_BYTE_OUT['PY_BYTES']) - rng.get_rng_byte_stream_data(out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) + rng.get_rng_bytes(n_bytes=15, postproc_id=rava.D_RNG_POSTPROC['NONE'], list_output=True) + rng.get_rng_bytes(n_bytes=15, postproc_id=rava.D_RNG_POSTPROC['XOR'], list_output=True) + rng.get_rng_bytes(n_bytes=15, postproc_id=rava.D_RNG_POSTPROC['XOR_DICHTL'], list_output=True) + rng.get_rng_bytes(n_bytes=15, postproc_id=rava.D_RNG_POSTPROC['VON_NEUMANN'], list_output=True) + rng.get_rng_int8s(n_ints=15, int_delta=10) + rng.get_rng_int8s(n_ints=15, int_delta=100) + rng.get_rng_int16s(n_ints=15, int_delta=1000) + rng.get_rng_floats(n_floats=15) + + rng.get_rng_byte_stream_status() + rng.snd_rng_byte_stream_start(n_bytes=10, stream_interval_ms=200, postproc_id=rava.D_RNG_POSTPROC['NONE']) + rng.get_rng_byte_stream_data(list_output=True) rng.snd_rng_byte_stream_stop() def HEALTH(): rng.snd_health_startup_run() - rng.get_health_startup_results() - rava.print_health_startup_results(*rng.get_health_startup_results()) - + rng.get_health_continuous_errors() @@ -177,8 +166,8 @@ def PERIPHERALS(): rng.snd_periph_d1_trigger_input(on=True) rng.snd_periph_d1_trigger_input(on=False) - rng.snd_periph_d1_comparator(neg_to_adc12=False) - rng.snd_periph_d1_comparator(neg_to_adc12=True) + rng.snd_periph_d1_comparator(neg_to_d5=False) + rng.snd_periph_d1_comparator(neg_to_d5=True) rng.snd_periph_d1_comparator(on=False) rng.snd_periph_digi_mode(periph_id=1, mode_id=rava.D_PERIPH_MODES['OUTPUT']) @@ -187,13 +176,13 @@ def PERIPHERALS(): rng.snd_periph_d1_delay_us_test(delay_us=10) # Running with an unconnected D2 may flood the driver with random signaling - rng.snd_periph_d2_input_capture(on=True) + rng.snd_periph_d2_input_capture(on=True) rng.snd_periph_d2_input_capture(on=False) - rng.get_periph_d2_input_capture() + rng.get_periph_d2_input_capture() - rng.snd_periph_d3_timer3_trigger_output(delay_ms=1) - rng.snd_periph_d3_timer3_trigger_output(delay_ms=10) - rng.snd_periph_d3_timer3_trigger_output(delay_ms=100) + rng.snd_periph_d3_timer3_trigger_output(interval_ms=1) + rng.snd_periph_d3_timer3_trigger_output(interval_ms=10) + rng.snd_periph_d3_timer3_trigger_output(interval_ms=100) rng.snd_periph_d3_timer3_trigger_output(on=False) rng.snd_periph_d3_timer3_pwm(freq_prescaler=1, top=2**16-1, duty=1000) @@ -202,15 +191,15 @@ def PERIPHERALS(): rng.snd_periph_d3_timer3_pwm(on=False) rng.snd_periph_d4_pin_change(on=True) - rng.snd_periph_d4_pin_change(on=False) + rng.snd_periph_d4_pin_change(on=False) - rng.get_periph_d5_adc_read(ref_5v=0, clk_prescaler=1, oversampling_n_bits=0) rng.get_periph_d5_adc_read(ref_5v=0, clk_prescaler=6, oversampling_n_bits=0) + rng.get_periph_d5_adc_read(ref_5v=1, clk_prescaler=6, oversampling_n_bits=0) rng.get_periph_d5_adc_read(ref_5v=0, clk_prescaler=6, oversampling_n_bits=6) - rng.get_periph_d5_adc_read(on=False) + rng.get_periph_d5_adc_read(ref_5v=1, clk_prescaler=6, oversampling_n_bits=6) -def INTERFACES(): +def INTERFACES(): rng.get_interface_ds18bs0() @@ -255,6 +244,7 @@ def LAMP(): rng.snd_lamp_mode(on=False) rng.snd_lamp_debug(on=True) + rng.get_lamp_debug() rng.snd_lamp_debug(on=False) rng.get_lamp_statistics() \ No newline at end of file diff --git a/examples/rng_async.py b/examples/rng_async.py index 2228c25..88ad214 100644 --- a/examples/rng_async.py +++ b/examples/rng_async.py @@ -1,5 +1,5 @@ ''' -This example showcases asynchronous RNG functionality. +This example showcases asynchronous RNG functionality. This example code is in the public domain. Author: Gabriel Guerrer @@ -15,7 +15,7 @@ async def main(): rng = rava.RAVA_RNG_AIO() dev_sns = rava.find_rava_sns() if len(dev_sns): - await rng.connect(dev_sns[0]) + await rng.connect(dev_sns[0]) else: rava.lg.error('No device found') exit() @@ -25,15 +25,14 @@ async def main(): print('\nRNG setup: {}\n'.format(await rng.get_rng_setup())) # Generate random data + N_DATA = 30 results = await asyncio.gather( - rng.get_rng_pulse_counts(n_counts=10), - rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB']), - rng.get_rng_bytes(n_bytes=10, postproc_id=rava.D_RNG_POSTPROC['NONE'], - out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']), - rng.get_rng_int8s(n_ints=10, int_max=99), - rng.get_rng_int16s(n_ints=10, int_max=999), - rng.get_rng_floats(n_floats=10), - rng.get_rng_doubles(n_doubles=10) + rng.get_rng_pulse_counts(n_counts=N_DATA), + rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB']), + rng.get_rng_bytes(n_bytes=N_DATA, postproc_id=rava.D_RNG_POSTPROC['NONE'], list_output=True), + rng.get_rng_int8s(n_ints=N_DATA, int_delta=100), + rng.get_rng_int16s(n_ints=N_DATA, int_delta=1000), + rng.get_rng_floats(n_floats=N_DATA) ) print('\nRNG data: {}\n'.format(results)) diff --git a/examples/rng_byte_stream.py b/examples/rng_byte_stream.py index 5876ce6..f0271ba 100644 --- a/examples/rng_byte_stream.py +++ b/examples/rng_byte_stream.py @@ -11,19 +11,18 @@ rng = rava.RAVA_RNG() dev_sns = rava.find_rava_sns() if len(dev_sns): - rng.connect(serial_number=dev_sns[0]) + rng.connect(serial_number=dev_sns[0]) else: rava.lg.error('No device found') exit() # Generate 3 bytes every 0.5s -rng.snd_rng_byte_stream_start(n_bytes=3, stream_delay_ms=500) +rng.snd_rng_byte_stream_start(n_bytes=5, stream_interval_ms=500) # Print 10 first values print() for i in range(10): - rnd_a, rnd_b = rng.get_rng_byte_stream_data( - out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) + rnd_a, rnd_b = rng.get_rng_byte_stream_data(list_output=True) print('RNG A, B = {}, {}'.format(rnd_a, rnd_b)) # Stop stream diff --git a/examples/rng_bytes_to_file.py b/examples/rng_bytes_to_file.py index 5295b9c..44eea01 100644 --- a/examples/rng_bytes_to_file.py +++ b/examples/rng_bytes_to_file.py @@ -15,7 +15,7 @@ rng = rava.RAVA_RNG() dev_sns = rava.find_rava_sns() if len(dev_sns): - rng.connect(serial_number=dev_sns[0]) + rng.connect(serial_number=dev_sns[0]) else: rava.lg.error('No device found') exit() @@ -24,11 +24,11 @@ with open(FILE_OUTPUT, mode='bw') as f: # Generate bytes - bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=N_BYTES//2, + bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=N_BYTES//2, postproc_id=rava.D_RNG_POSTPROC['NONE'], - out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY'], + list_output=False, timeout=100) - + # Write to file f.write(bytes_a) f.write(bytes_b) diff --git a/examples/rng_doubles.py b/examples/rng_doubles.py new file mode 100644 index 0000000..6434a43 --- /dev/null +++ b/examples/rng_doubles.py @@ -0,0 +1,56 @@ +''' +This example showcases the RNG generation of double precision floating-point +numbers. + +This example code is in the public domain. +Author: Gabriel Guerrer +''' + +import struct +import numpy as np +import rng_rava as rava + +# Find RAVA device and connect +rng = rava.RAVA_RNG() +dev_sns = rava.find_rava_sns() +if len(dev_sns): + rng.connect(serial_number=dev_sns[0]) +else: + rava.lg.error('No device found') + exit() + +def get_rng_doubles(n_doubles): + if n_doubles >= (2**32) // 8: + print('RNG Doubles: Maximum n_doubles is {}'.format((2**32) // 8)) + return None + + # 64 bits floating point number + bytes_res = rng.get_rng_bytes(n_bytes=n_doubles*8, timeout=None) + if bytes_res is None: + return + rnd_bytes_a, rnd_bytes_b = bytes_res + + # XOR them + int_a = int.from_bytes(rnd_bytes_a, 'little') + int_b = int.from_bytes(rnd_bytes_b, 'little') + rnd_bytes = (int_a ^ int_b).to_bytes(len(rnd_bytes_a), 'little') + # Convert bytes to ints + rnd_lists = struct.unpack('<{}Q'.format(n_doubles), rnd_bytes) + rnd_ints = np.array(rnd_lists, dtype=np.uint64) + + # IEEE754 bit pattern for single precision floating point value in the + # range of 1.0 - 2.0. Uses the first 52 bits and fixes the float + # exponent to 1023 + rnd_ints_tmp = (rnd_ints & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000 + rnd_bytes_filtered = rnd_ints_tmp.tobytes() + rnd_lists_filtered = struct.unpack('<{}d'.format(n_doubles), rnd_bytes_filtered) + rnd_doubles = np.array(rnd_lists_filtered, dtype=np.float64) + return rnd_doubles - 1 + +# Get and print 100 doubles +doubles = get_rng_doubles(100) +doubles_str = '\n'.join(['{:.16f}'.format(d) for d in doubles]) +print(doubles_str) + +# Close device +rng.close() \ No newline at end of file diff --git a/examples/rng_example.py b/examples/rng_example.py index b71144b..4d0992c 100644 --- a/examples/rng_example.py +++ b/examples/rng_example.py @@ -15,11 +15,11 @@ rng.connect(serial_number=rava_sns[0]) ''' -The default PWM and RNG configuration parameters are stored in the EEPROM memory -and can be accessed with rng.get_eeprom_pwm() and rng.get_eeprom_rng(). If -desired, users can modify the default values using the respective snd_ functions. -Additionally, it is possible to make non-permanent configuration changes using -the following commands: +The default PWM and RNG configuration parameters are stored in the EEPROM memory +and can be accessed with rng.get_eeprom_pwm() and rng.get_eeprom_rng(). If +desired, users can modify the default values using the respective snd_ +functions. Additionally, it is possible to make non-permanent configuration +changes using the following commands: ''' # Configure PWM @@ -36,22 +36,19 @@ pc_a, pc_b = rng.get_rng_pulse_counts(n_counts=100) # Generate a random bit XORing both channels -bit = rng.get_rng_bits(bit_type_id=rava.D_RNG_BIT_SRC['AB_XOR']) +bit = rng.get_rng_bits(bit_source_id=rava.D_RNG_BIT_SRC['AB_XOR']) # Generate 100 random bytes en each channel without post-processing # Output as numpy array -bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=100, - postproc_id=rava.D_RNG_POSTPROC['NONE'], - out_type=rava.D_RNG_BYTE_OUT['NUMPY_ARRAY']) +bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=100, + postproc_id=rava.D_RNG_POSTPROC['NONE'], + list_output=True) # Generate 100 8-bit integers between 0 and 99 -ints8 = rng.get_rng_int8s(n_ints=100, int_max=100) +ints8 = rng.get_rng_int8s(n_ints=100, int_delta=100) -# Generate 100 16-bit integers between 0 and 999 -ints16 = rng.get_rng_int16s(n_ints=100, int_max=999) +# Generate 100 16-bit integers between 0 and 9999 +ints16 = rng.get_rng_int16s(n_ints=100, int_delta=10000) # Generate 100 32-bit floats ranging between 0 and 1 -floats = rng.get_rng_floats(n_floats=100) - -# Generate 100 64-bit doubles ranging between 0 and 1 -doubles = rng.get_rng_doubles(n_doubles=100) \ No newline at end of file +floats = rng.get_rng_floats(n_floats=100) \ No newline at end of file diff --git a/examples/rng_pulse_counts.py b/examples/rng_pulse_counts.py index e475410..d68c326 100644 --- a/examples/rng_pulse_counts.py +++ b/examples/rng_pulse_counts.py @@ -1,18 +1,19 @@ ''' This example showcases RNG pulse count measurements for different values of the -sampling interval configuration. +sampling interval configuration. This example code is in the public domain. Author: Gabriel Guerrer ''' +import numpy as np import rng_rava as rava # Find RAVA device and connect rng = rava.RAVA_RNG() dev_sns = rava.find_rava_sns() if len(dev_sns): - rng.connect(serial_number=dev_sns[0]) + rng.connect(serial_number=dev_sns[0]) else: rava.lg.error('No device found') exit() @@ -20,19 +21,21 @@ # Config PWM rng.snd_pwm_setup(freq_id=rava.D_PWM_FREQ['50_KHZ'], duty=20) -# Vary sampling intervals +# Vary sampling intervals sampling_intervals = range(1, 20+1) -print() for si in sampling_intervals: rng.snd_rng_setup(sampling_interval_us=si) # Measure pulse counts pcs_a, pcs_b = rng.get_rng_pulse_counts(n_counts=5000) + # Calculate mean values + pcs_mean_a = np.array(pcs_a, dtype=np.uint8).mean() + pcs_mean_b = np.array(pcs_b, dtype=np.uint8).mean() + # Inform mean values - print('si={} us; pc_a={:.2f}, pc_b={:.2f}' - .format(si, pcs_a.mean(), pcs_b.mean())) - + print('\nsi={} us; pc_a={:.2f}, pc_b={:.2f}'.format(si, pcs_mean_a, pcs_mean_b)) + # Close device rng.close() \ No newline at end of file diff --git a/examples/rng_throughput.py b/examples/rng_throughput.py index 655dbf8..763427b 100644 --- a/examples/rng_throughput.py +++ b/examples/rng_throughput.py @@ -16,7 +16,7 @@ rng = rava.RAVA_RNG() dev_sns = rava.find_rava_sns() if len(dev_sns): - rng.connect(serial_number=dev_sns[0]) + rng.connect(serial_number=dev_sns[0]) else: rava.lg.error('No device found') exit() @@ -32,17 +32,15 @@ def rng_bytes_throughput(n_bytes, n_repeat, postproc_id): delta_s = deltas.mean() freq = (n_bytes / delta_s) * 8 / 1000 freq *= 2 # Considering both channels - interv = (delta_s/ n_bytes) * 1e6 - print('Throughput: Produced {} x {} bytes in each channel' - .format(n_bytes, n_repeat)) - print(' Freq Kbit/s = {:.3f}'.format(freq) ) + interv = (delta_s/ n_bytes) * 1e6 + print('Throughput: Produced {} x {} bytes in each channel'.format(n_bytes, n_repeat)) + print(' Freq Kbit/s = {:.3f}'.format(freq) ) print(' Byte interval (us) = {:.3f}'.format(interv)) # Obtain the throughput for different post-processing options for pp_str in ['NONE', 'XOR', 'XOR_DICHTL', 'VON_NEUMANN']: print('\n> PP {}'.format(pp_str)) - rng_bytes_throughput(n_bytes=N_BYTES, n_repeat=N_REPEAT, - postproc_id=rava.D_RNG_POSTPROC[pp_str]) + rng_bytes_throughput(n_bytes=N_BYTES, n_repeat=N_REPEAT, postproc_id=rava.D_RNG_POSTPROC[pp_str]) # Close device rng.close() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index caa1d5d..4e99f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,14 @@ build-backend = "setuptools.build_meta" [project] name = "rng_rava" -version = "1.0.0" +version = "1.0.1" authors = [ { name="Gabriel Guerrer", email="gabrielguerrer@gmail.com" }, ] description = "Python driver for the RAVA RNG device" readme = "README.md" requires-python = ">=3.7" -dependencies = [ - "numpy", +dependencies = [ "pyserial" ] classifiers = [ @@ -22,5 +21,4 @@ classifiers = [ ] [project.urls] -"Source" = "https://github.com/gabrielguerrer/rng_rava_driver_py" -"Homepage" = "https://rava.cc" \ No newline at end of file +"Homepage" = "https://github.com/gabrielguerrer/rng_rava_driver_py" \ No newline at end of file diff --git a/src/rng_rava/rava_defs.py b/src/rng_rava/rava_defs.py index b9424d6..2532c44 100644 --- a/src/rng_rava/rava_defs.py +++ b/src/rng_rava/rava_defs.py @@ -8,51 +8,37 @@ Definitions and variables used by the RAVA modules. """ -import logging - -##################### -## LOGGING - -# RAVA_LOG_LEVEL = logging.DEBUG -RAVA_LOG_LEVEL = logging.INFO - -LOG_FILL = 14 * ' ' - -logging.basicConfig(level=RAVA_LOG_LEVEL, - format='%(asctime)s %(levelname)s %(message)s', - datefmt='%H:%M:%S') - -lg = logging.getLogger(__name__) - - -##################### -# DEFINITIONS +# RAVA_LOG_LEVEL = 10 # logging.DEBUG +RAVA_LOG_LEVEL = 20 # logging.INFO +LOG_FILL = 17 * ' ' RAVA_USB_VID = 0x1209 # https://pid.codes -RAVA_USB_PID = 0x4884 +RAVA_USB_PID = 0x4884 COMM_MSG_START = b'$' COMM_MSG_LEN = 8 +PERIPH_PORTS = 5 + GET_TIMEOUT_S = 3. -SERIAL_LISTEN_LOOP_DELAY_S = 0.02 # 20 ms, 50 Hz readings +SERIAL_LISTEN_LOOP_INTERVAL_S = 0.02 # 20 ms, 50 Hz readings -RNG_BYTE_STREAM_MAX_DELAY_MS = 4194 +RNG_BYTE_STREAM_MAX_INTERVAL_MS = 4194 D_DEV_COMM = { 'DEVICE_SERIAL_NUMBER':1, 'DEVICE_TEMPERATURE':2, 'DEVICE_FREE_RAM':3, - 'DEVICE_REBOOT':4, + 'DEVICE_REBOOT':4, 'DEVICE_DEBUG':5, 'EEPROM_RESET_TO_DEFAULT':10, 'EEPROM_DEVICE':11, 'EEPROM_FIRMWARE':12, 'EEPROM_PWM':13, - 'EEPROM_RNG':14, + 'EEPROM_RNG':14, 'EEPROM_LED':15, 'EEPROM_LAMP':16, @@ -63,14 +49,15 @@ 'RNG_BITS':42, 'RNG_BYTES':43, 'RNG_TIMING_DEBUG_D1':44, - + 'RNG_INT8S':50, 'RNG_INT16S':51, + 'RNG_FLOATS':52, 'RNG_STREAM_START':60, 'RNG_STREAM_STOP':61, 'RNG_STREAM_BYTES':62, - 'RNG_STREAM_STATUS':63, + 'RNG_STREAM_STATUS':63, 'HEALTH_STARTUP_RUN':70, 'HEALTH_STARTUP_RESULTS':71, @@ -87,7 +74,7 @@ 'LAMP_MODE':90, 'LAMP_STATISTICS':91, 'LAMP_DEBUG':92, - + 'PERIPH_MODE':100, 'PERIPH_READ':101, 'PERIPH_WRITE':102, @@ -124,13 +111,6 @@ } D_RNG_BIT_SRC_INV = {v: k for k, v in D_RNG_BIT_SRC.items()} -D_RNG_BYTE_OUT = { - 'PY_BYTES':1, - 'PY_LIST':2, - 'NUMPY_ARRAY':3, -} -D_RNG_BYTE_OUT_INV = {v: k for k, v in D_RNG_BYTE_OUT.items()} - D_RNG_POSTPROC = { 'NONE':0, 'XOR':1, @@ -141,7 +121,7 @@ D_PERIPH_MODES = { 'INPUT':0, - 'OUTPUT':1 + 'OUTPUT':1 } D_PERIPH_MODES_INV = {v: k for k, v in D_PERIPH_MODES.items()} diff --git a/src/rng_rava/rava_rng.py b/src/rng_rava/rava_rng.py index ea1f0c7..91fb1ba 100644 --- a/src/rng_rava/rava_rng.py +++ b/src/rng_rava/rava_rng.py @@ -5,43 +5,44 @@ """ """ -The RAVA driver implements the code for communicating with an RAVA device -running the RAVA firmware. The computer running the driver assumes the role of +The RAVA driver implements the code for communicating with an RAVA device +running the RAVA firmware. The computer running the driver assumes the role of the leader device, sending command requests and reading the data replies. -The RAVA_RNG class enables the request of pulse counts, random bits, random -bytes, and random numbers (integers and floats). Additionally, it establishes -the circuit's basic functionality encompassing key modules such as EEPROM, PWM, +The RAVA_RNG class enables the request of pulse counts, random bits, random +bytes, and random numbers (integers and floats). Additionally, it establishes +the circuit's basic functionality encompassing key modules such as EEPROM, PWM, heath tests, peripherals, and interfaces. -The functions that provide access to RAVA's functionality are prefixed with +The functions that provide access to RAVA's functionality are prefixed with "snd" and "get". "Snd" commands are unidirectional and do not expect a device's -response. Conversely, "get" commands are bidirectional, where the driver -immediately attempts to retrieve the expected information after signaling the +response. Conversely, "get" commands are bidirectional, where the driver +immediately attempts to retrieve the expected information after signaling the RAVA device. -The communication exchanges start with an 8-byte message. The first byte holds -the character '$' (00100100), signifying the message start. The second byte -encodes the command's identification code, while the subsequent bytes house the +The communication exchanges start with an 8-byte message. The first byte holds +the character '$' (00100100), signifying the message start. The second byte +encodes the command's identification code, while the subsequent bytes house the command's specific data. The commands are sent to the RAVA device using the -snd_rava_msg() function, which generates the 8-byte information with the +snd_rava_msg() function, which generates the 8-byte information with the pack_rava_msg() function. -The driver operates with a parallel thread running the loop_serial_listen() -function to continuously monitor RAVA responses. When a new message is detected, -it undergoes further processing within the process_serial_comm() function. This -function stores the command's variables in a queue object associated with the -respective command ID. To achieve this, the 6-byte data is transformed into the -command's specific variables using the unpack_rava_msgdata() function. These -variables are then available for retrieval by employing the get_queue_data() +The driver operates with a parallel thread running the loop_serial_listen() +function to continuously monitor RAVA responses. When a new message is detected, +it undergoes further processing within the process_serial_comm() function. This +function stores the command's variables in a queue object associated with the +respective command ID. To achieve this, the 6-byte data is transformed into the +command's specific variables using the unpack_rava_msgdata() function. These +variables are then available for retrieval by employing the get_queue_data() function. -The queue-based design not only mitigates read-ordering conflicts but also -serves as the foundation for the asynchronous driver version, which uses asyncio -queues and implements get_queue_data() as an async function. This capability is +The queue-based design not only mitigates read-ordering conflicts but also +serves as the foundation for the asynchronous driver version, which uses asyncio +queues and implements get_queue_data() as an async function. This capability is realized in the RAVA_RNG_AIO class. """ +import logging import struct import time import threading @@ -50,66 +51,69 @@ import serial from serial.tools.list_ports import comports -import numpy as np from rng_rava.rava_defs import * +logging.basicConfig(level=RAVA_LOG_LEVEL, + format='%(asctime)s %(levelname)-7s %(message)s', + datefmt='%H:%M:%S') + +lg = logging.getLogger('rava') + + def find_rava_sns(usb_vid=RAVA_USB_VID, usb_pid=RAVA_USB_PID): - return [port_info.serial_number for port_info in comports() + sns = [port_info.serial_number for port_info in comports() if (port_info.vid == usb_vid and port_info.pid == usb_pid)] + sns.sort() + return sns def find_rava_port(serial_number): if isinstance(serial_number, bytes): serial_number = serial_number.decode() - ports = [port_info.device for port_info in comports() - if port_info.serial_number == serial_number] + ports = [port_info.device for port_info in comports() + if port_info.serial_number == serial_number] if len(ports): return ports[0] - else: + else: return None def find_usb_info(port): - return [(port_info.product, port_info.vid, port_info.pid) for port_info - in comports() if (port_info.device == port)][0] + return [(port_info.product, port_info.serial_number, port_info.vid, port_info.pid) for port_info in comports() + if (port_info.device == port)][0] -def process_bytes(bytes_data, out_type): - if not out_type in D_RNG_BYTE_OUT_INV: - lg.error('Process Bytes: Unknown out_type {}'.format(out_type)) - return None - - if out_type == D_RNG_BYTE_OUT['PY_BYTES']: - bytes_proc = bytes_data - elif out_type == D_RNG_BYTE_OUT['PY_LIST']: - bytes_proc = list(struct.unpack('<{}B'.format(len(bytes_data)), bytes_data)) - elif out_type == D_RNG_BYTE_OUT['NUMPY_ARRAY']: - bytes_tuple = struct.unpack('<{}B'.format(len(bytes_data)), bytes_data) - bytes_proc = np.array(bytes_tuple, dtype=np.uint8) - return bytes_proc +def bytes_to_numlist(bytes_data, num_type='B'): + int_size = len(bytes_data) + if num_type.lower() == 'h': + int_size = int_size // 2 + elif num_type.lower() in ['i', 'l', 'f']: + int_size = int_size // 4 + elif num_type.lower() in ['q', 'd']: + int_size = int_size // 8 + + unpack_str = '<{}{}'.format(int_size, num_type) + return list(struct.unpack(unpack_str, bytes_data)) def print_health_startup_results(test_success, test_vars_dict): success_str = 'Success' if test_success else 'Failed' - (pc_avg_a, pc_avg_b, pc_avg_dif_a, pc_avg_dif_b, - pc_avg_min, pc_avg_diff_min, pc_result) = \ - test_vars_dict['pulse_count'] - (bias_a, bias_b, bias_abs_treshold, bias_result) = \ - test_vars_dict['bit_bias'] - (chisq_a, chisq_b, chisq_max_treshold, chisq_result) = \ - test_vars_dict['byte_bias'] - + pc_result, pc_a, pc_b, pc_min = test_vars_dict['pulse_count'] + pc_diff_result, pc_diff_a, pc_diff_b, pc_diff_min = test_vars_dict['pulse_count_diff'] + bias_result, bias_a, bias_b, bias_abs_treshold = test_vars_dict['bit_bias'] + chisq_result, chisq_a, chisq_b, chisq_max_treshold = test_vars_dict['byte_bias'] + lg.info('Startup Health Tests: {}'.format(success_str) + \ - '\n{}Pulse count: {}'.format(LOG_FILL, bool(pc_result)) + \ + '\n{}Pulse Count: {}'.format(LOG_FILL, bool(pc_result)) + \ '\n{} pc_a={:.2f}, pc_b={:.2f}, pc_tresh={:.2f}'\ - .format(LOG_FILL, pc_avg_a, pc_avg_b, pc_avg_min) + \ + .format(LOG_FILL, pc_a, pc_b, pc_min) + \ + '\n{}Pulse Count Difference: {}'.format(LOG_FILL, bool(pc_diff_result)) + \ '\n{} pc_diff_a={:.2f}, pc_diff_b={:.2f}, pc_diff_tresh={:.2f}'\ - .format(LOG_FILL, pc_avg_dif_a, pc_avg_dif_b, - pc_avg_diff_min) + \ + .format(LOG_FILL, pc_diff_a, pc_diff_b, pc_diff_min) + \ '\n{}Bit bias: {}'.format(LOG_FILL, bool(bias_result)) + \ '\n{} bias_a={:.4f}, bias_b={:.4f}, bias_tresh={:.2f}'\ .format(LOG_FILL, bias_a, bias_b, bias_abs_treshold) + \ @@ -119,20 +123,19 @@ def print_health_startup_results(test_success, test_vars_dict): class RAVA_RNG: - + rava_instances = weakref.WeakSet() - + def __init__(self, dev_name='RAVA_RNG'): self.dev_name = dev_name self.queue_type = queue.Queue - + # Debug lg.debug('> {} INIT'.format(self.dev_name)) - + # Variables defined upon connection self.dev_serial_number = '' - self.dev_firmware_version = 0. - self.dev_firmware_led = None + self.dev_firmware_dict = None self.dev_usb_port = '' self.dev_usb_name = '' self.dev_usb_vid = None @@ -149,13 +152,18 @@ def __init__(self, dev_name='RAVA_RNG'): # Byte streaming variables self.rng_streaming = False + # Callback functions + self.cbkfcn_device_close = None + self.cbkfcn_rng_stream_data_available = None + self.cbkfcn_d2_input_capture_available = None + # Finalize any previous RAVA instance - for rava_instance in self.rava_instances.copy(): - rava_instance.close() - self.rava_instances.remove(rava_instance) + for rava in self.rava_instances.copy(): + rava.close() + self.rava_instances.remove(rava) # Add new instance to weakref list - self.rava_instances.add(self) + self.rava_instances.add(self) def __del__(self): @@ -170,55 +178,47 @@ def connect(self, serial_number): # Find serial port port = find_rava_port(serial_number) if port is None: - lg.error('{} Connect: No device found with SN {}' - .format(self.dev_name, serial_number)) + lg.error('{} Connect: No device found with SN {}'.format(self.dev_name, serial_number)) return False # Open serial connection if not self.open_serial(port): return False - - # Save SN info - self.dev_serial_number = serial_number # Reset serial data queues self.init_queue_data() - + # Start listening for serial commands - self.serial_listen_thread = \ - threading.Thread(target=self.loop_serial_listen) + self.serial_listen_thread = threading.Thread(target=self.loop_serial_listen) self.serial_listen_thread.start() # Stop any active RNG byte stream self.snd_rng_byte_stream_stop() # Request firmware info - firmw_info = self.get_eeprom_firmware() - self.dev_firmware_version = firmw_info['version'] - self.dev_firmware_led = firmw_info['led_enabled'] + self.dev_firmware_dict = self.get_eeprom_firmware() # Print connection info lg.info('{} Connect: Success' - '\n{}{}, Firmware v{:.2f}, SN={}, at {}' - .format(self.dev_name, LOG_FILL, self.dev_usb_name, - self.dev_firmware_version, self.dev_serial_number, - self.serial.port)) + '\n{} {}, Firmware v{}, SN={}, at {}' + .format(self.dev_name, LOG_FILL, + self.dev_usb_name, self.dev_firmware_dict['version'], self.dev_serial_number, self.serial.port)) # Request Health startup info - if firmw_info['health_startup_enabled']: + if self.dev_firmware_dict['health_startup_enabled']: test_success, test_vars = self.get_health_startup_results() # Print test info print_health_startup_results(test_success, test_vars) # Error? Users have then a limited command variety (see Firmware) - if not test_success: - lg.warning('{} Connect: Startup tests failed' - .format(self.dev_name)) + if not test_success: + lg.error('{} Connect: Startup tests failed'.format(self.dev_name)) + return False return True - + def connected(self): return self.serial.is_open @@ -228,11 +228,17 @@ def close(self): lg.debug('> {} CLOSE'.format(self.dev_name)) # Stop loop_serial_listen - self.serial_connected.clear() + self.serial_connected.clear() # Close serial connection self.serial.close() + # Callback + try: + self.cbkfcn_device_close() + except: + pass + def init_queue_data(self): # Create one queue for each command @@ -243,7 +249,7 @@ def init_queue_data(self): def put_queue_data(self, comm, value, comm_ext_id=0): # Check key - if not (comm in self.serial_data): + if comm not in self.serial_data: lg.error('{} Data: Unknown comm {}'.format(self.dev_name, comm)) return False @@ -252,16 +258,16 @@ def put_queue_data(self, comm, value, comm_ext_id=0): comm_ext = '{}_{}'.format(comm, comm_ext_id) # Queue exists? - if not (comm_ext in self.serial_data): + if comm_ext not in self.serial_data: self.serial_data[comm_ext] = self.queue_type() else: comm_ext = comm - + # Try writing to queue try: self.serial_data[comm_ext].put(value) return True - + except queue.Full: lg.error('{} Data: {} Queue full'.format(self.dev_name, comm_ext)) return False @@ -269,7 +275,7 @@ def put_queue_data(self, comm, value, comm_ext_id=0): def get_queue_data(self, comm, comm_ext_id=0, timeout=GET_TIMEOUT_S): # Check key - if not (comm in self.serial_data): + if comm not in self.serial_data: lg.error('{} Data: Unknown comm {}'.format(self.dev_name, comm)) return None @@ -278,20 +284,19 @@ def get_queue_data(self, comm, comm_ext_id=0, timeout=GET_TIMEOUT_S): comm_ext = '{}_{}'.format(comm, comm_ext_id) # Queue exists? - if not (comm_ext in self.serial_data): + if comm_ext not in self.serial_data: self.serial_data[comm_ext] = self.queue_type() else: comm_ext = comm - + # Try reading from queue try: return self.serial_data[comm_ext].get(timeout=timeout) - + except queue.Empty: - lg.error('{} Data: Timeout retrieving {}' - .format(self.dev_name, comm_ext)) + lg.error('{} Data: Timeout retrieving {}'.format(self.dev_name, comm_ext)) return None - + def open_serial(self, port): # Set serial port @@ -304,15 +309,13 @@ def open_serial(self, port): # Get USB parameters self.dev_usb_port = port - self.dev_usb_name, self.dev_usb_vid, self.dev_usb_pid = \ - find_usb_info(port) + self.dev_usb_name, self.dev_serial_number, self.dev_usb_vid, self.dev_usb_pid = find_usb_info(port) return True - + except Exception as err: lg.error('{} Serial: Failed opening {}' - '\n {}{} - {}' - .format(self.dev_name, port, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, port, LOG_FILL, type(err).__name__, err)) return False @@ -320,12 +323,11 @@ def inwaiting_serial(self): # Read in_waiting try: return self.serial.in_waiting - + except Exception as err: lg.error('{} Serial: Failed reading in_waiting' - '\n {}{} - {}' - .format(self.dev_name, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, LOG_FILL, type(err).__name__, err)) # Close device self.close() return None @@ -337,12 +339,11 @@ def read_serial(self, n_bytes): with self.serial_read_lock: data = self.serial.read(n_bytes) return data - + except Exception as err: lg.error('{} Serial: Failed reading' - '\n {}{} - {}' - .format(self.dev_name, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, LOG_FILL, type(err).__name__, err)) # Close device self.close() return None @@ -352,18 +353,17 @@ def write_serial(self, comm_bytes): # Write serial try: with self.serial_write_lock: - self.serial.write(comm_bytes) + self.serial.write(comm_bytes) return True - + except Exception as err: lg.error('{} Serial: Failed writing' - '\n {}{} - {}' - .format(self.dev_name, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, LOG_FILL, type(err).__name__, err)) # Close device self.close() return False - + def loop_serial_listen(self): try: @@ -375,55 +375,60 @@ def loop_serial_listen(self): # Command available? if self.inwaiting_serial(): + + # Starts with $? if self.read_serial(1) == COMM_MSG_START: comm_msg = self.read_serial(COMM_MSG_LEN-1) comm_id = comm_msg[0] comm_data = comm_msg[1:] - # Debug - lg.debug('> COMM RCV {}'. - format([D_DEV_COMM_INV[comm_id], - *[c for c in comm_data]])) - - # Process Command - self.process_serial_comm(comm_id, comm_data) - - # The non-blocking method is prefered for finishing the thread + # Known command id? + if comm_id in D_DEV_COMM_INV: + # Debug + lg.debug('> COMM RCV {}'.format([D_DEV_COMM_INV[comm_id], *[c for c in comm_data]])) + + # Process Command + self.process_serial_comm(comm_id, comm_data) + + else: + lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) + + else: + lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) + + # The non-blocking method is prefered for finishing the thread # when closing the device else: - time.sleep(SERIAL_LISTEN_LOOP_DELAY_S) + time.sleep(SERIAL_LISTEN_LOOP_INTERVAL_S) except Exception as err: lg.error('{} Serial Listen Loop: Error' - '\n{}{} - {}' - .format(self.dev_name, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, LOG_FILL, type(err).__name__, err)) + # Close device self.close() - def process_serial_comm(self, comm_id, comm_data): + def process_serial_comm(self, comm_id, comm_data): # DEVICE_SERIAL_NUMBER - if comm_id == D_DEV_COMM['DEVICE_SERIAL_NUMBER']: + if comm_id == D_DEV_COMM['DEVICE_SERIAL_NUMBER']: sn_n_bytes = self.unpack_rava_msgdata(comm_data, 'B') sn = self.read_serial(sn_n_bytes).decode() self.put_queue_data('DEVICE_SERIAL_NUMBER', sn) - # DEVICE_TEMPERATURE - elif comm_id == D_DEV_COMM['DEVICE_TEMPERATURE']: + elif comm_id == D_DEV_COMM['DEVICE_TEMPERATURE']: # temperature - self.put_queue_data('DEVICE_TEMPERATURE', - self.unpack_rava_msgdata(comm_data, 'f')) + self.put_queue_data('DEVICE_TEMPERATURE', self.unpack_rava_msgdata(comm_data, 'f')) # DEVICE_FREE_RAM - elif comm_id == D_DEV_COMM['DEVICE_FREE_RAM']: + elif comm_id == D_DEV_COMM['DEVICE_FREE_RAM']: # free_ram - self.put_queue_data('DEVICE_FREE_RAM', - self.unpack_rava_msgdata(comm_data, 'H')) + self.put_queue_data('DEVICE_FREE_RAM', self.unpack_rava_msgdata(comm_data, 'H')) # DEVICE_DEBUG - elif comm_id == D_DEV_COMM['DEVICE_DEBUG']: + elif comm_id == D_DEV_COMM['DEVICE_DEBUG']: debug_bytes = self.unpack_rava_msgdata(comm_data, 'BBBBBB') debug_ints = [x for x in debug_bytes] lg.debug('> RAVA DEBUG MSG {} {} {} {} {} {}'.format(*debug_ints)) @@ -431,64 +436,53 @@ def process_serial_comm(self, comm_id, comm_data): # EEPROM_DEVICE elif comm_id == D_DEV_COMM['EEPROM_DEVICE']: # temp_calib_slope, temp_calib_intercept - self.put_queue_data('EEPROM_DEVICE', - self.unpack_rava_msgdata(comm_data, 'Hh')) + self.put_queue_data('EEPROM_DEVICE', self.unpack_rava_msgdata(comm_data, 'Hh')) # EEPROM_FIRMWARE elif comm_id == D_DEV_COMM['EEPROM_FIRMWARE']: - # version_hi, version_lo, parameters - self.put_queue_data('EEPROM_FIRMWARE', - self.unpack_rava_msgdata(comm_data, 'BBB')) + # version_major, version_minor, version_patch, modules + self.put_queue_data('EEPROM_FIRMWARE', self.unpack_rava_msgdata(comm_data, 'BBBB')) # EEPROM_PWM elif comm_id == D_DEV_COMM['EEPROM_PWM']: # freq_id, duty - self.put_queue_data('EEPROM_PWM', - self.unpack_rava_msgdata(comm_data, 'BB')) + self.put_queue_data('EEPROM_PWM', self.unpack_rava_msgdata(comm_data, 'BB')) # EEPROM_RNG elif comm_id == D_DEV_COMM['EEPROM_RNG']: # sampling_interval_us - self.put_queue_data('EEPROM_RNG', - self.unpack_rava_msgdata(comm_data, 'B')) + self.put_queue_data('EEPROM_RNG', self.unpack_rava_msgdata(comm_data, 'B')) # EEPROM_LED elif comm_id == D_DEV_COMM['EEPROM_LED']: # led_attached - self.put_queue_data('EEPROM_LED', - self.unpack_rava_msgdata(comm_data, 'B')) + self.put_queue_data('EEPROM_LED', self.unpack_rava_msgdata(comm_data, '?')) # EEPROM_LAMP elif comm_id == D_DEV_COMM['EEPROM_LAMP']: - exp_mag_smooth_n_trials, extra_n_bytes = \ - self.unpack_rava_msgdata(comm_data, 'BB') + exp_mag_smooth_n_trials, extra_n_bytes = self.unpack_rava_msgdata(comm_data, 'BB') extra_bytes = self.read_serial(extra_n_bytes) - exp_dur_max_ms, exp_z_significant = \ - struct.unpack(' INCAPT count={}, ovflw={}, s={:.3f}' - .format(input_capture_count, input_capture_1s_n, - input_capture_count*0.000016 + input_capture_1s_n)) + # input_capture_interval_s + self.put_queue_data('PERIPH_D2_TIMER3_INPUT_CAPTURE', self.unpack_rava_msgdata(comm_data, 'f')) + # Callback + try: + self.cbkfcn_d2_input_capture_available() + except: + pass # PERIPH_D5_ADC elif comm_id == D_DEV_COMM['PERIPH_D5_ADC']: # adc_read_mv - self.put_queue_data('PERIPH_D5_ADC', - self.unpack_rava_msgdata(comm_data, 'f')) - + self.put_queue_data('PERIPH_D5_ADC', self.unpack_rava_msgdata(comm_data, 'f')) + # INTERFACE_DS18B20 elif comm_id == D_DEV_COMM['INTERFACE_DS18B20']: # temperature - self.put_queue_data('INTERFACE_DS18B20', - self.unpack_rava_msgdata(comm_data, 'f')) + self.put_queue_data('INTERFACE_DS18B20', self.unpack_rava_msgdata(comm_data, 'f')) ##################### @@ -598,16 +598,14 @@ def pack_rava_msg(self, comm_str, data_user=[], data_user_fmt=''): # Transform comm and variables into a 8-byte RAVA message # Get comm id - if not(comm_str in D_DEV_COMM): - lg.error('{} MSG Pack: Unknown command {}' - .format(self.dev_name, comm_str)) + if comm_str not in D_DEV_COMM: + lg.error('{} MSG Pack: Unknown command {}'.format(self.dev_name, comm_str)) return None comm_id = D_DEV_COMM[comm_str] # Check if data and data_fmt have the same size if len(data_user) != len(data_user_fmt): - lg.error('{} MSG Pack: Data and data_fmt must have the same size' - .format(self.dev_name)) + lg.error('{} MSG Pack: Data and data_fmt must have the same size'.format(self.dev_name)) return None # Msg data contains (COMM_MSG_LEN - 2) bytes @@ -618,23 +616,22 @@ def pack_rava_msg(self, comm_str, data_user=[], data_user_fmt=''): lg.error('{} MSG Pack: Data contains {} bytes - the maximum is {}' .format(self.dev_name, data_usr_n_bytes, data_n_bytes)) return None - + # Fill remaining data bytes with 0 data_fmt = data_user_fmt + (data_n_bytes - data_usr_n_bytes) * 'B' data = data_user + (data_n_bytes - data_usr_n_bytes) * [0] # Pack rava msg return struct.pack(' COMM SND {}'.format(comm_dbg)) return write_success - - - def unpack_rava_msgdata(self, data, data_get_format): + + + def unpack_rava_msgdata(self, data, data_get_format): # Transform RAVA 6-byte message data into variables # Msg data contains (COMM_MSG_LEN - 2) bytes @@ -655,12 +652,11 @@ def unpack_rava_msgdata(self, data, data_get_format): lg.error('{} MSG Unpack: Data contains {} bytes - expected {}' .format(self.dev_name, len(data), data_n_bytes)) return None - - # Check data_get_fmt + + # Check data_get_fmt dataget_n_bytes = struct.calcsize('<' + data_get_format) if (dataget_n_bytes == 0) or (dataget_n_bytes > data_n_bytes): - lg.error('{} MSG Unpack: Data format asks for {} bytes - expected' - ' 0 < size <= {}' + lg.error('{} MSG Unpack: Data format asks for {} bytes - expected 0 < size <= {}' .format(self.dev_name, dataget_n_bytes, data_n_bytes)) return None @@ -674,7 +670,31 @@ def unpack_rava_msgdata(self, data, data_get_format): return vars[:vars_n][0] else: return vars[:vars_n] - + + + ##################### + ## CALLBACK REGISTER + + def cbkreg_device_close(self, fcn_device_close): + if callable(fcn_device_close): + self.cbkfcn_device_close = fcn_device_close + else: + lg.error('{} Callback: Provide fcn_device_close as a function'.format(self.dev_name)) + + + def cbkreg_stream_data_available(self, fcn_rng_stream_data_available): + if callable(fcn_rng_stream_data_available): + self.cbkfcn_rng_stream_data_available = fcn_rng_stream_data_available + else: + lg.error('{} Callback: Provide fcn_stream_data_available as a function'.format(self.dev_name)) + + + def cbkreg_d2_input_capture_available(self, fcn_d2_input_capture_available): + if callable(fcn_d2_input_capture_available): + self.cbkfcn_d2_input_capture_available = fcn_d2_input_capture_available + else: + lg.error('{} Callback: Provide fcn_input_capture_available as a function'.format(self.dev_name)) + ##################### ## DEVICE @@ -683,13 +703,13 @@ def get_device_serial_number(self, timeout=GET_TIMEOUT_S): comm = 'DEVICE_SERIAL_NUMBER' if self.snd_rava_msg(comm): return self.get_queue_data(comm, timeout=timeout) - + def get_device_temperature(self, timeout=GET_TIMEOUT_S): comm = 'DEVICE_TEMPERATURE' if self.snd_rava_msg(comm): return self.get_queue_data(comm, timeout=timeout) - + def get_device_free_ram(self, timeout=GET_TIMEOUT_S): comm = 'DEVICE_FREE_RAM' @@ -700,7 +720,7 @@ def get_device_free_ram(self, timeout=GET_TIMEOUT_S): def snd_device_reboot(self): comm = 'DEVICE_REBOOT' return self.snd_rava_msg(comm) - + ##################### ## EEPROM @@ -724,61 +744,56 @@ def get_eeprom_device(self, timeout=GET_TIMEOUT_S): data_vars = self.get_queue_data(comm, timeout=timeout) data_names = ['temp_calib_slope', 'temp_calib_intercept'] return dict(zip(data_names, data_vars)) - + def get_eeprom_firmware(self, timeout=GET_TIMEOUT_S): comm = 'EEPROM_FIRMWARE' if self.snd_rava_msg(comm): - version_hi, version_lo, modules = \ - self.get_queue_data(comm, timeout=timeout) - - version = float('{}.{}'.format(version_hi, version_lo)) + version_major, version_minor, version_patch, modules = self.get_queue_data(comm, timeout=timeout) + + version = '{}.{}.{}'.format(version_major, version_minor, version_patch) health_startup_enabled = modules & 1 << 0 != 0 health_continuous_enabled = modules & 1 << 1 != 0 led_enabled = modules & 1 << 2 != 0 lamp_enabled = modules & 1 << 3 != 0 peripherals_enabled = modules & 1 << 4 != 0 serial1_enabled = modules & 1 << 5 != 0 - return {'version':version, - 'health_startup_enabled':health_startup_enabled, + return {'version':version, + 'health_startup_enabled':health_startup_enabled, 'health_continuous_enabled':health_continuous_enabled, - 'led_enabled':led_enabled, + 'led_enabled':led_enabled, 'lamp_enabled':lamp_enabled, 'peripherals_enabled':peripherals_enabled, 'serial1_enabled':serial1_enabled } - - def snd_eeprom_pwm(self, freq_id, duty): - if not freq_id in D_PWM_FREQ_INV: - lg.error('{} EEPROM PWM: Unknown freq_id {}' - .format(self.dev_name, freq_id)) + + def snd_eeprom_pwm(self, freq_id, duty): + if freq_id not in D_PWM_FREQ_INV: + lg.error('{} EEPROM PWM: Unknown freq_id {}'.format(self.dev_name, freq_id)) return False if duty == 0: lg.error('{} EEPROM PWM: Provide a duty > 0'.format(self.dev_name)) return False - + comm = 'EEPROM_PWM' rava_send = False return self.snd_rava_msg(comm, [rava_send, freq_id, duty], 'BBB') - def get_eeprom_pwm(self, timeout=GET_TIMEOUT_S): + def get_eeprom_pwm(self, timeout=GET_TIMEOUT_S): comm = 'EEPROM_PWM' rava_send = True if self.snd_rava_msg(comm, [rava_send], 'B'): freq_id, duty = self.get_queue_data(comm, timeout=timeout) - return {'freq_id':freq_id, - 'freq_str':D_PWM_FREQ_INV[freq_id], - 'duty':duty} + return {'freq_id':freq_id, 'freq_str':D_PWM_FREQ_INV[freq_id], 'duty':duty} def snd_eeprom_rng(self, sampling_interval_us): if sampling_interval_us == 0: - lg.error('{} EEPROM RNG: Provide a sampling_interval_us > 0' - .format(self.dev_name)) + lg.error('{} EEPROM RNG: Provide a sampling_interval_us > 0'.format(self.dev_name)) return False - + comm = 'EEPROM_RNG' rava_send = False return self.snd_rava_msg(comm, [rava_send, sampling_interval_us], 'BB') @@ -795,7 +810,7 @@ def get_eeprom_rng(self, timeout=GET_TIMEOUT_S): def snd_eeprom_led(self, led_attached): comm = 'EEPROM_LED' rava_send = False - return self.snd_rava_msg(comm, [rava_send, led_attached], 'BB') + return self.snd_rava_msg(comm, [rava_send, bool(led_attached)], 'BB') def get_eeprom_led(self, timeout=GET_TIMEOUT_S): @@ -804,27 +819,22 @@ def get_eeprom_led(self, timeout=GET_TIMEOUT_S): if self.snd_rava_msg(comm, [rava_send], 'B'): led_attached = self.get_queue_data(comm, timeout=timeout) return {'led_attached':led_attached} - - def snd_eeprom_lamp(self, exp_dur_max_ms, exp_z_significant, - exp_mag_smooth_n_trials): + + def snd_eeprom_lamp(self, exp_dur_max_ms, exp_z_significant, exp_mag_smooth_n_trials): if exp_dur_max_ms < 60000: - lg.error('{} EEPROM LAMP: Provide an exp_dur_max_ms >= 60000' - .format(self.dev_name)) + lg.error('{} EEPROM LAMP: Provide an exp_dur_max_ms >= 60000'.format(self.dev_name)) return False if exp_z_significant < 1.0: - lg.error('{} EEPROM LAMP: Provide an exp_z_significant >= 1.0' - .format(self.dev_name)) + lg.error('{} EEPROM LAMP: Provide an exp_z_significant >= 1.0'.format(self.dev_name)) return False if exp_mag_smooth_n_trials == 0: - lg.error('{} EEPROM LAMP: Provide an exp_mag_smooth_n_trials > 0' - .format(self.dev_name)) + lg.error('{} EEPROM LAMP: Provide an exp_mag_smooth_n_trials > 0'.format(self.dev_name)) return False - + comm = 'EEPROM_LAMP' rava_send = False - if self.snd_rava_msg(comm, - [rava_send, exp_mag_smooth_n_trials, 8], 'BBB'): + if self.snd_rava_msg(comm, [rava_send, exp_mag_smooth_n_trials, 8], 'BBB'): comm_extra = struct.pack(' 0'.format(self.dev_name)) return False - + comm = 'PWM_SETUP' rava_send = False return self.snd_rava_msg(comm, [rava_send, freq_id, duty], 'BBB') @@ -863,9 +871,7 @@ def get_pwm_setup(self, timeout=GET_TIMEOUT_S): comm = 'PWM_SETUP' if self.snd_rava_msg(comm, [rava_send], 'B'): freq_id, duty = self.get_queue_data(comm, timeout=timeout) - return {'freq_id':freq_id, - 'freq_str':D_PWM_FREQ_INV[freq_id], - 'duty':duty} + return {'freq_id':freq_id, 'freq_str':D_PWM_FREQ_INV[freq_id], 'duty':duty} ##################### @@ -873,10 +879,9 @@ def get_pwm_setup(self, timeout=GET_TIMEOUT_S): def snd_rng_setup(self, sampling_interval_us): if sampling_interval_us == 0: - lg.error('{} RNG: Provide a sampling_interval_us > 0'. - format(self.dev_name)) + lg.error('{} RNG: Provide a sampling_interval_us > 0'.format(self.dev_name)) return False - + comm = 'RNG_SETUP' rava_send = False return self.snd_rava_msg(comm, [rava_send, sampling_interval_us], 'BB') @@ -888,7 +893,7 @@ def get_rng_setup(self, timeout=GET_TIMEOUT_S): if self.snd_rava_msg(comm, [rava_send], 'B'): sampling_interval_us = self.get_queue_data(comm, timeout=timeout) return {'sampling_interval_us':sampling_interval_us} - + def snd_rng_timing_debug_d1(self, on=True): comm = 'RNG_TIMING_DEBUG_D1' @@ -898,172 +903,116 @@ def snd_rng_timing_debug_d1(self, on=True): def get_rng_pulse_counts(self, n_counts, timeout=GET_TIMEOUT_S): comm = 'RNG_PULSE_COUNTS' if self.snd_rava_msg(comm, [n_counts], 'L'): - counts_bytes_a, counts_bytes_b = \ - self.get_queue_data(comm, timeout=timeout) + counts_bytes_a, counts_bytes_b = self.get_queue_data(comm, timeout=timeout) - counts_a = process_bytes(counts_bytes_a, - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']) - counts_b = process_bytes(counts_bytes_b, - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']) + counts_a = bytes_to_numlist(counts_bytes_a, 'B') + counts_b = bytes_to_numlist(counts_bytes_b, 'B') return counts_a, counts_b - def get_rng_bits(self, bit_type_id, timeout=GET_TIMEOUT_S): - if not bit_type_id in D_RNG_BIT_SRC_INV: - lg.error('{} RNG Bits: Unknown bit_type_id {}' - .format(self.dev_name, bit_type_id)) + def get_rng_bits(self, bit_source_id, timeout=GET_TIMEOUT_S): + if bit_source_id not in D_RNG_BIT_SRC_INV: + lg.error('{} RNG Bits: Unknown bit_source_id {}'.format(self.dev_name, bit_source_id)) return None comm = 'RNG_BITS' - if self.snd_rava_msg(comm, [bit_type_id], 'B'): - bit_type_id_recv, *bits = self.get_queue_data(comm, timeout=timeout) - - if bit_type_id == D_RNG_BIT_SRC['AB_RND']: - bit_type_str = D_RNG_BIT_SRC_INV[bit_type_id_recv] - return bit_type_str, bits[0] - elif bit_type_id == D_RNG_BIT_SRC['AB']: - return tuple(bits) + if self.snd_rava_msg(comm, [bit_source_id], 'B'): + bit_source_id_recv, *bits = self.get_queue_data(comm, timeout=timeout) + + if bit_source_id == D_RNG_BIT_SRC['AB_RND']: + bit_type_str = D_RNG_BIT_SRC_INV[bit_source_id_recv] + return [bit_type_str, bits[0]] + elif bit_source_id == D_RNG_BIT_SRC['AB']: + return list(bits) else: return bits[0] - def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], - request_id=0, out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY'], + def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id=0, list_output=True, timeout=GET_TIMEOUT_S): - if not postproc_id in D_RNG_POSTPROC_INV: - lg.error('{} RNG Bytes: Unknown postproc_id {}' - .format(self.dev_name, postproc_id)) + if postproc_id not in D_RNG_POSTPROC_INV: + lg.error('{} RNG Bytes: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) return None - + comm = 'RNG_BYTES' if self.snd_rava_msg(comm, [n_bytes, postproc_id, request_id], 'LBB'): - bytes_data = self.get_queue_data(comm, comm_ext_id=request_id, - timeout=timeout) - - if not (bytes_data is None): + bytes_data = self.get_queue_data(comm, comm_ext_id=request_id, timeout=timeout) + + if bytes_data is not None: rng_bytes_a, rng_bytes_b = bytes_data - rng_a = process_bytes(rng_bytes_a, out_type) - rng_b = process_bytes(rng_bytes_b, out_type) - return rng_a, rng_b + + if list_output: + rng_a = bytes_to_numlist(rng_bytes_a, 'B') + rng_b = bytes_to_numlist(rng_bytes_b, 'B') + return rng_a, rng_b + else: + return rng_bytes_a, rng_bytes_b + else: return None, None - def get_rng_int8s(self, n_ints, int_max, timeout=GET_TIMEOUT_S): + def get_rng_int8s(self, n_ints, int_delta, timeout=GET_TIMEOUT_S): if n_ints >= 2**32: - lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer' - .format(self.dev_name)) + lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer'.format(self.dev_name)) return None - if int_max >= 2**8: - lg.error('{} RNG Ints: Provide int_max as a 8-bit integer' - .format(self.dev_name)) + if int_delta >= 2**8: + lg.error('{} RNG Ints: Provide int_delta as a 8-bit integer'.format(self.dev_name)) + return None + if int_delta < 2: + lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) return None comm = 'RNG_INT8S' - if self.snd_rava_msg(comm, [n_ints, int_max], 'LB'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'LB'): ints_bytes = self.get_queue_data(comm, timeout=timeout) - ints_list = struct.unpack('<{}B'.format(n_ints), ints_bytes) - return np.array(ints_list, dtype=np.uint8) + return bytes_to_numlist(ints_bytes, 'B') - def get_rng_int16s(self, n_ints, int_max, timeout=GET_TIMEOUT_S): + def get_rng_int16s(self, n_ints, int_delta, timeout=GET_TIMEOUT_S): if n_ints >= 2**32: - lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer' - .format(self.dev_name)) + lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer'.format(self.dev_name)) + return None + if int_delta >= 2**16: + lg.error('{} RNG Ints: Provide int_delta as a 16-bit integer'.format(self.dev_name)) return None - if int_max >= 2**16: - lg.error('{} RNG Ints: Provide int_max as a 16-bit integer' - .format(self.dev_name)) + if int_delta < 2: + lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) return None comm = 'RNG_INT16S' - if self.snd_rava_msg(comm, [n_ints, int_max], 'LH'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'LH'): ints_bytes = self.get_queue_data(comm, timeout=timeout) - ints_list = struct.unpack('<{}H'.format(n_ints), ints_bytes) - return np.array(ints_list, dtype=np.uint16) + return bytes_to_numlist(ints_bytes, 'H') - def get_rng_floats(self, n_floats): - if n_floats >= (2**32) // 4: - lg.error('{} RNG Floats: Maximum n_floats is {}' - .format(self.dev_name, (2**32) // 4)) + def get_rng_floats(self, n_floats, timeout=GET_TIMEOUT_S): + if n_floats >= 2**32: + lg.error('{} RNG Floats: Provide n_floats as a 32-bit integer'.format(self.dev_name)) return None - # 32 bits floating point number - bytes_res = self.get_rng_bytes(n_bytes=n_floats*4, timeout=None) - if bytes_res is None: - return - rnd_bytes_a, rnd_bytes_b = bytes_res - - # XOR them - int_a = int.from_bytes(rnd_bytes_a, 'little') - int_b = int.from_bytes(rnd_bytes_b, 'little') - rnd_bytes = (int_a ^ int_b).to_bytes(len(rnd_bytes_a), 'little') - # Convert bytes to ints - rnd_lists = struct.unpack('<{}I'.format(n_floats), rnd_bytes) - rnd_ints = np.array(rnd_lists, dtype=np.uint32) - - # IEEE754 bit pattern for single precision floating point value in the - # range of 1.0 - 2.0. Uses the first 23 bytes and fixes the float - # exponent to 127 - rnd_ints_tmp = (rnd_ints & 0x007FFFFF) | 0x3F800000 - rnd_bytes_filtered = rnd_ints_tmp.tobytes() - rnd_lists_filtered = struct.unpack('<{}f'.format(n_floats), - rnd_bytes_filtered) - rnd_floats = np.array(rnd_lists_filtered, dtype=np.float32) - return rnd_floats - 1 - - - def get_rng_doubles(self, n_doubles): - if n_doubles >= (2**32) // 8: - lg.error('{} RNG Doubles: Maximum n_doubles is {}' - .format(self.dev_name, (2**32) // 8)) - return None + comm = 'RNG_FLOATS' + if self.snd_rava_msg(comm, [n_floats], 'L'): + ints_bytes = self.get_queue_data(comm, timeout=timeout) + return bytes_to_numlist(ints_bytes, 'f') + - # 64 bits floating point number - bytes_res = self.get_rng_bytes(n_bytes=n_doubles*8, timeout=None) - if bytes_res is None: - return - rnd_bytes_a, rnd_bytes_b = bytes_res - - # XOR them - int_a = int.from_bytes(rnd_bytes_a, 'little') - int_b = int.from_bytes(rnd_bytes_b, 'little') - rnd_bytes = (int_a ^ int_b).to_bytes(len(rnd_bytes_a), 'little') - # Convert bytes to ints - rnd_lists = struct.unpack('<{}Q'.format(n_doubles), rnd_bytes) - rnd_ints = np.array(rnd_lists, dtype=np.uint64) - - # IEEE754 bit pattern for single precision floating point value in the - # range of 1.0 - 2.0. Uses the first 52 bytes and fixes the float - # exponent to 1023 - rnd_ints_tmp = (rnd_ints & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000 - rnd_bytes_filtered = rnd_ints_tmp.tobytes() - rnd_lists_filtered = struct.unpack('<{}d'.format(n_doubles), - rnd_bytes_filtered) - rnd_doubles = np.array(rnd_lists_filtered, dtype=np.float64) - return rnd_doubles - 1 - - - def snd_rng_byte_stream_start(self, n_bytes, stream_delay_ms, + def snd_rng_byte_stream_start(self, n_bytes, stream_interval_ms, postproc_id=D_RNG_POSTPROC['NONE']): - if not postproc_id in D_RNG_POSTPROC_INV: - lg.error('{} RNG Stream: Unknown postproc_id {}' - .format(self.dev_name, postproc_id)) + if postproc_id not in D_RNG_POSTPROC_INV: + lg.error('{} RNG Stream: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) return None if n_bytes >= 2**16: - lg.error('{} RNG Stream: Provide n_bytes as a 16-bit integer' - .format(self.dev_name)) + lg.error('{} RNG Stream: Provide n_bytes as a 16-bit integer'.format(self.dev_name)) return None - if stream_delay_ms > RNG_BYTE_STREAM_MAX_DELAY_MS: - lg.error('{} RNG Stream: Provide a stream_delay_ms <= {}ms.' - .format(self.dev_name, RNG_BYTE_STREAM_MAX_DELAY_MS)) + if stream_interval_ms > RNG_BYTE_STREAM_MAX_INTERVAL_MS: + lg.error('{} RNG Stream: Provide a stream_interval_ms <= {}ms.' + .format(self.dev_name, RNG_BYTE_STREAM_MAX_INTERVAL_MS)) return None comm = 'RNG_STREAM_START' - msg_success = self.snd_rava_msg(comm, - [n_bytes, postproc_id, stream_delay_ms], 'HBH') - if msg_success: + msg_success = self.snd_rava_msg(comm, [n_bytes, postproc_id, stream_interval_ms], 'HBH') + if msg_success: self.rng_streaming = True return msg_success @@ -1073,36 +1022,39 @@ def snd_rng_byte_stream_stop(self): comm = 'RNG_STREAM_STOP' msg_success = self.snd_rava_msg(comm) - if msg_success: + if msg_success: self.rng_streaming = False - # Read remaining stream bytes + # Read remaining stream bytes if 'RNG_STREAM_BYTES' in self.serial_data: while not self.serial_data['RNG_STREAM_BYTES'].empty(): self.serial_data['RNG_STREAM_BYTES'].get_nowait() - + return msg_success - def get_rng_byte_stream_data(self, out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY'], - timeout=GET_TIMEOUT_S): + def get_rng_byte_stream_data(self, list_output=True, timeout=GET_TIMEOUT_S): if not self.rng_streaming: - lg.error('{} RNG Stream: Streaming is disabled'. - format(self.dev_name)) + lg.warning('{} RNG Stream: Streaming is disabled'.format(self.dev_name)) return None, None comm = 'RNG_STREAM_BYTES' - byte_data = self.get_queue_data(comm, timeout=timeout) - + bytes_data = self.get_queue_data(comm, timeout=timeout) + # Timeout? - if byte_data is None: + if bytes_data is not None: + rng_bytes_a, rng_bytes_b = bytes_data + + if list_output: + rng_a = bytes_to_numlist(rng_bytes_a, 'B') + rng_b = bytes_to_numlist(rng_bytes_b, 'B') + return rng_a, rng_b + else: + return rng_bytes_a, rng_bytes_b + + else: return None, None - - rng_bytes_a, rng_bytes_b = byte_data - rng_a = process_bytes(rng_bytes_a, out_type) - rng_b = process_bytes(rng_bytes_b, out_type) - return rng_a, rng_b - + def get_rng_byte_stream_status(self, timeout=GET_TIMEOUT_S): comm = 'RNG_STREAM_STATUS' @@ -1121,11 +1073,10 @@ def snd_health_startup_run(self): def get_health_startup_results(self, timeout=GET_TIMEOUT_S): comm = 'HEALTH_STARTUP_RESULTS' if self.snd_rava_msg(comm): - success, pc_avg_vars, bias_bit_vars, bias_byte_vars = \ - self.get_queue_data(comm, timeout=timeout) - data_vars = [pc_avg_vars, bias_bit_vars, bias_byte_vars] - data_names = ['pulse_count', 'bit_bias', 'byte_bias'] - return bool(success), dict(zip(data_names, data_vars)) + success, pc_avg_vars, pc_avg_diff_vars, bias_bit_vars, bias_byte_vars = self.get_queue_data(comm, timeout=timeout) + data_vars = [pc_avg_vars, pc_avg_diff_vars, bias_bit_vars, bias_byte_vars] + data_names = ['pulse_count', 'pulse_count_diff', 'bit_bias', 'byte_bias'] + return success, dict(zip(data_names, data_vars)) def get_health_continuous_errors(self, timeout=GET_TIMEOUT_S): @@ -1133,7 +1084,7 @@ def get_health_continuous_errors(self, timeout=GET_TIMEOUT_S): if self.snd_rava_msg(comm): data_vars = self.get_queue_data(comm, timeout=timeout) n_errors = sum(data_vars) - data_names = ['repetitive_count_a', 'repetitive_count_b', + data_names = ['repetitive_count_a', 'repetitive_count_b', 'adaptative_proportion_a', 'adaptative_proportion_b'] return n_errors, dict(zip(data_names, data_vars)) @@ -1143,70 +1094,63 @@ def get_health_continuous_errors(self, timeout=GET_TIMEOUT_S): def snd_periph_digi_mode(self, periph_id, mode_id): if periph_id == 0 or periph_id > 5: - lg.error('{} Periph: Provide a periph_id between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph: Provide a periph_id between 1 and 5'.format(self.dev_name)) return None - if not mode_id in D_PERIPH_MODES_INV: - lg.error('{} Periph: Unknown mode_id {}' - .format(self.dev_name, mode_id)) + if mode_id not in D_PERIPH_MODES_INV: + lg.error('{} Periph: Unknown mode_id {}'.format(self.dev_name, mode_id)) return None - + comm = 'PERIPH_MODE' return self.snd_rava_msg(comm, [periph_id, mode_id], 'BB') - + def get_periph_digi_state(self, periph_id=1, timeout=GET_TIMEOUT_S): if periph_id == 0 or periph_id > 5: - lg.error('{} Periph: Provide a periph_id between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph: Provide a periph_id between 1 and 5'.format(self.dev_name)) return None - + comm = 'PERIPH_READ' if self.snd_rava_msg(comm, [periph_id], 'B'): - return self.get_queue_data(comm, timeout=timeout) + digi_state, digi_mode = self.get_queue_data(comm, timeout=timeout) + return digi_state, D_PERIPH_MODES_INV[digi_mode] def snd_periph_digi_state(self, periph_id, digi_state): if periph_id == 0 or periph_id > 5: - lg.error('{} Periph: Provide a periph_id between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph: Provide a periph_id between 1 and 5'.format(self.dev_name)) return None - + comm = 'PERIPH_WRITE' - rava_send = False return self.snd_rava_msg(comm, [periph_id, digi_state], 'BB') - + def snd_periph_digi_pulse(self, periph_id=1, pulse_duration_us=100): if periph_id == 0 or periph_id > 5: - lg.error('{} Periph: Provide a periph_id between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph: Provide a periph_id between 1 and 5'.format(self.dev_name)) return None if pulse_duration_us == 0: - lg.error('{} Periph: Provide a pulse_duration_us > 0' - .format(self.dev_name)) + lg.error('{} Periph: Provide a pulse_duration_us > 0'.format(self.dev_name)) return None - + comm = 'PERIPH_PULSE' return self.snd_rava_msg(comm, [periph_id, pulse_duration_us], 'BH') - + def snd_periph_d1_trigger_input(self, on=True): comm = 'PERIPH_D1_TRIGGER_INPUT' return self.snd_rava_msg(comm, [on], 'B') - - def snd_periph_d1_comparator(self, neg_to_adc12=False, on=True): + + def snd_periph_d1_comparator(self, neg_to_d5=False, on=True): comm = 'PERIPH_D1_COMPARATOR' - return self.snd_rava_msg(comm, [on, neg_to_adc12], 'BB') - + return self.snd_rava_msg(comm, [on, neg_to_d5], 'BB') + def snd_periph_d1_delay_us_test(self, delay_us): if delay_us == 0: - lg.error('{} Periph: Provide a delay_us > 0' - .format(self.dev_name)) + lg.error('{} Periph: Provide a delay_us > 0'.format(self.dev_name)) return None - + comm = 'PERIPH_D1_DELAY_US_TEST' return self.snd_rava_msg(comm, [delay_us], 'B') @@ -1214,10 +1158,10 @@ def snd_periph_d1_delay_us_test(self, delay_us): def snd_periph_d2_input_capture(self, on=True): comm = 'PERIPH_D2_TIMER3_INPUT_CAPTURE' send_res = self.snd_rava_msg(comm, [on], 'B') - + # Read remaining stream bytes if not on: - time.sleep(.1) + time.sleep(.2) while not self.serial_data[comm].empty(): self.self.serial_data[comm].get_nowait() return send_res @@ -1226,31 +1170,27 @@ def snd_periph_d2_input_capture(self, on=True): def get_periph_d2_input_capture(self, timeout=GET_TIMEOUT_S): comm = 'PERIPH_D2_TIMER3_INPUT_CAPTURE' return self.get_queue_data(comm, timeout=timeout) - - def snd_periph_d3_timer3_trigger_output(self, delay_ms=1, on=True): - if delay_ms == 0: - lg.error('{} Periph: Provide a delay_ms > 0' - .format(self.dev_name)) + + def snd_periph_d3_timer3_trigger_output(self, interval_ms=1, on=True): + if interval_ms == 0 or interval_ms > RNG_BYTE_STREAM_MAX_INTERVAL_MS: + lg.error('{} Periph: Provide a {} > interval_ms > 0'.format(self.dev_name, RNG_BYTE_STREAM_MAX_INTERVAL_MS)) return None - + comm = 'PERIPH_D3_TIMER3_TRIGGER_OUTPUT' - return self.snd_rava_msg(comm, [on, delay_ms], 'BH') + return self.snd_rava_msg(comm, [on, interval_ms], 'BH') + - - def snd_periph_d3_timer3_pwm(self, freq_prescaler=1, top=2**8-1, duty=10, + def snd_periph_d3_timer3_pwm(self, freq_prescaler=1, top=2**8-1, duty=10, on=True): if freq_prescaler == 0 or freq_prescaler > 5: - lg.error('{} Periph D3: Provide a freq_prescaler between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph D3: Provide a freq_prescaler between 1 and 5'.format(self.dev_name)) return None if top == 0: - lg.error('{} Periph D3: Provide a top > 0' - .format(self.dev_name)) + lg.error('{} Periph D3: Provide a top > 0'.format(self.dev_name)) return None if duty == 0: - lg.error('{} Periph D3: Provide a duty > 0' - .format(self.dev_name)) + lg.error('{} Periph D3: Provide a duty > 0'.format(self.dev_name)) return None comm = 'PERIPH_D3_TIMER3_PWM' @@ -1262,22 +1202,16 @@ def snd_periph_d4_pin_change(self, on=True): return self.snd_rava_msg(comm, [on], 'B') - def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, - oversampling_n_bits=0, on=True, - timeout=GET_TIMEOUT_S): + def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, oversampling_n_bits=0, on=True, timeout=GET_TIMEOUT_S): if clk_prescaler == 0 or clk_prescaler > 7: - lg.error('{} Periph D5: Provide a clk_prescaler between 1 and 7' - .format(self.dev_name)) + lg.error('{} Periph D5: Provide a clk_prescaler between 1 and 7'.format(self.dev_name)) return None if oversampling_n_bits > 6: - lg.error('{} Periph D5: Provide a oversampling_n_bits <= 6' - .format(self.dev_name)) + lg.error('{} Periph D5: Provide a oversampling_n_bits <= 6'.format(self.dev_name)) return None - + comm = 'PERIPH_D5_ADC' - if self.snd_rava_msg(comm, - [on, ref_5v, clk_prescaler, oversampling_n_bits], - 'BBBB'): + if self.snd_rava_msg(comm, [on, ref_5v, clk_prescaler, oversampling_n_bits], 'BBBB'): if on: return self.get_queue_data(comm, timeout=timeout) diff --git a/src/rng_rava/rava_rng_aio.py b/src/rng_rava/rava_rng_aio.py index e47e250..b29585d 100644 --- a/src/rng_rava/rava_rng_aio.py +++ b/src/rng_rava/rava_rng_aio.py @@ -5,7 +5,7 @@ """ """ -The RAVA_RNG_AIO class offers the same functionality as RAVA_RNG but within an +The RAVA_RNG_AIO class offers the same functionality as RAVA_RNG but within an asynchronous framework. """ @@ -23,7 +23,7 @@ def __init__(self): # Variables self.serial_listen_task = None - + async def connect(self, serial_number): # Debug @@ -32,20 +32,16 @@ async def connect(self, serial_number): # Find serial port port = find_rava_port(serial_number) if port is None: - lg.error('{} Connect: No device found with SN {}' - .format(self.dev_name, serial_number)) + lg.error('{} Connect: No device found with SN {}'.format(self.dev_name, serial_number)) return False # Open serial connection if not self.open_serial(port): return False - - # Save SN info - self.dev_serial_number = serial_number # Reset serial data queues self.init_queue_data() - + # Start listening for serial commands self.serial_listen_task = asyncio.create_task(self.loop_serial_listen()) @@ -53,35 +49,32 @@ async def connect(self, serial_number): self.snd_rng_byte_stream_stop() # Request firmware info - firmw_info = await self.get_eeprom_firmware() - self.dev_firmware_version = firmw_info['version'] - self.dev_firmware_led = firmw_info['led_enabled'] + self.dev_firmware_dict = await self.get_eeprom_firmware() # Print connection info lg.info('{} Connect: Success' - '\n{}{}, Firmware v{:.2f}, SN={}, at {}' - .format(self.dev_name, LOG_FILL, self.dev_usb_name, - self.dev_firmware_version, self.dev_serial_number, - self.serial.port)) + '\n{} {}, Firmware v{}, SN={}, at {}' + .format(self.dev_name, LOG_FILL, + self.dev_usb_name, self.dev_firmware_dict['version'], self.dev_serial_number, self.serial.port)) # Request Health startup info - if firmw_info['health_startup_enabled']: + if self.dev_firmware_dict['health_startup_enabled']: test_success, test_vars = await self.get_health_startup_results() # Print test info print_health_startup_results(test_success, test_vars) # Error? Users have then a limited command variety (see Firmware) - if not test_success: - lg.warning('{} Connect: Startup tests failed' - .format(self.dev_name)) + if not test_success: + lg.error('{} Connect: Startup tests failed'.format(self.dev_name)) + return False return True - + def put_queue_data(self, comm, value, comm_ext_id=0): # Check key - if not (comm in self.serial_data): + if comm not in self.serial_data: lg.error('{} Data: Unknown comm {}'.format(self.dev_name, comm)) return False @@ -90,24 +83,24 @@ def put_queue_data(self, comm, value, comm_ext_id=0): comm_ext = '{}_{}'.format(comm, comm_ext_id) # Queue exists? - if not (comm_ext in self.serial_data): + if comm_ext not in self.serial_data: self.serial_data[comm_ext] = self.queue_type() else: comm_ext = comm - + # Try writing to queue try: self.serial_data[comm_ext].put_nowait(value) return True - + except asyncio.QueueFull: lg.error('{} Data: {} Queue full'.format(self.dev_name, comm_ext)) return False - + async def get_queue_data(self, comm, comm_ext_id=0): # Check key - if not (comm in self.serial_data): + if comm not in self.serial_data: lg.error('{} Data: Unknown comm {}'.format(self.dev_name, comm)) return None @@ -116,11 +109,11 @@ async def get_queue_data(self, comm, comm_ext_id=0): comm_ext = '{}_{}'.format(comm, comm_ext_id) # Queue exists? - if not (comm_ext in self.serial_data): + if comm_ext not in self.serial_data: self.serial_data[comm_ext] = self.queue_type() else: comm_ext = comm - + # Read asyncio queue return await self.serial_data[comm_ext].get() @@ -135,32 +128,40 @@ async def loop_serial_listen(self): # Command available? if self.inwaiting_serial(): + + # Starts with $? if self.read_serial(1) == COMM_MSG_START: comm_msg = self.read_serial(COMM_MSG_LEN-1) comm_id = comm_msg[0] comm_data = comm_msg[1:] - # Debug - lg.debug('> COMM RCV {}'. - format([D_DEV_COMM_INV[comm_id], - *[c for c in comm_data]])) - - # Process Command - self.process_serial_comm(comm_id, comm_data) - - # The non-blocking method is prefered for finishing the thread + # Known command id? + if comm_id in D_DEV_COMM_INV: + # Debug + lg.debug('> COMM RCV {}'.format([D_DEV_COMM_INV[comm_id], *[c for c in comm_data]])) + + # Process Command + self.process_serial_comm(comm_id, comm_data) + + else: + lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) + + else: + lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) + + # The non-blocking method is prefered for finishing the thread # when closing the device else: - await asyncio.sleep(SERIAL_LISTEN_LOOP_DELAY_S) + await asyncio.sleep(SERIAL_LISTEN_LOOP_INTERVAL_S) except Exception as err: lg.error('{} Serial Listen Loop: Error' - '\n{}{} - {}' - .format(self.dev_name, - LOG_FILL, type(err).__name__, err)) + '\n{} {} - {}' + .format(self.dev_name, LOG_FILL, type(err).__name__, err)) + # Close device self.close() - + ##################### ## DEVICE @@ -169,19 +170,20 @@ async def get_device_serial_number(self): comm = 'DEVICE_SERIAL_NUMBER' if self.snd_rava_msg(comm): return await self.get_queue_data(comm) - + async def get_device_temperature(self): comm = 'DEVICE_TEMPERATURE' if self.snd_rava_msg(comm): return await self.get_queue_data(comm) - + async def get_device_free_ram(self): comm = 'DEVICE_FREE_RAM' if self.snd_rava_msg(comm): return await self.get_queue_data(comm) - + + ##################### ## EEPROM @@ -198,34 +200,32 @@ async def get_eeprom_device(self): async def get_eeprom_firmware(self): comm = 'EEPROM_FIRMWARE' if self.snd_rava_msg(comm): - version_hi, version_lo, modules = await self.get_queue_data(comm) - - version = float('{}.{}'.format(version_hi, version_lo)) + version_major, version_minor, version_patch, modules = await self.get_queue_data(comm) + + version = '{}.{}.{}'.format(version_major, version_minor, version_patch) health_startup_enabled = modules & 1 << 0 != 0 health_continuous_enabled = modules & 1 << 1 != 0 led_enabled = modules & 1 << 2 != 0 lamp_enabled = modules & 1 << 3 != 0 peripherals_enabled = modules & 1 << 4 != 0 serial1_enabled = modules & 1 << 5 != 0 - return {'version':version, - 'health_startup_enabled':health_startup_enabled, + return {'version':version, + 'health_startup_enabled':health_startup_enabled, 'health_continuous_enabled':health_continuous_enabled, - 'led_enabled':led_enabled, + 'led_enabled':led_enabled, 'lamp_enabled':lamp_enabled, 'peripherals_enabled':peripherals_enabled, 'serial1_enabled':serial1_enabled } - - - async def get_eeprom_pwm(self): + + + async def get_eeprom_pwm(self): comm = 'EEPROM_PWM' rava_send = True if self.snd_rava_msg(comm, [rava_send], 'B'): freq_id, duty = await self.get_queue_data(comm) - return {'freq_id':freq_id, - 'freq_str':D_PWM_FREQ_INV[freq_id], - 'duty':duty} - + return {'freq_id':freq_id, 'freq_str':D_PWM_FREQ_INV[freq_id], 'duty':duty} + async def get_eeprom_rng(self): comm = 'EEPROM_RNG' @@ -233,7 +233,7 @@ async def get_eeprom_rng(self): if self.snd_rava_msg(comm, [rava_send], 'B'): sampling_interval_us = await self.get_queue_data(comm) return {'sampling_interval_us':sampling_interval_us} - + async def get_eeprom_led(self): comm = 'EEPROM_LED' @@ -248,10 +248,9 @@ async def get_eeprom_lamp(self): rava_send = True if self.snd_rava_msg(comm, [rava_send], 'B'): data_vars = await self.get_queue_data(comm) - data_names = ['exp_dur_max_ms', 'exp_z_significant', - 'exp_mag_smooth_n_trials'] + data_names = ['exp_dur_max_ms', 'exp_z_significant', 'exp_mag_smooth_n_trials'] return dict(zip(data_names, data_vars)) - + ##################### ## PWM @@ -261,11 +260,9 @@ async def get_pwm_setup(self): comm = 'PWM_SETUP' if self.snd_rava_msg(comm, [rava_send], 'B'): freq_id, duty = await self.get_queue_data(comm) - return {'freq_id':freq_id, - 'freq_str':D_PWM_FREQ_INV[freq_id], - 'duty':duty} + return {'freq_id':freq_id, 'freq_str':D_PWM_FREQ_INV[freq_id], 'duty':duty} + - ##################### ## RNG @@ -275,174 +272,126 @@ async def get_rng_setup(self): if self.snd_rava_msg(comm, [rava_send], 'B'): sampling_interval_us = await self.get_queue_data(comm) return {'sampling_interval_us':sampling_interval_us} - + async def get_rng_pulse_counts(self, n_counts): comm = 'RNG_PULSE_COUNTS' if self.snd_rava_msg(comm, [n_counts], 'L'): counts_bytes_a, counts_bytes_b = await self.get_queue_data(comm) - counts_a = process_bytes(counts_bytes_a, - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']) - counts_b = process_bytes(counts_bytes_b, - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']) + counts_a = bytes_to_numlist(counts_bytes_a, 'B') + counts_b = bytes_to_numlist(counts_bytes_b, 'B') return counts_a, counts_b - async def get_rng_bits(self, bit_type_id): - if not bit_type_id in D_RNG_BIT_SRC_INV: - lg.error('{} RNG Bits: Unknown bit_type_id {}' - .format(self.dev_name, bit_type_id)) + async def get_rng_bits(self, bit_source_id): + if bit_source_id not in D_RNG_BIT_SRC_INV: + lg.error('{} RNG Bits: Unknown bit_source_id {}'.format(self.dev_name, bit_source_id)) return None comm = 'RNG_BITS' - if self.snd_rava_msg(comm, [bit_type_id], 'B'): - bit_type_id_recv, *bits = await self.get_queue_data(comm) - - if bit_type_id == D_RNG_BIT_SRC['AB_RND']: - bit_type_str = D_RNG_BIT_SRC_INV[bit_type_id_recv] - return bit_type_str, bits[0] - elif bit_type_id == D_RNG_BIT_SRC['AB']: - return tuple(bits) + if self.snd_rava_msg(comm, [bit_source_id], 'B'): + bit_source_id_recv, *bits = await self.get_queue_data(comm) + + if bit_source_id == D_RNG_BIT_SRC['AB_RND']: + bit_type_str = D_RNG_BIT_SRC_INV[bit_source_id_recv] + return [bit_type_str, bits[0]] + elif bit_source_id == D_RNG_BIT_SRC['AB']: + return list(bits) else: return bits[0] - - async def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']): - if not postproc_id in D_RNG_POSTPROC_INV: - lg.error('{} RNG Bytes: Unknown postproc_id {}' - .format(self.dev_name, postproc_id)) + + async def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id=0, list_output=True): + if postproc_id not in D_RNG_POSTPROC_INV: + lg.error('{} RNG Bytes: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) return None - + comm = 'RNG_BYTES' - if self.snd_rava_msg(comm, [n_bytes, postproc_id], 'LB'): - bytes_data = await self.get_queue_data(comm) + if self.snd_rava_msg(comm, [n_bytes, postproc_id, request_id], 'LBB'): + bytes_data = await self.get_queue_data(comm, comm_ext_id=request_id) - if not (bytes_data is None): + if bytes_data is not None: rng_bytes_a, rng_bytes_b = bytes_data - rng_a = process_bytes(rng_bytes_a, out_type) - rng_b = process_bytes(rng_bytes_b, out_type) - return rng_a, rng_b + + if list_output: + rng_a = bytes_to_numlist(rng_bytes_a, 'B') + rng_b = bytes_to_numlist(rng_bytes_b, 'B') + return rng_a, rng_b + else: + return rng_bytes_a, rng_bytes_b + else: return None, None - async def get_rng_int8s(self, n_ints, int_max): + async def get_rng_int8s(self, n_ints, int_delta): if n_ints >= 2**32: - lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer' - .format(self.dev_name)) + lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer'.format(self.dev_name)) + return None + if int_delta >= 2**8: + lg.error('{} RNG Ints: Provide int_delta as a 8-bit integer'.format(self.dev_name)) return None - if int_max >= 2**8: - lg.error('{} RNG Ints: Provide int_max as a 8-bit integer' - .format(self.dev_name)) + if int_delta < 2: + lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) return None - + comm = 'RNG_INT8S' - if self.snd_rava_msg(comm, [n_ints, int_max], 'LB'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'LB'): ints_bytes = await self.get_queue_data(comm) - ints_list = struct.unpack('<{}B'.format(n_ints), ints_bytes) - return np.array(ints_list, dtype=np.uint8) + return bytes_to_numlist(ints_bytes, 'B') - async def get_rng_int16s(self, n_ints, int_max): + async def get_rng_int16s(self, n_ints, int_delta): if n_ints >= 2**32: - lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer' - .format(self.dev_name)) + lg.error('{} RNG Ints: Provide n_ints as a 32-bit integer'.format(self.dev_name)) + return None + if int_delta >= 2**16: + lg.error('{} RNG Ints: Provide int_delta as a 16-bit integer'.format(self.dev_name)) return None - if int_max >= 2**16: - lg.error('{} RNG Ints: Provide int_max as a 16-bit integer' - .format(self.dev_name)) + if int_delta < 2: + lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) return None - + comm = 'RNG_INT16S' - if self.snd_rava_msg(comm, [n_ints, int_max], 'LH'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'LH'): ints_bytes = await self.get_queue_data(comm) - ints_list = struct.unpack('<{}H'.format(n_ints), ints_bytes) - return np.array(ints_list, dtype=np.uint16) + return bytes_to_numlist(ints_bytes, 'H') async def get_rng_floats(self, n_floats): - if n_floats >= (2**32) // 4: - lg.error('{} RNG Floats: Maximum n_floats is {}' - .format(self.dev_name, (2**32) // 4)) + if n_floats >= 2**32: + lg.error('{} RNG Floats: Provide n_floats as a 32-bit integer'.format(self.dev_name)) return None - - # 32 bits floating point number - bytes_res = await self.get_rng_bytes(n_bytes=n_floats*4) - if bytes_res is None: - return - rnd_bytes_a, rnd_bytes_b = bytes_res - - # XOR them - int_a = int.from_bytes(rnd_bytes_a, 'little') - int_b = int.from_bytes(rnd_bytes_b, 'little') - rnd_bytes = (int_a ^ int_b).to_bytes(len(rnd_bytes_a), 'little') - # Convert bytes to ints - rnd_lists = struct.unpack('<{}I'.format(n_floats), rnd_bytes) - rnd_ints = np.array(rnd_lists, dtype=np.uint32) - - # IEEE754 bit pattern for single precision floating point value in the - # range of 1.0 - 2.0. Uses the first 23 bytes and fixes the float - # exponent to 127 - rnd_ints_tmp = (rnd_ints & 0x007FFFFF) | 0x3F800000 - rnd_bytes_filtered = rnd_ints_tmp.tobytes() - rnd_lists_filtered = struct.unpack('<{}f'.format(n_floats), - rnd_bytes_filtered) - rnd_floats = np.array(rnd_lists_filtered, dtype=np.float32) - return rnd_floats - 1 - - - async def get_rng_doubles(self, n_doubles): - if n_doubles >= (2**32) // 8: - lg.error('{} RNG Doubles: Maximum n_doubles is {}' - .format(self.dev_name, (2**32) // 8)) - return None - - # 64 bits floating point number - bytes_res = await self.get_rng_bytes(n_bytes=n_doubles*8) - if bytes_res is None: - return - rnd_bytes_a, rnd_bytes_b = bytes_res - - # XOR them - int_a = int.from_bytes(rnd_bytes_a, 'little') - int_b = int.from_bytes(rnd_bytes_b, 'little') - rnd_bytes = (int_a ^ int_b).to_bytes(len(rnd_bytes_a), 'little') - # Convert bytes to ints - rnd_lists = struct.unpack('<{}Q'.format(n_doubles), rnd_bytes) - rnd_ints = np.array(rnd_lists, dtype=np.uint64) - - # IEEE754 bit pattern for single precision floating point value in the - # range of 1.0 - 2.0. Uses the first 52 bytes and fixes the float - # exponent to 1023 - rnd_ints_tmp = (rnd_ints & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000 - rnd_bytes_filtered = rnd_ints_tmp.tobytes() - rnd_lists_filtered = struct.unpack('<{}d'.format(n_doubles), - rnd_bytes_filtered) - rnd_doubles = np.array(rnd_lists_filtered, dtype=np.float64) - return rnd_doubles - 1 - - - async def get_rng_byte_stream_data(self, - out_type=D_RNG_BYTE_OUT['NUMPY_ARRAY']): + + comm = 'RNG_FLOATS' + if self.snd_rava_msg(comm, [n_floats], 'L'): + ints_bytes = await self.get_queue_data(comm) + return bytes_to_numlist(ints_bytes, 'f') + + + async def get_rng_byte_stream_data(self, list_output=True): if not self.rng_streaming: - lg.error('{} RNG Stream: Streaming is disabled'. - format(self.dev_name)) + lg.warning('{} RNG Stream: Streaming is disabled'.format(self.dev_name)) return None, None comm = 'RNG_STREAM_BYTES' - byte_data = await self.get_queue_data(comm) - + bytes_data = await self.get_queue_data(comm) + # Timeout? - if byte_data is None: + if bytes_data is not None: + rng_bytes_a, rng_bytes_b = bytes_data + + if list_output: + rng_a = bytes_to_numlist(rng_bytes_a, 'B') + rng_b = bytes_to_numlist(rng_bytes_b, 'B') + return rng_a, rng_b + else: + return rng_bytes_a, rng_bytes_b + + else: return None, None - - rng_bytes_a, rng_bytes_b = byte_data - rng_a = process_bytes(rng_bytes_a, out_type) - rng_b = process_bytes(rng_bytes_b, out_type) - return rng_a, rng_b - + async def get_rng_byte_stream_status(self): comm = 'RNG_STREAM_STATUS' @@ -456,60 +405,54 @@ async def get_rng_byte_stream_status(self): async def get_health_startup_results(self): comm = 'HEALTH_STARTUP_RESULTS' if self.snd_rava_msg(comm): - success, pc_avg_vars, bias_bit_vars, bias_byte_vars = \ - await self.get_queue_data(comm) - data_vars = [pc_avg_vars, bias_bit_vars, bias_byte_vars] - data_names = ['pulse_count', 'bit_bias', 'byte_bias'] - return bool(success), dict(zip(data_names, data_vars)) - + success, pc_avg_vars, pc_avg_diff_vars, bias_bit_vars, bias_byte_vars = await self.get_queue_data(comm) + data_vars = [pc_avg_vars, pc_avg_diff_vars, bias_bit_vars, bias_byte_vars] + data_names = ['pulse_count', 'pulse_count_diff', 'bit_bias', 'byte_bias'] + return success, dict(zip(data_names, data_vars)) + async def get_health_continuous_errors(self): comm = 'HEALTH_CONTINUOUS_ERRORS' if self.snd_rava_msg(comm): data_vars = await self.get_queue_data(comm) n_errors = sum(data_vars) - data_names = ['repetitive_count_a', 'repetitive_count_b', + data_names = ['repetitive_count_a', 'repetitive_count_b', 'adaptative_proportion_a', 'adaptative_proportion_b'] return n_errors, dict(zip(data_names, data_vars)) - + ##################### ## PERIPHERALS async def get_periph_digi_state(self, periph_id=1): if periph_id == 0 or periph_id > 5: - lg.error('{} Periph: Provide a periph_id between 1 and 5' - .format(self.dev_name)) + lg.error('{} Periph: Provide a periph_id between 1 and 5'.format(self.dev_name)) return None - + comm = 'PERIPH_READ' if self.snd_rava_msg(comm, [periph_id], 'B'): - return await self.get_queue_data(comm) - - + digi_state, digi_mode = await self.get_queue_data(comm) + return digi_state, D_PERIPH_MODES_INV[digi_mode] + + async def get_periph_d2_input_capture(self): comm = 'PERIPH_D2_TIMER3_INPUT_CAPTURE' return await self.get_queue_data(comm) - - async def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, - oversampling_n_bits=0, on=True): + + async def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, oversampling_n_bits=0, on=True): if clk_prescaler == 0 or clk_prescaler > 7: - lg.error('{} Periph D5: Provide a clk_prescaler between 1 and 7' - .format(self.dev_name)) + lg.error('{} Periph D5: Provide a clk_prescaler between 1 and 7'.format(self.dev_name)) return None if oversampling_n_bits > 6: - lg.error('{} Periph D5: Provide a oversampling_n_bits <= 6' - .format(self.dev_name)) + lg.error('{} Periph D5: Provide a oversampling_n_bits <= 6'.format(self.dev_name)) return None - + comm = 'PERIPH_D5_ADC' - if self.snd_rava_msg(comm, - [on, ref_5v, clk_prescaler, oversampling_n_bits], - 'BBBB'): + if self.snd_rava_msg(comm, [on, ref_5v, clk_prescaler, oversampling_n_bits], 'BBBB'): if on: return await self.get_queue_data(comm) - + ##################### ## INTERFACES diff --git a/src/rng_rava/rava_rng_led.py b/src/rng_rava/rava_rng_led.py index f6f5872..8b77eef 100644 --- a/src/rng_rava/rava_rng_led.py +++ b/src/rng_rava/rava_rng_led.py @@ -5,12 +5,12 @@ """ """ -The RAVA_RNG_LED class implements the code for controlling the LED and LAMP -modules within the RAVA circuit. It allows users to adjust the color and the -intensity of the attached LED. Users can also activate the LAMP mode and +The RAVA_RNG_LED class implements the code for controlling the LED and LAMP +modules within the RAVA circuit. It allows users to adjust the color and the +intensity of the attached LED. Users can also activate the LAMP mode and retrieve statistical information on its operation. -For more information on how LED and LAMP modules operate, refer to the firmware +For more information on how LED and LAMP modules operate, refer to the firmware documentation. """ @@ -23,24 +23,25 @@ class RAVA_RNG_LED(RAVA_RNG): def __init__(self): super().__init__(dev_name='RAVA_RNG_LED') + self.cbkfcn_lamp_debug = None + def connect(self, serial_number): if not super().connect(serial_number=serial_number): return False # LED firmware enabled? - if not self.dev_firmware_led: - lg.warning('{} Connect: LED code is disabled in the firmware' - .format(self.dev_name)) + if not self.dev_firmware_dict['led_enabled']: + lg.warning('{} Connect: LED code is disabled in the firmware'.format(self.dev_name)) # LED attached? if not self.get_led_attached(): lg.warning('{} Connect: Firmware claims no LED is attached.' - '\n{} If false, fix it with snd_eeprom_led(led_attached=True)' - .format(self.dev_name, LOG_FILL)) - + '\n{} If false, fix it with snd_eeprom_led(led_attached=True)' + .format(self.dev_name, LOG_FILL)) + return True - + def process_serial_comm(self, comm_id, comm_data): super().process_serial_comm(comm_id=comm_id, comm_data=comm_data) @@ -48,96 +49,98 @@ def process_serial_comm(self, comm_id, comm_data): # LED_STATUS if comm_id == D_DEV_COMM['LED_STATUS']: # color, intensity, color_fading, intensity_fading - self.put_queue_data('LED_STATUS', - self.unpack_rava_msgdata(comm_data, 'BBBB')) - + self.put_queue_data('LED_STATUS', self.unpack_rava_msgdata(comm_data, 'BBBB')) + # LAMP_DEBUG - elif comm_id == D_DEV_COMM['LAMP_DEBUG']: - z, mag, mag_avg = self.unpack_rava_msgdata(comm_data, 'fBB') - lg.debug('> LAMP DBG: z={:.2f}, mag={}, mag_avg={}' - .format(z, mag, mag_avg)) + elif comm_id == D_DEV_COMM['LAMP_DEBUG']: + # z, mag, mag_avg + self.put_queue_data('LAMP_DEBUG', self.unpack_rava_msgdata(comm_data, 'fBB')) + # Callback + try: + self.cbkfcn_lamp_debug() + except: + pass # LAMP_STATISTICS elif comm_id == D_DEV_COMM['LAMP_STATISTICS']: - exp_n, exp_n_zsig, n_extra_bytes = \ - self.unpack_rava_msgdata(comm_data, 'HHB') + exp_n, exp_n_zsig, n_extra_bytes = self.unpack_rava_msgdata(comm_data, 'HHB') exp_colors_bytes = self.read_serial(n_extra_bytes) exp_colors = struct.unpack('= 2**8: - lg.error('{} LED Color: Provide color_hue as a 8-bit integer' - .format(self.dev_name)) + lg.error('{} LED Color: Provide color_hue as a 8-bit integer'.format(self.dev_name)) return None if intensity >= 2**8: - lg.error('{} LED Color: Provide intensity as a 8-bit integer' - .format(self.dev_name)) + lg.error('{} LED Color: Provide intensity as a 8-bit integer'.format(self.dev_name)) return None - + comm = 'LED_COLOR' return self.snd_rava_msg(comm, [color_hue, intensity], 'BB') def snd_led_color_fade(self, color_hue_tgt, duration_ms): if color_hue_tgt >= 2**8: - lg.error('{} LED Color: Provide color_hue_tgt as a 8-bit ' - 'integer'.format(self.dev_name)) + lg.error('{} LED Color: Provide color_hue_tgt as a 8-bit integer'.format(self.dev_name)) return None if duration_ms >= 2**16: - lg.error('{} LED Color: Provide duration_ms as a 16-bit integer' - .format(self.dev_name)) + lg.error('{} LED Color: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None - + comm = 'LED_COLOR_FADE' return self.snd_rava_msg(comm, [color_hue_tgt, duration_ms], 'BH') def snd_led_color_oscillate(self, n_cycles, duration_ms): if n_cycles >= 2**8: - lg.error('{} LED Color: Provide n_cycles as a 8-bit integer' - .format(self.dev_name)) + lg.error('{} LED Color: Provide n_cycles as a 8-bit integer'.format(self.dev_name)) return None if duration_ms >= 2**16: - lg.error('{} LED Color: Provide duration_ms as a 16-bit integer' - .format(self.dev_name)) + lg.error('{} LED Color: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None - + comm = 'LED_COLOR_OSCILLATE' return self.snd_rava_msg(comm, [n_cycles, duration_ms], 'BH') def snd_led_intensity(self, intensity): if intensity >= 2**8: - lg.error('{} LED Intensity: Provide intensity as a 8-bit ' - 'integer'.format(self.dev_name)) + lg.error('{} LED Intensity: Provide intensity as a 8-bit integer'.format(self.dev_name)) return None - + comm = 'LED_INTENSITY' return self.snd_rava_msg(comm, [intensity], 'B') def snd_led_intensity_fade(self, intensity_tgt, duration_ms): if intensity_tgt >= 2**8: - lg.error('{} LED Intensity: Provide intensity_tgt as a 8-bit ' - 'integer'.format(self.dev_name)) + lg.error('{} LED Intensity: Provide intensity_tgt as a 8-bit integer'.format(self.dev_name)) return None if duration_ms >= 2**16: - lg.error('{} LED Intensity: Provide duration_ms as a 16-bit ' - 'integer'.format(self.dev_name)) + lg.error('{} LED Intensity: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None - + comm = 'LED_INTENSITY_FADE' - return self.snd_rava_msg(comm, [intensity_tgt, duration_ms], 'BH') + return self.snd_rava_msg(comm, [intensity_tgt, duration_ms], 'BH') def snd_led_fade_stop(self): @@ -148,18 +151,14 @@ def snd_led_fade_stop(self): def get_led_status(self, timeout=GET_TIMEOUT_S): comm = 'LED_STATUS' if self.snd_rava_msg(comm): - color_hue, intensity, color_fading, intensity_fading = \ - self.get_queue_data(comm, timeout=timeout) + color_hue, intensity, color_fading, intensity_fading = self.get_queue_data(comm, timeout=timeout) if color_hue in D_LED_COLOR_INV: color_str = D_LED_COLOR_INV[color_hue] else: color_str = '' - return {'color_hue':color_hue, - 'color':color_str, - 'intensity':intensity, - 'color_fading':color_fading, - 'intensity_fading':intensity_fading} - + return {'color_hue':color_hue, 'color':color_str, 'intensity':intensity, + 'color_fading':color_fading, 'intensity_fading':intensity_fading} + ##################### ## LAMP @@ -170,17 +169,27 @@ def snd_lamp_mode(self, on=True): def get_lamp_statistics(self, timeout=GET_TIMEOUT_S): - # Every get command resets the statistics values + # Every get command resets the statistics values comm = 'LAMP_STATISTICS' if self.snd_rava_msg(comm): data_vars = self.get_queue_data(comm, timeout=timeout) - data_names = ['exp_n', 'exp_n_zsig', - 'red', 'orange', 'yellow', 'green', - 'cyan', 'blue', 'purple', 'pink'] + data_names = ['exp_n', 'exp_n_zsig', + 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'pink'] return dict(zip(data_names, data_vars)) - - + + def snd_lamp_debug(self, on=True): - # Set lg.setLevel(10), and then run snd_lamp_mode(True) comm = 'LAMP_DEBUG' - return self.snd_rava_msg(comm, [on], 'B') \ No newline at end of file + return self.snd_rava_msg(comm, [on], 'B') + + + def get_lamp_debug(self, timeout=GET_TIMEOUT_S): + comm = 'LAMP_DEBUG' + data_vars = self.get_queue_data(comm, timeout=timeout) + + # Timeout? + if data_vars is None: + return None + + data_names = ['z', 'mag', 'mag_avg'] + return dict(zip(data_names, data_vars)) \ No newline at end of file