Merge alpha branch

This commit is contained in:
Arthur Golubtsov
2019-07-05 18:40:37 +03:00
13 changed files with 684 additions and 277 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

12
.gitignore vendored
View File

@@ -109,5 +109,15 @@ Drone/test_animation/
Drone/animation.csv
Drone/client_logs
images/
.vscode/
\.idea/
Drone/_copter_client_old_\.py
Drone/test_cl\.py
Server/testj\.ipynb
Server/tst_client\.py
Server/tst\.py

View File

@@ -22,6 +22,8 @@ get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
arming = rospy.ServiceProxy('/mavros/cmd/arming', CommandBool)
landing = rospy.ServiceProxy('/land', Trigger)
services_list = [navigate, set_position, set_rates, set_mode, get_telemetry, arming,landing]
logger.info("Proxy services inited")
# globals
@@ -41,9 +43,11 @@ FLIP_MIN_Z = 2.0
checklist = []
def arming_wrapper(state=False, interrupter=INTERRUPTER):
def arming_wrapper(state=False, *args, **kwargs):
arming(state)
def interrupt():
logger.info("Performing function interrupt")
INTERRUPTER.set()
@@ -63,10 +67,8 @@ def check(check_name):
def inner(f):
def wrapper(*args, **kwargs):
failures = f(*args, **kwargs)
print(failures)
msgs = []
for failure in failures:
#print(failure)
msg = "[{}]: Failure: {}".format(check_name, failure)
msgs.append(msg)
logger.warning(msg)
@@ -87,6 +89,16 @@ def _check_nans(*values):
return any(math.isnan(x) for x in values)
@check("Ros services")
def check_ros_services():
timeout = 5.0
for service in services_list:
try:
service.wait_for_service(timeout=timeout)
except (rospy.ServiceException, rospy.ROSException) as e:
yield ("ROS service {} is not available!".format(service.name))
@check("FCU connection")
def check_connection():
telemetry = get_telemetry()
@@ -95,7 +107,7 @@ def check_connection():
@check("Linear velocity estimation")
def check_linear_speeds(speed_limit=0.1):
def check_linear_speeds(speed_limit=0.15):
telemetry = get_telemetry(frame_id='body')
if _check_nans(telemetry.vx, telemetry.vy, telemetry.vz):
@@ -367,6 +379,7 @@ def flip(min_z = FLIP_MIN_Z, frame_id = FRAME_ID): #TODO Flip in different direc
if start_telemetry.z < min_z - TOLERANCE:
logger.warning("Can't do flip! Flip failed!")
#print("Can't do flip! Flip failed!")
return False
else:
# Flip!
set_rates(thrust=1) # bump up
@@ -383,3 +396,8 @@ def flip(min_z = FLIP_MIN_Z, frame_id = FRAME_ID): #TODO Flip in different direc
logger.info('Flip succeeded!')
#print('Flip succeeded!')
navto(x=start_telemetry.x, y=start_telemetry.y, z=start_telemetry.z, yaw=start_telemetry.yaw, frame_id=frame_id) # finish flip
<<<<<<< HEAD
=======
return True
>>>>>>> alpha

View File

