unihedron

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 9 Imported by: 0

README

unihedron

A Go driver for Unihedron Sky Quality Meter (SQM) devices over their FTDI USB-serial link — the SQM-LU, and over the same ASCII protocol the SQM-LU-DL and SQM-LE.

The SQM-LU presents itself as an FTDI virtual COM port. The protocol is plain ASCII: single-character command types, each reply terminated with \r\n.

Hardware-validated against a live SQM-LU (protocol 4, model 3, feature 76).

Use

sqm, err := unihedron.OpenFirst()      // or OpenPort("/dev/cu.usbserial-XXXX"), OpenBySerial("AG0JWD3W")
if err != nil { log.Fatal(err) }
defer sqm.Close()

info, _ := sqm.UnitInfo()               // protocol / model / feature / serial
cal,  _ := sqm.Calibration()            // stored calibration constants
r,    _ := sqm.Reading()                // averaged reading
fmt.Println(r)                          // 11.19 mag/arcsec², 3248 Hz, 0.000s, 25.1°C

Reading carries the sky brightness (MagPerArcsec2), sensor FrequencyHz, PeriodCounts / PeriodSeconds, and sensor TempC. UnaveragedReading() issues ux for the most-recent (un-boxcar-averaged) sample. IntervalSettings() reads the timed- report parameters (firmware feature ≥13). Command(prefix, raw) is the escape hatch for any command not wrapped by a typed method.

CLI — sqmsnap
go build ./cmd/sqmsnap
./sqmsnap                    # unit + calibration + one reading
./sqmsnap -json              # machine-readable
./sqmsnap -watch 5s          # poll every 5s
./sqmsnap -list              # probe FTDI ports; list the ones that are SQMs (bridge + unit serial)
./sqmsnap -unaveraged        # use ux instead of rx

Protocol notes

Command reference: SQM-LU Operator's Manual §8 "Commands and responses" (unihedron.com).

Read-only by design: the driver wraps the reading/info/calibration/interval-query commands. The calibration-write and firmware-upgrade commands (zcalA/B/D, zcal5-8, 0x19, :) are intentionally not wrapped; issue them via Command if you must.

License

MIT — see LICENSE.

Documentation

Overview

Package unihedron is a pure-Go driver for Unihedron Sky Quality Meter (SQM) devices over their FTDI USB-serial link: the SQM-LU and (over the same ASCII protocol) the SQM-LU-DL and SQM-LE. Like the Pegasus focusers — and unlike the ZWO/Astroasis USB-HID accessories — the SQM-LU presents itself as an FTDI virtual COM port (an OS serial device), and the protocol is plain ASCII: single-character command types (e.g. "rx", "ix", "cx"), each reply terminated with "\r\n".

The command set is Unihedron's published SQM-LU serial protocol (section 8, "Commands and responses", of the SQM-LU Operator's Manual). The transport opens the FTDI virtual COM port at 115200 8N1 and does line I/O via go.bug.st/serial — no vendor library, no raw-USB FTDI reimplementation. It uses only the library's pure-Go paths (port I/O everywhere; the USB-VID enumerator off macOS, device-name matching on macOS), so it builds for any target with CGO_ENABLED=0.

Index

Constants

View Source
const (
	VID  uint16 = 0x0403
	Baud        = 115200
)

