-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathfields.py
More file actions
140 lines (113 loc) · 4.95 KB
/
Copy pathfields.py
File metadata and controls
140 lines (113 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'''Access to fields within a record structure.'''
from __future__ import print_function
from ctypes import *
from .imports import get_field_offsets, get_DBF_values
from epicscorelibs.ca.dbr import *
from epicscorelibs.ca.dbr import ca_timestamp, EPICS_epoch
# Pick up the DBF_ definitions from the C helper layer. This is safer than
# defining the values here, as unfortunately the values have been known to
# change between EPICS releases.
for name, value in get_DBF_values().items():
globals()[name] = value
# Mapping from record field type to ctypes type. Type STRING is handled
# specially, and types ENUM, MENU, DEVICE and FWDLINK aren't supported.
#
# (The translation for DBF_ULONG is c_int32 rather than c_uint32 because this
# gets turned into the long Python type, which ends up causing grief
# downstream for no good purpose -- for example, enums are of type DBF_ULONG,
# but this cannot be written with caput.)
DbfCodeToCtypes = {
DBF_STRING: c_char * 40,
DBF_CHAR: c_byte,
DBF_UCHAR: c_ubyte,
DBF_SHORT: c_int16,
DBF_USHORT: c_uint16,
DBF_LONG: c_int32,
DBF_INT64: c_int64,
DBF_UINT64: c_uint64,
DBF_ULONG: c_int32, # Should be uint32, but causes trouble later.
DBF_FLOAT: c_float,
DBF_DOUBLE: c_double,
DBF_ENUM: c_uint16,
DBF_MENU: c_uint16,
DBF_INLINK: c_char_p,
DBF_OUTLINK: c_char_p,
DBF_NOACCESS: c_void_p,
}
class RecordFactory(object):
def __init__(self, record_type, fields):
'''Uses the EPICS static database to discover the offset in the record
type and the size of each of the specified fields.'''
self.fields = get_field_offsets(record_type)
missing = set(fields) - set(self.fields)
assert not missing, \
'Fields not supported by %s: %s' % (record_type, sorted(missing))
def __call__(self, record):
'''Converts a raw pointer to a record structure into a _Record object
with automatic access to fields configured in the original call to
the constructor.'''
return _Record(self.fields, record)
class _Record(object):
'''Wraps record together with field definitions.'''
def __init__(self, fields, record):
self.__dict__['fields'] = fields
self.__dict__['record'] = c_void_p(record)
# This trick allows us to pass the record back to a ctype function.
self.__dict__['_as_parameter_'] = record
def __getattr__(self, field):
offset, size, field_type = self.fields[field]
address = c_void_p(offset + self.record.value)
if field == 'TIME':
return self.__get_time(address)
elif field_type == DBF_STRING:
raw_string = string_at(cast(address, c_char_p))
return raw_string.decode(errors = 'replace')
elif field_type in [DBF_INLINK, DBF_OUTLINK]:
raw_string = cast(address, POINTER(c_char_p))[0]
return raw_string.decode(errors = 'replace')
else:
ctypes_type = DbfCodeToCtypes[field_type]
return cast(address, POINTER(ctypes_type))[0]
def __setattr__(self, field, value):
offset, size, field_type = self.fields[field]
address = c_void_p(offset + self.record.value)
if field == 'TIME':
self.__set_time(address, value)
elif field_type == DBF_STRING:
value = str(value).encode()
buffer = create_string_buffer(value)
if size > len(value) + 1:
size = len(value) + 1
memmove(address, buffer, size)
else:
ctypes_type = DbfCodeToCtypes[field_type]
cast(address, POINTER(ctypes_type))[0] = value
# Directly write EPICS compatible value to .VAL field
def write_val(self, value):
offset, size, field_type = self.fields['VAL']
address = c_void_p(offset + self.record.value)
ctypes_type = DbfCodeToCtypes[field_type]
assert sizeof(value) == size, \
'Incompatible field and value: %d %d' % (sizeof(value), size)
cast(address, POINTER(ctypes_type))[0] = value
def read_val(self):
offset, size, field_type = self.fields['VAL']
address = c_void_p(offset + self.record.value)
ctypes_type = DbfCodeToCtypes[field_type]
return cast(address, POINTER(ctypes_type)).contents
def __get_time(self, address):
timestamp = cast(address, POINTER(ca_timestamp))[0]
timestamp.secs += EPICS_epoch
return timestamp
def __set_time(self, address, new_time):
address = cast(address, POINTER(ca_timestamp))
if isinstance(new_time, ca_timestamp):
address[0] = new_time
else:
# Assume the timestamp is a floating point
address[0].secs = int(new_time)
address[0].nsec = int(1e9 * (new_time % 1.0))
# Fixup the EPICS epoch offset: this never shows outside low level
# library access.
address[0].secs -= EPICS_epoch
__all__ = ['RecordFactory', 'ca_timestamp']