python-ipmi 0.5.8__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. pyipmi/__init__.py +90 -72
  2. pyipmi/bmc.py +192 -23
  3. pyipmi/chassis.py +204 -60
  4. pyipmi/constants.py +35 -0
  5. pyipmi/dcmi.py +351 -14
  6. pyipmi/errors.py +34 -22
  7. pyipmi/event.py +62 -3
  8. pyipmi/fields.py +33 -28
  9. pyipmi/fru.py +1099 -114
  10. pyipmi/helper.py +50 -16
  11. pyipmi/hpm.py +908 -187
  12. pyipmi/interfaces/__init__.py +14 -4
  13. pyipmi/interfaces/aardvark.py +119 -177
  14. pyipmi/interfaces/base.py +87 -0
  15. pyipmi/interfaces/ipmb.py +248 -42
  16. pyipmi/interfaces/ipmbdev.py +46 -162
  17. pyipmi/interfaces/ipmidev.py +244 -0
  18. pyipmi/interfaces/ipmitool.py +157 -89
  19. pyipmi/interfaces/mock.py +14 -13
  20. pyipmi/interfaces/openipmblink.py +312 -0
  21. pyipmi/interfaces/rmcp.py +219 -195
  22. pyipmi/interfaces/rmcpplus.py +570 -0
  23. pyipmi/interfaces/router.py +242 -0
  24. pyipmi/ipmitool.py +1113 -428
  25. pyipmi/lan.py +251 -76
  26. pyipmi/logger.py +4 -4
  27. pyipmi/messaging.py +207 -18
  28. pyipmi/mixin.py +52 -0
  29. pyipmi/msgs/__init__.py +0 -1
  30. pyipmi/msgs/constants.py +98 -0
  31. pyipmi/msgs/dcmi.py +177 -19
  32. pyipmi/msgs/device_messaging.py +34 -0
  33. pyipmi/msgs/fru.py +3 -1
  34. pyipmi/msgs/hpm.py +7 -7
  35. pyipmi/msgs/message.py +119 -83
  36. pyipmi/msgs/picmg.py +7 -5
  37. pyipmi/msgs/registry.py +18 -9
  38. pyipmi/msgs/sel.py +4 -4
  39. pyipmi/msgs/vita.py +0 -1
  40. pyipmi/picmg.py +490 -52
  41. pyipmi/sdr.py +583 -94
  42. pyipmi/sel.py +185 -20
  43. pyipmi/sensor.py +163 -38
  44. pyipmi/session.py +58 -34
  45. pyipmi/state.py +13 -5
  46. pyipmi/utils.py +60 -55
  47. pyipmi/version.py +1 -1
  48. pyipmi/vita.py +281 -0
  49. python_ipmi-0.6.0.dist-info/METADATA +312 -0
  50. python_ipmi-0.6.0.dist-info/RECORD +62 -0
  51. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.0.dist-info}/WHEEL +1 -1
  52. pyipmi/emulation.py +0 -395
  53. python_ipmi-0.5.8.dist-info/METADATA +0 -207
  54. python_ipmi-0.5.8.dist-info/RECORD +0 -56
  55. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.0.dist-info}/entry_points.txt +0 -0
  56. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.0.dist-info}/licenses/AUTHORS +0 -0
  57. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.0.dist-info}/licenses/COPYING +0 -0
  58. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.0.dist-info}/top_level.txt +0 -0
pyipmi/__init__.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  # Copyright (c) 2014 Kontron Europe GmbH
3
2
  #
4
3
  # This library is free software; you can redistribute it and/or
@@ -15,10 +14,11 @@
15
14
  # License along with this library; if not, write to the Free Software
16
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
16
 
18
- from __future__ import absolute_import
17
+ from __future__ import annotations
19
18
 
20
19
  import time
21
20
  import ast
21
+ from typing import Any, Literal
22
22
 
23
23
  from . import bmc
24
24
  from . import chassis
@@ -27,42 +27,45 @@ from . import event
27
27
  from . import fru
28
28
  from . import hpm
29
29
  from . import lan
30
+ from . import logger # noqa: F401 - installs the NullHandler
30
31
  from . import messaging
31
32
  from . import picmg
32
33
  from . import sdr
33
34
  from . import sel
