radpro

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2025 License: EUPL-1.2 Imports: 11 Imported by: 0

README

radpro

radpro is a small and simple library for RadPro devices. It implements the communication protocol that allows you to retrieve info from a device.

Read the Go documentation for details.

Documentation

Overview

Package radpro implements a simple library for RadPro devices. Specifically, it implements the RadPro communication protocol

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrResponse indicates an error response from the device.
	// This means that the executed command was not valid according to the device.
	ErrResponse = errors.New("error response")

	// ErrInvalidResponse indicates that the command executed successfully,
	// but the response from the device could not be parsed.
	ErrInvalidResponse = errors.New("invalid response")
)

Functions

func RecordValues

func RecordValues(i iter.Seq[Record]) iter.Seq[Record]

RecordValues returns an iterator that only returns the non-zero values from the given record iterator.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro"
	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	// This requests all log entries from the device:
	log, _ := device.DataLog(nil)

	for record := range radpro.RecordValues(log) {
		fmt.Printf("%s: %d\n", record.Time.UTC(), record.PulseCount)
	}

}
Output:

2023-07-22 04:26:40 +0000 UTC: 1542
2023-07-22 04:27:40 +0000 UTC: 1618
2023-07-22 04:28:40 +0000 UTC: 1693

Types

type Device

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

Device represents a RadPro device.

func New

func New(rwc io.ReadWriteCloser) *Device

New returns a device that communicates over the given io.ReadWriteCloser.

func OpenSerial

func OpenSerial(path string) (*Device, error)

OpenSerial opens the serial device on the given path, and uses it to initialize a new device.

func (*Device) BatteryVoltage

func (d *Device) BatteryVoltage() (float64, error)

BatteryVoltage returns the current battery voltage of the device. The precision is limited to three decimal places.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	voltage, _ := device.BatteryVoltage()
	fmt.Printf("Battery voltage: %.3f V\n", voltage)
}
Output:

Battery voltage: 1.421 V

func (*Device) Close

func (d *Device) Close() error

Close closes the connection of the device and releases all associated resources. The behaviour of calling close twice is dependent on the type of io.ReadWriteCloser that the Device was initialized with.

func (*Device) DataLog

func (d *Device) DataLog(options *LogOptions) (iter.Seq[Record], error)

DataLog returns an iterator for the data log of the device. It only returns records depending on the limits set in the options. All records are returned when the options are nil.

The iterator yields empty Record values to indicate the start of logging sessions unless NoSessions is set in LogOptions. Use Record.IsZero to identify and/or RecordValues to discard them afterward.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	// This requests all log entries from the device:
	logEntries, _ := device.DataLog(nil)

	for entry := range logEntries {
		fmt.Printf("%s: %d\n", entry.Time.UTC(), entry.PulseCount)
	}

}
Output:

0001-01-01 00:00:00 +0000 UTC: 0
2023-07-22 04:26:40 +0000 UTC: 1542
2023-07-22 04:27:40 +0000 UTC: 1618
2023-07-22 04:28:40 +0000 UTC: 1693
Example (Limits)
package main

import (
	"fmt"
	"time"

	"go.hofstra.dev/radpro"
	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	// This requests all log entries between the given limits,
	// and omits the zero value records that would normally be present.
	logEntries, _ := device.DataLog(&radpro.LogOptions{
		Start:      time.Date(2023, 7, 22, 4, 26, 0, 0, time.UTC),
		End:        time.Date(2023, 7, 22, 4, 29, 0, 0, time.UTC),
		Max:        4,
		NoSessions: true,
	})

	for entry := range logEntries {
		fmt.Printf("%s: %d\n", entry.Time.UTC(), entry.PulseCount)
	}

}
Output:

2023-07-22 04:26:40 +0000 UTC: 1542
2023-07-22 04:27:40 +0000 UTC: 1618
2023-07-22 04:28:40 +0000 UTC: 1693

func (*Device) Info

func (d *Device) Info() (Info, error)

Info returns the Info for the device.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	info, _ := device.Info()
	fmt.Printf("Hardware type: %s\n", info.HardwareType)
	fmt.Printf("Software version: %s\n", info.SoftwareVersion)
	fmt.Printf("Device ID: %s\n", info.DeviceID)
}
Output:

Hardware type: FS2011 (STM32F051C8)
Software version: Rad Pro 2.0/en
Device ID: radprotest

func (*Device) PowerState

func (d *Device) PowerState() (bool, error)

PowerState returns the current power state of the device, with 'true' indicating 'on' and 'false' indicating 'off'.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	powerState, _ := device.PowerState()
	fmt.Printf("Power state: %t\n", powerState)
}
Output:

Power state: false

func (*Device) RandomData

func (d *Device) RandomData() ([]byte, error)

RandomData retrieves up to 16 bytes of random data from the device. It may return zero bytes of data if no data is available.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	data, _ := device.RandomData()

	fmt.Printf("%x\n", data)
}
Output:

9155facb75c00e331cf7fd625102f37a

func (*Device) SetPowerState

func (d *Device) SetPowerState(v bool) error