FTDI vendor ID (the SQM-LU's USB-serial bridge) and the SQM-LU line speed.

Variables

This section is empty.

Functions

This section is empty.

Types

type Calibration

type Calibration struct {
	LightCalOffset float64 // light calibration offset, mag/arcsec²
	DarkCalPeriod  float64 // dark calibration time period, seconds
	LightCalTempC  float64 // temperature during light calibration, °C
	SensorOffset   float64 // factory reference light-source offset (≈8.71), mag/arcsec²
	DarkCalTempC   float64 // temperature during dark calibration, °C
}

Calibration is a decoded "cx" calibration-information response (SQM-LU manual §8.3.1).

type DeviceInfo

type DeviceInfo struct {
	Port    string // e.g. /dev/cu.usbserial-XXXX, /dev/ttyUSB0, COM3
	Serial  string // USB iSerialNumber (from the enumerator); "" if unavailable
	Product string // USB iProduct string (from the enumerator); "" if unavailable
}

DeviceInfo identifies an opened serial port plus the USB-descriptor properties the enumerator reports for it before the port is opened. Serial is the FTDI bridge's USB iSerialNumber — a stable per-unit identity that disambiguates several FTDI devices sharing VID 0x0403 and survives replug / port renumbering. Note this is the USB bridge's serial, not the SQM's own serial number (which UnitInfo returns).

func Enumerate

func Enumerate() ([]DeviceInfo, error)

Enumerate lists attached FTDI serial ports (the raw candidate list, not yet identified as SQMs). Matching is per-OS: enum_other.go uses the USB VID via the pure-Go enumerator; enum_darwin.go matches the FTDI device-name convention, deliberately avoiding the enumerator's macOS cgo (IOKit) path so the driver builds for any target with CGO_ENABLED=0. Use Discover to get only the ports that are actually Unihedron SQMs.

type Discovered

type Discovered struct {
	DeviceInfo
	Unit UnitInfo
}

Discovered is a Unihedron identified by probing an FTDI serial port: the enumerated port and USB-bridge descriptor (DeviceInfo), plus the SQM's own identity from ix (Unit.Serial is the meter's serial number, distinct from the FTDI bridge's Serial).

func Discover

func Discover() ([]Discovered, error)

Discover enumerates FTDI serial ports and probes each with the ix command, returning only the ports that identify as a Unihedron SQM. Ports that are busy (held exclusively by another driver — e.g. a Pegasus focuser, which also uses FTDI VID 0x0403, on Linux) or that accept the open but don't answer as an SQM are skipped. This reliably separates SQMs from other FTDI devices, so callers don't need to supply a serial number.

type IntervalSettings

type IntervalSettings struct {
	PeriodEEPROM    int     // interval period stored in EEPROM (boot value), seconds
	PeriodRAM       int     // interval period in RAM (live value), seconds
	ThresholdEEPROM float64 // report threshold stored in EEPROM, mag/arcsec²
	ThresholdRAM    float64 // report threshold in RAM, mag/arcsec²
}

IntervalSettings is a decoded "Ix" interval-report settings response (manual §8.7.3, firmware Feature ≥13). Period is in seconds, Threshold in mag/arcsec².

type Reading

type Reading struct {
	MagPerArcsec2 float64 // sky brightness in mag/arcsec² (darker sky → larger value; 0.00 means the sensor is saturated by light)
	FrequencyHz   int     // light-sensor output frequency, Hz
	PeriodCounts  int64   // sensor period in counts of the 460.8 kHz (14.7456 MHz/32) clock
	PeriodSeconds float64 // sensor period in seconds (PeriodCounts / 460800)
	TempC         float64 // temperature at the light sensor, °C
}

Reading is a decoded "rx"/"ux" reading response (SQM-LU manual §8.2.1/§8.2.2).

func (Reading) String

func (r Reading) String() string

type SQM

type SQM struct {
	// contains filtered or unexported fields
}

SQM is an opened Unihedron Sky Quality Meter.

func New

func New(t Transport, info DeviceInfo) *SQM

New wraps an already-open Transport. Most callers use OpenFirst / OpenPort; New is for a custom Transport (alternate backend, or a fake for testing).

func OpenBySerial

func OpenBySerial(serial string) (*SQM, error)

OpenBySerial opens the SQM whose USB-bridge serial number matches serial (case-insensitive). The serial comes from the USB descriptor via the enumerator — read before opening — so it disambiguates several FTDI devices sharing VID 0x0403 and binds the same physical unit across replug / port renumbering.

func OpenFirst

func OpenFirst() (*SQM, error)

OpenFirst finds and opens the first attached SQM (FTDI) serial port.

func OpenPort

func OpenPort(port string) (*SQM, error)

OpenPort opens the SQM on a specific serial port (from Enumerate).

func (*SQM) Calibration

func (s *SQM) Calibration() (Calibration, error)

Calibration requests the stored calibration constants with the "cx" command.

func (*SQM) Close

func (s *SQM) Close() error

func (*SQM) Command

func (s *SQM) Command(prefix byte, cmd string) (string, error)

Command issues a raw command string and returns the first reply line beginning with prefix (the response type byte, e.g. 'r', 'i', 'c'), trimmed of its "\r\n". This is the escape hatch for commands not wrapped by a typed method. Note SQM commands are sent verbatim (no line terminator).

func (*SQM) Info

func (s *SQM) Info() DeviceInfo

Info returns the port/USB descriptor of the opened device. For the SQM's own firmware/serial identity, use UnitInfo.

func (*SQM) IntervalSettings

func (s *SQM) IntervalSettings() (IntervalSettings, error)

IntervalSettings requests the timed-interval reporting parameters with the "Ix" command (firmware Feature ≥13; older units will time out).

func (*SQM) Reading

func (s *SQM) Reading() (Reading, error)

Reading requests an averaged reading with the "rx" command (last-8 boxcar average in period mode; frequency mode is inherently averaged over a 1 s sample).

func (*SQM) UnaveragedReading

func (s *SQM) UnaveragedReading() (Reading, error)

UnaveragedReading requests the most recent, un-averaged reading with the "ux" command.

func (*SQM) UnitInfo

func (s *SQM) UnitInfo() (UnitInfo, error)

UnitInfo requests firmware/identity details with the "ix" command.

type Transport

type Transport interface {
	Write(p []byte) (int, error)
	Read(p []byte) (int, error)
	Close() error
}

Transport is a byte-level serial channel to the FTDI VCP port (satisfied by a go.bug.st/serial port); the device logic frames ASCII commands over it. Read should block up to a short timeout and return 0 bytes (not an error) when nothing is available, so command() can poll to a deadline.

type UnitInfo

type UnitInfo struct {
	Protocol int // data-protocol revision (independent of Feature)
	Model    int // hardware model the firmware targets
	Feature  int // software feature revision
	Serial   int // the SQM's own unique serial number
}

UnitInfo is a decoded "ix" unit-information response (SQM-LU manual §8.2.5). All four are firmware-reported identity numbers; Feature gates optional capabilities (≥13 interval reporting, ≥14 serial in interval reports, ≥44 I²C accessories).

Directories

Path Synopsis
cmd
sqmsnap command
sqmsnap is a small CLI over the unihedron driver: it opens a Unihedron SQM (by port, by USB serial, or the first one found), prints unit info + calibration + a reading, and can poll readings at an interval.
sqmsnap is a small CLI over the unihedron driver: it opens a Unihedron SQM (by port, by USB serial, or the first one found), prints unit info + calibration + a reading, and can poll readings at an interval.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL