Protobuf prototype

This commit is contained in:
Konstantin Lampalzer 2022-03-18 18:11:16 +01:00
parent e277a83c5f
commit 9b567b8c6c
119 changed files with 8599 additions and 0 deletions

View file

@ -1,233 +0,0 @@
import json
import os
import time
from typing import Dict, Tuple, List
import requests
from compLib.LogstashLogging import Logging
API_URL = os.getenv("API_URL", "http://localhost:5000/") + "api/"
api_override = os.getenv("API_FORCE", "")
if api_override != "":
print(f"API_URL was set to {API_URL} but was overwritten with {api_override}")
API_URL = api_override
API_URL_GET_DELIVERY = API_URL + "getDelivery"
API_URL_GET_MATERIAL = API_URL + "getMaterial"
API_URL_GET_GARBAGE = API_URL + "getGarbage"
API_URL_GET_LIST_CARGO = API_URL + "listCargo"
API_URL_GET_CARGO = API_URL + "getCargo/"
API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
API_URL_GET_POS = API_URL + "getPos"
API_URL_GET_OP = API_URL + "getOp"
API_URL_GET_GOAL = API_URL + "getGoal"
API_URL_GET_ITEMS = API_URL + "getItems"
API_URL_GET_SCORES = API_URL + "getScores"
API_URL_GET_METEOROID = API_URL + "getMeteoroids"
class Seeding:
"""Class used for communicating with seeding api
"""
@staticmethod
def get_delivery() -> Tuple[Dict, int]:
"""Makes the /api/getDelivery call to the api.
:return: Json Object and status code as returned by the api.
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_DELIVERY)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.get_delivery = {result}, status code = {res.status_code}")
return result, res.status_code
@staticmethod
def get_material() -> Tuple[Dict, int]:
"""Makes the /api/getMaterial call to the api.
:return: Json Object and status code as returned by the api.
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_MATERIAL)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.get_material = {result}, status code = {res.status_code}")
return result, res.status_code
@staticmethod
def get_garbage() -> Tuple[Dict, int]:
"""Makes the /api/getGarbage call to the api.
:return: Json Object and status code as returned by the api.
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_GARBAGE)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.get_garbage {result}, status code = {res.status_code}")
return result, res.status_code
@staticmethod
def list_cargo() -> Tuple[Dict, int]:
"""Makes the /api/listCargo call to the api.
:return: Json Object and status code as returned by the api.
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_LIST_CARGO)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.list_cargo {result}, status code = {res.status_code}")
return result, res.status_code
@staticmethod
def get_cargo(color: str) -> Tuple[Dict, int]:
"""Makes the /api/getCargo call to the api.
:param color: Color parameter which specifies which cargo should be taken. (A string which is either "green", "red", "yellow" or "blue") The function only picks up one package.
:return: Json Object and status code as returned by the api.
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_CARGO + color)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.get_cargo {result}, status code = {res.status_code}")
return result, res.status_code
@staticmethod
def get_robot_state() -> Tuple[Dict, int]:
res = requests.get(API_URL_GET_ROBOT_STATE)
result = json.loads(res.content)
Logging.get_logger().debug(f"Seeding.get_robot_state {result}, status code = {res.status_code}")
return result, res.status_code
class Position:
"""Datastructure for holding a position
"""
def __init__(self, x, y, degrees):
self.x = x
self.y = y
self.degrees = degrees
def __repr__(self):
return f"Position(x={self.x}, y={self.y}, degrees={self.degrees})"
def __str__(self):
return f"Position(x={round(self.x, 5)}, y={round(self.y, 5)}, degrees={round(self.degrees, 5)})"
class DoubleElim:
"""Class used for communicating with double elimination api
"""
@staticmethod
def get_position() -> Tuple[Position, int]:
"""Makes the /api/getPos call to the api.
:return: A Position object with robot position
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_POS)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_position timeout!")
time.sleep(0.01)
return DoubleElim.get_position()
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_position = {response}, status code = {res.status_code}")
return Position(response["x"], response["y"], response["degrees"]), res.status_code
@staticmethod
def get_opponent() -> Tuple[Position, int]:
"""Makes the /api/getOp call to the api.
:return: A Position object with opponents robot position
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_OP)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_opponent timeout!")
time.sleep(0.01)
return DoubleElim.get_opponent()
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_opponent = x:{response}, status code = {res.status_code}")
return Position(response["x"], response["y"], response["degrees"]), res.status_code
@staticmethod
def get_goal() -> Tuple[Position, int]:
"""Makes the /api/getGoal call to the api.
:return: A Position object with x and y coordinates of the goal, rotation is always -1
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_GOAL)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_goal timeout!")
time.sleep(0.01)
return DoubleElim.get_goal()
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_goal = {response}, status code = {res.status_code}")
return Position(response["x"], response["y"], -1), res.status_code
@staticmethod
def get_items() -> Tuple[List[Dict], int]:
"""Makes the /api/getItems call to the api.
:return: A list will all items currently on the game field. Items are dictionaries that look like: {"id": 0, "x": 0, "y": 0}
:rtype: Tuple[List[Dict], int]
"""
res = requests.get(API_URL_GET_ITEMS)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_items timeout!")
time.sleep(0.01)
return DoubleElim.get_items()
elif res.status_code == 503:
return [], 503
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_items = {response}, status code = {res.status_code}")
return response, res.status_code
@staticmethod
def get_scores() -> Tuple[Dict, int]:
"""Makes the /api/getScores call to the api.
:return: A dictionary with all scores included like: {"self":2,"opponent":0}
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_SCORES)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_scores timeout!")
time.sleep(0.01)
return DoubleElim.get_scores()
elif res.status_code == 503:
return {"self": 0, "opponent": 0}, 503
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_scores = {response}, status code = {res.status_code}")
return response, res.status_code
@staticmethod
def get_meteoroids() -> Tuple[List[Dict], int]:
"""Makes the /api/getMeteoroids call to the api.
:return: A list will all meteoroids currently on the game field. Meteoroids are dictionaries that look like: {"x": 0, "y": 0}
:rtype: Tuple[List[Dict], int]
"""
res = requests.get(API_URL_GET_METEOROID)
if res.status_code == 408:
Logging.get_logger().error(f"DoubleElim.get_meteoroids timeout!")
time.sleep(0.01)
return DoubleElim.get_meteoroids()
elif res.status_code == 503:
return [], 503
response = json.loads(res.content)
Logging.get_logger().debug(f"DoubleElim.get_meteoroids = {response}, status code = {res.status_code}")
return response, res.status_code

View file

@ -1,49 +0,0 @@
import string
from compLib.LogstashLogging import logstash_logger
from compLib.Spi import Spi, Register
LINE_COUNT = 4
CHARS_PER_LINE = 16
class Display(object):
"""Access the display on the robot.
The display is split into 4 Rows and 16 Columns. Each function call changes one line at a time.
"""
@staticmethod
def write(line: int, text: str):
"""Write a string of text to the integrated display.
:param line: Line to write. Between 1 and 4
:param text: Text to write. Up to 16 characters
:raises: IndexError
"""
if len(text) > CHARS_PER_LINE:
logstash_logger.error(f"Too many characters specified!")
return
if line <= 0 or line > LINE_COUNT:
raise IndexError("Invalid line number specified")
to_write = [0] * CHARS_PER_LINE
for i in range(len(text)):
to_write[i] = ord(text[i])
if line == 1:
Spi.write_array(Register.DISPLAY_LINE_1_C0, CHARS_PER_LINE, to_write)
elif line == 2:
Spi.write_array(Register.DISPLAY_LINE_2_C0, CHARS_PER_LINE, to_write)
elif line == 3:
Spi.write_array(Register.DISPLAY_LINE_3_C0, CHARS_PER_LINE, to_write)
elif line == 4:
Spi.write_array(Register.DISPLAY_LINE_4_C0, CHARS_PER_LINE, to_write)
@staticmethod
def clear():
"""Clear the display
"""
for i in range(1, LINE_COUNT + 1):
Display.write(i, "")

View file

@ -1,127 +0,0 @@
import atexit
from enum import Enum
from compLib.LogstashLogging import Logging
from compLib.Spi import Spi, Register
MOTOR_COUNT = 4
encoder_start_values = [0] * (MOTOR_COUNT + 1)
class Encoder(object):
"""Class used to read the encoders
"""
@staticmethod
def handle_wrap(raw_value, port):
"""Handle overflow and underflow of int for encoders.
:param raw_value: Raw value which was read on port
:param port: Port, which the motor is connected to. Between 1 and 4
:raises: IndexError
:return: Current encoder position
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid encoder port specified!")
diff = raw_value - encoder_start_values[port]
if diff > 2 ** 31:
diff -= 2 ** 32
elif diff < -2 ** 31:
diff += 2 ** 32
return diff
@staticmethod
def read_raw(port: int) -> int:
"""Read raw encoder from a specified port. Will not be reset to 0 at start.
:param port: Port, which the motor is connected to. Between 1 and 4
:raises: IndexError
:return: Current encoder position
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid encoder port specified!")
raw_value = 0
if port == 1:
raw_value = Spi.read(Register.MOTOR_1_POS_B3, 4)
elif port == 2:
raw_value = Spi.read(Register.MOTOR_2_POS_B3, 4)
elif port == 3:
raw_value = Spi.read(Register.MOTOR_3_POS_B3, 4)
elif port == 4:
raw_value = Spi.read(Register.MOTOR_4_POS_B3, 4)
return raw_value
@staticmethod
def read_all_raw():
"""Read all encoders at once.
This is faster than read_raw as it only uses one SPI call for all encoders!
:return: Tuple of all current raw encoder positions
"""
encoders = Spi.read_array(Register.MOTOR_1_POS_B3, 4 * 4)
encoder_1 = int.from_bytes(
encoders[0:4], byteorder='big', signed=False)
encoder_2 = int.from_bytes(
encoders[4:8], byteorder='big', signed=False)
encoder_3 = int.from_bytes(
encoders[8:12], byteorder='big', signed=False)
encoder_4 = int.from_bytes(
encoders[12:16], byteorder='big', signed=False)
return (encoder_1, encoder_2, encoder_3, encoder_4)
@staticmethod
def read_all():
"""Read all encoders at once.
This is faster than read as it only uses one SPI call for all encoders!
:return: Tuple of all current encoder positions
"""
encoders = Encoder.read_all_raw()
return (Encoder.handle_wrap(encoders[0], 1),
Encoder.handle_wrap(encoders[1], 2),
Encoder.handle_wrap(encoders[2], 3),
Encoder.handle_wrap(encoders[3], 4))
@staticmethod
def read(port: int) -> int:
"""Read encoder from a specified port
:param port: Port, which the motor is connected to. Between 1 and 4
:raises: IndexError
:return: Current encoder position
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid encoder port specified!")
return Encoder.handle_wrap(Encoder.read_raw(port), port)
@staticmethod
def clear(port: int):
"""Reset encoder position to 0
:param port: Port, which the motor is connected to. Between 1 and 4
:raises: IndexError
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid encoder port specified!")
encoder_start_values[port] = Encoder.read_raw(port)
@staticmethod
def clear_all():
"""Reset all encoder positions to 0
"""
for i in range(1, MOTOR_COUNT + 1):
encoder_start_values[i] = Encoder.read_raw(i)

View file

@ -1,116 +0,0 @@
import datetime
import os
import socket
import threading
import time
import systemd.daemon
from compLib.Lock import Lock
try:
from compLib.LogstashLogging import Logging
except Exception as e:
import logging
class Logger():
def __init__(self):
self.logger = logging.Logger('compApi background')
def get_logger(self):
return self.logger
Logging = Logger()
print(f"Could not import compLib.LogstashLogging: {str(e)}")
Logging.get_logger().error(f"Could not import compLib.LogstashLogging: {str(e)}")
print("after basic imports")
RUN_IP_CHECK = False
try:
from compLib.Display import Display
from compLib.Spi import Spi
from compLib import __version__
RUN_IP_CHECK = True
except Exception as e:
print(f"Could not import display or spi for ip output {str(e)}")
Logging.get_logger().warning(f"Could not import display or spi for ip output {str(e)}")
print(f"After display and Spi import")
__run = """raspivid -t 0 -b 5000000 -w 1280 -h 720 -fps 30 -n -o - | gst-launch-1.0 fdsrc ! video/x-h264,width=1280,height=720,framerate=30/1,noise-reduction=1,profile=high,stream-format=byte-stream ! h264parse ! queue ! flvmux streamable=true ! rtmpsink location=\"rtmp://localhost/live/stream\""""
STREAM_RASPI = False if os.getenv("STREAM_RASPI", "false") == "false" else True
IP_OUTPUT = False if os.getenv("IP_OUTPUT", "true") != "true" else True
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception:
IP = 'Not connected'
print(f"Error could not query ip: {e}")
finally:
s.close()
return IP
def write_ip_to_screen():
while os.getenv("IP_OUTPUT", "true") == "true":
try:
if not Lock.is_locked():
Lock.lock()
ip = str(get_ip())
print(f"writing {ip} to display")
Display.write(2, f"LIB: V{__version__}")
Display.write(3, f"FW: V{Spi.get_version()}")
Display.write(4, f"IP: {ip}")
Display.write(1, datetime.datetime.now().strftime("%b %d %H:%M:%S"))
Lock.unlock()
time.sleep(10)
except Exception as e:
print(f"Exception in write ip thread: {e}")
time.sleep(5)
if __name__ == '__main__':
try:
systemd.daemon.notify(systemd.daemon.Notification.READY)
except:
Logging.get_logger().warning("Warning, old systemd version detected")
systemd.daemon.notify('READY=1')
ip_output = None
if RUN_IP_CHECK and IP_OUTPUT:
print("starting ip output")
Logging.get_logger().info("starting ip output")
Lock.unlock()
try:
Spi.disable_health_check()
ip_output = threading.Thread(target=write_ip_to_screen)
ip_output.start()
print("starting ip output - DONE")
Logging.get_logger().info("starting ip output - DONE")
except Exception as e:
print(f"could not start ip output -> {str(e)}")
Logging.get_logger().error(f"could not start ip output -> {str(e)}")
if STREAM_RASPI:
print("starting gstreamer background process")
Logging.get_logger().info("starting gstreamer background process")
os.system(__run)
print("gstreamer stopped...")
Logging.get_logger().error("gstreamer stopped...")
else:
print("not starting gstreamer background process")
Logging.get_logger().info("not starting gstreamer background process")
if ip_output is not None:
ip_output.join()
else:
print("ip display output failed to initialize.. sleeping for a day, good night")
Logging.get_logger().info("ip display output failed to initialize.. sleeping for a day, good night")
time.sleep(60 * 60 * 24)