SetPowerState sets the power state of the device, with 'true' indicating 'on' and 'false' indicating 'off'.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetPowerState(true)

	powerState, _ := device.PowerState()
	fmt.Printf("On: %t\n", powerState)

	_ = device.SetPowerState(false)

	powerState, _ = device.PowerState()
	fmt.Printf("On: %t\n", powerState)
}
Output:

On: true
On: false

func (*Device) SetTime

func (d *Device) SetTime(t time.Time) error

SetTime sets the time of the device to the given value.

Example
package main

import (
	"fmt"
	"time"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetTime(time.Date(2025, 9, 24, 17, 0, 38, 0, time.UTC))

	ts, _ := device.Time()
	fmt.Println(ts.UTC())
}
Output:

2025-09-24 17:00:38 +0000 UTC

func (*Device) SetTimeZone

func (d *Device) SetTimeZone(loc *time.Location) error

SetTimeZone sets the timezone of the device to the given location.

Example
package main

import (
	"fmt"
	"time"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	device.SetTimeZone(time.FixedZone("PT", -8*60*60))

	loc, _ := device.TimeZone()
	fmt.Printf("Time zone: %s\n", loc)
}
Output:

Time zone: UTC-8

func (*Device) SetTubeLifetime

func (d *Device) SetTubeLifetime(lifetime time.Duration) error

SetTubeLifetime sets the tube's operational life in seconds.

Example
package main

import (
	"fmt"
	"time"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetTubeLifetime(24 * time.Hour)

	lifetime, _ := device.TubeLifetime()
	fmt.Println(lifetime)
}
Output:

24h0m0s

func (*Device) SetTubePWMDutyCycle

func (d *Device) SetTubePWMDutyCycle(v float64) error

SetTubePWMDutyCycle set the PWM duty cycle of the high-voltage generator of the tube. The value is between 0 and 1 with precision limited to five decimal places.

This functionality is not supported on all devices.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetTubePWMDutyCycle(0.5)

	pwmDutyCycle, _ := device.TubePWMDutyCycle()
	fmt.Printf("%.3f%%\n", 100*pwmDutyCycle)
}
Output:

50.000%

func (*Device) SetTubePWMFrequency

func (d *Device) SetTubePWMFrequency(frequency float64) error

SetTubePWMFrequency sets the PWM frequency of the high-voltage generator of the tube in Hz. Precision is limited to two decimal places.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetTubePWMFrequency(1234.56)

	pwmFrequency, _ := device.TubePWMFrequency()
	fmt.Printf("%.2f Hz\n", pwmFrequency)
}
Output:

1234.56 Hz

func (*Device) SetTubePulseCount

func (d *Device) SetTubePulseCount(v uint32) error

SetTubePulseCount sets the tube's lifetime pulse count.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.SetTubePulseCount(4321)

	pulseCount, _ := device.TubePulseCount()
	fmt.Println(pulseCount)
}
Output:

4321

func (*Device) StartBootloader

func (d *Device) StartBootloader() error

StartBootloader starts the system bootloader so the device can be updated.

This functionality is not supported on all devices.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	err := device.StartBootloader()
	fmt.Printf("Error: %v\n", err)
}
Output:

Error: <nil>

func (*Device) Time

func (d *Device) Time() (time.Time, error)

Time returns the current time according to the device.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	ts, _ := device.Time()
	fmt.Println(ts.UTC().String())
}
Output:

2023-07-22 04:26:40 +0000 UTC

func (*Device) TimeZone

func (d *Device) TimeZone() (*time.Location, error)

TimeZone returns a time.Location matching the device.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	loc, _ := device.TimeZone()
	fmt.Printf("Time zone: %s\n", loc)
}
Output:

Time zone: UTC+0

func (*Device) TubeDeadTime

func (d *Device) TubeDeadTime() (time.Duration, error)

TubeDeadTime returns an upper bound of the tube's dead time, with precision limited to 0.1 µs.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	deadTime, _ := device.TubeDeadTime()
	fmt.Println(deadTime)
}
Output:

242µs

func (*Device) TubeDeadTimeCompensation

func (d *Device) TubeDeadTimeCompensation() (time.Duration, error)

TubeDeadTimeCompensation returns the tube's dead-time compensation, with precision limited to 0.1 µs.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	deadTimeCompensation, _ := device.TubeDeadTimeCompensation()
	fmt.Println(deadTimeCompensation)
}
Output:

250µs

func (*Device) TubeLifetime

func (d *Device) TubeLifetime() (time.Duration, error)

TubeLifetime returns the tube's operational life in seconds.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	lifetime, _ := device.TubeLifetime()
	fmt.Println(lifetime)
}
Output:

4h26m40s

func (*Device) TubePWMDutyCycle

func (d *Device) TubePWMDutyCycle() (float64, error)

TubePWMDutyCycle returns the PWM duty cycle of the high-voltage generator of the tube. The value is between 0 and 1 with precision limited to five decimal places.

This functionality is not supported on all devices.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	pwmDutyCycle, _ := device.TubePWMDutyCycle()
	fmt.Printf("%.3f%%\n", 100*pwmDutyCycle)
}
Output:

9.750%

func (*Device) TubePWMFrequency

