//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: } } } }