147 lines
3.5 KiB
Go
147 lines
3.5 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, status 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,
|
|
Severity: int(severity),
|
|
Status: int(status),
|
|
}
|
|
|
|
// 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,
|
|
Severity: 3, // INVALID
|
|
}
|
|
select {
|
|
case ch <- v:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|