src/indoor_positioning/positioning.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 Use this class to perform indoor positioning with the metraTec IPS system. The position of a receiver is evaluated by
4 the received messages of RF beacons placed in the environment. Requires a YAML file with the configuration of zones.
5 
6 Usage:
7 Initialize the class by passing the directory of the config file. Then collect any number of beacon pings and use the
8 get_zone() function to return the current zone. Passing a list of pings instead of a single ping leads to averaging
9 over all the passed messages.
10 """
11 
12 from zone import Zone
13 from beacon import Beacon
14 import yaml
15 
16 
18  """Use the metraTec IPS system to perform positioning of a receiver depending on placement of RF beacons."""
19  def __init__(self, config_dir):
20  """
21  Initialize class object by passing the directory to a YAML config file containing zone information.
22  :param config_dir: String: directory of the YAML config file containing the definitions of zones
23  """
24  # store number of specified beacons
25  self.n_beacons = 0
26  # get data from config file and initialize zone objects
27  self.zones = []
28  self.parse_config(config_dir)
29 
31  """
32  Return a list of EIDs of all beacons that are configured in the config file
33  :return: [String]: EIDs of all beacons that are defined
34  """
35  eids = []
36  # iterate over all zones
37  for z in self.zones:
38  # get every beacon in the zone
39  for b in z.beacons:
40  eids.append(b.eid)
41  return eids
42 
43  def get_beacon(self, eid):
44  """
45  Return beacon object with the specified EID.
46  :param eid: String: EID of the beacon object that should be fetched from the initialized zones
47  :return: Beacon/None: beacon object with the specified EID
48  """
49  # iterate over all zones and search for beacon
50  for z in self.zones:
51  for b in z.beacons:
52  if b.eid == eid:
53  return b
54  # return None if no matching beacon was found
55  return None
56 
57  def parse_config(self, yml_dir):
58  """
59  Read the YAML config files of zones, parse the data and create zone/beacon objects. The created zone objects
60  are appended to the classes list of zones for later use.
61  :param yml_dir: String: directory of the YAML config file containing the definitions of zones
62  """
63  # open YAML config file and store content as dictionary
64  with open(yml_dir, 'r') as stream:
65  yml = yaml.load(stream)
66 
67  # parse dictionary of YAML content and create list of zones
68  for i in range(len(yml)):
69  # get content of all zones in yml file and append to zones list
70  z = 'zone' + str(i) # dictionary keys for each zone (zone0, zone1, zone2, etc.)
71  zone = yml[z] # content of current zone
72  # get polygon of current zone
73  poly = []
74  for j in range(0, len(zone['polygon']), 3):
75  poly.append([zone['polygon'][j], zone['polygon'][j+1], zone['polygon'][j+2]])
76  # get content of all beacons of current zone from beacon0, beacon1, beacon2, etc.
77  beacons = []
78  for k in range(len(zone)-4): # -4 because of non-beacon entries (name, frame_id, polygon)
79  b = 'beacon' + str(k) # dictionary keys for each beacon inside the zone
80  beacon = zone[b] # content of current beacon
81  beacons.append(Beacon(beacon['EID'], beacon['position'], zone['frame_id'])) # initialize Beacon object
82  # increment number of beacons
83  self.n_beacons += 1
84  # append information about current zone to list and start with next iteration if more zones are defined
85  self.zones.append(Zone(zone['name'], zone['frame_id'], zone['threshold'], poly, beacons))
86 
87  def get_zone(self, pings):
88  """
89  Take average RSSI value for each beacon ping contained in the passed list of beacon pings. Then return the
90  zone which the beacon with the highest value belongs to.
91  :param pings: List of Strings: beacon pings collected over time to average [BCN <EID> <RSSI>, ...]
92  :return: Zone/None: zone object of beacons with highest signal strength
93  """
94  # get means of RSSI values for each individual beacon
95  means = self.get_mean(pings)
96  # sort mean dictionary by values and look for corresponding beacon/zone
97  # if the top beacon is not found, continue with the next one until all beacons are exhausted
98  for key, value in sorted(means.items(), key=lambda (k, v): v, reverse=True):
99  # look for key (EID) in every zone and return when match is found
100  for z in self.zones: # iterate over zones
101  # continue to next zone if mean RSSI value is lower than the configured threshold
102  if value < z.threshold:
103  continue
104  for b in z.beacons: # iterate over beacons inside the zone
105  if b.eid == key: # if the EID of the beacon is the same as the current key (EID) return the zone
106  print('Current zone: {}. RSSI: {}'.format(z.name, value))
107  return z
108  # return None if no matching beacon has been found
109  print('None of the received beacon pings match a beacon from the configuration file. Returning \'None\'')
110  return None
111 
112  @staticmethod
113  def get_mean(pings):
114  """
115  Compute the mean of RSSI values of a list of collected beacon pings.
116  Sort the passed list of pings by their unique EID and allocate their measured RSSI values. Then compute and
117  return the mean RSSI value of all received messages for each individual beacon.
118  :param pings: List of Strings: beacon pings collected over time to average [BCN <EID> <RSSI>, ...]
119  :return: Dict: key-value-pairs of EID and computed mean of RSSI values {<EID>: <AVERAGE_RSSI>, ...}
120  """
121  # create empty dictionary for <EID>: [<RSSI_VALUE>, ...] pairs
122  ordered_values = dict()
123  # loop through every entry in the list of received pings
124  for p in pings:
125  # remove end of line character
126  if p[-1] == '\r':
127  p = p[:-1]
128  # split string on every SPACE
129  split = p.split(' ')
130  # check whether values are valid and skip current entry if they are not
131  if len(split) != 3 or split[0] != 'BCN' or len(split[1]) != 16 or len(split[2]) != 4:
132  print('Encountered invalid beacon ping: {}'.format(p))
133  continue
134  # append current RSSI value to respective entry in list if the current EID is already there
135  if split[1] in ordered_values:
136  ordered_values[split[1]].append(int(split[2]))
137  # create new entry in for EID-RSSI-pairs if the current EID is not yet in the dictionary
138  else:
139  ordered_values[split[1]] = [int(split[2])]
140  # compute average RSSI value for each beacon
141  for o in ordered_values:
142  ordered_values[o] = sum(ordered_values[o]) / len(ordered_values[o])
143  # return mean RSSI values attached to their respective EIDs
144  return ordered_values
145 
146 
147 if __name__ == '__main__':
148  """Testing the class and its methods."""
149  # initialize positioning class
150  pos = Positioning('/home/metratec/catkin_ws/src/indoor_positioning/config/zones.yml')
151  # create a list of pings that would usually be collected from the receiver and stored in a buffer
152  dummy_pings = ['BCN 0123456789ABCDEF -060', 'BCN 0123456789ABCDEF -070', 'BCN 0123456789ABCDFF -070',
153  'BCN 0123456789ABCDFF -090', 'BCN 0123456789ABCFFF -070', 'BCN 0123456789ABCFFF -050',
154  'BCN 0123456789ABFFFF -070', 'BCN 0123456789ABFFFF -060', 'BCN 0123456789ABFFFF -090']
155  # get the current zone of the receiver according to above list of beacon pings
156  # Note: the above beacons have to be defined in the config file for this to work
157  zone = pos.get_zone(dummy_pings)
158  # break here to view objects and variables etc.
159  breakpoint = 0


indoor_positioning
Author(s): Christian Arndt
autogenerated on Mon Jun 10 2019 13:33:13