Possibility to check for existence of target value in MAP file

Hi,

I have a MAP file addresses.map, which consists of pairs of MAC address and device name, like this:

12-34-45-AB-CD-EE=bluetoothBeacon1
12-34-45-AB-CD-EF=bluetoothBeacon2

I use that MAP file in a rule which checks if one or more bluetooth devices are present:

rule "Presence JSON"
when
	Item bluetoothDevicesJSON received update
then
	val jsonMsg = bluetoothDevicesJSON.state.toString

	//val senderAddress = transform("JSONPATH", "$.sensorAddress", jsonMsg)
	var int numberOfdevices = Integer::parseInt(transform("JSONPATH", "$.devices.length()", jsonMsg)) - 1

	for (i : 0 .. numberOfdevices) {
		val deviceAddress = transform("JSONPATH", "$.devices["+ i +"].address", jsonMsg)
		//val deviceRSSI = transform("JSONPATH", "$.devices[" + i + "].rssi", jsonMsg)

		val sw = gPresent.members.filter[s|s.name == transform("MAP", "addresses.map", deviceAddress)].head

		if (sw !== null) {
			sw.sendCommand(ON)
		}
	}
end

That seems to work quite well. Unfortunately, there are quite a few other devices “in” that bluetoothDevicesJSON string, for which there is no entry in the MAP file. My openHAB log now consists of many entries like the following:

2018-04-05 11:06:09.438 [WARN ] [rm.AbstractFileTransformationService] - Could not transform '4D-54-78-AA-C3-A7' with the file 'addresses.map' : Target value not found in map for '4D-54-78-AA-C3-A7'

Is there a way to check for the existence of an entry first before “doing” the actualy transformation? Or what could I do about that?

Thank you.

I am afraid, you can do that.
But you can add the other MACs addresses in the MAP file and transform them to the same arbitrary value and then get your rule to check for that

Yea, I thought of that, then started adding address with “UnknownX” as device name, but after having added 16 unknown device addresses, it looks like at least one of my devices, if not more, uses some kind of randomized addresses. So that won’t work either.

Most devices do this nowadays. It’s a privacy feature. I know for sure that Apple and most newer Android devices do. Older FitBit devices do not and tracker tiles and the like definitely do not.

This is going to be a hack but it just might be worth it in this case.

  • read in the contents of the map file to a String
  • split the String on newlines
  • split each line on =
  • put the MACs into a Set
import java.util.Set
import java.util.HashSet

val Set<String> validMacs = new HashSet<String>()

rule "Get Valid Mac"
when
    System started
then
    val lines = executeCommandLine("cat /etc/openhab2/transform/addresses.map").split("\n")
    lines.forEach[ line |
        val mac = line.split("=")
        validMacs.put(mac)
    ]
end

rule "Presence JSON"
when
...

    if(validMacs.contains(deviceAddress)) {
        val sw = gPresent.members.filter ...
        
...

I’ve just typed in the above. It may not work a written. But it illustrates the idea.

Your approach gave me an idea for one of my rules. Thanks for posting!

1 Like

You’re welcome! A lot of the presence stuff I use comes from your “Generic Presence Detection” thread, so thank you for that!

And thank’s for your hack, which I am now using. There were just a few things that had to be corrected to make it work. The code now looks as follows:

import java.util.Set
import java.util.HashSet

val Set<String> validMacs = new HashSet<String>()

rule "Get valid presence MAC addresses"
when
    System started 
then
    validMacs.clear()

    val lines = executeCommandLine("cat /etc/openhab2/transform/presenceDevices.map", 1000).split("\n")
    lines.forEach[ line |

        val mac = line.split("=").get(0)
        validMacs.add(mac)

	logInfo("Presence", "Added MAC to valid presence MAC list: " + mac)
    ]
end

Thanks again!

1 Like

You shouldn’t have to clear validMacs in the System started rule. That rule only runs once after OH starts and therefore validMacs will also have just been created and therefore already empty.

It occurred to me that the forEach loop could become a one-liner with a map reduce:

lines.map[ split("=").get(0) ].reduce[ validMacs, mac | validMacs.put(mac) ]

or even better a map forEach

lines.map[ split("=").get(0) ].forEach[ mac | validMacs.put(mac) ]

I’ve never done a map this complicated before but have no reason to believe it won’t work. I always forget about the map/reduce methods. They are super powerful.

You’re right, I added another trigger for the rule for testing reasons, which is why I added the clear command. I removed that now.

The “map forEach” seems to be working just fine, thanks!

1 Like