diff --git a/CHANGELOG.md b/CHANGELOG.md index 448ee60..1db95bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ ## 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 +* Including hardware float generation + + +## v1.0.2 +* Checking for n > 0 in data generation +* Max n of pulse counts, bytes, ints, and floats changed to 2^16 (instead of 2^32) +* Improved the disconnection detection methodology +* Corrected the int_delta in integers generation \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index bfa26bb..4109bb9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ +include CHANGELOG.md include examples/*.py \ No newline at end of file diff --git a/README.md b/README.md index fa7d2b0..48b4835 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ The [RAVA Python Driver](https://github.com/gabrielguerrer/rng_rava_driver_py) implements the code for communicating with an [RAVA Device](https://github.com/gabrielguerrer/rng_rava) running the [RAVA Firmware](https://github.com/gabrielguerrer/rng_rava_firmware). -The computer running the driver assumes the role of the leader device, sending -command requests and reading the data replies. + +The firmware establishes a USB CDC communication protocol with the computer +host. Both Linux and Windows are equipped with generic drivers for CDC, +eliminating the need for any specific driver software. This package implements +the serial commands used to control the RAVA device. The RAVA_RNG class enables the request of pulse counts, random bits, random bytes, and random numbers (integers and floats). Additionally, it establishes @@ -33,8 +36,8 @@ The driver code is available as the pip install rng_rava ``` -Requirements: - * [pyserial](https://github.com/pyserial/pyserial) +Requirements: [pyserial](https://github.com/pyserial/pyserial) + ## Usage @@ -73,7 +76,7 @@ pc_a, pc_b = rng.get_rng_pulse_counts(n_counts=100) 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 +# Output as list, instead of bytestring bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=100, postproc_id=rava.D_RNG_POSTPROC['NONE'], list_output=True) @@ -88,11 +91,18 @@ ints16 = rng.get_rng_int16s(n_ints=100, int_delta=10000) floats = rng.get_rng_floats(n_floats=100) ``` +## Firmware Compatibility + +Regarding the [RAVA Firmware](https://github.com/gabrielguerrer/rng_rava_firmware): +* Firmware v1.0 is compatible with driver versions from v1.0.0 to latest + + ## Associated projects - [RAVA Device](https://github.com/gabrielguerrer/rng_rava) - [RAVA Firmware](https://github.com/gabrielguerrer/rng_rava_firmware) -- RAVA Python Diagnostics Tool +- [RAVA Python Diagnostics](https://github.com/gabrielguerrer/rng_rava_diagnostics_py) + ## Contact diff --git a/examples/led_test.py b/examples/led_test.py index 180d07e..4ce97dd 100644 --- a/examples/led_test.py +++ b/examples/led_test.py @@ -18,17 +18,21 @@ exit() # Set color as blue (intensity=0) +print('> Blue color') rngled.snd_led_color(color_hue=rava.D_LED_COLOR['BLUE'], intensity=0) # Fade to full intensity +print('> Fade to full intensity') rngled.snd_led_intensity_fade(intensity_tgt=255, duration_ms=2000) time.sleep(3) -# Oscilate colors +# Oscillate colors +print('> Oscillate colors') rngled.snd_led_color_oscillate(n_cycles=3, duration_ms=4000) time.sleep(5) # Oscilate intensities +print('> Oscillate intensity') for i in range(2): rngled.snd_led_intensity_fade(intensity_tgt=10, duration_ms=2000) time.sleep(2) @@ -37,10 +41,12 @@ time.sleep(1) # Fade color to red +print('> Fade color to red') rngled.snd_led_color_fade(color_hue_tgt=rava.D_LED_COLOR['RED'], duration_ms=2000) time.sleep(3) # Turn off lights +print('> Lights off') rngled.snd_led_intensity(intensity=0) # Close device diff --git a/examples/rava_interactive.py b/examples/rava_interactive.py index a6640f3..6d8be3a 100644 --- a/examples/rava_interactive.py +++ b/examples/rava_interactive.py @@ -12,8 +12,8 @@ import rng_rava as rava # Set logging level -rava.lg.setLevel(10) # DEBUG -# rava.lg.setLevel(20) # INFO +# rava.lg.setLevel(10) # DEBUG +rava.lg.setLevel(20) # INFO # Find RAVA device and connect rava_sns = rava.find_rava_sns() @@ -90,7 +90,7 @@ 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=15) + rng.get_rng_pulse_counts(n_counts=15, list_output=True) 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']) @@ -244,7 +244,7 @@ def LAMP(): rng.snd_lamp_mode(on=False) rng.snd_lamp_debug(on=True) - rng.get_lamp_debug() + rng.get_lamp_debug_data() rng.snd_lamp_debug(on=False) rng.get_lamp_statistics() \ No newline at end of file diff --git a/examples/rng_bytes_acquisition.py b/examples/rng_bytes_acquisition.py new file mode 100644 index 0000000..1e83a64 --- /dev/null +++ b/examples/rng_bytes_acquisition.py @@ -0,0 +1,67 @@ +''' +This example showcases the generation of a binary file containing random bytes. + +This example code is in the public domain. +Author: Gabriel Guerrer +''' + +import itertools +import rng_rava as rava + +# Variables +FILE_OUTPUT = 'random.bin' +N_BYTES = 1000000 # 1MB +N_CHUNK = 10000 + +# 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() + +# Calculate n measurements +n_measurements = N_BYTES // N_CHUNK +n_bytes_remmaining = (N_BYTES % N_CHUNK) + +# Open file +with open(FILE_OUTPUT, mode='bw') as f: + + # Loop over n measurements + for i in range(n_measurements): + print('{:.0f}%'.format(i / n_measurements * 100)) + + # Generate bytes + bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=N_CHUNK // 2, + postproc_id=rava.D_RNG_POSTPROC['NONE'], + list_output=False, + timeout=None) + + # Alternate RNG A and B bytes + bytes_ab = bytes(itertools.chain.from_iterable(zip(bytes_a, bytes_b))) + + # Write to file + f.write(bytes_ab) + + # Remaining bytes + if n_bytes_remmaining: + + # Generate bytes + bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=n_bytes_remmaining // 2, + postproc_id=rava.D_RNG_POSTPROC['NONE'], + list_output=False, + timeout=None) + + # Alternate RNG A and B bytes + bytes_ab = bytes(itertools.chain.from_iterable(zip(bytes_a, bytes_b))) + + # Write to file + f.write(bytes_ab) + + # Finished + print('100%') + +# Close device +rng.close() \ No newline at end of file diff --git a/examples/rng_bytes_to_file.py b/examples/rng_bytes_to_file.py deleted file mode 100644 index 44eea01..0000000 --- a/examples/rng_bytes_to_file.py +++ /dev/null @@ -1,37 +0,0 @@ -''' -This example showcases the generation of a binary file containing random bytes. - -This example code is in the public domain. -Author: Gabriel Guerrer -''' - -import rng_rava as rava - -# Variables -FILE_OUTPUT = 'random.bin' -N_BYTES = 1000000 # 1MB - -# 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() - -# Open file -with open(FILE_OUTPUT, mode='bw') as f: - - # Generate bytes - bytes_a, bytes_b = rng.get_rng_bytes(n_bytes=N_BYTES//2, - postproc_id=rava.D_RNG_POSTPROC['NONE'], - list_output=False, - timeout=100) - - # Write to file - f.write(bytes_a) - f.write(bytes_b) - -# Close device -rng.close() \ No newline at end of file diff --git a/examples/rng_example.py b/examples/rng_example.py index 4d0992c..8e58498 100644 --- a/examples/rng_example.py +++ b/examples/rng_example.py @@ -39,16 +39,16 @@ 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 +# Output as list, instead of bytestring 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_delta=100) +ints8 = rng.get_rng_int8s(n_ints=100, int_delta=99) # Generate 100 16-bit integers between 0 and 9999 -ints16 = rng.get_rng_int16s(n_ints=100, int_delta=10000) +ints16 = rng.get_rng_int16s(n_ints=100, int_delta=9999) # Generate 100 32-bit floats ranging between 0 and 1 floats = rng.get_rng_floats(n_floats=100) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4e99f0b..e0b94e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "rng_rava" -version = "1.0.1" +version = "1.0.2" authors = [ { name="Gabriel Guerrer", email="gabrielguerrer@gmail.com" }, ] diff --git a/src/rng_rava/__init__.py b/src/rng_rava/__init__.py index 85fb0c3..f131089 100644 --- a/src/rng_rava/__init__.py +++ b/src/rng_rava/__init__.py @@ -1,3 +1,9 @@ +""" +Copyright (c) 2023 Gabriel Guerrer + +Distributed under the MIT license - See LICENSE for details +""" + from .rava_defs import * from .rava_rng import * from .rava_rng_aio import * diff --git a/src/rng_rava/rava_defs.py b/src/rng_rava/rava_defs.py index 2532c44..28024fb 100644 --- a/src/rng_rava/rava_defs.py +++ b/src/rng_rava/rava_defs.py @@ -16,16 +16,13 @@ RAVA_USB_PID = 0x4884 COMM_MSG_START = b'$' - COMM_MSG_LEN = 8 -PERIPH_PORTS = 5 - -GET_TIMEOUT_S = 3. - SERIAL_LISTEN_LOOP_INTERVAL_S = 0.02 # 20 ms, 50 Hz readings - RNG_BYTE_STREAM_MAX_INTERVAL_MS = 4194 +GET_TIMEOUT_S = 3. + +PERIPH_PORTS = 5 D_DEV_COMM = { 'DEVICE_SERIAL_NUMBER':1, diff --git a/src/rng_rava/rava_rng.py b/src/rng_rava/rava_rng.py index 91fb1ba..82681ca 100644 --- a/src/rng_rava/rava_rng.py +++ b/src/rng_rava/rava_rng.py @@ -6,8 +6,8 @@ """ 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. +running the RAVA firmware. The computer running the driver assumes the leader's +role, 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 @@ -40,6 +40,11 @@ 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. + +The cable disconnection of an operational device is detected by the serial +functions. In response, the close() method is invoked. This method halts the +serial listen loop, closes the serial connection, and triggers the registered +device_close callback function. """ import logging @@ -55,6 +60,8 @@ from rng_rava.rava_defs import * +### LOGGING + logging.basicConfig(level=RAVA_LOG_LEVEL, format='%(asctime)s %(levelname)-7s %(message)s', datefmt='%H:%M:%S') @@ -62,6 +69,8 @@ lg = logging.getLogger('rava') +### FUNCTIONS + def find_rava_sns(usb_vid=RAVA_USB_VID, usb_pid=RAVA_USB_PID): sns = [port_info.serial_number for port_info in comports() if (port_info.vid == usb_vid and port_info.pid == usb_pid)] @@ -72,13 +81,9 @@ def find_rava_sns(usb_vid=RAVA_USB_VID, usb_pid=RAVA_USB_PID): 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] - if len(ports): - return ports[0] - else: - return None + return ports[0] if len(ports) else None def find_usb_info(port): @@ -122,6 +127,8 @@ def print_health_startup_results(test_success, test_vars_dict): .format(LOG_FILL, chisq_a, chisq_b, chisq_max_treshold)) +### RAVA_RNG + class RAVA_RNG: rava_instances = weakref.WeakSet() @@ -141,6 +148,13 @@ def __init__(self, dev_name='RAVA_RNG'): self.dev_usb_vid = None self.dev_usb_pid = None + self.health_startup_enabled = None + self.health_startup_success = None + self.health_continuous_enabled = None + self.led_enabled = None + self.lamp_enabled = None + self.peripherals_enabled = None + # Serial variables self.serial = serial.Serial() self.serial_connected = threading.Event() # Used by loop_serial_listen() @@ -153,9 +167,9 @@ def __init__(self, dev_name='RAVA_RNG'): 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 + self.cbkfcn_device_close = lambda: None + self.cbkfcn_rng_stream_data_available = lambda: None + self.cbkfcn_d2_input_capture_available = lambda: None # Finalize any previous RAVA instance for rava in self.rava_instances.copy(): @@ -197,6 +211,11 @@ def connect(self, serial_number): # Request firmware info self.dev_firmware_dict = self.get_eeprom_firmware() + self.health_startup_enabled = self.dev_firmware_dict['health_startup_enabled'] + self.health_continuous_enabled = self.dev_firmware_dict['health_continuous_enabled'] + self.led_enabled = self.dev_firmware_dict['led_enabled'] + self.lamp_enabled = self.dev_firmware_dict['lamp_enabled'] + self.peripherals_enabled = self.dev_firmware_dict['peripherals_enabled'] # Print connection info lg.info('{} Connect: Success' @@ -205,14 +224,14 @@ def connect(self, serial_number): self.dev_usb_name, self.dev_firmware_dict['version'], self.dev_serial_number, self.serial.port)) # Request Health startup info - if self.dev_firmware_dict['health_startup_enabled']: - test_success, test_vars = self.get_health_startup_results() + if self.health_startup_enabled: + self.health_startup_success, test_vars = self.get_health_startup_results() # Print test info - print_health_startup_results(test_success, test_vars) + print_health_startup_results(self.health_startup_success, test_vars) - # Error? Users have then a limited command variety (see Firmware) - if not test_success: + # Error? Users have then a limited command variety (see Firmware code) + if not self.health_startup_success: lg.error('{} Connect: Startup tests failed'.format(self.dev_name)) return False @@ -240,6 +259,8 @@ def close(self): pass + ## QUEUE + def init_queue_data(self): # Create one queue for each command self.serial_data = {} @@ -298,6 +319,8 @@ def get_queue_data(self, comm, comm_ext_id=0, timeout=GET_TIMEOUT_S): return None + ## SERIAL + def open_serial(self, port): # Set serial port self.serial.port = port @@ -328,6 +351,7 @@ def inwaiting_serial(self): lg.error('{} Serial: Failed reading in_waiting' '\n{} {} - {}' .format(self.dev_name, LOG_FILL, type(err).__name__, err)) + # Close device self.close() return None @@ -344,6 +368,7 @@ def read_serial(self, n_bytes): lg.error('{} Serial: Failed reading' '\n{} {} - {}' .format(self.dev_name, LOG_FILL, type(err).__name__, err)) + # Close device self.close() return None @@ -360,54 +385,70 @@ def write_serial(self, comm_bytes): lg.error('{} Serial: Failed writing' '\n{} {} - {}' .format(self.dev_name, LOG_FILL, type(err).__name__, err)) + # Close device self.close() return False def loop_serial_listen(self): - try: - # Debug - lg.debug('> {} SERIAL LISTEN LOOP'.format(self.dev_name)) - - # Loop while connected - while self.serial_connected.is_set(): - - # 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:] - - # 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 + # Debug + lg.debug('> {} LOOP SERIAL LISTEN'.format(self.dev_name)) + + # Loop while connected + while self.serial_connected.is_set(): + + # Command available? + comm_inwaiting = self.inwaiting_serial() + if comm_inwaiting is None: + continue # Disconnected + + if comm_inwaiting > 0: + + # Read command starting char + comm_start = self.read_serial(1) + if comm_start is None: + continue # Disconnected + + # Starts with $? + if comm_start == COMM_MSG_START: + + # Read remaining command bytes + comm_msg = self.read_serial(COMM_MSG_LEN-1) + if comm_msg is None: + continue # Disconnected + + comm_id = comm_msg[0] + comm_data = comm_msg[1:] + + # 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 + try: self.process_serial_comm(comm_id, comm_data) - else: - lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) + except Exception as err: + lg.error('{} Serial Listen Loop: Error processing command_id {}' + '\n{} {} - {}' + .format(self.dev_name, comm_id, LOG_FILL, type(err).__name__, err)) + + # Close device + self.close() else: - lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) + lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) - # The non-blocking method is prefered for finishing the thread - # when closing the device else: - 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)) + lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) - # Close device - self.close() + # The non-blocking method is prefered for finishing the thread + # when closing the device + else: + time.sleep(SERIAL_LISTEN_LOOP_INTERVAL_S) def process_serial_comm(self, comm_id, comm_data): @@ -462,7 +503,10 @@ def process_serial_comm(self, comm_id, comm_data): elif comm_id == D_DEV_COMM['EEPROM_LAMP']: 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(' 0'.format(self.dev_name)) + return None, None + if n_counts >= 2**16: + lg.error('{} RNG PC: Provide n_counts as a 16-bit integer'.format(self.dev_name)) + return None, None + comm = 'RNG_PULSE_COUNTS' - if self.snd_rava_msg(comm, [n_counts], 'L'): + if self.snd_rava_msg(comm, [n_counts], 'H'): counts_bytes_a, counts_bytes_b = self.get_queue_data(comm, timeout=timeout) - - counts_a = bytes_to_numlist(counts_bytes_a, 'B') - counts_b = bytes_to_numlist(counts_bytes_b, 'B') - return counts_a, counts_b + + if (counts_bytes_a is None) or (counts_bytes_a is None): + return None, None + + else: + if list_output: + counts_a = bytes_to_numlist(counts_bytes_a, 'B') + counts_b = bytes_to_numlist(counts_bytes_b, 'B') + return counts_a, counts_b + else: + return counts_bytes_a, counts_bytes_b def get_rng_bits(self, bit_source_id, timeout=GET_TIMEOUT_S): @@ -928,19 +1024,26 @@ def get_rng_bits(self, bit_source_id, timeout=GET_TIMEOUT_S): return bits[0] - def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id=0, list_output=True, + def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id=0, list_output=True, timeout=GET_TIMEOUT_S): + if n_bytes == 0: + lg.error('{} RNG Bytes: Provide n_bytes > 0'.format(self.dev_name)) + return None, None + if n_bytes >= 2**16: + lg.error('{} RNG Bytes: Provide n_bytes as a 16-bit integer'.format(self.dev_name)) + return None, None if postproc_id not in D_RNG_POSTPROC_INV: lg.error('{} RNG Bytes: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) - return None + return None, 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 bytes_data is not None: - rng_bytes_a, rng_bytes_b = bytes_data + if self.snd_rava_msg(comm, [n_bytes, postproc_id, request_id], 'HBB'): + rng_bytes_a, rng_bytes_b = self.get_queue_data(comm, comm_ext_id=request_id, timeout=timeout) + if (rng_bytes_a is None) or (rng_bytes_b is None): + return None, None + + else: if list_output: rng_a = bytes_to_numlist(rng_bytes_a, 'B') rng_b = bytes_to_numlist(rng_bytes_b, 'B') @@ -948,59 +1051,76 @@ def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id= else: return rng_bytes_a, rng_bytes_b - else: - return None, None - 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)) + # int_delta = int_max - int_min + if n_ints == 0: + lg.error('{} RNG Ints: Provide n_ints > 0'.format(self.dev_name)) + return None + if n_ints >= 2**16: + lg.error('{} RNG Ints: Provide n_ints as a 16-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_delta < 2: - lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) + if int_delta == 0: + lg.error('{} RNG Ints: Provide int_delta > 0'.format(self.dev_name)) return None comm = 'RNG_INT8S' - if self.snd_rava_msg(comm, [n_ints, int_delta], 'LB'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'HB'): ints_bytes = self.get_queue_data(comm, timeout=timeout) - return bytes_to_numlist(ints_bytes, 'B') + if ints_bytes is None: + return None + else: + return bytes_to_numlist(ints_bytes, 'B') 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)) + # int_delta = int_max - int_min + if n_ints == 0: + lg.error('{} RNG Ints: Provide n_ints > 0'.format(self.dev_name)) + return None + if n_ints >= 2**16: + lg.error('{} RNG Ints: Provide n_ints as a 16-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_delta < 2: - lg.error('{} RNG Ints: Provide int_delta > 1'.format(self.dev_name)) + if int_delta == 0: + lg.error('{} RNG Ints: Provide int_delta > 0'.format(self.dev_name)) return None comm = 'RNG_INT16S' - if self.snd_rava_msg(comm, [n_ints, int_delta], 'LH'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'HH'): ints_bytes = self.get_queue_data(comm, timeout=timeout) - return bytes_to_numlist(ints_bytes, 'H') + if ints_bytes is None: + return None + else: + return bytes_to_numlist(ints_bytes, 'H') 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)) + if n_floats == 0: + lg.error('{} RNG Floats: Provide n_floats > 0'.format(self.dev_name)) + return None + if n_floats >= 2**16: + lg.error('{} RNG Floats: Provide n_floats as a 16-bit integer'.format(self.dev_name)) return None comm = 'RNG_FLOATS' - if self.snd_rava_msg(comm, [n_floats], 'L'): + if self.snd_rava_msg(comm, [n_floats], 'H'): ints_bytes = self.get_queue_data(comm, timeout=timeout) - return bytes_to_numlist(ints_bytes, 'f') + if ints_bytes is None: + return None + else: + return bytes_to_numlist(ints_bytes, 'f') def snd_rng_byte_stream_start(self, n_bytes, stream_interval_ms, postproc_id=D_RNG_POSTPROC['NONE']): - if postproc_id not in D_RNG_POSTPROC_INV: - lg.error('{} RNG Stream: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) + if n_bytes == 0: + lg.error('{} RNG Stream: Provide n_bytes > 0'.format(self.dev_name)) return None if n_bytes >= 2**16: lg.error('{} RNG Stream: Provide n_bytes as a 16-bit integer'.format(self.dev_name)) @@ -1009,9 +1129,12 @@ def snd_rng_byte_stream_start(self, n_bytes, stream_interval_ms, lg.error('{} RNG Stream: Provide a stream_interval_ms <= {}ms.' .format(self.dev_name, RNG_BYTE_STREAM_MAX_INTERVAL_MS)) return None + if postproc_id not in D_RNG_POSTPROC_INV: + lg.error('{} RNG Stream: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) + return None comm = 'RNG_STREAM_START' - msg_success = self.snd_rava_msg(comm, [n_bytes, postproc_id, stream_interval_ms], 'HBH') + msg_success = self.snd_rava_msg(comm, [n_bytes, stream_interval_ms, postproc_id], 'HHB') if msg_success: self.rng_streaming = True @@ -1026,9 +1149,9 @@ def snd_rng_byte_stream_stop(self): self.rng_streaming = False # 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() + # 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 @@ -1039,12 +1162,12 @@ def get_rng_byte_stream_data(self, list_output=True, timeout=GET_TIMEOUT_S): return None, None comm = 'RNG_STREAM_BYTES' - bytes_data = self.get_queue_data(comm, timeout=timeout) - - # Timeout? - if bytes_data is not None: - rng_bytes_a, rng_bytes_b = bytes_data + rng_bytes_a, rng_bytes_b = self.get_queue_data(comm, timeout=timeout) + if (rng_bytes_a is None) or (rng_bytes_b is None): + return None, None + + else: if list_output: rng_a = bytes_to_numlist(rng_bytes_a, 'B') rng_b = bytes_to_numlist(rng_bytes_b, 'B') @@ -1052,9 +1175,6 @@ def get_rng_byte_stream_data(self, list_output=True, timeout=GET_TIMEOUT_S): else: return rng_bytes_a, rng_bytes_b - else: - return None, None - def get_rng_byte_stream_status(self, timeout=GET_TIMEOUT_S): comm = 'RNG_STREAM_STATUS' @@ -1062,7 +1182,6 @@ def get_rng_byte_stream_status(self, timeout=GET_TIMEOUT_S): return self.get_queue_data(comm, timeout=timeout) - ##################### ## HEALTH def snd_health_startup_run(self): @@ -1089,7 +1208,6 @@ def get_health_continuous_errors(self, timeout=GET_TIMEOUT_S): return n_errors, dict(zip(data_names, data_vars)) - ##################### ## PERIPHERALS def snd_periph_digi_mode(self, periph_id, mode_id): @@ -1216,7 +1334,6 @@ def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, oversampling_n_bits= return self.get_queue_data(comm, timeout=timeout) - ##################### ## INTERFACES def get_interface_ds18bs0(self, timeout=GET_TIMEOUT_S): diff --git a/src/rng_rava/rava_rng_aio.py b/src/rng_rava/rava_rng_aio.py index b29585d..23dce2b 100644 --- a/src/rng_rava/rava_rng_aio.py +++ b/src/rng_rava/rava_rng_aio.py @@ -15,10 +15,12 @@ from rng_rava.rava_rng import * +### RAVA_RNG_AIO + class RAVA_RNG_AIO(RAVA_RNG): def __init__(self): - super().__init__(dev_name='RAVA_RNG_AIO') + super().__init__(dev_name='RAVA_AIO') self.queue_type = asyncio.Queue # Variables @@ -50,6 +52,11 @@ async def connect(self, serial_number): # Request firmware info self.dev_firmware_dict = await self.get_eeprom_firmware() + self.health_startup_enabled = self.dev_firmware_dict['health_startup_enabled'] + self.health_continuous_enabled = self.dev_firmware_dict['health_continuous_enabled'] + self.led_enabled = self.dev_firmware_dict['led_enabled'] + self.lamp_enabled = self.dev_firmware_dict['lamp_enabled'] + self.peripherals_enabled = self.dev_firmware_dict['peripherals_enabled'] # Print connection info lg.info('{} Connect: Success' @@ -58,20 +65,22 @@ async def connect(self, serial_number): self.dev_usb_name, self.dev_firmware_dict['version'], self.dev_serial_number, self.serial.port)) # Request Health startup info - if self.dev_firmware_dict['health_startup_enabled']: - test_success, test_vars = await self.get_health_startup_results() + if self.health_startup_enabled: + self.health_startup_success, test_vars = await self.get_health_startup_results() # Print test info - print_health_startup_results(test_success, test_vars) + print_health_startup_results(self.health_startup_success, test_vars) - # Error? Users have then a limited command variety (see Firmware) - if not test_success: + # Error? Users have then a limited command variety (see Firmware code) + if not self.health_startup_success: lg.error('{} Connect: Startup tests failed'.format(self.dev_name)) return False return True + ## QUEUE + def put_queue_data(self, comm, value, comm_ext_id=0): # Check key if comm not in self.serial_data: @@ -118,52 +127,68 @@ async def get_queue_data(self, comm, comm_ext_id=0): return await self.serial_data[comm_ext].get() - async def loop_serial_listen(self): - try: - # Debug - lg.debug('> {} SERIAL LISTEN LOOP'.format(self.dev_name)) - - # Loop while connected - while self.serial_connected.is_set(): - - # Command available? - if self.inwaiting_serial(): + ## 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:] - - # 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 + async def loop_serial_listen(self): + # Debug + lg.debug('> {} LOOP SERIAL LISTEN'.format(self.dev_name)) + + # Loop while connected + while self.serial_connected.is_set(): + + # Command available? + comm_inwaiting = self.inwaiting_serial() + if comm_inwaiting is None: + continue # Disconnected + + if comm_inwaiting > 0: + + # Read command starting char + comm_start = self.read_serial(1) + if comm_start is None: + continue # Disconnected + + # Starts with $? + if comm_start == COMM_MSG_START: + + # Read remaining command bytes + comm_msg = self.read_serial(COMM_MSG_LEN-1) + if comm_msg is None: + continue # Disconnected + + comm_id = comm_msg[0] + comm_data = comm_msg[1:] + + # 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 + try: self.process_serial_comm(comm_id, comm_data) - else: - lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) + except Exception as err: + lg.error('{} Serial Listen Loop: Error processing command_id {}' + '\n{} {} - {}' + .format(self.dev_name, comm_id, LOG_FILL, type(err).__name__, err)) + + # Close device + self.close() else: - lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) + lg.warning('{} Serial Listen Loop: Unknown command_id {}'.format(self.dev_name, comm_id)) - # The non-blocking method is prefered for finishing the thread - # when closing the device else: - 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)) + lg.warning('{} Serial Listen Loop: Commands must start with {}'.format(self.dev_name, COMM_MSG_START)) - # Close device - self.close() + # The non-blocking method is prefered for finishing the thread + # when closing the device + else: + await asyncio.sleep(SERIAL_LISTEN_LOOP_INTERVAL_S) - ##################### ## DEVICE async def get_device_serial_number(self): @@ -178,14 +203,11 @@ async def get_device_temperature(self): 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 async def get_eeprom_device(self): @@ -252,7 +274,6 @@ async def get_eeprom_lamp(self): return dict(zip(data_names, data_vars)) - ##################### ## PWM async def get_pwm_setup(self): @@ -263,7 +284,6 @@ async def get_pwm_setup(self): return {'freq_id':freq_id, 'freq_str':D_PWM_FREQ_INV[freq_id], 'duty':duty} - ##################### ## RNG async def get_rng_setup(self): @@ -275,8 +295,15 @@ async def get_rng_setup(self): async def get_rng_pulse_counts(self, n_counts): + if n_counts == 0: + lg.error('{} RNG PC: Provide n_counts > 0'.format(self.dev_name)) + return None, None + if n_counts >= 2**16: + lg.error('{} RNG PC: Provide n_counts as a 16-bit integer'.format(self.dev_name)) + return None, None + comm = 'RNG_PULSE_COUNTS' - if self.snd_rava_msg(comm, [n_counts], 'L'): + if self.snd_rava_msg(comm, [n_counts], 'H'): counts_bytes_a, counts_bytes_b = await self.get_queue_data(comm) counts_a = bytes_to_numlist(counts_bytes_a, 'B') @@ -303,12 +330,18 @@ async def get_rng_bits(self, bit_source_id): async def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], request_id=0, list_output=True): + if n_bytes == 0: + lg.error('{} RNG Bytes: Provide n_bytes > 0'.format(self.dev_name)) + return None, None + if n_bytes >= 2**16: + lg.error('{} RNG Bytes: Provide n_bytes as a 16-bit integer'.format(self.dev_name)) + return None, None if postproc_id not in D_RNG_POSTPROC_INV: lg.error('{} RNG Bytes: Unknown postproc_id {}'.format(self.dev_name, postproc_id)) - return None + return None, None comm = 'RNG_BYTES' - if self.snd_rava_msg(comm, [n_bytes, postproc_id, request_id], 'LBB'): + if self.snd_rava_msg(comm, [n_bytes, postproc_id, request_id], 'HBB'): bytes_data = await self.get_queue_data(comm, comm_ext_id=request_id) if bytes_data is not None: @@ -326,8 +359,11 @@ async def get_rng_bytes(self, n_bytes, postproc_id=D_RNG_POSTPROC['NONE'], reque 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)) + if n_ints == 0: + lg.error('{} RNG Ints: Provide n_ints > 0'.format(self.dev_name)) + return None + if n_ints >= 2**16: + lg.error('{} RNG Ints: Provide n_ints as a 16-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)) @@ -337,14 +373,17 @@ async def get_rng_int8s(self, n_ints, int_delta): return None comm = 'RNG_INT8S' - if self.snd_rava_msg(comm, [n_ints, int_delta], 'LB'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'HB'): ints_bytes = await self.get_queue_data(comm) return bytes_to_numlist(ints_bytes, 'B') 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)) + if n_ints == 0: + lg.error('{} RNG Ints: Provide n_ints > 0'.format(self.dev_name)) + return None + if n_ints >= 2**16: + lg.error('{} RNG Ints: Provide n_ints as a 16-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)) @@ -354,18 +393,21 @@ async def get_rng_int16s(self, n_ints, int_delta): return None comm = 'RNG_INT16S' - if self.snd_rava_msg(comm, [n_ints, int_delta], 'LH'): + if self.snd_rava_msg(comm, [n_ints, int_delta], 'HH'): ints_bytes = await self.get_queue_data(comm) return bytes_to_numlist(ints_bytes, 'H') async def get_rng_floats(self, n_floats): - if n_floats >= 2**32: - lg.error('{} RNG Floats: Provide n_floats as a 32-bit integer'.format(self.dev_name)) + if n_floats == 0: + lg.error('{} RNG Floats: Provide n_floats > 0'.format(self.dev_name)) + return None + if n_floats >= 2**16: + lg.error('{} RNG Floats: Provide n_floats as a 16-bit integer'.format(self.dev_name)) return None comm = 'RNG_FLOATS' - if self.snd_rava_msg(comm, [n_floats], 'L'): + if self.snd_rava_msg(comm, [n_floats], 'H'): ints_bytes = await self.get_queue_data(comm) return bytes_to_numlist(ints_bytes, 'f') @@ -399,7 +441,6 @@ async def get_rng_byte_stream_status(self): return await self.get_queue_data(comm) - ##################### ## HEALTH async def get_health_startup_results(self): @@ -421,7 +462,6 @@ async def get_health_continuous_errors(self): return n_errors, dict(zip(data_names, data_vars)) - ##################### ## PERIPHERALS async def get_periph_digi_state(self, periph_id=1): @@ -454,7 +494,6 @@ async def get_periph_d5_adc_read(self, ref_5v=1, clk_prescaler=0, oversampling_n return await self.get_queue_data(comm) - ##################### ## INTERFACES async def get_interface_ds18bs0(self): diff --git a/src/rng_rava/rava_rng_led.py b/src/rng_rava/rava_rng_led.py index 8b77eef..cc0f3a2 100644 --- a/src/rng_rava/rava_rng_led.py +++ b/src/rng_rava/rava_rng_led.py @@ -18,12 +18,24 @@ from rng_rava.rava_rng import * +### RAVA_RNG_LED + class RAVA_RNG_LED(RAVA_RNG): def __init__(self): - super().__init__(dev_name='RAVA_RNG_LED') + super().__init__(dev_name='RAVA_LED') + + # Variables defined upon connection + self.led_attached = None - self.cbkfcn_lamp_debug = None + # Variables + self.led_color = None + self.led_intensity = None + self.lamp_mode = None + self.lamp_debug = None + + # Callback functions + self.cbkfcn_lamp_debug = lambda: None def connect(self, serial_number): @@ -31,11 +43,12 @@ def connect(self, serial_number): return False # LED firmware enabled? - if not self.dev_firmware_dict['led_enabled']: + if not self.led_enabled: lg.warning('{} Connect: LED code is disabled in the firmware'.format(self.dev_name)) # LED attached? - if not self.get_led_attached(): + self.led_attached = self.get_eeprom_led()['led_attached'] + if not self.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)) @@ -43,6 +56,8 @@ def connect(self, serial_number): return True + ## SERIAL + def process_serial_comm(self, comm_id, comm_data): super().process_serial_comm(comm_id=comm_id, comm_data=comm_data) @@ -65,27 +80,29 @@ def process_serial_comm(self, comm_id, comm_data): elif comm_id == D_DEV_COMM['LAMP_STATISTICS']: 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)) @@ -94,6 +111,8 @@ def snd_led_color(self, color_hue, intensity=255): lg.error('{} LED Color: Provide intensity as a 8-bit integer'.format(self.dev_name)) return None + self.led_color = color_hue + self.led_intensity = intensity comm = 'LED_COLOR' return self.snd_rava_msg(comm, [color_hue, intensity], 'BB') @@ -101,11 +120,18 @@ def snd_led_color(self, color_hue, intensity=255): 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)) - return None + return None if duration_ms >= 2**16: lg.error('{} LED Color: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None + + if duration_ms == 0: + lg.error('{} LED Color: Provide duration_ms > 0'.format(self.dev_name)) + return None + if color_hue_tgt - self.led_color == 0: + return None + self.led_color = color_hue_tgt comm = 'LED_COLOR_FADE' return self.snd_rava_msg(comm, [color_hue_tgt, duration_ms], 'BH') @@ -117,6 +143,13 @@ def snd_led_color_oscillate(self, n_cycles, duration_ms): if duration_ms >= 2**16: lg.error('{} LED Color: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None + + if n_cycles == 0: + lg.error('{} LED Color: Provide n_cycles > 0'.format(self.dev_name)) + return None + if duration_ms == 0: + lg.error('{} LED Color: Provide duration_ms > 0'.format(self.dev_name)) + return None comm = 'LED_COLOR_OSCILLATE' return self.snd_rava_msg(comm, [n_cycles, duration_ms], 'BH') @@ -127,6 +160,7 @@ def snd_led_intensity(self, intensity): lg.error('{} LED Intensity: Provide intensity as a 8-bit integer'.format(self.dev_name)) return None + self.led_intensity = intensity comm = 'LED_INTENSITY' return self.snd_rava_msg(comm, [intensity], 'B') @@ -138,7 +172,14 @@ def snd_led_intensity_fade(self, intensity_tgt, duration_ms): if duration_ms >= 2**16: lg.error('{} LED Intensity: Provide duration_ms as a 16-bit integer'.format(self.dev_name)) return None + + if duration_ms == 0: + lg.error('{} LED Intensity: Provide duration_ms > 0'.format(self.dev_name)) + return None + if intensity_tgt - self.led_intensity == 0: + return None + self.led_intensity = intensity_tgt comm = 'LED_INTENSITY_FADE' return self.snd_rava_msg(comm, [intensity_tgt, duration_ms], 'BH') @@ -160,10 +201,11 @@ def get_led_status(self, timeout=GET_TIMEOUT_S): 'color_fading':color_fading, 'intensity_fading':intensity_fading} - ##################### ## LAMP def snd_lamp_mode(self, on=True): + self.lamp_mode = on + comm = 'LAMP_MODE' return self.snd_rava_msg(comm, [on], 'B') @@ -179,11 +221,12 @@ def get_lamp_statistics(self, timeout=GET_TIMEOUT_S): def snd_lamp_debug(self, on=True): + self.lamp_debug = on comm = 'LAMP_DEBUG' return self.snd_rava_msg(comm, [on], 'B') - def get_lamp_debug(self, timeout=GET_TIMEOUT_S): + def get_lamp_debug_data(self, timeout=GET_TIMEOUT_S): comm = 'LAMP_DEBUG' data_vars = self.get_queue_data(comm, timeout=timeout)