func (d *Device) TubePWMFrequency() (float64, error)

TubePWMFrequency returns the PWM frequency of the high-voltage generator of the tube in Hz. Precision is limited to two decimal places.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	pwmFrequency, _ := device.TubePWMFrequency()
	fmt.Printf("%.2f Hz\n", pwmFrequency)
}
Output:

1250.00 Hz

func (*Device) TubePulseCount

func (d *Device) TubePulseCount() (uint32, error)

TubePulseCount returns the tube's lifetime pulse count. This value is updated continuously, but overflows after math.MaxUint32 counts.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	pulseCount, _ := device.TubePulseCount()
	fmt.Println(pulseCount)
}
Output:

1500

func (*Device) TubeRate

func (d *Device) TubeRate() (float64, error)

TubeRate returns the tube's instantaneous rate in counts per minute (cpm). The precision is limited to three decimal places, and the value is updated every second. For better precision use the difference between two Device.TubePulseCount measurements. Divide by Device.TubeSensitivity to convert the value in cpm to µSv/h.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	rate, _ := device.TubeRate()
	fmt.Printf("%.3f\n", rate)
}
Output:

142.857

func (*Device) TubeSensitivity

func (d *Device) TubeSensitivity() (float64, error)

TubeSensitivity returns the tube's sensitivity in cpm/(µSv/h). This value can be used to divide a value in cpm (eg Device.TubeRate) to calculate the radiation dose rate in µSv/h. The precision of the returned value is limited to three decimal places.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	sensitivity, _ := device.TubeSensitivity()
	fmt.Printf("%.3f cpm/(µSv/h)\n", sensitivity)
}
Output:

153.800 cpm/(µSv/h)

func (*Device) WipeDataLog

func (d *Device) WipeDataLog() error

WipeDataLog clears the data log from the device's memory.

Example
package main

import (
	"fmt"

	"go.hofstra.dev/radpro/radprotest"
)

func main() {
	device := radprotest.NewDevice()
	defer device.Close()

	_ = device.WipeDataLog()

	logEntries, _ := device.DataLog(nil)

	for entry := range logEntries {
		fmt.Printf("%s: %d\n", entry.Time.UTC(), entry.PulseCount)
	}

}

type Info

type Info struct {
	// Hardware type.
	HardwareType string

	// Software version and language.
	SoftwareVersion string

	// Unique device identifier.
	DeviceID string
}

Info contains the information about the device.

type LogOptions

type LogOptions struct {
	// End limits the records to ones after the given time.
	// No such limit is set when left at the zero value.
	Start time.Time

	// End limits the records to ones before the given time.
	// No such limit is set when left at the zero value.
	End time.Time

	// Max limits the number of records returned to the given amount.
	// No such limit is set when left at the zero value.
	Max int

	// NoSessions omits the zero value log records that mark the start
	// of log session markers in the result.
	NoSessions bool
}

LogOptions contains the options for the Device.DataLog method.

type Record

type Record struct {
	Time       time.Time
	PulseCount uint32
}

A Record represents an entry from the device's data log. See Device.DataLog for details.

func (Record) IsZero

func (e Record) IsZero() bool

IsZero returns true if the Record is the zero value. This indicates the start of a new logging session.

Example
package main

import (
	"fmt"
	"time"

	"go.hofstra.dev/radpro"
)

func main() {
	records := []radpro.Record{
		{},
		{Time: time.Unix(1690000000, 0), PulseCount: 1542},
		{Time: time.Unix(1690000060, 0), PulseCount: 1618},
		{Time: time.Unix(1690000120, 0), PulseCount: 1693},
	}

	for _, record := range records {
		fmt.Printf("%s: %d => is zero: %t\n",
			record.Time.UTC(), record.PulseCount, record.IsZero())
	}

}
Output:

0001-01-01 00:00:00 +0000 UTC: 0 => is zero: true
2023-07-22 04:26:40 +0000 UTC: 1542 => is zero: false
2023-07-22 04:27:40 +0000 UTC: 1618 => is zero: false
2023-07-22 04:28:40 +0000 UTC: 1693 => is zero: false
Example (DeleteFunc)
package main

import (
	"fmt"
	"slices"
	"time"

	"go.hofstra.dev/radpro"
)

func main() {
	records := []radpro.Record{
		{},
		{Time: time.Unix(1690000000, 0), PulseCount: 1542},
		{Time: time.Unix(1690000060, 0), PulseCount: 1618},
		{Time: time.Unix(1690000120, 0), PulseCount: 1693},
	}

	// Remove all zero values from the slice.
	records = slices.DeleteFunc(records, radpro.Record.IsZero)

	for _, record := range records {
		fmt.Printf("%s: %d\n", record.Time.UTC(), record.PulseCount)
	}

}
Output:

2023-07-22 04:26:40 +0000 UTC: 1542
2023-07-22 04:27:40 +0000 UTC: 1618
2023-07-22 04:28:40 +0000 UTC: 1693

Directories

Path Synopsis
Package radprotest provides utilities for testing the radpro package.
Package radprotest provides utilities for testing the radpro package.

Jump to

Keyboard shortcuts

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