Control & Controller (aka DeviceControl & DeviceController) are a recurring patterns in MRL.
Control represents an interface for other consumers (e.g. ServoControl).  
Controller manages a resource (e.g. Arduino).

"Attach" is the ubiquitous concept of joining the two together.  The Lego Snap ! (e.g. ear.attach(mouth)).

The attach point is a method which joins the two together with the minimal amount of configuration data (e.g pin in servo.attach(arduino, pin)). 

To make "attach" robust we should follow a few guidelines.

  • Control should offer an interface where all config data can be queried for a successful attach ]
    Below is an example of Controller.attach(Control) with no parameters.  All the complexity of initializing the Controller is done in this attach.  Parameterized attaches set data on the Control or Controller then call this method.
  • class Arduino extends ServoController {
    
    public void attach(ServoControl servo){
      if (isAttached(servo)){
        return; // already attached to this controller
        }
      int pin = servo.getPin();
      ...
    
    }
    
    }
    
  • Here is the other direction with no parameters
    class Servo extends ServoControl {
    
    public void attach(ServoController arduino){
      if (isAttached(arduino)){
        return; // already attached to this controller
      }
       controller = arduino;
       controllerName = arduino.getName();
      ...
    }
    
    }
    

These are the two places where the "critical initialization code"  would go.
For Control the parameterless attach would :

  • set the controller reference
  • set the controller name
  • make sure that isAttached(controller) will return true the next time this attach is called with this controller

For Controller the parameterless attach would:

  • setup a device mapping
  • set references up to handle callbacks
  • set device index
  • increment device id
  • make sure that isAttach(control) will return true the next time this attach is called with this control

The last thing both attaches would do is call the other attach with itself as reference.  This will guarantee the "critical initialization code" of each service is called once and only once when a user decides to attach 2 services.

Bellow you can see the flow of  servo.attach(arduino, 7)

And if we do it this way we can support arduino.attach(servo, 7)   ... everything will work !

We must have the guards at the top - because if you dont, its possible to have this sort of flow.

This one you want to avoid.  If Control doesn't respond with isAttached(controller) == true the thread will go into an infinit loop.  Computer meditation with high CPU ... nothing is done, but its very busy.  I've done this with ear.attach(mouth) .. its not pretty.

But with the guards, and following these patterns of attaching services together we can easily support parameterized servo.attach(arduino, 7) or arduino.attach(servo, 7) and a myriad of other attach methods

Mats

7 years 3 months ago

I think we will run into problems when using Controller.attach(ControlName) when a service also can act as two different Controls/Controllers with the same signature for the rest of the parameters. It's possible to get the type of service, but not the interface to be used. I think that was the reason that i was forced to use setController instead of attach when I implemented the i2c interfaces. It has a clearly defined direction.

 

Excellent point Mats !

I don't see how setController solves the problem, perhaps you can explain, unless your saying its explicitly typed.

My proposal is to implement attach with a specific instance type parameter (no String name).

For example Arduino would implement via Controller interfaces  :

public void attach(MotorControl motor);
public void attach(ServoControl servo);
public void attach(UltrasonicSensor sensor);
etc...
A single 
public void attach(String name)  works in the Service !, because its implemenation will be

public void attach(String name) {
      attach(Runtime.getService(name));
}

Which will "auto-magically" route to the correct strongly typed "attach".  
If your Control implement more than one Control interface and your Controller supports both - then yes you'd need to cast or somehow be more explicit.  But this problem would occur with setController too .. Its an issue where 2 Services don't know how to attach to one another because they support so many interfaces it becomes ambiguous again even with type information.  I would suppose the next step in order to sort out the ambiguity would be to start mangling the names
e.g. attachMotorControl ..  which I'm not so excited about ...  you lose some of the power of overloading when you begin mangling method names .. but I guess its a 'balance'

Attach by name should not be in any interface !  (another mistake I made which we can correct)
but it can be in a Service...
perhaps it should be in the abstract Service class 

 

