ULP-ActivitySupport – blob

You can use Git to clone the repository via the web URL. Download snapshot (zip)
Take mobility path width requirements into account when routing
[ULP-ActivitySupport] / server.py
1 #!/usr/bin/env python3
3 import copy
4 import json
5 import os
6 import random
7 import sys
9 from flask import Flask, escape, json as flask_json, jsonify, render_template, request, send_from_directory
11 from data import Position, Intent, User, SmartUrbanObject, Context
12 from routing import RoutingEngine
13 from simulator import ContextSimulator
15 # ULE backend connector - disabled for now
16 # from iot_service import IoTService
18 # UBW micro info radiator connector
19 from ubw_info_radiator_service import MicroInfoRadiatorService
22 class DataSerializer(json.JSONEncoder):
23     def default(self, o):
24         if hasattr(o, 'toJSON'):
25             return o.toJSON()
26         return o.__dict__
29 data = {
30     'context': {},
31     'routing': {}
32 }
34 app = Flask(__name__)
35 app.json_encoder = DataSerializer
37 services = {
38     'iot': MicroInfoRadiatorService()
39 }
42 @app.route('/')
43 def index():
44     return '', 200
46 @app.route('/assets/<name>.png')
47 def get_asset(name):
48     return send_from_directory(os.path.join(app.root_path, 'assets'),
49                                name + '.png', mimetype='image/png')
51 @app.route('/<name>.js')
52 def get_script(name):
53     return send_from_directory(os.path.join(app.root_path, 'assets'),
54                                name + '.js', mimetype='text/javascript')
56 @app.route('/view/<name>')
57 def render_view(name):
58     return render_template('view.html', context=name)
60 @app.route('/context/<id>')
61 def get_context(id):
62     context = data['context'].get(id)
63     if context is None:
64         return 'Invalid context ID', 401
65     routing = data['routing'].get(id)
66     result = {
67         'context': context,
68         'transient': {
69             'suo_vicinity': {},
70             'suo_on_route': {},
71             'direct_control': [],
72             'paused': [],
73             'suo_state': {},
74         }
75     }
76     for user_id in context.user:
77         suos_on_route = {}
78         if routing is not None:
79             suos_on_route = routing.get_suos_on_route(user_id)
80         for suo_id in context.suo:
81             if context.suo[suo_id].is_user_in_range(context.user[user_id]):
82                 if user_id not in result['transient']['suo_vicinity']:
83                     result['transient']['suo_vicinity'][user_id] = []
84                 result['transient']['suo_vicinity'][user_id].append(suo_id)
85                 if suo_id in suos_on_route:
86                     del suos_on_route[suo_id]
87         if len(suos_on_route.keys()) > 0:
88             result['transient']['suo_on_route'][user_id] = suos_on_route
89         user = context.user[user_id]
90         if hasattr(user, 'original_intent'):
91             result['transient']['direct_control'].append(user_id)
92         if hasattr(user, 'paused') and user.paused:
93             result['transient']['paused'].append(user_id)
94     for suo_id in context.suo:
95         w = [255, 255, 255]
96         r = [255, 0, 0]
97         b = [0, 0, 255]
98         if suo_id == 'micro-0':
99             result['transient']['suo_state'][suo_id] = {}
100             result['transient']['suo_state'][suo_id]['display'] = [
101                 r,r,r,r,r,r,r,r,
102                 r,r,r,r,r,r,r,r,
103                 r,r,r,r,r,r,r,r,
104                 r,r,r,r,r,r,r,r,
105                 r,r,r,r,r,r,r,r,
106                 r,r,r,r,r,r,r,r,
107                 r,r,r,r,r,r,r,r,
108                 r,r,r,r,r,r,r,r,
109             ]
110         if suo_id == 'micro-2':
111             result['transient']['suo_state'][suo_id] = {}
112             result['transient']['suo_state'][suo_id]['display'] = [
113                 b,b,b,w,w,b,b,b,
114                 b,b,w,w,b,b,b,b,
115                 b,w,w,b,b,b,b,b,
116                 w,w,w,w,w,w,w,w,
117                 w,w,w,w,w,w,w,w,
118                 b,w,w,b,b,b,b,b,
119                 b,b,w,w,b,b,b,b,
120                 b,b,b,w,w,b,b,b,
121             ]
122     return jsonify(result)
124 @app.route('/routing/<id>')
125 def get_routing_network(id):
126     routing_engine = data['routing'].get(id)
127     if routing_engine is None:
128         return 'Invalid routing context ID', 401
129     result = copy.deepcopy(routing_engine.get_network())
130     return jsonify(result)
132 @app.route('/routing/<id>/route_nodes/<nodeA>/<nodeB>')
133 def get_route_nodes(id, nodeA, nodeB):
134     routing_engine = data['routing'].get(id)
135     if routing_engine is None:
136         return 'Invalid routing context ID', 401
137     result = routing_engine.route_between_nodes(nodeA, nodeB)
138     return jsonify(result)
140 @app.route('/routing/<id>/route/<coordsA>/<coordsB>')
141 def get_route_coords(id, coordsA, coordsB):
142     routing_engine = data['routing'].get(id)
143     if routing_engine is None:
144         return 'Invalid routing context ID', 401
145     coordsA = coordsA.split(',')
146     coordsA = { 'x': coordsA[0], 'y': coordsA[1] }
147     coordsB = coordsB.split(',')
148     coordsB = { 'x': coordsB[0], 'y': coordsB[1] }
149     result = routing_engine.route_between_coords(coordsA, coordsB)
150     return jsonify(result)
152 @app.route('/routing/<id>/route/<uids>')
153 def get_route_for_user(id, uids):
154     routing_engine = data['routing'].get(id)
155     if routing_engine is None:
156         return 'Invalid routing context ID', 401
157     result = {}
158     for uid in uids.split(','):
159         result[uid] = routing_engine.get_user_route(uid)
160     return jsonify(result)
162 @app.route('/<cid>/user/<uid>/update_position')
163 def update_position(cid, uid):
164     context = data['context'].get(cid)
165     if context is None:
166         return 'Invalid context ID', 401
167     user = context.user.get(uid)
168     if user is None:
169         return 'Invalid user ID', 401
170     lat = request.args.get('lat')
171     lon = request.args.get('lon')
172     if lat is None or lon is None:
173         return 'Must provide lat and lon parameters', 401
174     user.set_position(Position(float(lat), float(lon)))
175     return jsonify(user)
177 @app.route('/<cid>/admin/<command>/<param>')
178 def admin_command(cid, command, param):
179     context = data['context'].get(cid)
180     if context is None:
181         return 'Invalid context ID', 401
182     if command not in ['enable_direct_control', 'disable_direct_control', 'enable_pause', 'disable_pause', 'set_goal', 'set_mobility']:
183         return 'Invalid command', 401
184     else:
185         if ';' in param:
186             user = context.user.get(param.split(';')[0])
187         else:
188             user = context.user.get(param)
189         if user is None:
190             return 'Invalid user ID', 401
191         if not user.is_virtual:
192             return 'User is not virtual - cannot be controlled', 401
193     if command == 'enable_direct_control':
194         user = context.user[param]
195         if hasattr(user, 'original_intent'):
196             return 'User already under direct control', 401
197         user.original_intent = user.intent
198         user.intent = {}
199     if command == 'disable_direct_control':
200         user = context.user[param]
201         if not hasattr(user, 'original_intent'):
202             return 'User was not under direct control', 401
203         user.intent = user.original_intent
204         del user.original_intent
205     if command == 'enable_pause':
206         user = context.user[param]
207         if hasattr(user, 'paused') and user.paused:
208             return 'User already paused', 401
209         user.paused = True
210     if command == 'disable_pause':
211         user = context.user[param]
212         if not hasattr(user, 'paused') or not user.paused:
213             return 'User was not paused', 401
214         del user.paused
215     if command == 'set_goal':
216         uid, goal = param.split(';')
217         user = context.user[uid]
218         if not hasattr(user, 'original_intent'):
219             return 'User is not under direct control', 401
220         x, y = goal.split(',');
221         user.intent = { 1: Intent(1, Position( x = float(x), y = float(y))) }
222     if command == 'set_mobility':
223         uid, mobility = param.split(';')
224         user = context.user[uid]
225         if mobility not in ['foot', 'walker', 'wheelchair', 'scooter']:
226             return 'Invalid mobility', 401
227         user.mobility = mobility
228     return jsonify('success')
230 @app.route('/update_suo_list')
231 def update_suo_list(context = 'demo'):
232     devices = services['iot'].get_devices()
233     if devices is None: # connection error or something else we can't fix
234         return jsonify(None)
235     for device in devices:
236         if isinstance(services['iot'], MicroInfoRadiatorService):
237             new_suo = SmartUrbanObject(device['_id']['$oid'], 'microinforadiator')
238             del device['_id']
239             lat, lon = device['location']['coordinates']
240             del device['location']
241         else:
242             new_suo = SmartUrbanObject(device['id'], device['type'])
243             lat, lon = 0, 0 # TODO properly set position
244         new_suo.set_position(Position(lat = float(lat), lon = float(lon)))
245         for attrib in device:
246             setattr(new_suo, attrib, device[attrib])
247         data['context'][context].add_suo(new_suo)
248     return jsonify(data['context'][context])
252 # --- dummy profile service ---
255 users = {
256     '17318505-bdf4-4cb0-b918-5e073de20979': {
257         'first_name': 'Margot',
258         'last_name': 'Nowak',
259         'birth_year': 1938,
260         'bluetooth_ids': ['d6:ee:d4:16:ed:fc'],
261         'color_preference': [255, 0, 0],
262     },
263     '29673db0-35f6-45af-900d-edc15e65c831': {
264         'first_name': 'Emrah',
265         'last_name': 'Dogan',
266         'birth_year': 1927,
267         'bluetooth_ids': ['f6:be:90:32:3c:5e', 'eb:e3:0f:49:84:d2'],
268         'color_preference': [0, 0, 255],
269     },
272 @app.route('/users')
273 def get_users():
274     return jsonify(users)
276 @app.route('/user/<id>')
277 def get_user(id):
278     return jsonify(users.get(id))
280 @app.route('/user/by-bluetooth/<btid>')
281 def get_user_by_bluetooth(btid):
282     btid = btid.lower()
283     result = None
284     for user in users:
285         if btid in users[user]['bluetooth_ids']:
286             result = users[user]
287     return jsonify(result)
291 # Nur zum Testen, kommt irgendwann weg! :) 
293 def setup_demo_world():
294     world = Context('demo', 'Scooter-Park')
295     world.set_boundaries(54.135, 54.135)
296     routing_engine = RoutingEngine(world)
297     i = 0
298     for user_id in users:
299         user = User(user_id, users[user_id]['first_name'] + ' ' + users[user_id]['last_name'])
300         user.color_preference = users[user_id]['color_preference']
301         user.mobility = 'foot'
302         user.is_virtual = True
303         if i == 0:
304             pos = Position(x = 6.0, y = 41.0)
305             rotation = 0
306             user.add_intent(Intent(1, Position(x = 34.2, y = 44.6)))
307             user.add_intent(Intent(2, Position(x = 41.3, y = 11.2)))
308             user.add_intent(Intent(3, Position(x = 22.6, y = 11.2)))
309             user.add_intent(Intent(4, Position(x = 6.3, y = 18.8)))
310             user.add_intent(Intent(5, Position(x = 6.0, y = 41.0)))
311         else:
312             pos = Position(x = 22.1, y = 20.4)
313             rotation = 270
314             user.add_intent(Intent(1, Position(x = 38.0, y = 33.0)))
315             user.add_intent(Intent(2, Position(x = 13.8, y = 29.7)))
316             user.add_intent(Intent(3, Position(x = 22.1, y = 20.4)))
317         user.set_position(pos)
318         user.set_rotation(rotation)
319         world.add_user(user)
320         i += 1
321     data['context']['demo'] = world
322     data['routing']['demo'] = routing_engine
323     for i in range(3):
324         new_suo = SmartUrbanObject('micro-' + str(i), 'microinforadiator')
325         new_suo.set_position(Position(x = 16.24 + 3.8*i, y = 16.78))
326         new_suo.set_rotation(180)
327         new_suo.is_virtual = True
328         world.add_suo(new_suo)
331 if __name__ == '__main__':
332     setup_demo_world()
333     if '--register' in sys.argv:
334         services['iot'].register()
335     services['iot'].authenticate()
336     if '--sim' in sys.argv:
337         simulator = ContextSimulator(data['context']['demo'], data['routing']['demo'])
338         simulator.set_messaging_callback(lambda suo_id, msg: services['iot'].send_info_to_device(suo_id, msg))
339         simulator.loop_async()
340         for uid in data['context']['demo'].user:
341             simulator.loop_user_intents[uid] = True
342     app.run(host = '0.0.0.0', port = 5500)