This sample code is platform independent and can be run on Windows, Linux and macOS.

A sample source code for retrieving data from a SwiftRNG device utilizing the SwiftRNG device API.


import sys
import hashlib
import serial
from serial.tools import list_ports

# This Python 3 code demonstrates how to use the SwiftRNG device API.
# It retrieves entropy in 16,000-byte chunks and applies SHA-256 post-processing for SwiftRNG and SwiftRNG LE models.
# SwiftRNG Pro and SwiftRNG Z models don't require this post-processing.
# This code is compatible with all SwiftRNG models and works on Windows, Linux and macOS.
# You may need sudo permissions to open the device on Linux and macOS.

class SwiftRNG:
  """A simple example class for communicating with a SwiftRNG device over USB serial interface."""
  def __init__(self):
    self.post_process = True
    self.ser = None
    self.model = ""
    self.sha = hashlib.sha256()
  
  def __search_for_swiftrng_device(self):
    """Search for SwiftRNG devices using USB VID/PID and description."""
    plist = list(serial.tools.list_ports.comports())
    for p in plist:
      desc = p.hwid
      if "1FC9:8111" in desc and "SWRNG" in desc:
        return p
    return None

  def __clear(self):
    """Clear the receive buffer from any data that may have been left from a previous command."""
    self.ser.reset_input_buffer()

  def post_process_enabled(self):
    """Retrieves a flag indicating if post processing is required for the connected device."""
    return self.post_process

  def get_model(self):
    """Converts device model code to a name."""
    if self.ser is None:
      raise Exception("Device not connected")
    match self.model:
      case b'LWTSWRNG':
        return "SwiftRNG LE"
      case b'SWFTRNGZ':
        return "SwiftRNG Z"
      case b'SWRNGPRO':
        return "SwiftRNG Pro"
      case b'SwiftRNG':
        return "SwiftRNG"
      case _:
        return str(self.model, 'ASCII')

  def get_serial_number(self):
    """Retrieves the device serial number."""
    return str(self.process_cmd(b's', 15), 'ASCII')
      
  def get_version(self):
    """Retrieves the device version."""
    return str(self.process_cmd(b'v', 4), 'ASCII')

  def get_device_name(self):
    """Retrieves the device name as it is used in OS. For example 'COM3' in Windows or '/dev/ttyUSB' in Linux"""
    if self.ser is None:
      raise Exception("Device not connected")
    return self.device.device

  def get_entropy(self):
    """Retrieves 16,000 bytes of entropy. A sha256 post processing is applied for SwiftRNG and SwiftRNG LE."""
    x = self.process_cmd(b'x', 16000)
    if self.post_process == False:
      return x
    else:
      resp = bytearray()
      blocks = 16000 / 32
      cur_block = 0
      while (cur_block < blocks):
        subx = x[cur_block * 32 : cur_block * 32 + 32]
        self.sha.update(subx)
        cur_digest = self.sha.digest()
        cur_block += 1
        resp.extend(cur_digest)
    return resp

  def process_cmd(self, cmd, resp_size):
    """Sends a 1-byte command to device and returns a response. Handles timeout scenarios."""
    if self.ser is None:
      raise Exception("Device not connected")
    retry_count = 5
    while retry_count > 0:
      try:
        return self.__call_device_api(cmd, resp_size)
      except serial.SerialTimeoutException:
        retry_count -= 1
        self.__clear()
    raise serial.SerialTimeoutException("Timeout reached when processing command", cmd)

  def __call_device_api(self, cmd, resp_size):
    """Sends a 1-byte command to device and returns a response. Validates the status byte."""
    self.ser.write(cmd)
    self.ser.flush()
    act_resp_size = resp_size + 1
    resp = self.ser.read(act_resp_size)
    if len(resp) != act_resp_size:
      raise serial.SerialTimeoutException("Time out when reading response from device")
    status = resp[resp_size]
    if status != 0:
      raise Exception("Device internal state in error")
    return resp[0:resp_size]

  def close(self):
    """Closes the serial connection if previously established."""
    if self.ser is None:
      raise Exception("Device not connected")
    self.ser.close()
    self.ser = None
    
  def open(self):
    """Opens a connection to an exisiting SwiftRNG device."""
    if self.ser is not None:
      raise Exception("Device already connected")
    self.device = self.__search_for_swiftrng_device()
    if self.device is not None:
      self.ser = serial.Serial(self.device.device, timeout = 2, write_timeout = 0.05, exclusive = True)
      self.__clear()
      self.model = self.process_cmd(b'm', 8)
      if b'SWFTRNGZ' == self.model or b'SWRNGPRO' == self.model:
        # SwiftRNG Pro and SwiftRNG Z do not require post processing.
        self.post_process = False
      return True
    else:
      return False

  def __del__(self):
    if self.ser is not None:
      self.ser.close()
      self.ser = None


##################
# App main entry
##################

# Search for available SwiftRNG devices and open the first one available.
obj = SwiftRNG()
if obj.open() == False:
  print("Could not find any SwiftRNG device")
  sys.exit(1)


print("Found attached device:", obj.get_device_name())
print("Device model:", obj.get_model())
print("Device serial number:", obj.get_serial_number())
print("Device version:", obj.get_version())
print("Post process enabled:", obj.post_process_enabled())

# Retrieve one chunk (16,000 bytes) of entropy from device.
entropy = obj.get_entropy()
print("Retrieved", len(entropy), "bytes of entropy from device")
print("Printing the first 10 bytes from the entropy pool retrieved:")
for x in range(10):
  print ("entropy [", x, "]:", entropy[x]) 

# Retrieve unprocessed and unmodified random bytes generated from the first noise source
noise_source_1 = obj.process_cmd(b'<', 16000)
print("Retrieved", len(noise_source_1), "raw bytes from noise source 1")

# Retrieve unprocessed and unmodified random bytes generated from the second noise source
noise_source_2 = obj.process_cmd(b'>', 16000)
print("Retrieved", len(noise_source_2), "raw bytes from noise source 2")

# Retrieve frequency table of each noise source. Each table contains 256 16-bit integers.
freq_table = obj.process_cmd(b'f', 1024)
print("Retrieved frequency tables")

obj.close()