add commands to control the radio terminal

master
Florian Thienel 3 years ago
parent a20458a904
commit 8d6b674382

@ -211,6 +211,10 @@ func (c *COM) ClearSyntaxErrors(ctx context.Context) error {
return nil
}
func (c *COM) Request(ctx context.Context, request string) ([]string, error) {
return c.AT(ctx, request)
}
func (c *COM) AT(ctx context.Context, request string) ([]string, error) {
cmd := command{
request: request,

@ -0,0 +1,68 @@
package ctrl
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/ftl/tetra-pei/tetra"
)
// SetOperatingMode according to [PEI] 6.14.7.2
func SetOperatingMode(mode AIMode) string {
return fmt.Sprintf("AT+CTOM=%d", mode)
}
var requestOperatingModeResponse = regexp.MustCompile(`^\+CTOM: (\d+)$`)
// RequestOperatingMode reads the current operating mode according to [PEI] 6.14.7.4
func RequestOperatingMode(ctx context.Context, requester tetra.Requester) (AIMode, error) {
responses, err := requester.Request(ctx, "AT+CTOM?")
if err != nil {
return 0, err
}
if len(responses) < 1 {
return 0, fmt.Errorf("no response received")
}
response := strings.ToUpper(strings.TrimSpace(responses[0]))
parts := requestOperatingModeResponse.FindStringSubmatch(response)
if len(parts) != 2 {
return 0, fmt.Errorf("unexpected response: %s", responses[0])
}
result, err := strconv.Atoi(parts[1])
if err != nil {
return 0, err
}
return AIMode(result), nil
}
// SetTalkgroup according to [PEI] 6.15.6.2
func SetTalkgroup(gtsi string) string {
return fmt.Sprintf("AT+CTGS=1,%s", gtsi)
}
var requestTalkgroupResponse = regexp.MustCompile(`^\+CTGS: .*,(\d+)$`)
// RequestTalkgroup reads the current talkgroup according to [PEI] 6.15.6.4
func RequestTalkgroup(ctx context.Context, requester tetra.Requester) (string, error) {
responses, err := requester.Request(ctx, "AT+CTGS?")
if err != nil {
return "", err
}
if len(responses) < 1 {
return "", fmt.Errorf("no response received")
}
response := strings.ToUpper(strings.TrimSpace(responses[0]))
parts := requestTalkgroupResponse.FindStringSubmatch(response)
if len(parts) != 2 {
return "", fmt.Errorf("unexpected response: %s", responses[0])
}
return parts[1], nil
}

@ -0,0 +1,40 @@
package ctrl
import (
"fmt"
"strings"
)
// AIModeByName returns the AIMode with the given name
func AIModeByName(name string) (AIMode, error) {
sanitized := strings.ToUpper(strings.TrimSpace(name))
result, ok := AIModesByName[sanitized]
if !ok {
return 0, fmt.Errorf("invalid operating mode %s", name)
}
return result, nil
}
// AIMode represents an operating mode according to [PEI] 6.17.4
type AIMode byte
func (m AIMode) String() string {
for k, v := range AIModesByName {
if v == m {
return k
}
}
return "UNKNOWN"
}
// All supported operating modes
const (
TMO AIMode = iota
DMO
)
// AIModesByName maps all supported operating modes by their string representation
var AIModesByName = map[string]AIMode{
"TMO": TMO,
"DMO": DMO,
}

@ -20,16 +20,6 @@ func (f EncoderFunc) Encode() ([]byte, int) {
return f()
}
type Requester interface {
Request(context.Context, string) ([]string, error)
}
type RequesterFunc func(context.Context, string) ([]string, error)
func (f RequesterFunc) Request(ctx context.Context, request string) ([]string, error) {
return f(ctx, request)
}
const (
// CRLF line ending for AT commands
CRLF = "\x0d\x0a"
@ -53,7 +43,7 @@ func SendMessage(destination tetra.Identity, message Encoder) string {
var sendMessageDescription = regexp.MustCompile(`^\+CMGS: .+\(\d*-(\d*)\)$`)
// RequestMaxMessagePDUBits uses the given RequesterFunc to find out how many bits a message PDU may have (see [PEI] 6.13.2).
func RequestMaxMessagePDUBits(ctx context.Context, requester Requester) (int, error) {
func RequestMaxMessagePDUBits(ctx context.Context, requester tetra.Requester) (int, error) {
responses, err := requester.Request(ctx, "AT+CMGS=?")
if err != nil {
return 0, err

@ -1,11 +1,25 @@
package tetra
import (
"context"
"encoding/hex"
"regexp"
"strings"
)
// Requester is used for commands that return more than an error code.
type Requester interface {
Request(context.Context, string) ([]string, error)
}
// RequesterFunc wraps a match function into the Requester interface.
type RequesterFunc func(context.Context, string) ([]string, error)
// Request calls the wrapped RequesterFunc.
func (f RequesterFunc) Request(ctx context.Context, request string) ([]string, error) {
return f(ctx, request)
}
// Identity represents an identity of a party in a TETRA communication
type Identity string

Loading…
Cancel
Save