Servo Attach Bug
Thank you Mats for pointing out that a servo controller name should be work as well as a reference
(https://github.com/MyRobotLab/myrobotlab/issues/486)
I caused a bug by removing
servo.attach(name, pin, position)
And I removed it because it was never defined in the ServoControl interface.
Initially, I thought the problem of not being able to invoke was related to a different change which made the parameters primitive types (e.g. int, double, long) vs their boxed objects (Integer, Double, Long, ..)
I am certain, anything that is invoked or serialized is boxed - meaning if you call a method with ints, doubles, or longs - they will change into Integer, Doubles and Longs respectively.
if a method is defined in an interface with primitives, invoking it will first fail, then try again with converted types.
To sum up :
I think in general, the interfaces defined for services are expected to be invoked at some time, and its more proficient to define them as boxed primitives.
So, lets do it this way -> attach(String name, Integer pin, Double pos)
Importance of Interfaces
Mats stressed the importance of intefaces, and I agree. They are "contracts" and mostly effort is put in to adbide and work with these contracts. Additionally, it is most likely the thing which is tested the most.
Part of the reason the bug above came about, was that the method I took out of Servo, never belonged to the ServoControl interface. If we want to support methods as "contracts" they must be in the interfaces.
Less is the new More
So going with the analogy of "contracts", the ones which are huge are much more difficult to use or maintain. Smaller interfaces are preferrable. So the smallest set of important yet generalized methods should be in an interface.
Patterns ! - Control Controller and Listener
We have a common pattern running through a lot of the services.
Control and Controller are something you can attach to another thing :)
Control I usually think of as a big chunk of configuration, and the Controller is the thing that does work with it.
An example would be MotorControl and MotorController.
MotorControl has pins, type, and desired speed, and/or a bunch of other configuration. And functions to tell the controller to work with this config, usually of the form controller.doSomething(this). By supplying itself as configuration, the MotorController is given all the necessary data to do its job.
MotorController acts on the supplied MotorControl data to do what is requested. The MotorController is a software driver for a specific implementation - e.g. Arduino, RasPi, LMT20, Sabertooth, Robotclaw ..
Controlling is when you send commands.
Conversely, when you recieve data your a Listener.
Listeners are often sensors. So sensors sometimes come with all 3 Controll, Controller & Listener interfaces.
Some sensors you can control by sending them commands to change their data. For example, if you want to enable or disable them or perhaps change some feature they have.
Listener often only have one "call back" function
e.g. this is the EncoderListener
void onEncoderData(EncoderData data)
Making Interfaces Kinder, Gentler & mor Fun for Users
I as a user won't remember if its
servo.attach(arduino) or
arduino.attach(servo) or
arduino.attach("servo") or
servo.attach("arduino")
So we as developers should supply all of them.
But wait there's more !
servo.attach(arduino, pin)
arduino.attach(servo, pin)
servo.attach("arduino", pin)
arduino.attach("servo", pin)
servo.attach(arduino, pin, pos)
arduino.attach(servo, pin, pos)
servo.attach("arduino", pin, pos)
arduino.attach("servo", pin, pos)
Abstracts to the Rescue
To deal with this matrix of possiblities, Abstracts are extermely helpful. You do all the mundane settings or type conversions there... Once !
e.g.
@Override public void attach(ServoController controller, Integer pin, Double pos, Double velocity) throws Exception { this.pin = pin; this.targetPos = pos; this.velocity = velocity; attach(controller); }
AbstractSpeechSynthesis was recently refactored in this way, such that all SpeechSynthesis services benefit from the common implementation.