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 CALLBACKREACHED) so that you can use reflection to read the values.

 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     }