├── .gitignore ├── LICENSE ├── README.md └── RTLS_framework ├── RTLS_Broker.py ├── RTLS_Constants.py ├── RTLS_Filter.py ├── RTLS_Run.py ├── RTLS_Server.py ├── RTLS_UI.py ├── RTLS_Utils.py ├── __pycache__ ├── RTLS_Broker.cpython-39.pyc ├── RTLS_Constants.cpython-39.pyc ├── RTLS_Filter.cpython-39.pyc ├── RTLS_Server.cpython-39.pyc ├── RTLS_UI.cpython-39.pyc └── RTLS_Utils.cpython-39.pyc ├── configs ├── config_beacon.csv └── config_gateway.csv ├── font └── NanumSquare_acR.ttf ├── images ├── SNL_map.png └── intflow.png ├── requirements.txt └── templates ├── base.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 FIVEYOUNGWOO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## BLE-Sensing-Based Real-Time Localization Framework 2 | 3 | This code supports a real-time location estimation system (RTLS) framework, utilizing multiple gateways and beacons for detecting and tracking various objects indoors. 4 | 5 | Within this RTLS framework, BLE packets broadcast by several beacons are collected by each gateway. The collected data is logged over HTTP in JSON format. 6 | 7 | The RSSI values measured by the gateways are processed in real-time using Flask, enabling the parsing and recording of each beacon's information. Furthermore, The estimated location is automatically calculated from quadrilateral methods in the local server, it displays the estimated coordinates of each beacon on a graphical map in PyQt. 8 | 9 | We have ascertained that the positioning accuracy of the RTLS framework achieved a precision of 94% in a 3X7 m indoor scenario. 10 | 11 | ## Requirements 12 | - (HW) MINEW IoT G1 gateway 13 | - (HW) MINEW E5, E7, E8 beacons 14 | - (SW) Flask 15 | - (SW) Python 16 | - (SW) PyQt 17 | -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Broker.py: -------------------------------------------------------------------------------- 1 | 2 | ######################################################## NOTE ####################################################### 3 | # Date : 2022-05-27 to 2022-06-30 # 4 | # # 5 | # Execute the server broker # 6 | ##################################################################################################################### 7 | 8 | import time 9 | from threading import Thread 10 | from multiprocessing import * 11 | 12 | from RTLS_UI import * 13 | from RTLS_Utils import * 14 | from RTLS_Server import * 15 | from RTLS_Filter import * 16 | from RTLS_Constants import * 17 | 18 | # Class for sharing estimated position values. 19 | class SharedMemory(): x, y = [], [] 20 | 21 | # Description of Broker function as below: 22 | # [Acquire] the pre-processed beacon packets from each gateway. 23 | # [Calculate] distance by using filtered-RSSI, transmit power, and path-loss. 24 | # [Copy] the calculated distance between each gateway and beacons. 25 | # [Estimate] a number of beacon positions based on the Quadrilateration algorithm. 26 | # [Share] the estimated positions based on SharedMemory class. 27 | 28 | def broker(beacon_sample, token_with_port, area_size): 29 | while True: 30 | # Acquire of beacon packet from each gateway. 31 | GW1 = func_read_JSON(token = token_with_port[0][0], port = token_with_port[0][1]) 32 | GW2 = func_read_JSON(token = token_with_port[1][0], port = token_with_port[1][1]) 33 | GW3 = func_read_JSON(token = token_with_port[2][0], port = token_with_port[2][1]) 34 | GW4 = func_read_JSON(token = token_with_port[3][0], port = token_with_port[3][1]) 35 | 36 | # Check the lenght of received dictonary. 37 | if (func_condition_GW(GW1, GW2, GW3, GW4)): 38 | for i in range(beacon_sample): 39 | try: 40 | # Estimate distance between gateway and each target beacons. 41 | d0 = func_cal_distance(GW1[i].get('RSSI'), GW1[i].get('POWER')) 42 | d1 = func_cal_distance(GW2[i].get('RSSI'), GW2[i].get('POWER')) 43 | d2 = func_cal_distance(GW3[i].get('RSSI'), GW3[i].get('POWER')) 44 | d3 = func_cal_distance(GW4[i].get('RSSI'), GW4[i].get('POWER')) 45 | 46 | # Quadrilateration algorithm-based estimated beacon positions. 47 | est_x, est_y = func_cal_quad(area_size, d0, d1, d2, d3) 48 | 49 | # Performance check_point : estimated beacon positions. 50 | # print('estimated X_{} = {}, estimated Y_{} = {}'.format(i, est_x, i, est_y)) 51 | 52 | # Copy the estimated beacon positions. 53 | SharedMemory.x.extend(est_x) 54 | SharedMemory.y.extend(est_y) 55 | 56 | # Gateway has not acq. or sent yet... wait for a few seconds. 57 | except IndexError: 58 | pass 59 | 60 | # Delete the stack of global-list memory. 61 | if len(SharedMemory.x) % beacon_sample == 0 and len(SharedMemory.y) % beacon_sample == 0: 62 | del SharedMemory.x[:-beacon_sample] 63 | del SharedMemory.y[:-beacon_sample] 64 | 65 | # For wait print() because the values updated fast. Only using performance check_opint 66 | # time.sleep(2) 67 | 68 | 69 | # Description of Broker_run function as below: 70 | # [Read] the gateway, and beacon configuration files from CSV. 71 | # [Generate] the webhook server token with the port number. 72 | # [Run] the defined webhook server services from RTLS_Server.py. 73 | # [Run] the RTLS.Ui.py after server processing conditions. 74 | 75 | def broker_run(): 76 | # Loading each configulation files. 77 | load_area = func_gateway_config("configs\config_gateway.csv") 78 | load_number = func_beacon_config("configs\config_beacon.csv") 79 | 80 | # Setting indoor size and number of beacons. 81 | area_size = (load_area[0][4],load_area[1][4]) 82 | beacon_sample = len(load_number) 83 | 84 | # Configuration of delimiter and port number. 85 | token_with_port = [('GW1', 2998), ('GW2', 2999), ('GW3', 3000), ('GW4', 3001)] 86 | 87 | # Asynchronous server based on multi-process. 88 | for token, port in token_with_port: 89 | proc = Process(target = RunFlaskApp, args = (token, port)) 90 | proc.start() 91 | 92 | # Checking your multi-processor. 93 | PROCESSOR_BOL.append(proc.is_alive()) 94 | 95 | # Checking your multi-processor equals to defined 'token_with_port'. 96 | if len(PROCESSOR_BOL) == len(token_with_port): 97 | 98 | application = QApplication(sys.argv) 99 | application.setFont(QFont("나눔스퀘어_ac", 8)) 100 | 101 | broker_thread = Thread(target = broker, args = ((beacon_sample, token_with_port, area_size))) 102 | broker_thread.start() 103 | 104 | interface = UserInterface([SharedMemory.x, SharedMemory.y]) 105 | interface.show() 106 | application.exec_() -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Constants.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # A collection of constants and coefficients # 5 | ##################################################################################################################### 6 | 7 | import socket 8 | 9 | ############################################################################################ 10 | # For ServerHandler.py 11 | PROCESSOR_BOL = [] # Number of running multi-processes [list]. 12 | EXPORT_ACTIVE = True # Export csv file per each gateway devices [boolean]. 13 | MAX_PKT = 5000 # Maximum number of packet. 14 | AT_LEAST_INFO = 10 # Minimum number of beacon information for calculating after flush. 15 | 16 | ############################################################################################ 17 | # For filter, calculation, and algorithms. 18 | ENV_FACTOR = 1.7 19 | 20 | # Indoors coefficient when there is line of sight to the router. 21 | # 1.6 to 1.8 for indoors when there is line of sight to the router. 22 | # 2.7 to 3.5 for urban areas; 23 | # 3.0 to 5.0 in suburban areas; 24 | 25 | ############################################################################################ 26 | # For read JSON function. 27 | PREFIX_HTTP = 'http://' # Prefix of http address. 28 | DELIMITER_PORT = ':' # Delimiter of port number. 29 | DELIMITER_TOKEN = '/' # Delimiter of gateway token. 30 | URL= socket.gethostbyname(socket.gethostname()) # Find your localhost IP address automatically. -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Filter.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # 1D Kalman filtering method. # 5 | ##################################################################################################################### 6 | 7 | import numpy as np 8 | 9 | from RTLS_Utils import * 10 | from RTLS_Constants import * 11 | 12 | 13 | # Y. Shen, B. Hwang, and J. P. Jeong, ‘Particle filtering-based indoor positioning system for beacon tag tracking,’ IEEE Access. 14 | def func_kalman_filter(measured_rssi,idx): 15 | KF_RSSI = [] 16 | KF_RSSI.append(measured_rssi) 17 | 18 | # from the reference paper. 19 | processNoise = 0.05 20 | measurementNoise = np.var(KF_RSSI) 21 | predictedRSSI, errorCovariance = 0, 0 22 | 23 | # Start 2nd applied the kalman filtering case. 24 | if idx != 0: 25 | priorRSSI = KF_RSSI[idx-1] 26 | priorErrorCovariance = 1 27 | 28 | # Start 1st applied the kalman filtering case. 29 | else: 30 | priorRSSI = KF_RSSI[idx] 31 | 32 | # Error covariance. 33 | priorErrorCovariance = errorCovariance + processNoise 34 | 35 | # KalmanGain formula. 36 | kalmanGain = priorErrorCovariance / (priorErrorCovariance + measurementNoise) 37 | 38 | # K = P / P + R (P: error covariance, R: measurement noise). 39 | predictedRSSI = priorRSSI + (kalmanGain * (measured_rssi - priorRSSI)) 40 | 41 | # RSSI Estimation Expression RSSI = Previous RSSI + (KalmanGain*(Current RSSI - Previous RSSI)). 42 | errorCovariance = (1 - kalmanGain) * priorErrorCovariance 43 | 44 | # Save the predicted RSSI values. 45 | KF_RSSI[idx] = predictedRSSI 46 | 47 | # After finding the RSSI, the newly calibrated error covariance equation P = (1 - KalmanGain)previous error covariance. 48 | return int(predictedRSSI) -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Run.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # RTLS_run.py as Main file # 5 | # # 6 | # Overview of RTLS_Run.py below: # 7 | # [Call] the necessary values, and functions from RTLS_Constants.py and RTLS_Utils.py # 8 | # [Read] the scenario environment and H/W configurations such as area, number of beacons and gateways from CSV file # 9 | # [Ready] the gateway dedicated webhook server token and port number for executing the RTLS_Server.py # 10 | # [Run] the webhook server services based on the allocated parameters # 11 | # -> where the 'services' is including the pre-processing of the received beacon packets, and filtering # 12 | # [Write] pre-processed and filtered beacon packets in each defined URL address by using the webhook server # 13 | # [Read] the written beacon information from each different URL address in RTLS Broker.broker() function # 14 | # # 15 | # Our RTLS goals: # 16 | # First, to estimate the positions of the livestock. # 17 | # Second, to handle the RTLS processing automatically. # 18 | ##################################################################################################################### 19 | 20 | import os 21 | import RTLS_Broker 22 | 23 | if __name__ == '__main__': 24 | os.environ["WERKZEUG_RUN_MAIN"] = 'true' 25 | RTLS_Broker.broker_run() -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Server.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # If triggered 'GET', or 'POST' events, then we can receive the beacon information from localhost webhook server. # 5 | # We received the ibeacon information as below: # 6 | # 1) dict.get('timestamp') | '2022-05-26T03:03:32.970Z' # 7 | # 2) dict.get('type') | 'iBeacon' # 8 | # 3) dict.get('mac') | 'AC233FAA45E5' # 9 | # 4) dict.get('bleName') | '' # 10 | # 5) dict.get('ibeaconUuid') | 'E2C56DB5DFFB48D2B060D0F5A71096E0' # 11 | # 6) dict.get('ibeaconMajor') | '0' # 12 | # 7) dict.get('ibeaconMinor') | '0' # 13 | # 8) dict.get('rssi') | '-29' # 14 | # 9) dict.get('ibeaconTxPower') | '-59' # 15 | # 10) dict.get('battery') | '' # 16 | ##################################################################################################################### 17 | 18 | import re 19 | import os 20 | import sys 21 | import signal 22 | import logging 23 | import threading 24 | 25 | from RTLS_Utils import * 26 | from RTLS_Filter import * 27 | from RTLS_Constants import * 28 | from multiprocessing import * 29 | from flask import Flask, request, jsonify 30 | 31 | # Get number of beacons. 32 | beacon_list = func_beacon_config('configs\config_beacon.csv') 33 | 34 | # Remove the some symbol ":". 35 | for i in range(len(beacon_list)): 36 | beacon_list[i] = re.sub(":","",beacon_list[i]) 37 | 38 | # Remove warning nofitications. 39 | logger = logging.getLogger(__name__) 40 | handler = logging.StreamHandler(stream=sys.stdout) 41 | 42 | # Parent class for running HookServer (with some preprocessing). 43 | class HookServer(threading.Thread): 44 | def __init__ (self, token, port): 45 | 46 | self.save_info = [] 47 | self.value = [] 48 | self.beacon_dict = [] 49 | self.dict = {} 50 | 51 | self.token = token 52 | self.port = port 53 | 54 | # Setup for a Flask application instants. 55 | self.server_app = Flask(__name__, static_folder='./media') 56 | self.server_app.debug = False 57 | 58 | # Ignore web-hook server tracking information. 59 | log = logging.getLogger('werkzeug') 60 | log.disabled = True 61 | 62 | @self.server_app.route('/' + self.token, methods=['GET', 'POST']) 63 | def index1(): 64 | # If 'GET' request. 65 | if request.method == 'GET': 66 | 67 | # JSON LONG convert to the dictionary. 68 | content = request.args.to_dict() 69 | 70 | # If 'POST' request. 71 | elif request.method == 'POST': 72 | 73 | # Read the JSON-LONG data. 74 | content = request.get_json() 75 | 76 | # Received beacon information (dict) per time slot (1 sec). 77 | MAX_EXTRACT_LOOP = len(content) 78 | 79 | # Unwrapping of i-beacon dictionary . 80 | for i in range(0, MAX_EXTRACT_LOOP): 81 | 82 | # Acquired the raw beacon packets . 83 | self.save_info = list(func_combine(content[i].get('mac'), content[i].get('rssi'), content[i].get('ibeaconTxPower'))) 84 | 85 | # Copy the received beacon JSON data. 86 | self.value = self.save_info 87 | 88 | # Get received beacon packet length. 89 | Pkt = len(self.value[0]) 90 | save_Pkt = len(self.save_info[0]) 91 | 92 | # Checking the received beacon addresses. 93 | if (list(set(beacon_list) & set(self.value[0]))): 94 | 95 | # Pre-allocate rssi, txpower list space. 96 | self.dict = {i:{'rssi':[], 'txpower':[]} for i in beacon_list} 97 | 98 | for i in range (Pkt): 99 | # Applied the Kalman filtering for equalizing the outlier RSSI data. 100 | self.value[1][i] = func_kalman_filter(self.value[1][i],i) 101 | 102 | # Get received beacon packets. 103 | mac_address = self.value[0][i] 104 | self.dict[mac_address]['rssi'].append(self.value[1][i]) 105 | self.dict[mac_address]['txpower'].append(self.value[2][i]) 106 | 107 | # Automatically creates a dictionary and parses each beacon information. 108 | self.beacon_dict = [{'MAC': Mac, 'RSSI': values['rssi'], 'POWER': values['txpower']} for Mac, values in self.dict.items()] 109 | 110 | # List management for return values. 111 | if (Pkt % 10 == 0): 112 | del self.value[0][0:] 113 | del self.value[1][0:] 114 | del self.value[2][0:] 115 | 116 | # Export acquired beacon information from each gateway. 117 | if (EXPORT_ACTIVE and save_Pkt % MAX_PKT == 0): 118 | 119 | # Save the received beacon information every MAX_Pkt '5000'. 120 | func_export(self.token, self.save_info[0], self.save_info[1], self.save_info[2]) 121 | 122 | del self.save_info[0][0:] 123 | del self.save_info[1][0:] 124 | del self.save_info[2][0:] 125 | 126 | # Somtimes HTML rendering causes a huge delay, so we just returning the extrected values, and HTML status code. 127 | # If you need rendering on HTML, add return render_dictlate("index.html") 128 | return jsonify(self.beacon_dict), 200 129 | 130 | # A child class that inherits and executes HookServer. 131 | class RunFlaskApp(HookServer): 132 | def __init__(self, token, port): 133 | 134 | # Inherit variables from parent class. 135 | super().__init__(token, port) 136 | 137 | # Handling of the HookServer execution. 138 | try: 139 | # Where host IP 0.0.0.0 that grants external access. 140 | self.server_app.run(host='0.0.0.0', port=self.port, use_reloader=False, threaded=True) 141 | 142 | except: 143 | print('[{}] Server occured error !!'.format(self.token)) 144 | os.kill(os.getpid(), signal.SIGTERM) 145 | 146 | -------------------------------------------------------------------------------- /RTLS_framework/RTLS_UI.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # calculate ui borders, the layout is something like this: # 5 | # ________________________________ # 6 | # |___________TITLE BAR____________| (title %) # 7 | # | | 1.GATEWAY | of # 8 | # | |------------| | # 9 | # | | 2.BEACON | | # 10 | # | |------------| | # 11 | # | MAP | 3.UPDATE | | # 12 | # | |------------| | # 13 | # | | | | # 14 | # | | 4.CTRL | | # 15 | # |___________________|____________| v # 16 | # # 17 | # (stats %) of ----------------> # 18 | ##################################################################################################################### 19 | 20 | import math 21 | import time 22 | import random 23 | from PyQt5.QtGui import * 24 | from PyQt5.QtCore import * 25 | from PyQt5.QtWidgets import * 26 | from numpy import nan_to_num 27 | 28 | # pip3 install git+https://github.com/yjg30737/pyqt-translucent-full-loading-screen-thread.git --upgrade 29 | from pyqt_translucent_full_loading_screen_thread import LoadingThread, LoadingTranslucentScreen 30 | 31 | # Import Our defined function. 32 | from RTLS_Utils import * 33 | 34 | 35 | # Thread class for the overlay loading screen. 36 | class LoadingInterface(LoadingThread): 37 | def __init__(self, *args, **kwargs): 38 | super().__init__(*args, **kwargs) 39 | def run(self): 40 | time.sleep(3) 41 | 42 | 43 | # Handling mouse drag events. 44 | class MovingObject(QGraphicsEllipseItem): 45 | def __init__(self, x, y, r,color): 46 | super().__init__(0, 0, r, r) 47 | self.setPos(x, y) 48 | self.setBrush(QColor(color)) 49 | self.setAcceptHoverEvents(True) 50 | 51 | 52 | def mousePressEvent(self, event): 53 | pass 54 | 55 | 56 | # Override mouse movement events. 57 | def mouseMoveEvent(self, event): 58 | orig_cursor_positon = event.lastScenePos() 59 | updated_cursor_position = event.scenePos() 60 | orig_position = self.scenePos() 61 | 62 | updated_cursor_x = updated_cursor_position.x() - orig_cursor_positon.x() + orig_position.x() 63 | updated_cursor_y = updated_cursor_position.y() - orig_cursor_positon.y() + orig_position.y() 64 | self.setPos(QPointF(updated_cursor_x, updated_cursor_y)) 65 | 66 | 67 | # Override mouseRelaseEvent. 68 | def mouseReleaseEvent(self, event): 69 | print('x: {0}, y : {1}'.format(self.pos().x(),self.pos().y())) 70 | 71 | 72 | # Interactive User Interface Class. 73 | # Receives the estimated location from RTLS_Broker.py. 74 | class UserInterface(QMainWindow): 75 | def __init__(self, CopyList): 76 | super().__init__() 77 | 78 | # Save the received the beacon positions. 79 | self.CopyList = CopyList 80 | 81 | # Create a timer to automatically call a function. 82 | self.update_timer = QTimer() 83 | 84 | # Initialize the variables. 85 | self.btn_state = 0 86 | 87 | # Size of pixmap size when first executed. 88 | self.origin_size = {"X": 930,"Y": 945} 89 | 90 | # Beacon table column. 91 | self.beacon_table_col = 1 92 | 93 | # X-axis, y-axis, number of beacons of pixmap when resize event. 94 | self.pixmap_resize_x, self.pixmap_resize_Y, self.beacon_cnt = 0, 0, 0 95 | 96 | # Beacon MAC address, name, color, estimated positions in beacon table. 97 | self.beacon_mac, self.beacon_name, self.random_color, self.table_beacon_x, self.table_beacon_y = [], [], [], [], [] 98 | 99 | # Display the clustered-text box. 100 | self.beacon_items, self.TypeName, self.beacontable_node_id, self.text_line = [], [], [], [] 101 | 102 | # Save the (x, y) for writing gateway position in table. 103 | self.table_gw_x, self.table_gw_y = [], [] 104 | 105 | # Save the (x, y) for drawing gateway position in pixmap. 106 | self.origin_beacon_x, self.origin_beacon_y = [], [] 107 | 108 | # Save the received the configulation set from the config_gateway.csv. 109 | self.gw_value = 0 110 | 111 | # Overlay loading screen. 112 | self.__initUi__() 113 | self.func_beacon_mac_append() 114 | self.start_gui() 115 | 116 | 117 | # Calling the loading thread. 118 | def __initUi__(self): 119 | self.__startLoadingThread() 120 | 121 | 122 | # Starting the loading thread. 123 | def __startLoadingThread(self): 124 | self.__loadingTranslucentScreen = LoadingTranslucentScreen(parent=self, description_text='Connecting') 125 | self.__loadingTranslucentScreen.setDescriptionLabelDirection('Right') 126 | self.__thread = LoadingInterface(loading_screen=self.__loadingTranslucentScreen) 127 | self.__thread.start() 128 | 129 | 130 | # Starting the interactive user interface. 131 | def start_gui(self): 132 | widget = QWidget(self) 133 | ui_grid = QGridLayout(widget) 134 | 135 | # Set gird layout positions. 136 | ui_grid.addWidget(self.ImageLayout(),0, 0, 6, 1) 137 | ui_grid.addWidget(self.Logo(),0, 1, 1, 1) 138 | ui_grid.addWidget(self.GWTable(), 1, 1, 2, 1) 139 | ui_grid.addWidget(self.BeaconTable(), 3, 1, 1, 1) 140 | ui_grid.addWidget(self.Controller(),4, 1, 1, 1) 141 | self.func_beacon_display() 142 | 143 | # Set QWidget frame. 144 | self.setCentralWidget(widget) 145 | self.setWindowTitle('Multi-gateway-based livestock tracker software') 146 | self.setGeometry(0,0,1000,900) 147 | self.showMaximized() 148 | 149 | 150 | # Display INTFLOW logo on the upper right corner side. 151 | def Logo(self): 152 | groupbox = QGroupBox() 153 | groupbox.setStyleSheet("border-style : none") 154 | 155 | logo = QPixmap('images\intflow.png') 156 | logo_img = QLabel() 157 | logo_img.setPixmap(logo) 158 | logo_img.setAlignment(Qt.AlignCenter) 159 | 160 | vbox = QVBoxLayout() 161 | vbox.addWidget(logo_img) 162 | groupbox.setLayout(vbox) 163 | return groupbox 164 | 165 | 166 | # Display real-time GW, and beacon positions. 167 | def ImageLayout(self): 168 | img_box = QGroupBox('Floor Layout and Node Locations') 169 | img_box.setFont(QFont('나눔스퀘어_ac', 9)) 170 | img_box.setStyleSheet("color : black") 171 | 172 | 173 | # Read gateway configulation information from config_gateway.csv. 174 | self.gw_value = func_gateway_config("configs\config_gateway.csv") 175 | self.gw_area_x = int(self.gw_value[0][4]) 176 | self.gw_area_y = int(self.gw_value[1][4]) 177 | self.gw_address = self.gw_value[1] 178 | self.gw_color = self.gw_value[3] 179 | indoor_size = (self.gw_area_x,self.gw_area_y) 180 | 181 | 182 | # Generate gateway position automatically. 183 | # [(GW1), (GW2), (GW3), (GW4)] 184 | self.gateway_pos=[[0,0],[indoor_size[0],0],[indoor_size[0],indoor_size[1]],[0,indoor_size[1]]] 185 | self.nodemap = QPixmap('images/SNL_map.png').scaled(934,945) 186 | 187 | self.img_view = QGraphicsView(self) 188 | self.img_scene = QGraphicsScene() 189 | self.img_scene.addPixmap(self.nodemap) 190 | 191 | # Display function of gateway positons. 192 | self.func_GW_Draw() 193 | 194 | self.img_view.setScene(self.img_scene) 195 | self.vbox = QVBoxLayout() 196 | self.vbox.addWidget(self.img_view) 197 | 198 | img_box.setLayout(self.vbox) 199 | 200 | return img_box 201 | 202 | # Writing the beacon information such as Type, MAC address, estimated positions. 203 | def BeaconTable(self): 204 | beacon_table_box = QGroupBox('Beacon Table') 205 | beacon_table_box.setFont(QFont('나눔스퀘어_ac', 9)) 206 | beacon_table_box.setStyleSheet("color : black;") 207 | 208 | # TableWidget-based beacon table frame. 209 | self.beacon_table = QTableWidget() 210 | self.beacon_table.setColumnCount(5) 211 | self.beacon_table.setStyleSheet("Color : black;") 212 | self.beacon_table.showGrid() 213 | self.beacon_table.setAutoScroll(True) 214 | self.beacon_table.setEditTriggers(QAbstractItemView.NoEditTriggers) 215 | self.beacon_table.verticalHeader().setVisible(False) 216 | 217 | # Set the size of the header to match the size of the table. 218 | self.beacon_table.setHorizontalHeaderLabels(["Type", "Name", "Address", "( x , y )", "Color"]) 219 | self.beacon_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 220 | 221 | # Layout vbox and save the layout in groupbox. 222 | vbox = QVBoxLayout() 223 | vbox.addWidget(self.beacon_table) 224 | beacon_table_box.setLayout(vbox) 225 | 226 | return beacon_table_box 227 | 228 | # Writing the gateway information such as Type, MAC address, estimated positions. 229 | def GWTable(self): 230 | gw_table_box = QGroupBox('Gateway Table') 231 | gw_table_box.setFont(QFont('나눔스퀘어_ac', 9)) 232 | gw_table_box.setStyleSheet("color : black;") 233 | 234 | # TableWidget-based beacon gateway table frame. 235 | self.GWTable = QTableWidget() 236 | self.GWTable.setRowCount(4) 237 | self.GWTable.setColumnCount(5) 238 | self.GWTable.setStyleSheet("Color : black") 239 | self.GWTable.showGrid() 240 | self.GWTable.setAutoScroll(True) 241 | self.GWTable.setEditTriggers(QAbstractItemView.NoEditTriggers) 242 | 243 | # Set the size of the header to match the size of the table. 244 | self.GWTable.setHorizontalHeaderLabels(["Type", "Name", "Address", "( x , y )", "Color"]) 245 | self.GWTable.verticalHeader().setVisible(False) 246 | self.GWTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 247 | self.GWTable.verticalHeader().setSectionResizeMode(QHeaderView.Stretch) 248 | 249 | table_item = [] 250 | table_item.append([]) 251 | 252 | # Save the positons to display in the gateway table. 253 | for i in range(len(self.gw_value[3])-1): 254 | # If the indices are 0 and 3, the x position is 0, so it is save through a conditional statement. 255 | if i == 0 or i == 3: 256 | self.table_gw_x.append(0) 257 | # The other indexes 1 and 2 save the value of area_x. 258 | else: 259 | self.table_gw_x.append(self.gw_area_x) 260 | # If the indices are 0 and 1, the y-position is 0, so it is save through a conditional statement. 261 | if i == 0 or i == 1: 262 | self.table_gw_y.append(0) 263 | # The other indices 2 and 3 save the value of area_y. 264 | else: 265 | self.table_gw_y.append(self.gw_area_y) 266 | 267 | # Set gateway information in table using loop statement. 268 | for i in range(len(self.gw_value[3])-1): 269 | for j in range(len(self.gw_value[3])-1): 270 | table_text = ["BLE 5.0 / WiFi IoT","GATEWAY "+str(j),self.gw_address[j],"( "+str(self.table_gw_x[j])+" , "+str(self.table_gw_y[j])+" )"] 271 | GW_Table_item = QTableWidgetItem() 272 | GW_Table_item.setText(table_text[i]) 273 | GW_Table_item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter) 274 | self.GWTable.setItem(j,i,GW_Table_item) 275 | 276 | gw_color = QLabel() 277 | gw_color.setStyleSheet("background-color:"+self.gw_color[i]) 278 | 279 | colorWidget = QWidget(self) 280 | layoutColor = QHBoxLayout(colorWidget) 281 | layoutColor.addWidget(gw_color) 282 | colorWidget.setLayout(layoutColor) 283 | self.GWTable.setCellWidget(i,4,colorWidget) 284 | 285 | vbox = QVBoxLayout() 286 | vbox.addWidget(self.GWTable) 287 | gw_table_box.setLayout(vbox) 288 | 289 | return gw_table_box 290 | 291 | # Display controller layout. 292 | def Controller(self): 293 | controller_box = QGroupBox('Controller') 294 | controller_box.setFont(QFont('나눔스퀘어_ac', 9)) 295 | controller_box.setStyleSheet("color : blak; background-color :white") 296 | 297 | # Set gird layout positions. 298 | grid_layout = QGridLayout() 299 | grid_layout.addWidget(self.func_update_window(),0,0) 300 | grid_layout.addWidget(self.func_resizing_groupbox(),1,0) 301 | grid_layout.addWidget(self.func_controller_btn(),2,0) 302 | controller_box.setFixedHeight(330) 303 | controller_box.setLayout(grid_layout) 304 | 305 | return controller_box 306 | 307 | # Display the controller buttons. 308 | def func_controller_btn(self): 309 | btn_box = QGroupBox('Controller Button') 310 | btn_box.setFont(QFont("나눔스퀘어_ac",9)) 311 | btn_box.setStyleSheet("color : black; background-color :white") 312 | 313 | # Beacon add button. 314 | add_btn = QPushButton('ADD') 315 | add_btn.setFont(QFont("나눔스퀘어_ac",12)) 316 | add_btn.clicked.connect(self.func_addbtn) 317 | add_btn.clicked.connect(self.func_pop_up) 318 | add_btn.setStyleSheet("color: black;") 319 | add_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 320 | 321 | # Beacon remove button. 322 | remove_btn = QPushButton('REMOVE') 323 | remove_btn.setFont(QFont("나눔스퀘어_ac",12)) 324 | remove_btn.clicked.connect(self.func_remove_beacon) 325 | remove_btn.setStyleSheet("color : black;") 326 | remove_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 327 | 328 | # Beacon information display button. 329 | self.OnOff_btn = QPushButton("ON/OFF") 330 | self.OnOff_btn.setFont(QFont("나눔스퀘어_ac",12)) 331 | self.OnOff_btn.setStyleSheet("color : black;") 332 | self.OnOff_btn.setCheckable(True) 333 | self.OnOff_btn.clicked.connect(self.func_change_button) 334 | self.OnOff_btn.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) 335 | 336 | # Program exit button. 337 | exit_btn = QPushButton("EXIT") 338 | exit_btn.setFont(QFont("나눔스퀘어_ac",12)) 339 | exit_btn.clicked.connect(QCoreApplication.instance().quit) 340 | exit_btn.setStyleSheet("color : red") 341 | exit_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 342 | 343 | 344 | vbox = QVBoxLayout() 345 | hbox = QHBoxLayout() 346 | hbox.addWidget(self.OnOff_btn) 347 | hbox.addWidget(add_btn) 348 | hbox.addWidget(remove_btn) 349 | hbox.addWidget(exit_btn) 350 | 351 | vbox.addLayout(hbox) 352 | btn_box.setLayout(vbox) 353 | return btn_box 354 | 355 | # Popup that occurs when the maximum number of beacons is exceeded 356 | def func_pop_up(self): 357 | load_number = func_beacon_config("configs\config_beacon.csv") 358 | beacon_sample = len(load_number) 359 | beacon_cnt = len(self.beacon_mac) 360 | if (beacon_cnt >= beacon_sample): 361 | msgBox = QMessageBox() 362 | msgBox.setIcon(QMessageBox.NoIcon) 363 | msgBox.setText("모든 비콘을 사용하고 있습니다.") 364 | msgBox.exec_() 365 | 366 | # Check the ON/OFF changing. 367 | def func_change_button(self): 368 | if self.OnOff_btn.isChecked(): 369 | self.func_Display_On() 370 | else: 371 | self.func_Display_Off() 372 | 373 | # Display update period controller. 374 | def func_update_window(self): 375 | update_groupbox = QGroupBox("Update Setting") 376 | update_groupbox.setFixedHeight(100) 377 | update_groupbox.setFont(QFont("나눔스퀘어_ac",9)) 378 | update_groupbox.setStyleSheet("color : black; background-color :white") 379 | period_label = QLabel("Period") 380 | self.period_value = QLabel("0.2") 381 | 382 | self.slider = QSlider(Qt.Horizontal, self) 383 | self.slider.setSingleStep(8) 384 | self.slider.setTickInterval(10) 385 | self.slider.setValue(10) # slider default gw_value 10 386 | self.func_update_time(float(self.slider.value()/50)) 387 | self.slider.setTickPosition(QSlider.TicksBelow) 388 | self.slider.setRange(0, 70) 389 | self.slider.valueChanged.connect(self.func_changed_slider) 390 | 391 | hbox = QHBoxLayout() 392 | hbox.addWidget(period_label) 393 | hbox.addWidget(self.slider) 394 | hbox.addWidget(self.period_value) 395 | update_groupbox.setLayout(hbox) 396 | return update_groupbox 397 | 398 | def func_changed_slider(self): 399 | self.slider_value = int(self.slider.value() / 10) 400 | self.float_value = 0 401 | 402 | if self.slider_value == 0: # 0 second 403 | self.func_update_time(self.slider_value) 404 | 405 | elif self.slider_value == 1:# 0.2 second update period 406 | self.float_value = float(self.slider_value/5) 407 | self.func_update_time(float(self.float_value)) 408 | 409 | elif self.slider_value == 2: # 0.5 second update period 410 | self.float_value = float(self.slider_value/4) 411 | self.func_update_time(float(self.float_value)) 412 | 413 | elif self.slider_value == 3: # 1초 second update period 414 | self.func_update_time(int(self.slider_value-2)) 415 | 416 | elif self.slider_value == 4: # 2초 second update period 417 | self.func_update_time(int(self.slider_value-2)) 418 | 419 | elif self.slider_value == 5: # 3초 second update period 420 | self.func_update_time(int(self.slider_value-2)) 421 | 422 | elif self.slider_value == 6: # 5초 second update period 423 | self.func_update_time(int(self.slider_value-1)) 424 | 425 | elif self.slider_value == 7: # 10second update period 426 | self.func_update_time(int(self.slider_value+3)) 427 | 428 | if self.slider_value <3: # less 3 429 | self.period_value.setText(str(self.float_value)) 430 | elif self.slider_value <6: # less 6 431 | self.period_value.setText(str(self.slider_value-2)) 432 | elif self.slider_value == 6: # equal to 6 433 | self.period_value.setText(str(self.slider_value-1)) 434 | elif self.slider_value == 7: # equal to 7 435 | self.period_value.setText(str(self.slider_value+3)) 436 | 437 | 438 | # A function where update_period is automatically called by slider_value using a timer 439 | def func_update_time(self,slider_value): 440 | if slider_value > 0: 441 | self.update_timer.start(slider_value*1000) 442 | self.update_timer.timeout.connect(self.func_update_period) 443 | else: 444 | self.update_timer.stop() 445 | 446 | # Resizing 2D map function. 447 | def func_resizing_groupbox(self): 448 | resize_box = QGroupBox("Display Setting") 449 | resize_box.setFont(QFont('나눔스퀘어_ac', 9)) 450 | resize_box.setStyleSheet("color : black; background-color : white") 451 | 452 | resize_box_width = QLabel("Width : ") 453 | resize_box_height = QLabel("Height : ") 454 | self.resize_lineedit_X = QLineEdit() 455 | self.resize_lineedit_Y = QLineEdit() 456 | 457 | resize_box_width.setFixedSize(50,50) 458 | resize_box_height.setFixedSize(50,50) 459 | self.resize_lineedit_X.setFixedSize(150,30) 460 | self.resize_lineedit_Y.setFixedSize(150,30) 461 | self.resize_lineedit_X.mousePressEvent = self.func_mousePressEvent 462 | 463 | # resize button 464 | resize_btn_ok = QPushButton("OK") 465 | resize_btn_ok.setFont(QFont("나눔스퀘어_ac",12)) 466 | resize_btn_ok.clicked.connect(self.func_resizing) 467 | resize_btn_ok.setStyleSheet("color : black;") 468 | resize_btn_ok.setFixedSize(110,50) 469 | 470 | # reset button 471 | reset_btn = QPushButton("Reset") 472 | reset_btn.setFont(QFont("나눔스퀘어_ac",12)) 473 | reset_btn.clicked.connect(self.func_reset) 474 | reset_btn.setStyleSheet("color : black;") 475 | reset_btn.setFixedSize(110,50) 476 | 477 | vbox = QVBoxLayout() 478 | hbox = QHBoxLayout() 479 | 480 | hbox.addWidget(resize_box_width) 481 | hbox.addStretch(1) 482 | hbox.addWidget(self.resize_lineedit_X) 483 | hbox.addStretch(4) 484 | hbox.addWidget(resize_box_height) 485 | hbox.addStretch(1) 486 | hbox.addWidget(self.resize_lineedit_Y) 487 | hbox.addStretch(6) 488 | hbox.addWidget(reset_btn) 489 | hbox.addStretch(1) 490 | hbox.addWidget(resize_btn_ok) 491 | vbox.addLayout(hbox) 492 | 493 | resize_box.setLayout(vbox) 494 | return resize_box 495 | 496 | # Delete the information in the textline displayed on the screen when the mouse is clicked. 497 | def func_mousePressEvent(self, event): 498 | self.resize_lineedit_X.clear() 499 | 500 | # beacon MAC Address save type of list 501 | def func_beacon_mac_append(self): 502 | path = "configs\config_beacon.csv" 503 | beacon_csv_loader = pd.read_csv(path, names =['Address'], header = None) 504 | self.beacon_mac = beacon_csv_loader['Address'].values.tolist() 505 | 506 | # Output the created beacon to the screen 507 | def func_beacon_display(self): 508 | for i in range(len(self.beacon_mac)): 509 | self.func_beacon_create() 510 | 511 | # A function to make the position variables of beacon in list form. 512 | def func_beacon_append(self): 513 | self.table_x = [] 514 | self.table_y = [] 515 | 516 | # Copy estimated beacon positions form the IPS.py 517 | self.estimated_X = self.CopyList[0] 518 | self.estimated_Y = self.CopyList[1] 519 | 520 | # Generate, and copy resize beacon position values. 521 | for i in range(len(self.beacon_mac)): 522 | self.origin_beacon_x.append(nan_to_num(self.estimated_X[i]) * 100) 523 | self.origin_beacon_y.append(nan_to_num(self.estimated_Y[i]) * 100) 524 | 525 | # Calcuate beacon position in area. 526 | for i in range(len(self.beacon_mac)): 527 | self.table_x.append(round(nan_to_num(self.estimated_X[i]), 2)) # self.table_x말고 table_beacon_x 되는지 확인 528 | self.table_y.append(round(nan_to_num(self.estimated_Y[i]), 2)) 529 | 530 | # table 531 | self.table_beacon_x = self.table_x 532 | self.table_beacon_y = self.table_y 533 | 534 | # A function that draws a beacon on the display. 535 | def func_beacon_draw(self): 536 | try: 537 | beacon_img = QGraphicsEllipseItem() 538 | 539 | # Calling a function that stores the positions of the beacon as a list. 540 | self.func_beacon_append() 541 | 542 | # Get a random color in hexadecimal. 543 | self.random_color.append("#"+''.join([random.choice('ABCDEF0123456789') for i in range(6)])) # random color create 544 | 545 | if self.pixmap_resize_x == 0: 546 | # Output the beacon to the existing positions on the display. 547 | beacon_img.setRect(self.origin_beacon_x[self.beacon_cnt],self.origin_beacon_y[self.beacon_cnt], 10, 10) 548 | 549 | else: 550 | # Calculate the proportion of the resized amount and output the beacon on the display. 551 | beacon_img.setRect(self.origin_beacon_x[self.beacon_cnt]*(self.pixmap_resize_x/self.origin_size["X"]), 552 | self.origin_beacon_y[self.beacon_cnt]*(self.pixmap_resize_Y/self.origin_size["Y"]), 10, 10) 553 | 554 | # Assign randomly obtained color to beacon. 555 | self.beacon_color = self.random_color[self.beacon_cnt] 556 | self.penColor = QColor(self.beacon_color) 557 | beacon_img.setBrush(self.penColor) 558 | beacon_img.setPen(QPen(self.penColor, 3)) 559 | self.img_scene.addItem(beacon_img) 560 | self.beacon_items.append(beacon_img) 561 | except IndexError: 562 | pass 563 | 564 | # Function that displays information of beacon as text. 565 | def func_Display_On(self): 566 | try: 567 | # The button is not pressed. 568 | if self.btn_state == 0: 569 | 570 | # Loop as many beacon as created. 571 | for i in range(self.beacon_cnt): 572 | text_box = QGraphicsRectItem() 573 | 574 | # In case of original screen. 575 | if self.pixmap_resize_x == 0: 576 | text_pos_x = self.origin_beacon_x[i] 577 | text_pos_y = self.origin_beacon_y[i] 578 | # In case of changed screen. 579 | else: 580 | # beacon_coordinates_x, and beacon_coordinates_Y calculated as a percentage. 581 | text_pos_x = self.origin_beacon_x[i] * (self.pixmap_resize_x/self.origin_size["X"]) 582 | text_pos_y = self.origin_beacon_y[i] * (self.pixmap_resize_Y/self.origin_size["Y"]) 583 | 584 | # Create a text box containing MAC_Address, coordinates in the calculated coordinate values. 585 | beacon_name=self.img_scene.addText('MAC : {0}\nCoordinate : ({1}, {2}) '.format(str(self.beacon_mac[i]), str(self.table_beacon_x[i]), str(self.table_beacon_y[i]))) 586 | self.beacon_name.append(beacon_name) 587 | 588 | # Where we use the '+12' because hide the overwrap the beacon nodes. 589 | self.beacon_name[i].setPos(int(text_pos_x) + 12, int(text_pos_y) + 12) 590 | text_box.setRect(text_pos_x + 12,text_pos_y +12 ,135,30) 591 | 592 | text_box.setPen(QPen(QColor(self.random_color[i]),1)) 593 | 594 | self.text_line.append(text_box) 595 | self.img_scene.addItem(text_box) 596 | 597 | # Button state pressed. 598 | self.btn_state = 1 599 | 600 | # The button is pressed. 601 | elif self.btn_state == 1: 602 | # Since the button is pressed, the beacon information is added one by one. 603 | text_box = QGraphicsRectItem() 604 | 605 | # In case of original screen. 606 | if self.pixmap_resize_x == 0: 607 | text_pos_x = self.origin_beacon_x[self.beacon_cnt] 608 | text_pos_y = self.origin_beacon_y[self.beacon_cnt] 609 | 610 | # In case of resize screen. 611 | else: 612 | # Coordinates_x and Coordinates_y after correction calculated as a percentage 613 | text_pos_x = self.origin_beacon_x[self.beacon_cnt] * (self.pixmap_resize_x/self.origin_size["X"]) 614 | text_pos_y = self.origin_beacon_y[self.beacon_cnt] * (self.pixmap_resize_Y/self.origin_size["Y"]) 615 | 616 | # Create a text box containing MAC_Address, coordinates in the calculated coordinate values 617 | beacon_text=self.img_scene.addText('MAC : {0}\nCoordinate : ({1}, {2}) '.format(str(self.beacon_mac[self.beacon_cnt]),str(self.table_beacon_x[self.beacon_cnt]), str(self.table_beacon_y[self.beacon_cnt]))) 618 | self.beacon_name.append(beacon_text) 619 | 620 | # Where we use the '+12' because hide the overwrap the beacon nodes. 621 | self.beacon_name[self.beacon_cnt].setPos(int(text_pos_x) + 12, int(text_pos_y) + 12) 622 | text_box.setRect(text_pos_x + 12, text_pos_y +12 ,135,30) 623 | text_box.setPen(QPen(QColor(self.random_color[self.beacon_cnt]),1)) 624 | self.text_line.append(text_box) 625 | self.img_scene.addItem(text_box) 626 | 627 | except IndexError: 628 | pass 629 | except RuntimeError: 630 | pass 631 | 632 | # Delete text from screen when button off is clicked. 633 | def func_Display_Off(self): 634 | try: 635 | # The button is pressed. 636 | if self.btn_state == 1: 637 | self.btn_state = 0 638 | 639 | # Loop as many as the number of created beacons. 640 | for i in range(int(self.beacon_cnt)): 641 | self.func_Display_Off_undo() 642 | except IndexError: 643 | self.btn_state = 0 644 | 645 | # Delete the text information of the beacon. 646 | def func_Display_Off_undo(self): 647 | try: 648 | # Delete variables stored in display textbox. 649 | beacon = self.beacon_name.pop() 650 | self.img_scene.removeItem(beacon) 651 | line = self.text_line.pop() 652 | self.img_scene.removeItem(line) 653 | del beacon 654 | del line 655 | except RuntimeError: 656 | pass 657 | 658 | # Function to find the length of gateway calculated by area. 659 | # For drawing the gateway positions in 2D-pixmap. 660 | def func_GW_image_distance(self): 661 | 662 | # When the area positions are smaller than the existing pixmap size. 663 | if (self.gw_area_x *100) < self.origin_size["X"] or self.gw_area_y * 100 < self.origin_size["Y"]: 664 | 665 | # Save the positions of the gateway using a loop. 666 | for i in range(len(self.gw_value[3])-1): 667 | if i == 0 or i == 3: 668 | self.gateway_pos[i][0] = 10 669 | else: 670 | self.gateway_pos[i][0] = 900 671 | if i == 0 or i == 1: 672 | self.gateway_pos[i][1] = 10 673 | else: 674 | self.gateway_pos[i][1] = 900 675 | else: 676 | for i in range(len(self.gw_value[3])-1): 677 | if i == 0 or i == 3: 678 | self.gateway_pos[i][0] = 10 679 | else: 680 | self.gateway_pos[i][0] = 900 681 | if i == 0 or i == 1: 682 | self.gateway_pos[i][1] = 10 683 | else: 684 | self.gateway_pos[i][1] = 900 685 | 686 | # Show gateway on screen. 687 | def func_GW_Draw(self): 688 | # Gateway gw_value read from csv file and store in new variable. 689 | self.func_GW_image_distance() 690 | 691 | # Loop output to 4 gateway screens. 692 | for i in range(len(self.gw_value[3])-1): 693 | gateway = MovingObject(int(self.gateway_pos[i][0]),int(self.gateway_pos[i][1]),20,self.gw_color[i]) 694 | self.img_scene.addItem(gateway) 695 | 696 | 697 | # Delete the beacon information. 698 | def func_undo(self): 699 | item = self.beacon_items.pop() 700 | self.img_scene.removeItem(item) 701 | origin_beacon_x = self.origin_beacon_x.pop() 702 | origin_beacon_y = self.origin_beacon_y.pop() 703 | beacon_area_x = self.table_beacon_x.pop() 704 | beacon_area_y = self.table_beacon_y.pop() 705 | self.beacon_cnt -=1 706 | 707 | del item 708 | del origin_beacon_x 709 | del origin_beacon_y 710 | del beacon_area_x 711 | del beacon_area_y 712 | 713 | # Delete beacon information. 714 | def func_remove_beacon(self): 715 | try: 716 | # Delete beacon information. 717 | self.func_undo() 718 | 719 | # Delete beacon information of textbox when clicking display button. 720 | if self.btn_state == 1: 721 | self.func_Display_Off_undo() 722 | 723 | # Col +1 for index management after deleting and deleting columns remaining in the table. 724 | self.beacon_table.removeRow(self.beacon_cnt) 725 | self.beacon_table_col -=2 726 | self.beacon_table.setRowCount(self.beacon_table_col) 727 | self.beacon_table_col +=1 728 | except IndexError: 729 | self.beacon_cnt=0 730 | 731 | # Beacon information generation function. 732 | def func_beacon_create(self): 733 | try: 734 | # Beacon screen output. 735 | self.func_beacon_draw() 736 | if self.table_beacon_x[self.beacon_cnt] > 0: 737 | # Beacon information generation. 738 | beacon_mac_address = QTableWidgetItem() 739 | self.text = str(self.beacon_mac[self.beacon_cnt]) 740 | beacon_mac_address.setText(self.text) 741 | beacon_mac_address.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter) 742 | self.beacon_table.setRowCount(self.beacon_table_col) 743 | self.beacon_table.setItem(self.beacon_cnt,2,beacon_mac_address) 744 | 745 | # Original screen. 746 | beacon_coordinate = QTableWidgetItem() 747 | 748 | # Original coordinate X, Y. 749 | self.text = "( "+str(self.table_beacon_x[self.beacon_cnt])+" , "+str(self.table_beacon_y[self.beacon_cnt])+" )" 750 | beacon_coordinate.setText(self.text) 751 | 752 | # Center the texts. 753 | beacon_coordinate.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter) 754 | 755 | # Output to beacon table. 756 | self.beacon_table.setItem(self.beacon_cnt,3,beacon_coordinate) 757 | 758 | # Beacon Type information. 759 | beacon_type = QTableWidgetItem() 760 | self.text = "BLE 5.0 / E8" 761 | beacon_type.setText(self.text) 762 | beacon_type.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter) 763 | self.beacon_table.setItem(self.beacon_cnt,0,beacon_type) 764 | self.TypeName.append(self.text) 765 | 766 | # Beacon ID information. 767 | beacon_id = QTableWidgetItem() 768 | self.text = "BEACON " + str(self.beacon_cnt) 769 | beacon_id.setText(self.text) 770 | beacon_id.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter) 771 | self.beacon_table.setItem(self.beacon_cnt,1,beacon_id) 772 | self.beacontable_node_id.append(self.text) 773 | 774 | # Beacon Color information. 775 | beacon_table_color = QLabel() 776 | beacon_table_color.setStyleSheet("background-color:"+self.beacon_color) 777 | 778 | colorWidget = QWidget(self) 779 | layoutColor = QHBoxLayout(colorWidget) 780 | layoutColor.addWidget(beacon_table_color) 781 | colorWidget.setLayout(layoutColor) 782 | self.beacon_table.setCellWidget(self.beacon_cnt,4,colorWidget) 783 | 784 | # The button pressed. 785 | if self.btn_state == 1: 786 | self.func_Display_On() 787 | self.beacon_cnt+=1 788 | self.beacon_table_col += 1 789 | except IndexError: 790 | pass 791 | 792 | # Show gateway information in screen scaled positions. 793 | def func_resize_gateway(self): 794 | 795 | # Gateway output in screen scaled positions. 796 | for i in range(len(self.gw_value[3])-1): 797 | resize_gw = MovingObject(int(math.ceil((self.gateway_pos[i][0])*(self.pixmap_resize_x/self.origin_size["X"]))),int(math.ceil((self.gateway_pos[i][1])*(self.pixmap_resize_Y/self.origin_size["Y"]))),20,self.gw_color[i]) 798 | self.img_scene.addItem(resize_gw) 799 | 800 | # Regenerate the beacon with the positions calculated with the modified screen. 801 | def func_resize_draw(self): 802 | # Output the beacon with the positions calculated with the modified screen. 803 | for i in range(int(self.beacon_cnt)): 804 | resize_beacon = QGraphicsRectItem() 805 | resize_beacon.setRect(self.origin_beacon_x[i]*(self.pixmap_resize_x/self.origin_size["X"]),self.origin_beacon_y[i]*(self.pixmap_resize_Y/self.origin_size["Y"]), 10,10) 806 | self.beacon_color = self.random_color[i] 807 | self.penColor = QColor(self.beacon_color) 808 | resize_beacon.setBrush(self.penColor) 809 | resize_beacon.setPen(QPen(self.penColor,3 )) 810 | self.img_scene.addItem(resize_beacon) 811 | self.beacon_items.append(resize_beacon) 812 | 813 | # When a button is pressed on the original screen. 814 | if(self.btn_state == 1): 815 | self.func_resize_beacon_update() 816 | 817 | 818 | # Original beacon screen data remove. 819 | def func_resize_undo(self): 820 | beacon_item = self.beacon_items.pop() 821 | del beacon_item 822 | 823 | # Text information output of beacon in resized screen. 824 | def func_resize_beacon_update(self): 825 | try: 826 | # Original size screen button pressed. 827 | if self.btn_state == 1: 828 | 829 | # Button state change. 830 | self.btn_state = 0 831 | 832 | # Original beacon coordinate information display delete. 833 | for i in range(int(self.beacon_cnt)): 834 | self.func_Display_Off_undo() 835 | except IndexError: 836 | self.btn_state = 0 837 | 838 | # Resize beacon coordinate information display output. 839 | for i in range(self.beacon_cnt): 840 | resize_line = QGraphicsRectItem() 841 | 842 | # Coordinates_x, and Coordinates_y after correction calculated as a percentage 843 | text_pos_x = self.origin_beacon_x[i] * (self.pixmap_resize_x/self.origin_size["X"]) 844 | text_pos_y = self.origin_beacon_y[i] * (self.pixmap_resize_Y/self.origin_size["Y"]) 845 | beacon_name = self.img_scene.addText('MAC : {0}\nCoordinate : ({1}, {2}) '.format(str(self.beacon_mac[i]),str(self.table_beacon_x[i]), str(self.table_beacon_y[i]))) 846 | self.beacon_name.append(beacon_name) 847 | self.beacon_name[i].setPos(text_pos_x,text_pos_y) 848 | resize_line.setRect(self.origin_beacon_x[i] * (self.pixmap_resize_x/self.origin_size["X"]), self.origin_beacon_y[i] * (self.pixmap_resize_Y/self.origin_size["Y"]),135,30) 849 | resize_line.setPen(QPen(QColor(self.random_color[i]),1)) 850 | self.text_line.append(resize_line) 851 | self.img_scene.addItem(resize_line) 852 | 853 | self.btn_state = 1 854 | 855 | # Resize screen function. 856 | def func_resizing(self): 857 | try: 858 | # Line edit information save. 859 | self.text_X = self.resize_lineedit_X.text() 860 | self.text_Y = self.resize_lineedit_Y.text() 861 | if (int(self.text_X) and int(self.text_Y) >= 500): 862 | 863 | # # resize pixmap scaled. 864 | self.nodemap = QPixmap('images/SNL_map.png').scaled(int(self.text_X),int(self.text_Y)) 865 | self.pixmap_resize_x = int(self.text_X) 866 | self.pixmap_resize_Y = int(self.text_Y) 867 | self.img_scene = QGraphicsScene() 868 | 869 | # Add a new pixmap to the img_scene. 870 | self.img_scene.addPixmap(self.nodemap) 871 | 872 | # Gateway output in resized screen. 873 | self.func_resize_gateway() 874 | 875 | # Original beacon screen data remove. 876 | for i in range(len(self.beacon_items)): 877 | # Deletion of beacons on the old screen. 878 | self.func_resize_undo() 879 | self.resize_lineedit_X.clear() 880 | self.resize_lineedit_Y.clear() 881 | 882 | # Regenerate as many beacons as the number of beacons with the modified coordinates after deletion. 883 | self.func_resize_draw() 884 | self.img_view.setScene(self.img_scene) 885 | self.vbox.addWidget(self.img_view) 886 | else: 887 | self.resize_lineedit_X.clear() 888 | self.resize_lineedit_Y.clear() 889 | pass 890 | except ValueError: 891 | pass 892 | 893 | def func_reset_gateway(self): 894 | # Gateway output in screen scaled coordinates. 895 | for i in range(4): 896 | reset_gw = MovingObject(int(self.gateway_pos[i][0]),int(self.gateway_pos[i][1]),20,self.gw_color[i]) 897 | self.img_scene.addItem(reset_gw) 898 | 899 | def func_reset_draw(self): 900 | # Output the beacon with the coordinates calculated with the modified screen. 901 | for i in range(int(self.beacon_cnt)): 902 | reset_beacon = QGraphicsRectItem() 903 | reset_beacon.setRect(self.origin_size["X"],self.origin_size["Y"], 10,10) 904 | self.beacon_color = self.random_color[i] 905 | self.penColor = QColor(self.beacon_color) 906 | reset_beacon.setBrush(self.penColor) 907 | reset_beacon.setPen(QPen(self.penColor,3 )) 908 | self.img_scene.addItem(reset_beacon) 909 | self.beacon_items.append(reset_beacon) 910 | 911 | # When a button is pressed on the original screen. 912 | if(self.btn_state == 1): 913 | self.func_reset_beacon_update() 914 | 915 | def func_reset_beacon_update(self): 916 | try: 917 | # Original size screen button pressed. 918 | if self.btn_state == 1: 919 | 920 | # Button state change. 921 | self.btn_state =0 922 | 923 | # Original beacon coordinate information display delete. 924 | for i in range(int(self.beacon_cnt)): 925 | self.func_Display_Off_undo() 926 | except IndexError: 927 | self.btn_state =0 928 | 929 | # Resize beacon coordinate information display output. 930 | for i in range(self.beacon_cnt): 931 | reset_line = QGraphicsRectItem() 932 | 933 | # Position_x, and position_y after correction calculated as a percentage 934 | beacon_name = self.img_scene.addText('MAC : {0}\nCoordinate : ({1}, {2}) '.format(str(self.beacon_mac[i]),str(self.table_beacon_x[i]), str(self.table_beacon_y[i]))) 935 | self.beacon_name.append(beacon_name) 936 | self.beacon_name[i].setPos(self.origin_size["X"], self.origin_size["Y"]) 937 | reset_line.setRect(self.origin_size["X"], self.origin_size["Y"],135,30) 938 | reset_line.setPen(QPen(QColor(self.random_color[i]),1)) 939 | self.text_line.append(reset_line) 940 | self.img_scene.addItem(reset_line) 941 | 942 | self.btn_state = 1 943 | 944 | def func_reset(self): 945 | try: 946 | # In case of resizing. 947 | if self.pixmap_resize_x != 0: 948 | 949 | # Create a size pixmap of the original image. 950 | self.nodemap = QPixmap('images/SNL_map.png').scaled(self.origin_size["X"],self.origin_size["Y"]) 951 | self.img_scene = QGraphicsScene() 952 | self.img_scene.addPixmap(self.nodemap) 953 | 954 | # Gateway output on the display as original gateway positions. 955 | self.func_reset_gateway() 956 | for i in range(len(self.beacon_items)): 957 | 958 | # Deletion of beacons on the old screen. 959 | self.func_resize_undo() 960 | 961 | # Initialize the values of the resize x-axis and y-axis to 0. 962 | self.pixmap_resize_x = 0 963 | self.pixmap_resize_Y = 0 964 | 965 | # Outputs the calculated beacon with the positions of the original beacon. 966 | self.func_reset_draw() 967 | self.img_view.setScene(self.img_scene) 968 | self.vbox.addWidget(self.img_view) 969 | except ValueError: 970 | pass 971 | except RuntimeError: 972 | pass 973 | 974 | # A function that recalls the deleted beacon information. 975 | def func_addbtn(self): 976 | beacon_cnt = len(self.beacon_mac) 977 | 978 | # Delete as many as the number of created beacons. 979 | for i in range(beacon_cnt): 980 | self.func_remove_beacon() 981 | 982 | # Save of self.beacon_items values. 983 | for i in range(beacon_cnt): 984 | self.func_beacon_create() 985 | 986 | def func_update_period(self): 987 | item_len= len(self.beacon_mac) 988 | 989 | # Delete of self.beacon_items values. 990 | for i in range(item_len): 991 | self.func_remove_beacon() 992 | 993 | # Save of self.beacon_items values. 994 | for i in range(item_len): 995 | self.func_beacon_create() -------------------------------------------------------------------------------- /RTLS_framework/RTLS_Utils.py: -------------------------------------------------------------------------------- 1 | ######################################################## NOTE ####################################################### 2 | # Date : 2022-05-27 to 2022-06-30 # 3 | # # 4 | # Defined the functions collections. # 5 | ##################################################################################################################### 6 | 7 | import os 8 | import sys 9 | import csv 10 | import json 11 | import requests 12 | import numpy as np 13 | import pandas as pd 14 | from sympy import Symbol 15 | from datetime import datetime 16 | 17 | from RTLS_Filter import * 18 | from RTLS_Constants import * 19 | 20 | # Define the exporting csv function. 21 | def func_export(token, mac_info, rssi_info, txpower_info): 22 | 23 | # Define file path (write your specific path). 24 | FILE_PATH = 'C:\\Users\\duddn\\Desktop\\SNL_project\\dataset\\' 25 | 26 | # Define timestap. 27 | TIME_STAMP = str(datetime.now().strftime('%Y%m%d%H%M%S')) 28 | 29 | # '/' replace. 30 | GW_NUMBER = token.replace('/', '_') 31 | 32 | # Define file type <.csv>. 33 | FILE_TYPE = '.csv' 34 | 35 | # Getting the beacon datas (list to dictionary). 36 | dict_test = dict(MAC = mac_info, RSSI = rssi_info, POWER = txpower_info) 37 | 38 | try: 39 | # Generate timestamp_gateway.csv. 40 | with open(FILE_PATH + TIME_STAMP + GW_NUMBER + FILE_TYPE, 'w') as outfile: 41 | # Creating a csv writer object. 42 | writerfile = csv.writer(outfile) 43 | 44 | # Writing dictionary keys as headings of csv. 45 | writerfile.writerow(dict_test.keys()) 46 | 47 | # Writing list of dictionary. 48 | writerfile.writerows(zip(*dict_test.values())) 49 | 50 | except IOError: 51 | print("I/O error") 52 | 53 | print('[{}] CSV export successful!'.format(GW_NUMBER.strip('_'))) 54 | return 55 | 56 | # Define the finding file path function. 57 | def func_find_file(wanted_file_string): 58 | if type(wanted_file_string) != str: 59 | print('Your input is not a string. Fllow the example :: configs/config_beacon.csv') 60 | sys.exit() 61 | else: 62 | pass 63 | return os.path.abspath(wanted_file_string) 64 | 65 | # Read the beacon default information 66 | # Define the reading beacon information function. 67 | def func_beacon_config(BC_CONFIG_PATH): 68 | path = func_find_file(BC_CONFIG_PATH) 69 | csv_loader = pd.read_csv(path, names =['Address'], header = None) 70 | beacon_address = csv_loader['Address'].values.tolist() 71 | 72 | # Return beacon_address 73 | return beacon_address 74 | 75 | # Read the gateway default information 76 | def func_gateway_config(gw_config_path): 77 | # If we have gateway configuration ? 78 | if (gw_config_path != None): 79 | path = gw_config_path 80 | csv_loader = pd.read_csv(path, names =['Type', 'Numbering', 'Address', 'X', 'Y','Color'], header = None) 81 | 82 | # convert column to list [Numbering] 83 | gateway_numbering = csv_loader['Numbering'].values.tolist() 84 | gateway_address = csv_loader['Address'].values.tolist() 85 | gateway_X = csv_loader['X'].values.tolist() 86 | gateway_Y = csv_loader['Y'].values.tolist() 87 | gateway_Color = csv_loader['Color'].values.tolist() 88 | 89 | # If we don't have gateway configuration ? 90 | else: 91 | sys.exit('Could not find the configuration file') 92 | 93 | # Return gateway_numbering for UI client panel 94 | return gateway_numbering, gateway_address, gateway_X, gateway_Y, gateway_Color 95 | 96 | # Define the read JSON function. 97 | def func_read_JSON(token, port): 98 | # Concat your webhook server address automatically. 99 | url_connector = (PREFIX_HTTP + URL + DELIMITER_PORT + str(port) + DELIMITER_TOKEN + token) 100 | 101 | try: 102 | # Try to connect your defined server. 103 | connect = requests.get(url_connector) 104 | 105 | if connect.status_code == 200: 106 | # Return . 107 | return json.loads(connect.text) 108 | 109 | except requests.exceptions.Timeout: 110 | pass 111 | except requests.exceptions.ConnectionError: 112 | pass 113 | except requests.exceptions.HTTPError: 114 | pass 115 | except requests.exceptions.RequestException: 116 | pass 117 | 118 | # Check multiple gateay accesses. 119 | def func_condition_GW(G1, G2, G3, G4): 120 | condition = (G1 and G2 and G3 and G4 is not None) 121 | return condition 122 | 123 | 124 | # Local-based 125 | def func_combine(mac, rssi, tx): 126 | mac_info = [] 127 | rssi_info = [] 128 | power_info = [] 129 | 130 | mac_info.append(mac) 131 | rssi_info.append(rssi) 132 | power_info.append(tx) 133 | return mac_info, rssi_info, power_info 134 | 135 | 136 | # Calcuate of the env_factor for distance equation. 137 | # We have to chaning if-elif structure to switch-dict. 138 | def func_cal_ENV(): 139 | 140 | # Get Area size from configulation CSV file. 141 | gateway_value = func_gateway_config("configs\config_gateway.csv") 142 | 143 | # Less than 100m. 144 | if int(gateway_value[0][4]) * int(gateway_value[1][4]) < 100: 145 | ENV_FACTOR = 1.7 146 | 147 | # Less than 500m. 148 | elif int(gateway_value[0][4]) * int(gateway_value[1][4]) < 500: 149 | ENV_FACTOR = 2.1 150 | 151 | # Less than 1km. 152 | elif int(gateway_value[0][4]) * int(gateway_value[1][4]) < 1000: 153 | ENV_FACTOR = 2.4 154 | 155 | # Less than 1.5km. 156 | elif int(gateway_value[0][4]) * int(gateway_value[1][4]) < 1500: 157 | ENV_FACTOR = 2.9 158 | 159 | # Less than 2.0km. 160 | elif int(gateway_value[0][4]) * int(gateway_value[1][4]) < 2000: 161 | ENV_FACTOR = 3.4 162 | 163 | # Over 2.5Km. 164 | else: 165 | ENV_FACTOR = 4.4 166 | 167 | return ENV_FACTOR 168 | 169 | 170 | # RSSI signal-based estimating distance(m) between gateway and target beacon. 171 | def func_cal_distance(rssi, tx_power): 172 | 173 | # Calculates the distance through path-loss propagation model. 174 | path_loss = func_cal_ENV() 175 | 176 | est_distance = [] 177 | 178 | for i in range(0, len(rssi)): 179 | est_distance.append(10 **((tx_power[i] - rssi[i]) / (10 * path_loss))) 180 | 181 | # We calculate the distance average at every second. 182 | # In this approach can be reducing Outliers in a second. 183 | return np.mean(est_distance) 184 | 185 | 186 | # Quadrilateration algorithm. : for debugging integrated IPS software. 187 | # We will improve accuracy based on the hybrid algorithm such as combining fingerprinting and quadrilateration. 188 | # In this algorithm function, we can generate the estimated beacon position automatically. 189 | def func_cal_quad(area_size, d0, d1, d2, d3): 190 | list_x = [] 191 | list_y = [] 192 | 193 | # Getting indoor size. 194 | indoor_size = area_size 195 | 196 | # Generate gateway position automatically. 197 | gateway_pos=[(0,0),(indoor_size[0],0),(indoor_size[0],indoor_size[1]),(0,indoor_size[1])] 198 | 199 | est_x = Symbol('x') 200 | est_y = Symbol('y') 201 | 202 | # Gateway 1 position. 203 | GW0_X = int(gateway_pos[0][0]) 204 | GW0_Y = int(gateway_pos[0][1]) 205 | 206 | # Gateway 2 position. 207 | GW1_X = int(gateway_pos[1][0]) 208 | GW1_Y = int(gateway_pos[1][1]) 209 | 210 | # Gateway 3 position. 211 | GW2_X = int(gateway_pos[2][0]) 212 | GW2_Y = int(gateway_pos[2][1]) 213 | 214 | # Gateway 4 position. 215 | GW3_X = int(gateway_pos[3][0]) 216 | GW3_Y = int(gateway_pos[3][1]) 217 | 218 | # [NOTE] Equation of a circle with radius as distance obtained by RSSI. 219 | # pow((est_x - GW0_X),2) + pow((est_y - GW0_Y),2) == pow(d0,2) 220 | # pow((est_x - GW1_X),2) + pow((est_y - GW1_Y),2) == pow(d1,2) 221 | # pow((est_x - GW2_X),2) + pow((est_y - GW2_Y),2) == pow(d2,2) 222 | # pow((est_x - GW3_X),2) + pow((est_y - GW3_Y),2) == pow(d3,2) 223 | 224 | # [NOTE] Equation passing through the intersection of GW0 and GW1 .. GW2 and GW3. 225 | # 2 * (GW1_X - GW0_X) * est_x + 2 * (GW1_Y - GW0_Y) == d0 - d1 - pow(GW0_X,2) + pow(GW1_X,2) - pow(GW0_Y,2) + pow(GW1_Y,2) 226 | # 2 * (GW2_X - GW1_X) * est_x + 2 * (GW2_Y - GW1_Y) == d1 - d2 - pow(GW1_X,2) + pow(GW2_X,2) - pow(GW1_Y,2) + pow(GW2_Y,2) 227 | # 2 * (GW3_X - GW2_X) * est_x + 2 * (GW3_Y - GW2_Y) == d2 - d3 - pow(GW2_X,2) + pow(GW3_X,2) - pow(GW2_Y,2) + pow(GW3_Y,2) 228 | 229 | # Replace each polynomial with the letters A .. I to improve the complexity of the operation. 230 | A = 2 * (GW1_X - GW0_X) 231 | B = 2 * (GW1_Y - GW0_Y) 232 | 233 | C = pow(d0, 2) - pow(d1, 2) - pow(GW0_X,2) + pow(GW1_X,2) - pow(GW0_Y,2) + pow(GW1_Y,2) 234 | 235 | D = 2 * (GW2_X - GW1_X) 236 | E = 2 * (GW2_Y - GW1_Y) 237 | F = pow(d1, 2) - pow(d2, 2) - pow(GW1_X,2) + pow(GW2_X,2) - pow(GW1_Y,2) + pow(GW2_Y,2) 238 | 239 | G = 2 * (GW3_X - GW2_X) 240 | H = 2 * (GW3_Y - GW2_Y) 241 | I = pow(d2 ,2) - pow(d3, 2) - pow(GW2_X,2) + pow(GW3_X,2) - pow(GW2_Y,2) + pow(GW3_Y,2) 242 | 243 | # Coordinates obtained by trilateration. <3> 244 | est_x = ((F * B) - (E * C)) / ((B * D) - (E * A)) 245 | est_y = ((F * A) - (D * C)) / ((A * E) - (D * B)) 246 | 247 | # Coordinates obtained by quadrilateration. <4> 248 | if (((G*est_x) + (H*est_y)) == I): 249 | est_x = ((I * E) - (H * F)) / ((E * G) - (H * D)) 250 | est_y = ((I * D) - (G * F)) / ((D * H) - (G * E)) 251 | 252 | # Copy the estimated beacon positions each (x, y). 253 | list_x.append(est_x) 254 | list_y.append(est_y) 255 | 256 | return list_x, list_y -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_Broker.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_Broker.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_Constants.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_Constants.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_Filter.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_Filter.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_Server.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_Server.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_UI.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_UI.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/__pycache__/RTLS_Utils.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/__pycache__/RTLS_Utils.cpython-39.pyc -------------------------------------------------------------------------------- /RTLS_framework/configs/config_beacon.csv: -------------------------------------------------------------------------------- 1 | AC:23:3F:AA:45:F5 2 | -------------------------------------------------------------------------------- /RTLS_framework/configs/config_gateway.csv: -------------------------------------------------------------------------------- 1 | GATEWAY,C0,AC:23:3F:C0:E3:DF,, magenta 2 | GATEWAY,C1,AC:23:3F:C0:C0:BD,,green 3 | GATEWAY,C2,AC:23:3F:C0:E3:DE,,yellow 4 | GATEWAY,C3,AC:23:3F:C0:C0:C0,, orange 5 | AREA,4,3,, 6 | -------------------------------------------------------------------------------- /RTLS_framework/font/NanumSquare_acR.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/font/NanumSquare_acR.ttf -------------------------------------------------------------------------------- /RTLS_framework/images/SNL_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/images/SNL_map.png -------------------------------------------------------------------------------- /RTLS_framework/images/intflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FIVEYOUNGWOO/BLE-Sensing-Based-Real-Time-Localization-Framework/a2554bdeafa7d803932fdc76ba874332ad2c2c2b/RTLS_framework/images/intflow.png -------------------------------------------------------------------------------- /RTLS_framework/requirements.txt: -------------------------------------------------------------------------------- 1 | APScheduler==3.9.1 2 | decorator==4.4.2 3 | docopt==0.6.2 4 | entrypoints==0.3 5 | Faker==8.2.0 6 | Flask==2.1.2 7 | Jinja2==3.1.2 8 | livereload==2.6.3 9 | matplotlib==3.5.1 10 | PyQt5==5.15.6 11 | PyQt5-Qt5==5.15.2 12 | PyQt5-sip==12.10.1 13 | requests==2.25.1 14 | schedule==1.1.0 15 | urllib3==1.26.7 16 | uvicorn==0.17.6 17 | vine==5.0.0 18 | vsearch==1.1.0 19 | 20 | pip3 install git+https://github.com/yjg30737/pyqt-translucent-full-loading-screen-thread.git --upgrade -------------------------------------------------------------------------------- /RTLS_framework/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} 8 | 9 | 10 | 12 | 13 | webhook 14 | 15 | 16 |
17 |

Flask webhook Application.

18 |

Change your content and watch your changes automatically reload.

19 |
20 |
21 | 22 |
23 | {% block body %} {% endblock %} 24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /RTLS_framework/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{% endblock %} 3 | {% block body %} 4 | 5 | {% endblock %} --------------------------------------------------------------------------------