@@ -13,16 +13,20 @@ logger = logging.getLogger(__name__)
interrupt_event = threading.Event()
id = "Empty id"
anim_id = "Empty id"
# TODO refactor as class
# TODO separate code for frames transformations (e.g. for gps)
def get_id(filepath="animation.csv"):
global id
global anim_id
try:
animation_file = open(filepath)
except IOError:
logging.error("File {} can't be opened".format(filepath))
id = "No animation"
return id
anim_id = "No animation"
return anim_id
else:
with animation_file:
csv_reader = csv.reader(
@@ -30,20 +34,21 @@ def get_id(filepath="animation.csv"):
)
row_0 = csv_reader.next()
if len(row_0) == 1:
id = row_0[0]
print("Got animation_id: {}".format(id))
anim_id = row_0[0]
print("Got animation_id: {}".format(anim_id))
else:
print("No animation id in file")
return id
return anim_id
def load_animation(filepath="animation.csv", x0=0, y0=0, z0=0):
imported_frames = []
global id
global anim_id
try:
animation_file = open(filepath)
except IOError:
logging.error("File {} can't be opened".format(filepath))
id = "No animation"
anim_id = "No animation"
else:
with animation_file:
csv_reader = csv.reader(
@@ -51,8 +56,8 @@ def load_animation(filepath="animation.csv", x0=0, y0=0, z0=0):
)
row_0 = csv_reader.next()
if len(row_0) == 1:
id = row_0[0]
print("Got animation_id: {}".format(id))
anim_id = row_0[0]
print("Got animation_id: {}".format(anim_id))
else:
print("No animation id in file")
frame_number, x, y, z, yaw, red, green, blue = row_0
@@ -87,7 +92,6 @@ def convert_frame(frame):
def execute_frame(point=(), color=(), yaw=float('Nan'), frame_id='aruco_map', use_leds=True,
flight_func=FlightLib.navto, flight_kwargs=None, interrupter=interrupt_event):
if flight_kwargs is None:
flight_kwargs = {}
@@ -112,7 +116,7 @@ def execute_animation(frames, frame_delay, frame_id='aruco_map', use_leds=True,
tasking.wait(next_frame_time, interrupter)
def takeoff(z=1.5, safe_takeoff=True, frame_id = 'map', timeout=5.0, use_leds=True,
def takeoff(z=1.5, safe_takeoff=True, frame_id='map', timeout=5.0, use_leds=True,
interrupter=interrupt_event):
print(interrupter.is_set())
if use_leds:
@@ -120,9 +124,9 @@ def takeoff(z=1.5, safe_takeoff=True, frame_id = 'map', timeout=5.0, use_leds=Tr
if interrupter.is_set():
return
result = FlightLib.takeoff(z=z, wait=False, timeout_takeoff=timeout, frame_id=frame_id, emergency_land=safe_takeoff,
interrupter=interrupter)
interrupter=interrupter)
if result == 'not armed':
raise Exception('STOP') # Raise exception to clear task_manager if copter can't arm
raise Exception('STOP') # Raise exception to clear task_manager if copter can't arm
if interrupter.is_set():
return
if use_leds:

View File

@@ -15,6 +15,7 @@ current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentfra
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
#logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
import messaging_lib as messaging
@@ -107,7 +108,7 @@ class Client(object):
logger.critical("Caught interrupt, exiting!")
self.selector.close()
def _reconnect(self, timeout=2, attempt_limit=5):
def _reconnect(self, timeout=3.0, attempt_limit=5):
logger.info("Trying to connect to {}:{} ...".format(self.server_host, self.server_port))
attempt_count = 0
while not self.connected:
@@ -133,7 +134,7 @@ class Client(object):
if attempt_count >= attempt_limit:
logger.info("Too many attempts. Trying to get new server IP")
self.broadcast_bind()
self.broadcast_bind(timeout*2, attempt_limit)
attempt_count = 0
@@ -146,30 +147,42 @@ class Client(object):
self._process_connections()
def broadcast_bind(self):
def broadcast_bind(self, timeout=3.0, attempt_limit=5):
broadcast_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
broadcast_client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
broadcast_client.bind(("", self.broadcast_port))
broadcast_client.settimeout(timeout)
attempt_count = 0
try:
while True:
data, addr = broadcast_client.recvfrom(self.BUFFER_SIZE)
message = messaging.MessageManager()
message.income_raw = data
message.process_message()
if message.content:
logger.info("Received broadcast message {} from {}".format(message.content, addr))
if message.content["command"] == "server_ip":
args = message.content["args"]
self.server_port = int(args["port"])
self.server_host = args["host"]
self.write_config(False,
ConfigOption("SERVER", "port", self.server_port),
ConfigOption("SERVER", "host", self.server_host))
logger.info("Binding to new IP: {}:{}".format(self.server_host, self.server_port))
break
while attempt_count <= attempt_limit:
try:
data, addr = broadcast_client.recvfrom(self.BUFFER_SIZE)
except socket.error as error:
logger.warning("Could not receive broadcast due error: {}".format(error))
attempt_count += 1
else:
message = messaging.MessageManager()
message.income_raw = data
message.process_message()
if message.content:
logger.info("Received broadcast message {} from {}".format(message.content, addr))
if message.content["command"] == "server_ip":
args = message.content["args"]
self.server_port = int(args["port"])
self.server_host = args["host"]
self.write_config(False,
ConfigOption("SERVER", "port", self.server_port),
ConfigOption("SERVER", "host", self.server_host))
logger.info("Binding to new IP: {}:{}".format(self.server_host, self.server_port))
self.on_broadcast_bind()
break
finally:
broadcast_client.close()
def on_broadcast_bind(self):
pass
def _process_connections(self):
while True:
events = self.selector.select(timeout=1)

View File

@@ -12,7 +12,7 @@ import messaging_lib as messaging
import tasking_lib as tasking
import animation_lib as animation
#logging.basicConfig( # TODO all prints as logs
# logging.basicConfig( # TODO all prints as logs
# level=logging.DEBUG, # INFO
# format="%(asctime)s [%(name)-7.7s] [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s",
# handlers=[
@@ -21,7 +21,8 @@ import animation_lib as animation
logger = logging.getLogger(__name__)
#import ros_logging
# import ros_logging
class CopterClient(client.Client):
def load_config(self):
@@ -40,26 +41,69 @@ class CopterClient(client.Client):
self.USE_LEDS = self.config.getboolean('PRIVATE', 'use_leds')
self.LED_PIN = self.config.getint('PRIVATE', 'led_pin')
def on_broadcast_bind(self):
configure_chrony_ip(self.server_host)
restart_service("chrony")
def start(self, task_manager_instance):
client.logger.info("Init ROS node")
rospy.init_node('Swarm_client', anonymous=True)
if self.USE_LEDS:
LedLib.init_led(self.LED_PIN)
task_manager_instance.start()
super(CopterClient, self).start()
def restart_service(name):
os.system("systemctl restart {}".format(name))
def configure_chrony_ip(ip, path="/etc/chrony/chrony.conf", ip_index=1):
try:
with open(path, 'r') as f:
raw_content = f.read()
except IOError as e:
print("Reading error {}".format(e))
return False
content = raw_content.split(" ")
try:
current_ip = content[ip_index]
except IndexError:
print("Something wrong with config")
return False
if "." not in current_ip:
print("That's not ip!")
return False
if current_ip != ip:
content[ip_index] = ip
try:
with open(path, 'w') as f:
f.write(" ".join(content))
except IOError:
print("Error writing")
return False
return True
@messaging.request_callback("selfcheck")
def _response_selfcheck():
check = FlightLib.selfcheck()
return check if check else "OK"
@messaging.request_callback("anim_id")
def _response_animation_id():
return animation.get_id()
@messaging.request_callback("batt_voltage")
def _response_batt():
return FlightLib.get_telemetry('body').voltage
@@ -69,41 +113,37 @@ def _response_batt():
def _response_cell():
return FlightLib.get_telemetry('body').cell_voltage
@messaging.message_callback("test")
def _command_test(**kwargs):
print("test")
logger.info("logging info test")
print("stdout test")
@messaging.message_callback("service_restart")
def _command_service_restart(**kwargs):
os.system("systemctl restart" + kwargs["name"])
restart_service(kwargs["name"])
@messaging.message_callback("repair_chrony")
def _command_chrony_repair():
configure_chrony_ip(client.active_client.server_host)
restart_service("chrony")
@messaging.message_callback("led_test")
def _command_led_test(*args, **kwargs):
def _command_led_test(**kwargs):
LedLib.chase(255, 255, 255)
time.sleep(2)
LedLib.off()
@messaging.message_callback("led_fill")
def _command_emergency_led_fill(**kwargs):
r = g = b = 0
try:
r = kwargs["red"]
except KeyError:
pass
try:
g = kwargs["green"]
except KeyError:
pass
try:
b = kwargs["blue"]
except KeyError:
pass
LedLib.fill(r, g, b)
@messaging.message_callback("led_fill")
def _command_led_fill(**kwargs):
r = kwargs.get("red", 0)
g = kwargs.get("green", 0)
b = kwargs.get("blue", 0)
LedLib.fill(r, g, b)
@messaging.message_callback("flip")
@@ -139,10 +179,10 @@ def _command_land(**kwargs):
def _command_disarm(**kwargs):
task_manager.reset()
task_manager.add_task(-5, 0, FlightLib.arming_wrapper,
task_kwargs={
"state": False
}
)
task_kwargs={
"state": False
}
)
@messaging.message_callback("stop")
@@ -151,24 +191,24 @@ def _command_stop(**kwargs):
@messaging.message_callback("pause")
def _command_stop(**kwargs):
def _command_pause(**kwargs):
task_manager.pause()
@messaging.message_callback("resume")
def _command_stop(**kwargs):
task_manager.resume()
def _command_resume(**kwargs):
task_manager.resume(time_to_start_next_task=kwargs.get("time", 0))
@messaging.message_callback("start")
def _play_animation(**kwargs):
start_time = float(kwargs["time"]) # TODO
if (animation.get_id() == 'No animation'):
if animation.get_id() == 'No animation':
print("Can't start animation without animation file!")
return
print("Start time = {}, wait for {} seconds".format(start_time, time.time()-start_time))
print("Start time = {}, wait for {} seconds".format(start_time, time.time() - start_time))
frames = animation.load_animation(os.path.abspath("animation.csv"),
x0=client.active_client.X0 + client.active_client.X0_COMMON,
@@ -176,59 +216,58 @@ def _play_animation(**kwargs):
)
task_manager.add_task(start_time, 0, animation.takeoff,
task_kwargs={
"z": client.active_client.TAKEOFF_HEIGHT,
"timeout": client.active_client.TAKEOFF_TIME,
"safe_takeoff": client.active_client.SAFE_TAKEOFF,
#"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
}
)
task_kwargs={
"z": client.active_client.TAKEOFF_HEIGHT,
"timeout": client.active_client.TAKEOFF_TIME,
"safe_takeoff": client.active_client.SAFE_TAKEOFF,
# "frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
}
)
rfp_time = start_time + client.active_client.TAKEOFF_TIME
task_manager.add_task(rfp_time, 0, animation.execute_frame,
task_kwargs={
"point": animation.convert_frame(frames[0])[0],
"color": animation.convert_frame(frames[0])[1],
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
"flight_func": FlightLib.reach_point,
}
)
task_kwargs={
"point": animation.convert_frame(frames[0])[0],
"color": animation.convert_frame(frames[0])[1],
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
"flight_func": FlightLib.reach_point,
}
)
frame_time = rfp_time + client.active_client.RFP_TIME
frame_delay = 0.125 # TODO from animation file
for frame in frames:
point, color, yaw = animation.convert_frame(frame) # TODO add param to calculate delta
task_manager.add_task(frame_time, 0, animation.execute_frame,
task_kwargs={
"point": animation.convert_frame(frame)[0],
"color": animation.convert_frame(frame)[1],
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
"flight_func": FlightLib.navto,
}
)
task_kwargs={
"point": point,
"color": color,
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
"flight_func": FlightLib.navto,
}
)
frame_time += frame_delay
land_time = frame_time + client.active_client.LAND_TIME
task_manager.add_task(land_time, 0, animation.land,
task_kwargs={
"timeout": client.active_client.TAKEOFF_TIME,
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
},
)
task_kwargs={
"timeout": client.active_client.TAKEOFF_TIME,
"frame_id": client.active_client.FRAME_ID,
"use_leds": client.active_client.USE_LEDS,
},
)
if __name__ == "__main__":
copter_client = CopterClient()
task_manager = tasking.TaskManager()
copter_client.start(task_manager)
#ros_logging.route_logger_to_ros()
#ros_logging.route_logger_to_ros("__main__")
#ros_logging.route_logger_to_ros("client")
#ros_logging.route_logger_to_ros("messaging")
# ros_logging.route_logger_to_ros()
# ros_logging.route_logger_to_ros("__main__")
# ros_logging.route_logger_to_ros("client")
# ros_logging.route_logger_to_ros("messaging")