View file

@ -1,81 +0,0 @@
from compLib.LogstashLogging import Logging
from compLib.MetricsLogging import MetricsLogging
from compLib.Spi import Spi, Register
SENSOR_COUNT = 5
class IRSensor(object):
"""Access the different IR Sensors of the robot
"""
@staticmethod
def read(sensor: int) -> int:
"""Read one infrared sensor
:param sensor: Which sensor to read. Between 1 and 5
:raises: IndexError
:return: Sensor value. 10 bit accuracy
:rtype: int
"""
if sensor <= 0 or sensor > SENSOR_COUNT:
raise IndexError("Invalid sensor specified!")
result = 0
if sensor == 1:
result = Spi.read(Register.IR_1_H, 2)
elif sensor == 2:
result = Spi.read(Register.IR_2_H, 2)
elif sensor == 3:
result = Spi.read(Register.IR_3_H, 2)
elif sensor == 4:
result = Spi.read(Register.IR_4_H, 2)
elif sensor == 5:
result = Spi.read(Register.IR_5_H, 2)
MetricsLogging.put("Infrared", result, sensor)
return result
@staticmethod
def read_all():
"""Read all IR sensors at once.
This is faster than read as it only uses one SPI call for all sensors!
:return: Tuple of all current ir sensors
"""
sensors = Spi.read_array(Register.IR_1_H, 5 * 2)
sensor_1 = int.from_bytes(
sensors[0:2], byteorder='big', signed=False)
sensor_2 = int.from_bytes(
sensors[2:4], byteorder='big', signed=False)
sensor_3 = int.from_bytes(
sensors[4:6], byteorder='big', signed=False)
sensor_4 = int.from_bytes(
sensors[6:8], byteorder='big', signed=False)
sensor_5 = int.from_bytes(
sensors[8:10], byteorder='big', signed=False)
return (sensor_1, sensor_2, sensor_3, sensor_4, sensor_5)
@staticmethod
def set(sensor: int, on: bool):
"""Turn on / off a IR emitter
:param sensor: Which sensor to read. Between 1 and 5
:raises: IndexError
"""
if sensor <= 0 or sensor > SENSOR_COUNT:
raise IndexError("Invalid sensor specified!")
if sensor == 1:
Spi.write(Register.IR_1_LED, 1, on)
elif sensor == 2:
Spi.write(Register.IR_2_LED, 1, on)
elif sensor == 3:
Spi.write(Register.IR_3_LED, 1, on)
elif sensor == 4:
Spi.write(Register.IR_4_LED, 1, on)
elif sensor == 5:
Spi.write(Register.IR_5_LED, 1, on)

