From ecfa70548223fc620519e331ea6b7f304bb77888 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Fri, 3 Apr 2020 19:50:16 +0300 Subject: [PATCH 1/7] Client: Updates for compability with clover and simulations --- Drone/FlightLib/FlightLib.py | 10 ++++++++-- Drone/copter_client.py | 10 ++++++++-- Drone/visual_pose_watchdog.py | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Drone/FlightLib/FlightLib.py b/Drone/FlightLib/FlightLib.py index 8c70b09..6ecb302 100644 --- a/Drone/FlightLib/FlightLib.py +++ b/Drone/FlightLib/FlightLib.py @@ -6,7 +6,13 @@ import time import logging import threading import rospy -from clever import srv + +# for backward compatibility with clever +try: + from clever import srv +except ImportError: + from clover import srv + from mavros_msgs.srv import SetMode from mavros_msgs.srv import CommandBool from std_srvs.srv import Trigger @@ -337,7 +343,7 @@ def takeoff(height=TAKEOFF_HEIGHT, speed=TAKEOFF_SPEED, tolerance=TOLERANCE, fra return 'interrupted' climb = abs(get_telemetry_locked(frame_id=frame_id).z - start.z) - rospy.logdebug("Takeoff to {:.2f} of {:.2f} meters".format(climb, height)) + rospy.loginfo("Takeoff to {:.2f} of {:.2f} meters".format(climb, height)) time_passed = time.time() - time_start diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 150e694..4efba23 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -4,7 +4,13 @@ import time import math import rospy import numpy -from clever import srv + +# for backward compatibility with clever +try: + from clever import srv +except ImportError: + from clover import srv + import datetime import logging import threading @@ -13,7 +19,6 @@ import subprocess from collections import namedtuple from FlightLib import FlightLib -from FlightLib import LedLib import client @@ -86,6 +91,7 @@ class CopterClient(client.Client): rospy.loginfo("Init ROS node") rospy.init_node('clever_show_client') if self.config.led_use: + from FlightLib import LedLib LedLib.init_led(self.config.led_pin) task_manager_instance.start() # TODO move to self if self.config.copter_frame_id == "floor": diff --git a/Drone/visual_pose_watchdog.py b/Drone/visual_pose_watchdog.py index 4ca07cb..7f62957 100644 --- a/Drone/visual_pose_watchdog.py +++ b/Drone/visual_pose_watchdog.py @@ -5,7 +5,13 @@ import time import math import logging import threading -from clever.srv import SetAttitude + +# for backward compatibility with clever +try: + from clever import SetAttitude +except ImportError: + from clover import SetAttitude + from sensor_msgs.msg import Range from mavros_msgs.msg import State, PositionTarget from mavros_msgs.srv import SetMode, CommandBool @@ -16,7 +22,7 @@ from geometry_msgs.msg import PoseStamped import inspect # Add parent dir to PATH to import messaging_lib current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parent_dir = os.path.dirname(current_dir) -sys.path.insert(0, parent_dir) +sys.path.insert(0, parent_dir) from config import ConfigManager From 7630e8b51bac0fa94678ff612233180116e31d05 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Fri, 3 Apr 2020 19:50:40 +0300 Subject: [PATCH 2/7] Client: Add requirements file for standalone install --- Drone/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Drone/requirements.txt diff --git a/Drone/requirements.txt b/Drone/requirements.txt new file mode 100644 index 0000000..b9b5ed8 --- /dev/null +++ b/Drone/requirements.txt @@ -0,0 +1,2 @@ +selectors2 +psutil \ No newline at end of file From d8bc67692ff2d606830aa93e4be8bad2a471792a Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 7 Apr 2020 10:12:31 +0300 Subject: [PATCH 3/7] Client: Update FCU parameters parser --- Drone/copter_client.py | 6 +++++- Drone/mavros_mavlink.py | 46 +++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 4efba23..f6856d0 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -74,6 +74,10 @@ flightlib_logger = logging.getLogger('FlightLib') flightlib_logger.setLevel(logging.INFO) flightlib_logger.addHandler(handler) +mavros_mavlink_logger = logging.getLogger('mavros_mavlink') +mavros_mavlink_logger.setLevel(logging.INFO) +mavros_mavlink_logger.addHandler(handler) + class CopterClient(client.Client): def __init__(self, config_path="config/client.ini"): @@ -89,7 +93,7 @@ class CopterClient(client.Client): def start(self, task_manager_instance): rospy.loginfo("Init ROS node") - rospy.init_node('clever_show_client') + rospy.init_node('clever_show_client', anonymous=True) if self.config.led_use: from FlightLib import LedLib LedLib.init_led(self.config.led_pin) diff --git a/Drone/mavros_mavlink.py b/Drone/mavros_mavlink.py index 7a6005e..b4602c4 100644 --- a/Drone/mavros_mavlink.py +++ b/Drone/mavros_mavlink.py @@ -6,6 +6,8 @@ from mavros_msgs.srv import ParamGet, ParamSet from mavros_msgs.msg import State, ParamValue from pymavlink.dialects.v20 import common as mavlink +logger = logging.getLogger(__name__) + send_command_long = rospy.ServiceProxy('/mavros/cmd/command', CommandLong) get_param = rospy.ServiceProxy('/mavros/param/get', ParamGet) set_param = rospy.ServiceProxy('/mavros/param/set', ParamSet) @@ -127,23 +129,45 @@ def stop_subscriber(): def load_param_file(px4_file): result = True + err_lines = "" + err_params = "" + lines_commented = "" + params_loaded = "" try: px4_params = open(px4_file) except IOError: - logging.error("File {} can't be opened".format(filepath)) + logger.error("File {} can't be opened".format(filepath)) result = False - else: + else: with open(px4_file) as px4_params: + row = 0 for line in px4_params: - param_str_array = line[:-1].split('\t') - param_name = param_str_array[2] - param_value_str = param_str_array[3] - param_type = param_str_array[4] - if param_type == '6': - param_value = ParamValue(integer=int(param_value_str)) + row += 1 + param_str_array = line.split('\t') + if len(param_str_array) == 5 and '#' not in param_str_array[0]: + param_name = param_str_array[2] + param_value_str = param_str_array[3] + param_type = int(param_str_array[4]) + if param_type == 6: + param_value = ParamValue(integer=int(param_value_str)) + else: + param_value = ParamValue(real=float(param_value_str)) + if not set_param(param_name, param_value): + err_params += "{} ,".format(row) + result = False + else: + params_loaded += "{} ,".format(row) + elif '#' in param_str_array[0]: + lines_commented += "{} ,".format(row) else: - param_value = ParamValue(real=float(param_value_str)) - if not set_param(param_name, param_value): - result = False + err_lines += "{} ,".format(row) + if err_lines: + logger.info("Can't parse lines: {}".format(err_lines[:-1])) + if err_params: + logger.info("Can't set params from lines: {}".format(err_params[:-1])) + if lines_commented: + logger.info("Lines commented: {}".format(lines_commented[:-1])) + if params_loaded: + logger.info("Params are successfully loaded from lines: {}".format(params_loaded[:-1])) return result From 6354d524f638ef3d693863c24da19715da98bf3d Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 7 Apr 2020 10:34:25 +0300 Subject: [PATCH 4/7] Client: Remove waste spaces --- Drone/client.py | 2 +- Drone/client_setup.sh | 4 ++-- Drone/copter_client.py | 14 +++++++------- Drone/mavros_mavlink.py | 4 ++-- Drone/tasking_lib.py | 20 ++++++++++---------- Drone/visual_pose_watchdog.py | 18 +++++++++--------- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Drone/client.py b/Drone/client.py index 9fb5081..88c5057 100644 --- a/Drone/client.py +++ b/Drone/client.py @@ -13,7 +13,7 @@ from contextlib import closing import inspect # Add parent dir to PATH to import messaging_lib current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parent_dir = os.path.dirname(current_dir) -sys.path.insert(0, parent_dir) +sys.path.insert(0, parent_dir) logger = logging.getLogger(__name__) diff --git a/Drone/client_setup.sh b/Drone/client_setup.sh index 81f1496..b92b777 100755 --- a/Drone/client_setup.sh +++ b/Drone/client_setup.sh @@ -2,7 +2,7 @@ # $1 - ssid, $2 - password of wifi router # $3 - hostname of rpi -# $4 - server ip +# $4 - server ip if [ $(whoami) != "root" ]; then echo -e "\nThis should be run as root!\n" @@ -38,7 +38,7 @@ country=GB network={ ssid="$1" psk="$2" - scan_ssid=1 + scan_ssid=1 } EOF diff --git a/Drone/copter_client.py b/Drone/copter_client.py index f6856d0..6fdcf33 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -303,7 +303,7 @@ def _response_animation_id(*args, **kwargs): if result != 'No animation': logger.debug("Saving corrected animation") offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, + frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, offset[0], offset[1], offset[2], *client.active_client.config.animation_ratio) # Correct start and land frames in animation corrected_frames, start_action, start_delay = animation.correct_animation(frames, @@ -390,10 +390,10 @@ def _command_move_start_to_current_position(*args, **kwargs): telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) logger.debug("x_telem = {}, y_telem = {}".format(telem.x, telem.y)) if not math.isnan(telem.x): - client.active_client.config.set('PRIVATE', 'offset', + client.active_client.config.set('PRIVATE', 'offset', [telem.x - x_start, telem.y - y_start, client.active_client.config.private_offset[2]], write=True) - logger.info("Set start delta: {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], + logger.info("Set start delta: {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], client.active_client.config.private_offset[1])) else: logger.debug("Wrong telemetry") @@ -403,17 +403,17 @@ def _command_move_start_to_current_position(*args, **kwargs): @messaging.message_callback("reset_start") def _command_reset_start(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', + client.active_client.config.set('PRIVATE', 'offset', [0, 0, client.active_client.config.private_offset[2]], write=True) - logger.info("Reset start to {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], + logger.info("Reset start to {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], client.active_client.config.private_offset[1])) @messaging.message_callback("set_z_to_ground") def _command_set_z(*args, **kwargs): telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - client.active_client.config.set('PRIVATE', 'offset', + client.active_client.config.set('PRIVATE', 'offset', [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], telem.z], write=True) logger.info("Set z offset to {:.2f}".format(client.active_client.config.private_offset[2])) @@ -421,7 +421,7 @@ def _command_set_z(*args, **kwargs): @messaging.message_callback("reset_z_offset") def _command_reset_z(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', + client.active_client.config.set('PRIVATE', 'offset', [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], 0], write=True) logger.info("Reset z offset to {:.2f}".format(client.active_client.config.private_offset[2])) diff --git a/Drone/mavros_mavlink.py b/Drone/mavros_mavlink.py index b4602c4..90ee00c 100644 --- a/Drone/mavros_mavlink.py +++ b/Drone/mavros_mavlink.py @@ -63,7 +63,7 @@ def calibrate(sensor): return False # Make calibration message calibration_message = calibration_msg(sensor) - # Send mavlink calibration command + # Send mavlink calibration command send_command_long(False, mavlink.MAV_CMD_PREFLIGHT_CALIBRATION, 0, *calibration_message) rospy.loginfo('Send {} calibration message'.format(sensor)) # Wait until system status to uninit (during calibration on px4) @@ -87,7 +87,7 @@ def get_calibration_status(): if mag_status.value.integer == 0 and mag_status.success: status_text += "mag: uncalibrated; " if acc_status.value.integer == 0 and acc_status.success: - status_text += "acc: uncalibrated; " + status_text += "acc: uncalibrated; " if status_text == "": if not gyro_status.success or not mag_status.success or not acc_status.success: status_text = "NO_INFO" diff --git a/Drone/tasking_lib.py b/Drone/tasking_lib.py index 1827c3c..c64dbcb 100644 --- a/Drone/tasking_lib.py +++ b/Drone/tasking_lib.py @@ -54,7 +54,7 @@ class TaskManager(object): self._wait_interrupt_event.set() self._running_event.clear() - task = Task(task_function, task_args, task_kwargs, task_delayable) + task = Task(task_function, task_args, task_kwargs, task_delayable) count = next(self._counter) entry = (timestamp, priority, count, task) @@ -64,21 +64,21 @@ class TaskManager(object): entry_old = self.task_queue[0] else: entry_old = entry - + heapq.heappush(self.task_queue, entry) if self.task_queue[0] != entry_old: self._task_interrupt_event.set() #print("Task queue updated with more priority task") - + if self._reset_event.is_set(): self._task_interrupt_event.set() self._reset_event.clear() #print("Task queue updated after reset") - + self._wait_interrupt_event.clear() self._running_event.set() - + # #print(self.task_queue) def pop_task(self): @@ -89,7 +89,7 @@ class TaskManager(object): def get_last_task_name(self): return self._last_task - + def get_current_task(self): try: start_time, priority, count, task = self.task_queue[0] @@ -134,7 +134,7 @@ class TaskManager(object): if self.task_queue: next_task_time = self.task_queue[0][0] if time_to_start_next_task > next_task_time: - self._timeshift = time_to_start_next_task - next_task_time + self._timeshift = time_to_start_next_task - next_task_time self._wait_interrupt_event.clear() self._task_interrupt_event.clear() self._running_event.set() @@ -178,7 +178,7 @@ class TaskManager(object): #print("Interrupter is set: {}".format(self._task_interrupt_event.is_set())) try: task.func(*task.args, interrupter=self._task_interrupt_event, **task.kwargs) - + except Exception as e: logger.error("Error '{}' occurred in task {}".format(e, task)) #print("Error '{}' occurred in task {}".format(e, task)) @@ -211,10 +211,10 @@ class TaskManager(object): #try: #print("Pop {} function!".format(task.func.__name__)) #except Exception as e: - #print("Pop something!") + #print("Pop something!") if self._task_interrupt_event.is_set(): - self._task_interrupt_event.clear() + self._task_interrupt_event.clear() logger.info("Execution done") #print("Execution done") diff --git a/Drone/visual_pose_watchdog.py b/Drone/visual_pose_watchdog.py index 7f62957..d36aba2 100644 --- a/Drone/visual_pose_watchdog.py +++ b/Drone/visual_pose_watchdog.py @@ -144,7 +144,7 @@ def laser_callback(data): laser_range = data.range def emergency_land(disarm_if_timeout = True): - global emergency_land_thrust, laser_range + global emergency_land_thrust, laser_range current_thrust = emergency_land_thrust action_timestamp = time.time() while armed: @@ -153,13 +153,13 @@ def emergency_land(disarm_if_timeout = True): try: set_attitude(thrust = current_thrust, yaw = 0, frame_id = 'body', auto_arm = True) except rospy.ServiceException as e: - logger.info(e) + logger.info(e) delta = time.time() - action_timestamp if delta > timeout_to_disarm and disarm_if_timeout: try: arming(False) except rospy.ServiceException as e: - logger.info(e) + logger.info(e) if (laser_range < 0.1 or delta > emergency_land_decrease_thrust_after) and current_thrust >= 0.: current_thrust -= 0.02 if current_thrust <= 0.03: @@ -167,7 +167,7 @@ def emergency_land(disarm_if_timeout = True): try: arming(False) except rospy.ServiceException as e: - logger.info(e) + logger.info(e) rate.sleep() def emergency_land_service(request): @@ -181,7 +181,7 @@ def emergency_land_service(request): responce.success = False responce.message = "Copter is disarmed, no need for emergency landing!" emergency_land_called = False - return responce + return responce def watchdog_callback(event): global visual_pose_last_timestamp, armed, mode, watchdog_action, laser_range @@ -224,7 +224,7 @@ def watchdog_callback(event): try: arming(False) except rospy.ServiceException as e: - logger.info(e) + logger.info(e) rate.sleep() elif watchdog_action == 'disarm': logger.info('Visual pose data is too old, copter is armed, disarming...') @@ -232,15 +232,15 @@ def watchdog_callback(event): try: arming(False) except rospy.ServiceException as e: - logger.info(e) - rate.sleep() + logger.info(e) + rate.sleep() elif watchdog_action == 'emergency_land': if visual_pose_dt > visual_pose_timeout: logger.info('Visual pose data is too old, copter is armed, emergency landing...') if pos_delta > pos_delta_max: logger.info('Position delta is {} m, copter is armed, emergency landing...'.format(pos_delta)) emergency_land() - logger.info('Disarmed') + logger.info('Disarmed') emergency = False if emergency_land_called: emergency = True From d81d2800df7607593c0fd31192d6ec1002d1cb9a Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 7 Apr 2020 11:23:14 +0300 Subject: [PATCH 5/7] Server: Add clever_dir parameter --- Server/config/spec/configspec_server.ini | 3 +++ Server/server_qt.py | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Server/config/spec/configspec_server.ini b/Server/config/spec/configspec_server.ini index 13e6199..84c8c12 100644 --- a/Server/config/spec/configspec_server.ini +++ b/Server/config/spec/configspec_server.ini @@ -5,6 +5,9 @@ config_version = float(default='1.0') port = integer(default=25000) buffer_size = integer(default=1024) +[CLIENT] + clever_dir = string(default=/home/pi/catkin_ws/src/clever/clover) + [TABLE] # True -> clients are removed on disconnection # False -> disconnected clients indicated diff --git a/Server/server_qt.py b/Server/server_qt.py index 7c033ab..ff26e59 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -438,7 +438,7 @@ class MainWindow(QtWidgets.QMainWindow): def request_any_file(self, client_path=None, copters=None): if client_path is None: _client_path, ok = QInputDialog.getText(self, "Enter path of file to request from client", "Source:", - QLineEdit.Normal, "/home/pi/") + QLineEdit.Normal, "") if not ok: return client_path = _client_path @@ -467,7 +467,7 @@ class MainWindow(QtWidgets.QMainWindow): return c_path, ok = QInputDialog.getText(self, "Enter path (and name) to send on client", "Destination:", - QLineEdit.Normal, "/home/pi/") # TODO config? + QLineEdit.Normal, "") # TODO config? if not ok: return @@ -483,7 +483,7 @@ class MainWindow(QtWidgets.QMainWindow): @pyqtSlot() def send_calibrations(self): self.send_directory_files("Select directory with calibrations", ('.yaml', ), match_id=True, - client_path="/home/pi/catkin_ws/src/clever/clever/camera_info/", + client_path=os.path.join(self.server.config.client_clever_dir,"camera_info/"), client_filename="calibration.yaml") # TODO callback to reload clever? # from os.path import expanduser # TODO on client @@ -495,13 +495,13 @@ class MainWindow(QtWidgets.QMainWindow): copter.client.send_message("service_restart", kwargs={"name": "clever"}) self.send_files("Select aruco map configuration file", "Aruco map files (*.txt)", onefile=True, - client_path="/home/pi/catkin_ws/src/clever/aruco_pose/map/", + client_path=os.path.abspath(os.path.join(self.server.config.client_clever_dir,"../aruco_pose/map/")), client_filename="animation_map.txt", callback=callback) @pyqtSlot() def send_launch(self): self.send_directory_files("Select directory with launch files", ('.launch', '.yaml'), match_id=False, - client_path='/home/pi/catkin_ws/src/clever/clever/launch/') # TODO clever restart callback? + client_path=os.path.join(self.server.config.client_clever_dir,"launch/")) # TODO clever restart callback? @pyqtSlot() def send_fcu_parameters(self): @@ -652,7 +652,7 @@ if __name__ == "__main__": logging.StreamHandler(), msgbox_handler ]) - + sys.excepthook = except_hook # for debugging (exceptions traceback) app = QApplication(sys.argv) From bea1f013c3e12c3325d36a5adc73a69c4450432c Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 7 Apr 2020 12:12:43 +0300 Subject: [PATCH 6/7] Server: Fix playing music after 0 seconds time --- Server/server_qt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Server/server_qt.py b/Server/server_qt.py index ff26e59..2c315a8 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -310,16 +310,17 @@ class MainWindow(QtWidgets.QMainWindow): @confirmation_required("This operation will takeoff selected copters with delay and start animation. Proceed?") def send_start_time_selected(self): time_now = server.time_now() + time_lag = 0.1 dt = self.ui.start_delay_spin.value() logging.info('Wait {} seconds to start animation'.format(dt)) if self.ui.music_checkbox.isChecked(): music_dt = self.ui.music_delay_spin.value() - asyncio.ensure_future(self.play_music_at_time(music_dt + time_now), loop=loop) + asyncio.ensure_future(self.play_music_at_time(music_dt + time_now + time_lag), loop=loop) logging.info('Wait {} seconds to play music'.format(music_dt)) # This filter constraints takeoff in real world, when copter state was normal and then some checks were failed for a while # for copter in filter(lambda copter: copter.states.all_checks, self.model.user_selected()): for copter in self.model.user_selected(): - server.send_starttime(copter.client, dt + time_now) + server.send_starttime(copter.client, dt + time_now + time_lag) @pyqtSlot() def pause_resume_selected(self): From 3d96569b3cc89d04dee46bed2f410d54a942d33e Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 7 Apr 2020 14:44:53 +0300 Subject: [PATCH 7/7] Docs: Update server.md --- docs/ru/server.md | 68 +++++++---------------------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/docs/ru/server.md b/docs/ru/server.md index 3829c40..1f94914 100644 --- a/docs/ru/server.md +++ b/docs/ru/server.md @@ -7,8 +7,6 @@ * [Настройка](#настройка-сервера) * [Дополнительные операции](#дополнительные-операции) -[TOC] - ## Интерфейс сервера Сервер имеет визуальный графический интерфейс для удобства взаимодействия. @@ -17,7 +15,11 @@ ### Таблица состояния коптеров -При первом подключении клиента к серверу в таблицу добавляется строка для отображения состояния клиента, содержащая только имя клиента (`copter ID`). Если на клиентах настроена автоматическая передача телеметрии, данные в таблице будут обновляться автоматически. Так же возможно запросить телеметрию выбранных клиентов с помощью кнопки [`Preflight check`](#управление) Строки можно сортировать по возрастанию или убыванию значений любого из столбцов, кликнув по его заголовку. +При первом подключении клиента к серверу в таблицу добавляется строка для отображения состояния клиента, содержащая только имя клиента (`copter ID`). Если на клиентах настроена автоматическая передача телеметрии, данные в таблице будут обновляться автоматически. Так же возможно запросить телеметрию выбранных клиентов с помощью кнопки [`Preflight check`](#управление). + +Строки можно сортировать по возрастанию или убыванию значений любого из столбцов, кликнув по его заголовку. + +Столбцы можно менять местами и изменять их ширину: все изменения сохраняются в настройках сервера. Ячейки таблицы подсвечиваются: @@ -177,63 +179,13 @@ ### Файл конфигурации -Конфигурация сервера задаётся в файле [server.ini](../../Server/config/server.ini), имеющем следующий вид по умолчанию: +Конфигурация сервера создаётся согласно [спецификации](../../Server/config/spec/configspec_server.ini), в ней можно посмотреть значения по умолчанию для любого параметра после ключевого слова `default`. Все изменения сохраняются в файл конфигурации `server.ini` в папке `clever-show/Server/config`. -```ini -# This is generated config with default values -# Modify to configure -config_name = server -config_version = 1.0 +Доступно редактирование конфигурации сервера через GUI модуль `Config editor` через меню `Server -> Edit server config`. -[SERVER] - port = 25000 - buffer_size = 1024 +Конфигурация по умолчанию является полностью работоспособной и не требует изменений для быстрого старта клиента. -[TABLE] - # True -> clients are removed on disconnection - # False -> disconnected clients indicated - remove_disconnected = False - [[PRESETS]] - current = DEFAULT - [[[DEFAULT]]] - copter_id = True, 100 - git_version = True, 75 - config_version = True, 140 - animation_id = True, 100 - battery = True, 100 - fcu_status = True, 100 - calibration_status = True, 65 - mode = True, 100 - selfcheck = True, 65 - current_position = True, 250 - start_position = True, 150 - last_task = True, 250 - time_delta = True, 241 - -[CHECKS] - check_git_version = True - check_current_position = True - # in percents; set 0 to disable this check - battery_min = 50.0 - # in meters; set 0 to disable this check - start_pos_delta_max = 1.0 - # in seconds - time_delta_max = 1.0 - -[BROADCAST] - send = True - listen = True - port = 8181 - # delay for message sending in seconds - delay = 5.0 - -[NTP] - use = False - host = ntp1.stratum2.ru - port = 123 -``` - -Данный файл конфигурации автоматически генерируется при первом запуске сервера, если отсутствует файл конфигурации. Пользовательский файл может содержать неполный набор параметров - в этом случае будут использоваться значения по умолчанию для отсутствующих параметров. Конфигурация по умолчанию является полностью работоспособной и не требует изменений для быстрого начала работы системы. +### Описание параметров #### Корневой раздел @@ -309,4 +261,4 @@ config_version = 1.0 ### Column preset editor -... \ No newline at end of file +...