View File

@@ -10,7 +10,9 @@ Task = collections.namedtuple("Task", ["func", "args", "kwargs", "delayable", ])
INTERRUPTER = threading.Event()
def wait(end, interrupter=INTERRUPTER, maxsleep=0.1): # Added features to interrupter sleep and set max sleeping interval
def wait(end, interrupter=INTERRUPTER, maxsleep=0.1):
# Added features to interrupter sleep and set max sleeping interval
while not interrupter.is_set(): # Basic implementation of pause module until()
now = time.time()
@@ -42,7 +44,10 @@ class TaskManager(object):
self._timeshift = 0.0
def add_task(self, timestamp, priority, task_function,
task_args=(), task_kwargs={}, task_delayable=False):
task_args=(), task_kwargs=None, task_delayable=False):
if task_kwargs is None:
task_kwargs = {}
self._wait_interrupt_event.set()
self._running_event.clear()
@@ -80,7 +85,7 @@ class TaskManager(object):
return heapq.heappop(self.task_queue)
raise KeyError('Pop from an empty priority queue')
def start(self, timeouts=False):
def start(self):
#print("Task manager is started")
#logger.info("Task manager is started")
self._processor_thread.start()
@@ -92,13 +97,13 @@ class TaskManager(object):
with self._task_queue_lock:
del self.task_queue[:]
def shutdown(self):
def shutdown(self, timeout=5.0):
self.stop()
self._shutdown_event.set()
self._wait_interrupt_event.set()
self._task_interrupt_event.set()
self._running_event.clear()
self._processor_thread.join(timeout=5)
# self._wait_interrupt_event.set()
# self._task_interrupt_event.set()
# self._running_event.clear() #already exists in pause
self._processor_thread.join(timeout=timeout)
def pause(self, interrupt=True):
if interrupt:
@@ -108,7 +113,7 @@ class TaskManager(object):
#logger.info("Task queue paused")
#print("Task queue paused")
def resume(self, time_to_start_next_task = 0):
def resume(self, time_to_start_next_task=0.0):
if self.task_queue:
next_task_time = self.task_queue[0][0]
if time_to_start_next_task > next_task_time:

