Skip to content

Commit

Permalink
Update Image retreival to use new HTTP-based system + more
Browse files Browse the repository at this point in the history
More: Use better type hints for GroundStation-dependent classes, attempt filter for groundstation vs autopilot logs, other bugfixes
  • Loading branch information
krishnans2006 committed Jun 6, 2022
1 parent dbd2a1e commit 91f8046
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ cython_debug/
!/server/assets/odlc_images/sample.png
/server/handlers/uav/uav_mission.txt
/server/handlers/ugv/ugv_mission.txt
/client/public/map/*/*/*
odlc.json
log.txt
debug_log.txt
11 changes: 7 additions & 4 deletions client/src/pages/FlightData/tabs/Logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const Logs = () => {
const [logs, setLogs] = useState([])
const [autoScroll, setAutoScroll] = useState(true)
const [filters, setFilters] = useState(["[INFO ]", "[IMPORTANT]", "[WARNING ]", "[ERROR ]", "[CRITICAL ]"])
const [types, setTypes] = useState(["(groundstation)", "(autopilot)"])
const autoScrollRef = useRef()
const scrollDiv = useRef()
const container = useRef()
Expand Down Expand Up @@ -86,6 +87,7 @@ const Logs = () => {
return (
<StyledContainer>
<CheckboxList name="logFilter" onChange={(e) => {
console.log(e.target.value)
if (e.target.checked) {
if (!filters.includes(e.target.value)) {
setFilters([...filters, e.target.value])
Expand All @@ -109,9 +111,10 @@ const Logs = () => {
<CheckboxList.Option checked={filters.includes("[ERROR ]")} value="[ERROR ]" color={colors.ERROR}>Error</CheckboxList.Option>
<CheckboxList.Option checked={filters.includes("[CRITICAL ]")} value="[CRITICAL ]" color={colors.CRITICAL}>Critical</CheckboxList.Option>
</Column>
<div>
<Column gap="0em">
<ScrollButton onChange={() => window.open("http://localhost:5000/logs")}>Open Log File</ScrollButton>
<ScrollButton onChange={() => { scrollDiv.current.scrollIntoView(); setAutoScroll(true) }}>Scroll To End</ScrollButton>
</div>
</Column>
</Row>
</CheckboxList>
<StyledLogsContainer ref={container}>
Expand Down Expand Up @@ -144,9 +147,9 @@ const StyledLog = ({ content }) => {
}

const ScrollButton = styled(Button)`
margin: 2em 0 0 auto;
margin: auto 0 0 auto;
width: 75%;
height: 2.5em;
height: 1.5em;
`

const StyledContainer = styled.div`
Expand Down
9 changes: 8 additions & 1 deletion server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ def log_root(message, *args, **kwargs):

logger = logging.getLogger("groundstation")
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("[%(levelname)-9s] %(asctime)s %(message)s")

autopilot = logging.getLogger("autopilot")
autopilot.setLevel(logging.DEBUG)

formatter = logging.Formatter("[%(levelname)-9s] (%(name)s) %(asctime)s %(message)-500s")

# console_handler = logging.StreamHandler(sys.stdout)
# console_handler.setLevel(logging.IMPORTANT)
Expand All @@ -53,17 +57,20 @@ def log_root(message, *args, **kwargs):
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
autopilot.addHandler(file_handler)

debug_file_handler = logging.FileHandler("logs/debug.log", mode="w")
debug_file_handler.setLevel(logging.DEBUG)
debug_file_handler.setFormatter(formatter)
logger.addHandler(debug_file_handler)
autopilot.addHandler(debug_file_handler)

LOG_STREAM = StringIO()
string_handler = logging.StreamHandler(LOG_STREAM)
string_handler.setLevel(logging.DEBUG)
string_handler.setFormatter(formatter)
logger.addHandler(string_handler)
autopilot.addHandler(string_handler)

logger.info("STARTED LOGGING")

Expand Down
26 changes: 20 additions & 6 deletions server/groundstation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import time
from threading import Thread

import requests

import errors
from handlers import DummyUAV, ProdUAV
from handlers import DummyUGV, ProdUGV
Expand Down Expand Up @@ -92,14 +94,26 @@ def ugv_thread(self):
time.sleep(0.1)

def image_thread(self):
if not self.config["uav"]["images"]["type"] == "prod": # Initialize a socket connection
self.image.socket_connect()
if self.config["uav"]["images"]["type"] == "prod": # Initialize a socket connection
while True:
time.sleep(1)
try:
res = requests.get(f"{self.config['uav']['images']['url']}/last_image")
except Exception:
self.logger.error("[Image] Cannot connect to FlightSoftware, retrying in 5 seconds")
time.sleep(4)
continue
if res.status_code != 200:
self.logger.error("[Image] Unable to retreive image count, retrying in 5 seconds")
time.sleep(4)
continue
img_cnt = res.json()["result"]
if img_cnt != self.image.img_count:
self.image.retreive_image(img_cnt)
else: # Use a dummy connection
while True:
run = self.image.retreive_images()
if run:
self.logger.info("[Image] Successfully identified ODLC from Image")
time.sleep(0.1)
self.image.dummy_retreive_image()
time.sleep(2)

def async_calls(self):
print("╔═══ STARTING ASYNC THREADS")
Expand Down
63 changes: 24 additions & 39 deletions server/handlers/image.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,49 @@
from __future__ import annotations
import base64
import logging
import string
import time
import typing
from random import random, randint, choice

import eventlet
import requests
import socketio
from dotenv import load_dotenv

from handlers.utils import decorate_all_functions, log

load_dotenv()

if typing.TYPE_CHECKING:
from groundstation import GroundStation


@decorate_all_functions(log, logging.getLogger("groundstation"))
class ImageHandler:
def __init__(self, gs, config):
self.logger = logging.getLogger("groundstation")
self.gs = gs
self.gs: GroundStation = gs
self.config = config
self.sio = self.app = None
self.img_count = -1
print("╠ CREATED IMAGE HANDLER")
self.logger.info("CREATED IMAGE HANDLER")

def initialize(self):
self.sio = socketio.Server(max_http_buffer_size=40_000_000)
self.app = socketio.WSGIApp(self.sio)

@self.sio.event
def connect(sid, _):
self.logger.important("[Image] Socketio connection established (sid: %s)", sid)

@self.sio.event
def image(sid, data):
self.logger.debug(
"[Image] Successfully retreived image from socketio connection (sid: %s)", sid
)
img = data["image"]
if self.process_image(img):
self.logger.info("[Image] Successfully identified ODLC from Image (sid: %s)", sid)

@self.sio.event
def disconnect(sid):
self.logger.warning("[Image] Lost socketio connection (sid: %s)", sid)

print("╠ INITIALIZED IMAGE HANDLER")
self.logger.info("INITIALIZED IMAGE HANDLER")

def socket_connect(self):
eventlet.wsgi.server(
eventlet.listen(
(self.config["uav"]["images"]["host"], self.config["uav"]["images"]["port"])
),
self.app,
log_output=False,
)
def retreive_image(self, img_cnt):
for i in range(self.img_count, img_cnt):
self.logger.info("Retreiving image %s", i)
img_res = requests.get(self.config["uav"]["images"]["url"] + f"/image/{i}")
if img_res.status_code == 200 and self.process_image(img_res.content):
self.logger.info("[Image] Successfully identified ODLC from Image")
time.sleep(1)
self.img_count = img_cnt

# When socket connection is not used
def retreive_images(self):
# Retreives Image from UAV
def dummy_retreive_image(self):
# Retrieves Image from UAV
if random() < 1: # Every image (until CV implementation)
with open("assets/odlc_images/sample.png", "rb") as image_file:
img = base64.b64encode(image_file.read())
Expand All @@ -67,14 +53,14 @@ def retreive_images(self):
return False

def process_image(self, image):
if random() < 0.05: # 5% chance that the image is of an ODLC
if random() < 1: # 5% chance that the image is of an ODLC
# Dummy Data
self.gs.interop.odlc.add_to_queue(
self.gs.interop.odlc_add_to_queue(
image,
"standard",
random() * 90,
random() * -90,
randint(0, 360),
self.gs.uav.lat,
self.gs.uav.lon,
self.gs.uav.orientation["yaw"],
choice(
[
"circle",
Expand Down Expand Up @@ -119,7 +105,6 @@ def process_image(self, image):
"orange",
]
),
log=False,
)
return True
return False
Expand Down
11 changes: 8 additions & 3 deletions server/handlers/interop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations
import base64
import json
import logging
import os
import typing
from datetime import datetime, timedelta, date

from auvsi_suas.client import client
Expand All @@ -12,6 +14,9 @@
from errors import InvalidRequestError, InvalidStateError, GeneralError, ServiceUnavailableError
from handlers.utils import decorate_all_functions, log

if typing.TYPE_CHECKING:
from groundstation import GroundStation


def json_serial(obj):
if isinstance(obj, (datetime, date)):
Expand Down Expand Up @@ -55,7 +60,7 @@ def __init__(self, gs, config):
self.logger = logging.getLogger("groundstation")
print("╠ CREATED INTEROP HANDLER")
self.logger.info("CREATED INTEROP ERROR")
self.gs = gs
self.gs: GroundStation = gs
self.config = config
self.mission_id = self.config["interop"]["mission_id"]
self.login_status = False
Expand Down Expand Up @@ -206,7 +211,7 @@ def odlc_get_queue(self, filter_val=3):

def odlc_add_to_queue(
self,
image: str = None,
image: bytes = None,
type_: str = None,
lat: float = None,
lon: float = None,
Expand All @@ -219,7 +224,7 @@ def odlc_add_to_queue(
):
try:
with open(f"assets/odlc_images/{len(self.odlc_queued_data)}.png", "wb") as file:
file.write(base64.b64decode(image))
file.write(image)
base_obj = {
"created": datetime.now(),
"auto_submit": datetime.now() + timedelta(minutes=5),
Expand Down
7 changes: 6 additions & 1 deletion server/handlers/uav/dummy.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from __future__ import annotations
import json
import logging
import math
import os
import random
import typing

from dronekit import Command
from pymavlink import mavutil as uavutil

from errors import GeneralError, ServiceUnavailableError, InvalidRequestError
from handlers.utils import decorate_all_functions, log

if typing.TYPE_CHECKING:
from groundstation import GroundStation

COMMANDS = {
"WAYPOINT": uavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
"GEOFENCE": uavutil.mavlink.MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION,
Expand Down Expand Up @@ -68,7 +73,7 @@ class DummyUAVHandler:

def __init__(self, gs, config):
self.logger = logging.getLogger("groundstation")
self.gs = gs
self.gs: GroundStation = gs
self.config = config
self.port = self.config["uav"]["telemetry"]["port"]
self.serial = self.config["uav"]["telemetry"]["serial"]
Expand Down
13 changes: 9 additions & 4 deletions server/handlers/uav/prod.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations
import json
import logging
import math
import os
import typing
from typing import Optional

from dronekit import connect, Command, VehicleMode, Vehicle
Expand All @@ -10,6 +12,9 @@
from errors import GeneralError, InvalidRequestError, InvalidStateError
from handlers.utils import decorate_all_functions, log

if typing.TYPE_CHECKING:
from groundstation import GroundStation

BAUDRATE = 57600

COMMANDS = {
Expand Down Expand Up @@ -139,7 +144,7 @@ class UAVHandler:

def __init__(self, gs, config):
self.logger = logging.getLogger("groundstation")
self.gs = gs
self.gs: GroundStation = gs
self.config = config
self.port = self.config["uav"]["telemetry"]["port"]
self.serial = self.config["uav"]["telemetry"]["serial"]
Expand Down Expand Up @@ -271,11 +276,11 @@ def set_home(self):

def calibrate(self):
try:
self.vehicle.send_calibrate_accelerometer()
self.vehicle.send_calibrate_accelerometer(simple=True)
self.vehicle.send_calibrate_barometer()
self.vehicle.send_calibrate_gyro()
self.vehicle.send_calibrate_magnetometer()
self.vehicle.send_calibrate_vehicle_level()
# self.vehicle.send_calibrate_magnetometer()
# self.vehicle.send_calibrate_vehicle_level()
return {}
except Exception as e:
raise GeneralError(str(e)) from e
Expand Down
Loading

0 comments on commit 91f8046

Please sign in to comment.