View file

@ -1,25 +0,0 @@
from filelock import FileLock, Timeout
import atexit
FILELOCK_PATH = "/root/complib.lock"
global_lock = FileLock(FILELOCK_PATH, timeout=1)
class Lock(object):
@staticmethod
def lock():
global_lock.acquire()
@staticmethod
def unlock():
global_lock.release()
@staticmethod
def is_locked():
try:
global_lock.acquire()
global_lock.release()
return False
except Timeout:
return True
atexit.register(Lock.unlock)

View file

@ -1,102 +0,0 @@
import logging
import os
import sys
import requests
from logstash_async.transport import HttpTransport
from logstash_async.handler import AsynchronousLogstashHandler
EXTENSIVE_LOGGING = os.getenv("EXTENSIVE_LOGGING", "False")
if EXTENSIVE_LOGGING == "True":
EXTENSIVE_LOGGING = True
else:
EXTENSIVE_LOGGING = False
host = 'logstash.robo4you.at'
port = 443
logstash_logger = logging.getLogger('logstash')
logstash_logger.setLevel(logging.INFO)
transport = HttpTransport(
host,
port,
username="robo",
password="competition",
timeout=60.0,
)
asynchronousLogstashHandler = AsynchronousLogstashHandler(
host,
port,
transport=transport,
database_path='logs.db'
)
class StreamToLogger(object):
"""
Fake file-like stream object that redirects writes to a logger instance.
"""
def __init__(self, textio, log_level=logging.INFO):
self.logger = logging.getLogger('logstash')
self.console = textio
self.log_level = log_level
self.linebuf = ''
def write(self, buf):
self.console.write(buf)
temp_linebuf = self.linebuf + buf
for line in buf.splitlines(True):
self.linebuf += line
def flush(self):
if self.linebuf != '':
self.logger.log(self.log_level, self.linebuf.rstrip())
self.linebuf = ''
self.console.flush()
asynchronousLogstashHandler.flush()
if EXTENSIVE_LOGGING:
try:
r = requests.get(f"https://{host}:{port}")
if r.status_code == 401:
logstash_logger.addHandler(asynchronousLogstashHandler)
so = StreamToLogger(sys.stdout, logging.INFO)
sys.stdout = so
se = StreamToLogger(sys.stderr, logging.ERROR)
sys.stderr = se
else:
print(f"Could not connect to {host} -> ERROR CODE: {r.status_code}!")
except requests.exceptions.ConnectionError as identifier:
print(f"Could not connect to {host}!")
print(f"Error loading logger was -> {identifier}")
else:
print("Extensive logging is disabled! No logs will be sent over network...")
class Logging(object):
"""Can be used to manually use the logger or turn up the logging level used for debugging this library.
"""
@staticmethod
def get_logger() -> logging.Logger:
"""Get the logger object used to communicate with logstash
:return: Python logger
:rtype: logging.Logger
"""
return logstash_logger
@staticmethod
def set_debug():
"""Turns up the logging level of the library to debug. Should be used, when debugging with the
Competition organizers
"""
logstash_logger.setLevel(logging.DEBUG)

