Skip to content

Commit

Permalink
improve code documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ftl committed Feb 7, 2021
1 parent 1f0cd3b commit fac8d82
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 15 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# TCI Go Client Library

This is a client library for Expert Electronic's [TCI protocol](https://github.com/maksimus1210/TCI) written in Go. It comes with a simple CLI client application that allows to send single commands to the TCI server and to monitor incoming TCI messages.
This is a client library for Expert Electronic's [TCI protocol](https://github.com/maksimus1210/TCI) written in Go. The currently supported version of TCI is 1.4.

Currently, there is no support for IQ data.
The library comes with a simple CLI client application that allows to some simple things and is mainly ment as example on how to use this library:

* monitor incoming TCI message
* send text as CW
* generate and transmit a one- or two-tone-signal for simple measurements

## Some Details About TCI

Things that I did not find in the TCI reference document and that may be useful if you also want to develop a client:

* TCI is based on a websocket connection. Commands are send as text messages, IQ data is send as binary messages.
* When you send a command you need to wait until the server confirms it by returning your command in response, before you can send the next command.
* The binary messages come in little-endian byte order.
* Audio data always comes in stereo, even the tx audio.

## Build

Expand All @@ -29,6 +35,11 @@ go install github.com/ftl/tci

For more information about how to use the CLI client application, simply run the command `tci --help`.

## Disclaimer