34
35
  from . import sensor
36
+ from . import vita
35
37
  from . import msgs
36
38
 
37
39
  from .errors import IpmiTimeoutError, CompletionCodeError, RetryError
40
+ from .msgs import Message
38
41
  from .msgs.registry import create_request_by_name
39
42
  from .session import Session
40
43
  from .utils import check_rsp_completion_code, is_string
41
44
 
42
45
  try:
43
- from version import __version__
46
+ from .version import __version__
44
47
  except ImportError:
45
48
  __version__ = 'dev'
46
49
 
47
50
 
48
- def create_connection(interface):
51
+ def create_connection(interface: Any) -> Ipmi:
49
52
  session = Session()
50
53
  session.interface = interface
51
54
  return Ipmi(interface=interface, session=session)
52
55
 
53
56
 
54
- class Requester(object):
57
+ class Requester:
55
58
  """The Requester class.
56
59
 
57
60
  This represents an IPMI device which initiates a request/response
58
61
  message exchange.
59
62
  """
60
63
 
61
- def __init__(self, ipmb_address):
64
+ def __init__(self, ipmb_address: int) -> None:
62
65
  self.ipmb_address = ipmb_address
63
66
 
64
67
 
65
- class NullRequester(object):
68
+ class NullRequester:
66
69
  """The NullRequester class.
67
70
 
68
71
  This requester is used for interfaces which doesn't require a valid
@@ -70,31 +73,32 @@ class NullRequester(object):
70
73
  """
71
74
 
72
75
  @property
73
- def ipmb_address(self):
76
+ def ipmb_address(self) -> int:
74
77
  raise AssertionError('NullRequester does not provide an IPMB address')
75
78
 
76
79
 
77
- class Routing(object):
80
+ class Routing:
78
81
  """The Target class represents an IPMI target."""
79
82
 
80
- def __init__(self, rq_sa, rs_sa, channel):
83
+ def __init__(self, rq_sa: int, rs_sa: int, channel: int | None) -> None:
81
84
  self.rq_sa = rq_sa
82
85
  self.rs_sa = rs_sa
83
86
  self.channel = channel
84
87
 
85
- def __str__(self):
88
+ def __str__(self) -> str:
86
89
  s = 'Routing: Rq: %s Rs: %s Ch: %s' \
87
90
  % (self.rq_sa, self.rs_sa, self.channel)
88
91
  return s
89
92
 
90
93
 
91
- class Target(object):
94
+ class Target:
92
95
  """The Target class represents an IPMI target."""
93
96
 
94
- routing = None
95
- ipmb_address = None
97
+ routing: list[Routing] | None = None
98
+ ipmb_address: int | None = None
96
99
 
97
- def __init__(self, ipmb_address=None, routing=None):
100
+ def __init__(self, ipmb_address: int | None = None,
101
+ routing: str | list[tuple] | None = None) -> None:
98
102
  """Initializer for the Target class.
99
103
 
100
104
  `ipmb_address` is the IPMB target address
@@ -107,49 +111,57 @@ class Target(object):
107
111
  if routing:
108
112
  self.set_routing(routing)
109
113
 
110
- def set_routing_information(self, routing):
114
+ def set_routing_information(self, routing: str | list[tuple]) -> None:
111
115
  self.set_routing(routing)
112
116
 
113
- def set_routing(self, routing):
117
+ def set_routing(self, routing: str | list[tuple]) -> None:
114
118
  """Set the path over which a target is reachable.
115
119
 
116
- The path is given as a list of tuples in the form (address,
117
- bridge_channel).
120
+ Each hop of the path is a tuple ``(rq_sa, rs_sa, channel)``: the
121
+ requester address, the responder address and the channel of the
122
+ bridge to the next hop. The channel of the last hop is None.
118
123
 
119
- Example #1: access to an ATCA blade in a chassis
120
- slave = 0x81, target = 0x82
121
- routing = [(0x81,0x20,0),(0x20,0x82,None)]
124
+ Args:
125
+ routing: The list of hops, or its string representation as
126
+ given on the command line.
122
127
 
123
- Example #2: access to an AMC in a uTCA chassis
124
- slave = 0x81, target = 0x72
125
- routing = [(0x81,0x20,0),(0x20,0x82,7),(0x20,0x72,None)]
128
+ Example #1, access to an ATCA blade in a chassis (slave 0x81,
129
+ target 0x82)::
126
130
 
131
+ routing = [(0x81, 0x20, 0), (0x20, 0x82, None)]
127
132
 
128
- uTCA - MCH AMC
129
- .-------------------. .--------.
130
- | .-----------| | |
131
- | ShMC | CM | | MMC |
132
- channel=0 | | | channel=7 | |
133
- 81 ------------| 0x20 |0x82 0x20 |-------------| 0x72 |
134
- | | | | |
135
- | | | | |
136
- | `-----------| | |
137
- `-------------------´ `--------´
138
- `------------´ `---´ `---------------´
133
+ Example #2, access to an AMC in a uTCA chassis (slave 0x81,
134
+ target 0x72)::
139
135
 
140
- Example #3: access to an AMC in a ATCA AMC carrier
136
+ routing = [(0x81, 0x20, 0), (0x20, 0x82, 7), (0x20, 0x72, None)]
141
137
 
142
- slave = 0x81, target = 0x72
143
- routing = [(0x81,0x20,0),(0x20,0x8e,7),(0x20,0x80,None)]
138
+ uTCA - MCH AMC
139
+ .-------------------. .--------.
140
+ | .-----------| | |
141
+ | ShMC | CM | | MMC |
142
+ channel=0 | | | channel=7 | |
143
+ 81 ------------| 0x20 |0x82 0x20 |-------------| 0x72 |
144
+ | | | | |
145
+ | | | | |
146
+ | `-----------| | |
147
+ `-------------------´ `--------´
148
+ `------------´ `---´ `---------------´
144
149
 
150
+ Example #3, access to an AMC in an ATCA AMC carrier (slave 0x81,
151
+ target 0x72)::
152
+
153
+ routing = [(0x81, 0x20, 0), (0x20, 0x8e, 7), (0x20, 0x80, None)]
145
154
  """
146
155
  if is_string(routing):
147
156
  # if type(routing) in [unicode, str]:
148
157
  routing = ast.literal_eval(routing)
149
158
  self.routing = [Routing(*route) for route in routing]
150
159
 
151
- def __str__(self):
152
- string = 'Target: IPMB: 0x%02x\n' % self.ipmb_address
160
+ def __str__(self) -> str:
161
+ if self.ipmb_address is None:
162
+ string = 'Target: IPMB: none\n'
163
+ else:
164
+ string = 'Target: IPMB: 0x%02x\n' % self.ipmb_address
153
165
  if self.routing:
154
166
  for route in self.routing:
155
167
  string += ' %s\n' % route
@@ -158,10 +170,11 @@ class Target(object):
158
170
 
159
171
  class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
160
172
  sdr.Sdr, sensor.Sensor, event.Event, sel.Sel, lan.Lan,
161
- messaging.Messaging):
173
+ messaging.Messaging, vita.Vita):
162
174
 
163
- def __init__(self, interface=None, target=None, session=Session(),
164
- requester=NullRequester()):
175
+ def __init__(self, interface: Any = None, target: Target | None = None,
176
+ session: Session | None = None,
177
+ requester: Any = None) -> None:
165
178
  self._interface = interface
166
179
 
167
180
  # we need a session, set if not passed
@@ -172,33 +185,35 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
172
185
  self._session.interface = interface
173
186
 
174
187
  self._target = target
175
- self.requester = requester
188
+ self.requester = requester if requester is not None else NullRequester()
176
189
 
177
190
  for base in Ipmi.__bases__:
178
- base.__init__(self)
191
+ base.__init__(self) # type: ignore[misc]
179
192
 
180
- def __enter__(self):
193
+ def __enter__(self) -> Ipmi:
181
194
  self.open()
182
195
  return self
183
196
 
184
- def __exit__(self, exception_type, exception_value, traceback):
197
+ def __exit__(self, exception_type: Any, exception_value: Any,
198
+ traceback: Any) -> Literal[False]:
185
199
  self.close()
186
200
  return False
187
201
 
188
- def open(self):
202
+ def open(self) -> None:
189
203
  self.interface.open()
190
204
  if self.session is not None:
191
205
  self.session.establish()
192
206
 
193
- def close(self):
207
+ def close(self) -> None:
194
208
  if self.session is not None:
195
209
  self.session.close()
196
210
  self.interface.close()
197
211
 
198
- def is_ipmc_accessible(self):
212
+ def is_ipmc_accessible(self) -> bool:
199
213
  return self.interface.is_ipmc_accessible(self.target)
200
214
 
201
- def wait_until_ipmb_is_accessible(self, timeout, interval=0.25):
215
+ def wait_until_ipmb_is_accessible(self, timeout: float,
216
+ interval: float = 0.25) -> None:
202
217
  start_time = time.time()
203
218
  while time.time() < start_time + (timeout):
204
219
  try:
@@ -208,7 +223,7 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
208
223
 
209
224
  self.is_ipmc_accessible()
210
225
 
211
- def send_message(self, req, retry=3):
226
+ def send_message(self, req: Message, retry: int = 3) -> Message:
212
227
  req.target = self.target
213
228
  req.requester = self.requester
214
229
  rsp = None
@@ -226,7 +241,8 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
226
241
 
227
242
  return rsp
228
243
 
229
- def send_message_with_name(self, name, *args, **kwargs):
244
+ def send_message_with_name(self, name: str, *args: Any,
245
+ **kwargs: Any) -> Message:
230
246
  req = create_request_by_name(name)
231
247
 
232
248
  for key, value in kwargs.items():
@@ -236,7 +252,7 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
236
252
  check_rsp_completion_code(rsp)
237
253
  return rsp
238
254
 
239
- def raw_command(self, lun, netfn, raw_bytes):
255
+ def raw_command(self, lun: int, netfn: int, raw_bytes: bytes) -> bytes:
240
256
  """Send the raw command data and return the raw response.
241
257
 
242
258
  lun: the logical unit number
@@ -248,33 +264,35 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
248
264
  return self.interface.send_and_receive_raw(self.target, lun, netfn,
249
265
  raw_bytes)
250
266
 
251
- def _get_interface(self):
267
+ @property
268
+ def interface(self) -> Any:
252
269
  try:
253
270
  return self._interface
254
271
  except AttributeError:
255
- raise RuntimeError('No interface has been set')
272
+ raise RuntimeError('No interface has been set') from None
256
273
 
257
- def _get_session(self):
274
+ @interface.setter
275
+ def interface(self, interface: Any) -> None:
276
+ self._interface = interface
277
+
278
+ @property
279
+ def session(self) -> Session:
258
280
  try:
259
281
  return self._session
260
282
  except AttributeError:
261
- raise RuntimeError('No IPMI session has been set')
283
+ raise RuntimeError('No IPMI session has been set') from None
284
+
285
+ @session.setter
286
+ def session(self, session: Session) -> None:
287
+ self._session = session
262
288
 
263
- def _get_target(self):
289
+ @property
290
+ def target(self) -> Target | None:
264
291
  try:
265
292
  return self._target
266
293
  except AttributeError:
267
- raise RuntimeError('No IPMI target has been set')
268
-
269
- def _set_interface(self, interface):
270
- self._interface = interface
294
+ raise RuntimeError('No IPMI target has been set') from None
271
295
 
272
- def _set_session(self, session):
273
- self._session = session
274
-
275
- def _set_target(self, target):
296
+ @target.setter
297
+ def target(self, target: Target | None) -> None:
276
298
  self._target = target
277
-
278
- target = property(_get_target, _set_target)
279
- interface = property(_get_interface, _set_interface)
280
- session = property(_get_session, _set_session)
pyipmi/bmc.py CHANGED
@@ -14,27 +14,80 @@
14
14
  # License along with this library; if not, write to the Free Software
15
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
- from .msgs import create_request_by_name
17
+ """BMC device commands: device ID, resets, I2C access and watchdog timer.
18
+
19
+ The commands are the methods of :class:`Bmc`, which are available on
20
+ :class:`pyipmi.Ipmi`.
21
+
22
+ Example:
23
+ Print the device ID and the firmware version of the BMC::
24
+
25
+ device_id = ipmi.get_device_id()
26
+ print(device_id.device_id, device_id.fw_revision)
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from array import array
32
+
33
+ from .msgs import create_request_by_name, Message
18
34
  from .utils import check_completion_code
19
35
  from .state import State
20
36
  from .fields import VersionField
37
+ from .constants import manufacturer_name
38
+ from .mixin import IpmiMixin
39
+
40
+
41
+ class Bmc(IpmiMixin):
42
+ """BMC device commands, available on :class:`pyipmi.Ipmi`."""
21
43
 
44
+ def get_device_id(self) -> DeviceId:
45
+ """Get the device ID of the controller.
22
46
 