View file

@ -1,97 +0,0 @@
from influxdb_client import InfluxDBClient, Point, WritePrecision
import socket
import uuid
import os
import multiprocessing
import datetime
import requests
import time
CONCURRENCY = 1
HOSTNAME = socket.gethostname()
RUN_TRACE = str(uuid.uuid4())
TOKEN = "aCdxnntw-Zp3agci8Z32lQAR_ep6MhdmWOJG7ObnoikYqe7nKAhFYx1jVGBpipQuId79SC4Jl0J6IBYVqauJyw=="
ORG = "robo4you"
BUCKET = "compAIR"
INFLUX_HOST = "https://influxdb.comp-air.at"
EXTENSIVE_LOGGING = os.getenv("EXTENSIVE_LOGGING", "True")
if EXTENSIVE_LOGGING == "True":
EXTENSIVE_LOGGING = True
else:
EXTENSIVE_LOGGING = False
influx_client = InfluxDBClient(url=INFLUX_HOST, token=TOKEN)
write_api = influx_client.write_api()
point_queue = multiprocessing.Queue()
workers = []
class MetricsLogging():
"""Used to send metrics / points to a influxdb
"""
@staticmethod
def is_influx_reachable() -> bool:
"""Check if we can send metrics to the database
:return: Is reachable?
:rtype: bool
"""
try:
r = requests.get(INFLUX_HOST)
if r.status_code == 200:
return True
else:
print(f"Could not connect to {INFLUX_HOST} -> ERROR CODE: {r.status_code}!")
return False
except requests.exceptions.RequestException as identifier:
print(f"Could not connect to {INFLUX_HOST}!")
@staticmethod
def put(sensorType, value, port):
"""Put a datapoint into a time-series-database
:param sensorType: A key used to identify a datapoint
:param value: Value measured by the sensor
:param port: Port of the sensor which was read
"""
if EXTENSIVE_LOGGING:
point = Point(sensorType) \
.tag("host", HOSTNAME) \
.tag("runID", RUN_TRACE) \
.tag("port", port) \
.field("value", value) \
.time(datetime.datetime.utcnow(), WritePrecision.MS)
point_queue.put_nowait(point)
@staticmethod
def worker():
point = object()
for job in iter(point_queue.get, point):
try:
write_api.write(BUCKET, ORG, point)
time.sleep(0.1)
except Exception as e:
pass
finally:
pass
@staticmethod
def start_workers():
global EXTENSIVE_LOGGING
if EXTENSIVE_LOGGING:
if MetricsLogging.is_influx_reachable():
for i in range(CONCURRENCY):
worker = multiprocessing.Process(target=MetricsLogging.worker, daemon=True)
worker.start()
workers.append(worker)
else:
EXTENSIVE_LOGGING = False
MetricsLogging.start_workers()

View file

