Nanodrop

CIHex.pmDocsLicense

Elixir library for interfacing with NanoDrop 1000 spectrophotometers over USB.

The NanoDrop 1000 internally uses an Ocean Optics USB2000 spectrometer and communicates via the OOI (Ocean Optics Interface) protocol. This library provides a clean Elixir API for spectrum acquisition, absorbance measurements, and common assays like nucleic acid and protein quantification.

Features

Installation

Add nanodrop to your list of dependencies in mix.exs:

def deps do
  [
    {:nanodrop, "~> 0.1.0"}
  ]
end

USB Library

This library requires usb for USB communication. It's listed as an optional dependency, so you'll need to add it explicitly on nodes that have USB access:

def deps do
  [
    {:nanodrop, "~> 0.1.0"},
    {:usb, "~> 0.2.1"}
  ]
end

Linux udev Rules

On Linux, you'll need udev rules to access the device without root. Create /etc/udev/rules.d/99-nanodrop.rules:

# NanoDrop 1000 / Ocean Optics USB2000
SUBSYSTEM=="usb", ATTR{idVendor}=="2457", ATTR{idProduct}=="1002", MODE="0666"

Then reload rules: sudo udevadm control --reload-rules && sudo udevadm trigger

Quick Start

# Start the server (connects to first available device)
{:ok, pid} = Nanodrop.start_link()

# Calibrate at the start of each session
:ok = Nanodrop.set_dark(pid)   # Close pedestal arm, no sample
:ok = Nanodrop.set_blank(pid)  # Water or buffer on pedestal

# Measure nucleic acid concentration
{:ok, result} = Nanodrop.measure_nucleic_acid(pid)
# => %{
#      a260: 1.5,
#      a280: 0.75,
#      a260_a280: 2.0,
#      concentration_ng_ul: 75.0,
#      spectrum: %{absorbance: [...], wavelengths: [...], ...}
#    }

# Or get the full spectrum and analyze manually
{:ok, spectrum} = Nanodrop.get_spectrum(pid)
abs_280 = Nanodrop.absorbance_at(spectrum, 280.0)

Calibration

For accurate absorbance measurements, calibrate with two reference spectra:

  1. Dark - Detector baseline with no light. Close the pedestal arm with nothing on the pedestal.
  2. Blank - 100% transmission reference. Place your solvent (water, buffer) on the pedestal.

Absorbance is calculated as: A = -log10((sample - dark) / (blank - dark))

Calibration expires after 30 minutes of inactivity (no measurements for 5+ minutes). The library will return {:error, :recalibration_needed} when recalibration is required.

API Reference

Device Management

# List connected devices
Nanodrop.list_devices()
#=> [%{vendor_id: 0x2457, product_id: 0x1002, bus: 1, address: 19, device_ref: #Ref<...>}]

# Start with a specific device
{:ok, pid} = Nanodrop.start_link(device: device_info)

# Get device info
Nanodrop.info(pid)
Nanodrop.serial_number(pid)
Nanodrop.wavelength_calibration(pid)

Configuration

# Set integration time (3,000 - 655,350,000 microseconds)
Nanodrop.set_integration_time(pid, 100_000)  # 100ms

# Check calibration status
Nanodrop.calibrated?(pid)  #=> true | false

Spectrum Acquisition

# Raw spectrum (no calibration required)
{:ok, spectrum} = Nanodrop.get_raw_spectrum(pid)

# Absorbance spectrum (requires calibration)
{:ok, spectrum} = Nanodrop.get_spectrum(pid)

# Get absorbance at specific wavelength
Nanodrop.absorbance_at(spectrum, 260.0)

Assays

# Nucleic acid quantification
{:ok, result} = Nanodrop.measure_nucleic_acid(pid)
{:ok, result} = Nanodrop.measure_nucleic_acid(pid, factor: 40.0)  # RNA

# Protein quantification
{:ok, result} = Nanodrop.measure_protein(pid)
{:ok, result} = Nanodrop.measure_protein(pid, extinction_coefficient: 1.4)

Distributed Operation

This library is designed for distributed Erlang deployments. Run the NanoDrop server on a node with USB access and control it from anywhere in the cluster.

Direct Node Reference

# On the device node (e.g., nanodrop@device.local)
{:ok, pid} = Nanodrop.start_link(name: Nanodrop)

# From a remote node
Nanodrop.set_dark({Nanodrop, :"nanodrop@device.local"})
Nanodrop.set_blank({Nanodrop, :"nanodrop@device.local"})
{:ok, result} = Nanodrop.measure_nucleic_acid({Nanodrop, :"nanodrop@device.local"})

Global Registration

# On the device node
{:ok, pid} = Nanodrop.start_link(name: {:global, :nanodrop})

# From any connected node
Nanodrop.set_dark({:global, :nanodrop})
{:ok, result} = Nanodrop.measure_nucleic_acid({:global, :nanodrop})

Process Groups

# On the device node
{:ok, pid} = Nanodrop.start_link()
:pg.join(:spectrophotometers, pid)

# From any connected node
[pid | _] = :pg.get_members(:spectrophotometers)
{:ok, result} = Nanodrop.measure_nucleic_acid(pid)

Network-Only Mode

On nodes without USB access, configure network-only mode to skip device initialization:

# config/config.exs
config :nanodrop, network_only: true

This causes Nanodrop.start_link/1 to return :ignore, allowing the application to start without USB hardware.

Hardware

Supported Devices

Device USB VID USB PID Status
NanoDrop 1000 0x2457 0x1002 Supported
Ocean Optics USB2000 0x2457 0x1002 Should work

Specifications

License

MIT License - see LICENSE for details.

Acknowledgments