23
- class Bmc(object):
24
- def get_device_id(self):
47
+ Returns:
48
+ The device ID, firmware version, IPMI version and supported
49
+ functions of the controller.
50
+ """
25
51
  return DeviceId(self.send_message_with_name('GetDeviceId'))
26
52
 
27
- def get_device_guid(self):
53
+ def get_device_guid(self) -> DeviceGuid:
54
+ """Get the GUID of the controller.
55
+
56
+ Returns:
57
+ The device GUID.
58
+ """
28
59
  return DeviceGuid(self.send_message_with_name('GetDeviceGuid'))
29
60
 
30
- def cold_reset(self):
61
+ def cold_reset(self) -> None:
62
+ """Cold reset the controller, it is reinitialized."""
31
63
  self.send_message_with_name('ColdReset')
32
64
 
33
- def warm_reset(self):
65
+ def warm_reset(self) -> None:
66
+ """Warm reset the controller, its state is kept."""
34
67
  self.send_message_with_name('WarmReset')
35
68
 
36
- def i2c_write_read(self, bus_type, bus_id, channel, address, count,
37
- data=None):
69
+ def i2c_write_read(self, bus_type: int, bus_id: int, channel: int,
70
+ address: int, count: int,
71
+ data: bytes | None = None) -> array:
72
+ """Write to and read from a device on an I2C bus of the controller.
73
+
74
+ The data is written first, then ``count`` bytes are read.
75
+
76
+ Args:
77
+ bus_type: 0 for a public bus (IPMB), 1 for a private bus.
78
+ bus_id: The bus ID.
79
+ channel: The channel number of a public bus.
80
+ address: The 7-bit I2C address of the device.
81
+ count: The number of bytes to read.
82
+ data: The data to write, nothing is written if None or empty.
83
+
84
+ Returns:
85
+ The data read.
86
+
87
+ Raises:
88
+ CompletionCodeError: The controller rejected the request, e.g.
89
+ because the device did not acknowledge.
90
+ """
38
91
  req = create_request_by_name('MasterWriteRead')
39
92
  req.bus_id.type = bus_type
40
93
  req.bus_id.id = bus_id
@@ -47,14 +100,55 @@ class Bmc(object):
47
100
  check_completion_code(rsp.completion_code)
48
101
  return rsp.data
49
102
 
50
- def i2c_write(self, bus_type, bus_id, channel, address, data):
103
+ def i2c_write(self, bus_type: int, bus_id: int, channel: int,
104
+ address: int, data: bytes) -> None:
105
+ """Write to a device on an I2C bus of the controller.
106
+
107
+ Args:
108
+ bus_type: 0 for a public bus (IPMB), 1 for a private bus.
109
+ bus_id: The bus ID.
110
+ channel: The channel number of a public bus.
111
+ address: The 7-bit I2C address of the device.
112
+ data: The data to write.
113
+
114
+ Raises:
115
+ CompletionCodeError: The controller rejected the request.
116
+ """
51
117
  self.i2c_write_read(bus_type, bus_id, channel, address, 0, data)
52
118
 
53
- def i2c_read(self, bus_type, bus_id, channel, address, count):
119
+ def i2c_read(self, bus_type: int, bus_id: int, channel: int,
120
+ address: int, count: int) -> array:
121
+ """Read from a device on an I2C bus of the controller.
122
+
123
+ Args:
124
+ bus_type: 0 for a public bus (IPMB), 1 for a private bus.
125
+ bus_id: The bus ID.
126
+ channel: The channel number of a public bus.
127
+ address: The 7-bit I2C address of the device.
128
+ count: The number of bytes to read.
129
+
130
+ Returns:
131
+ The data read.
132
+
133
+ Raises:
134
+ CompletionCodeError: The controller rejected the request.
135
+ """
54
136
  return self.i2c_write_read(bus_type, bus_id, channel,
55
137
  address, count, None)
56
138
 
57
- def set_watchdog_timer(self, config):
139
+ def set_watchdog_timer(self, config: Watchdog) -> None:
140
+ """Set the watchdog timer.
141
+
142
+ The timer is started with :meth:`reset_watchdog_timer`.
143
+
144
+ Args:
145
+ config: The settings of the watchdog timer. All attributes
146
+ except ``is_running`` and ``present_countdown`` have to be
147
+ set.
148
+
149
+ Raises:
150
+ CompletionCodeError: The controller rejected the settings.
151
+ """
58
152
  req = create_request_by_name('SetWatchdogTimer')
59
153
  req.timer_use.timer_use = config.timer_use
60
154
  req.timer_use.dont_stop = config.dont_stop and 1 or 0
@@ -69,14 +163,49 @@ class Bmc(object):
69
163
  rsp = self.send_message(req)
70
164
  check_completion_code(rsp.completion_code)
71
165
 
72
- def get_watchdog_timer(self):
166
+ def get_watchdog_timer(self) -> Watchdog:
167
+ """Get the settings and the present countdown of the watchdog timer.
168
+
169
+ Returns:
170
+ The watchdog timer, ``dont_stop`` is not set.
171
+ """
73
172
  return Watchdog(self.send_message_with_name('GetWatchdogTimer'))
74
173
 
75
- def reset_watchdog_timer(self):
174
+ def reset_watchdog_timer(self) -> None:
175
+ """Start or restart the watchdog timer with its initial countdown.
176
+
177
+ Raises:
178
+ CompletionCodeError: The timer was not set before (0x80).
179
+ """
76
180
  self.send_message_with_name('ResetWatchdogTimer')
77
181
 
78
182
 
79
183
  class Watchdog(State):
184
+ """The settings of the watchdog timer.
185
+
186
+ The ``TIMER_USE_*`` and ``TIMEOUT_ACTION_*`` constants are the timer
187
+ uses and the timeout actions.
188
+
189
+ Attributes:
190
+ timer_use (int): The use of the timer, one of the ``TIMER_USE_*``
191
+ constants.
192
+ is_running (bool): Only returned: the timer is running.
193
+ dont_log (bool): Don't log the timer expiration in the SEL.
194
+ pre_timeout_interrupt (int): The interrupt before the timeout
195
+ action: 0 none, 1 SMI, 2 NMI / diagnostic interrupt, 3 messaging
196
+ interrupt.
197
+ timeout_action (int): The action on the timer expiration, one of
198
+ the ``TIMEOUT_ACTION_*`` constants.
199
+ pre_timeout_interval (int): The time in seconds before the timeout
200
+ action at which the pre-timeout interrupt is generated.
201
+ timer_use_expiration_flags (int): A bit mask with one bit per timer
202
+ use, bit 1 for BIOS FRB2 up to bit 5 for OEM: the expired timer
203
+ uses when returned, the flags to clear when set.
204
+ initial_countdown (int): The countdown value in 100 ms units.
205
+ present_countdown (int): Only returned: the present countdown value
206
+ in 100 ms units.
207
+ """
208
+
80
209
  TIMER_USE_OEM = 5
81
210
  TIMER_USE_SMS_OS = 4
82
211
  TIMER_USE_OS_LOAD = 3
@@ -88,6 +217,10 @@ class Watchdog(State):
88
217
  TIMEOUT_ACTION_POWER_DOWN = 2
89
218
  TIMEOUT_ACTION_POWER_CYCLE = 3
90
219
 
220
+ #: Only used to set the timer: keep a running timer running. It is not
221
+ #: returned by :meth:`Bmc.get_watchdog_timer`.
222
+ dont_stop: bool | None
223
+
91
224
  __properties__ = [
92
225
  # (property, description)
93
226
  ('timer_use', ''),
@@ -102,7 +235,7 @@ class Watchdog(State):
102
235
  ('present_countdown', ''),
103
236
  ]
104
237
 
105
- def _from_response(self, rsp):
238
+ def _from_response(self, rsp: Message) -> None:
106
239
  self.timer_use = rsp.timer_use.timer_use
107
240
  self.is_running = bool(rsp.timer_use.is_running)
108
241
  self.dont_log = bool(rsp.timer_use.dont_log)
@@ -115,8 +248,30 @@ class Watchdog(State):
115
248
 
116
249
 
117
250
  class DeviceId(State):
118
-
119
- def __str__(self):
251
+ """The device ID of a controller.
252
+
253
+ Attributes:
254
+ device_id (int): The device ID.
255
+ revision (int): The device revision.
256
+ provides_sdrs (bool): The device provides device SDRs.
257
+ available (bool): The device firmware, an SDR update or the
258
+ self-initialization is in progress. Despite its name, the
259
+ device is in normal operation if this is False.
260
+ fw_revision (VersionField): The firmware revision.
261
+ ipmi_version (VersionField): The IPMI version, e.g. 2.0.
262
+ manufacturer_id (int): The IANA manufacturer ID.
263
+ manufacturer_name (str | None): The name of a well known
264
+ manufacturer, see :func:`pyipmi.constants.manufacturer_name`.
265
+ None for other manufacturers.
266
+ product_id (int): The product ID.
267
+ supported_functions (list[str]): The supported device functions,
268
+ see :meth:`supports_function`.
269
+ aux (list[int] | None): The auxiliary firmware revision, None if
270
+ the controller does not report it.
271
+ """
272
+
273
+ def __str__(self) -> str:
274
+ """Return the device ID fields as one line."""
120
275
  string = 'Device ID: %d' % self.device_id
121
276
  string += ' revision: %d' % self.revision
122
277
  string += ' available: %d' % self.available
@@ -127,15 +282,20 @@ class DeviceId(State):
127
282
  string += ' functions: %s' % ','.join(self.supported_functions)
128
283
  return string
129
284
 
130
- def supports_function(self, name):
131
- """Return if a function is supported.
285
+ def supports_function(self, name: str) -> bool:
286
+ """Return whether the device supports a function.
287
+
288
+ Args:
289
+ name: The function, one of 'SENSOR', 'SDR_REPOSITORY', 'SEL',
290
+ 'FRU_INVENTORY', 'IPMB_EVENT_RECEIVER',
291
+ 'IPMB_EVENT_GENERATOR', 'BRIDGE' or 'CHASSIS', in any case.
132
292
 
133
- `name` is one of 'SENSOR', 'SDR_REPOSITORY', 'SEL', 'FRU_INVENTORY',
134
- 'IPMB_EVENT_RECEIVER', 'IPMB_EVENT_GENERATOR', 'BRIDGE', 'CHASSIS'.
293
+ Returns:
294
+ True if the function is supported.
135
295
  """
