Initial working fully go release

This commit is contained in:
Martino Ferrari
2026-04-30 23:01:01 +02:00
parent 6e51ffc5e1
commit 90669c5fd6
22 changed files with 2950 additions and 196 deletions
+46 -55
View File
@@ -1,12 +1,12 @@
// Command catools provides caget/caput/camonitor/cainfo equivalents using
// the pure-Go goca CA client library.
// Command catools provides CA (Channel Access) command-line utilities using
// the pure-Go goca library.
//
// Usage:
//
// catools caget <pv> [pv...] # get current value(s)
// catools caput <pv> <value> # write a value
// catools cainfo <pv> [pv...] # print metadata (units, limits, type)
// catools camonitor <pv> [pv...] # stream live updates (Ctrl-C to stop)
// catools get <pv> [pv...] Get current value(s)
// catools put <pv> <value> Write a value
// catools monitor <pv> [pv...] Stream live updates (Ctrl-C to stop)
// catools info <pv> [pv...] Print metadata (type, units, limits)
//
// Environment:
//
@@ -30,13 +30,13 @@ import (
)
func usage() {
fmt.Fprintln(os.Stderr, `catools — pure-Go EPICS CA command-line tools
fmt.Fprintln(os.Stderr, `catools — EPICS Channel Access command-line tool
Commands:
caget <pv> [pv...] Get current value(s)
caput <pv> <value> Write a value
cainfo <pv> [pv...] Print metadata (type, units, limits)
camonitor <pv> [pv...] Stream live updates (Ctrl-C to stop)
Usage:
catools get <pv> [pv...] Get current value(s)
catools put <pv> <value> Write a value
catools monitor <pv> [pv...] Stream live updates (Ctrl-C to stop)
catools info <pv> [pv...] Print metadata (type, units, limits)
Environment:
EPICS_CA_ADDR_LIST Space-separated CA server addresses
@@ -63,36 +63,36 @@ func main() {
}
switch cmd {
case "caget":
case "get":
if len(args) == 0 {
fatalf("caget: no PV names specified")
fatalf("get: no PV names specified")
}
runCaget(ctx, cli, args)
case "caput":
runGet(ctx, cli, args)
case "put":
if len(args) < 2 {
fatalf("caput: usage: catools caput <pv> <value>")
fatalf("put: usage: catools put <pv> <value>")
}
runCaput(ctx, cli, args[0], args[1])
case "cainfo":
runPut(ctx, cli, args[0], args[1])
case "info":
if len(args) == 0 {
fatalf("cainfo: no PV names specified")
fatalf("info: no PV names specified")
}
runCainfo(ctx, cli, args)
case "camonitor":
runInfo(ctx, cli, args)
case "monitor":
if len(args) == 0 {
fatalf("camonitor: no PV names specified")
fatalf("monitor: no PV names specified")
}
runCamonitor(ctx, cli, args)
runMonitor(ctx, cli, args)
default:
fatalf("unknown command %q — use caget, caput, cainfo, or camonitor", cmd)
fatalf("unknown command %q — use get, put, monitor, or info", cmd)
}
}
// -------------------------------------------------------------------------- //
// caget //
// get //
// -------------------------------------------------------------------------- //
func runCaget(ctx context.Context, cli *ca.Client, pvs []string) {
func runGet(ctx context.Context, cli *ca.Client, pvs []string) {
tctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
@@ -107,14 +107,14 @@ func runCaget(ctx context.Context, cli *ca.Client, pvs []string) {
}
// -------------------------------------------------------------------------- //
// caput //
// put //
// -------------------------------------------------------------------------- //
func runCaput(ctx context.Context, cli *ca.Client, pv, valueStr string) {
func runPut(ctx context.Context, cli *ca.Client, pv, valueStr string) {
tctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// First get metadata to determine the right type.
// Get metadata to determine the right type.
ci, err := cli.GetCtrl(tctx, pv)
if err != nil {
fatalf("%s: get metadata: %v", pv, err)
@@ -125,22 +125,18 @@ func runCaput(ctx context.Context, cli *ca.Client, pv, valueStr string) {
case proto.DBFString:
value = valueStr
case proto.DBFEnum:
// Try numeric first, then string.
if n, err := strconv.ParseInt(valueStr, 10, 32); err == nil {
value = int32(n)
} else {
// Search enum strings.
if ci.Enum != nil {
for i, s := range ci.Enum.Strings {
if strings.EqualFold(s, valueStr) {
value = int32(i)
break
}
} else if ci.Enum != nil {
for i, s := range ci.Enum.Strings {
if strings.EqualFold(s, valueStr) {
value = int32(i)
break
}
}
if value == nil {
fatalf("%s: unknown enum value %q", pv, valueStr)
}
}
if value == nil {
fatalf("%s: unknown enum value %q", pv, valueStr)
}
default:
if n, err := strconv.ParseFloat(valueStr, 64); err == nil {
@@ -156,20 +152,19 @@ func runCaput(ctx context.Context, cli *ca.Client, pv, valueStr string) {
fatalf("%s: put: %v", pv, err)
}
// Read back the new value.
tv, err := cli.Get(tctx, pv)
if err != nil {
fmt.Printf("Old: ?\nNew: %s (write sent, readback failed: %v)\n", valueStr, err)
fmt.Printf("%-30s %s (write sent, readback failed: %v)\n", pv, valueStr, err)
return
}
fmt.Printf("%-30s %s\n", pv, formatValue(tv))
}
// -------------------------------------------------------------------------- //
// cainfo //
// info //
// -------------------------------------------------------------------------- //
func runCainfo(ctx context.Context, cli *ca.Client, pvs []string) {
func runInfo(ctx context.Context, cli *ca.Client, pvs []string) {
tctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
@@ -220,10 +215,10 @@ func printInfo(pv string, ci ca.CtrlInfo) {
}
// -------------------------------------------------------------------------- //
// camonitor //
// monitor //
// -------------------------------------------------------------------------- //
func runCamonitor(ctx context.Context, cli *ca.Client, pvs []string) {
func runMonitor(ctx context.Context, cli *ca.Client, pvs []string) {
type entry struct {
pv string
ch chan proto.TimeValue
@@ -249,8 +244,6 @@ func runCamonitor(ctx context.Context, cli *ca.Client, pvs []string) {
return
}
// Fan out: one goroutine per PV, print to stdout.
done := make(chan struct{})
for _, e := range entries {
go func(e *entry) {
for {
@@ -269,8 +262,6 @@ func runCamonitor(ctx context.Context, cli *ca.Client, pvs []string) {
}
<-ctx.Done()
close(done)
for _, e := range entries {
e.can()
}
@@ -283,14 +274,14 @@ func runCamonitor(ctx context.Context, cli *ca.Client, pvs []string) {
func formatValue(tv proto.TimeValue) string {
var val string
switch {
case tv.Doubles != nil:
case tv.Str != "":
val = tv.Str
case len(tv.Doubles) > 1:
parts := make([]string, len(tv.Doubles))
for i, v := range tv.Doubles {
parts[i] = strconv.FormatFloat(v, 'g', -1, 64)
}
val = "[" + strings.Join(parts, " ") + "]"
case tv.Str != "":
val = tv.Str
default:
val = strconv.FormatFloat(tv.Double, 'g', -1, 64)
}