I am in no association with [Expert Electronics](https://eesdr.com) of any form. I develop this library on my own, in my free time, for my own leisure. I hope the
time invested will also help others. I am not liable for anything that happens to you or your equipment through the use of this software. That said - have fun!

## License
This software is published under the [MIT License](https://www.tldrlegal.com/l/mit).

Expand Down
21 changes: 18 additions & 3 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/*
The package client provides a client implementation for the TCI protocol.
*/
package client

import (
Expand All @@ -15,10 +18,10 @@ import (
// DefaultPort of TCI
const DefaultPort = 40001

// DefaultTimeout is the defaultduration to wait for a reply to a command.
// DefaultTimeout is the default duration to wait for the reply of a command.
var DefaultTimeout = time.Duration(50 * time.Millisecond)

// ErrTimeout indicates a timeout while waiting for a reply to command.
// ErrTimeout indicates a timeout while waiting for the reply of a command.
var ErrTimeout = errors.New("timeout")

// ErrNotConnected indicates that there is currently no TCI connection available.
Expand Down Expand Up @@ -84,7 +87,8 @@ func newClient(host *net.TCPAddr, listeners []interface{}) *Client {
return result
}

// Open a connection to the given host
// Open a connection to the given host. The given listeners are notified about any incoming message.
// Open returns as soon as the READY; message was received.
func Open(host *net.TCPAddr, listeners ...interface{}) (*Client, error) {
client := newClient(host, listeners)
err := client.connect()
Expand All @@ -95,6 +99,11 @@ func Open(host *net.TCPAddr, listeners ...interface{}) (*Client, error) {
return client, nil
}

// KeepOpen opens a connection to the given host and tries to keep an open connection by automatically
// trying to reconnect when an established connection is lost (after the given grace period). The given
// listeners are notified about any incoming message.
// KeepOpen returns immediately. If you want to know when the connection is available, add a ConnectionListener to the
// list of listeners.
func KeepOpen(host *net.TCPAddr, retryInterval time.Duration, listeners ...interface{}) *Client {
client := newClient(host, listeners)
go func() {
Expand Down Expand Up @@ -271,6 +280,7 @@ func (c *Client) writeLoop(conn clientConn, incoming <-chan Message) {
}
}

// Ready handles the READY; message coming from the TCI host. Should not be called from outside!
func (c *Client) Ready() {
select {
case <-c.ready:
Expand All @@ -279,6 +289,7 @@ func (c *Client) Ready() {
}
}

// Connected indicates if there is currently a TCI connection established.
func (c *Client) Connected() bool {
if c.disconnectChan == nil {
return false
Expand All @@ -291,6 +302,8 @@ func (c *Client) Connected() bool {
}
}

// Disconnect the TCI connection. If this client was created using KeepOpen, the automatic
// retry stops and the client stays disconnected.
func (c *Client) Disconnect() {
// When the connection was disconnected from the outside, we keep it closed.
select {
Expand All @@ -310,6 +323,7 @@ func (c *Client) Disconnect() {
}
}

// WhenDisconnected calls the given function when the client gets disconnected.
func (c *Client) WhenDisconnected(f func()) {
if c.disconnectChan == nil {
f()
Expand Down Expand Up @@ -339,6 +353,7 @@ func (c *Client) command(cmd string, args ...interface{}) (Message, error) {
}

// SendTXAudio sends the given samples as reply to a TXChrono message.
// The samples need to be in stereo, i.e. channel 1 and channel 2 interleaved.
func (c *Client) SendTXAudio(trx int, sampleRate AudioSampleRate, samples []float32) error {
msg, err := NewTXAudioMessage(trx, sampleRate, samples)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions client/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package client

import "fmt"

// VFO represents a VFO in TCI.
// VFO represents a VFO in TCI. In the TCI documentation the VFOs area also named "channel".
type VFO int

// All available VFOs in TCI.
Expand All @@ -14,7 +14,7 @@ const (
// Mode represents the modulation type of a TRX.
type Mode string

// All available modes in TCI.
// All modes acvailable in TCI.
const (
ModeAM = Mode("am")
ModeSAM = Mode("sam")
Expand All @@ -40,7 +40,7 @@ const (
SignalSourceVAC = SignalSource("vac")
)

// IQSampleRate represents the sample rate for IQ data in Hz.
// IQSampleRate represents the sample rate for IQ data in samples per second.
type IQSampleRate int

// All available sample rates for IQ data.
Expand All @@ -50,7 +50,7 @@ const (
IQSampleRate192k = IQSampleRate(192000)
)

// AudioSampleRate represents the sample rate for audio data in Hz.
// AudioSampleRate represents the sample rate for audio data in samples per second.
type AudioSampleRate int

// All available sample rates for audio data.
Expand Down
14 changes: 12 additions & 2 deletions client/device.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package client

// DeviceInfo contains the basic information about the SunSDR device which are transmitted when the TCI connection is established.
// This information cannot be requested through TCI, so it is automatically collected by the client and provided through this type.
type DeviceInfo struct {
Name string
DeviceName string
ProtocolName string
ProtocolVersion string
MinVFOFrequency int
Expand All @@ -14,37 +16,45 @@ type DeviceInfo struct {
Modes []Mode
}

// SetDeviceName handles the DEVICE message.
func (d *DeviceInfo) SetDeviceName(name string) {
d.Name = name
d.DeviceName = name
}

// SetProtocol handles the PROTOCOL message.
func (d *DeviceInfo) SetProtocol(name string, version string) {
d.ProtocolName = name
d.ProtocolVersion = version
}

// SetVFOLimits handles the VFO_LIMITS message.
func (d *DeviceInfo) SetVFOLimits(min int, max int) {
d.MinVFOFrequency = min
d.MaxVFOFrequency = max
}

// SetIFLimits handles the IF_LIMITS message.
func (d *DeviceInfo) SetIFLimits(min int, max int) {
d.MinIFFrequency = min
d.MaxIFFrequency = max
}

// SetTRXCount handles the TRX_COUNT message.
func (d *DeviceInfo) SetTRXCount(count int) {
d.TRXCount = count
}

// SetChannelCount handles the CHANNEL_COUNT message.
func (d *DeviceInfo) SetChannelCount(count int) {
d.ChannelCount = count
}

// SetRXOnly handles the RECEIVE_ONLY message.
func (d *DeviceInfo) SetRXOnly(value bool) {
d.RXOnly = value
}

// SetModes handles the MODULATIONS_LIST message.
func (d *DeviceInfo) SetModes(modes []Mode) {
d.Modes = modes
}
6 changes: 5 additions & 1 deletion client/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

var messageExp = regexp.MustCompile(`(?P<name>[A-Za-z_]+)(:(?P<args>[A-Za-z0-9-.]+(,[A-Za-z0-9-.]+)*))?;`)

// ParseMessage parses the given string as a TCI message.
// ParseMessage interprets the given string as a TCI message.
func ParseTextMessage(s string) (Message, error) {
matches := messageExp.FindStringSubmatch(s)
if len(matches) == 0 {
Expand Down Expand Up @@ -117,6 +117,7 @@ func (m Message) ToFloat(i int) (float64, error) {
}

// NewTXAudioMessage returns a binary message of type TXAudioStream that contains the given samples.
// The binary message can directly be send through a websocket connection to the TCI server.
func NewTXAudioMessage(trx int, sampleRate AudioSampleRate, samples []float32) ([]byte, error) {
msg := &encodedBinaryMessage{
TRX: uint32(trx),
Expand Down Expand Up @@ -184,6 +185,7 @@ type encodedBinaryMessage struct {
Reserved [9]uint32
}

// BinaryMessage represents a binary message that is exchanged between the TCI server and a client.
type BinaryMessage struct {
TRX int
SampleRate int
Expand All @@ -195,8 +197,10 @@ type BinaryMessage struct {
Data []float32
}

// BinaryMessageType represents the type of a BinaryMessage
type BinaryMessageType uint32

// All message types available in TCI.
const (
IQStreamMessage BinaryMessageType = iota
RXAudioStreamMessage
Expand Down
Loading

0 comments on commit fac8d82

Please sign in to comment.