Initial go port of epics
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// epicsEpochOffset is the number of Unix seconds between the Unix epoch
|
||||
// (1970-01-01 00:00:00 UTC) and the EPICS epoch (1990-01-01 00:00:00 UTC).
|
||||
const epicsEpochOffset = 631152000
|
||||
|
||||
// maxStringSize is the fixed size of a CA string value (MAX_STRING_SIZE).
|
||||
const maxStringSize = 40
|
||||
|
||||
// maxEnumStates is the maximum number of enum strings (MAX_ENUM_STATES).
|
||||
const maxEnumStates = 16
|
||||
|
||||
// maxEnumStringSize is the fixed size of each enum string (MAX_ENUM_STRING_SIZE).
|
||||
const maxEnumStringSize = 26
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Alarm types //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
// AlarmSeverity mirrors epicsAlarmSeverity.
|
||||
type AlarmSeverity int
|
||||
|
||||
const (
|
||||
SeverityNone AlarmSeverity = 0
|
||||
SeverityMinor AlarmSeverity = 1
|
||||
SeverityMajor AlarmSeverity = 2
|
||||
SeverityInvalid AlarmSeverity = 3
|
||||
)
|
||||
|
||||
func (s AlarmSeverity) String() string {
|
||||
switch s {
|
||||
case SeverityNone:
|
||||
return "NO_ALARM"
|
||||
case SeverityMinor:
|
||||
return "MINOR"
|
||||
case SeverityMajor:
|
||||
return "MAJOR"
|
||||
case SeverityInvalid:
|
||||
return "INVALID"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// TimeValue — decoded DBR_TIME_* payload //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
// TimeValue is the decoded form of any DBR_TIME_* payload.
|
||||
// Exactly one of the value fields is populated based on the DBR type.
|
||||
type TimeValue struct {
|
||||
Timestamp time.Time
|
||||
Status int16
|
||||
Severity AlarmSeverity
|
||||
// Value fields — only one is set.
|
||||
Double float64
|
||||
Float float32
|
||||
Long int32
|
||||
Short int16
|
||||
Enum uint16
|
||||
Char uint8
|
||||
Str string
|
||||
// Waveform values (when element count > 1).
|
||||
Doubles []float64
|
||||
}
|
||||
|
||||
// decodeTimestamp converts EPICS secPastEpoch+nsec to a Go time.Time.
|
||||
func decodeTimestamp(secPastEpoch, nsec uint32) time.Time {
|
||||
return time.Unix(int64(secPastEpoch)+epicsEpochOffset, int64(nsec)).UTC()
|
||||
}
|
||||
|
||||
// caString extracts a null-terminated string from a fixed-size byte slice.
|
||||
func caString(b []byte) string {
|
||||
idx := strings.IndexByte(string(b), 0)
|
||||
if idx < 0 {
|
||||
return string(b)
|
||||
}
|
||||
return string(b[:idx])
|
||||
}
|
||||
|
||||
// DBR_TIME_* wire layouts (all big-endian):
|
||||
//
|
||||
// Common header (12 bytes):
|
||||
// [0:2] int16 status
|
||||
// [2:4] int16 severity
|
||||
// [4:8] uint32 secPastEpoch
|
||||
// [8:12] uint32 nsec
|
||||
//
|
||||
// DBR_TIME_DOUBLE (type 25): 4-byte RISC pad at [12:16], float64 at [16:24]. Total=24.
|
||||
// DBR_TIME_FLOAT (type 20): float32 at [12:16]. Total=16.
|
||||
// DBR_TIME_LONG (type 22): int32 at [12:16]. Total=16.
|
||||
// DBR_TIME_SHORT (type 19): int16 at [12:14], 2-byte pad [14:16]. Total=16.
|
||||
// DBR_TIME_ENUM (type 23): uint16 at [12:14], 2-byte pad [14:16]. Total=16.
|
||||
// DBR_TIME_CHAR (type 24): uint8 at [12:13], 3-byte pad [13:16]. Total=16.
|
||||
// DBR_TIME_STRING (type 21): [40]byte at [12:52]. Total=52.
|
||||
|
||||
// DecodeTimeValue decodes a DBR_TIME_* payload.
|
||||
// dbrType is one of the DBRTime* constants; payload is the full message payload
|
||||
// (may include multiple elements for waveforms; count is the element count).
|
||||
func DecodeTimeValue(dbrType uint16, count uint32, payload []byte) (TimeValue, bool) {
|
||||
if len(payload) < 12 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
|
||||
status := int16(binary.BigEndian.Uint16(payload[0:]))
|
||||
severity := AlarmSeverity(binary.BigEndian.Uint16(payload[2:]))
|
||||
sec := binary.BigEndian.Uint32(payload[4:])
|
||||
nsec := binary.BigEndian.Uint32(payload[8:])
|
||||
|
||||
tv := TimeValue{
|
||||
Timestamp: decodeTimestamp(sec, nsec),
|
||||
Status: status,
|
||||
Severity: severity,
|
||||
}
|
||||
|
||||
switch dbrType {
|
||||
case DBRTimeDouble:
|
||||
// 4-byte RISC pad at [12:16], value at [16:24]
|
||||
if count > 1 {
|
||||
if len(payload) < 16+int(count)*8 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
vals := make([]float64, count)
|
||||
for i := range vals {
|
||||
bits := binary.BigEndian.Uint64(payload[16+i*8:])
|
||||
vals[i] = math.Float64frombits(bits)
|
||||
}
|
||||
tv.Doubles = vals
|
||||
tv.Double = vals[0]
|
||||
} else {
|
||||
if len(payload) < 24 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
bits := binary.BigEndian.Uint64(payload[16:])
|
||||
tv.Double = math.Float64frombits(bits)
|
||||
}
|
||||
|
||||
case DBRTimeFloat:
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
bits := binary.BigEndian.Uint32(payload[12:])
|
||||
tv.Float = math.Float32frombits(bits)
|
||||
tv.Double = float64(tv.Float)
|
||||
|
||||
case DBRTimeLong:
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Long = int32(binary.BigEndian.Uint32(payload[12:]))
|
||||
tv.Double = float64(tv.Long)
|
||||
|
||||
case DBRTimeShort:
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Short = int16(binary.BigEndian.Uint16(payload[12:]))
|
||||
tv.Double = float64(tv.Short)
|
||||
|
||||
case DBRTimeEnum:
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Enum = binary.BigEndian.Uint16(payload[12:])
|
||||
tv.Double = float64(tv.Enum)
|
||||
|
||||
case DBRTimeChar:
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Char = payload[12]
|
||||
tv.Double = float64(tv.Char)
|
||||
|
||||
case DBRTimeString:
|
||||
if len(payload) < 12+maxStringSize {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Str = caString(payload[12 : 12+maxStringSize])
|
||||
tv.Double = 0
|
||||
|
||||
default:
|
||||
return TimeValue{}, false
|
||||
}
|
||||
|
||||
return tv, true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// CtrlDouble — decoded DBR_CTRL_DOUBLE payload (type 34) //
|
||||
// -------------------------------------------------------------------------- //
|
||||
//
|
||||
// Wire layout (88 bytes total, all big-endian):
|
||||
//
|
||||
// [0:2] int16 status
|
||||
// [2:4] int16 severity
|
||||
// [4:6] int16 precision
|
||||
// [6:8] uint16 RISC_pad0
|
||||
// [8:16] [8]byte units
|
||||
// [16:24] float64 upper_disp_limit
|
||||
// [24:32] float64 lower_disp_limit
|
||||
// [32:40] float64 upper_alarm_limit
|
||||
// [40:48] float64 upper_warning_limit
|
||||
// [48:56] float64 lower_warning_limit
|
||||
// [56:64] float64 lower_alarm_limit
|
||||
// [64:72] float64 upper_ctrl_limit
|
||||
// [72:80] float64 lower_ctrl_limit
|
||||
// [80:88] float64 value
|
||||
|
||||
// CtrlDouble is the decoded form of a DBR_CTRL_DOUBLE payload.
|
||||
type CtrlDouble struct {
|
||||
Status int16
|
||||
Severity AlarmSeverity
|
||||
Precision int16
|
||||
Units string
|
||||
UpperDispLimit float64
|
||||
LowerDispLimit float64
|
||||
UpperAlarmLimit float64
|
||||
UpperWarnLimit float64
|
||||
LowerWarnLimit float64
|
||||
LowerAlarmLimit float64
|
||||
UpperCtrlLimit float64
|
||||
LowerCtrlLimit float64
|
||||
Value float64
|
||||
}
|
||||
|
||||
// DecodeCtrlDouble decodes a DBR_CTRL_DOUBLE payload (minimum 88 bytes).
|
||||
func DecodeCtrlDouble(p []byte) (CtrlDouble, bool) {
|
||||
if len(p) < 88 {
|
||||
return CtrlDouble{}, false
|
||||
}
|
||||
f64 := func(off int) float64 {
|
||||
return math.Float64frombits(binary.BigEndian.Uint64(p[off:]))
|
||||
}
|
||||
return CtrlDouble{
|
||||
Status: int16(binary.BigEndian.Uint16(p[0:])),
|
||||
Severity: AlarmSeverity(binary.BigEndian.Uint16(p[2:])),
|
||||
Precision: int16(binary.BigEndian.Uint16(p[4:])),
|
||||
Units: caString(p[8:16]),
|
||||
UpperDispLimit: f64(16),
|
||||
LowerDispLimit: f64(24),
|
||||
UpperAlarmLimit: f64(32),
|
||||
UpperWarnLimit: f64(40),
|
||||
LowerWarnLimit: f64(48),
|
||||
LowerAlarmLimit: f64(56),
|
||||
UpperCtrlLimit: f64(64),
|
||||
LowerCtrlLimit: f64(72),
|
||||
Value: f64(80),
|
||||
}, true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// CtrlLong — decoded DBR_CTRL_LONG payload (type 33) //
|
||||
// -------------------------------------------------------------------------- //
|
||||
//
|
||||
// Wire layout (48 bytes total):
|
||||
//
|
||||
// [0:2] int16 status
|
||||
// [2:4] int16 severity
|
||||
// [4:12] [8]byte units
|
||||
// [12:16] int32 upper_disp_limit
|
||||
// [16:20] int32 lower_disp_limit
|
||||
// [20:24] int32 upper_alarm_limit
|
||||
// [24:28] int32 upper_warning_limit
|
||||
// [28:32] int32 lower_warning_limit
|
||||
// [32:36] int32 lower_alarm_limit
|
||||
// [36:40] int32 upper_ctrl_limit
|
||||
// [40:44] int32 lower_ctrl_limit
|
||||
// [44:48] int32 value
|
||||
|
||||
// CtrlLong is the decoded form of a DBR_CTRL_LONG payload.
|
||||
type CtrlLong struct {
|
||||
Status int16
|
||||
Severity AlarmSeverity
|
||||
Units string
|
||||
UpperDispLimit int32
|
||||
LowerDispLimit int32
|
||||
UpperAlarmLimit int32
|
||||
UpperWarnLimit int32
|
||||
LowerWarnLimit int32
|
||||
LowerAlarmLimit int32
|
||||
UpperCtrlLimit int32
|
||||
LowerCtrlLimit int32
|
||||
Value int32
|
||||
}
|
||||
|
||||
// DecodeCtrlLong decodes a DBR_CTRL_LONG payload (minimum 48 bytes).
|
||||
func DecodeCtrlLong(p []byte) (CtrlLong, bool) {
|
||||
if len(p) < 48 {
|
||||
return CtrlLong{}, false
|
||||
}
|
||||
i32 := func(off int) int32 {
|
||||
return int32(binary.BigEndian.Uint32(p[off:]))
|
||||
}
|
||||
return CtrlLong{
|
||||
Status: int16(binary.BigEndian.Uint16(p[0:])),
|
||||
Severity: AlarmSeverity(binary.BigEndian.Uint16(p[2:])),
|
||||
Units: caString(p[4:12]),
|
||||
UpperDispLimit: i32(12),
|
||||
LowerDispLimit: i32(16),
|
||||
UpperAlarmLimit: i32(20),
|
||||
UpperWarnLimit: i32(24),
|
||||
LowerWarnLimit: i32(28),
|
||||
LowerAlarmLimit: i32(32),
|
||||
UpperCtrlLimit: i32(36),
|
||||
LowerCtrlLimit: i32(40),
|
||||
Value: i32(44),
|
||||
}, true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// CtrlEnum — decoded DBR_CTRL_ENUM payload (type 31) //
|
||||
// -------------------------------------------------------------------------- //
|
||||
//
|
||||
// Wire layout (424 bytes total):
|
||||
//
|
||||
// [0:2] int16 status
|
||||
// [2:4] int16 severity
|
||||
// [4:6] int16 no_str (number of valid enum strings, max 16)
|
||||
// [6:422] [16][26]byte strs (enum string table)
|
||||
// [422:424] uint16 value
|
||||
|
||||
// CtrlEnum is the decoded form of a DBR_CTRL_ENUM payload.
|
||||
type CtrlEnum struct {
|
||||
Status int16
|
||||
Severity AlarmSeverity
|
||||
Strings []string // len == no_str
|
||||
Value uint16
|
||||
}
|
||||
|
||||
const ctrlEnumSize = 2 + 2 + 2 + maxEnumStates*maxEnumStringSize + 2 // 424
|
||||
|
||||
// DecodeCtrlEnum decodes a DBR_CTRL_ENUM payload (minimum 424 bytes).
|
||||
func DecodeCtrlEnum(p []byte) (CtrlEnum, bool) {
|
||||
if len(p) < ctrlEnumSize {
|
||||
return CtrlEnum{}, false
|
||||
}
|
||||
noStr := int(binary.BigEndian.Uint16(p[4:]))
|
||||
noStr = min(noStr, maxEnumStates)
|
||||
strs := make([]string, noStr)
|
||||
for i := range strs {
|
||||
off := 6 + i*maxEnumStringSize
|
||||
strs[i] = caString(p[off : off+maxEnumStringSize])
|
||||
}
|
||||
return CtrlEnum{
|
||||
Status: int16(binary.BigEndian.Uint16(p[0:])),
|
||||
Severity: AlarmSeverity(binary.BigEndian.Uint16(p[2:])),
|
||||
Strings: strs,
|
||||
Value: binary.BigEndian.Uint16(p[422:]),
|
||||
}, true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// CtrlString — decoded DBR_CTRL_STRING payload (type 28) //
|
||||
// -------------------------------------------------------------------------- //
|
||||
//
|
||||
// Wire layout (44 bytes):
|
||||
//
|
||||
// [0:2] int16 status
|
||||
// [2:4] int16 severity
|
||||
// [4:44] [40]byte value
|
||||
|
||||
// CtrlString is the decoded form of a DBR_CTRL_STRING payload.
|
||||
type CtrlString struct {
|
||||
Status int16
|
||||
Severity AlarmSeverity
|
||||
Value string
|
||||
}
|
||||
|
||||
// DecodeCtrlString decodes a DBR_CTRL_STRING payload (minimum 44 bytes).
|
||||
func DecodeCtrlString(p []byte) (CtrlString, bool) {
|
||||
if len(p) < 4+maxStringSize {
|
||||
return CtrlString{}, false
|
||||
}
|
||||
return CtrlString{
|
||||
Status: int16(binary.BigEndian.Uint16(p[0:])),
|
||||
Severity: AlarmSeverity(binary.BigEndian.Uint16(p[2:])),
|
||||
Value: caString(p[4 : 4+maxStringSize]),
|
||||
}, true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Put payload encoders //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
// EncodeDouble encodes a float64 value as a big-endian DBR_DOUBLE payload
|
||||
// padded to 8 bytes.
|
||||
func EncodeDouble(v float64) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, math.Float64bits(v))
|
||||
return b
|
||||
}
|
||||
|
||||
// EncodeLong encodes an int32 value as a big-endian DBR_LONG payload
|
||||
// padded to 8 bytes.
|
||||
func EncodeLong(v int32) []byte {
|
||||
b := make([]byte, 8) // 4 bytes value + 4 bytes pad
|
||||
binary.BigEndian.PutUint32(b, uint32(v))
|
||||
return b
|
||||
}
|
||||
|
||||
// EncodeShort encodes an int16 value as a big-endian DBR_SHORT payload
|
||||
// padded to 8 bytes.
|
||||
func EncodeShort(v int16) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint16(b, uint16(v))
|
||||
return b
|
||||
}
|
||||
|
||||
// EncodeString encodes a string as a null-terminated, 8-byte padded
|
||||
// DBR_STRING payload (capped at maxStringSize characters).
|
||||
func EncodeString(v string) []byte {
|
||||
if len(v) >= maxStringSize {
|
||||
v = v[:maxStringSize-1]
|
||||
}
|
||||
b := make([]byte, maxStringSize)
|
||||
copy(b, v)
|
||||
return PadBytes(b)
|
||||
}
|
||||
|
||||
// EncodeEventMask builds the 16-byte payload for a CA_PROTO_EVENT_ADD request.
|
||||
// mask is typically DBEDefault (DBEValue | DBEAlarm).
|
||||
//
|
||||
// Wire layout (16 bytes, all big-endian):
|
||||
//
|
||||
// [0:4] float32 m_lval (not used, zero)
|
||||
// [4:8] float32 p_delta (delta trigger, zero = disabled)
|
||||
// [8:12] float32 p_final (final trigger, zero = disabled)
|
||||
// [12:14] int16 p_count (element count, zero = use PV's count)
|
||||
// [14:16] int16 m_mask (DBE_VALUE | DBE_ALARM | ...)
|
||||
func EncodeEventMask(mask uint16) []byte {
|
||||
b := make([]byte, 16)
|
||||
binary.BigEndian.PutUint16(b[14:], mask) // m_mask is at offset 14, not 12
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user