From 2fde814997b1c007dd5eeea1f64d8d04eaf38daa Mon Sep 17 00:00:00 2001 From: dm1sh Date: Sat, 3 Jun 2023 20:50:55 +0300 Subject: [PATCH] merged vova comments --- PyQt_app.ui | 60 ++++---- config.json | 4 +- main.py | 338 ++++++++++++++++++++++++++-------------------- openapi_mock.json | 2 +- 4 files changed, 222 insertions(+), 182 deletions(-) diff --git a/PyQt_app.ui b/PyQt_app.ui index 07ac61c..26b1bab 100644 --- a/PyQt_app.ui +++ b/PyQt_app.ui @@ -42,14 +42,14 @@ - 350 - 0 - 61 - 41 + 340 + 10 + 71 + 21 - Get URL + GET URL @@ -103,7 +103,6 @@ 8 - 50 false @@ -128,7 +127,6 @@ - 50 false @@ -235,7 +233,7 @@ - :/img/off.svg + :/img/off.svg true @@ -254,7 +252,7 @@ - :/img/ON.svg + :/img/ON.svg true @@ -273,7 +271,7 @@ - :/img/led_on.svg + :/img/led_on.svg true @@ -295,7 +293,7 @@ - :/img/led_off.svg + :/img/led_off.svg true @@ -340,7 +338,7 @@ - :/img/led_on.svg + :/img/led_on.svg true @@ -359,7 +357,7 @@ - :/img/led_off.svg + :/img/led_off.svg true @@ -391,7 +389,7 @@ - :/img/led_on.svg + :/img/led_on.svg true @@ -410,7 +408,7 @@ - :/img/led_off.svg + :/img/led_off.svg true @@ -429,7 +427,7 @@ - :/img/off.svg + :/img/off.svg true @@ -448,7 +446,7 @@ - :/img/ON.svg + :/img/ON.svg true @@ -467,7 +465,7 @@ - :/img/off.svg + :/img/off.svg true @@ -486,7 +484,7 @@ - :/img/ON.svg + :/img/ON.svg true @@ -504,7 +502,6 @@ 8 - 50 false @@ -556,7 +553,6 @@ 8 - 50 false @@ -564,7 +560,7 @@ Освещенность - + 550 @@ -592,7 +588,6 @@ 8 - 50 false @@ -612,7 +607,6 @@ 8 - 50 false @@ -847,7 +841,6 @@ - 50 false @@ -908,7 +901,6 @@ 8 - 50 false @@ -968,7 +960,6 @@ Times New Roman 8 - 50 false @@ -1041,14 +1032,14 @@ - 350 - 30 - 61 - 41 + 340 + 40 + 71 + 21 - Post URL + POST URL @@ -1071,13 +1062,14 @@ 0 0 1185 - 18 + 28 - + + diff --git a/config.json b/config.json index 724f788..86444ec 100644 --- a/config.json +++ b/config.json @@ -27,5 +27,5 @@ "leds7": {"red": 0, "green": 0, "blue": 0}, "leds8": {"red": 0, "green": 0, "blue": 0} }, - "defaultUpdateInterval": 2000 -} \ No newline at end of file + "defaultUpdateInterval": 5000 +} diff --git a/main.py b/main.py index 083bdbd..5c334a4 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ import typing from PyQt5 import QtCore, QtGui, QtWidgets #импорт нужный библиотек from PyQt5 import uic from PyQt5.QtGui import QColor, QPalette -from PyQt5.QtGui import QPixmap +from PyQt5.QtGui import QPixmap, QMouseEvent from PyQt5.QtCore import QTimer, QJsonDocument, QUrl, QTime from PyQt5.QtWidgets import * from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply @@ -15,26 +15,15 @@ import os from plot import Plot import Res_rc - -default_led_data = { #список из словарей с начальными параметрами у светодиодов, надо сделать его атрибутом главного класса - "leds1": {"red": 0, "green": 0, "blue": 0}, - "leds2": {"red": 0, "green": 0, "blue": 0}, - "leds3": {"red": 0, "green": 0, "blue": 0}, - "leds4": {"red": 0, "green": 0, "blue": 0}, - "leds5": {"red": 0, "green": 0, "blue": 0}, - "leds6": {"red": 0, "green": 0, "blue": 0}, - "leds7": {"red": 0, "green": 0, "blue": 0}, - "leds8": {"red": 0, "green": 0, "blue": 0}, -} - -RGB=['red', 'green',"blue"] +RGB = ['red', 'green',"blue"] def read_conf() -> dict: - """ Utility for reading, modifying and logging config - открывает файл 'config.json', загружает его содержимое в переменную 'conf' в формате словаря (dictionary) при помощи функции 'json.load()', а затем выводит все ключи словаря 'conf' при помощи цикла 'for'. + открывает файл 'config.json', + загружает его содержимое в переменную 'conf' в формате словаря (dictionary) при помощи функции 'json.load()', + а затем выводит все ключи словаря 'conf' при помощи цикла 'for'. """ # Opening JSON file @@ -42,9 +31,6 @@ def read_conf() -> dict: conf = json.load(f) f.close() - # Overwrite config if want to - # ... # TODO: ничего делать не нужно, просто оставьте этот комментарий в конечном файле (или преведите на русский) - # Config logging print("Found arguments:") for i in conf: @@ -55,7 +41,6 @@ def read_conf() -> dict: class AppWindow(QMainWindow): - """ Main application window class """ @@ -75,11 +60,6 @@ class AppWindow(QMainWindow): # Set window title self.setWindowTitle("Lr4") - # Load RGB leds default values - self.rgb_leds_state = conf["defaultRGBLeds"] - - # TODO: установить цвета RGB светодиодов по умолчанию - # Init request url editors self.ui.lineEdit_URL.setText("http://" + conf['defaultMDNSname'] + conf['defaultPostRoute']) self.ui.lineEdit_request.setText("http://" + conf['defaultMDNSname'] + conf['defaultGetRoute']) @@ -89,50 +69,153 @@ class AppWindow(QMainWindow): getattr(self.ui, f"pushButton_switch_lamp{i}").setCheckable(True) # вкл режим перекл getattr(self.ui, f"pushButton_switch_lamp{i}").setChecked(False) # нач значение getattr(self.ui, f"label_lamp_on{i}").hide() - getattr(self.ui, f"pushButton_switch_lamp{i}").toggled["bool"].connect(lambda val: self.handle_toggle_lamp(f"LED{i}", val)) + getattr(self.ui, f"pushButton_switch_lamp{i}").toggled["bool"].connect(lambda val, i=i: self.handle_toggle_lamp(i, val)) - # TODO: Инициализировать остальные элементы из conf - - # Init request manual triggers + # Setup request manual triggers self.ui.pushButton_send_post.clicked.connect(self.send_message) # привязываем функцию к кнопке Отправить self.ui.pushButton_send_get.clicked.connect(self.get_value_from_macket) # привязываем функцию к кнопке Отправить GET запрос + # For convinience store RGB LED strip in list self.ui.led_array = [getattr(self.ui, f"leds{i}") for i in range(1,9)] - self.led_data=default_led_data + # Store RGB LEDs default values + self.rgb_leds_state = conf["defaultRGBLeds"] + + # Init RGB LEDs as turned off + self.switch_all(False) + + # Setup RGB LED stip click handlers for led in self.ui.led_array: led.mousePressEvent = self.set_color - self.ui.pushButton_leds_on.clicked.connect(lambda: self.switch_all(True))#обработка кнопк включения индикаторов - self.ui.pushButton_leds_off.clicked.connect(lambda: self.switch_all(False))#обработка кнопки выключения индикаторов - self.ui.pushButton_leds_color.clicked.connect(self.set_color_all)#обработка кнопки изменения цвета индикаторов + # Setup handlers for packet RGB LED changing + self.ui.pushButton_leds_on.clicked.connect(lambda: self.switch_all(True)) # обработка кнопк включения индикаторов + self.ui.pushButton_leds_off.clicked.connect(lambda: self.switch_all(False)) # обработка кнопки выключения индикаторов + self.ui.pushButton_leds_color.clicked.connect(lambda: self.set_color_all()) # обработка кнопки изменения цвета индикаторов + + # Init timer and connect data fetching with interval from defaults + self.timer = QTimer(self) + self.timer.setInterval(conf["defaultUpdateInterval"]) + self.timer.timeout.connect(self.get_value_from_macket) + + # Init autoupdate interval input + self.ui.spinBox_autoupdate.setValue(conf["defaultUpdateInterval"] // 1000) + + # Setup autoupdate interval updating on input change + self.ui.spinBox_autoupdate.valueChanged.connect(lambda i: self.timer.setInterval(i * 1000)) + + # Setup autoupdate toggler + self.ui.checkBox_autoupdate.stateChanged.connect(self.handle_toggle_autoupdate) - def handle_toggle_lamp(self, Name: str, checked: bool): - + def handle_toggle_autoupdate(self): """ - Переключение одноцветных лампочек + Toggles timer """ - n = Name[-1] - - # TODO: переписать названия пушей и лейблов - # сделано - if checked: - getattr(self.ui, 'pushButton_switch_lamp' + n).setText("Выкл") - getattr(self.ui, 'label_lamp_on' + n).hide() - getattr(self.ui, 'label_lamp_off' + n).show() - - print("I'm worked too much") # TODO: нужно что-то более осмысленное + if self.ui.checkBox_autoupdate.isChecked(): + interval_s = self.ui.spinBox_autoupdate.value() + self.timer.start(interval_s * 1000) else: - getattr(self.ui, 'label_lamp_off' + n).hide() - getattr(self.ui, 'label_lamp_on' + n).show() - getattr(self.ui, 'pushButton_switch_lamp' + n).setText("Вкл") + self.timer.stop() - print ("I'm worked too") # TODO: нужно что-то более осмысленное + def with_autosend(func): + """ + Decorator for doing automatic post request after function invocation + """ + + def wrapper(self, *args, **kwargs): # the fuction that will be called as handler args - positional arguments, kwargs - named arguments + func(self, *args, **kwargs) # calling actual handler + + if self.ui.checkBox_autoupdate.isChecked(): + self.send_message() + + return wrapper - def collect_lamps_state(self) -> dict: + @with_autosend + def handle_toggle_lamp(self, n: int, checked: bool): + """ + Toggles lamps in interface + """ + + pushButton_switch_lamp = getattr(self.ui, f'pushButton_switch_lamp{n}') + label_lamp_on = getattr(self.ui, f'label_lamp_on{n}') + label_lamp_off = getattr(self.ui, f'label_lamp_off{n}') + + if checked: + pushButton_switch_lamp.setText("Выкл") + pushButton_switch_lamp.setChecked(True) + label_lamp_on.show() + label_lamp_off.hide() + else: + pushButton_switch_lamp.setText("Вкл") + pushButton_switch_lamp.setChecked(False) + label_lamp_on.hide() + label_lamp_off.show() + + + @with_autosend + def switch_all(self, on: bool): + """ + Switches all RGB LEDs in strip on or off + """ + + if on: + for led in self.ui.led_array: + self.paint_led_color(led, QColor(255, 255, 255)) + else: + for led in self.ui.led_array: + self.paint_led_color(led, QColor(0, 0, 0)) + + + @with_autosend + def set_color_all(self): + """ + Sets color using dialog for all RGB LEDs in strip + """ + + color = QColorDialog.getColor() + + if color.isValid(): + palette = QPalette() + palette.setColor(QPalette.Button, color) + self.ui.pushButton_leds_color.setPalette(palette) + + for led in self.ui.led_array: + self.paint_led_color(led, color) + + + @with_autosend + def set_color(self, event: QMouseEvent): + """ + Asks in dialog and updates color of single RGB LED + """ + + sender = QApplication.widgetAt(event.globalPos()) + colors = sender.palette().color(QPalette.Background) + color = QColorDialog.getColor() + if color.isValid(): + self.paint_led_color(sender, color) + else: + self.paint_led_color(sender, QColor(0, 0, 0)) + + def paint_led_color(self, led: QLabel, color: QColor): + """ + Actually sets RGB LED color in interface and state storage + """ + + led.setStyleSheet(f"background-color: rgb({','.join(map(lambda c: str(getattr(color, c)()), RGB))})") + + for c in RGB: + self.rgb_leds_state[led.objectName()][c] = getattr(color, c)() + + + def collect_lamps_state(self) -> dict[str, bool]: + """ + Composes proper object structure with lamps state + """ + lamps_state = {} for i in range(1,4): @@ -140,68 +223,41 @@ class AppWindow(QMainWindow): return lamps_state - # ПРАВКА###### - def switch_all(self, cond: bool): - - if cond: - for led in self.ui.led_array: - led.setStyleSheet(f"background-color: white;") - for s in RGB: - self.led_data[led.objectName()][s] = 255 - - else: - for led in self.ui.led_array: - led.setStyleSheet(f"background-color: black;") - for s in RGB: - self.led_data[led.objectName()][s] = 0 - - def set_color_all(self): #метод изменения цвета индикаторам led - color = QColorDialog.getColor() #получение цвета, который пользователь выберет в спец. окне - if color.isValid(): - palette = QPalette() - palette.setColor(QPalette.Button, color) - self.ui.pushButton_leds_color.setPalette(palette) - for led in self.ui.led_array: - led.setStyleSheet(f"background-color: {color.name()};") - for s in RGB: - self.led_data[led.objectName()][s] = getattr(color, s)() - - - def set_color(self, event): - sender = QApplication.widgetAt(event.globalPos()) - colors = sender.palette().color(QPalette.Background) ##???????? раньше был colors = led.palette().color(QPalette.Background) нужно проверить - color = QColorDialog.getColor() - if color.isValid(): - sender.setStyleSheet(f"background-color: {color.name()};") - for s in RGB: - self.led_data[sender.objectName()][s] = getattr(color, s)() - else: - self.ui.leds.setStyleSheet(" ") -########## - - - def compose_post_json_data(self) -> dict: + def compose_post_json_data(self) -> dict: json_data = {} json_data.update(self.rgb_leds_state) json_data.update(self.collect_lamps_state()) - - # TODO: добавить отправку остальных данных (см. grdive и поля, которые парсит приложение из stand_code/mDNS_ESP8266.ino) return json_data - def log_post_request(self, url, json_data): + def log_post_request(self, url: str, json_data: dict): json_str = json.dumps(json_data, separators=(',', ':')) data_str = 'Я отправляю текст на: ' + url + '\n'+ json_str - self.ui.textEdit_message.setPlainText(data_str) + self.ui.textEdit_message.append(data_str) - def send_message(self): + def with_cancel(reply_name: str): # retuns decorator with argument enclosed + """ + Decorator for cancelling previous request of same type + """ + def inner(func): # function decorator + def wrapper(self): # the fuction that will be called as handler + if hasattr(self, reply_name): + getattr(self, reply_name).abort() # abort currently pending request + + func(self) # calling actual handler + + return wrapper + return inner + + @with_cancel('POST_reply') + def send_message(self): """ POST запрос """ @@ -223,14 +279,14 @@ class AppWindow(QMainWindow): request.setRawHeader(b'Accept', b'text/plain') # Do POST request and store its reply object - self.post_reply = self.nam.post(request, data.toJson()) + self.POST_reply = self.nam.post(request, data.toJson()) # Set callback for request finishing signal - self.post_reply.finished.connect(self.handle_post_reply) + self.POST_reply.finished.connect(self.handle_post_reply) - def get_value_from_macket(self): ###переименовать элементы, которые находятся в for'aх - + @with_cancel('GET_reply') + def get_value_from_macket(self): """ GET запрос """ @@ -239,96 +295,88 @@ class AppWindow(QMainWindow): request = QNetworkRequest(QUrl(url)) - self.get_reply = self.nam.get(request) + self.GET_reply = self.nam.get(request) # Set callback for request finishing signal - self.get_reply.finished.connect(self.handle_get_reply) + self.GET_reply.finished.connect(self.handle_get_reply) # TODO: если кто хочет поупражняться в понимании, что здесь происходит, напишите аннотацию для всех аргументов функции, используя typing.Callable (just google it) def with_err_handling(reply_name: str): # retuns decorator with argument enclosed + """ + Decorator for error and cancellation of request handling + """ + + operation = reply_name.split('_')[0] def inner(func): # function decorator def wrapper(self): # the fuction that will be called as handler - reply = getattr(self, reply_name) # gets actual reply by its name (ex: self.get_reply) + reply = getattr(self, reply_name) # gets actual reply by its name (ex: self.GET_reply) err = reply.error() if err == QNetworkReply.NetworkError.NoError: - self.ui.textEdit_message.append('О, все прошло успешно!') + self.ui.textEdit_message.append(f'О, {operation} запрос прошёл успешно!') func(self) # calling actual handler + elif err == QNetworkReply.NetworkError.OperationCanceledError: + self.ui.textEdit_message.append(f"{operation} запрос был отменён, так как не успел выполниться до нового вызова") else: status_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) - self.ui.textEdit_message.append(f'Ошибка при получении данных: {status_code}') + self.ui.textEdit_message.append(f'Ошибка при {operation} запросе: {status_code}') return wrapper return inner #это не игрок в hearthstone - @with_err_handling('post_reply') + @with_err_handling('POST_reply') def handle_post_reply(self): - res = self.post_reply.readAll().data() + res = self.POST_reply.readAll().data() self.ui.textEdit_message.append(res.decode()) - @with_err_handling('get_reply') + @with_err_handling('GET_reply') def handle_get_reply(self): - res = self.get_reply.readAll().data() + res = self.GET_reply.readAll().data() data = json.loads(res) #функция преобразования данных в объект питон self.ui.textEdit_message.append(json.dumps(data, separators=(',', ':'))) #выводим значение в line_edit - self.ui.textEdit_message.append(str(data["temperature"])) + # Update lamps + for i in range(1,4): + self.handle_toggle_lamp(i, data[f"LED{i}"]) + # convert buttons buttons_status = list() for i in range(1, 4): buttons_status.append(data[f"button{i}State"]) - """" - bs.append(data["button1State"]) - bs.append(data["button2State"]) - bs.append(data["button3State"]) - """ + self.update_buttons(buttons_status) - - self.update_pressure(data["pressure"]) - color_l=("ambient_light", "red_light", "green_light", "blue_light", "lightness","acceleration_x", "acceleration_y", "acceleration_z" ) + # Update LCDs + color_l=("ambient_light", "red_light", "green_light", "blue_light", "lightness","acceleration_x", "acceleration_y", "acceleration_z", "pressure") - # TODO: переименовать lcdNumber_N в lcdNumber_....._light for s in color_l: getattr(self.ui, "lcd_" + s).display(data[s]) - """ - self.ui.lcdNumber_7.display(data["ambient_light"]) - self.ui.lcdNumber_2.display (data["red_light"]) - self.ui.lcdNumber_3.display (data["green_light"]) - self.ui.lcdNumber_4.display (data["blue_light"]) - self.ui.lcdNumber_8.display (data["lightness"]) - self.ui.lcdNumber_5.display(data["acceleration_x"]) - self.ui.lcdNumber_9.display (data["acceleration_y"]) - self.ui.lcdNumber_6.display (data["acceleration_z"]) - """ - - for i in range(1,4): - self.handle_toggle_lamp[f"LED{i}", data[f"LED{i}"]] + # Append dot to plot self.plot.update(data["temperature"]) - def update_buttons(self, bs):#на панели есть 3 тумблера, данный метод осуществляет их обработку - for i in range(1, 4): - button_state = bs[i-1] - if button_state == 'True': - getattr(self.ui, f'label_tumbler_on{i}').show() - getattr(self.ui, f'label_tumbler_off_{i}').hide() + def update_buttons(self, bs: list[bool]): + for i in range(1, 4): + button_on = bs[i-1] + + label_tumbler_on = getattr(self.ui, f'label_tumbler_on{i}') + label_tumbler_off = getattr(self.ui, f'label_tumbler_off{i}') + + if button_on: + label_tumbler_on.show() + label_tumbler_off.hide() else: - getattr(self.ui, f'label_tumbler_on_{i}').hide() - getattr(self.ui, f'label_tumbler_off_{i}').show() - - - def update_pressure(self, p): - self.ui.lcd_pressure.display(p) + label_tumbler_on.hide() + label_tumbler_off.show() diff --git a/openapi_mock.json b/openapi_mock.json index 823a7c1..3f41d86 100644 --- a/openapi_mock.json +++ b/openapi_mock.json @@ -17,7 +17,7 @@ "responses": [ { "uuid": "cead61d4-7745-455c-a3a9-cb8854cefcc2", - "body": "{\n \"LED1\": false,\n \"LED2\": true,\n \"LED3\": false,\n \"button1State\": true,\n \"button2State\": false,\n \"button3State\": true,\n \"temperature\": 25,\n \"pressure\": 100,\n \"ambient_light\": 100,\n \"red_light\": 200,\n \"green_light\": 300,\n \"blue_light\": 400,\n \"lightness\": 500,\n \"acceleration_x\": 0.1,\n \"acceleration_y\": 0.2,\n \"acceleration_z\": 0.3,\n \"leds1\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds2\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds3\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds4\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds5\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds6\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds7\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n },\n \"leds8\": {\n \"red\": 100,\n \"green\": 150,\n \"blue\": 200\n }\n}", + "body": "{\n \"LED1\": {{boolean}},\n \"LED2\": {{boolean}},\n \"LED3\": {{boolean}},\n \"button1State\": {{boolean}},\n \"button2State\": {{boolean}},\n \"button3State\": {{boolean}},\n \"temperature\": {{float 10 30}},\n \"pressure\": {{float 800 1200}},\n \"ambient_light\": {{int 0 255}},\n \"red_light\": {{int 0 255}},\n \"green_light\": {{int 0 255}},\n \"blue_light\": {{int 0 255}},\n \"lightness\": {{int 0 255}},\n \"acceleration_x\": {{float 0 1}},\n \"acceleration_y\": {{float 0 1}},\n \"acceleration_z\": {{float 0 1}},\n {{#repeat 8 comma=true}}\n \"leds{{add @index 1}}\": {\n \"red\": {{int 0 255}},\n \"green\": {{int 0 255}},\n \"blue\": {{int 0 255}}\n },\n {{/repeat}}\n}", "latency": 0, "statusCode": 200, "label": "",