├── .gitignore ├── README.md ├── bubblelog.py ├── ble ├── testblescan.py ├── bleThread.py ├── blescan2.py └── blescan.py ├── iBeaconChart.py ├── lights.py ├── webmap.py ├── config ├── config.py └── conflocal.py ├── utils.py └── BeaconAir.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *tx* 3 | *.*.swp 4 | *.*.*.swp 5 | *.xml 6 | *.temp 7 | *.test 8 | *nohup.out 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2 | # BeaconAir Source code 3 | # 4 | # SwitchDoc Labs 5 | # www.switchdoc.com 6 | # 7/8/2014 7 | # (c) 2014 under GPL2 license 8 | # 9 | # 10 | 11 | -------------------------------------------------------------------------------- /bubblelog.py: -------------------------------------------------------------------------------- 1 | # bubblelog.py 2 | # logs system information to RasPiConnect Bubble Control 3 | # 4 | # jcs 6/10/14 5 | # 6 | 7 | from time import gmtime, strftime 8 | def writeToBubbleLog(message): 9 | 10 | f = open("/home/pi/BeaconAir/state/bubblelog.txt", "a") 11 | time = strftime("%H:%M:%S", gmtime()) 12 | f.write(time+":"+message+"\n") 13 | f.close() 14 | 15 | 16 | -------------------------------------------------------------------------------- /ble/testblescan.py: -------------------------------------------------------------------------------- 1 | # test BLE Scanning software 2 | # jcs 6/8/2014 3 | 4 | import blescan 5 | import sys 6 | 7 | import bluetooth._bluetooth as bluez 8 | 9 | dev_id = 0 10 | try: 11 | sock = bluez.hci_open_dev(dev_id) 12 | print "ble thread started" 13 | 14 | except: 15 | print "error accessing bluetooth device..." 16 | sys.exit(1) 17 | 18 | blescan.hci_le_set_scan_parameters(sock) 19 | blescan.hci_enable_le_scan(sock) 20 | 21 | while True: 22 | returnedList = blescan.parse_events(sock, 10) 23 | print "----------" 24 | for beacon in returnedList: 25 | print beacon 26 | 27 | -------------------------------------------------------------------------------- /ble/bleThread.py: -------------------------------------------------------------------------------- 1 | # bleThread - background thread reading BLE adverts 2 | # jcs 6/8/2014 3 | # 4 | 5 | import sys 6 | import time 7 | 8 | 9 | import bluetooth._bluetooth as bluez 10 | 11 | 12 | sys.path.append('/home/pi/BeaconAir/config') 13 | sys.path.append('/home/pi/BeaconAir/ble') 14 | 15 | 16 | import blescan 17 | import utils 18 | 19 | 20 | 21 | 22 | def bleDetect(source, repeatcount, queue): 23 | 24 | 25 | 26 | 27 | dev_id = 0 28 | try: 29 | sock = bluez.hci_open_dev(dev_id) 30 | except: 31 | print "error accessing bluetooth device..." 32 | sys.exit(1) 33 | blescan.hci_le_set_scan_parameters(sock) 34 | blescan.hci_enable_le_scan(sock) 35 | 36 | 37 | while True: 38 | returnedList = blescan.parse_events(sock, repeatcount) 39 | #print "result put in queue" 40 | #print "returnList = ", returnedList 41 | queue.put(returnedList) 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /iBeaconChart.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Builds iBeaconCount graph string 4 | # filename: detection.py 5 | # Version 1.0 06/19/2014 6 | # 7 | # 8 | 9 | 10 | import sys 11 | import time 12 | 13 | import utils 14 | 15 | 16 | sys.path.append('/home/pi/BeaconAir/config') 17 | 18 | 19 | #global variables 20 | 21 | datalist = []; 22 | # if conflocal.py is not found, import default conf.py 23 | 24 | # Check for user imports 25 | try: 26 | import conflocal as conf 27 | except ImportError: 28 | import conf 29 | 30 | 31 | def buildGraphString(): 32 | 33 | global datalist 34 | 35 | response = "" 36 | valuecount = "" 37 | f = open("/home/pi/BeaconAir/state/iBeaconCountGraph.txt", "w") 38 | for i in range(len(datalist)): 39 | response += str(datalist[i]) 40 | valuecount += str(i) 41 | if (i < len(datalist)-1): 42 | response +="^^" 43 | valuecount +="^^" 44 | 45 | if (len(response) != 0): 46 | fullresponse = response + "||" + valuecount 47 | else: 48 | fullresponse = "" 49 | 50 | f.write(fullresponse) 51 | 52 | f.close() 53 | 54 | 55 | def iBeacondetect(RSSIArray): 56 | 57 | global datalist 58 | 59 | count = 0 60 | for beacon in RSSIArray: 61 | if (beacon < 0): 62 | count += 1 63 | 64 | 65 | if (len(datalist) == 10): 66 | if (len(datalist) > 0): 67 | datalist.pop(0) 68 | datalist.append(count) 69 | 70 | buildGraphString() 71 | f = open("/home/pi/BeaconAir/state/beaconCount.txt", "w") 72 | f.write(str(count)) 73 | f.close() 74 | 75 | return count 76 | -------------------------------------------------------------------------------- /lights.py: -------------------------------------------------------------------------------- 1 | #light control 2 | # 3 | # JCS 06/09/14 4 | # 5 | 6 | # controls the lights from Hue 7 | 8 | import sys 9 | from phue import Bridge 10 | 11 | import bubblelog 12 | 13 | # set up the hue variable 14 | hue = None 15 | 16 | sys.path.append('./config') 17 | 18 | 19 | def initializeHue(address): 20 | global hue 21 | hue = Bridge(address) 22 | 23 | def setInitialLightState(currentLightState): 24 | global hue 25 | return 26 | 27 | 28 | # if conflocal.py is not found, import default conf.py 29 | 30 | # Check for user imports 31 | try: 32 | import conflocal as conf 33 | except ImportError: 34 | import conf 35 | 36 | import utils 37 | 38 | def checkForLightTrigger (myPosition, currentDistanceSensitivity, currentBrightnessSensitivity, lightStateArray): 39 | global hue 40 | 41 | for light in conf.LightList: 42 | 43 | # check for light match 44 | lightDistance = utils.distanceBetweenTwoPoints([light[2], light[3]], myPosition) 45 | brightness = getTheBrightness(lightDistance, currentBrightnessSensitivity) 46 | if (lightDistance < currentDistanceSensitivity): 47 | print "Brightness set to: %3.2f" % brightness 48 | if (lightStateArray[light[0]] == 1): 49 | print "Light %i:%s is already ON" % (light[0], light[1]) 50 | else: 51 | print "Turn Light %i:%s ON" % (light[0], light[1]) 52 | lightStateArray[light[0]] = 1 53 | hue.set_light(light[7],'on', True) 54 | bubblelog.writeToBubbleLog("Light#%i (%s) turned ON " % (light[0], light[1]) ) 55 | #hue.set_light(light[7],'bri', (brightness *256)) 56 | 57 | elif (lightDistance > currentDistanceSensitivity): 58 | if (lightStateArray[light[0]] == 0): 59 | print "Light %i:%s is already OFF" % (light[0], light[1]) 60 | else: 61 | print "Turn Light %i:%s OFF" % (light[0], light[1]) 62 | lightStateArray[light[0]] = 0 63 | hue.set_light(light[7],'on', False) 64 | bubblelog.writeToBubbleLog("Light#%i (%s) turned OFF " % (light[0], light[1]) ) 65 | 66 | 67 | 68 | def getTheBrightness(lightDistance, currentBrightnessSensitivity): 69 | 70 | global hue 71 | newBrightness = (1.0 - lightDistance/currentBrightnessSensitivity) 72 | if (newBrightness > 1.0): 73 | newBrightness = 1.0 74 | elif (newBrightness < 0.0): 75 | newBrightness = 0.0 76 | 77 | 78 | return newBrightness 79 | 80 | 81 | def allLights(ON_OFF, lightStateArray): 82 | global hue 83 | 84 | for light in conf.LightList: 85 | print "Setting light:", light[0] 86 | if (ON_OFF == True): 87 | lightStateArray[light[0]] = 1 88 | else: 89 | lightStateArray[light[0]] = 0 90 | 91 | 92 | hue.set_light(light[7],'on', ON_OFF) 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /webmap.py: -------------------------------------------------------------------------------- 1 | # webmap.py 2 | # builds the webmap code from state and configuration files 3 | # jcs 6/9/2014 4 | # 5 | import utils 6 | # if conflocal.py is not found, import default conf.py 7 | 8 | 9 | # Check for user imports 10 | try: 11 | import conflocal as conf 12 | except ImportError: 13 | import conf 14 | 15 | 16 | def buildWebMapToFile(myPosition, rollingRSSIArray, currentLightStateArray, displaybeacon, displaylights): 17 | 18 | #print currentLightStateArray 19 | 20 | f = open("/home/pi/RasPiConnectServer/Templates/W-1a.txt", "w") 21 | 22 | webresponse = "" 23 | 24 | if (displaybeacon == True): 25 | for beacon in conf.BeaconList: 26 | 27 | webresponse += "\n" 31 | 32 | #webresponse += "
\n" 52 | 53 | # now do my Position 54 | # minus means old position 55 | if ((myPosition[0] > 0.0) and (myPosition[1] > 0.0)): 56 | webresponse += "\n" 60 | else: 61 | myPosition = [-myPosition[0], -myPosition[1]] 62 | webresponse += "\n" 66 | 67 | 68 | #print webresponse 69 | f.write(webresponse) 70 | 71 | f.close() 72 | 73 | -------------------------------------------------------------------------------- /config/config.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # 4 | # configuration file - contains customization for exact system 5 | # JCS 06/07/2014 6 | # 7 | serverURL = "http://example.example.com:9600" 8 | 9 | DEBUG = True 10 | 11 | LIGHT_BRIGHTNESS_SENSITIVITY = 2.0 12 | LIGHT_DISTANCE_SENSITIVITY = 2.0 13 | # configuration for house 14 | # list of iBeacons with x,y coordinates. Top left corner of image is 0,0 15 | # list of lists 16 | # Beacon format: 17 | # BeaconNumber, LocalName, x, y, UDID, Major, Minor, Measured Power (from spec), x in px, y in px 18 | # BeaconNumber is incremental from 0 up. Don't skip a number 19 | 20 | #pix converter 21 | 22 | def pixelConv(pixels): 23 | return pixels * 0.0375 # in meters 24 | 25 | def meterToPixel(meters): 26 | return int(meters / 0.0375) # in pixels 27 | 28 | BeaconList=[] 29 | BeaconCount = 0 30 | 31 | Beacon = [BeaconCount,"Estimote #0 Beacon", pixelConv(314), pixelConv(507), "b9407f30f5f8466eaff925556b57fe6d", 64507, 5414, -64, 314, 507] 32 | BeaconList.append(Beacon) 33 | BeaconCount += 1 34 | 35 | Beacon = [BeaconCount,"Estimote #1 Beacon", pixelConv(110), pixelConv(36), "b9407f30f5f8466eaff925556b57fe6d", 28849,11936, -64, 137, 36] 36 | BeaconList.append(Beacon) 37 | BeaconCount += 1 38 | 39 | Beacon = [BeaconCount,"Estimote #2 Beacon", pixelConv(137), pixelConv(144), "b9407f30f5f8466eaff925556b57fe6d", 56124, 58492, -64, 137, 144] 40 | BeaconList.append(Beacon) 41 | BeaconCount += 1 42 | 43 | Beacon = [BeaconCount,"Estimote #3 Beacon", pixelConv(188), pixelConv(57), "b9407f30f5f8466eaff925556b57fe6d", 740, 4735, -64, 188, 57] 44 | BeaconList.append(Beacon) 45 | BeaconCount += 1 46 | 47 | Beacon = [BeaconCount,"Estimote #4 Beacon", pixelConv(198), pixelConv(135), "b9407f30f5f8466eaff925556b57fe6d", 28495, 8272, -64, 198, 135] 48 | BeaconList.append(Beacon) 49 | BeaconCount += 1 50 | 51 | Beacon = [BeaconCount,"Estimote #5 Beacon", pixelConv(272), pixelConv(145), "b9407f30f5f8466eaff925556b57fe6d", 13072, 42423, -64, 272, 145] 52 | BeaconList.append(Beacon) 53 | BeaconCount += 1 54 | 55 | Beacon = [BeaconCount, "ParticleFirst Beacon", pixelConv(315), pixelConv(435), "8a ef b0 31 6c 32 48 6f 82 5b e2 6f a1 93 48 7d", 0, 0, -73, 315, 435] 56 | BeaconList.append(Beacon) 57 | BeaconCount += 1 58 | 59 | Beacon = [BeaconCount, "ParticleSecond Beacon", pixelConv(290), pixelConv(470), "8a ef b0 31 6c 32 48 6f 82 5b e2 6f a1 93 48 7d", 1, 1, -73, 290, 470] 60 | BeaconList.append(Beacon) 61 | BeaconCount += 1 62 | 63 | # transform 64 | 65 | for beacon in BeaconList: 66 | beacon[4] = beacon[4].replace(" ", "") 67 | beacon[4] = beacon[4].replace("-", "") 68 | beacon[4] = beacon[4].upper() 69 | 70 | 71 | #list of lights 72 | #Light Format 73 | # LightNumber, LocalName, x, y, pixel x, pixel y, light on/off (1/0), huelightnumber 74 | 75 | LightList=[] 76 | LightCount = 0 77 | Light = [LightCount, "Lab Left", pixelConv(330), pixelConv(435),330, 435,0, 2] 78 | LightList.append(Light) 79 | LightCount += 1 80 | 81 | Light = [LightCount, "Lab Right", pixelConv(330), pixelConv(490), 330, 490, 0, 5] 82 | LightList.append(Light) 83 | LightCount += 1 84 | 85 | Light = [LightCount, "Living Room Top Left", pixelConv(147), pixelConv(36), 147, 36, 0, 7] 86 | LightList.append(Light) 87 | LightCount += 1 88 | 89 | Light = [LightCount, "Living Room Top Right", pixelConv(188), pixelConv(47), 188, 47, 0, 6] 90 | LightList.append(Light) 91 | LightCount += 1 92 | 93 | Light = [LightCount, "Living Room Lower Right", pixelConv(188), pixelConv(135), 188, 135, 0, 8] 94 | LightList.append(Light) 95 | LightCount += 1 96 | 97 | 98 | Light = [LightCount, "Living Room Left Bloom", pixelConv(137), pixelConv(126), 137, 126, 0, 9] 99 | LightList.append(Light) 100 | LightCount += 1 101 | 102 | 103 | Light = [LightCount, "Living Room Right Bloom", pixelConv(237), pixelConv(25), 237, 25, 0, 10] 104 | LightList.append(Light) 105 | LightCount += 1 106 | 107 | 108 | Light = [LightCount, "Right Bedroom", pixelConv(31), pixelConv(59), 31, 59, 0, 10] 109 | LightList.append(Light) 110 | LightCount += 1 111 | 112 | 113 | Light = [LightCount, "Left Bedroom", pixelConv(32), pixelConv(115), 31, 115, 0, 10] 114 | LightList.append(Light) 115 | LightCount += 1 116 | 117 | 118 | -------------------------------------------------------------------------------- /config/conflocal.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # configuration file - contains customization for exact system 4 | # JCS 06/07/2014 5 | # 6 | 7 | serverURL = "http://rfw.wardner.com:9600" 8 | 9 | DEBUG = True 10 | 11 | LIGHT_BRIGHTNESS_SENSITIVITY = 2.0 12 | LIGHT_DISTANCE_SENSITIVITY = 2.0 13 | # configuration for house 14 | # list of iBeacons with x,y coordinates. Top left corner of image is 0,0 15 | # list of lists 16 | # Beacon format: 17 | # BeaconNumber, LocalName, x, y, UDID, Major, Minor, Measured Power (from spec), x in px, y in px 18 | # BeaconNumber is incremental from 0 up. Don't skip a number 19 | 20 | #pix converter 21 | 22 | def pixelConv(pixels): 23 | return pixels * 0.0375 # in meters 24 | 25 | def meterToPixel(meters): 26 | return int(meters / 0.0375) # in pixels 27 | 28 | BeaconList=[] 29 | BeaconCount = 0 30 | 31 | Beacon = [BeaconCount,"Estimote #0 Beacon", pixelConv(314), pixelConv(507), "b9407f30f5f8466eaff925556b57fe6d", 64507, 5414, -64, 314, 507] 32 | BeaconList.append(Beacon) 33 | BeaconCount += 1 34 | 35 | Beacon = [BeaconCount,"Estimote #1 Beacon", pixelConv(110), pixelConv(36), "b9407f30f5f8466eaff925556b57fe6d", 28849,11936, -64, 137, 36] 36 | BeaconList.append(Beacon) 37 | BeaconCount += 1 38 | 39 | Beacon = [BeaconCount,"Estimote #2 Beacon", pixelConv(137), pixelConv(144), "b9407f30f5f8466eaff925556b57fe6d", 56124, 58492, -64, 137, 144] 40 | BeaconList.append(Beacon) 41 | BeaconCount += 1 42 | 43 | Beacon = [BeaconCount,"Estimote #3 Beacon", pixelConv(188), pixelConv(57), "b9407f30f5f8466eaff925556b57fe6d", 740, 4735, -64, 188, 57] 44 | BeaconList.append(Beacon) 45 | BeaconCount += 1 46 | 47 | Beacon = [BeaconCount,"Estimote #4 Beacon", pixelConv(198), pixelConv(135), "b9407f30f5f8466eaff925556b57fe6d", 28495, 8272, -64, 198, 135] 48 | BeaconList.append(Beacon) 49 | BeaconCount += 1 50 | 51 | Beacon = [BeaconCount,"Estimote #5 Beacon", pixelConv(272), pixelConv(145), "b9407f30f5f8466eaff925556b57fe6d", 13072, 42423, -64, 272, 145] 52 | BeaconList.append(Beacon) 53 | BeaconCount += 1 54 | 55 | Beacon = [BeaconCount, "ParticleFirst Beacon", pixelConv(315), pixelConv(435), "8a ef b0 31 6c 32 48 6f 82 5b e2 6f a1 93 48 7d", 0, 0, -73, 315, 435] 56 | BeaconList.append(Beacon) 57 | BeaconCount += 1 58 | 59 | Beacon = [BeaconCount, "ParticleSecond Beacon", pixelConv(290), pixelConv(470), "8a ef b0 31 6c 32 48 6f 82 5b e2 6f a1 93 48 7d", 1, 1, -73, 290, 470] 60 | BeaconList.append(Beacon) 61 | BeaconCount += 1 62 | 63 | # transform 64 | 65 | for beacon in BeaconList: 66 | beacon[4] = beacon[4].replace(" ", "") 67 | beacon[4] = beacon[4].replace("-", "") 68 | beacon[4] = beacon[4].upper() 69 | 70 | 71 | #list of lights 72 | #Light Format 73 | # LightNumber, LocalName, x, y, pixel x, pixel y, light on/off (1/0), huelightnumber 74 | 75 | hue = 0 76 | LightList=[] 77 | LightCount = 0 78 | Light = [LightCount, "Lab Left", pixelConv(330), pixelConv(435),330, 435,0, 2] 79 | LightList.append(Light) 80 | LightCount += 1 81 | 82 | Light = [LightCount, "Lab Right", pixelConv(330), pixelConv(490), 330, 490, 0, 5] 83 | LightList.append(Light) 84 | LightCount += 1 85 | 86 | Light = [LightCount, "Living Room Top Left", pixelConv(147), pixelConv(36), 147, 36, 0, 7] 87 | LightList.append(Light) 88 | LightCount += 1 89 | 90 | Light = [LightCount, "Living Room Top Right", pixelConv(188), pixelConv(47), 188, 47, 0, 6] 91 | LightList.append(Light) 92 | LightCount += 1 93 | 94 | Light = [LightCount, "Living Room Lower Right", pixelConv(188), pixelConv(135), 188, 135, 0, 8] 95 | LightList.append(Light) 96 | LightCount += 1 97 | 98 | 99 | Light = [LightCount, "Living Room Left Bloom", pixelConv(137), pixelConv(126), 137, 126, 0, 9] 100 | LightList.append(Light) 101 | LightCount += 1 102 | 103 | 104 | Light = [LightCount, "Living Room Right Bloom", pixelConv(237), pixelConv(25), 237, 25, 0, 10] 105 | LightList.append(Light) 106 | LightCount += 1 107 | 108 | 109 | Light = [LightCount, "Right Bedroom", pixelConv(31), pixelConv(59), 31, 59, 0, 10] 110 | LightList.append(Light) 111 | LightCount += 1 112 | 113 | 114 | Light = [LightCount, "Left Bedroom", pixelConv(32), pixelConv(115), 31, 115, 0, 10] 115 | LightList.append(Light) 116 | LightCount += 1 117 | 118 | 119 | -------------------------------------------------------------------------------- /ble/blescan2.py: -------------------------------------------------------------------------------- 1 | # BLE iBeaconScanner based on https://github.com/adamf/BLE/blob/master/ble-scanner.py 2 | # JCS 06/07/14 3 | 4 | # BLE scanner based on https://github.com/adamf/BLE/blob/master/ble-scanner.py 5 | # BLE scanner, based on https://code.google.com/p/pybluez/source/browse/trunk/examples/advanced/inquiry-with-rssi.py 6 | 7 | # https://github.com/pauloborges/bluez/blob/master/tools/hcitool.c for lescan 8 | # https://kernel.googlesource.com/pub/scm/bluetooth/bluez/+/5.6/lib/hci.h for opcodes 9 | # https://github.com/pauloborges/bluez/blob/master/lib/hci.c#L2782 for functions used by lescan 10 | 11 | # performs a simple device inquiry, and returns a list of ble advertizements 12 | # discovered device 13 | 14 | # NOTE: Python's struct.pack() will add padding bytes unless you make the endianness explicit. Little endian 15 | # should be used for BLE. Always start a struct.pack() format string with "<" 16 | 17 | import os 18 | import sys 19 | import struct 20 | import bluetooth._bluetooth as bluez 21 | 22 | LE_META_EVENT = 0x3e 23 | LE_PUBLIC_ADDRESS=0x00 24 | LE_RANDOM_ADDRESS=0x01 25 | LE_SET_SCAN_PARAMETERS_CP_SIZE=7 26 | OGF_LE_CTL=0x08 27 | OCF_LE_SET_SCAN_PARAMETERS=0x000B 28 | OCF_LE_SET_SCAN_ENABLE=0x000C 29 | OCF_LE_CREATE_CONN=0x000D 30 | 31 | LE_ROLE_MASTER = 0x00 32 | LE_ROLE_SLAVE = 0x01 33 | 34 | # these are actually subevents of LE_META_EVENT 35 | EVT_LE_CONN_COMPLETE=0x01 36 | EVT_LE_ADVERTISING_REPORT=0x02 37 | EVT_LE_CONN_UPDATE_COMPLETE=0x03 38 | EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE=0x04 39 | 40 | # Advertisment event types 41 | ADV_IND=0x00 42 | ADV_DIRECT_IND=0x01 43 | ADV_SCAN_IND=0x02 44 | ADV_NONCONN_IND=0x03 45 | ADV_SCAN_RSP=0x04 46 | 47 | 48 | def returnnumberpacket(pkt): 49 | myInteger = 0 50 | multiple = 256 51 | for c in pkt: 52 | myInteger += struct.unpack("B",c)[0] * multiple 53 | multiple = 1 54 | return myInteger 55 | 56 | def returnstringpacket(pkt): 57 | myString = ""; 58 | for c in pkt: 59 | myString += "%02x " %struct.unpack("B",c)[0] 60 | return myString 61 | 62 | def printpacket(pkt): 63 | for c in pkt: 64 | sys.stdout.write("%02x " % struct.unpack("B",c)[0]) 65 | print 66 | 67 | def get_packed_bdaddr(bdaddr_string): 68 | packable_addr = [] 69 | addr = bdaddr_string.split(':') 70 | addr.reverse() 71 | for b in addr: 72 | packable_addr.append(int(b, 16)) 73 | return struct.pack(" -1): # beacon found 71 | #print "beaconnumberFound = ", beaconnumber 72 | 73 | if (int(RSSIArray[beaconnumber]) > -1): # reset rolling average 74 | rollingRSSIArray[beaconnumber] = float(beacon[5]) 75 | RSSIArray[beaconnumber] = beacon[5] 76 | TimeStampArray[beaconnumber] = time.time() 77 | kFilteringFactor = 0.1 78 | rollingRSSIArray[beaconnumber] = (int(beacon[5]) * kFilteringFactor) + (rollingRSSIArray[beaconnumber] * (1.0 - kFilteringFactor)) 79 | 80 | 81 | 82 | # distance and accuracy routines 83 | 84 | 85 | def XcalculateDistanceWithRSSI(rssi,beaconnumber): 86 | #formula adapted from David Young's Radius Networks Android iBeacon Code 87 | if (rssi == 0): 88 | return -1.0; # if we cannot determine accuracy, return -1. 89 | 90 | 91 | beacon = conf.BeaconList[beaconnumber]; 92 | txPower = beacon[7] 93 | ratio = float(rssi)*1.0/float(txPower); 94 | if (ratio < 1.0) : 95 | return pow(ratio,10); 96 | else: 97 | accuracy = (0.89976) * pow(ratio,7.7095) + 0.111; 98 | return accuracy; 99 | 100 | 101 | def calculateDistanceWithRSSI(rssi,beaconnumber): 102 | 103 | 104 | beacon = conf.BeaconList[beaconnumber]; 105 | txPower = beacon[7] 106 | ratio_db = txPower - rssi; 107 | ratio_linear = pow(10, ratio_db / 10); 108 | r = pow(ratio_linear, .5); 109 | return r 110 | 111 | 112 | def distanceBetweenTwoPoints(point1, point2): 113 | 114 | dist = ( (point2[0] - point1[0])**2 + (point2[1] - point1[1])**2 )**0.5 115 | return dist 116 | 117 | 118 | def getXYFrom3Beacons(beaconnumbera, beaconnumberb, beaconnumberc, rollingRSSIArray): 119 | 120 | beacona = conf.BeaconList[beaconnumbera]; 121 | beaconb = conf.BeaconList[beaconnumberb]; 122 | beaconc = conf.BeaconList[beaconnumberc]; 123 | xa = float(beacona[2]) 124 | ya = float(beacona[3]) 125 | xb = float(beaconb[2]) 126 | yb = float(beaconb[3]) 127 | xc = float(beaconc[2]) 128 | yc = float(beaconc[3]) 129 | 130 | ra = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumbera], beaconnumbera )) 131 | rb = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumberb], beaconnumberb )) 132 | rc = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumberc], beaconnumberc )) 133 | 134 | #print "xa,ya:", xa,ya, "xb,yb", xb,yb, "xc,yc", xc,yc, "ra, rb, rc", ra, rb, rc 135 | S = (pow(xc, 2.) - pow(xb, 2.) + pow(yc, 2.) - pow(yb, 2.) + pow(rb, 2.) - pow(rc, 2.)) / 2.0 136 | T = (pow(xa, 2.) - pow(xb, 2.) + pow(ya, 2.) - pow(yb, 2.) + pow(rb, 2.) - pow(ra, 2.)) / 2.0 137 | 138 | #print "S,T=", S, T 139 | #print "((ya - yb) * (xb - xc)) ", ((ya - yb) * (xb - xc)) 140 | #print "((yc - yb) * (xb - xa)) ", ((yc - yb) * (xb - xa)) 141 | #print "denominator=", (((ya - yb) * (xb - xc)) - ((yc - yb) * (xb - xa))) 142 | 143 | try: 144 | y = ((T * (xb - xc)) - (S * (xb - xa))) / (((ya - yb) * (xb - xc)) - ((yc - yb) * (xb - xa))) 145 | x = ((y * (ya - yb)) - T) / (xb - xa) 146 | 147 | except ZeroDivisionError as detail: 148 | print 'Handling run-time error:', detail 149 | return [-1,-1] 150 | 151 | point = [x, y] 152 | 153 | return point 154 | 155 | def AlternativeGetXYFrom3Beacons(beaconnumbera, beaconnumberb, beaconnumberc, rollingRSSIArray): 156 | 157 | beacona = conf.BeaconList[beaconnumbera]; 158 | beaconb = conf.BeaconList[beaconnumberb]; 159 | beaconc = conf.BeaconList[beaconnumberc]; 160 | ax = float(beacona[2]) 161 | ay = float(beacona[3]) 162 | bx = float(beaconb[2]) 163 | by = float(beaconb[3]) 164 | cx = float(beaconc[2]) 165 | cy = float(beaconc[3]) 166 | 167 | dA = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumbera], beaconnumbera )) 168 | dB = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumberb], beaconnumberb )) 169 | dC = float(calculateDistanceWithRSSI(rollingRSSIArray[beaconnumberc], beaconnumberc )) 170 | 171 | x = ( ( (pow(dA,2)-pow(dB,2)) + (pow(cx,2)-pow(ax,2)) + (pow(by,2)-pow(ay,2)) ) * (2*cy-2*by) - ( (pow(dB,2)-pow(dC,2)) + (pow(cx,2)-pow(cx,2)) + (pow(cy,2)-pow(by,2)) ) *(2*by-2*ay) ) / ( (2*bx-2*cx)*(2*by-2*ay)-(2*ax-2*bx)*(2*cy-2*by) ); 172 | 173 | y = ( (pow(dA,2)-pow(dB,2)) + (pow(cx,2)-pow(ax,2)) + (pow(by,2)-pow(ay,2)) + x*(2*ax-2*bx)) / (2*by-2*ay); 174 | 175 | 176 | point = [x, y] 177 | 178 | return point 179 | 180 | 181 | def haveThreeGoodBeacons(rollingRSSIArray): 182 | 183 | goodbeacons = 0 184 | for i in range(0, len(rollingRSSIArray)): 185 | if (rollingRSSIArray[i] < 0): 186 | goodbeacons += 1 187 | print "goodbeacons=", goodbeacons 188 | return goodbeacons 189 | 190 | 191 | def get_item(item): 192 | return item[1] 193 | 194 | def get3ClosestBeacons(rollingRSSIArray): 195 | 196 | #print ("len=", len(rollingRSSIArray)) 197 | sortedBeacons = [] 198 | for i in range(0, len(rollingRSSIArray)): 199 | sortedBeacons.append([i, rollingRSSIArray[i]]) 200 | 201 | #print("beaconlist=", sortedBeacons) 202 | 203 | mySorted = sorted(sortedBeacons, key=get_item) 204 | 205 | #print("as beaconlist=", mySorted) 206 | 207 | return [mySorted[0][0], mySorted[1][0], mySorted[2][0]] 208 | 209 | 210 | # debugging routines 211 | 212 | 213 | def printBeaconStatus(beacon, RSSIArray, TimeStampArray, rollingRSSIArray): 214 | 215 | print "----------" 216 | print "BeaconNumber: ", beacon[0] 217 | print "BeaconName: ", beacon[1] 218 | print "x,y: ", beacon[2], beacon[3] 219 | print "UDID: ", beacon[4] 220 | print "Major: ", beacon[5] 221 | print "Minor: ", beacon[6] 222 | print "Last RSSI: ", RSSIArray[beacon[0]] 223 | print "Rolling RSSI: ", rollingRSSIArray[beacon[0]] 224 | print "TimeStamp: ", datetime.datetime.fromtimestamp(TimeStampArray[beacon[0]]) 225 | print "----------" 226 | 227 | def printBeaconDistance(beacon, RSSIArray, TimeStampArray,rollingRSSIArray): 228 | 229 | print "BN: ", beacon[0],"x,y: ", beacon[2], beacon[3],"RSSI:", RSSIArray[beacon[0]], "rollingRSSI: %3.2f" % rollingRSSIArray[beacon[0]] , "Distance: %3.2f" % calculateDistanceWithRSSI(rollingRSSIArray[beacon[0]], beacon[0]) 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /BeaconAir.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # BeaconAir - Reads iBeacons and controls HUE lights 4 | # JCS 6/7/14 5 | # 6 | # 7 | import sys 8 | import time 9 | import utils 10 | 11 | sys.path.append('./ble') 12 | sys.path.append('./config') 13 | 14 | # if conflocal.py is not found, import default conf.py 15 | 16 | # Check for user imports 17 | try: 18 | import conflocal as conf 19 | except ImportError: 20 | import conf 21 | 22 | 23 | 24 | 25 | 26 | import bleThread 27 | 28 | import lights 29 | import webmap 30 | import bubblelog 31 | import iBeaconChart 32 | 33 | from threading import Thread 34 | from Queue import Queue 35 | 36 | # State Variables 37 | 38 | currentiBeaconRSSI=[] 39 | rollingiBeaconRSSI=[] 40 | currentiBeaconTimeStamp=[] 41 | 42 | # Light State Variables 43 | 44 | currentLightState= [] 45 | 46 | LIGHT_BRIGHTNESS_SENSITIVITY = 2.0 47 | LIGHT_DISTANCE_SENSITIVITY = 2.0 48 | BEACON_ON = True 49 | DISPLAY_BEACON_ON = True 50 | DISPLAY_LIGHTS_ON = True 51 | 52 | # init state variables 53 | for beacon in conf.BeaconList: 54 | currentiBeaconRSSI.append(0) 55 | rollingiBeaconRSSI.append(0) 56 | currentiBeaconTimeStamp.append(time.time()) 57 | 58 | # init light state variables 59 | for light in conf.LightList: 60 | currentLightState.append(0) 61 | 62 | lights.initializeHue('192.168.1.6') 63 | 64 | lights.setInitialLightState(currentLightState) 65 | 66 | # recieve commands from RasPiConnect Execution Code 67 | 68 | def completeCommand(): 69 | 70 | f = open("/home/pi/BeaconAir/state/BeaconAirCommand.txt", "w") 71 | f.write("DONE") 72 | f.close() 73 | 74 | def processCommand(): 75 | global LIGHT_BRIGHTNESS_SENSITIVITY 76 | global LIGHT_DISTANCE_SENSITIVITY 77 | global BEACON_ON 78 | global DISPLAY_BEACON_ON 79 | global DISPLAY_LIGHTS_ON 80 | global currentLightState 81 | 82 | f = open("/home/pi/BeaconAir/state/BeaconAirCommand.txt", "r") 83 | command = f.read() 84 | f.close() 85 | 86 | if (command == "") or (command == "DONE"): 87 | # Nothing to do 88 | return False 89 | 90 | # Check for our commands 91 | 92 | print "Processing Command: ", command 93 | 94 | if (command == "BEACONON"): 95 | BEACON_ON = True 96 | completeCommand() 97 | return True 98 | 99 | if (command == "BEACONOFF"): 100 | BEACON_ON = False 101 | completeCommand() 102 | return True 103 | 104 | if (command == "ALLLIGHTSON"): 105 | lights.allLights(True, currentLightState ) 106 | completeCommand() 107 | return True 108 | 109 | if (command == "ALLLIGHTSOFF"): 110 | lights.allLights(False, currentLightState) 111 | completeCommand() 112 | return True 113 | 114 | if (command == "BEACONON"): 115 | BEACON_ON = True 116 | completeCommand() 117 | return True 118 | 119 | if (command == "BEACONOFF"): 120 | BEACON_ON = False 121 | completeCommand() 122 | return True 123 | 124 | if (command == "DISPLAYBEACONON"): 125 | DISPLAY_BEACON_ON = True 126 | completeCommand() 127 | return True 128 | 129 | if (command == "DISPLAYBEACONOFF"): 130 | DISPLAY_BEACON_ON = False 131 | completeCommand() 132 | return True 133 | 134 | if (command == "DISPLAYLIGHTSON"): 135 | DISPLAY_LIGHTS_ON = True 136 | completeCommand() 137 | return True 138 | 139 | if (command == "DISPLAYLIGHTSOFF"): 140 | DISPLAY_LIGHTS_ON = False 141 | completeCommand() 142 | return True 143 | 144 | if (command == "UPDATESENSITIVITIES"): 145 | 146 | try: 147 | f = open("/home/pi/BeaconAir/state/distanceSensitivity.txt", "r") 148 | commandresponse = f.read() 149 | LIGHT_DISTANCE_SENSITIVITY = float(commandresponse) 150 | f.close() 151 | except: 152 | LIGHT_DISTANCE_SENSITIVITY = 2.0 153 | 154 | try: 155 | f = open("/home/pi/BeaconAir/state/brightnessSensitivity.txt", "r") 156 | commandresponse = f.read() 157 | f.close() 158 | LIGHT_BRIGHTNESS_SENSITIVITY = float(commandresponse) 159 | except: 160 | LIGHT_BRIGHTNESS_SENSITIVITY = 2.0 161 | print "LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY= ", LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY 162 | completeCommand() 163 | return True 164 | 165 | completeCommand() 166 | return True 167 | 168 | # build configuration Table 169 | 170 | 171 | # set up BLE thread 172 | # set up a communication queue 173 | 174 | 175 | 176 | queueBLE = Queue() 177 | BLEThread = Thread(target=bleThread.bleDetect, args=(__name__,10,queueBLE,)) 178 | BLEThread.daemon = True 179 | BLEThread.start() 180 | 181 | bubblelog.writeToBubbleLog("BeaconAir Started") 182 | 183 | # the main loop of BeaconAir 184 | myPosition = [0,0] 185 | lastPosition = [1,1] 186 | beacons = [] 187 | while True: 188 | if (BEACON_ON == True): 189 | # check for iBeacon Updates 190 | print "Queue Length =", queueBLE.qsize() 191 | if (queueBLE.empty() == False): 192 | result = queueBLE.get(False) 193 | print "------" 194 | utils.processiBeaconList(result,currentiBeaconRSSI, currentiBeaconTimeStamp,rollingiBeaconRSSI) 195 | utils.clearOldValues(10,currentiBeaconRSSI, currentiBeaconTimeStamp,rollingiBeaconRSSI) 196 | for beacon in conf.BeaconList: 197 | utils.printBeaconDistance(beacon, currentiBeaconRSSI, currentiBeaconTimeStamp,rollingiBeaconRSSI) 198 | # update position 199 | if (utils.haveThreeGoodBeacons(rollingiBeaconRSSI) >= 3): 200 | oldbeacons = beacons 201 | beacons = utils.get3ClosestBeacons(rollingiBeaconRSSI) 202 | print "beacons=", beacons 203 | if (cmp(oldbeacons, beacons) != 0): 204 | bubblelog.writeToBubbleLog("closebeacons:%i,%i,%i" % (beacons[0], beacons[1], beacons[2])) 205 | 206 | # setup for Kludge 207 | #rollingiBeaconRSSI[7] = rollingiBeaconRSSI[6] 208 | 209 | myPosition = utils.getXYFrom3Beacons(beacons[0],beacons[1],beacons[2], rollingiBeaconRSSI) 210 | print "myPosition1 = %3.2f,%3.2f" % (myPosition[0], myPosition[1]) 211 | #bubblelog.writeToBubbleLog("position updated:%3.2f,%3.2f" % (myPosition[0], myPosition[1])) 212 | 213 | # calculate jitter in position 214 | jitter = (((lastPosition[0] - myPosition[0])/lastPosition[0]) + ((lastPosition[1] - myPosition[1])/lastPosition[1]))/2.0 215 | jitter = jitter * 100.0 # to get to percent 216 | lastPosition = myPosition 217 | print "jitter=", jitter 218 | 219 | f = open("/home/pi/BeaconAir/state/distancejitter.txt", "w") 220 | 221 | f.write(str(jitter)) 222 | f.close() 223 | 224 | for light in conf.LightList: 225 | lightdistance = utils.distanceBetweenTwoPoints([light[2],light[3]], myPosition) 226 | print "distance to light %i : %3.2f" % (light[0], lightdistance) 227 | print "LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY= ", LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY 228 | lights.checkForLightTrigger(myPosition, LIGHT_DISTANCE_SENSITIVITY, LIGHT_BRIGHTNESS_SENSITIVITY, currentLightState) 229 | print "DISPLAY_BEACON_ON, DISPLAY_LIGHTS_ON", DISPLAY_BEACON_ON, DISPLAY_LIGHTS_ON 230 | # build webpage 231 | webmap.buildWebMapToFile(myPosition, rollingiBeaconRSSI, currentLightState, DISPLAY_BEACON_ON, DISPLAY_LIGHTS_ON) 232 | 233 | # build beacon count graph 234 | iBeaconChart.iBeacondetect(rollingiBeaconRSSI) 235 | else: 236 | # lost position 237 | myPosition = [-myPosition[0], -myPosition[1]] 238 | 239 | #print currentiBeaconRSSI 240 | #print currentiBeaconTimeStamp 241 | 242 | # end of BEACON_ON - always process commands 243 | else: 244 | if (queueBLE.empty() == False): 245 | result = queueBLE.get(False) 246 | print "------" 247 | print "Beacon Disabled" 248 | # process commands from RasPiConnect 249 | 250 | processCommand() 251 | 252 | time.sleep(0.25) 253 | --------------------------------------------------------------------------------