1""" Helper functions for RapidCode Python samples.
7from _imports
import RapidCode, RAPIDCODE_DIR, constants, platform
24 """True only for a real console: isatty() is also True for the NUL device on Windows."""
27 mode = ctypes.c_uint()
28 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
29 return bool(ctypes.windll.kernel32.GetConsoleMode(handle, ctypes.byref(mode)))
33 """stdin is not a terminal (automated test run): never reports a key press."""
38 """Windows console: msvcrt.kbhit() reports a pending key press without blocking."""
39 return _msvcrt.kbhit()
43 """Linux/macOS terminal: select() with a zero timeout polls stdin without blocking.
44 Note the terminal is line-buffered by default, so this reports the key after Enter."""
45 readable, _, _ = _select.select([sys.stdin], [], [], 0)
51except (AttributeError, OSError, ValueError):
52 stdin_is_interactive =
False
54if not stdin_is_interactive:
55 key_pressed = _key_pressed_no_terminal
56elif platform.system() ==
"Windows":
57 import msvcrt
as _msvcrt
58 key_pressed = _key_pressed_windows
60 import select
as _select
61 key_pressed = _key_pressed_posix
72AL_STATUS_ERROR_SUFFIX =
"+ERR"
76 """Decode one node's AL Status register to its state name, e.g. OP or SAFEOP+ERR."""
77 state_name = AL_STATUS_STATES.get(al_status & 0x0F,
"?")
79 state_name += AL_STATUS_ERROR_SUFFIX
84 """Decode the network-wide AL Status (NetworkStatus.AlStatus), the OR of every node's
85 register, to the states present, e.g. OP or SAFEOP|OP+ERR.
87 Only the single-bit states are unambiguous when ORed: BOOT (3) is INIT (1) | PREOP (2),
88 so a network showing INIT|PREOP may hold a node in BOOT."""
89 states_present = [name
for bit, name
in ((0x01,
"INIT"), (0x02,
"PREOP"), (0x04,
"SAFEOP"), (0x08,
"OP"))
91 state_name =
"|".join(states_present)
if states_present
else "NONE"
93 state_name += AL_STATUS_ERROR_SUFFIX
98 """Get enum name using reflection on SWIG-generated constants.
101 prefix: The enum prefix (e.g., "RSINetworkState_RSINetworkState")
102 value: The enum value to look up
105 The enum name with prefix stripped, or "UNKNOWN(value)" if not found
108 get_enum_name("RSINetworkState_RSINetworkState", controller.NetworkStateGet())
109 # Returns "OPERATIONAL" for RSINetworkState_RSINetworkStateOPERATIONAL
111 for name
in dir(RapidCode):
112 if name.startswith(prefix):
113 if getattr(RapidCode, name) == value:
114 return name[len(prefix):]
115 return f
"UNKNOWN({value})"
118def get_creation_parameters():
121 creation_params: RapidCode.CreationParameters = RapidCode.CreationParameters()
122 creation_params.RmpPath = RAPIDCODE_DIR
123 creation_params.NicPrimary = constants.RMP_NIC_PRIMARY
125 if platform.system() ==
"Windows":
126 creation_params.NodeName = constants.RMP_NODE_NAME
127 elif platform.system() ==
"Linux":
128 creation_params.CpuAffinity = constants.RMP_CPU_AFFINITY
130 raise Exception(
"Unsupported platform")
132 return creation_params
134def check_errors(rsi_object):
138 error_string_builder =
""
139 i = rsi_object.ErrorLogCountGet()
140 while rsi_object.ErrorLogCountGet() > 0:
141 error:RapidCode.RsiError = rsi_object.ErrorLogGet()
142 error_type =
"WARNING" if error.isWarning
else "ERROR"
143 error_string_builder += f
"{error_type}: {error.text}\n"
144 if len(error_string_builder) > 0:
145 print(error_string_builder)
146 if "ERROR" in error_string_builder:
147 raise Exception(error_string_builder)
148 return "ERROR" in error_string_builder, error_string_builder
150def start_the_network(controller):
154 if controller.NetworkStateGet() != RapidCode.RSINetworkState_RSINetworkStateOPERATIONAL:
155 print(
"Starting Network..")
156 controller.NetworkStart()
158 if controller.NetworkStateGet() != RapidCode.RSINetworkState_RSINetworkStateOPERATIONAL:
159 start_error = controller.LastNetworkStartErrorGet()
161 "Network start error: "
162 f
"{enum_to_name(start_error, 'RSINetworkStartError')} ({start_error})"
165 messages_to_read = controller.NetworkLogMessageCountGet()
167 for i
in range(messages_to_read):
168 print(controller.NetworkLogMessageGet(i))
169 print(
"Expected OPERATIONAL state but the network did not get there.")
172 print(
"Network Started")
174def abort_motion_object(motion_object):
178 motion_object.EStopAbort()
179 motion_object.MotionDoneWait()
180 motion_object.ClearFaults()
183 verify_idle_state(motion_object)
185def verify_idle_state(motion_object):
188 if motion_object.StateGet() != RapidCode.RSIState_RSIStateIDLE:
189 source = motion_object.SourceGet()
190 error_msg = f
"Axis or MultiAxis {motion_object.NumberGet()} is expected to be in IDLE state, but is in state {enum_to_name(motion_object.StateGet(), 'RSIState')}. " \
191 f
"\nError Source: {motion_object.SourceNameGet(source)}"
192 raise Exception(error_msg)
195 """Reverse lookup: int value -> enum name"""
196 for name
in dir(RapidCode):
197 if name.startswith(prefix +
"_" + prefix)
and getattr(RapidCode, name) == value:
198 return name.split(prefix +
"_" + prefix)[1]
204_FIRMWARE_VALUE_ATTRIBUTES = {
206 "INT8":
"Int8",
"UINT8":
"UInt8",
207 "INT16":
"Int16",
"UINT16":
"UInt16",
"SHORT":
"Int16",
"USHORT":
"UInt16",
208 "INT32":
"Int32",
"UINT32":
"UInt32",
"MASK32":
"UInt32",
209 "INT64":
"Int64",
"UINT64":
"UInt64",
"MASK64":
"UInt64",
210 "FLOAT":
"Float",
"DOUBLE":
"Double",
215 """Name of the FirmwareValue attribute that holds a value of the given RSIDataType.
216 Look it up once per address (it reflects over the RapidCode module), then read many
217 values with getattr(firmware_value, attribute)."""
220 return _FIRMWARE_VALUE_ATTRIBUTES[data_type_name]
222 raise ValueError(f
"RSIDataType {data_type_name} cannot be read from a FirmwareValue")
from None
226 """The cheapest way to read one recorded value of the given RSIDataType from Python.
228 Returns a callable reader(recorder_number, record_index, data_index) for use after
229 RecorderRecordDataRetrieveBulk. Each RecorderRecordData*Get call crosses the SWIG
230 boundary once, and that crossing dominates the cost of draining a Recorder from Python:
231 RecorderRecordDataValueGet / RecorderRecordDataDoubleGet return a plain number (about
232 0.65 us per value), while RecorderRecordDataFirmwareValueGet returns a FirmwareValue
233 object that then needs an attribute read (about 1.3 us). A large Recorder set (many nodes,
234 every sample) only keeps up with the plain getters.
236 The Recorder copies 8 bytes from every address. Types up to 32 bits use ValueGet (the
237 low 32 bits) and are masked to their width and, if signed, sign-extended, so the bytes
238 next to a narrow value do not leak in; FLOAT reinterprets those 32 bits. DOUBLE uses
239 DoubleGet. 64-bit integer types use DoubleGet and reinterpret the 8 bytes, which is exact
240 unless the value's top 12 bits are all set (a NaN bit pattern): counters never get there.
243 value_get = controller.RecorderRecordDataValueGet
244 double_get = controller.RecorderRecordDataDoubleGet
245 if data_type_name ==
"INT32":
247 if data_type_name
in (
"UINT32",
"MASK32"):
248 return lambda recorder, record, index: value_get(recorder, record, index) & 0xFFFFFFFF
249 if data_type_name ==
"BOOL":
250 return lambda recorder, record, index: bool(value_get(recorder, record, index) & 0xFF)
251 unsigned_widths = {
"UINT8": 8,
"UINT16": 16,
"USHORT": 16}
252 if data_type_name
in unsigned_widths:
253 mask = (1 << unsigned_widths[data_type_name]) - 1
254 return lambda recorder, record, index: value_get(recorder, record, index) & mask
255 signed_widths = {
"INT8": 8,
"INT16": 16,
"SHORT": 16}
256 if data_type_name
in signed_widths:
257 mask = (1 << signed_widths[data_type_name]) - 1
258 sign_bit = 1 << (signed_widths[data_type_name] - 1)
259 return lambda recorder, record, index: ((value_get(recorder, record, index) & mask) ^ sign_bit) - sign_bit
260 if data_type_name ==
"FLOAT":
261 return lambda recorder, record, index: struct.unpack(
"<f", struct.pack(
"<I", value_get(recorder, record, index) & 0xFFFFFFFF))[0]
262 if data_type_name ==
"DOUBLE":
264 if data_type_name
in (
"UINT64",
"MASK64"):
265 return lambda recorder, record, index: struct.unpack(
"<Q", struct.pack(
"<d", double_get(recorder, record, index)))[0]
266 if data_type_name ==
"INT64":
267 return lambda recorder, record, index: struct.unpack(
"<q", struct.pack(
"<d", double_get(recorder, record, index)))[0]
268 raise ValueError(f
"RSIDataType {data_type_name} cannot be read from a Recorder record")
272 """Read a FirmwareValue (a 64-bit union) as the Python value of the given RSIDataType.
274 Pair it with the AddressDataTypeGet family (Axis, MultiAxis, MotionController,
275 RapidCodeNetworkNode) so recorded or interrupt user data is interpreted with the
276 type the firmware actually stores at that address, e.g.:
278 data_type = node.AddressDataTypeGet(RapidCode.RSINetworkNodeAddressType_RSINetworkNodeAddressTypeAL_STATUS)
279 value = helpers.firmware_value_get(controller.RecorderRecordDataFirmwareValueGet(0), data_type)
bool _stdin_is_console_windows()
str firmware_value_attribute(data_type)
enum_to_name(value, prefix)
recorder_value_reader(controller, data_type)
bool _key_pressed_no_terminal()
bool _key_pressed_windows()
firmware_value_get(firmware_value, data_type)
str get_enum_name(str prefix, int value)
bool _key_pressed_posix()
str decode_al_status_ored(int al_status)
str decode_al_status(int al_status)