Commit 777fc1e2 by Oleksandr Barabash

ether v0.1.0 commit

parent 5b9c96dc
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
-----END CERTIFICATE-----
......@@ -14,9 +14,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import asyncio
import logging
import sys
import uuid
import vincenty as v
import numpy as np
......@@ -37,14 +37,18 @@ from czml3.properties import (
Position, Polyline, PolylineMaterial, PolylineOutlineMaterial,
PolylineDashMaterial, Color, Material
)
from multiprocessing import Process, Queue
from multiprocessing import Process, Queue, Value, Lock, RLock
from bottle import (route, run, request, get, put, response,
redirect, template, static_file)
from sys import version_info
from utils.database_writer import DatabaseWriter
from utils.ether_service.utils.receivers_controller import \
ReceiverController
from utils.ether_service.ether_service import EtherService
from utils.log import init_logging
from utils.ether_service.utils.log import init_logging
from utils.ws_client import WSClient
logger = logging.getLogger(__name__)
......@@ -67,7 +71,7 @@ DATABASE_RETURN = Queue()
d = 40000 # draw distance of LOBs in meters
heading_d = 20000
max_age = 5000
receivers = []
# receivers = []
shared_dict = dict(response=None)
......@@ -85,6 +89,16 @@ class math_settings:
plotintersects = False
class Context:
""" This is an app context.
We use this crap cause we are working with bad df-agg code """
db_writer = None
ws_client = None
receiver_controller = None
receivers_queue = Queue()
# queues
# noinspection PyPep8Naming
class receiver:
""" Stores all variables pertaining to a reveiver.
......@@ -104,9 +118,8 @@ class receiver:
last_processed_at = 0
d_2_last_intersection = [d]
def __init__(self, station_url, station_name=None):
self.station_url = station_url
self.station_id = station_name or f"Unknown-{uuid.uuid4()}"
def __init__(self, station_id):
self.station_id = station_id
self.isAuto = True
self.isActive = True
self.flipped = False
......@@ -125,9 +138,49 @@ class receiver:
self.confidence = 0
self.doa_time = 0
# noinspection PyUnresolvedReferences
def wr_xml(self, station_id, doa, conf, pwr, freq,
latitude, longitude, heading):
# Kerberos-ify the data
confidence_str = "{}".format(np.max(int(float(conf) * 100)))
max_power_level_str = "{:.1f}".format((np.maximum(-100, float(pwr) + 100)))
epoch_time = int(1000 * round(time.time(), 3))
# create the file structure
ET = {}
data = ET.Element('DATA')
xml_st_id = ET.SubElement(data, 'STATION_ID')
xml_time = ET.SubElement(data, 'TIME')
xml_freq = ET.SubElement(data, 'FREQUENCY')
xml_location = ET.SubElement(data, 'LOCATION')
xml_latitide = ET.SubElement(xml_location, 'LATITUDE')
xml_longitude = ET.SubElement(xml_location, 'LONGITUDE')
xml_heading = ET.SubElement(xml_location, 'HEADING')
xml_doa = ET.SubElement(data, 'DOA')
xml_pwr = ET.SubElement(data, 'PWR')
xml_conf = ET.SubElement(data, 'CONF')
xml_st_id.text = str(station_id)
xml_time.text = str(epoch_time)
xml_freq.text = str(freq / 1000000)
xml_latitide.text = str(latitude)
xml_longitude.text = str(longitude)
xml_heading.text = str(heading)
xml_doa.text = doa
xml_pwr.text = max_power_level_str
xml_conf.text = confidence_str
# create a new XML file with the results
html_str = ET.tostring(data, encoding="unicode")
self.DOA_res_fd.seek(0)
self.DOA_res_fd.write(html_str)
self.DOA_res_fd.truncate()
# print("Wrote XML")
# Updates receiver from the remote URL
def update(self, first_run=False):
""" update """
return
try:
xml_contents = etree.parse(self.station_url)
xml_station_id = xml_contents.find('STATION_ID')
......@@ -169,8 +222,6 @@ class receiver:
# TODO(s1z): I've left this line just to show
# that there's something wrong with the receiver!
if first_run:
self.station_id = "Unknown"
if self.isActive:
return
......@@ -187,14 +238,13 @@ class receiver:
self.isActive = False
exc_info = (type(ex), ex, ex.__traceback__)
logger.error("Error occurred", exc_info=exc_info)
logger.error(f"Problem connecting to {self.station_url}, "
logger.error(f"Problem connecting to {self.station_id}, "
f"receiver deactivated. Reactivate in WebUI.")
def receiver_dict(self):
""" Returns receivers properties as a dict,
useful for passing data to the WebUI"""
return {'station_id': self.station_id,
'station_url': self.station_url,
return {'stationId': self.station_id,
'latitude': self.latitude,
'longitude': self.longitude,
'heading': self.heading,
......@@ -593,7 +643,7 @@ def write_geojson(best_point, all_the_points):
# noinspection PyPep8Naming
def write_czml(best_point, all_the_points, ellipsedata,
plotallintersects, eps):
plot_all_intersects, eps):
""" Writes output.czml used by the WebUI """
point_properties = {
"pixelSize": 5.0,
......@@ -626,7 +676,7 @@ def write_czml(best_point, all_the_points, ellipsedata,
best_point_packets = []
ellipse_packets = []
if len(all_the_points) > 0 and (plotallintersects or eps == "0"):
if len(all_the_points) > 0 and (plot_all_intersects or eps == "0"):
all_the_points = np.array(all_the_points)
scaled_time = minmax_scale(all_the_points[:, -1])
all_the_points = np.column_stack((all_the_points, scaled_time))
......@@ -723,22 +773,28 @@ def write_rx_czml():
"height": 48,
"width": 48,
}
for index, x in enumerate(receivers):
if x.isActive and ms.receiving:
if x.confidence > min_conf and x.power > min_power:
receivers_doa = Context.receiver_controller.get_doa()
for index, (station_id, doa_list) in enumerate(receivers_doa.items()):
for doa in doa_list:
confidence = np.max(int(float(doa.confidence) * 100))
power = np.maximum(-100, float(doa.power) + 100)
print(doa.degrees)
if confidence > min_conf and power > min_power:
lob_color = green
elif x.confidence <= min_conf and x.power > min_power:
elif confidence <= min_conf and power > min_power:
lob_color = orange
else:
lob_color = red
lob_start_lat = x.latitude
lob_start_lon = x.longitude
lob_start_lat = doa.latitude
lob_start_lon = doa.longitude
lob_stop_lat, lob_stop_lon = v.direct(
lob_start_lat, lob_start_lon, x.doa, x.lob_length()
lob_start_lat, lob_start_lon, doa.degrees, 60200
)
lob_packets.append(
Packet(
id=f"LOB-{x.station_id}-{index}",
id=f"LOB-{station_id}-{index}",
polyline=Polyline(
material=Material(
polylineOutline=PolylineOutlineMaterial(
......@@ -762,13 +818,14 @@ def write_rx_czml():
)
)
)
heading_start_lat = x.latitude
heading_start_lon = x.longitude
heading_start_lat = doa.latitude
heading_start_lon = doa.longitude
heading_stop_lat, heading_stop_lon = v.direct(
heading_start_lat, heading_start_lon, x.heading, heading_d)
heading_start_lat, heading_start_lon, doa.heading, heading_d
)
lob_packets.append(
Packet(
id=f"HEADING-{x.station_id}-{index}",
id=f"HEADING-{station_id}-{index}",
polyline=Polyline(
material=PolylineMaterial(
polylineDash=PolylineDashMaterial(
......@@ -790,25 +847,101 @@ def write_rx_czml():
)
)
)
else:
lob_packets = []
if x.isMobile is True:
rx_icon = {"image": {"uri": "/static/flipped_car.svg"}}
# if x.heading > 0 or x.heading < 180:
# rx_icon = {"image":{"uri":"/static/flipped_car.svg"},
# "rotation":math.radians(360 - x.heading + 90)}
# elif x.heading < 0 or x.heading > 180:
# rx_icon = {"image":{"uri":"/static/car.svg"},
# "rotation":math.radians(360 - x.heading - 90)}
else:
rx_icon = {"image": {"uri": "/static/tower.svg"}}
receiver_point_packets.append(
Packet(id=f"{x.station_id}-{index}",
billboard={**rx_properties, **rx_icon},
position={"cartographicDegrees": [x.longitude,
x.latitude, 15]})
)
receiver_point_packets.append(
Packet(id=f"{station_id}-{index}",
billboard={**rx_properties, **rx_icon},
position={"cartographicDegrees": [doa.longitude,
doa.latitude, 15]})
)
# for index, x in enumerate(l_receivers):
# if x.isActive and ms.receiving:
# if x.confidence > min_conf and x.power > min_power:
# lob_color = green
# elif x.confidence <= min_conf and x.power > min_power:
# lob_color = orange
# else:
# lob_color = red
# lob_start_lat = x.latitude
# lob_start_lon = x.longitude
# lob_stop_lat, lob_stop_lon = v.direct(
# lob_start_lat, lob_start_lon, x.doa, x.lob_length()
# )
# lob_packets.append(
# Packet(
# id=f"LOB-{x.station_id}-{index}",
# polyline=Polyline(
# material=Material(
# polylineOutline=PolylineOutlineMaterial(
# color=Color(rgba=lob_color),
# outlineColor=Color(rgba=[0, 0, 0, 255]),
# outlineWidth=2
# )
# ),
# clampToGround=True,
# width=5,
# positions=Position(
# cartographicDegrees=[
# lob_start_lon,
# lob_start_lat,
# height,
# lob_stop_lon,
# lob_stop_lat,
# height
# ]
# )
# )
# )
# )
# heading_start_lat = x.latitude
# heading_start_lon = x.longitude
# heading_stop_lat, heading_stop_lon = v.direct(
# heading_start_lat, heading_start_lon, x.heading, heading_d)
# lob_packets.append(
# Packet(
# id=f"HEADING-{x.station_id}-{index}",
# polyline=Polyline(
# material=PolylineMaterial(
# polylineDash=PolylineDashMaterial(
# color=Color(rgba=gray),
# gapColor=Color(rgba=[0, 0, 0, 0])
# )
# ),
# clampToGround=True,
# width=2,
# positions=Position(
# cartographicDegrees=[
# heading_start_lon,
# heading_start_lat,
# height,
# heading_stop_lon,
# heading_stop_lat,
# height]
# )
# )
# )
# )
# else:
# lob_packets = []
#
# if x.isMobile is True:
# rx_icon = {"image": {"uri": "/static/flipped_car.svg"}}
# # if x.heading > 0 or x.heading < 180:
# # rx_icon = {"image":{"uri":"/static/flipped_car.svg"},
# # "rotation":math.radians(360 - x.heading + 90)}
# # elif x.heading < 0 or x.heading > 180:
# # rx_icon = {"image":{"uri":"/static/car.svg"},
# # "rotation":math.radians(360 - x.heading - 90)}
# else:
# rx_icon = {"image": {"uri": "/static/tower.svg"}}
# receiver_point_packets.append(
# Packet(id=f"{x.station_id}-{index}",
# billboard={**rx_properties, **rx_icon},
# position={"cartographicDegrees": [x.longitude,
# x.latitude, 15]})
# )
document_list = [top, ]
document_list.extend(receiver_point_packets)
document_list.extend(lob_packets)
......@@ -908,7 +1041,7 @@ def cesium():
'minpoints': ms.min_samp,
'rx_state': "checked" if ms.receiving is True else "",
'intersect_state': "checked" if ms.plotintersects is True else "",
'receivers': receivers
'receivers': Context.receiver_controller.receivers
}
)
......@@ -947,7 +1080,7 @@ def rx_params():
with information to fill in the RX cards. """
all_rx = {'receivers': {}}
rx_properties = []
for index, x in enumerate(receivers):
for index, x in enumerate(Context.receiver_controller.receivers):
# x.update() # this line blocks web execution, should be async!
rx = x.receiver_dict()
rx['uid'] = index
......@@ -966,15 +1099,15 @@ def tx_czml_out():
min_samp = request.query.minpts if request.query.minpts else str(
ms.min_samp)
if request.query.plotpts == "true":
plotallintersects = True
plot_all_intersects = True
elif request.query.plotpts == "false":
plotallintersects = False
plot_all_intersects = False
else:
plotallintersects = ms.plotintersects
plot_all_intersects = ms.plotintersects
response.set_header(
'Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0')
output = write_czml(*process_data(database_name, eps, min_samp),
plotallintersects, eps)
plot_all_intersects, eps)
return str(output)
......@@ -984,35 +1117,19 @@ def update_rx(action):
""" PUT request to update receiver variables from the WebUI """
data = json.load(request.body)
if action == "new":
receiver_url = data['station_url'].strip()
if any(x.station_url == receiver_url for x in receivers):
logger.warning(f"The station with the url '{receiver_url}' "
"already exists!")
return redirect('/rx_params')
add_receiver(receiver_url)
station_id = data.get('stationId', None)
if station_id is not None:
Context.receiver_controller.register_receiver(station_id)
add_rx_db(station_id)
elif action == "del":
index = int(data['uid'])
command = "DELETE FROM receivers WHERE station_id=?"
DATABASE_EDIT_Q.put(
(command, [(receivers[index].station_id,), ], True))
DATABASE_RETURN.get(timeout=1)
DATABASE_EDIT_Q.put(("done", None, False))
# del_receiver(receivers[index].station_id)
del receivers[index]
station_id = data.get("stationId", None)
if station_id is not None:
Context.receiver_controller.unregister_receiver(station_id)
delete_rx_db(station_id)
elif action == "activate":
index = int(data['uid'])
receivers[index].isActive = data['state']
Context.receiver_controller.update_receiver_active(data)
else:
action = int(action)
try:
receivers[action].isMobile = data['mobile']
receivers[action].inverted = data['inverted']
receivers[action].isSingle = data['single']
# receivers[action].station_url = data['station_url']
receivers[action].update()
update_rx_table()
except IndexError:
logger.error("I got some bad data. Doing nothing out of spite.")
Context.receiver_controller.update_receiver(data)
return redirect('/rx_params')
......@@ -1137,7 +1254,8 @@ def run_receiver(receivers):
if rx.isActive:
# This is very bad part of the code
# We have to change this to the queue for each receiver.
rx.update()
# rx.update()
pass
except IOError:
logger.error("Problem connecting to receiver.")
rx.d_2_last_intersection = []
......@@ -1329,39 +1447,41 @@ def fetch_first_or_none(query):
# noinspection SqlDialectInspection
def add_receiver(receiver_url, station_name=None):
def add_receiver(station_id):
""" Adds a new receiver to the program, saves it in the database. """
conn = sqlite3.connect(database_name)
c = conn.cursor()
try:
if any(x.station_url == receiver_url for x in receivers):
if any(x.station_id == station_id for x in receivers):
logger.warning("Duplicate receiver, ignoring.")
else:
new_receiver = receiver(receiver_url, station_name)
new_receiver = receiver(station_id)
receivers.append(new_receiver)
new_rx = new_receiver.receiver_dict()
to_table = [new_rx['station_id'], new_rx['station_url'],
to_table = [new_rx['stationId'],
new_rx['auto'],
new_rx['mobile'], new_rx['single'], new_rx['latitude'],
new_rx['mobile'],
new_rx['single'],
new_rx['latitude'],
new_rx['longitude']]
command = "INSERT OR IGNORE INTO receivers VALUES (?,?,?,?,?,?,?)"
command = "INSERT OR IGNORE INTO receivers VALUES (?,?,?,?,?,?)"
DATABASE_EDIT_Q.put((command, [to_table, ], True))
DATABASE_RETURN.get(timeout=1)
DATABASE_EDIT_Q.put(("done", None, False))
mobile = fetch_first_or_none(
c.execute("SELECT isMobile FROM receivers "
"WHERE station_id = ?", [new_rx['station_id']])
"WHERE station_id = ?", [new_rx['stationId']])
)
single = fetch_first_or_none(
c.execute("SELECT isSingle FROM receivers "
"WHERE station_id = ?", [new_rx['station_id']])
"WHERE station_id = ?", [new_rx['stationId']])
)
if mobile is not None:
new_receiver.isMobile = bool(mobile)
if single is not None:
new_receiver.isSingle = bool(single)
logger.info("Created new DF Station at " + receiver_url)
logger.info("Created new DF Station at " + station_id)
except AttributeError:
pass
......@@ -1385,12 +1505,41 @@ def read_rx_table():
# noinspection SqlDialectInspection
def add_rx_db(station_id):
""" Create receiver in DB """
for receiver in Context.receiver_controller.receivers:
if receiver.station_id == station_id:
new_rx = receiver.receiver_dict()
to_table = [new_rx['stationId'],
new_rx['auto'],
new_rx['mobile'],
new_rx['single'],
new_rx['latitude'],
new_rx['longitude']]
command = "INSERT OR IGNORE INTO receivers VALUES (?,?,?,?,?,?)"
DATABASE_EDIT_Q.put((command, [to_table, ], True))
DATABASE_RETURN.get(timeout=1)
DATABASE_EDIT_Q.put(("done", None, False))
return
# noinspection SqlDialectInspection
def delete_rx_db(station_id):
""" Delete receiver from DB """
command = "DELETE FROM receivers WHERE station_id=?"
DATABASE_EDIT_Q.put(
(command, [(station_id,), ], True))
DATABASE_RETURN.get(timeout=1)
DATABASE_EDIT_Q.put(("done", None, False))
# noinspection SqlDialectInspection
def update_rx_table():
""" Updates the database with any changes made to the receivers. """
for item in receivers:
for item in Context.receiver_controller.receivers:
rx = item.receiver_dict()
to_table = [rx['auto'], rx['mobile'], rx['single'],
rx['latitude'], rx['longitude'], rx['station_id']]
rx['latitude'], rx['longitude'], rx['stationId']]
command = """UPDATE receivers SET isAuto=?,
isMobile=?,
isSingle=?,
......@@ -1431,112 +1580,18 @@ def fetch_aoi_data():
return aoi_list
class DatabaseWriter(threading.Thread):
""" Database Writer thread """
def __init__(self, db_name):
super().__init__(name=self.__class__.__name__, daemon=True)
self.database_name = db_name
self._is_running = False
self._is_running_lock = threading.Lock()
@property
def is_running(self):
""" Get _is_running safely """
with self._is_running_lock:
return self._is_running
@is_running.setter
def is_running(self, value: bool):
""" set _is_running safely """
with self._is_running_lock:
self._is_running = value
def stop(self):
""" Stop the Thread """
self.is_running = False
def start(self):
""" Start thread """
self.is_running = True
super().start()
# noinspection SqlDialectInspection
def run(self):
""" One thread responsible for all database write operations. """
conn = sqlite3.connect(self.database_name)
c = conn.cursor()
c.execute("""CREATE TABLE IF NOT EXISTS receivers (
station_id TEXT UNIQUE,
station_url TEXT,
isAuto INTEGER,
isMobile INTEGER,
isSingle INTEGER,
latitude REAL,
longitude REAL)
""")
c.execute("""CREATE TABLE IF NOT EXISTS interest_areas (
uid INTEGER,
aoi_type TEXT,
latitude REAL,
longitude REAL,
radius INTEGER)
""")
c.execute("""CREATE TABLE IF NOT EXISTS intersects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time INTEGER,
latitude REAL,
longitude REAL,
num_parents INTEGER,
confidence INTEGER,
aoi_id INTEGER)""")
c.execute("""CREATE TABLE IF NOT EXISTS lobs (time INTEGER,
station_id TEXT,
latitude REAL,
longitude REAL,
confidence INTEGER,
lob REAL)""")
conn.commit()
while self.is_running:
# items should be list of lists
command, items, reply = DATABASE_EDIT_Q.get()
if command == "done":
conn.commit()
elif command == "close":
conn.commit()
conn.close()
if reply:
DATABASE_RETURN.put(True)
break
else:
c.executemany(command, items)
if reply:
DATABASE_RETURN.put(True)
time.sleep(0.001)
def finish():
""" Thangs to do before closing the program. """
logger.info("Processing df-aggregator clean-up tasks")
ms.receiving = False
update_rx_table()
DATABASE_EDIT_Q.put(("close", None, True))
DATABASE_RETURN.get(timeout=1)
if geofile is not None:
write_geojson(*process_data(database_name)[:2])
# kill(getpid(), signal.SIGTERM)
logger.info("clean-up has done")
def stop_server(server):
""" Stop server and execute finish gracefuly """
def wr(_signum, _frame):
""" We need a wraper to dynamicaly set server """
update_rx_table()
server.stop()
server.join()
finish()
# server.join()
if geofile is not None: # OLEKSA, FIX THIS PLEASE ASAP !!!
write_geojson(*process_data(database_name)[:2])
return wr
......@@ -1591,7 +1646,7 @@ if __name__ == '__main__':
logging_level = logging.DEBUG
if not options.debugging: # Limit some spam
# noinspection SpellCheckingInspection
logging.getLogger("geventwebsocket.handler").level = logging.WARNING
logging.getLogger("geventwebsocket").level = logging.WARNING
logging.getLogger("asyncio").level = logging.INFO
logging_level = logging.INFO
init_logging(level=logging_level)
......@@ -1624,20 +1679,10 @@ if __name__ == '__main__':
web = BottleWebServer(options.ipaddr, options.port)
web.start()
db_writer = DatabaseWriter(database_name)
db_writer = DatabaseWriter(database_name, DATABASE_EDIT_Q, DATABASE_RETURN)
db_writer.start()
###############################################
# Reds receivers from the database first, then
# loads receivers in from a user provided list.
###############################################
read_rx_table()
if rx_file:
with open(rx_file, "r") as file2:
for receiver_url in file2.readlines():
add_receiver(receiver_url.strip())
###############################################
# Run the main loop!
###############################################
# while True:
......@@ -1651,7 +1696,24 @@ if __name__ == '__main__':
# # Changed from 1 sec to 10 ms.
# time.sleep(0.001)
ether_service = EtherService(web, db_writer, receivers, shared_dict)
io_loop = asyncio.get_event_loop()
receiver_controller = ReceiverController(database_name, rx_file)
receiver_controller.set_epsilon(ms.eps)
receiver_controller.set_sampling(ms.min_samp)
receiver_controller.set_confidence(ms.min_conf)
receiver_controller.load_receivers()
receiver_controller.start()
ws_client = WSClient(io_loop=io_loop, scheme="https", host="cds.s1z.info",
path="/api/v1/ws-agg",
user="kraken", password="kraken", ca_file="./ca.pem",
on_data_handler=receiver_controller.handle_data)
ws_client.start()
Context.db_writer = db_writer
Context.ws_client = ws_client
Context.receiver_controller = receiver_controller
ether_service = EtherService(web, db_writer, shared_dict, ws_client,
receiver_controller)
signal.signal(signal.SIGINT, stop_server(ether_service))
signal.signal(signal.SIGTERM, stop_server(ether_service))
ether_service.start()
......
......@@ -9,4 +9,6 @@ scikit_learn>=0.23.2
# ether requirements
absl-py==1.4.0
aiohttp==3.8.4
simplejson==3.18.4
simplejson==3.19.1
marshmallow-dataclass==8.5.12
stringcase==1.2.0
......@@ -40,7 +40,7 @@ function editReceivers(rx_json, id) {
if (receivers[id].single) isSingle = "checked";
var stationIDhtml =
`Station ID: <a href="${receivers[id].station_url}" target="_blank">${receivers[id].station_id}</a>`;
`Station ID: <p>${receivers[id].stationId}</p>`;
var singleModeHtml = `&emsp;Single Receiver Mode: <input ${isSingle} id="singlerx_toggle_${id}" type="checkbox" />`;
......@@ -51,7 +51,7 @@ function editReceivers(rx_json, id) {
var freqHtml = `Tuned to ${receivers[id].frequency} MHz`;
var edit_stationIDhtml =
`Station ID:<input style="width: 105px;" type="text" value="${receivers[id].station_id}" name="station_id_${id}" />`;
`Station ID:<input style="width: 105px;" type="text" value="${receivers[id].stationId}" name="station_id_${id}" />`;
var edit_locationHtml =
`Latitude:<input style="width: 105px;" type="text" value="${receivers[id].latitude}" name="station_lat_${id}" />
......@@ -142,8 +142,8 @@ function editReceivers(rx_json, id) {
// ****************************************************
// * Sends Rx station URL to backend and refreshes map
// ****************************************************
function makeNewRx(url) {
const new_rx = { "station_url": url };
function makeNewRx(stationId) {
const new_rx = { "stationId": stationId };
// console.log(new_rx);
const otherParams = {
headers: {
......@@ -178,8 +178,8 @@ function destroyRxCards() {
// *******************************************
// * Removes Rx from Backend and Reloads Map
// *******************************************
function deleteReceiver(uid) {
const del_rx = { "uid": uid };
function deleteReceiver(stationId) {
const del_rx = { "stationId": stationId };
// console.log(new_rx);
const otherParams = {
headers: {
......@@ -200,8 +200,8 @@ function deleteReceiver(uid) {
// *******************************************************
// * Updates Rx active state from Backend and Reloads Map
// *******************************************************
function activateReceiver(uid, state) {
const activate_rx = { "uid": uid, "state": state };
function activateReceiver(stationId, state) {
const activate_rx = { "stationId": stationId, "active": state };
const otherParams = {
headers: {
"content-type": "application/json"
......@@ -224,7 +224,7 @@ function showReceivers(rx_json, id) {
const receivers = rx_json['receivers'];
var stationIDhtml =
`Station ID: <a href="${receivers[id].station_url}" target="_blank">${receivers[id].station_id}</a>`;
`Station ID: <p>${receivers[id].stationId}</p>`;
var locationHtml =
`Location: ${receivers[id].latitude}&#176;, ${receivers[id].longitude}&#176;`;
......@@ -244,7 +244,7 @@ function showReceivers(rx_json, id) {
const headingspan = document.getElementById(`${id}-heading`);
const freqspan = document.getElementById(`${id}-freq`);
document.getElementById(`${id}-activate`)
.setAttribute('onclick', `activateReceiver(${receivers[id].uid}, ${!receivers[id].active})`);
.setAttribute('onclick', `activateReceiver('${receivers[id].stationId}', ${!receivers[id].active})`);
if (receivers[id].active == true) {
document.getElementById(`${id}-activate`)
......@@ -308,7 +308,7 @@ function createReceivers(rx_json, id) {
deletecheck.classList.add("edit-checkbox", "delete-icon");
deletecheck.type = 'checkbox';
deletecheck.id = `${receivers[i].uid}-delete`;
deletecheck.setAttribute('onclick', `deleteReceiver(${receivers[i].uid})`);
deletecheck.setAttribute('onclick', `deleteReceiver('${receivers[i].stationId}')`);
const activateiconspan = document.createElement('span');
activateiconspan.classList.add("material-icons", "activate-icon", "no-select");
......
""" Database writer. Obsolete code that was moved into the process and was
pushed out to this file """
import logging
import signal
import sqlite3
import time
from multiprocessing import Process, Value
from utils.ether_service.utils.log import init_logging
logger = logging.getLogger(__name__)
class DatabaseWriter(Process):
""" Database Writer thread """
def __init__(self, db_name, db_edit_queue, db_return_queue,
loglevel=logging.INFO):
self.database_name = db_name
self.db_edit_queue = db_edit_queue
self.db_return_queue = db_return_queue
self._is_running = Value('i', 0)
self._started = Value('i', 0)
self.loglevel = loglevel
super().__init__(name=self.__class__.__name__, daemon=True)
@property
def has_started(self) -> bool:
""" return _server """
with self._started.get_lock():
return self._started.value == 1
@has_started.setter
def has_started(self, value: bool) -> None:
""" set _started """
with self._started.get_lock():
self._started.value = 1 if value else 0
@property
def is_running(self):
""" Get _is_running safely """
with self._is_running.get_lock():
return self._is_running.value == 1
@is_running.setter
def is_running(self, value: bool):
""" set _is_running safely """
with self._is_running.get_lock():
self._is_running.value = 1 if value else 0
def start(self):
""" Start the process """
logger.warning("start")
self.is_running = True
super().start()
def stop(self):
""" Stop the process """
self.db_edit_queue.put(("close", None, True))
self.db_return_queue.get(timeout=1)
self.is_running = False
# noinspection SqlDialectInspection
def run(self):
""" One thread responsible for all database write operations. """
init_logging(level=self.loglevel)
signal.signal(signal.SIGINT, lambda x, y: None)
signal.signal(signal.SIGTERM, lambda x, y: None)
conn = sqlite3.connect(self.database_name)
c = conn.cursor()
c.execute("""CREATE TABLE IF NOT EXISTS receivers (
station_id TEXT UNIQUE,
isAuto INTEGER,
isMobile INTEGER,
isSingle INTEGER,
latitude REAL,
longitude REAL)
""")
c.execute("""CREATE TABLE IF NOT EXISTS interest_areas (
uid INTEGER,
aoi_type TEXT,
latitude REAL,
longitude REAL,
radius INTEGER)
""")
c.execute("""CREATE TABLE IF NOT EXISTS intersects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time INTEGER,
latitude REAL,
longitude REAL,
num_parents INTEGER,
confidence INTEGER,
aoi_id INTEGER)""")
c.execute("""CREATE TABLE IF NOT EXISTS lobs (time INTEGER,
station_id TEXT,
latitude REAL,
longitude REAL,
confidence INTEGER,
lob REAL)""")
conn.commit()
self.has_started = True
while self.is_running:
command, items, reply = self.db_edit_queue.get()
if command == "done":
conn.commit()
elif command == "close":
conn.commit()
conn.close()
if reply:
self.db_return_queue.put(True)
break
else:
c.executemany(command, items)
if reply:
self.db_return_queue.put(True)
time.sleep(0.001)
""" Kraken data packet implementation """
from dataclasses import dataclass
from typing import Dict
import marshmallow_dataclass
from marshmallow import post_dump, pre_load
from stringcase import camelcase, snakecase
from ..utils.functions import capitalize_first_char
@dataclass
class BasicDataclass:
""" Kraken Packet """
@pre_load
def to_snake_case(self, data, **_kwargs):
""" to snake case pre load method.
Converts javaStyle parameters into the python_style syntax.
:type data: dict
:return str"""
return {snakecase(key): value for key, value in data.items()}
@post_dump
def to_camel_case(self, data, **_kwargs):
""" to camel case post load method """
return {camelcase(key): value for key, value in data.items()}
@classmethod
def get_schema(cls, *args, **kwargs):
""" Get schema """
return marshmallow_dataclass.class_schema(cls)(*args, **kwargs)
def dump(self, *args, **kwargs) -> Dict:
""" Serialize to JSON object """
return self.get_schema(*args, **kwargs).dump(self)
@classmethod
def load(cls, data, *args, **kwargs) -> "BasicDataclass":
""" Deserialize from JSON object """
return cls.get_schema(*args, **kwargs).load(data)
""" Kraken data packet implementation """
from dataclasses import dataclass
from ..basic_dataclass import BasicDataclass
example_packet = {
"tStamp": 1681082256511,
"latitude": "46.6280265",
"longitude": "32.4401186",
"gpsBearing": "0",
"radioBearing": "249",
"conf": "0.69",
"power": "-69.9",
"freq": 441875000,
"antType": "ULA",
"latency": 100,
"doaArray": "0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.33,0.33,0.33,0.33,0.33,0.32,0.32,0.32,0.32,0.32,0.31,0.31,0.31,0.30,0.30,0.30,0.29,0.29,0.28,0.28,0.27,0.27,0.26,0.26,0.25,0.25,0.24,0.24,0.23,0.23,0.22,0.22,0.21,0.21,0.20,0.19,0.19,0.18,0.18,0.17,0.17,0.16,0.16,0.15,0.15,0.14,0.14,0.13,0.13,0.12,0.12,0.11,0.11,0.10,0.10,0.09,0.08,0.08,0.07,0.07,0.06,0.06,0.05,0.05,0.04,0.04,0.03,0.03,0.02,0.02,0.01,0.01,0.01,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.01,0.01,0.02,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.11,0.12,0.14,0.16,0.18,0.20,0.22,0.25,0.27,0.30,0.32,0.35,0.38,0.41,0.44,0.47,0.50,0.53,0.55,0.58,0.61,0.64,0.67,0.69,0.72,0.74,0.76,0.79,0.80,0.82,0.84,0.85,0.86,0.87,0.88,0.88,0.89,0.89,0.89,0.88,0.88,0.88,0.87,0.86,0.85,0.85,0.83,0.82,0.81,0.80,0.79,0.77,0.76,0.75,0.73,0.72,0.71,0.69,0.68,0.67,0.66,0.64,0.63,0.62,0.61,0.60,0.59,0.58,0.57,0.56,0.55,0.55,0.54,0.53,0.53,0.52,0.52,0.51,0.51,0.50,0.50,0.49,0.49,0.49,0.49,0.49,0.49,0.48,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,",
"id": "Fake"
}
@dataclass
class KrPacket(BasicDataclass):
""" Kraken Packet """
t_stamp: int
latitude: float
longitude: float
gps_bearing: int
radio_bearing: int # int or float ???
conf: float
power: float
freq: int
ant_type: str
latency: int # useless shit
doa_array: str
id: str
def __repr__(self):
return f"<KrP: {self.id}::{self.radio_bearing}>"
""" This is a Protocol to wirk with obsolete DF-Aggregator objects """
from typing import Protocol
""" This is a receiver (KrRPI) representation """
import logging
import time
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import List, Dict
from marshmallow import post_dump, EXCLUDE
class Receiver(Protocol):
from .basic_dataclass import BasicDataclass
from .data.kr_packet import KrPacket
from ..utils.rb_tree import RBTree
logger = logging.getLogger(__name__)
load_only = dict(load_only=True)
@dataclass
class Bearing:
""" Bearing Param for calculation """
degrees: float
num: int
power: float
confidence: float
#
latitude: float = field(default=0.0)
longitude: float = field(default=0.0)
heading: float = field(default=0.0)
# noinspection PyPep8Naming
@dataclass
class Receiver(BasicDataclass):
""" Receiver object """
...
station_id: str
# TODO(s1z): obsolete fields. remove this as soon as possible
latitude: float = field(default=0.0)
longitude: float = field(default=0.0)
heading: float = field(default=0.0)
doa: float = field(default=0.0)
frequency: float = field(default=0.0)
power: float = field(default=0.0)
confidence: float = field(default=0.0)
doa_time: int = field(default=0)
mobile: bool = field(default=False)
single: bool = field(default=False)
active: bool = field(default=True)
auto: bool = field(default=True)
inverted: bool = field(default=True)
flipped: bool = field(default=False)
packets: List[KrPacket] = field(default_factory=list, metadata=load_only)
bearings: List[Bearing] = field(default_factory=list, metadata=load_only)
samplings: int = field(init=False, metadata=load_only)
tree: RBTree = field(init=False, metadata=load_only)
class Meta:
""" Meta """
unknown = EXCLUDE
@post_dump
def post_dump(self: "Receiver", data: Dict, **_kwargs) -> Dict:
""" Remove unwanted fields """
data.pop("packets", None)
data.pop("bearings", None)
data.pop("samplings", None)
data.pop("tree", None)
return data
def __post_init__(self):
self.tree = RBTree()
self.samplings = 10
def __eq__(self: "Receiver", other: "Receiver") -> bool:
""" compare two receivers """
return self.station_id == other.station_id
@property
def isActive(self: "Receiver") -> bool:
""" Always active """
return self.active
@property
def isSingle(self: "Receiver") -> bool:
""" link to the single parameter. Obsolete, remove me when fixed """
return self.single
@isSingle.setter
def isSingle(self: "Receiver", value: bool):
""" single setter """
self.single = value
@property
def isMobile(self: "Receiver") -> bool:
""" link to the mobile parameter. Obsolete, remove me when fixed """
return self.mobile
@isMobile.setter
def isMobile(self: "Receiver", value: bool) -> None:
""" mobile setter """
self.mobile = value
def receiver_dict(self: "Receiver") -> Dict[str, any]:
""" obsolete serializator """
return self.get_schema().dump(self)
def clear_bearings(self: "Receiver") -> None:
""" Remove all bearings """
nodes = self.tree.get_all()
for node in nodes:
node.items = []
self.tree.delete_node(node.val)
def update_config(self: "Receiver", packet: KrPacket) -> None:
""" Update latitude, longitude, frequency, heading """
# update position if needed
self.latitude = packet.latitude
self.longitude = packet.longitude
self.frequency = packet.freq
self.heading = packet.gps_bearing
@staticmethod
def get_median_bearing(bearing_one: Bearing,
bearing_two: Bearing) -> Bearing:
""" Get median bearing """
total_num = bearing_one.num + bearing_two.num
total_power = bearing_one.power + bearing_two.power
median_degrees = ((bearing_one.degrees * bearing_one.num) +
(bearing_two.degrees * bearing_two.num)) / total_num
total_confidence = bearing_one.confidence + bearing_two.confidence
return Bearing(median_degrees, total_num,
total_power, total_confidence)
def create_absolute_bearings(self, bearings: List[Bearing],
bearings_len: int) -> List[Bearing]:
""" Create bearings """
i = 0
absolute_bearings = []
while i < bearings_len:
curr_bearing = bearings[i]
if i == 0:
absolute_bearings.append(curr_bearing)
i += 1
prev_bearing = absolute_bearings.pop()
if abs(prev_bearing.degrees - curr_bearing.degrees) <= 3:
absolute_bearings.append(self.get_median_bearing(prev_bearing,
curr_bearing))
else:
absolute_bearings.append(curr_bearing)
i += 1
for a_bearing in absolute_bearings:
a_bearing.power = a_bearing.power / a_bearing.num
a_bearing.confidence = a_bearing.confidence / a_bearing.num
# This code is just copied from the old DF-AGG
if self.inverted:
a_bearing.degrees = self.heading + (360 - a_bearing.degrees)
elif self.flipped:
a_bearing.degrees = self.heading + (180 - a_bearing.degrees)
else:
a_bearing.degrees = self.heading + a_bearing.degrees
if a_bearing.degrees < 0:
a_bearing.degrees += 360
elif a_bearing.degrees > 359:
a_bearing.degrees -= 360
return absolute_bearings
def add_packet(self: "Receiver", packet: KrPacket) -> None:
""" Add KrRpi packet to the receiver """
now = int(time.time() * 1000)
# insert packet
self.tree.insert_node(packet.radio_bearing, packet)
bearings = []
bearings_len = 0
# remove old packets
nodes = self.tree.get_all()
for node in nodes:
bearing = Bearing(node.val, 0, 0, 0)
slice_id = None
for idx, item in enumerate(node.items):
if now - item.t_stamp > 20000: # 20 seconds old
slice_id = idx
else:
bearing.num += 1
bearing.power += item.power
bearing.confidence += item.conf
# add bearings
if bearing.num > 0:
bearings_len += 1
bearings.append(bearing)
# slice the list
if slice_id is not None:
node.items = node.items[slice_id:]
def update(self, first_run=False):
""" Some update logic """
...
# remove node if we have 0 records
if len(node.items) == 0:
self.tree.delete_node(node.val)
def receiver_dict(self):
""" Receiver representation in json format """
...
self.bearings = self.create_absolute_bearings(bearings, bearings_len)
def lob_length(self):
""" Some magic here, i suppose. We skip it for now.
There's a bit of hardcode with 200... """
...
@staticmethod
def lob_length() -> int:
""" lob_length """
return 40200
""" Ether web service """
import asyncio
import logging
from asyncio import AbstractEventLoop
from threading import Thread, Lock
from typing import List, Dict
from typing import List, Dict, Optional
from aiohttp import web
from utils.ether_service.entities.receiver import Receiver
from utils.ether_service.handlers.api.v1.ws import WSHandler
from utils.ether_service.utils import ProcessService
from utils.run_app_fixed import create_task
from utils.ws_client import WSClient
logger = logging.getLogger(__name__)
......@@ -20,15 +23,20 @@ class EtherService(Thread):
2. Do math on the background.
3. Update global variables for the obsolete web service."""
def __init__(self, web_service: Thread, db_writer: Thread,
receivers: List[Receiver], shared_dict: Dict,
def __init__(self, web_service: "BottleWebServer",
db_writer: "DatabaseWriter",
shared_dict: Dict,
ws_client: WSClient,
receiver_controller: ProcessService,
*,
io_loop: Optional[AbstractEventLoop] = None,
interface="0.0.0.0", port=8090):
super().__init__(name=self.__class__.__name__, daemon=True)
self.web_service = web_service
self.db_writer = db_writer
self.receivers = receivers
self.shared_dict = shared_dict
self.ws_client = ws_client
self.receiver_controller = receiver_controller
self._is_running = False
self._is_running_lock = Lock()
......@@ -36,7 +44,7 @@ class EtherService(Thread):
self.interface = interface
self.port = port
self.io_loop = asyncio.get_event_loop()
self.io_loop = io_loop or asyncio.get_event_loop()
self.web_app = web.Application()
self.web_app.add_routes([
WSHandler().compile(),
......@@ -58,10 +66,10 @@ class EtherService(Thread):
def run(self):
""" Thread routine """
self.is_running = True
self.main_task = create_task(self.web_app, host=self.interface,
port=self.port, loop=self.io_loop,
handle_signals=False,
print=lambda x: x)
# self.main_task = create_task(self.web_app, host=self.interface,
# port=self.port, loop=self.io_loop,
# handle_signals=False,
# print=lambda x: x)
logger.info("Server has started")
self.io_loop.run_forever()
logger.info("Server has stopped")
......@@ -75,7 +83,9 @@ class EtherService(Thread):
""" Stop the app """
logger.info("Server is stopping")
self.is_running = False
# add web_server.stop()
# add db_writer.stop()
self.io_loop.call_soon_threadsafe(self.main_task.cancel)
# self.web_service.stop() # How do we stop this crap ?
self.db_writer.stop()
self.receiver_controller.stop()
if self.main_task is not None:
self.io_loop.call_soon_threadsafe(self.main_task.cancel)
self.io_loop.call_soon_threadsafe(self.io_loop.stop)
......@@ -85,6 +85,11 @@ class WSHandler(BaseHandler):
ws = None
connection_id = None
async def handle_text(self, data: str) -> None:
""" handle web socket data """
logging.info(f"Binary data received: {data}")
await self.ws.send_str(data)
@staticmethod
async def handle_binary(data: bytes) -> None:
""" handle web socket data """
......@@ -95,11 +100,11 @@ class WSHandler(BaseHandler):
if msg.type == aiohttp.WSMsgType.ERROR:
ex = self.ws.exception()
exc_info = (ex.__class__, ex, ex.__traceback__)
logging.error("Ws connection closed", exc_info=exc_info)
logging.error("Ws error", exc_info=exc_info)
elif msg.type == aiohttp.WSMsgType.BINARY:
await self.handle_binary(msg.data)
elif msg.type == aiohttp.WSMsgType.TEXT:
await self.ws.send_str(msg.data)
await self.handle_text(msg.data)
@auth_required
async def handler(self, request: Request) -> WebSocketResponse:
......
""" Thread Service implementation """
import logging
import signal
from multiprocessing import Process, Value, Queue
from time import sleep
from ..utils.log import init_logging
logger = logging.getLogger(__name__)
class ProcessService(Process):
""" Service that has start and stop methods and works as a daemon """
def __init__(self, loglevel=logging.INFO):
self.loglevel = loglevel
self._is_running = Value('i', 0)
self.cmd_queue = Queue()
super().__init__(name=self.__class__.__name__, daemon=True)
@property
def is_running(self):
""" Get _is_running safely """
with self._is_running.get_lock():
return self._is_running.value == 1
@is_running.setter
def is_running(self, value: bool):
""" set _is_running safely """
with self._is_running.get_lock():
self._is_running.value = 1 if value else 0
def start(self) -> None:
""" Start the process """
self.is_running = True
super().start()
def stop(self) -> None:
""" Stop the process """
self.is_running = False
self.cmd_queue.put((0, None))
def run(self) -> None:
""" Routine """
signal.signal(signal.SIGINT, lambda x, y: None)
signal.signal(signal.SIGTERM, lambda x, y: None)
init_logging(level=self.loglevel)
while self.is_running:
try:
command = self.cmd_queue.get()
self.handle_command(*command)
except Exception as ex:
exc_info = type(ex), ex, ex.__traceback__
logger.error("main loop execution error", exc_info=exc_info)
sleep(0.001) # sleep for 1ms to prevent 100% CPU load
self.cmd_queue.close()
logger.info(f"{self.name} has been stopped")
def handle_command(self, command: int, *params) -> None:
""" Handle command. Overwrite this in child classes """
raise NotImplementedError()
""" Decorators """
import warnings
def deprecated(f):
""" Show deprecated warning """
def wr(*args, **kwargs):
""" wrapper """
warnings.warn(f"{f} is Deprecated", DeprecationWarning)
return f(*args, **kwargs)
return wr
""" Useful functions """
def capitalize_first_char(data: str) -> str:
""" Capitalize only the first char """
if isinstance(data, str):
if len(data) > 0:
return data[:1].upper() + data[1:]
return data
raise AttributeError(f'Data type "{type(data)}" is not supported')
""" JSON helper module """
import sys
from typing import Any, Union, Optional, Mapping, Iterable
import simplejson as json
import simplejson.scanner as json_scanner
from absl import logging
def json_loads(data: Union[str, bytes], default: Optional[Any] = None) -> \
Union[Mapping[str, Any], Iterable[Mapping[str, Any]]]:
""" Json.loads wrapper, tries to load data and prints errors if any """
try:
j_data = json.loads(data, strict=False)
if isinstance(j_data, dict) or isinstance(j_data, list):
return j_data
except TypeError:
logging.error("json_loads::TypeError:", exc_info=sys.exc_info())
except json_scanner.JSONDecodeError:
logging.error("json_loads::JSONDecodeError:", exc_info=sys.exc_info())
return default
def json_dumps(*args: Any, **kwargs: Mapping[str, Any]) -> str:
""" Json.dumps wrapper, tries to dump data and prints errors if any """
try:
return json.dumps(*args, **kwargs)
except Exception:
logging.error("json_dumps::error:", exc_info=sys.exc_info())
raise
""" Red Black Tree implementation """
import logging
logger = logging.getLogger()
class Color:
""" Node color """
BLACK = 0
RED = 1
class Node:
""" Node """
def __init__(self, val, item=None):
self.val = val
self.parent = None
self.left = None
self.right = None
self.color = Color.RED
self.items = [] if item is None else [item]
def __repr__(self):
""" repr """
return f"<Node::{self.val}({len(self.items)})>"
def get_node(self, val):
""" get node """
if self.val == val:
return self
elif val < self.val and self.left is not None:
return self.left.get_node(val)
elif val > self.val and self.right is not None:
return self.right.get_node(val)
def get_all(self):
""" get all """
nodes = []
if self.left is not None:
nodes.extend(self.left.get_all())
if self.val != -1:
nodes.append(self)
if self.right is not None:
nodes.extend(self.right.get_all())
return nodes
class RBTree:
""" Red Black Tree """
def __init__(self):
self.NULL = Node(-1)
self.NULL.color = Color.BLACK
self.NULL.left = None
self.NULL.right = None
self.root = self.NULL
def insert_node(self, key, item):
""" Insert node """
node = Node(key, item)
node.parent = None
node.val = key
node.left = self.NULL
node.right = self.NULL
node.color = Color.RED
y = None
x = self.root
while x != self.NULL: # Find position for new node
y = x
if node.val < x.val:
x = x.left
elif node.val > x.val:
x = x.right
elif node.val == x.val:
x.items.append(item)
return
node.parent = y # Set parent of Node as y
if y is None: # If parent i.e, is none then it is root node
self.root = node
elif node.val < y.val: # Check if it is right Node or Left Node
# by checking the value
y.left = node
else:
y.right = node
if node.parent is None: # Root node is always Black
node.color = 0
return
if node.parent.parent is None: # If parent of node is Root Node
return
self.fix_insert(node)
def minimum(self, node):
""" Find the min value """
while node.left != self.NULL:
node = node.left
return node
def rotate_left(self, x):
""" Rotate left """
y = x.right # Y = Right child of x
x.right = y.left # Change right child of x to left child of y
if y.left != self.NULL:
y.left.parent = x
y.parent = x.parent # Change parent of y as parent of x
if x.parent is None: # If parent of x == None ie. root node
self.root = y # Set y as root
elif x is x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y
def rotate_right(self, x):
""" Rotate right """
y = x.left # Y = Left child of x
x.left = y.right # Change left child of x to right child of y
if y.right != self.NULL:
y.right.parent = x
y.parent = x.parent # Change parent of y as parent of x
if x.parent is None: # If x is root node
self.root = y # Set y as root
elif x == x.parent.right:
x.parent.right = y
else:
x.parent.left = y
y.right = x
x.parent = y
def fix_insert(self, k):
""" Fix insert """
while k.parent.color == Color.RED: # While parent is red
if k.parent == k.parent.parent.right: # if parent is right child of its parent
u = k.parent.parent.left # Left child of grandparent
if u.color == Color.RED: # if color of left child of grandparent i.e, uncle node is red
u.color = Color.BLACK # Set both children of grandparent node as black
k.parent.color = Color.BLACK
k.parent.parent.color = Color.RED # Set grandparent node as Red
k = k.parent.parent # Repeat the algo with Parent node to check conflicts
else:
if k == k.parent.left: # If k is left child of it's parent
k = k.parent
self.rotate_right(k) # Call for right rotation
k.parent.color = Color.BLACK
k.parent.parent.color = Color.RED
self.rotate_left(k.parent.parent)
else: # if parent is left child of its parent
u = k.parent.parent.right # Right child of grandparent
if u.color == Color.RED: # if color of right child of grandparent i.e, uncle node is red
u.color = Color.BLACK # Set color of childs as black
k.parent.color = Color.BLACK
k.parent.parent.color = Color.RED # set color of grandparent as Red
k = k.parent.parent # Repeat algo on grandparent to remove conflicts
else:
if k == k.parent.right: # if k is right child of its parent
k = k.parent
self.rotate_left(k) # Call left rotate on parent of k
k.parent.color = Color.BLACK
k.parent.parent.color = Color.RED
self.rotate_right(k.parent.parent) # Call right rotate on grandparent
if k == self.root: # If k reaches root then break
break
self.root.color = Color.BLACK # Set color of root as black
def fix_delete(self, x):
""" Fix Nodes after deletion """
while x != self.root and x.color == Color.BLACK: # Repeat until x reaches nodes and color of x is black
if x == x.parent.left: # If x is left child of its parent
s = x.parent.right # Sibling of x
if s.color == Color.RED: # if sibling is red
s.color = Color.BLACK # Set its color to black
x.parent.color = Color.RED # Make its parent red
self.rotate_left(x.parent) # Call for left rotate on parent of x
s = x.parent.right
# If both the child are black
if s.left.color == Color.BLACK and s.right.color == Color.BLACK:
s.color = Color.RED # Set color of s as red
x = x.parent
else:
if s.right.color == Color.BLACK: # If right child of s is black
s.left.color = Color.BLACK # set left child of s as black
s.color = Color.RED # set color of s as red
self.rotate_right(s) # call right rotation on x
s = x.parent.right
s.color = x.parent.color
x.parent.color = Color.BLACK # Set parent of x as black
s.right.color = Color.BLACK
self.rotate_left(x.parent) # call left rotation on parent of x
x = self.root
else: # If x is right child of its parent
s = x.parent.left # Sibling of x
if s.color == Color.RED: # if sibling is red
s.color = Color.BLACK # Set its color to black
x.parent.color = Color.RED # Make its parent red
self.rotate_right(x.parent) # Call for right rotate on parent of x
s = x.parent.left
if s.right.color == Color.BLACK and s.right.color == Color.BLACK:
s.color = Color.RED
x = x.parent
else:
if s.left.color == Color.BLACK: # If left child of s is black
s.right.color = Color.BLACK # set right child of s as black
s.color = Color.RED
self.rotate_left(s) # call left rotation on x
s = x.parent.left
s.color = x.parent.color
x.parent.color = Color.BLACK
s.left.color = Color.BLACK
self.rotate_right(x.parent)
x = self.root
x.color = Color.BLACK
# Function to transplant nodes
def __rb_transplant(self, u, v):
if u.parent is None:
self.root = v
elif u == u.parent.left:
u.parent.left = v
else:
u.parent.right = v
v.parent = u.parent
def delete_node_helper(self, node, key):
""" Delete node """
z = self.NULL
while node != self.NULL: # Search for the node having that value/ key and store it in 'z'
if node.val == key:
z = node
if node.val <= key:
node = node.right
else:
node = node.left
if z == self.NULL: # If Kwy is not present then deletion not possible so return
print("Value not present in Tree !!")
return
y = z
y_original_color = y.color # Store the color of z- node
if z.left == self.NULL: # If left child of z is NULL
x = z.right # Assign right child of z to x
self.__rb_transplant(z, z.right) # Transplant Node to be deleted with x
elif z.right == self.NULL: # If right child of z is NULL
x = z.left # Assign left child of z to x
self.__rb_transplant(z, z.left) # Transplant Node to be deleted with x
else: # If z has both the child nodes
y = self.minimum(z.right) # Find minimum of the right sub tree
y_original_color = y.color # Store color of y
x = y.right
if y.parent == z: # If y is child of z
x.parent = y # Set parent of x as y
else:
self.__rb_transplant(y, y.right)
y.right = z.right
y.right.parent = y
self.__rb_transplant(z, y)
y.left = z.left
y.left.parent = y
y.color = z.color
if y_original_color == Color.BLACK: # If color is black then fixing is needed
self.fix_delete(x)
# Deletion of node
def delete_node(self, val):
""" Delete node """
self.delete_node_helper(self.root, val) # Call for deletion
def __printCall(self, node, indent, last):
if node != self.NULL:
print(indent, end=' ')
if last:
print("R----", end=' ')
indent += " "
else:
print("L----", end=' ')
indent += "| "
s_color = "RED" if node.color == Color.RED else "BLACK"
print(str(node.val) + "(" + s_color + "): ", node.items)
self.__printCall(node.left, indent, False)
self.__printCall(node.right, indent, True)
def get_node(self, val):
""" Get the Node by the value """
return self.root.get_node(val)
def get_all(self):
""" Get all nodes """
return self.root.get_all()
def print_tree(self):
""" print """
self.__printCall(self.root, "", True)
if __name__ == "__main__":
bst = RBTree()
for i in range(1, 6):
bst.insert_node(i, {1: 2})
bst.insert_node(100, {2: 3})
bst.insert_node(100, {3: 4})
bst.insert_node(100, {4: 5})
# bst.print_tree()
print(bst.get_node(100))
print(bst.get_node(120))
# print("\nAfter deleting an element")
# print(bst.get_all())
""" ReceiverController implementation """
import logging
import sqlite3
from multiprocessing import Queue
from typing import Optional, Dict, List
from marshmallow import EXCLUDE
from .decorators import deprecated
from ..entities.data.kr_packet import KrPacket
from ..entities.receiver import Receiver
from ..utils.ProcessService import ProcessService
from ..utils.json_func import json_loads
logger = logging.getLogger()
class ReceiverCommands:
""" Receiver Controller commands """
HANDLE_DATA = 200
REGISTER_RECEIVER = 210 # Register new receiver
UNREGISTER_RECEIVER = 220 # Unregister receiver
LOAD_RECEIVERS = 300 # "Load" from DB/File registered receiver
GET_RECEIVERS = 350 # Get registered receivers list
GET_DOA = 355
UPDATE_RECEIVER = 360
UPDATE_RECEIVER_ACTIVE = 370
SET_EPSILON = 410
SET_SAMPLING = 420
SET_CONFIDENCE = 430
# noinspection SqlDialectInspection
class ReceiverController(ProcessService):
""" The receiver controller.
Controls all working receivers and potentially new ones """
def __init__(self, database_name: Optional[str],
receivers_file: Optional[str] = None) -> None:
self.database_name = database_name
self.receivers_file = receivers_file
self._registered_receivers = {}
self._unregistered_receivers = {}
self.inner_queue = Queue()
self.doa_queue = Queue()
super().__init__()
@property
def receivers(self) -> List[Receiver]:
""" get receivers """
self.cmd_queue.put_nowait((ReceiverCommands.GET_RECEIVERS, ))
return self.inner_queue.get()
def get_doa(self) -> List[Dict]:
""" Get receivers' bearings """
self.cmd_queue.put_nowait((ReceiverCommands.GET_DOA, ))
return self.doa_queue.get()
def set_epsilon(self, value: int) -> None:
""" Set epsilon for all receivers """
self.cmd_queue.put_nowait((ReceiverCommands.SET_EPSILON, value))
def set_sampling(self, value: int) -> None:
""" Set min sampling size for all receivers """
self.cmd_queue.put_nowait((ReceiverCommands.SET_SAMPLING, value))
def set_confidence(self, value: int) -> None:
""" Set min confidence for all receivers """
self.cmd_queue.put_nowait((ReceiverCommands.SET_CONFIDENCE, value))
def update_receiver(self, data: Dict) -> None:
""" Update receiver parameters """
self.cmd_queue.put_nowait((ReceiverCommands.UPDATE_RECEIVER, data))
def update_receiver_active(self, data: Dict) -> None:
""" Update receiver active parameter """
self.cmd_queue.put_nowait((ReceiverCommands.UPDATE_RECEIVER_ACTIVE,
data))
@deprecated
def load_receivers(self) -> None:
""" Load stored receivers """
self.cmd_queue.put_nowait((ReceiverCommands.LOAD_RECEIVERS, ))
def register_receiver(self, station_id: str) -> None:
""" Register receiver """
self.cmd_queue.put_nowait((ReceiverCommands.REGISTER_RECEIVER,
station_id))
def unregister_receiver(self, station_id: str) -> None:
""" Unregister receiver """
self.cmd_queue.put_nowait((ReceiverCommands.UNREGISTER_RECEIVER,
station_id))
def get_registered_receiver(self, station_id) -> Optional[Receiver]:
""" Get registered receiver by station_id """
return self._registered_receivers.get(station_id, None)
def get_or_create_unregistered_receiver(self,
station_id) -> Optional[Receiver]:
""" Get or create unregistered receiver """
receiver = self._unregistered_receivers.get(station_id, None)
if receiver is None and station_id is not None:
receiver = Receiver(station_id)
self._unregistered_receivers[station_id] = receiver
return receiver
def get_receiver(self, station_id) -> Optional[Receiver]:
""" Get a receiver by station_id """
return (self.get_registered_receiver(station_id) or
self.get_or_create_unregistered_receiver(station_id))
def add_registered_receiver(self, receiver: Receiver) -> None:
""" Add registered receiver """
if receiver.station_id not in self._registered_receivers:
self._registered_receivers[receiver.station_id] = receiver
return
logger.warning(f"Receiver '{receiver.station_id}' is already "
f"in registered list")
def pop_registered_receiver(self, station_id: str) -> Optional[Receiver]:
""" Pop receiver from the unregistered list.
We execute this when we're adding a receiver into registered """
if station_id in self._registered_receivers:
return self._registered_receivers.pop(station_id, None)
def del_registered_receiver(self, receiver: Receiver) -> None:
""" Delete receiver from the unregistered list.
We execute this when we're adding a receiver into registered """
if receiver.station_id in self._registered_receivers:
del self._registered_receivers[receiver.station_id]
def add_unregistered_receiver(self, receiver: Receiver) -> None:
""" Add Unregistered receiver.
Whether receiver was unregistered or metadata received
from unknown receiver, the receiver goes to this list """
if receiver.station_id not in self._unregistered_receivers:
self._unregistered_receivers[receiver.station_id] = receiver
return
logger.warning(f"Receiver '{receiver.station_id}' is already "
f"in unregistered list")
def pop_unregistered_receiver(self, station_id: str) -> Optional[Receiver]:
""" Pop receiver from the unregistered list.
We execute this when we're adding a receiver into registered """
if station_id in self._unregistered_receivers:
return self._unregistered_receivers.pop(station_id, None)
def del_unregistered_receiver(self, receiver: Receiver) -> None:
""" Delete receiver from the unregistered list.
We execute this when we're adding a receiver into registered """
if receiver.station_id in self._unregistered_receivers:
del self._unregistered_receivers[receiver.station_id]
def handle_data(self, data: bytes) -> None:
""" Handle incoming data """
self.cmd_queue.put_nowait((ReceiverCommands.HANDLE_DATA, data))
def load_receivers_from_file(self):
""" Reads receivers from the text file """
if self.receivers_file:
with open(self.receivers_file, "r") as f:
for receiver_name in f.readlines():
self.register_receiver(receiver_name.strip())
def load_receivers_from_db(self):
""" Reads receivers from the database into the program. """
conn = None
try:
conn = sqlite3.connect(self.database_name)
c = conn.cursor()
c.execute("SELECT station_id FROM receivers")
rx_list = c.fetchall()
for data in rx_list:
(station_id,) = [v.strip() for v in data]
self.register_receiver(station_id)
except sqlite3.OperationalError:
logger.error("Failed to fetch receivers from DB")
finally:
conn.close() if conn is not None else None
def handle_command(self, command: int, *params) -> None:
""" Handle receiver commands """
if command == ReceiverCommands.HANDLE_DATA:
(data_bytes, ) = params
data = json_loads(data_bytes, {})
kr_packet = KrPacket.load(data, unknown=EXCLUDE)
receiver = self.get_registered_receiver(kr_packet.id)
if receiver and receiver.active:
# TODO(s1z): Add bearings to the queue
return receiver.add_packet(kr_packet)
# create receiver if does not exist
if receiver is None:
receiver = self.get_or_create_unregistered_receiver(
kr_packet.id
)
receiver.update_config(kr_packet)
elif command == ReceiverCommands.REGISTER_RECEIVER:
(station_id, ) = params
if station_id not in self._registered_receivers:
receiver = self.pop_unregistered_receiver(station_id)
if receiver is None:
receiver = Receiver(station_id)
self.add_registered_receiver(receiver)
elif command == ReceiverCommands.UNREGISTER_RECEIVER:
(station_id, ) = params
if station_id not in self._unregistered_receivers:
receiver = self.pop_registered_receiver(station_id)
if receiver is None:
receiver = Receiver(station_id)
self.add_unregistered_receiver(receiver)
elif command == ReceiverCommands.LOAD_RECEIVERS:
self.load_receivers_from_file()
self.load_receivers_from_db()
elif command == ReceiverCommands.GET_RECEIVERS:
data = [receiver for _, receiver in
self._registered_receivers.items()]
self.inner_queue.put(data)
elif command == ReceiverCommands.SET_CONFIDENCE:
(confidence, ) = params
for _, receiver in self._registered_receivers.items():
receiver.confidence = confidence
elif command == ReceiverCommands.SET_SAMPLING:
(samplings, ) = params
for _, receiver in self._registered_receivers.items():
receiver.samplings = samplings
for _, receiver in self._unregistered_receivers.items():
receiver.samplings = samplings
elif command == ReceiverCommands.UPDATE_RECEIVER:
(data, ) = params
update_receiver = Receiver.load(data)
station_id = update_receiver.station_id
receiver = self.get_receiver(station_id)
if receiver is not None:
receiver.mobile = update_receiver.mobile
receiver.inverted = update_receiver.inverted
receiver.single = update_receiver.single
receiver.active = update_receiver.active
elif command == ReceiverCommands.UPDATE_RECEIVER_ACTIVE:
(data, ) = params
update_receiver = Receiver.load(data)
station_id = update_receiver.station_id
receiver = self.get_receiver(station_id)
if receiver is not None:
receiver.active = update_receiver.active
if not receiver.active:
receiver.clear_bearings()
elif command == ReceiverCommands.GET_DOA:
doa_list = {}
for _, receiver in self._registered_receivers.items():
receiver_doa = []
for doa in receiver.bearings:
doa.latitude = receiver.latitude
doa.longitude = receiver.longitude
doa.heading = receiver.heading
receiver_doa.append(doa)
doa_list[receiver.station_id] = receiver_doa
self.doa_queue.put_nowait(doa_list)
""" Web Socket Client """
import asyncio
import ssl
import sys
from asyncio import AbstractEventLoop
from typing import Callable, Optional
import aiohttp
from absl import logging
from yarl import URL
class WSClient:
""" WebSocket Client """
def __init__(self, io_loop: AbstractEventLoop, scheme: str, host: str,
path: str, user: str, password: str, ca_file: str = None,
on_data_handler: Optional[Callable] = None):
self.io_loop = io_loop
self.scheme = scheme
self.host = host
self.path = path
self.user = user
self.password = password
self.ca_file = ca_file
self.url = URL.build(scheme=self.scheme, host=self.host,
path=self.path, user=self.user,
password=self.password)
# Create SSL Context
self.ssl_context = ssl.create_default_context()
if self.ca_file is not None:
self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
self.ssl_context.load_verify_locations(cafile=self.ca_file)
self.is_running = False
self.ws = None
self.main_job = None
self.ping_job = None
self.send_job = None
self.queue = asyncio.Queue()
self.on_data_handlers = []
self.add_on_data_handler(on_data_handler)
def add_on_data_handler(self, handler: Callable) -> None:
""" Add on data handler """
if callable(handler):
self.io_loop.call_soon_threadsafe(self.on_data_handlers.append,
handler)
def del_on_data_handler(self, handler: Callable) -> None:
""" Del on data handler """
def delete():
""" real function """
try:
index = self.on_data_handlers.index(handler)
self.on_data_handlers.pop(index)
except ValueError:
pass
self.io_loop.call_soon_threadsafe(delete)
def on_data_received(self, data: bytes) -> None:
""" On data received via websocket """
for handler in self.on_data_handlers:
handler(data)
def send_data(self, data: str) -> None:
""" Send data to the server """
data_binary = data.encode("utf-8")
self.queue.put_nowait(data_binary)
async def send_worker(self):
""" Send worker """
data = None
while self.is_running:
if self.ws is not None:
if data is None:
data = await self.queue.get()
# noinspection PyBroadException
try:
await self.ws.send_bytes(data)
self.queue.task_done()
data = None
except Exception:
exc_info = sys.exc_info()
logging.warning("send worker failed with error",
exc_info=exc_info)
await asyncio.sleep(0)
async def ping_worker(self):
""" Ping worker """
while self.is_running:
await asyncio.sleep(10)
if self.ws is not None:
# noinspection PyBroadException
try:
await self.ws.ping()
except Exception:
exc_info = sys.exc_info()
logging.warning("ping worker failed with error",
exc_info=exc_info)
def start(self) -> None:
""" Start client """
self.stop()
self.is_running = True
self.main_job = self.io_loop.create_task(self.run())
self.ping_job = self.io_loop.create_task(self.ping_worker())
self.send_job = self.io_loop.create_task(self.send_worker())
def stop(self) -> None:
""" Stop client """
self.is_running = False
if self.main_job:
self.main_job.cancel()
self.main_job = None
if self.ping_job:
self.ping_job.cancel()
self.ping_job = None
if self.send_job:
self.send_job.cancel()
self.send_job = None
async def run(self):
""" start web socket connection """
while self.is_running:
session = aiohttp.ClientSession()
# noinspection PyBroadException
try:
async with session.ws_connect(self.url,
ssl=self.ssl_context) as ws:
logging.info("WS opened.")
self.ws = ws
async for msg in ws:
# noinspection PyUnresolvedReferences
if msg.type == aiohttp.WSMsgType.BINARY:
# noinspection PyUnresolvedReferences
self.on_data_received(msg.data)
elif msg.type == aiohttp.WSMsgType.TEXT:
logging.warning("WS received 'TEXT' data, "
"ignoring...")
elif msg.type == aiohttp.WSMsgType.CLOSED:
logging.warning("WS Closed, raising an IOError.")
raise IOError("WS Closed")
elif msg.type == aiohttp.WSMsgType.ERROR:
raise self.ws.exception()
except Exception as ex:
exc_info = (ex.__class__, ex, ex.__traceback__)
logging.warning("WS error.", exc_info=exc_info)
self.ws = None
await asyncio.sleep(0)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment