Skip to content

Commit 2043822

Browse files
authored
Specific Exceptions: Adapting nican interface (+ add some typing) (#1100)
1 parent 158c17a commit 2043822

File tree

1 file changed

+98
-54
lines changed

1 file changed

+98
-54
lines changed

can/interfaces/nican.py

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@
1717
import logging
1818
import sys
1919

20-
from can import CanError, BusABC, Message
20+
from can import BusABC, Message
21+
import can.typechecking
22+
from ..exceptions import (
23+
CanError,
24+
CanInterfaceNotImplementedError,
25+
CanOperationError,
26+
CanInitializationError,
27+
)
28+
from typing import Optional, Tuple, Type
29+
2130

2231
logger = logging.getLogger(__name__)
2332

@@ -74,15 +83,48 @@ class TxMessageStruct(ctypes.Structure):
7483
]
7584

7685

77-
def check_status(result, function, arguments):
86+
class NicanError(CanError):
87+
"""Error from NI-CAN driver."""
88+
89+
def __init__(self, function, error_code: int, arguments) -> None:
90+
super().__init__(
91+
message=f"{function} failed: {get_error_message(self.error_code)}",
92+
error_code=error_code,
93+
)
94+
95+
#: Function that failed
96+
self.function = function
97+
98+
#: Arguments passed to function
99+
self.arguments = arguments
100+
101+
102+
class NicanInitializationError(NicanError, CanInitializationError):
103+
pass
104+
105+
106+
class NicanOperationError(NicanError, CanOperationError):
107+
pass
108+
109+
110+
def check_status(
111+
result: int,
112+
function,
113+
arguments,
114+
error_class: Type[NicanError] = NicanOperationError,
115+
) -> int:
78116
if result > 0:
79117
logger.warning(get_error_message(result))
80118
elif result < 0:
81-
raise NicanError(function, result, arguments)
119+
raise error_class(function, result, arguments)
82120
return result
83121

84122

