├── Vestigo Base ├── web │ ├── coords.cfg │ ├── logo.png │ ├── blueprint.png │ ├── coord.html │ ├── style.css │ └── view.html ├── addresses.cfg ├── vestigo_base.ini ├── locations.cfg ├── vestigo_base.py ├── logger.py ├── settings.py └── server.py ├── Vestigo ├── vestigo.ini ├── vestigo.py ├── logger.py ├── settings.py └── scan.py ├── LICENSE.txt └── readme.md /Vestigo Base/web/coords.cfg: -------------------------------------------------------------------------------- 1 | {"Office":[553,387],"Bedroom":[177,200]} -------------------------------------------------------------------------------- /Vestigo Base/web/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteveAbb/Vestigo/HEAD/Vestigo Base/web/logo.png -------------------------------------------------------------------------------- /Vestigo Base/web/blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteveAbb/Vestigo/HEAD/Vestigo Base/web/blueprint.png -------------------------------------------------------------------------------- /Vestigo Base/addresses.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "ble": { 3 | "FF:E7:CC:83:D0:8E": { 4 | "name": "Watch", 5 | "timeout": 5 6 | } 7 | }, 8 | "disc": { }, 9 | "nonDisc": { } 10 | } -------------------------------------------------------------------------------- /Vestigo Base/vestigo_base.ini: -------------------------------------------------------------------------------- 1 | [Base Server] 2 | #Port: 8080 3 | #ForwardData: http://abbagnaro.com 4 | #ForwardTimeout: 8 5 | #Recache: 60 6 | #ForwardLocation: True 7 | 8 | [Logging] 9 | #UseLog: False 10 | #File: vestigoBase.log 11 | #Size In Kilobytes 12 | #MaxSize: 1024 13 | #FileCount=5 14 | #STDOUT: True -------------------------------------------------------------------------------- /Vestigo/vestigo.ini: -------------------------------------------------------------------------------- 1 | [Scan Modes] 2 | #LE: True 3 | #Discoverable: True 4 | #NonDiscoverable: True 5 | 6 | [Read Timeouts] 7 | #Timeout In Seconds 8 | #Discoverable: 2 9 | #NonDiscoverable: 5 10 | 11 | [Base Server] 12 | #Reader: Reader1 13 | URL: http://localhost:8080 14 | #Timeout: 10 15 | #Recache: 60 16 | 17 | [Logging] 18 | #UseLog: False 19 | #File: vestigo.log 20 | #Size In Kilobytes 21 | #MaxSize: 1024 22 | #FileCount=5 23 | #STDOUT: True -------------------------------------------------------------------------------- /Vestigo Base/locations.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "Office": 3 | [ 4 | { 5 | "reader":"Reader1", 6 | "rssi": 7 | { 8 | "min":-70, 9 | "max":0 10 | }, 11 | "grpr": 12 | { 13 | "min":-10, 14 | "max":50 15 | } 16 | } 17 | ], 18 | "Bedroom": 19 | [ 20 | { 21 | "reader":"Reader2", 22 | "rssi": 23 | { 24 | "min":-70, 25 | "max":0 26 | }, 27 | "grpr": 28 | { 29 | "min":-50, 30 | "max":50 31 | } 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /Vestigo Base/vestigo_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from settings import Settings 4 | from server import Server 5 | from logger import Logger 6 | 7 | def main(): 8 | try: 9 | #Read config file 10 | settings=Settings() 11 | 12 | #Set up logger 13 | logger=Logger(settings) 14 | 15 | #Create scanner 16 | server=Server(settings,logger) 17 | 18 | #Begin scanning 19 | server.start() 20 | 21 | except KeyboardInterrupt: 22 | server.stop() 23 | 24 | if __name__ == "__main__": 25 | main() 26 | 27 | -------------------------------------------------------------------------------- /Vestigo/vestigo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from settings import Settings 4 | from scan import Scanner 5 | from logger import Logger 6 | 7 | def main(): 8 | try: 9 | #Read config file 10 | settings=Settings() 11 | 12 | #Set up logger 13 | logger=Logger(settings) 14 | 15 | #Create scanner 16 | scanner=Scanner(settings,logger) 17 | 18 | #Begin scanning 19 | scanner.StartScanning() 20 | 21 | except KeyboardInterrupt: 22 | scanner.StopScanning() 23 | 24 | if __name__ == "__main__": 25 | main() 26 | 27 | -------------------------------------------------------------------------------- /Vestigo/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from settings import Settings 4 | import logging 5 | import logging.handlers 6 | import time 7 | 8 | class Logger(): 9 | def __init__(self,settings): 10 | self._logFile=settings.logging_File 11 | self._logger = logging.getLogger('vestigo') 12 | self._logger.setLevel(logging.DEBUG) 13 | self._settings=settings 14 | handler = logging.handlers.RotatingFileHandler(self._logFile, maxBytes=1024*int(settings.logging_MaxSize), backupCount=int(settings.logging_FileCount)) 15 | 16 | self._logger.addHandler(handler) 17 | 18 | def log(self,data): 19 | data="["+time.strftime('%X %x')+"] "+str(data) 20 | if(self._settings.logging_STDOUT): 21 | print data 22 | if(self._settings.logging_UseLog): 23 | self._logger.debug(data) -------------------------------------------------------------------------------- /Vestigo Base/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from settings import Settings 4 | import logging 5 | import logging.handlers 6 | import time 7 | 8 | class Logger(): 9 | def __init__(self,settings): 10 | self._logFile=settings.logging_File 11 | self._logger = logging.getLogger('VestigoBase') 12 | self._logger.setLevel(logging.DEBUG) 13 | self._settings=settings 14 | handler = logging.handlers.RotatingFileHandler(self._logFile, maxBytes=1024*int(settings.logging_MaxSize), backupCount=int(settings.logging_FileCount)) 15 | 16 | self._logger.addHandler(handler) 17 | 18 | def log(self,data): 19 | data="["+time.strftime('%X %x')+"] "+str(data) 20 | if(self._settings.logging_STDOUT): 21 | print data 22 | if(self._settings.logging_UseLog): 23 | self._logger.debug(data) -------------------------------------------------------------------------------- /Vestigo Base/web/coord.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 20 | 21 | 22 |
23 |
117 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/Vestigo/scan.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import pexpect
4 | import subprocess
5 | import thread
6 | from multiprocessing.pool import ThreadPool
7 | import time
8 | from settings import Settings
9 | import requests
10 | import json
11 | from logger import Logger
12 |
13 | class Scanner():
14 | def __init__(self,settings,logger):
15 | self._logger=logger
16 | self._bleScanProc = None
17 | self._scanProc=None
18 | self._addrToRssi={}
19 | self._keepScanning=True
20 | self._settings=settings
21 | self._lastFetch=None
22 | self._nonDiscKeepScanning=True
23 | self._addresses=None
24 |
25 | def log(self,data):
26 | self._logger.log(data)
27 |
28 | def getAddresses(self,type=None):
29 | fetchNewAssets=False
30 | if(self._lastFetch is not None):
31 | if((time.time()-self._lastFetch)>int(self._settings.baseServer_Recache)):
32 | fetchNewAssets=True
33 | else:
34 | fetchNewAssets=True
35 |
36 | if(fetchNewAssets):
37 | try:
38 | self._lastFetch=time.time()
39 | self.log("Recaching")
40 | resp=requests.get(self._settings.baseServer_URL+"/addresses/?reader="+self._settings.baseServer_Reader,timeout=int(self._settings.baseServer_Timeout))
41 | self._addresses=resp.json()
42 | self.log("Finished recache.")
43 | self._nonDiscKeepScanning=False
44 | except Exception,error:
45 | self.log("Error rechaching addresses: "+str(error)+". Will retry on next recache.")
46 |
47 | if(type is None):
48 | return self._addresses
49 | elif(type is "all"):
50 | return dict(self._addresses["ble"].items() + self._addresses["disc"].items()+self._addresses["nonDisc"].items())
51 | else:
52 | return self._addresses[type]
53 |
54 | def send_payload(self,addr,rssi):
55 | if(addr in self.getAddresses("all")):
56 | self._addrToRssi[addr]=rssi;
57 | if(addr in self.getAddresses("ble")):
58 | type="BLE"
59 | elif(addr in self.getAddresses("disc")):
60 | type="Discoverable"
61 | else:
62 | type="Non-Discoverable"
63 |
64 | if((type=="BLE" and self._settings.scanMode_LE) or (type=="Discoverable" and self._settings.scanMode_Disc) or (type=="Non-Discoverable" and self._settings.scanMode_NonDisc)):
65 | try:
66 | payload={"reader":self._settings.baseServer_Reader,"name":self.getAddresses("all")[addr]["name"],"address":addr,"rssi":rssi,"type":type}
67 | self.log("Sending payload to: "+self._settings.baseServer_URL)
68 | self.log("Payload:")
69 | self.log(json.dumps(payload,indent=4))
70 | headers = {'content-type': 'application/json'}
71 | resp = requests.post(self._settings.baseServer_URL, data=json.dumps(payload), headers=headers,timeout=int(self._settings.baseServer_Timeout))
72 | self.log("Resp: "+str(resp.status_code))
73 | except Exception, error:
74 | self.log("Error with request: "+str(error))
75 |
76 | def scanProcessSpawner(self):
77 | while self._keepScanning:
78 | self._scanProc = subprocess.Popen(['hcitool', 'scan'],cwd='/usr/bin',stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
79 | self._scanProc.wait()
80 | if(self._settings.readTimeout_Disc>0):
81 | time.sleep(self._settings.readTimeout_Disc)
82 |
83 | def nonDiscScanProcessSpawner(self,addr):
84 | while (self._nonDiscKeepScanning):
85 | result=pexpect.run("hcitool rssi "+addr,timeout=None)
86 | if "RSSI return value:" not in result:
87 | pexpect.run("hcitool cc "+addr,timeout=None)
88 | else:
89 | rssi=result.rstrip().split(": ")[1]
90 |
91 | if(addr in self._addrToRssi):
92 | if (rssi!=self._addrToRssi[addr]):
93 | self.send_payload(addr,rssi)
94 | else:
95 | self.send_payload(addr,rssi)
96 | if(self._settings.readTimeout_NonDisc>0):
97 | time.sleep(self._settings.readTimeout_NonDisc)
98 |
99 | def nonDiscScanPoolInitiate(self):
100 | pool = ThreadPool(processes=10)
101 | while self._keepScanning:
102 | self.log("Mapping non-discoverable addresses to thread pool executors")
103 | self._nonDiscKeepScanning=True
104 | pool.map(self.nonDiscScanProcessSpawner, self.getAddresses("nonDisc").keys())
105 |
106 | def bleScanProccessSpawnerAsync(self):
107 | self._bleScanProc = subprocess.Popen(['hcitool', 'lescan', '--duplicates'],cwd='/usr/bin',stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
108 |
109 | def parseHcidump(self):
110 | self.log("Starting parse of hcidump stdout")
111 | child=pexpect.spawn("hcidump",timeout=None)
112 | while self._keepScanning:
113 | child.expect("(([0-9A-F]{2}[:-]){5}([0-9A-F]{2}))",timeout=None)
114 | addr=child.after
115 | if (addr in self.getAddresses("nonDisc")):
116 | continue
117 | child.expect("(-\d{2})",timeout=None)
118 | rssi=child.after
119 |
120 | if(addr in self._addrToRssi):
121 | if (rssi!=self._addrToRssi[addr]):
122 | self.send_payload(addr,rssi)
123 | else:
124 | self.send_payload(addr,rssi)
125 |
126 | def StartScanning(self):
127 | self.log(json.dumps(self.getAddresses(), indent=4))
128 | if(self._settings.scanMode_LE):
129 | self.log("Spawning BLE scan child proccess")
130 | self.bleScanProccessSpawnerAsync()
131 | if(self._settings.scanMode_Disc):
132 | self.log("Spawning discoverable child process")
133 | thread.start_new_thread (self.scanProcessSpawner, ())
134 | if(self._settings.scanMode_NonDisc):
135 | self.log("Initiating thread pool for non discoverable addresses")
136 | thread.start_new_thread (self.nonDiscScanPoolInitiate, ())
137 | self.parseHcidump()
138 |
139 | def StopScanning(self):
140 | self._keepScanning=False
141 | if(self._settings.scanMode_LE):
142 | self.log("Killing low energy scan child process")
143 | self.log(self._bleScanProc.pid)
144 | try: self._bleScanProc.terminate()
145 | except(OSError): pass
146 | self.log("Killed.")
147 | if(self._settings.scanMode_Disc):
148 | self.log("Killing normal scan child process")
149 | self.log(self._scanProc.pid)
150 | try: self._scanProc.terminate()
151 | except(OSError): pass
152 | self.log("Killed.")
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Vestigo - Bluetooth tracking system
2 | ========================================================
3 |
4 | What is it
5 | ------------
6 | Vestigo is a proof of concept and educational project I took on to learn about, develop and show the ability and practicality of using the Bluetooth standard as a means to track asset locations in real-time.
7 |
8 | How it works
9 | ------------
10 | Vestigo currently supports three modes of reading a Bluetooth device:
11 |
12 | - Discoverable
13 | - A Bluetooth device that is configured as discoverable.
14 | - Non-Discoverable
15 | - A Bluetooth device that accepts connections but is not Broadcasting itself
16 | - Low Energy (BLE)
17 | - A Bluetooth 4.0 Low Energy device that is discoverable
18 |
19 | Discoverable and BLE devices return an standard RSSI where-as Non-Discoverable devices currently only return their golden receive power range (GRPR). It is important to note that Vestigo does not attempt to pair with any devices (unless it's non-discoverable, in which case it continuously attempts to connect, to obtain the device's GRPR, but it will never successfully pair).
20 |
21 | There are two sets up scripts. One set: `Vestigo`, is meant to be run on the reading devices, and the other set: `Vestigo Base` is meant to be run on a central server that each reader can connect to. I've been using Raspberry Pi's as cheap readers with a WiFi and Bluetooth adapter and they work great.
22 |
23 | On the base server, you will be able to configure the location rules that associate a device's RSSI or GRPR to a corresponding location identifier through the definition of RSSI/GRPR thresholds.
24 |
25 | Configuration
26 | -------------
27 | ### Vestigo (Reader)
28 |
29 | Vestigo has one configuration file: `Vestigo\vestigo.ini`.
30 | This configuration file allows the user to
31 |
32 | - Enable/disable scan modes (BLE, Discoverable, Non-Discoverable)
33 | *It is important to note that enabling all scan modes at once is with no timeouts is discouraged as I've noticed it tends to bog down most adapters I've tested with
34 | - Define read timeouts - seconds between each attempt to read
35 | - Configure base server host, request timeout and rechace period in seconds (period between each request for updated assets)
36 | - Configure logging
37 |
38 | ### Vestigo Base (Server)
39 | Vestigo Base has one configuration file: `Vestigo Base\vestigo.ini`.
40 | This configuration file allows the user to
41 |
42 | - Define listen port for HTTP server
43 | - Define URL to forward all data to (useful if you want to write a web application that uses WebSockets and not have to poll the server, or if you want to store the data to a database)
44 | - Set recache period in seconds, for reloading locations and assets
45 | - Configure logging
46 |
47 | To configure assest and location rules, edit the JSON located in: `Vestigo Base\addresses.cfg` and `Vestigo Base\locations.cfg`
48 |
49 | ### Vestigo Base Web Server
50 |
51 | The Vestigo Base web server allows you to poll it for asset state information using the following request: http://HOST:PORT/states. It's states will be returned in JSON.
52 |
53 | I made a simple web application that servers up all the content in `Vestigo Base\web\` to a browser when a request is made to it's host with no parameters: http://HOST:PORT
54 |
55 | The web application allows you to provide a blueprint image: `Vestigo Base\web\blueprint.png` and configure locations to coordinates on that image through the use of the `Vestigo Base\web\coords.cfg` file.
56 |
57 | Sample screenshot below:
58 |
59 | 
60 |
61 | Hardware
62 | --------
63 | For testing I used a series of Model B Raspberry Pis as readers. I am running Raspbian on each of them, but have also tested it with Arch. The adapter I find that works the best for the Raspberry Pi (requires no drivers on Raspbian) and supports Bluetooth 4.0 is the Plugable USB-BT4LE Bluetooth 4.0 USB Adapter.
64 |
65 | I am currently using a series of Raspberry Pis to track my iPad and iPhone (non-discoverable devices with Bluetooth enabled), and a set of Stick N' Find BLE tags, throughout my house using a series of nested location rules with appropriate RSSI/GRPR thresholds.
66 |
67 | How to install it
68 | -----------------
69 | To install the Vestigo reader on a Raspberry Pi running Raspbian, follow these steps:
70 |
71 | Install the Bluetooth support package
72 |
73 | apt-get install bluetooth
74 |
75 | Verify the bluetooth daemon is running
76 |
77 | /etc/init.d/bluetooth status
78 |
79 | Verify your adapter is recognized
80 |
81 | hcitool dev
82 |
83 | Install hcidump
84 |
85 | apt-get install bluez-hcidump
86 |
87 | Install python library "requests"
88 | Using easy_install:
89 |
90 | easy_install install requests
91 |
92 | or using pip:
93 |
94 | pip install requests
95 |
96 |
97 | That's it!
98 |
99 | To start the reader run
100 |
101 | python vestigo.py
102 |
103 | To start the base server run
104 |
105 | python vestigo_base.py
106 |
107 |
108 | Remember to add devices to the addresses.cfg file. An easy way to find an address of a device is to use
109 |
110 | hcitool scan
111 |
112 | for discoverable devices, or
113 |
114 | hcitool lescan
115 |
116 | for BLE devices. For non-discoverable devices, put them into discoverable (such as opening up the bluetooth screen on iOS), and then capture their address before making them non-discoverable.
117 |
118 | Future features
119 | ---------------
120 | I have a load of features and enhancements I'd like to add to this project when I find more time. Here is a list of some that I'd like to add:
121 | - Proper daemonization of the reader and server processes
122 | - WebSocket based web application demo
123 | - Normalization modules for different types of device adapters and reader adapters. Allowing you to relationally skew the incoming SSI of each device based on the reader and devices adapter (since different types of adapters give off different SSI readings)
124 | - Research better means for getting a more accurate distance read of devices. Factor in Link Query?
125 | - Triangulation
126 | - Support for Non-Discoverable BLE devices (New Stick N' Finds)
127 |
--------------------------------------------------------------------------------
/Vestigo Base/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import thread
4 | import time
5 | from settings import Settings
6 | import requests
7 | import json
8 | from logger import Logger
9 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
10 | from urlparse import urlparse, parse_qs
11 | from SocketServer import ThreadingMixIn
12 | import threading
13 | from threading import Timer
14 | import collections
15 | import copy
16 |
17 | class Server():
18 | def __init__(self,settings,logger):
19 | self.assetToPayload={}
20 | self._logger=logger
21 | self._settings=settings
22 | self._lastFetchLocations=None
23 | self._lastFetchAddresses=None
24 | self._locations=None
25 | self._addresses=None
26 | self._addressToTimer = {}
27 | self._server = VestigoHTTPServer(("", self._settings.baseServer_Port), HTTPHandler, self.log,self.processPayload,self.getAddresses,self.assetToPayload)
28 |
29 | def processPayload(self, payload):
30 |
31 | lastLocation = None;
32 |
33 | if(self._settings.baseServer_ForwardLocation):
34 | if(payload["address"] in self.assetToPayload and "location" in self.assetToPayload[payload["address"]]):
35 | lastLocation = self.assetToPayload[payload["address"]]["location"]
36 |
37 | if("outofrange" in payload and payload["outofrange"]):
38 | self.log("Timeout timer elapsed. Moving " + payload["name"] + " into location: out of range.");
39 | payload["location"]="out of range";
40 | payload.pop("outofrange",None);
41 | else:
42 | ruleMatches=False
43 | for location in self.getLocations():
44 | for rule in self.getLocations(location):
45 | if(rule["reader"]==payload["reader"]):
46 | if(payload["address"] in self.getAddresses("nonDisc")):
47 | if(int(payload["rssi"])>=int(rule["grpr"]["min"]) and int(payload["rssi"])<=int(rule["grpr"]["max"])):
48 | ruleMatches=True
49 | else:
50 | if(int(payload["rssi"])>=int(rule["rssi"]["min"]) and int(payload["rssi"])<=int(rule["rssi"]["max"])):
51 | ruleMatches=True
52 |
53 | if(ruleMatches):
54 | self.log("Name: "+payload["name"]+" is in location: "+location)
55 | payload["location"]=location
56 | break
57 | if(ruleMatches):
58 | break
59 |
60 | if(ruleMatches and "timeout" in self.getAddresses("all")[payload["address"]]):
61 | if(payload["address"] in self._addressToTimer):
62 |
63 | self.log("Reseting timeout timer for " + payload["name"]);
64 | self._addressToTimer[payload["address"]].cancel();
65 | else:
66 | self.log("Starting timer for " + payload["name"] + " for " + str( self.getAddresses("all")[payload["address"]]["timeout"]) + " seconds.");
67 |
68 | timerPayload = copy.deepcopy(payload);
69 | timerPayload["rssi"] = 0
70 | timerPayload["reader"] = ""
71 | timerPayload["outofrange"] = True;
72 |
73 | timer = Timer(self.getAddresses("all")[payload["address"]]["timeout"], self.processPayload, (timerPayload,));
74 |
75 | self._addressToTimer[payload["address"]] = timer
76 | self._addressToTimer[payload["address"]].start()
77 |
78 | self.assetToPayload[payload["address"]]=payload;
79 |
80 | if(self._settings.baseServer_ForwardData is not None):
81 | if(self._settings.baseServer_ForwardLocation):
82 | if("location" not in payload or "location" in payload and lastLocation == payload["location"]):
83 | return
84 | self.log("Forwarding payload off to: "+self._settings.baseServer_ForwardData)
85 | try:
86 | self.log("Forward Payload: ")
87 | self.log(json.dumps(payload,indent=4))
88 | headers = {'content-type': 'application/json'}
89 | resp = requests.post(self._settings.baseServer_ForwardData, data=json.dumps(payload), headers=headers,timeout=int(self._settings.baseServer_ForwardTimeout))
90 | self.log("Resp: "+str(resp.status_code))
91 | except Exception, error:
92 | self.log("Error with forward request: "+str(error))
93 |
94 | def getAddresses(self,type=None):
95 | fetchNew=False
96 | if(self._lastFetchAddresses is not None):
97 | if((time.time()-self._lastFetchAddresses)>int(self._settings.baseServer_Recache)):
98 | fetchNew=True
99 | else:
100 | fetchNew=True
101 |
102 | if(fetchNew):
103 | try:
104 | self._lastFetchAddresses=time.time()
105 | self.log("Recaching addresses.")
106 | f = open("addresses.cfg")
107 | self._addresses=json.loads(f.read())
108 | f.close()
109 | self.log("Finished recache.")
110 | except Exception,error:
111 | self.log("Error rechaching addresses: "+str(error)+". Will retry on next recache.")
112 |
113 | if(type is None):
114 | return self._addresses
115 | elif(type is "all"):
116 | return dict(self._addresses["ble"].items() + self._addresses["disc"].items()+self._addresses["nonDisc"].items())
117 | else:
118 | return self._addresses[type]
119 |
120 | def getLocations(self,location=None):
121 | fetchNew=False
122 | if(self._lastFetchLocations is not None):
123 | if((time.time()-self._lastFetchLocations)>int(self._settings.baseServer_Recache)):
124 | fetchNew=True
125 | else:
126 | fetchNew=True
127 |
128 | if(fetchNew):
129 | try:
130 | self._lastFetchLocations=time.time()
131 | self.log("Recaching locations.")
132 | f = open("locations.cfg")
133 | self._locations=json.loads(f.read(),object_pairs_hook=collections.OrderedDict)
134 | f.close()
135 | self.log("Finished recache.")
136 | except Exception,error:
137 | self.log("Error rechaching locations: "+str(error)+". Will retry on next recache.")
138 | if location is None:
139 | return self._locations
140 | else:
141 | return self._locations[location]
142 |
143 | def log(self,data):
144 | self._logger.log(data)
145 |
146 | def start(self):
147 | self.log("Base server starting on port: "+str(self._settings.baseServer_Port))
148 | self._server.serve_forever()
149 |
150 | def stop(self):
151 | self.log("Base server shutting down...")
152 | self._server.shutdown()
153 | self.log("Killing all timers.");
154 | for key in self._addressToTimer:
155 | try:
156 | self._addressToTimer[key].cancel();
157 | except:
158 | pass
159 |
160 | class VestigoHTTPServer(ThreadingMixIn, HTTPServer):
161 | def __init__(self, server_address, RequestHandlerClass, log, processPayload,getAddresses,assetToPayload):
162 | HTTPServer.__init__(self, server_address, RequestHandlerClass)
163 | self.log = log
164 | self.processPayload=processPayload
165 | self.getAddresses=getAddresses
166 | self.assetToPayload=assetToPayload
167 |
168 | class HTTPHandler(BaseHTTPRequestHandler):
169 | def do_GET(self):
170 | dirs=self.path.split("/")
171 | if(len(dirs)>1):
172 | resource=dirs[1]
173 | if(resource == "addresses"):
174 | try:
175 | queryStr=parse_qs(urlparse(self.path).query)
176 | reader=str(queryStr["reader"][0])
177 | self.send_response(200)
178 | self.send_header('Content-type',"application/json")
179 | self.end_headers()
180 | self.wfile.write(json.dumps(self.server.getAddresses()))
181 | except IOError as e:
182 | self.send_response(404)
183 | self.server.log("Error with processing readers request for addresses: "+str(e))
184 | elif(resource == "states"):
185 | try:
186 | self.send_response(200)
187 | self.send_header('Content-type','application/json')
188 | self.end_headers()
189 | self.wfile.write(json.dumps(self.server.assetToPayload.values()))
190 | except IOError as e:
191 | self.send_response(404)
192 | self.server.log("Error with processing request for asset states: "+str(e))
193 | else:
194 | fileName="web/view.html"
195 | contentType="text/html"
196 | if("style.css" in dirs):
197 | fileName="web/style.css"
198 | contentType="text/css"
199 | elif("coords.cfg" in dirs):
200 | fileName="web/coords.cfg"
201 | contentType="application/json"
202 | elif("blueprint.png" in dirs):
203 | fileName="web/blueprint.png"
204 | contentType="image/png"
205 | elif("coord.html" in dirs):
206 | fileName="web/coord.html"
207 | contentType="text/html"
208 | elif("logo.png" in dirs):
209 | fileName="web/logo.png"
210 | contentType="image/png"
211 | f = open(fileName)
212 | self.send_response(200)
213 | self.send_header('Content-type',contentType)
214 | self.end_headers()
215 | self.wfile.write(f.read())
216 | f.close()
217 | else:
218 | self.send_response(404)
219 | self.server.log("Error with request. No resource specified")
220 |
221 | def do_POST(self):
222 | try:
223 | self.server.log("Asset data recieved from a reader.")
224 | content_len = int(self.headers.getheader('content-length'))
225 | request_data = self.rfile.read(content_len)
226 | self.send_response(200)
227 | self.server.log("Attempting to parse request content.")
228 | payload=json.loads(request_data)
229 | self.server.log("Payload recieved: ")
230 | self.server.log(json.dumps(payload,indent=4))
231 | self.server.processPayload(payload)
232 | except Exception, error:
233 | self.send_response(404)
234 | self.server.log("Error with payload request from reader: "+str(error))
235 |
236 | def log_message(self, format, *args):
237 | return
--------------------------------------------------------------------------------