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
attaching with name
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
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
attach
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.
Need to answer inline,
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.
Shoutbox Chatter
It was relevant to the post, so I thought I'd save it (shoubox reverse order)
So I added a very very
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" ?