Commit 3b16ace6 by Oleksandr Barabash

moved from xlsx to csv

parent b0b757a5
...@@ -18,3 +18,4 @@ package.json ...@@ -18,3 +18,4 @@ package.json
**.pyc **.pyc
.venv .venv
.idea .idea
.DS_Store
""" App """ """ Eter-cli implementation """
import abc
import asyncio import asyncio
import logging import logging
import os
import signal import signal
import sys import sys
from asyncio import AbstractEventLoop from asyncio import AbstractEventLoop
from typing import Optional from concurrent.futures import ThreadPoolExecutor
from typing import Any
from absl import app, flags from absl import app, flags
from absl.flags import FLAGS
from marshmallow import EXCLUDE from marshmallow import EXCLUDE
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from utils.ether_service.entities.data.kr_packet import KrPacketV2 from utils.ether_service.entities.data.kr_packet import KrPacketV2
from utils.ether_service.utils.json_func import json_loads from utils.ether_service.utils.json_func import json_loads
...@@ -24,7 +25,74 @@ flags.DEFINE_string('station_id', None, "Station Id") ...@@ -24,7 +25,74 @@ flags.DEFINE_string('station_id', None, "Station Id")
flags.DEFINE_string('file', None, "File where to save the history") flags.DEFINE_string('file', None, "File where to save the history")
def init_logging(filename=None, level=None): class PacketLogger(abc.ABC):
""" Abstract Logger """
@abc.abstractmethod
def log(self, kr_packet: KrPacketV2) -> None:
""" Log packet """
raise NotImplementedError()
@abc.abstractmethod
def save(self) -> None:
""" Save data """
raise NotImplementedError()
class CSVLogger(PacketLogger):
""" CSVLogger """
BREAK_LINE = "\r\n"
def __init__(self, file_path: str):
self.file_path = file_path
is_file_exists = os.path.exists(self.file_path)
self.file = open(self.file_path, "a+")
if not is_file_exists:
self.write("; ".join(["Station ID", "Timestamp", "Frequency",
"DoA", "Confidence", "Power", "Serial",
"Radio Bearing",
"Compass Offset"]) + self.BREAK_LINE)
self.save()
@staticmethod
def serialize_data(data: Any) -> str:
""" serialize data for the csv format """
if isinstance(data, str):
return data
return str(data)
# noinspection PyBroadException
def write(self, data: str) -> None:
""" Try write """
while True:
try:
self.file.write(data)
return
except Exception:
self.file = open(self.file_path, "a+")
def log(self, kr_packet: KrPacketV2) -> None:
""" Log packet """
self.write("; ".join([
self.serialize_data(kr_packet.station_id),
self.serialize_data(kr_packet.get_timestamp()),
self.serialize_data(kr_packet.freq / 1000000),
self.serialize_data(kr_packet.get_absolute_doa()),
self.serialize_data(kr_packet.conf),
self.serialize_data(kr_packet.power),
self.serialize_data(kr_packet.serial),
self.serialize_data(kr_packet.radio_bearing),
self.serialize_data(kr_packet.compass_offset)
]) + self.BREAK_LINE)
def save(self) -> None:
""" Save data """
self.file.flush()
self.file.close()
def init_logging(filename: str = None, level: int = None) -> None:
""" init logging on the app level """ """ init logging on the app level """
logging_config = {"format": "%(message)s", logging_config = {"format": "%(message)s",
"level": level or logging.DEBUG} "level": level or logging.DEBUG}
...@@ -34,82 +102,11 @@ def init_logging(filename=None, level=None): ...@@ -34,82 +102,11 @@ def init_logging(filename=None, level=None):
logging.basicConfig(**logging_config) logging.basicConfig(**logging_config)
def stop_server(server: WSClient, io_loop: AbstractEventLoop): def on_data_handler(packet_logger: PacketLogger):
""" Stop server and execute finish gracefully """ """ Handle data """
def wr(_signum, _frame):
""" We need a wraper to dynamicaly set server """
server.stop()
io_loop.stop()
return wr
class SpreadSheetHelper:
""" SpreadSheetHelper implementation """
def __init__(self):
self.workbook = Workbook()
self.sheet = self.workbook.active
self.sheet.title = "DoA"
self.row = 1
self.column = 1
self.create_headers()
self.format_columns()
def create_headers(self) -> None:
""" Create headers """
self.sheet.cell(self.row, self.column, "Station ID")
self.sheet.cell(self.row, self.column + 1, "Timestamp")
self.sheet.cell(self.row, self.column + 2, "Frequency")
self.sheet.cell(self.row, self.column + 3, "DoA")
self.sheet.cell(self.row, self.column + 4, "Confidence")
self.sheet.cell(self.row, self.column + 5, "Power")
self.sheet.cell(self.row, self.column + 6, "Serial")
self.sheet.cell(self.row, self.column + 7, "Radio Bearing")
self.sheet.cell(self.row, self.column + 8, "Compass Offset")
self.row += 1
def log_packet(self, kr_packet: KrPacketV2) -> None:
""" Log packet """
self.sheet.cell(self.row, self.column, kr_packet.station_id)
self.sheet.cell(self.row, self.column + 1, kr_packet.get_timestamp())
self.sheet.cell(self.row, self.column + 2, kr_packet.freq / 1000000)
self.sheet.cell(self.row, self.column + 3,
kr_packet.get_absolute_doa())
self.sheet.cell(self.row, self.column + 4, kr_packet.conf)
self.sheet.cell(self.row, self.column + 5, kr_packet.power)
self.sheet.cell(self.row, self.column + 6, kr_packet.serial)
self.sheet.cell(self.row, self.column + 7, kr_packet.radio_bearing)
self.sheet.cell(self.row, self.column + 8, kr_packet.compass_offset)
self.row += 1
def save(self, filename: str) -> None:
""" save workbook """
self.workbook.save(filename)
def format_columns(self) -> None:
""" set width """
for column_cells in self.sheet.columns:
new_column_letter = get_column_letter(column_cells[0].column)
self.sheet.column_dimensions[new_column_letter].width = 20
# noinspection PyShadowingNames
def try_to_log(helper: SpreadSheetHelper, kr_packet: KrPacketV2) -> None:
""" try to save log into the file """
if FLAGS.file is not None:
try:
helper.log_packet(kr_packet)
helper.save(FLAGS.file)
except IOError:
logging.error("save to file error!", exc_info=sys.exc_info())
def handle_message(helper: SpreadSheetHelper) -> None:
""" Handle message wrapper """
def wr(data: bytes) -> None: def wr(data: bytes) -> None:
""" Handle Message """ """ On Data handler """
message = json_loads(data) message = json_loads(data)
if message is None: if message is None:
logger.warning(f"Failed to parse message!") logger.warning(f"Failed to parse message!")
...@@ -118,35 +115,63 @@ def handle_message(helper: SpreadSheetHelper) -> None: ...@@ -118,35 +115,63 @@ def handle_message(helper: SpreadSheetHelper) -> None:
# noinspection PyBroadException # noinspection PyBroadException
try: try:
kr_packet = KrPacketV2.load(message, unknown=EXCLUDE) kr_packet = KrPacketV2.load(message, unknown=EXCLUDE)
station_id = FLAGS.station_id station_id = flags.FLAGS.station_id
if station_id is None or \ if station_id is None or \
station_id.lower() == kr_packet.station_id.lower(): station_id.lower() == kr_packet.station_id.lower():
logging.info(f"{kr_packet}") logger.info(f"{kr_packet}")
try_to_log(helper, kr_packet) packet_logger.log(kr_packet)
except Exception: except Exception:
logging.error("handle_message: Error!", exc_info=sys.exc_info()) logging.error("handle_message: Error!", exc_info=sys.exc_info())
return wr return wr
# noinspection PyShadowingNames def stop_server(server: WSClient, packet_logger: PacketLogger,
def main(_argv) -> None: io_loop: AbstractEventLoop):
""" Main """ """ Stop server and execute finish gracefully """
def wr(_signum, _frame):
""" We need a wraper to dynamicaly set server """
def callback():
""" callback """
if server.is_running:
logging.info("Stopping the service!")
packet_logger.save()
server.stop()
else:
logging.info("Service is already stopping!")
io_loop.call_soon_threadsafe(callback)
return wr
def main(_argv) -> int:
""" main """
# When main is called, means logging has been changed on the abseil level
# Change geventwebsocket and asyncio log levels
logging.getLogger("geventwebsocket").level = logging.WARNING logging.getLogger("geventwebsocket").level = logging.WARNING
logging.getLogger("asyncio").level = logging.INFO logging.getLogger("asyncio").level = logging.INFO
logging_level = logging.INFO
init_logging(level=logging_level) # change default level to INFO
helper = SpreadSheetHelper() init_logging(level=logging.INFO)
io_loop = asyncio.get_event_loop() io_loop = asyncio.get_event_loop()
packet_logger = CSVLogger(flags.FLAGS.file)
ws_client = WSClient(io_loop=io_loop, scheme="https", host="cds.s1z.info", ws_client = WSClient(io_loop=io_loop, scheme="https", host="cds.s1z.info",
path="/api/v1/ws-agg", path="/api/v1/ws-agg",
user="kraken", password="kraken", ca_file="./ca.pem", user="kraken", password="kraken", ca_file="./ca.pem",
on_data_handler=handle_message(helper)) on_data_handler=on_data_handler(packet_logger))
ws_client.start() ws_client.start()
signal.signal(signal.SIGINT, stop_server(ws_client, io_loop)) signal.signal(signal.SIGINT, stop_server(ws_client, packet_logger,
signal.signal(signal.SIGTERM, stop_server(ws_client, io_loop)) io_loop))
signal.signal(signal.SIGTERM, stop_server(ws_client, packet_logger,
io_loop))
io_loop.run_forever() io_loop.run_forever()
return 0
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -15,3 +15,4 @@ stringcase==1.2.0 ...@@ -15,3 +15,4 @@ stringcase==1.2.0
# excel # excel
openpyxl==3.1.2 openpyxl==3.1.2
aiofiles==23.1.0
\ No newline at end of file
...@@ -58,7 +58,7 @@ class KrPacketV2(BasicDataclass): ...@@ -58,7 +58,7 @@ class KrPacketV2(BasicDataclass):
).strftime('%Y-%m-%d %H:%M:%S') ).strftime('%Y-%m-%d %H:%M:%S')
def get_absolute_doa(self): def get_absolute_doa(self):
""" get abolute doa """ """ get absolute doa """
absolute_bearing = self.gps_bearing + (360 - self.radio_bearing) absolute_bearing = self.gps_bearing + (360 - self.radio_bearing)
if absolute_bearing < 0: if absolute_bearing < 0:
absolute_bearing += 360 absolute_bearing += 360
......
""" Web Socket Client """ """ Web Socket Client """
import asyncio import asyncio
import ssl import ssl
import sys
from asyncio import AbstractEventLoop from asyncio import AbstractEventLoop
from typing import Callable, Optional from typing import Callable, Optional, Awaitable
import aiohttp import aiohttp
from absl import logging from absl import logging
from yarl import URL from yarl import URL
class WSClientSocketError(Exception):
""" Basic Socket error """
pass
class WSClient: class WSClient:
""" WebSocket Client """ """ WebSocket Client """
def __init__(self, io_loop: AbstractEventLoop, scheme: str, host: str, def __init__(self, io_loop: AbstractEventLoop, scheme: str, host: str,
path: str, user: str, password: str, ca_file: str = None, path: str, user: str, password: str,
on_data_handler: Optional[Callable] = None): on_data_handler: Callable, ca_file: Optional[str] = None):
self.io_loop = io_loop self.io_loop = io_loop
self.scheme = scheme self.scheme = scheme
self.host = host self.host = host
...@@ -22,7 +26,9 @@ class WSClient: ...@@ -22,7 +26,9 @@ class WSClient:
self.user = user self.user = user
self.password = password self.password = password
self.ca_file = ca_file self.ca_file = ca_file
self.main_job: Optional[Awaitable] = None
self.on_data_handler = on_data_handler
self.url = URL.build(scheme=self.scheme, host=self.host, self.url = URL.build(scheme=self.scheme, host=self.host,
path=self.path, user=self.user, path=self.path, user=self.user,
password=self.password) password=self.password)
...@@ -33,122 +39,76 @@ class WSClient: ...@@ -33,122 +39,76 @@ class WSClient:
self.ssl_context.load_verify_locations(cafile=self.ca_file) self.ssl_context.load_verify_locations(cafile=self.ca_file)
self.is_running = False 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: def start(self) -> None:
""" Add on data handler """ """ Start client """
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: def start_callback(o) -> None:
""" On data received via websocket """ """ start callback """
for handler in self.on_data_handlers: o.is_running = True
handler(data) o.main_job = o.io_loop.create_task(o.run())
def send_data(self, data: str) -> None: self.io_loop.call_soon_threadsafe(start_callback, self)
""" Send data to the server """
data_binary = data.encode("utf-8")
self.queue.put_nowait(data_binary)
async def send_worker(self): def stop(self) -> None:
""" Send worker """ """ Stop client """
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): async def finisher(obj: "WSClient") -> None:
""" Ping worker """ """ Finishes the execution and clears the objects params """
while self.is_running: if obj.main_job is not None:
await asyncio.sleep(10) obj.main_job.cancel()
if self.ws is not None: obj.io_loop.stop()
# 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: def stop_callback(obj: "WSClient") -> None:
""" Start client """ """ start callback """
self.stop() obj.is_running = False
self.is_running = True obj.io_loop.create_task(finisher(obj))
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: self.io_loop.call_soon_threadsafe(stop_callback, self)
""" 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): async def run(self):
""" start web socket connection """ """ start web socket connection """
while self.is_running: while self.is_running:
ws = None
session = aiohttp.ClientSession() session = aiohttp.ClientSession()
# noinspection PyBroadException # noinspection PyBroadException
try: try:
async with session.ws_connect(self.url, async with session.ws_connect(self.url,
ssl=self.ssl_context) as ws: ssl=self.ssl_context) as ws:
logging.info("WS opened.")
self.ws = ws
async for msg in ws: async for msg in ws:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
if msg.type == aiohttp.WSMsgType.BINARY: if msg.type == aiohttp.WSMsgType.BINARY:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
self.on_data_received(msg.data) self.on_data_handler(msg.data)
elif msg.type == aiohttp.WSMsgType.CONTINUATION:
pass
elif msg.type == aiohttp.WSMsgType.TEXT: elif msg.type == aiohttp.WSMsgType.TEXT:
logging.warning("WS received 'TEXT' data, " pass # do nothing
"ignoring...") elif msg.type == aiohttp.WSMsgType.PING:
# TODO(s1z): Check if it's handled on the low level
pass
elif msg.type == aiohttp.WSMsgType.PONG:
# TODO(s1z): Check if it's handled on the low level
pass
elif msg.type == aiohttp.WSMsgType.CLOSE:
raise WSClientSocketError("CLOSE message received")
elif msg.type == aiohttp.WSMsgType.CLOSING:
raise WSClientSocketError("Socket is CLOSING")
elif msg.type == aiohttp.WSMsgType.CLOSED: elif msg.type == aiohttp.WSMsgType.CLOSED:
logging.warning("WS Closed, raising an IOError.") raise WSClientSocketError("Socket is CLOSED")
raise IOError("WS Closed")
elif msg.type == aiohttp.WSMsgType.ERROR: elif msg.type == aiohttp.WSMsgType.ERROR:
raise self.ws.exception() raise WSClientSocketError("Socket ERROR")
except Exception as ex: await asyncio.sleep(0)
await asyncio.sleep(0)
except WSClientSocketError as ex:
exc_info = (ex.__class__, ex, ex.__traceback__) exc_info = (ex.__class__, ex, ex.__traceback__)
logging.warning("WS error.", exc_info=exc_info) logging.warning("WS error.", exc_info=exc_info)
self.ws = None except Exception as ex:
exc_info = (ex.__class__, ex, ex.__traceback__)
logging.warning("Unknown Error received:", exc_info=exc_info)
finally: finally:
if ws is not None:
await ws.close()
if session is not None: if session is not None:
await session.close() await session.close()
await asyncio.sleep(0) 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