I agree to that that setController doesn't solve the problem. It was a workaround, and in no way a perfect solution. 

And I assume that you really intended:

class Arduino extends Service implements ServoController ....

and

class Servo extends Service implements ServoControl.....

just like it is today.

-------------------------------------------------------------------------------

To dig down into some more details. 

The DiyServo currently implements three different interfaces: ServoControl, MotorControl, PinListener

ServoControl because it acts the same way as a servo, so it felt natural to implement the methods that a Servo should implement. However, I couldn't implement all the methods in the ServoControl interface since ServoControl defines methods to attach a ServoController and I don't use any ServoController. I guess it would be better to define a DiyServoControl interface instead. 

MotorControl because ot the MotorControl / MotorController pair. What's a bit confusing is that the MotorController interface doesn't define how to attach. So the DiyServo.attach(MotorController) is only implementer in the DiyServo service. No corresponding attach extists in the Arduino. Only analog writes are used. Perhaps that's OK. The Arduino service don't need to know who is writing an analog value. The Motir service acts the same way,

PinListener because it needs an analog value. 

Both the Arduino and the Adafruit16CServoDriver implements ServoController and more may come in the future.

Both the Arduino and the Adafruit16CServoDriver implements MotorController and more may come in the future.

Both the Arduino and Ads1115 implements PinArrayControl. 

So if I use Arduino as a controller and want to attach to DiyServo as one of three alternatives: 

1. The Arduino acting as a MotorController and the DiyServo as a MotorControl

2. The Arduino acting as a PinArrayControl and the DiyServo as PinListner

3. The Arduino acting as a ServoController and the DiyServo as a ServoControl ( This one I really want to avoid since the DiyServo never want to use the Arduino as a ServoController ) 

I can't see how to do that with any attach method because they will conflict because of the tripple nature of both services. So now we already are at the attachMotorControl pattern. 

 

 

GroG

7 years 3 months ago

In reply to by Mats

Need to answer inline, because you brought up a lot of good points.

 

The DiyServo currently implements three different interfaces: ServoControl, MotorControl, PinListener

ServoControl because it acts the same way as a servo, so it felt natural to implement the methods that a Servo should implement.



Makes sense.  Although, I think I'm starting to understand the control interfaces as

AbsolutePositionControl & RelativePositionControl  -  they grew out of Servos (absolute) & Motors (relative) .. but the fact they are Servos & Motors are less important as to which methods they can implement .. moveTo(pos)  or move(pos)

 

However, I couldn't implement all the methods in the ServoControl interface since ServoControl defines methods to attach a ServoController and I don't use any ServoController. I guess it would be better to define a DiyServoControl interface instead.

 

Right, because all you really want is AbsolutePositionControl - not the legacy implementation :)

 

MotorControl because ot the MotorControl / MotorController pair. What's a bit confusing is that the MotorController interface doesn't define how to attach. So the DiyServo.attach(MotorController) is only implementer in the DiyServo service. No corresponding attach extists in the Arduino. Only analog writes are used. Perhaps that's OK. The Arduino service don't need to know who is writing an analog value. The Motor service acts the same way,

 

There seem like there are 2 interfaces on the Control.  A very general one which is consumed by other services & users (e.g. AbsolutePositionControl & RelativePositionControl).  Then there is a "backend" interface which describes how to attach to a Controller.  I think MotorControl attach methods should be added.  

 

PinListener because it needs an analog value.

Both the Arduino and the Adafruit16CServoDriver implements ServoController and more may come in the future.

Both the Arduino and the Adafruit16CServoDriver implements MotorController and more may come in the future.

Both the Arduino and Ads1115 implements PinArrayControl.

So if I use Arduino as a controller and want to attach to DiyServo as one of three alternatives:

1. The Arduino acting as a MotorController and the DiyServo as a MotorControl

2. The Arduino acting as a PinArrayControl and the DiyServo as PinListner

