1 """
2 The ckmodule class defines the abstract superclass of all ckbot modules
3 and those Module subclasses that are entirely hardware independent,
4 such as GenericModule and MissingModule. In addition, it defines the
5 AbstractNodeAdaptor and AbstractProtocol classes, which are used for
6 organizational purposes to allow all Protocol-s and
7 ProtocolNodeAdaptor-s to inherit from a single superclass
8
9 These classes were separated out of logical.py to allow non-CAN module
10 classes to inherit functionality from the Module base class.
11
12 The users of ckmodule are logical.py, which import *-s everything,
13 and python software modules which define Module subclasses.
14 """
15
16 from sys import platform as SYS_PLATFORM, stdout
17 from time import time as now
18 from warnings import warn
19 from glob import glob as GLOB_GLOB
20 from os import sep, getenv
21
22
23
24 _T0 = now()
26 """Display a progress message"""
27 stdout.write("%7.3f %s\n" % (now()-_T0,msg))
28 stdout.flush()
29
30
31
32 PYCKBOTPATH = getenv('PYCKBOTPATH',None)
33 if PYCKBOTPATH is None:
34 if __file__.rfind('py%sckbot'%sep)>-1:
35 PYCKBOTPATH = __file__[:__file__.rfind('py%sckbot'%sep)]
36 elif PYCKBOTPATH[-1:] != sep:
37 PYCKBOTPATH=PYCKBOTPATH+sep
38 if not GLOB_GLOB(PYCKBOTPATH+"py%sckbot%sckmodule.py" % (sep,sep)):
39 progress("WARNING: PYCKBOTPATH='%s' does not point to the right place" % PYCKBOTPATH )
44 """abstract superclass of all Protocol classes
45
46 Protocol classes are responsible for generating the Protocol Node
47 Adaptors for each module on the bus, when the bus is scanned.
48 Protocols implement the transactions that the underlying communication
49 infrastructure supports, and typically maintain state representing any
50 incomplete transactions.
51
52 AbstractProtocol subclasses must implement the following methods:
53 p.update()
54 p.hintNodes(nodes)
55 p.generatePNA( nid )
56
57 AbstractProtocol instances must have the following data attributes:
58 p.heartbeats -- dict -- nid to last heartbeat
59 """
61 """
62 Allow a bus parameter to be passed to all AbstractProtocol subclass
63 constructors
64 """
65 self.heartbeats = {}
66
68 """
69 *PURE* perform Protocol housekeeping operations
70 """
71 raise TypeError,"pure method called"
72
74 """
75 *PURE* use hint that specified nodes are available
76 """
77 raise TypeError,"pure method called"
78
80 """
81 *PURE* Generate a ProtocolNodeAdaptor for the specified nid
82 """
83 raise TypeError,"pure method called"
84
86 """abstract superclass of all Bus classes
87
88 Bus classes are responsible for wrapping OS hardware drivers and
89 presenting a pythonically correct, human readable interface.
90
91 Bus instances should be (mostly) stateless, and bus methods should
92 return as fast as possible.
93 """
95 """
96 Allow a port parameter to be passed to all AbstractBus subclass
97 constructors
98 """
99 pass
100
102 """abstract superclass of all ProtocolNodeAdaptor classes
103
104 AbstractNodeAdaptor subclasses must implement the get_typecode()
105 method, returning the module's type identification string
106 """
107 pass
108
110 "(organizational) abstract superclass of all PermissionError classes"
113
115 "(organizational) abstract superclass of all BusError classes"
118
120 "(organizational) abstract superclass of all ProtocolError classes"
123
125 """
126 Callable wrapper for providing access to object properties via
127 a getter function
128
129 AttributeGetter(obj,attr)() is getattr(obj,attr)
130 """
132 self.obj = obj
133 self.attr = attr
134
136 return "<%s at 0x%x for %s of %s>" % (
137 self.__class__.__name__, id(self), self.attr, repr(self.obj) )
138
140 return getattr(self.obj,self.attr)
141
143 """
144 Callable wrapper for providing access to object properties via
145 a setter function
146
147 AttributeSetter(obj,attr)(value) is setattr(obj,attr,value)
148 """
150 self.obj = obj
151 self.attr = attr
152
154 return "<%s at 0x%x for %s of %s>" % (
155 self.__class__.__name__, id(self), self.attr, repr(self.obj) )
156
158 setattr(self.obj,self.attr,value)
159
161 """
162 Abstract superclass representing a CKBot module.
163 Cluster creates the appropriate Module subclass by calling
164 Module.newFromDiscovery
165 """
166
167
168 Types = {}
169
170 @classmethod
172 """
173 Factory method for instantiating Module subclasses given a node id and typecode
174 INPUTS:
175 nid -- int -- the node id
176 typecode -- string -- the typecode string, which is looked up in
177 the Module.Types dictionary
178 pna -- a ProtocolNodeAdaptor -- typically created using a
179 Protocol instance's .generatePNA(nid) factory method
180 """
181 subclass = cls.Types.get(typecode,GenericModule)
182 m = subclass(nid, typecode, pna)
183 m.code_version = typecode
184 return m
185
186 - def __init__(self, node_id, typecode, pna ):
187 """
188 Concrete constructor.
189
190 ATTRIBUTES:
191 node_id -- 7 bit number to address a module uniquely
192 typecode -- version number of module code
193 pna -- ProtocolNodeAdaptor -- specialized for this node_id
194 """
195 self.node_id = int(node_id)
196 self.od = None
197 self.code_version = None
198 assert pna is None or isinstance(pna,AbstractNodeAdaptor)
199 assert pna is None or (pna.nid & 0xFF) == (node_id & 0xFF)
200 self.pna = pna
201 self.name = None
202 self._attr = {}
203
204 self.mcu = None
205 self.mem = None
206 self.od = None
207
209 """
210 This method creates an Object dictionary for this module.
211 """
212 if self.od is None:
213 self.od = self.pna.get_od(progress=progress)
214
216 """
217 Iterator for all Object Dictionary index addresses in this module
218
219 The addresses are returned as integers
220 """
221 if self.od is None:
222 return iter([])
223 return self.od.index_table.iterkeys()
224
226 """
227 Iterator for Object Dictionary properties exposed by this module
228
229 INPUTS:
230 perm -- string -- ''(default) all properties;
231 'R' readable/gettable
232 'W' writable/settable
233 OUTPUT:
234 property names returned as strings
235 """
236 if self.od is None or perm not in 'RW':
237 return iter([])
238 idx = self.od.name_table.iterkeys()
239 if perm=='R':
240 return (nm for nm in idx if self.od.name_table[nm].isReadable())
241 elif perm=='W':
242 return (nm for nm in idx if self.od.name_table[nm].isWritable())
243 return idx
244
246 """
247 Iterator for module attributes exposed by this module class
248
249 INPUTS:
250 perm -- string -- ''(default) all properties; 'R' readable/gettable
251 'W' writable/settable
252 OUTPUT:
253 property names returned as strings
254 """
255 plan = self._attr.iteritems()
256 if perm:
257 return (nm for nm,acc in plan if perm in acc)
258 return (nm for nm,_ in plan)
259
261 """(private)
262 Access python attributes exposed as properties
263 ('/@' property names)
264 """
265 def boolambda( b ):
266 if b:
267 return lambda : True
268 return lambda : False
269
270 perm = self._attr.get(prop,None)
271 if perm is None:
272 raise KeyError("Property '@%s' was not found in class %s"
273 % (prop,self.__class__.__name__) )
274
275 val = getattr( self, prop )
276
277
278
279
280
281 if req == 'isReadable': return boolambda( "R" in perm )
282 elif req == 'isWritable': return boolambda( "W" in perm )
283 elif req == 'get_sync':
284 if "1" in perm: return val
285 if "R" in perm: return AttributeGetter(self,prop)
286 elif req == 'set':
287 if "2" in perm: return val
288 if "W" in perm: return AttributeSetter(self,prop)
289 raise TypeError("Property '@%s' does not provide '%s'" % (prop,req))
290
292 """
293 Obtain a module attribute from a clp
294
295 At the module level, the clp is expected to start with '/'
296
297 This is the Module superclass implementation.
298 clp-s starting with /@ expose module attrbiutes
299 """
300 assert clp[0]=="/", "Logical clp-s only"
301 if clp[:2]=="/@":
302 return self._getAttrProperty(clp[2:],attr)
303
304 if self.od is None:
305 raise KeyError('ObjectDictionary was not initialized; use .get_od() or populate(...,walk=1)')
306
307 odo = self.od.name_table[clp[1:]]
308 return getattr( odo, attr )
309
311 """
312 This method sends a CAN Message to start this module.
313 """
314 self.pna.start()
315
317 """
318 This method sends a CAN Message to stop this module.
319 """
320 self.pna.stop()
321
323 """
324 This method sends a CAN Message to reset this module.
325 """
326 self.pna.reset()
327
329 """
330 Abstract superclass of servo modules. These support additional functionality
331 associated with position servos:
332
333 .set_pos -- sets position of the module. -- units in 100s of degrees between
334 -9000 and 9000. This is a "safe" version, which only accepts legal values
335 .set_pos_UNSAFE -- sets position of module, without any validity checking
336 .get_pos -- reads current position (may be at offset from set_pos, even without load)
337 .go_slack -- makes the servo go limp
338 .is_slack -- return True if and only if module is slack
339 """
340
341
342 POS_UPPER = 9000
343
344
345 POS_LOWER = -9000
346
356
358 """
359 Makes the servo go limp
360 """
361 raise RuntimeError("PURE")
362
364 """
365 Sets position of the module, without any validity checking
366
367 INPUT:
368 val -- units in 100s of degrees between -9000 and 9000
369 """
370 raise RuntimeError("PURE")
371
373 """
374 Sets position of the module, with safety checks.
375
376 INPUT:
377 val -- units in 100s of degrees between -9000 and 9000
378 """
379 raise RuntimeError("PURE")
380
381
383 """
384 Gets the actual position of the module
385 """
386 raise RuntimeError("PURE")
387
389 """
390 Asynchronously gets the actual position of the module
391 """
392 raise RuntimeError("PURE")
393
395 """
396 Gets the actual position of the module
397 """
398 raise RuntimeError("PURE")
399
401 """
402 Implementation of the .mem sub-object for modules that provide the generic IO memory interface
403
404 Instances of this class are created by the module subclass constructor, for those module software
405 versions that actually implement the interface.
406
407 Typical usage, assuming m is a module:
408 >>> m.mem[0xBAD] = 0xC0ED
409 >>> print m.mem[0xDEAD]
410 >>> m.mem[[0xBAD, 0xD00D]] = [0xBE, 0xEF]
411 >>> print m.mem[[0xC0ED, 0xBABE]]
412 >>> m.mem[0xF00] |= (1<<5) # set bit 5
413 >>> m.mem[0xF00] &= ~(1<<5) # clear bit 5
414 >>> m.mem[0xF00] ^= (1<<5) # toggle bit 5
415
416 If the module also has a .mcu sub-object, it will typically provide the constants needed instead of
417 the literals in the example above.
418 """
423
425 """
426 Read memory from microcontroller.
427
428 key can either be an integer memory address, or a list of such addresses.
429 When a list is used, returns a list of the results
430 """
431 if type(key) is list:
432 return [ self.get(k) for k in key ]
433
434
435
436 return self.get(key)
437
439 """
440 Write memory values in microcontroller
441
442 key can be an address and val a byte value, or key is a list of addresses, and val a sequence of
443 values (i.e. val can also be a tuple, or any other sequence type).
444 """
445 if type(key) is list:
446 for k,v in zip(key,val):
447 self[k]=v
448 else:
449 self.set(key,val)
450
452 """
453 Module mixin class implementing memory interface
454
455 This adds mem_read and mem_write methods, and a get_mem
456 """
457
459 "Write a byte to a memory address in the module's microcontroller"
460 self.pna.set( self.GIO_W_VAL, "B", val )
461 self.pna.set( self.GIO_W_ADDR, "H", addr )
462
464 "Read a memory address from the module's microncontroller"
465 self.pna.set( self.GIO_R_ADDR, "H", addr )
466 return self.pna.get_sync( self.GIO_R_VAL, "B" )
467
469 "Return a getter function for a memory address"
470
471 def getter():
472 return self.mem_read(addr)
473 return getter
474
476 "Return a setter function for a memory address"
477
478 def setter(val):
479 return self.mem_write(addr,val)
480 return setter
481
483 """
484 Concrete subclass of Module used for modules whose typecode was not
485 recognized
486 """
487 - def __init__(self, nid, typecode , pna):
488 Module.__init__(self,nid,typecode,pna)
489 warn(
490 RuntimeWarning("CAN node 0x%02X reported unknown typecode '%s'. Falling back on GenericModule class"% (nid, str(typecode)))
491 )
492
494 """
495 Concrete class representing required modules that were not discovered during
496 the call to Cluster.populate
497
498 Instances of this class "fake" servo and motor modules, and implement the
499 MemIx interface.
500
501 The .msg attribute may contain a callable. If present, this callable
502 will be called with messages describing any API calls made on the
503 MissingModule. Typically, one may set: m.msg = ckbot.logical.progress
504
505 """
506 TYPECODE = "<<missing>>"
507 - def __init__(self, nid, name, pna=None):
527
529 """get_od is not supported on MissingModule-s"""
530 pass
531
533 val = self._mem.get(addr,None)
534 if val is None:
535 self._mem[addr] = 0
536 val = 0
537 if self.msg is not None:
538 self.msg("%s.mem[0x%04X] --> %d" % (self.name,addr,val))
539 return val
540
542 self._mem[addr] = val
543 if self.msg is not None:
544 self.msg("%s.mem[0x%04X] <-- %d" % (self.name,addr,val))
545
547 self.slack = True
548 self.speed = 0
549 if self.msg is not None:
550 self.msg("%s.go_slack()" % self.name )
551
553 if self.msg is not None:
554 self.msg("%s.is_slack() --> %s" % (self.name,str(self.slack)))
555 return self.slack
556
558 self.slack = False
559 self.pos = val
560 if self.msg is not None:
561 self.msg("%s.set_pos(%d)" % (self.name,val))
562
564 self.slack = False
565 self.speed = val
566 if self.msg is not None:
567 self.msg("%s.set_speed(%d)" % (self.name,val))
568
570 self.slack = False
571 self.speed = val
572 if self.msg is not None:
573 self.msg("%s.set_torque(%d) [alias for speed]" % (self.name,val))
574
576 if self.msg is not None:
577 self.msg("%s.get_pos() --> %d" % (self.name,self.pos))
578 return self.pos
579
581 if self.msg is not None:
582 self.msg("%s.get_speed() --> %d" % (self.name,self.speed))
583 return self.speed
584