@ -1,226 +0,0 @@
import atexit
from enum import IntEnum
from compLib.LogstashLogging import Logging
from compLib.MetricsLogging import MetricsLogging
from compLib.Spi import Spi, Register
MOTOR_COUNT = 4
MAX_MOTOR_SPEED = 65535
MOTOR_PERCENTAGE_MULT = MAX_MOTOR_SPEED / 100.0
NEW_MOTOR_CURVE = [0.0, 0.0, 0.5, 60.0, 199.83333333333334, 377.66666666666663, 990.3333333333333, 1860.6666666666665, 2587.0, 3091.6666666666665, 3489.0, 3860.5, 4197.333333333333, 4432.166666666667, 4647.166666666666, 4873.166666666666, 5054.333333333334, 5208.666666666667, 5353.0, 5466.5, 5604.0]
MOTOR_CURVE = [0.0, 0.0, 426.5, 692.0, 842.5, 953.5, 1032.5, 1090.5, 1135.5, 1171.0, 1203.5, 1230.0, 1249.5, 1268.0, 1283.0, 1298.5, 1308.0, 1320.0, 1332.0, 1339.5, 1352.5]
SPEED_LOCK = False
SPEED_MULT = 1.0
class MotorMode(IntEnum):
COAST = 0,
FORWARD = 1,
BACKWARD = 2,
BREAK = 3
class Motor(object):
"""Class used to control the motors
"""
@staticmethod
def pwm(port: int, pwm: int, mode: MotorMode):
"""Set specified motor to a specific pwm value and motor mode
:param port: Port, which the motor is connected to. 1-4 allowed
:param pwm: Raw PWM value which the motor should be set to. From 0 to 2^16 - 1
:param mode: Motor mode. See enum MotorMode for more info
:raises: IndexError
"""
if SPEED_LOCK:
return
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if port == 1:
Spi.write(Register.MOTOR_1_PWM_H, 2, pwm)
Spi.write(Register.PWM_1_CTRL, 1, int(mode))
elif port == 2:
Spi.write(Register.MOTOR_2_PWM_H, 2, pwm)
Spi.write(Register.PWM_2_CTRL, 1, int(mode))
elif port == 3:
Spi.write(Register.MOTOR_3_PWM_H, 2, pwm)
Spi.write(Register.PWM_3_CTRL, 1, int(mode))
elif port == 4:
Spi.write(Register.MOTOR_4_PWM_H, 2, pwm)
Spi.write(Register.PWM_4_CTRL, 1, int(mode))
@staticmethod
def power_raw(port: int, percent: float, log_metric = True):
"""Set specified motor to percentage power
:param port: Port, which the motor is connected to. 1-4
:param percent: Percentage of max speed. between -100 and 100
:raises: IndexError
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if percent < -100 or percent > 100:
raise IndexError("Invalid Motor speed specified! Speed is between -100 and 100 percent!")
if log_metric:
MetricsLogging.put("MotorRaw", float(percent), port)
mode = MotorMode.COAST
if percent < 0:
percent = abs(percent)
mode = MotorMode.BACKWARD
elif percent > 0:
mode = MotorMode.FORWARD
percent *= SPEED_MULT
pwm = percent * MOTOR_PERCENTAGE_MULT
Motor.pwm(port, int(pwm), mode)
@staticmethod
def power(port: int, percent: float):
"""Set specified motor to percentage power, percentage is linearized
so that it's roughly proportional to the speed
:param port: Port, which the motor is connected to. 1-4
:param percent: Percentage of max speed. between -100 and 100
:raises: IndexError
"""
raw_power = raw_power = Motor.__linearizePower(MOTOR_CURVE, abs(percent))
if percent < 0:
raw_power *= -1
MetricsLogging.put("Motor", float(percent), port)
Motor.power_raw(port, raw_power, False)
@staticmethod
def all_off():
"""
Turns of all motors
"""
Logging.get_logger().debug(f"Motor.all_off")
for i in range(1, MOTOR_COUNT + 1):
Motor.active_break(i)
@staticmethod
def active_break(port: int):
"""
Actively break with a specific motor
:param port: Port, which the motor is connected to. 1-4
"""
Motor.pwm(port, 0, MotorMode.BREAK)
@staticmethod
def set_motor_curve(curve):
"""
Set the global motor curve, must be a float array with exactly 21 elements.
[0] = x ticks/s (0% power)
[1] = x ticks/s (5% power)
[2] = x ticks/s (10% power)
...
[20] = x ticks/s (100% power)
:param curve: float array with 21 elements
:raises: ValueError
"""
if (len(curve) != 21):
raise ValueError('The motor curve is invalid, check documentation for set_motor_curve()!')
global MOTOR_CURVE
MOTOR_CURVE = curve
@staticmethod
def get_motor_curve():
"""
Get the currently active motor curve. Check set_motor_curve() for more info.
:return: current motor curve
"""
return MOTOR_CURVE
@staticmethod
def __map(x, in_min, in_max, out_min, out_max):
"""
Linear interpolation. Check https://www.arduino.cc/reference/en/language/functions/math/map/
"""
x = float(x)
in_min = float(in_min)
in_max = float(in_max)
out_min = float(out_min)
out_max = float(out_max)
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
@staticmethod
def __interpolateSpeed(curve, speed):
"""
Interpolate a speed in the specified motor curve and return
the 'power percentage that is needed to reach that speed'
"""
if speed < min(curve) or speed > max(curve):
raise ValueError(f'Speed out of range: {str(speed)} ticks/s')
for index in range(len(curve) - 1):
if speed >= curve[index] and speed <= curve[index + 1] and curve[index] != curve[index + 1]:
return Motor.__map(speed, curve[index], curve[index + 1], index * 5, index * 5 + 5)
return 0
@staticmethod
def __linearizePower(curve, power):
"""
Takes raw power and returns it corrected so that the motor speed would be roughly linear
"""
if power < 0 or power > 100:
raise ValueError(f'Power out of range: {str(power)}%')
requiredSpeed = Motor.__map(power, 0, 100, min(curve), max(curve))
return Motor.__interpolateSpeed(curve, requiredSpeed)
@staticmethod
def robot_state_loop():
import time
from compLib.Api import Seeding
global SPEED_LOCK
global SPEED_MULT
while True:
try:
state, status = Seeding.get_robot_state()
SPEED_MULT = state["speed"] / 100.0
if state["left"] != -1 or state["right"] != -1:
Motor.power(1, state["right"])
Motor.power(4, state["left"])
SPEED_LOCK = True
time.sleep(state["time"])
SPEED_LOCK = False
Motor.power(1, 0)
Motor.power(4, 0)
else:
time.sleep(0.25)
except:
time.sleep(0.25)
pass
@staticmethod
def start_robot_state_loop():
from threading import Thread
robot_state_thread = Thread(target=Motor.robot_state_loop)
robot_state_thread.setDaemon(True)
robot_state_thread.start()
atexit.register(Motor.all_off)

View file

@ -1,101 +0,0 @@
import time
import math
from compLib.Encoder import Encoder
from compLib.Robot import Robot
last_run = time.time()
last_enc_left = 0
last_enc_right = 0
pos_x = 0
pos_y = 0
orientation = 0
class Odometry():
"""DTO used for holding all odometry information. \n
Coordinate system: \n
X: + Forward; - Backwards \n
Y: + Right; - Left \n
Orientation: + Right; - Left
"""
def __init__(self, x, y, orientation):
self.x = x
self.y = y
self.orientation = orientation
def get_x(self) -> float:
"""Returns distance driven on x-axis in meters"""
return self.x
def get_y(self) -> float:
"""Returns distance driven on y-axis in meters"""
return self.y
def get_orientation(self) -> float:
"""Returns degrees turned in radians"""
return self.orientation
def __str__(self):
return f"X: {self.x} Y: {self.y} O: {self.orientation}"
class Odom(object):
"""Class used to track the movement of the robot in X, Y, Theta (Orientation)
"""
@staticmethod
def get_odom() -> Odometry:
"""
:return: Current orientation of the robot
"""
return Odometry(pos_x, pos_y, orientation)
@staticmethod
def clear() -> None:
"""
Clears the current odometry information and start from X, Y, Orientation set to 0
"""
global last_run, last_enc_left, last_enc_right, pos_x, pos_y, orientation
last_run = 0
last_enc_left = 0
last_enc_right = 0
pos_x = 0
pos_y = 0
orientation = 0
@staticmethod
def update() -> None:
"""
Updates the current odometry information of the robot, Should be called in a loop with at least 100HZ.
Do not clear encoder positions between updates! Must be cleared, when clearing encoders!
"""
global last_run, last_enc_left, last_enc_right, pos_x, pos_y, orientation
now = time.time()
if last_run + 0.01 < now:
last_run = now
encoder_positions = Encoder.read_all()
current_left = encoder_positions[Robot.LEFT_PORT - 1]
current_right = -encoder_positions[Robot.RIGHT_PORT - 1]
distance_left = (current_left - last_enc_left) / Robot.TICKS_PER_METER
distance_right = (current_right - last_enc_right) / Robot.TICKS_PER_METER
last_enc_left = current_left
last_enc_right = current_right
distance = (distance_left + distance_right) / 2.0
theta = (distance_left - distance_right) / Robot.ARBOR_LENGTH_M
if distance != 0:
distance_x = math.cos(theta) * distance
distance_y = -math.sin(theta) * distance
pos_x = pos_x + (math.cos(orientation) * distance_x -
math.sin(orientation) * distance_y)
pos_y = pos_y + (math.sin(orientation) * distance_x +
math.cos(orientation) * distance_y)
if theta != 0:
orientation += theta

View file

@ -1,24 +0,0 @@
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
RESET_PIN = 23
BOOT_PIN = 17
class Reset:
"""Reset the co-processor
"""
@staticmethod
def reset_bot():
"""Reset the co-processor
"""
GPIO.setmode(GPIO.BCM)
GPIO.setup(RESET_PIN, GPIO.OUT)
GPIO.setup(BOOT_PIN, GPIO.OUT)
GPIO.output(RESET_PIN, GPIO.LOW)
GPIO.output(BOOT_PIN, GPIO.LOW)
time.sleep(0.1)
GPIO.output(RESET_PIN, GPIO.HIGH)
time.sleep(1.5)

View file

@ -1,24 +0,0 @@
import math
class Robot(object):
WHEEL_CIRCUMFERENCE_MM = 71.0 * math.pi
"""Circumference of a wheel in millimeters"""
TICKS_PER_TURN = 27.7 * 100.0
"""Ticks per 360 degree turn of a wheel"""
ARBOR_LENGTH_MM = 139.0
"""Distance between the two wheels in millimeters"""
ARBOR_LENGTH_M = ARBOR_LENGTH_MM / 1000.0
"""Distance between the two wheels in meters"""
TICKS_PER_METER = 1000.0 / WHEEL_CIRCUMFERENCE_MM * TICKS_PER_TURN
"""Ticks after driving one meter"""
LEFT_PORT = 4
"""Motor port for the left motor"""
RIGHT_PORT = 1
"""Motor port for the right motor"""

View file

@ -1,63 +0,0 @@
from compLib.Spi import Spi, Register
SERVO_COUNT = 8
MIN_ANGLE = -90.0
MAX_ANGLE = 90.0
def map(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
class Servo:
"""Control the servo ports on the robot
"""
@staticmethod
def set_position(port: int, angle: float, offset: float = 90.0):
"""Set position of servo connected to port
:param port: port between 1 and 8
:param angle: Angle of servo
"""
if port <= 0 or port > SERVO_COUNT:
raise IndexError("Invalid Servo port specified!")
angle = max(min(angle, MAX_ANGLE), MIN_ANGLE)
mapped_angle = int(map(angle, MIN_ANGLE, MAX_ANGLE, 0, 65535))
if port == 1:
Spi.write(Register.SERVO_1_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_1_CTRL, 1, 4)
elif port == 2:
Spi.write(Register.SERVO_2_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_1_CTRL, 1, 4)
elif port == 3:
Spi.write(Register.SERVO_3_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_2_CTRL, 1, 4)
elif port == 4:
Spi.write(Register.SERVO_4_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_2_CTRL, 1, 4)
elif port == 5:
Spi.write(Register.SERVO_5_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_3_CTRL, 1, 4)
elif port == 6:
Spi.write(Register.SERVO_6_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_3_CTRL, 1, 4)
elif port == 7:
Spi.write(Register.SERVO_7_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_4_CTRL, 1, 4)
elif port == 8:
Spi.write(Register.SERVO_8_PWM_H, 2, mapped_angle)
Spi.write(Register.PWM_4_CTRL, 1, 4)
@staticmethod
def setup_position():
"""Set position of servos to the position used during the setup process
"""
for i in range(1, SERVO_COUNT + 1):
Servo.set_position(i, 0)

View file

@ -1,222 +0,0 @@
import importlib
from threading import Thread
import multiprocessing
from enum import IntEnum
import time
import sys
import random
from compLib.LogstashLogging import Logging
SPI_BUS = 1
SPI_DEVICE = 2
SPI_SPEED = 4000000
SPI_BUFFER_SIZE = 32
SPI_HEALTH = True
# For development purposes
spi_found = importlib.util.find_spec("spidev") is not None
spi = None
spi_lock = multiprocessing.Lock()
last_spi_call = time.time()
if spi_found:
import spidev
spi = spidev.SpiDev()
spi.open(SPI_BUS, SPI_DEVICE)
spi.max_speed_hz = SPI_SPEED
spi.mode = 0
spi.bits_per_word = 8
class Register(IntEnum):
IDENTIFICATION_MODEL_ID = 1,
IDENTIFICATION_MODEL_REV_MAJOR = 2,
IDENTIFICATION_MODEL_REV_MINOR = 3,
IDENTIFICATION_MODEL_REV_PATCH = 4,
# Motor encoder positions
MOTOR_1_POS_B3 = 10,
MOTOR_1_POS_B2 = 11,
MOTOR_1_POS_B1 = 12,
MOTOR_1_POS_B0 = 13,
MOTOR_2_POS_B3 = 14,
MOTOR_2_POS_B2 = 15,
MOTOR_2_POS_B1 = 16,
MOTOR_2_POS_B0 = 17,
MOTOR_3_POS_B3 = 18,
MOTOR_3_POS_B2 = 19,
MOTOR_3_POS_B1 = 20,
MOTOR_3_POS_B0 = 21,
MOTOR_4_POS_B3 = 22,
MOTOR_4_POS_B2 = 23,
MOTOR_4_POS_B1 = 24,
MOTOR_4_POS_B0 = 25,
# PWM Control Modes
PWM_1_CTRL = 26,
PWM_2_CTRL = 27,
PWM_3_CTRL = 28,
PWM_4_CTRL = 29,
# Motor pwm speed
MOTOR_1_PWM_H = 30,
MOTOR_1_PWM_L = 31,
MOTOR_2_PWM_H = 32,
MOTOR_2_PWM_L = 33,
MOTOR_3_PWM_H = 34,
MOTOR_3_PWM_L = 35,
MOTOR_4_PWM_H = 36,
MOTOR_4_PWM_L = 37,
# Servo goal position
SERVO_1_PWM_H = 38,
SERVO_1_PWM_L = 39,
SERVO_2_PWM_H = 40,
SERVO_2_PWM_L = 41,
SERVO_3_PWM_H = 42,
SERVO_3_PWM_L = 43,
SERVO_4_PWM_H = 44,
SERVO_4_PWM_L = 45,
SERVO_5_PWM_H = 46,
SERVO_5_PWM_L = 47,
SERVO_6_PWM_H = 48,
SERVO_6_PWM_L = 49,
SERVO_7_PWM_H = 50,
SERVO_7_PWM_L = 51,
SERVO_8_PWM_H = 52,
SERVO_8_PWM_L = 53,
# IR Sensor value
IR_1_H = 54,
IR_1_L = 55,
IR_2_H = 56,
IR_2_L = 57,
IR_3_H = 58,
IR_3_L = 59,
IR_4_H = 60,
IR_4_L = 61,
IR_5_H = 62,
IR_5_L = 63,
IR_1_LED = 64,
IR_2_LED = 65,
IR_3_LED = 66,
IR_4_LED = 67,
IR_5_LED = 68,
# Display registers
DISPLAY_LINE_1_C0 = 69,
DISPLAY_LINE_2_C0 = 85,
DISPLAY_LINE_3_C0 = 101,
DISPLAY_LINE_4_C0 = 117
class Spi(object):
@staticmethod
def transfer(tx_buffer: list):
if not spi_found:
return [] * SPI_BUFFER_SIZE
write_reg = tx_buffer[1]
tx_buffer_copy = tx_buffer.copy()
with spi_lock:
spi.xfer(tx_buffer)
rx_buffer = spi.xfer([0] * SPI_BUFFER_SIZE)
if rx_buffer[1] != write_reg:
Logging.get_logger().error(f"Warning! SPI error during read/write of register {write_reg}! Retrying automagically")
time.sleep(random.uniform(0.0, 0.1))
return Spi.transfer(tx_buffer_copy)
global last_spi_call
last_spi_call = time.time()
return rx_buffer
@staticmethod
def read(reg: int, length: int):
return int.from_bytes(Spi.read_array(reg, length), byteorder='big', signed=False)
@staticmethod
def read_array(reg: int, length: int):
if not type(reg) is int:
reg = int(reg)
tx_buf = [0] * SPI_BUFFER_SIZE
tx_buf[0] = 0
tx_buf[1] = reg
tx_buf[2] = length
rx_buf = Spi.transfer(tx_buf)
return rx_buf[2:2 + length]
@staticmethod
def write(reg: int, length: int, value: int):
if not type(reg) is int:
reg = int(reg)
tx_buf = [0] * SPI_BUFFER_SIZE
tx_buf[0] = 1
tx_buf[1] = reg
tx_buf[2] = length
pos = 3
for i in value.to_bytes(length, 'big'):
tx_buf[pos] = i
pos += 1
rx_buf = Spi.transfer(tx_buf)
return int.from_bytes(rx_buf[2:2 + length], byteorder='big', signed=False)
@staticmethod
def write_array(reg: int, length: int, values):
if not type(reg) is int:
reg = int(reg)
tx_buf = [0] * SPI_BUFFER_SIZE
tx_buf[0] = 1
tx_buf[1] = reg
tx_buf[2] = length
pos = 3
for i in values:
tx_buf[pos] = i
pos += 1
rx_buf = Spi.transfer(tx_buf)
return rx_buf
@staticmethod
def health_check():
if Spi.read(Register.IDENTIFICATION_MODEL_ID, 1) != 3:
Logging.get_logger().error(f"Unable to read Version! Make sure the mainboard is connected!")
print("Unable to read Version! Make sure the mainboard is connected!")
@staticmethod
def start_health_check_loop():
health_check_thread = Thread(target=Spi.health_check_loop)
health_check_thread.setDaemon(True)
health_check_thread.start()
@staticmethod
def disable_health_check():
global SPI_HEALTH
SPI_HEALTH = False
@staticmethod
def health_check_loop():
while SPI_HEALTH:
if last_spi_call + 0.5 < time.time():
Spi.health_check()
time.sleep(0.1)
@staticmethod
def get_version():
major = Spi.read(Register.IDENTIFICATION_MODEL_REV_MAJOR, 1)
minor = Spi.read(Register.IDENTIFICATION_MODEL_REV_MINOR, 1)
patch = Spi.read(Register.IDENTIFICATION_MODEL_REV_PATCH, 1)
return f"{major}.{minor}.{patch}"

View file

@ -1,207 +0,0 @@
import os
import queue
import socket
import threading
import cv2
from flask import Flask, Response
from compLib.LogstashLogging import Logging
RTMP_SERVER = os.getenv("RTMP_SERVER", "rtmp://localhost/live/stream")
SERVE_VIDEO = os.getenv("SERVER_SRC", "/live")
BUILDING_DOCS = os.getenv("BUILDING_DOCS", "false")
app = Flask(__name__)
HTML = """
<html>
<head>
<title>Opencv Output</title>
</head>
<body>
<h1>Opencv Output</h1>
<img src="{{ VIDEO_DST }}">
</body>
</html>
"""
# it would be better to use jinja2 here, but I don't want to blow up the package dependencies...
HTML = HTML.replace("{{ VIDEO_DST }}", SERVE_VIDEO)
class __Streaming:
"""
Class that handles rtmp streaming for opencv.
DO NOT CREATE AN INSTANCE OF THIS CLASS YOURSELF!
This is automatically done when importing this module. Use Vision.Streaming which is
an instance of this class!
grab frames -> do your own processing -> publish frame -> view on http server
"""
class __NoBufferVideoCapture:
def __init__(self, cam):
self.cap = cv2.VideoCapture(cam)
self.cap.set(3, 640)
self.cap.set(4, 480)
self.q = queue.Queue(maxsize=5)
t = threading.Thread(target=self._reader)
t.daemon = True
t.start()
def _reader(self):
while True:
ret, frame = self.cap.read()
if not ret:
break
if not self.q.empty():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
def read(self):
return self.q.get()
def __init__(self):
"""
Create instance of __Streaming class
This is done implicitly when importing the vision module and will only fail if you would
create an object of this class. (There can (SHOULD!) only be one VideCapture)
"""
Logging.get_logger().info("capturing rtmp stream is disabled in this version")
#self.__camera_stream = cv2.VideoCapture(RTMP_SERVER)
#self.__camera_stream = cv2.VideoCapture(0)
#self.__camera_stream.set(3, 640)
#self.__camera_stream.set(4, 480)
self.__camera_stream = self.__NoBufferVideoCapture(0)
self.__newest_frame = None
self.__lock = threading.Lock()
Logging.get_logger().info("Initialized vision")
def get_frame(self):
"""
Grab the newest frame from the rtmp stream.
:return: An opencv frame
"""
#ret, img16 = self.__camera_stream.read()
img16 = self.__camera_stream.read()
return img16
def publish_frame(self, image):
"""
Publish an opencv frame to the http webserver.
:param image: Opencv frame that will be published
:return: None
"""
with self.__lock:
if image is not None:
self.__newest_frame = image.copy()
def _newest_frame_generator(self):
"""
Private generator which is called directly from flask server.
:return: Yields image/jpeg encoded frames published from publish_frame function.
"""
while True:
# use a buffer frame to copy the newest frame with lock and then freeing it immediately
buffer_frame = None
with self.__lock:
if self.__newest_frame is None:
continue
buffer_frame = self.__newest_frame.copy()
# encode frame for jpeg stream
(flag, encoded_image) = cv2.imencode(".jpg", buffer_frame)
# if there was an error try again with the next frame
if not flag:
continue
# else yield encoded frame with mimetype image/jpeg
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
bytearray(encoded_image) + b'\r\n')
Streaming = None
if BUILDING_DOCS == "false":
# instantiate private class __Streaming
Streaming = __Streaming()
Logging.get_logger().info("created instance of streaming class")
@app.route("/live")
def __video_feed():
"""
Define route for serving jpeg stream.
:return: Return the response generated along with the specific media.
"""
return Response(Streaming._newest_frame_generator(),
mimetype="multipart/x-mixed-replace; boundary=frame")
@app.route("/")
def __index():
"""
Define route for serving a static http site to view the stream.
:return: Static html page
"""
return HTML
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception as e:
IP = '127.0.0.1'
print(f"Error could not query ip: {e}")
finally:
s.close()
return IP
def __start_flask():
"""
Function for running flask server in a thread.
:return:
"""
Logging.get_logger().info("starting flask server")
app.run(host="0.0.0.0", port=9898, debug=True, threaded=True, use_reloader=False)
ip = get_ip()
Logging.get_logger().info(f"Vision stream started and can be viewed on: {ip}:9898")
print(f"\033[92mVision stream started and can be viewed on: {ip}:9898\033[0m")
if BUILDING_DOCS == "false":
# start flask service in the background
__webserver_thread = threading.Thread(target=__start_flask)
__webserver_thread.start()
# for debugging and testing start processing frames and detecting a 6 by 9 calibration chessboard
if __name__ == '__main__' and BUILDING_DOCS == "false":
while True:
frame = Streaming.get_frame()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# processing
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# find the chessboard corners
ret, corners = cv2.findChessboardCorners(gray, (6, 9), None)
cv2.drawChessboardCorners(frame, (6, 9), corners, ret)
Streaming.publish_frame(frame)

View file

@ -1,50 +0,0 @@
__version__ = "0.4.1-1"
import sys
import importlib
import compLib.LogstashLogging
import compLib.MetricsLogging
from compLib.Lock import Lock
apt_found = importlib.util.find_spec("apt") is not None
spi_found = importlib.util.find_spec("spidev") is not None
if apt_found:
import apt
try:
__versions = apt.Cache()["python3-complib"].versions
if len(__versions) != 1:
print(f"Starting compLib! \033[91mVersion: {__version__} is outdated\033[0m\n"
f"\033[92m[!] run the command 'sudo apt update && sudo apt install python3-complib' to install the newest version\033[0m")
else:
print(f"Starting compLib! \033[92mVersion: {__version__} is up to date\033[0m")
except Exception as e:
compLib.LogstashLogging.Logging.get_logger().error(f"error during checking apt package version -> {str(e)}")
print(f"\033[91merror during checking apt package version -> {str(e)}\033[0m\n")
else:
print("apt is not installed! This is for local development only!")
if Lock.is_locked():
print("Another program using compLib is still running! Delete /root/complib.lock if this is an error. Exiting...")
sys.exit()
else:
Lock.lock()
if spi_found:
from compLib.Spi import Spi
from compLib.Reset import Reset
from compLib.Encoder import Encoder
from compLib.Motor import Motor
import logging
print(f"\033[34mInitializing chipmunk board...\033[0m")
Reset.reset_bot()
Spi.health_check()
Spi.start_health_check_loop()
Motor.start_robot_state_loop()
Encoder.clear_all()
print(f"\033[34mReady\033[0m\n")
else:
print("spidev is not installed! This is for local development only!")