85-
def get_error_message(status_code):
123+
def check_status_init(*args, **kwargs) -> int:
124+
return check_status(*args, **kwargs, error_class=NicanInitializationError)
125+
126+
127+
def get_error_message(status_code: int) -> str:
86128
"""Convert status code to descriptive string."""
87129
errmsg = ctypes.create_string_buffer(1024)
88130
nican.ncStatusToString(status_code, len(errmsg), errmsg)
@@ -102,21 +144,28 @@ def get_error_message(status_code):
102144
ctypes.c_void_p,
103145
ctypes.c_void_p,
104146
]
105-
nican.ncConfig.errcheck = check_status
147+
nican.ncConfig.errcheck = check_status_init
148+
106149
nican.ncOpenObject.argtypes = [ctypes.c_char_p, ctypes.c_void_p]
107-
nican.ncOpenObject.errcheck = check_status
150+
nican.ncOpenObject.errcheck = check_status_init
151+
108152
nican.ncCloseObject.errcheck = check_status
153+
109154
nican.ncAction.argtypes = [ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong]
110155
nican.ncAction.errcheck = check_status
156+
111157
nican.ncRead.errcheck = check_status
158+
112159
nican.ncWrite.errcheck = check_status
160+
113161
nican.ncWaitForState.argtypes = [
114162
ctypes.c_ulong,
115163
ctypes.c_ulong,
116164
ctypes.c_ulong,
117165
ctypes.c_void_p,
118166
]
119167
nican.ncWaitForState.errcheck = check_status
168+
120169
nican.ncStatusToString.argtypes = [ctypes.c_int, ctypes.c_uint, ctypes.c_char_p]
121170
else:
122171
nican = None
@@ -137,37 +186,42 @@ class NicanBus(BusABC):
137186
"""
138187

139188
def __init__(
140-
self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs
141-
):
189+
self,
190+
channel: str,
191+
can_filters: Optional[can.typechecking.CanFilters] = None,
192+
bitrate: Optional[int] = None,
193+
log_errors: bool = True,
194+
**kwargs,
195+
) -> None:
142196
"""
143-
:param str channel:
144-
Name of the object to open (e.g. 'CAN0')
197+
:param channel:
198+
Name of the object to open (e.g. `"CAN0"`)
145199
146-
:param int bitrate:
147-
Bitrate in bits/s
200+
:param bitrate:
201+
Bitrate in bit/s
148202
149-
:param list can_filters:
203+
:param can_filters:
150204
See :meth:`can.BusABC.set_filters`.
151205
152-
:param bool log_errors:
206+
:param log_errors:
153207
If True, communication errors will appear as CAN messages with
154208
``is_error_frame`` set to True and ``arbitration_id`` will identify
155209
the error (default True)
156210
157-
:raises can.interfaces.nican.NicanError:
158-
If starting communication fails
159-
211+
:raise can.CanInterfaceNotImplementedError:
212+
If the current operating system is not supported or the driver could not be loaded.
213+
:raise can.interfaces.nican.NicanInitializationError:
214+
If the bus could not be set up.
160215
"""
161216
if nican is None:
162-
raise ImportError(
217+
raise CanInterfaceNotImplementedError(
163218
"The NI-CAN driver could not be loaded. "
164219
"Check that you are using 32-bit Python on Windows."
165220
)
166221

167222
self.channel = channel
168-
self.channel_info = "NI-CAN: " + channel
169-
if not isinstance(channel, bytes):
170-
channel = channel.encode()
223+
self.channel_info = f"NI-CAN: {channel}"
224+
channel_bytes = channel.encode("ascii")
171225

172226
config = [(NC_ATTR_START_ON_OPEN, True), (NC_ATTR_LOG_COMM_ERRS, log_errors)]
173227

@@ -208,31 +262,33 @@ def __init__(
208262
attr_id_list = AttrList(*(row[0] for row in config))
209263
attr_value_list = AttrList(*(row[1] for row in config))
210264
nican.ncConfig(
211-
channel,
265+
channel_bytes,
212266
len(config),
213267
ctypes.byref(attr_id_list),
214268
ctypes.byref(attr_value_list),
215269
)
216270

217271
self.handle = ctypes.c_ulong()
218-
nican.ncOpenObject(channel, ctypes.byref(self.handle))
272+
nican.ncOpenObject(channel_bytes, ctypes.byref(self.handle))
219273

220274
super().__init__(
221275
channel=channel,
222276
can_filters=can_filters,
223277
bitrate=bitrate,
224278
log_errors=log_errors,
225-
**kwargs
279+
**kwargs,
226280
)
227281

228-
def _recv_internal(self, timeout):
282+
def _recv_internal(
283+
self, timeout: Optional[float]
284+
) -> Tuple[Optional[Message], bool]:
229285
"""
230286
Read a message from a NI-CAN bus.
231287
232-
:param float timeout:
233-
Max time to wait in seconds or None if infinite
288+
:param timeout:
289+
Max time to wait in seconds or ``None`` if infinite
234290
235-
:raises can.interfaces.nican.NicanError:
291+
:raises can.interfaces.nican.NicanOperationError:
236292
If reception fails
237293
"""
238294
if timeout is None:
@@ -274,14 +330,19 @@ def _recv_internal(self, timeout):
274330
)
275331
return msg, True
276332

277-
def send(self, msg, timeout=None):
333+
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
278334
"""
279335
Send a message to NI-CAN.
280336
281-
:param can.Message msg:
337+
:param msg:
282338
Message to send
283339
284-
:raises can.interfaces.nican.NicanError:
340+
:param timeout:
341+
The timeout
342+
343+
.. warning:: This gets ignored.
344+
345+
:raises can.interfaces.nican.NicanOperationError:
285346
If writing to transmit buffer fails.
286347
It does not wait for message to be ACKed currently.
287348
"""
@@ -299,36 +360,19 @@ def send(self, msg, timeout=None):
299360
# Maybe it is possible to use ncCreateNotification instead but seems a
300361
# bit overkill at the moment.
301362
# state = ctypes.c_ulong()
302-
# nican.ncWaitForState(
303-
# self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state))
363+
# nican.ncWaitForState(self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state))
304364

305-
def reset(self):
365+
def reset(self) -> None:
306366
"""
307367
Resets network interface. Stops network interface, then resets the CAN
308368
chip to clear the CAN error counters (clear error passive state).
309369
Resetting includes clearing all entries from read and write queues.
370+
371+
:raises can.interfaces.nican.NicanOperationError:
372+
If resetting fails.
310373
"""
311374
nican.ncAction(self.handle, NC_OP_RESET, 0)
312375

313-
def shutdown(self):
376+
def shutdown(self) -> None:
314377
"""Close object."""
315378
nican.ncCloseObject(self.handle)
316-
317-
318-
class NicanError(CanError):
319-
"""Error from NI-CAN driver."""
320-
321-
def __init__(self, function, error_code, arguments):
322-
super().__init__()
323-
#: Status code
324-
self.error_code = error_code
325-
#: Function that failed
326-
self.function = function
327-
#: Arguments passed to function
328-
self.arguments = arguments
329-
330-
def __str__(self):
331-
return "Function %s failed:\n%s" % (
332-
self.function.__name__,
333-
get_error_message(self.error_code),
334-
)

0 commit comments

Comments
 (0)