View File

@@ -0,0 +1,263 @@
import sys
import re
import collections
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt as Qt
class CopterData:
class_attrs = collections.OrderedDict([('copter_id', None), ('anim_id', None), ('batt_v', None), ('batt_p', None),
('selfcheck', None), ('time_utc', None), ("time_delta", None),
("client", None), ("checked", 0)], )
def __init__(self, **kwargs):
self.attrs = self.class_attrs.copy()
self.attrs.update(kwargs)
for attr, value in self.attrs.items():
setattr(self, attr, value)
def __getitem__(self, key):
return getattr(self, list(self.attrs.keys())[key])
def __setitem__(self, key, value):
setattr(self, list(self.attrs.keys())[key], value)
class CopterDataModel(QtCore.QAbstractTableModel):
checks = {}
selected_ready_signal = QtCore.pyqtSignal(bool)
selected_takeoff_ready_signal = QtCore.pyqtSignal(bool)
def __init__(self, parent=None):
super(CopterDataModel, self).__init__(parent)
self.headers = ('copter ID', 'animation ID', 'battery V', 'battery %', 'selfcheck', 'time delta')
self.data_contents = []
def insertRows(self, contents, position='last', parent=QtCore.QModelIndex()):
rows = len(contents)
position = len(self.data_contents) if position == 'last' else position
self.beginInsertRows(parent, position, position + rows - 1)
self.data_contents[position:position] = contents
self.endInsertRows()
def user_selected(self):
return filter(lambda x: x.checked == Qt.Checked, self.data_contents)
def selfchecked_ready(self, contents=()):
contents = contents or self.data_contents
return filter(lambda x: all_checks(x), contents)
def takeoff_ready(self, contents=()):
contents = contents or self.data_contents
return filter(lambda x: takeoff_checks(x), contents)
def rowCount(self, n=None):
return len(self.data_contents)
def columnCount(self, n=None):
return len(self.headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self.headers[section]
def data(self, index, role=Qt.DisplayRole):
row = index.row()
col = index.column()
#print('row {}, col {}, role {}'.format(row, col, role))
if role == Qt.DisplayRole:
#print(self.data_contents[row][col])
return self.data_contents[row][col] or ""
elif role == Qt.BackgroundRole:
if col in self.checks.keys():
item = self.data_contents[row][col]
result = self.checks[col](item)
if result is None:
return QtGui.QBrush(Qt.yellow)
if result:
return QtGui.QBrush(Qt.green)
else:
return QtGui.QBrush(Qt.red)
elif role == Qt.CheckStateRole and col == 0:
return self.data_contents[row].checked
def update_model(self, index=QtCore.QModelIndex()):
#self.modelReset.emit()
self.selected_ready_signal.emit(set(self.user_selected()).issubset(self.selfchecked_ready()))
self.selected_takeoff_ready_signal.emit(set(self.user_selected()).issubset(self.takeoff_ready()))
self.dataChanged.emit(index, index, (QtCore.Qt.EditRole,))
@QtCore.pyqtSlot()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole:
self.data_contents[index.row()].checked = value
elif role == Qt.EditRole:
self.data_contents[index.row()][index.column()] = value
self.update_model(index)
else:
return False
return True
def flags(self, index):
roles = Qt.ItemIsSelectable | Qt.ItemIsEnabled
if index.column() == 0:
roles |= Qt.ItemIsUserCheckable
return roles
@QtCore.pyqtSlot(int, int, QtCore.QVariant)
def update_item(self, row, col, value):
self.setData(self.index(row, col), value)
@QtCore.pyqtSlot(object)
def add_client(self, client):
self.insertRows([client])
def col_check(col):
def inner(f):
CopterDataModel.checks[col] = f
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
return inner
@col_check(1)
def check_anim(item):
if not item:
return None
if str(item) == 'No animation':
return False
else:
return True
@col_check(2)
def check_bat_v(item):
if not item:
return None
if float(item) > 3.2: # todo config
return True
else:
return False
@col_check(3)
def check_bat_p(item):
if not item:
return None
if float(item) > 30: # todo config
return True
else:
return False
#return True #For testing
@col_check(4)
def check_selfcheck(item):
if not item:
return None
if item == "OK":
return True
else:
return False
@col_check(5)
def check_time_delta(item):
if not item:
return None
if abs(float(item)) < 1:
return True
else:
return False
def all_checks(copter_item):
for col, check in CopterDataModel.checks.items():
if not check(copter_item[col]):
return False
return True
def takeoff_checks(copter_item):
for i in range(3):
if not CopterDataModel.checks[2+i](copter_item[2+i]):
return False
return True
class CopterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(CopterProxyModel, self).__init__(parent)
@staticmethod
def human_sort_prepare(item):
if item:
item = [int(x) if x.isdigit() else x.lower() for x in re.split('([0-9]+)', str(item))]
else:
item = []
return item
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
return self.human_sort_prepare(leftData) < self.human_sort_prepare(rightData)
class SignalManager(QtCore.QObject):
update_data_signal = QtCore.pyqtSignal(int, int, QtCore.QVariant)
add_client_signal = QtCore.pyqtSignal(object)
if __name__ == '__main__':
import threading
import time
def timer():
idc = 1001
while True:
myModel.setData(myModel.index(0, 0), idc)
idc += 1
time.sleep(1)
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
tableView = QtWidgets.QTableView()
myModel = CopterDataModel(None)
proxyModel = CopterProxyModel()
proxyModel.setDynamicSortFilter(True)
proxyModel.setSourceModel(myModel)
tableView.setModel(proxyModel)
tableView.verticalHeader().hide()
tableView.setSortingEnabled(True)
tableView.show()
myModel.add_client(CopterData(copter_id=1000, checked=0, time_utc=1))
myModel.add_client(CopterData(checked=2, selfcheck="OK", time_utc=2))
myModel.add_client(CopterData(checked=2, selfcheck="not ok", time_utc="no"))
myModel.setData(myModel.index(0, 1), "test")
t = threading.Thread(target=timer, daemon=True)
t.start()
print(QtCore.QT_VERSION_STR)
app.exec_()

View File

@@ -140,7 +140,7 @@ class Server:
self.sel.register(self.server_socket, selectors.EVENT_READ | selectors.EVENT_WRITE, data=None)
while self.client_processor_thread_running.is_set():
events = self.sel.select(timeout=0)
events = self.sel.select()
for key, mask in events:
if key.data is None:
self._connect_client(key.fileobj)
@@ -159,7 +159,7 @@ class Server:
logging.info("Got connection from: {}".format(str(addr)))
conn.setblocking(False)
if not any(client_addr == addr[0] for client_addr in Client.clients.keys()):
if not any([client_addr == addr[0] for client_addr in Client.clients.keys()]):
client = Client(addr[0])
logging.info("New client")
else:

View File

@@ -12,22 +12,73 @@ from PyQt5.QtWidgets import QFileDialog, QMessageBox
from server_gui import Ui_MainWindow
from server import *
from copter_table_models import *
from emergency import *
def confirmation_required(text="Are you sure?", label="Confirm operation?"):
def inner(f):
def wrapper(*args, **kwargs):
reply = QMessageBox.question(
args[0], label,
text,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
print("Dialog accepted")
#print(args)
return f(args[0])
else:
print("Dialog declined")
return wrapper
return inner
# noinspection PyArgumentList,PyCallByClass
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.init_ui()
self.model = CopterDataModel()
self.proxy_model = CopterProxyModel()
self.signals = SignalManager()
self.init_model()
self.show()
def init_model(self):
self.proxy_model.setDynamicSortFilter(True)
self.proxy_model.setSourceModel(self.model)
# Initiate table and table self.model
self.ui.tableView.setModel(self.proxy_model)
self.ui.tableView.horizontalHeader().setStretchLastSection(True)
self.ui.tableView.setSortingEnabled(True)
# Connect signals to manipulate model from threads
self.signals.update_data_signal.connect(self.model.update_item)
self.signals.add_client_signal.connect(self.model.add_client)
# Connect model signals to UI
self.model.selected_ready_signal.connect(self.ui.start_button.setEnabled)
self.model.selected_takeoff_ready_signal.connect(self.ui.takeoff_button.setEnabled)
def client_connected(self, client: Client):
self.signals.add_client_signal.emit(CopterData(copter_id=client.copter_id, client=client))
def init_ui(self):
# Connecting
self.ui.check_button.clicked.connect(self.check_selected)
self.ui.check_button.clicked.connect(self.selfcheck_selected)
self.ui.start_button.clicked.connect(self.send_starttime)
self.ui.pause_button.clicked.connect(self.pause_all)
self.ui.pause_button.clicked.connect(self.pause_resume_selected)
self.ui.stop_button.clicked.connect(self.stop_all)
self.ui.emergency_button.clicked.connect(self.emergency)
@@ -40,103 +91,92 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.action_send_configurations.triggered.connect(self.send_configurations)
self.ui.action_send_Aruco_map.triggered.connect(self.send_aruco)
# Initing table and table model
self.ui.tableView.setModel(model)
self.ui.tableView.horizontalHeader().setStretchLastSection(True)
# self.ui = Communicate()
# self.ui.button.connect(self.button1)
# Set most safety-important buttons disabled
self.ui.start_button.setEnabled(False)
self.ui.takeoff_button.setEnabled(False)
@pyqtSlot()
def check_selected(self):
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
print("Copter {} checked".format(model.item(row_num, 0).text()))
copter = Client.get_by_id(item.text())
copter.get_response("anim_id", self._set_copter_data, callback_args=(row_num, 1))
copter.get_response("batt_voltage", self._set_copter_data, callback_args=(row_num, 2))
copter.get_response("cell_voltage", self._set_copter_data, callback_args=(row_num, 3))
copter.get_response("selfcheck", self._set_copter_data, callback_args=(row_num, 4))
copter.get_response("time", self._set_copter_data, callback_args=(row_num, 5))
def selfcheck_selected(self):
for copter in self.model.user_selected():
client = copter.client
self.ui.start_button.setEnabled(True)
self.ui.takeoff_button.setEnabled(True)
client.get_response("anim_id", self._set_copter_data, callback_args=(1, copter.copter_id))
client.get_response("batt_voltage", self._set_copter_data, callback_args=(2, copter.copter_id))
client.get_response("cell_voltage", self._set_copter_data, callback_args=(3, copter.copter_id))
client.get_response("selfcheck", self._set_copter_data, callback_args=(4, copter.copter_id))
client.get_response("time", self._set_copter_data, callback_args=(5, copter.copter_id))
def _set_copter_data(self, value, col, copter_id):
row = self.model.data_contents.index(next(
filter(lambda x: x.copter_id == copter_id, self.model.data_contents)))
def _set_copter_data(self, value, row, col):
if col == 1:
model.setData(model.index(row, col), value)
data = value
elif col == 2:
model.setData(model.index(row, col), "{} V".format(round(float(value), 3)))
data = "{}".format(round(float(value), 3))
elif col == 3:
batt_percent = ((float(value) - 3.2) / (4.2 - 3.2)) * 100
model.setData(model.index(row, col), "{} %".format(round(batt_percent, 3)))
batt_percent = ((float(value) - 3.2) / (4.2 - 3.2)) * 100 # TODO config
data = "{}".format(round(batt_percent, 3))
elif col == 4:
if value != "OK":
model.setData(model.index(row, col), str(value)) # TODO different handling
else:
model.setData(model.index(row, col), str(value))
data = str(value)
elif col == 5:
model.setData(model.index(row, col), time.ctime(int(value)))
#data = time.ctime(int(value))
data = "{}".format(round(float(value) - time.time(), 3))
if abs(float(data)) > 1:
Client.get_by_id(copter_id).send_message("repair_chrony")
#self.signals.update_data_signal.emit(row, col + 1, data2)
else:
print("No column matched for response")
return
self.signals.update_data_signal.emit(row, col, data)
@confirmation_required("This operation will takeoff selected copters with delay and start animation. Proceed?")
@pyqtSlot()
def send_starttime(self, **kwargs):
dt = self.ui.start_delay_spin.value()
for copter in self.model.user_selected():
if all_checks(copter):
server.send_starttime(copter.client, dt)
@confirmation_required("This operation will takeoff copters immediately. Proceed?")
@pyqtSlot()
def takeoff_selected(self, **kwargs):
for copter in self.model.user_selected():
if all_checks(copter):
copter.client.send_message("takeoff")
@confirmation_required("This operation will flip(!!!) copters immediately. Proceed?")
@pyqtSlot()
def flip(self, **kwargs):
for copter in self.model.user_selected():
if all_checks(copter):
copter.client.send_message("flip")
@pyqtSlot()
def send_starttime(self):
dt = self.ui.start_delay_spin.value()
reply = QMessageBox.question(
self, "Confirm operation",
"This operation will takeoff selected copters and start animation after {} seconds. Proceed?".format(dt),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
print("Accepted")
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
if True: # TODO checks for batt/selfckeck here
copter = Client.get_by_id(item.text())
server.send_starttime(copter, dt)
else:
print("Cancelled")
def test_leds(self):
for copter in self.model.user_selected():
copter.client.send_message("led_test")
@pyqtSlot()
def stop_all(self):
Client.broadcast_message("stop")
@pyqtSlot()
def pause_all(self):
def pause_resume_selected(self):
if self.ui.pause_button.text() == 'Pause':
Client.broadcast_message('pause')
for copter in self.model.user_selected():
copter.client.send_message("pause")
self.ui.pause_button.setText('Resume')
else:
Client.broadcast_message('resume')
self.ui.pause_button.setText('Pause')
self._resume_selected()
@pyqtSlot()
def test_leds(self):
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
if True: # TODO checks for batt/selfckeck here
copter = Client.get_by_id(item.text())
copter.send_message("led_test")
@pyqtSlot()
def takeoff_selected(self):
reply = QMessageBox.question(
self, "Confirm operation",
"This operation will takeoff copters immediately. Proceed?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
print("Accepted")
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
if True: # TODO checks for batt/selfckeck here
copter = Client.get_by_id(item.text())
copter.send_message("takeoff")
else:
print("Cancelled")
pass
#@confirmation_required("This operation will resume ALL copter tasks with given delay. Proceed?")
def _resume_selected(self, **kwargs):
time_gap = 0.1
for copter in self.model.user_selected():
copter.client.send_message('resume', {"time": server.time_now() + time_gap})
self.ui.pause_button.setText('Pause')
@pyqtSlot()
def land_all(self):
@@ -156,14 +196,11 @@ class MainWindow(QtWidgets.QMainWindow):
names = [os.path.basename(file).split(".")[0] for file in files]
print(files)
for file, name in zip(files, names):
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
copter = Client.get_by_id(item.text())
if name == copter.copter_id:
copter.send_file(file, "animation.csv") # TODO config
else:
print("Filename not matches with any drone connected")
for copter in self.model.user_selected():
if name == copter.copter_id:
copter.client.send_file(file, "animation.csv") # TODO config
else:
print("Filename has no matches with any drone selected")
@pyqtSlot()
def send_configurations(self):
@@ -178,11 +215,9 @@ class MainWindow(QtWidgets.QMainWindow):
value = sendable_config[section][option]
logging.debug("Got item from config:".format(section, option, value))
options.append(ConfigOption(section, option, value))
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
copter = Client.get_by_id(item.text())
copter.send_config_options(*options)
for copter in self.model.user_selected():
copter.client.send_config_options(*options)
@pyqtSlot()
def send_aruco(self):
@@ -190,102 +225,64 @@ class MainWindow(QtWidgets.QMainWindow):
if path:
filename = os.path.basename(path)
print("Selected file:", path, filename)
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
copter = Client.get_by_id(item.text())
copter.send_file(path, "/home/pi/catkin_ws/src/clever/aruco_pose/map/animation_map.txt")
copter.send_message("service_restart", {"name": "clever"})
for copter in self.model.user_selected():
copter.client.send_file(path, "/home/pi/catkin_ws/src/clever/aruco_pose/map/animation_map.txt")
copter.client.send_message("service_restart", {"name": "clever"})
@pyqtSlot()
def emergency(self):
client_row_min = 0
client_row_max = model.rowCount() - 1
client_row_max = self.model.rowCount() - 1
result = -1
while (result!=0) and (result != 3) and (result != 4):
while (result != 0) and (result != 3) and (result != 4):
# light_green_red(min, max)
client_row_mid = int(math.ceil((client_row_max+client_row_min) / 2.0))
print(client_row_min, client_row_mid, client_row_max)
for row_num in range(client_row_min, client_row_mid):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("led_fill", {"green": 255})
self.model.data_contents[row_num].client\
.send_message("led_fill", {"green": 255})
for row_num in range(client_row_mid, client_row_max + 1):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("led_fill", {"red": 255})
self.model.data_contents[row_num].client \
.send_message("led_fill", {"red": 255})
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
result = Dialog.exec()
print("Dialog result: {}".format(result))
if (client_row_max != client_row_min):
if (result == 1):
if client_row_max != client_row_min:
if result == 1:
for row_num in range(client_row_mid, client_row_max + 1):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("led_fill")
self.model.data_contents[row_num].client \
.send_message("led_fill")
client_row_max = client_row_mid - 1
elif (result == 2):
elif result == 2:
for row_num in range(client_row_min, client_row_mid):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("led_fill")
self.model.data_contents[row_num].client \
.send_message("led_fill")
client_row_min = client_row_mid
if result == 0:
Client.broadcast_message("led_fill")
elif result == 3:
for row_num in range(client_row_min, client_row_max + 1):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("land")
self.model.data_contents[row_num].client \
.send_message("land")
elif result == 4:
for row_num in range(client_row_min, client_row_max + 1):
item = model.item(row_num, 0)
copter = Client.get_by_id(item.text())
copter.send_message("disarm")
@pyqtSlot()
def flip(self):
reply = QMessageBox.question(
self, "Confirm operation",
"You are ready to turn the copter?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
print("Accepted")
for row_num in range(model.rowCount()):
item = model.item(row_num, 0)
if item.isCheckable() and item.checkState() == Qt.Checked:
if True: # TODO checks for batt/selfckeck here
copter = Client.get_by_id(item.text())
copter.send_message("flip")
else:
print("Cancelled")
pass
model = QStandardItemModel()
model.setHorizontalHeaderLabels(
('copter ID', 'animation ID', 'battery V', 'battery %', 'selfcheck', 'time UTC')
)
model.setColumnCount(6)
model.setRowCount(0)
def client_connected(self: Client):
copter_id_item = QStandardItem(self.copter_id)
copter_id_item.setCheckable(True)
model.appendRow((copter_id_item, ))
Client.on_first_connect = client_connected
self.model.data_contents[row_num].client \
.send_message("disarm")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
Client.on_first_connect = window.client_connected
server = Server(on_stop=app.quit)
server.start()

View File

@@ -1,12 +1,12 @@
[Unit]
Description=Clever Show Client Service
Requires=clever.service
After=clever.service
[Service]
WorkingDirectory=/home/pi/CleverSwarm/Drone
EnvironmentFile=/lib/systemd/system/roscore.env
ExecStart=/usr/bin/python /home/pi/CleverSwarm/Drone/copter_client.py
KillSignal=SIGKILL
Restart=on-failure
RestartSec=3

View File

@@ -44,7 +44,7 @@ sed -i '/<arg name="aruco_vpe"/c \ <arg name="aruco_vpe" default="true"/>' /h
sed -i '/<param name="map"/c \ <param name="map" value="\$\(find aruco_pose\)/map/animation_map.txt"/>' /home/pi/catkin_ws/src/clever/clever/launch/aruco.launch
sed -i '/<arg name="aruco"/c \ <arg name="aruco" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
sed -i '/<arg name="rangefinder_vl53l1x"/c \ <arg name="rangefinder_vl53l1x" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
#sed -i '/<arg name="optical_flow"/c \ <arg name="optical_flow" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
sed -i '/<arg name="optical_flow"/c \ <arg name="optical_flow" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
echo_stamp "Image was configured!" "SUCCESS"