BeaconAir – Track your Pi
BeaconAir Software
I have already described the iBeacon scanner and the Philips Hue Python library phue. Thus, the two major pieces remaining are the main program loop and the RasPiConnect Local.py
/control panel. The BeaconAir software block diagram is shown in Figure 7.
The main software (Listing 2) runs in a loop with a sleep period of 0.25 seconds at the end. It checks two sources. First, it checks a queue connected to the iBeacon scanning software running in a background thread (lines 1 and 2). An empty queue means no new iBeacon reports, so the software next checks to see whether any commands are waiting from the RasPiConnect control panel (line 37). If the queue has iBeacon results to deliver, the main loop processes the iBeacon information, sets various informational parameters, builds the new web page to deliver to RasPiConnect, and controls the lights. To make things clearer, I removed the debugging information. All calculations are in meters and converted to pixels for display.
Listing 2
BeaconAir
01 if (queueBLE.empty() == False): 02 result = queueBLE.get(False) 03 04 utils.processiBeaconList(result,currentiBeaconRSSI, currentiBeaconTimeStamp,rollingiBeaconRSSI) 05 utils.clearOldValues(10,currentiBeaconRSSI, currentiBeaconTimeStamp,rollingiBeaconRSSI) 06 07 # update position 08 if (utils.haveThreeGoodBeacons(rollingiBeaconRSSI) >= 3): 09 oldbeacons = beacons 10 beacons = utils.get3ClosestBeacons(rollingiBeaconRSSI) 11 if (cmp(oldbeacons, beacons) != 0): 12 bubblelog.writeToBubbleLog("closebeacons:%i,%i,%i" % (beacons[0], beacons[1], beacons[2])) 13 myPosition = utils.XgetXYFrom3Beacons(beacons[0],beacons[1],beacons[2], rollingiBeaconRSSI) 14 15 # calculate jitter in position 16 jitter = (((lastPosition[0] - myPosition[0])/lastPosition[0]) + ((lastPosition[1] - myPosition[1])/lastPosition[1]))/2.0 17 jitter = jitter * 100.0 # to get to percent 18 lastPosition = myPosition 19 20 f = open("/home/pi/BeaconAir/state/distancejitter.txt", "w") 21 22 f.write(str(jitter)) 23 f.close() 24 25 lights.checkForLightTrigger(myPosition, LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY, currentLightState) 26 27 # build webpage 28 webmap.buildWebMapToFile(myPosition, rollingiBeaconRSSI, currentLightState, DISPLAY_BEACON_ON, DISPLAY_LIGHTS_ON) 29 30 # build beacon count graph 31 iBeaconChart.iBeacondetect(rollingiBeaconRSSI) 32 else: 33 # lost position 34 myPosition = [-myPosition[0], -myPosition[1]] 35 36 # process commands from RasPiConnect 37 processCommand()
The program processes the incoming iBeacon list, fills the Beacon arrays, and clears out old values (lines 4 and 5). Next, it calculates the current BeaconAir physical position – but only if it finds more than three beacons (lines 8-13). I now have the latest calculated position, so I want to calculate the jitter in that position (lines 16-18). A big jitter value says that you are moving, that there are significant amounts of noise in the iBeacon reports, or both. Now I write out the jitter for RasPiConnect to read and send to the jitter graph on the control panel (lines 20-23).
Next, I calculate the distance from my position to all the lights and then turn on a light, change the brightness of a light, or turn a light off depending on the distance (line 25). Now I can build the web page for display on RasPiConnect (line 28). Finally, I update the current beacon count and build the graph for display on RasPiConnect (lines 31-34) that shows position, beacons, and lights (Figure 8).
When I started this project, I thought that building this live map was going to be the biggest problem. I looked at matplotlib on the Pi, but it was computationally expensive and complicated. Then, I looked at HTML drawing solutions and found that it was almost trivial to do. I used a Remote Webview in RasPiConnect for the control panel.
To make this work, I had to do the following steps:
- 1. Build a JPEG with the office or house plan. I took a picture of the house plans, used GIMP to draw the walls on the JPEG, and then remove the JPEG layer. Worked like a champ. Then, I had to measure a wall in meters, use GIMP to measure the same wall in pixels, and calculated a meters to pixels constant (0.0375 m/px in my case). I made the top of the JPEG 0,0, with x positive to the right and y positive down the left side.
- 2. Figure out the (x, y) position for each light and beacon and put it in the configuration file.
- 3. Run the software. The resulting HTML code is shown in Listing 3. I made my icons with transparent backgrounds (again using GIMP).
Listing 3
Building a Live Map
<html><head><title></title><style>body,html,iframe{margin:0;padding:0;} </style></head><body><div style='position: relative; left: 0; top: 0;'> <img src='http://example.example.com:9600/static/mainplanfull.png' style='position: relative; top: 0; left: 0;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 490px; left: 299px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 19px; left: 122px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 127px; left: 122px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 40px; left: 173px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 118px; left: 183px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 128px; left: 257px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 418px; left: 300px;'/> <img src='http://example.example.com:9600/static/iBeacon.png' style='position: absolute; top: 453px; left: 275px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 418px; left: 315px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 473px; left: 315px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 19px; left: 132px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 30px; left: 173px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 118px; left: 173px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 109px; left: 122px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 8px; left: 222px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 42px; left: 16px;'/> <img src='http://example.example.com:9600/static/OffLightBulb.png' style='position: absolute; top: 98px; left: 16px;'/> <img src='http://example.example.com:9600/static/red-pin.png' style='position: absolute; top: 378px; left: 217px;'/> </div></body></html>
BeaconAir Configuration File
The BeaconAir Configuration file (Listing 4) needs to be set up before the system can be used. It is located under config
in the main directory. The three major parts of the configuration file are scaling factors (lines 1-5), beacon configuration (lines 7-15), light configuration (lines 17-25). You can get the Hue light number from the Philips app under the light overview, or you can write a short program (look at some phue examples) to get the dictionary from the Philips Hue bridge.
Listing 4
BeaconAir Configuration File
01 def pixelConv(pixels): 02 return pixels * 0.0375 # in meters 03 04 def meterToPixel(meters): 05 return int(meters / 0.0375) # in pixels 06 07 # Beacon format: 08 # BeaconNumber, LocalName, x, y, UDID, Major, Minor, Measured Power (from spec), x in px, y in px 09 # BeaconNumber is incremental from 0 up. Don't skip a number 10 BeaconList=[] 11 BeaconCount = 0 12 13 Beacon = [BeaconCount,"Estimote #0 Beacon", pixelConv(314), pixelConv(507), "b9407f30f5f8466eaff925556b57fe6d", 64507, 5414, -64, 314, 507] 14 BeaconList.append(Beacon) 15 BeaconCount += 1 16 17 #list of lights 18 #Light Format 19 # LightNumber, LocalName, x, y, pixel x, pixel y, light on/off (1/0), huelightnumber 20 21 LightList=[] 22 LightCount = 0 23 Light = [LightCount, "Lab Left", pixelConv(330), pixelConv(435),330, 435,0, 2] 24 LightList.append(Light) 25 LightCount += 1
Buy this article as PDF
(incl. VAT)