Mocking Tinkerforge Sensors
A couple of days have passed since the TinkerForge-API has been published on maven central. But what can you do with it if there is no hardware around. This was my situation
as i wanted to try it out and play with the weatherstation from jaxenter (weatherstation).
So what can you do now? You can either mock the hardware sensor and struggle with the protocol of the sensors or you can mock the software sensor. The latter approach seems easier to me. So lets start.
If you only want to have a single value request (e.g. getTemperature()) the task is easy. You only have to override the method on sensor instantiation.
1 new BrickletTemperature("dV6", new IPConnection()){
2 @Override
3 public short getTemperature() throws TimeoutException, NotConnectedException {
4 return 42;
5 }
6 };
The more interesting task is to hack the callback listeners so that you continuously get measured values. Ok, this would be no real data but it should be sufficient for
testing and playing. To hack the sensor we need to dive a little bit into the internals of the sensors. If you add a listener to a sensor it is put into an internal list which is
processed if a specific callback event (for example for temperature) is fired. In this process the changed value is passed.
So the task is to create a thread which fires a specific callback event on a sensor.
1 //Creates Standard-BrickletTemperature with injected IPConnection
2 BrickletTemperature brickletTemperature = new BrickletTemperature("kjh6", ipConnection);
3
4 //Determine the callback event constants (note: callbackreached listeners are ignored)
5 List<Integer> ints = new ArrayList<>();
6 Field[] declaredFields = deviceClass.getDeclaredFields();
7 for (Field declaredField : declaredFields) {
8 String fieldName = declaredField.getName();
9 if (fieldName.startsWith("CALLBACK_") && !fieldName.endsWith("REACHED")) {
10 declaredField.setAccessible(true);
11 int callbackIndex = declaredField.getInt(device);
12 ints.add(callbackIndex);
13 }
14 }
15
16 //Creates a Value generator for the bricklet
17 try {
18 createBrickletMock(ipConnection, brickletTemperature, (byte) 200, ints);
19 } catch (NoSuchMethodException e) {e.printStackTrace();}
The callback events of each sensor are mapped to a constant in the sensor class which starts with CALLBACK (callback reached events have CALLBACK
1 static <Bricklet extends Device> void startCallbackListenerThread(IPConnection ipcon, Device bricklet, byte startValue, List<Integer> callbackIndizes) throws NoSuchMethodException {
2 Class<IPConnection> ipConnectionClass = IPConnection.class;
3 Method callDeviceListener = ipConnectionClass.getDeclaredMethod("callDeviceListener", Device.class, byte.class, byte[].class);
4 callDeviceListener.setAccessible(true);
5
6 //start thread for each callback event
7 for (int callbackIndex : callbackIndizes) {
8 new Thread(() -> {
9
10 try {
11
12 Random random = new Random();
13 while (true) {
14
15 //Generates values -1, 0 or 1
16 int randomDiff = random.nextInt(3) - 1;
17
18 //Invoke on device
19 callDeviceListener.invoke(ipcon, bricklet, (byte) callbackIndex, new byte[]{0, 0, 0, 0, 0, 0, 0, 0, (byte) (startValue + randomDiff), 0});
20
21
22 //wait 5s
23 Thread.sleep(THREAD_SLEEP_MILLIS);
24 }
25 } catch (IllegalAccessException | InvocationTargetException | InterruptedException e) {
26 e.printStackTrace();
27 }
28 }).start();
29 }
30 }