136
296
  return name.lower() in self.supported_functions
137
297
 
138
- def _from_response(self, rsp):
298
+ def _from_response(self, rsp: Message) -> None:
139
299
  self.device_id = rsp.device_id
140
300
  self.revision = rsp.device_revision.device_revision
141
301
  self.provides_sdrs = bool(rsp.device_revision.provides_device_sdrs)
@@ -148,6 +308,7 @@ class DeviceId(State):
148
308
  (rsp.ipmi_version & 0xf, (rsp.ipmi_version >> 4) & 0xf))
149
309
 
150
310
  self.manufacturer_id = rsp.manufacturer_id
311
+ self.manufacturer_name = manufacturer_name(self.manufacturer_id)
151
312
  self.product_id = rsp.product_id
152
313
 
153
314
  self.supported_functions = []
@@ -171,10 +332,18 @@ class DeviceId(State):
171
332
 
172
333
 
173
334
  class DeviceGuid(State):
174
- def __str__(self):
335
+ """The GUID of a controller.
336
+
337
+ Attributes:
338
+ device_guid (Sequence[int]): The 16 bytes of the GUID as returned.
339
+ device_guid_string (str): The GUID in the usual string format.
340
+ """
341
+
342
+ def __str__(self) -> str:
343
+ """Return the GUID string."""
175
344
  return 'Device GUID: %s' % self.device_guid_string
176
345
 
177
- def _from_response(self, rsp):
346
+ def _from_response(self, rsp: Message) -> None:
178
347
  self.device_guid = rsp.device_guid
179
348
  self.device_guid_string = \
180
349
  '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-' \