Per-unit instrument configuration

don’t know if it is really better, and also does not rely on a device card. but locking is an internal behavior, it auto-triggers inside start_shaking(), not something the user calls directly. So splitting into two separate capabilites indeed overexposes it.

A lighter pattern: private protocol + init-time check:

  class _CanLockInternally(Protocol):                                                                                                                                    
      async def _lock_plate(self) -> None: ...              
      async def _unlock_plate(self) -> None: ...                                                                                                                         
                                                                                                                                                                         
  class Shaker:
      def __init__(self, backend):                                                                                                                                       
          self._can_lock = isinstance(backend, _CanLockInternally)                                                                                                       
                                                                                                                                                                         
      async def start_shaking(self, speed):                                                                                                                              
          if self._can_lock:                                                                                                                                             
              await self.backend._lock_plate()                                                                                                                           
          await self.backend.start_shaking(speed) 

Non-locking backends just don’t implement the protocol, no dead methods, no supports_X flag. Locking shakers opt in by implementing _lock_plate() / _unlock_plate().

Same pattern works for supports_active_cooling on TemperatureController, anything that’s an internal behavior rather than a user-facing capability.

this would be description instead of prescription as well. which is nice of course, if it is sufficient.