Files
uopi/internal/datasource/epics/ca_callback.go
T
2026-04-27 12:42:10 +02:00

140 lines
3.4 KiB
Go

//go:build epics
package epics
/*
#cgo CFLAGS: -Wno-unused-function
#include "ca_wrapper.h"
*/
import "C"
import (
"time"
"unsafe"
"github.com/uopi/uopi/internal/datasource"
)
// goCAMonitorCallback is called from the C shim caMonitorCallbackShim on every
// CA monitor event. It runs on CA's internal callback thread, so it must be
// short and must not block. In particular it must not allocate heap memory
// that the Go GC would need to track across a CGo boundary.
//
//export goCAMonitorCallback
func goCAMonitorCallback(handle C.uintptr_t, dbrType C.int, count C.long,
dbr unsafe.Pointer, severity C.int, epicsTimeSecs C.double) {
h := uintptr(handle)
// Compute the Go timestamp. EPICS timestamps are seconds since the EPICS
// epoch (1 Jan 1990 UTC).
const epicsEpochOffset = 631152000 // Unix seconds of 1990-01-01 00:00:00 UTC
secs := float64(epicsTimeSecs)
fullSecs := int64(secs)
nsec := int64((secs - float64(fullSecs)) * 1e9)
ts := time.Unix(fullSecs+epicsEpochOffset, nsec)
// Map CA alarm severity to datasource quality.
var q datasource.Quality
switch int(severity) {
case 0: // NO_ALARM
q = datasource.QualityGood
case 1, 2: // MINOR_ALARM, MAJOR_ALARM
q = datasource.QualityUncertain
default: // INVALID_ALARM (3)
q = datasource.QualityBad
}
// Extract the value from the DBR buffer.
var data any
switch int(dbrType) {
case int(C.DBR_TIME_DOUBLE):
p := (*C.struct_dbr_time_double)(dbr)
data = float64(p.value)
case int(C.DBR_TIME_FLOAT):
p := (*C.struct_dbr_time_float)(dbr)
data = float64(p.value)
case int(C.DBR_TIME_LONG):
p := (*C.struct_dbr_time_long)(dbr)
data = int64(p.value)
case int(C.DBR_TIME_SHORT):
p := (*C.struct_dbr_time_short)(dbr)
data = int64(p.value)
case int(C.DBR_TIME_ENUM):
p := (*C.struct_dbr_time_enum)(dbr)
data = int64(p.value)
case int(C.DBR_TIME_STRING):
p := (*C.struct_dbr_time_string)(dbr)
// dbr_string is a fixed [MAX_STRING_SIZE]byte array
data = C.GoString((*C.char)(unsafe.Pointer(&p.value[0])))
default:
// Unknown type; mark as bad quality and return early.
q = datasource.QualityBad
data = float64(0)
}
v := datasource.Value{Timestamp: ts, Data: data, Quality: q}
// Look up the subscription channel under the global handle table lock and
// perform a non-blocking send so we never stall the CA callback thread.
handleMu.Lock()
ch, ok := handleTable[h]
handleMu.Unlock()
if ok {
select {
case ch <- v:
default:
// Subscriber is not reading fast enough; drop the update rather than
// block the CA callback thread.
}
}
}
// goCAConnectionCallback is called from the C shim caConnectionCallbackShim
// whenever a channel connects or disconnects.
//
//export goCAConnectionCallback
func goCAConnectionCallback(handle C.uintptr_t, connected C.int) {
h := uintptr(handle)
handleMu.Lock()
entry, ok := connTable[h]
handleMu.Unlock()
if !ok {
return
}
if int(connected) == 1 {
// Signal the one-shot "connected" channel if it has not been closed yet.
select {
case entry.connCh <- struct{}{}:
default:
}
} else {
// Channel disconnected; push a QualityBad sentinel to the subscriber.
handleMu.Lock()
ch, chOk := handleTable[h]
handleMu.Unlock()
if chOk {
v := datasource.Value{
Timestamp: time.Now(),
Data: float64(0),
Quality: datasource.QualityBad,
}
select {
case ch <- v:
default:
}
}
}
}