3. The Arduino acting as a ServoController and the DiyServo as a ServoControl ( This one I really want to avoid since the DiyServo never want to use the Arduino as a ServoController )

I can't see how to do that with any attach method because they will conflict because of the tripple nature of both services. So now we already are at the attachMotorControl pattern.

 

Or we refactor AbsolutePositionControl & RelativePositionControl (we can make it backwards compatible by having Servo extend AbsolutePositionControl & Motor extend RelativePositionControl

Create a DiyServoControl which inherits AbsolutePositionControl - perhaps RelativePositionControl too and add a DiyServoController.

 

This way you don't have unecessary ServoControl/ServoController methods.  You implement your own attach, and you can support all the things Motors & Servos can do.

 
We are back to simple attach again :)

GroG

7 years 3 months ago

It was relevant to the post, so I thought I'd save it (shoubox reverse order)

GroGWe have better things to do ! But again as a maintainer I only want one explicitly typed critical init code segment
GroGOr servo to arduino with a pin
GroGYa very true... Dunno how many times I forgot what attaches what with correct order.. all I remember is you can attach a mouth to an ear somehow ;)
kwatterssimple python code for people is a good thing. :) humans have bad memories when it comes to syntax.
GroGOne place in a service which handles the attach from one explicit service type to its own type
GroGDoh .. smartphone typing
GroG.. one place in a service which handles critical init details of attach one specific service type to its own type
GroGAs a person gluing services together I'd rather not remember the order of the way. Services attach.. but as a maintainer of code I want less code and only one place in a service which handles the critical init details
kwattersscripts are attaching/detaching reassigning in many different ways (in pyrobotlab.)
kwattersi think it will help keep the python syntax simple also.
GroGEither works .. both "critical init code" on each is run once and only once
GroGExactly I can attach ear to mouth or mouth to ear
kwattersspeech synthesis / speech recognition ?
kwattersok.. so how far do we go with this... what about textPublisher / textListeners ?
kwattersear.attach(mouth) yup.
GroGAlthough it shows control controller it works for the more general case of service attach to service
kwatterswith ServoController and ServoControl as base classes..
kwattershmm.. "extends" .. without multiple-interantance, i dont' think we have an issue.
GroGYa this would solve that problem
GroGIt does support control attaching to control or control attaching to controller
kwattersperhaps some of the complexity comes up because of the direction in which the attach occurs..
GroGBut ya it doesn't support everything
kwattersalways a good goal.
GroGI was aiming for simple rules and less code
kwattersright.. not trying to solve the worlds problems here.. just wrapping my head around it a bit.
GroGMultiple to multiple interfaces will be a problem even without string
kwatterswhich does the attach(String name) call?
kwatterssomething being both a servo and motor controller..
kwattershmm.. though.. if something implementes multiple controllers.. that will be an ambigious method call.
kwattersmoving it to the base service class is pretty good i think.
kwattersi noticed a few places where that was missing at one point..
GroGIn the service attach(string) is implemented
kwattersah. ok. yes i like that. nice
GroGNo not going away but not in the interface
kwattersor did i misread ?
GroGWhich would be great to support gui(s) with dynamic lists of attach targets
kwattersso attach(String name) going away ? that will break a lot of scripts..
GroGI hope so.. if followed, I think it will reduce code and complexity.. in addition it would support querying all attach(s) across all running services
kwatterssimple interfaces for all?
kwattershow goes the control controller manifesto ? :)

GroG

7 years 3 months ago

So I added a very very minimal AbsolutePositionControl & RelativePositionControl interfaces on "arduino3" branch to explore.

I was suprised that Motor has real implementation of an encoder and could support "moveTo".
I think this was kwatters implementation.

How does DiyServo differ from a Motor with an encoder ? 

I have not looked into the details of the Motor with encoder implemenation - There are several types of encoders digital, 2 phase, analog ..

Maybe my question should be, "How does a DiyServo differ from a Motor with an analog encoder" ?