1. Завантаження/збереження файлу налаштувань
Ось як завантажити та зберегти налаштування під час запуску та закриття.
-
Параметр файлу налаштувань має бути встановлений у віджеті
ScreenOptions. -
Шлях до файлу налаштувань має бути встановлений у конфігурації INI.
У функції def initialized__(self): додайте:
if self.w.PREFS_: # ім'я змінної (ім'я запису, значення за замовчуванням, тип, ім'я розділу) self.int_value = self.w.PREFS_.getpref('Integer_value', 75, int, 'CUSTOM_FORM_ENTRIES') self.string_value = self.w.PREFS_.getpref('String_value', 'on', str, 'CUSTOM_FORM_ENTRIES')
У функції closing_cleanup__() додайте:
if self.w.PREFS_: # ім'я змінної (ім'я запису, ім'я змінної, тип, ім'я розділу) self.w.PREFS_.putpref('Integer_value', self.integer_value, int, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('String_value', self.string_value, str, 'CUSTOM_FORM_ENTRIES')
2. Використання QSettings для читання/збереження змінних
Ось як завантажувати та зберігати змінні за допомогою функцій QSettings у PyQt:
-
Використовуйте
Групадля упорядкування та унікальності імен. -
_Враховувати значення
none, яке повертається під час читання параметра, який не має запису. -
Встановіть значення за замовчуванням для першого запуску за допомогою синтаксису
або _<значення_за_замовчуванням>_.
|
Note
|
Файл фактично зберігається в ~/.config/QtVcp |
У цьому прикладі:
-
Ми додаємо
or 20таor 2.5як значення за замовчуванням. -
Назви
MyGroupName,int_value,float_value,myIntegerтаmyFloatвизначаються користувачем. -
У функції
def initialized__(self):додайте:# встановити параметри сортування записаних стовпців self.SETTINGS_.beginGroup("MyGroupName") self.int_value = self.SETTINGS_.value('myInteger', type = int) or 20 self.float_value = self.SETTINGS_.value('myFloat', type = float) or 2.5 self.SETTINGS_.endGroup()
-
У функції
def closing_cleanup__(self):додайте:# збереження значень за допомогою QSettings self.SETTINGS_.beginGroup("MyGroupName") self.SETTINGS_.setValue('myInteger', self.int_value) self.SETTINGS_.setValue('myFloat', self.float_value) self.SETTINGS_.endGroup()
3. Додати базовий редактор стилів
Можливість редагувати стиль на екрані, що працює, зручна.
StyleSheetEditor у РОЗДІЛ ІМПОРТ:з qtvcp.widgets.stylesheeteditor імпортувати StyleSheetEditor як SSE
StyleSheetEditor у розділі INSTANTIATE SECTION:STYLEEDITOR = SSE()
прив'язку клавіш у СЕКЦІЇ ІНІЦІАЛІЗАЦІЇ:У функції +__init__.(self, halcomp, widgets, paths):+ додано:
KEYBIND.add_call('Key_F12','on_keycall_F12')
KEYBINDING SECTION:def on_keycall_F12(self,event,state,shift,cntrl): if state: STYLEEDITOR.load_dialog()
4. Запит на запис у діалоговому вікні
QtVCP використовує повідомлення STATUS для спливаючого відображення та повернення інформації з діалогових вікон.
Попередньо створені діалогові вікна відстежують свою останню позицію та включають опції затінення фокуса та звуку.
Щоб отримати інформацію з діалогового вікна, потрібно використовувати повідомлення STATUS general.
Status у IMPORT SECTIONз імпорту qtvcp.core Стан STATUS = Status()
Це завантажує та ініціалізує бібліотеку Status.
STATUS general у розділі INITIALIZE SECTIONУ функції +__init__.(self, halcomp, widgets, paths)+:
STATUS.connect('general',self.return_value)
Це реєструє STATUS для виклику функції self.return_value, коли надсилається загальне повідомлення.
ЗАГАЛЬНІ ФУНКЦІЇdef request_number(self): mess = {'NAME':'ENTRY','ID':'FORM__NUMBER', 'TITLE':'Set Tool Offset'} STATUS.emit('dialog-request', mess)
Функція
-
створює
словникPython за допомогою:-
NAME– потрібно встановити унікальну назву запуску dialogs.
NAMEвстановлює, який діалог запитувати.
ENTRYабоCALCULATORдозволяє вводити числа. -
ID- має бути встановлено на унікальне ім’я, яке надає функція.IDмає бути унікальним ключем. -
TITLEвстановлює заголовок діалогового вікна. -
До
dictможна додавати довільні дані. Діалогове вікно проігнорує їх, але поверне їх до коду повернення.
-
-
Надсилає
dictяк повідомленняdialog-requestSTATUS
CALLBACKS FROM STATUS.# Обробити повідомлення STATUS від set-tool-offset def return_value(self, w, message): num = message.get('RETURN') id_code = bool(message.get('ID') == 'FORM__NUMBER') name = bool(message.get('NAME') == 'ENTRY') if id_code and name and num is not None: print('The {} number from {} was: {}'.format(name, id_code, num))
Це захоплює всі загальні повідомлення, тому необхідно перевірити тип діалогового вікна та ідентифікаційний код, щоб підтвердити, що це наше діалогове вікно. У цьому випадку ми запросили діалогове вікно ENTRY, а наш унікальний ідентифікатор був FORM_NUMBER, тому тепер ми знаємо, що це повідомлення для нас. Діалогові вікна ENTRY або CALCULATOR повертають число з плаваючою комою.
5. Промовте привітання стартапу
Для цього потрібна бібліотека espeak, встановлена в системі.
Status у розділі IMPORTз імпорту qtvcp.core Стан STATUS = Status()
У функції init.(self, halcomp, widgets, paths):
STATUS.emit('play-alert','ГОВОРИТИ Будь ласка, не забудь змастити шляхи.')
SPEAK – це ключове слово: все після нього буде вимовлятися.
6. Функції панелі інструментів
Кнопки панелі інструментів та підменю додано в Qt Designer, але код, який змушує їх виконувати певні дії, додано до файлу обробника. Щоб додати підменю в Qt Designer:
-
Додайте
Qaction, ввівши його в стовпці панелі інструментів, а потім натиснувши значок + праворуч. -
Це додасть підстовпець, у який потрібно ввести ім’я.
-
Тепер оригінальна
QactionбудеQmenu. -
Тепер видаліть
Qaction, який ви додали доQmenu, меню залишиться як меню.
У цьому прикладі ми припускаємо, що ви додали панель інструментів з одним підменю та трьома діями. Ці дії будуть налаштовані для створення:
-
меню вибору нещодавніх файлів,
-
дія спливаючого діалогового вікна «Про нас»,
-
дія виходу з програми та
-
дія функції, визначеної користувачем.
objectName кнопки панелі інструментів використовується для ідентифікації кнопки під час її налаштування - допомога з описовими назвами.
Використовуючи меню редактора дій, клацніть правою кнопкою миші та виберіть «Редагувати».
Відредагуйте назву об’єкта, текст і тип кнопки для відповідної дії.
У цьому прикладі:
-
назва підменю має бути
menuRecent, -
назви дій мають бути
actionAbout,actionQuit,actionMyFunction
toolbar_actions у РОЗДІЛ ІМПОРТУfrom qtvcp.lib.toolbar_actions import ToolBarActions
ToolBarActions у INSTANTIATE LIBRARY SECTIONTOOLBAR = ToolBarActions()
РОЗДІЛІ СПЕЦІАЛЬНИХ ФУНКЦІЙУ функції def initialized__(self) додайте:
TOOLBAR.configure_submenu(self.w.menuRecent, 'recent_submenu') TOOLBAR.configure_action(self.w.actionAbout, 'about') TOOLBAR.configure_action(self.w.actionQuit, 'Quit', lambda d:self.w.close()) TOOLBAR.configure_action(self.w.actionMyFunction, 'My Function', self.my_function)
ЗАГАЛЬНОМУ РОЗДІЛІ ФУНКЦІЙdef my_function(self, widget, state): print('My function State = ()'.format(state))
Функція, яка викликається, якщо натиснуто кнопку дії «Моя функція».
7. Додати HAL-виводи, що викликають функції
Таким чином, вам не потрібно опитувати стан вхідних контактів.
Qhal у РОЗДІЛ ІМПОРТУfrom qtvcp.core import Qhal
Це дозволяє доступ до HAL-компонента QtVCP. Додаткова інформація: Qhal
Функція newPin Qhal повертає об’єкт QPin. Додаткова інформація: QPin
Qhal у РОЗДІЛІ `INSTANTIATE LIBRARYQHAL = Qhal()
У функції initialized__ переконайтеся, що є запис, подібний до цього:
########################################## # Спеціальні функції, що викликаються з QtVCP ########################################## # на цьому етапі: # віджети інстанціюються. # контакти HAL побудовані, але HAL не готовий до роботи def initialized__(self): self.pin_cycle_start_in = QHAL.newPin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN) self.pin_cycle_start_in.pinValueChanged.connect(lambda o,s: self.cycleStart(s))
ЗАГАЛЬНОМУ РОЗДІЛІ ФУНКЦІЙ##################### # загальні функції # ##################### def cycleStart(self, state): if state: tab = self.w.mainTab.currentWidget() if tab in( self.w.tab_auto, self.w.tab_graphics): ACTION.RUN(line=0) elif tab == self.w.tab_files: self.w.filemanager.load() elif tab == self.w.tab_mdi: self.w.mditouchy.run_command()
Ця функція припускає, що існує віджет вкладок з назвою mainTab, який містить вкладки з назвами tab_auto, tab_graphics, tab_filemanager та tab_mdi.
Таким чином, кнопка запуску циклу працює по-різному залежно від того, яка вкладка відображається.
Це спрощено — перевірка стану та перехоплення помилок можуть бути корисними.
8. Безпосереднє читання/запис системних HAL-виводів
Іноді потрібно зчитати системний пін, а створення HAL-піна та підключення до нього вимагає більше роботи, ніж потрібно. Ви можете зчитати його безпосередньо, не підключаючись до нього.
Ось як зчитати вивід, параметр або сигнал:
self.h.hal.get_value('spindle.0.at-speed') # або використовуйте бібліотеку Qhal ось так: QHAL.getValue('spindle.0.at-speed')
Ось як записати на непідключений контакт або некерований сигнал:
self.h.hal.set_p('componentName.pinName','10') self.h.hal.set_s('componentName.signalName','10') # або використовуйте бібліотеку Qhal ось так: QHAL.setPin('componentName.pinName', '10') QHAL.setSignal('componentName.signalName', '10')
Використання self.h.hal або QHAL.hal дозволяє отримати доступ до функцій модуля HAL Python: Інтерфейс Python HAL
9. Додати спеціальний повзунок максимальної швидкості на основі відсотків
Іноді потрібно створити віджет для виконання чогось, чого немає вбудованого. Вбудований повзунок максимальної швидкості діє на одиниці за хвилину, тут ми покажемо, як це зробити на відсотках.
Команда STATUS гарантує, що повзунок змінюватиметься, якщо LinuxCNC змінює поточну максимальну швидкість.
valueChanged.connect() викликає функцію, коли повзунок переміщується.
У Qt Designer додайте віджет QSlider під назвою mvPercent, а потім додайте наступний код до файлу обробника:
############################# # РОЗДІЛ СПЕЦІАЛЬНИХ ФУНКЦІЙ # ############################# def initialized__(self): self.w.mvPercent.setMaximum(100) STATUS.connect('max-velocity-override-changed', \ lambda w, data: self.w.mvPercent.setValue( \ (data / INFO.MAX_TRAJ_VELOCITY)*100 \ ) ) self.w.mvPercent.valueChanged.connect(self.setMVPercentValue) ##################### # ЗАГАЛЬНІ ФУНКЦІЇ # ##################### def setMVPercentValue(self, value): ACTION.SET_MAX_VELOCITY_RATE(INFO.MAX_TRAJ_VELOCITY * (value/100.0))
10. Увімкнення та вимкнення безперервного поштовху
Зазвичай вибір безперервного поштовху – це тимчасова кнопка, яка вимагає від вас вибору попереднього кроку поштовху.
Ми створимо кнопку, яка перемикатиметься між безперервним поштовхом та будь-яким уже вибраним приростом.
У дизайнері Qt:
-
Додати
ActionButtonбез жодної дії -
Назвіть це
btn_toggle_continuous. -
Встановіть властивість
checkableAbstractButtonу значенняTrue. -
Встановіть властивості
incr_imperial_numberтаincr_mm_numberоб’єктаActionButtonна0. -
Використовуйте редактор слотів Qt Designer, щоб за допомогою сигналу кнопки
clicked(bool)викликати функцію обробки формиtoggle_continuous_clicked().
Докладнішу інформацію див. у розділі Використання Qt Designer для додавання слотів.
Потім додайте цей фрагмент коду до файлу обробника у функції initialized__:
# на цьому етапі: # віджети інстанціюються. # контакти HAL побудовані, але HAL не готовий до роботи. def initialized__(self): STATUS.connect('jogincrement-changed', \ lambda w, d, t: self.record_jog_incr(d,t) \ ) # встановити приріст за замовчуванням для повернення назад self.L_incr = 0.01 self.L_text = "0.01in"
У розділі «ЗАГАЛЬНІ ФУНКЦІЇ» додайте:
##################### # ЗАГАЛЬНІ ФУНКЦІЇ # ##################### # якщо це не безперервний рух, запишіть останній крок поштовху # та вимкніть кнопку безперервного руху def record_jog_incr(self,d, t): if d != 0: self.L_incr = d self.L_text = t self.w.btn_toggle_continuous.safecheck(False)
У розділі ЗВОРОТНІ ВИКЛИКИ З ФОРМИ додайте:
####################### # ЗВОРОТНІ ВИКЛИКИ З ФОРМИ # ####################### def toggle_continuous_clicked(self, state): if state: # встановити безперервність (виклик функції кнопки дії) self.w.btn_toggle_continuous.incr_action() else: # скинути раніше записаний приріст ACTION.SET_JOG_INCR(self.L_incr, self.L_text)
11. Патч класу Віджет файлового менеджера
|
Note
|
Класове патчування (monkey patching) трохи нагадує «чорну магію», тому використовуйте його тільки в разі потреби. Основна проблема полягає в тому, що якщо функції бібліотеки віджетів змінюються під час розробки, вони можуть перестати працювати. Віджет «Файловий менеджер» призначений для завантаження вибраної програми в LinuxCNC. Але, можливо, ви хочете спочатку надрукувати ім’я файлу. |
Ми можемо «класовий патч» бібліотеки, щоб перенаправити виклик функції.
Ви можете зробити цей класовий патч всередині або зовні екземпляра HandlerClass.
Це змінить значення, яке представляє «self» у функції.
За межами HanderClass «self» буде виправленим екземпляром класу.
Всередині HanderClass «self» буде екземпляром HandlerClass.
Це змінить функції/змінні, до яких ви можете отримати доступ у функції.
Тут ми показуємо приклад всередині HandlerClass:
У розділі IMPORT SECTION додайте:
з qtvcp.widgets.file_manager імпортувати FileManager як FM
Ось що ми збираємося зробити:
-
Зберегти посилання на оригінальну функцію (1), щоб ми все ще могли її викликати
-
Перенаправте клас на виклик нашої користувацької функції (2) у файлі обробника.
########################################## # Спеціальні функції, що викликаються з QtVCP # ########################################## # Для зміни функцій у віджетах ми можемо використовувати «патч класу». # Патч класу має бути виконаний до створення екземпляра класу. def class_patch__(self): self.old_load = FM.load # зберегти посилання на стару функцію <1> FM.load = self.our_load # функція перенаправлення до нашої функції файлу дескрипторів <2>
-
Напишіть власну функцію, щоб замінити оригінальну:
Ця функція повинна мати таку саму сигнатуру, як і оригінальна функція.
self є екземпляром HandlerClass, а не екземпляром виправленого класу.
У цьому прикладі ми все ще будемо викликати оригінальну функцію, використовуючи посилання на неї, яке ми записали раніше.
Вона вимагає, щоб першим аргументом був екземпляр віджета, який у цьому випадку єself.w.filemanager(ім’я, надане в редакторі Qt Designer).##################### # ЗАГАЛЬНІ ФУНКЦІЇ # ##################### def our_load(self,fname): print(fname) self.old_load(self.w.filemanager,fname)
Тепер наша користувацька функція виведе шлях до файлу в термінал перед завантаженням файлу. Звісно, нудно, але принцип показано.
|
Note
|
Є ще один, дещо інший спосіб, який може мати свої переваги: ви можете зберегти посилання на оригінальну функцію в оригінальному класі. ########################################## # Спеціальні функції, що викликаються з QtVCP ########################################## # Для зміни функцій у віджетах ми можемо використовувати «класовий патч». # Класовий патч повинен бути виконаний до інстанціювання класу. def class_patch__(self): FM.super__load = FM.load # зберегти посилання на стару функцію в оригінальному класі FM.load = self.our_load # перенаправити функцію на нашу функцію обробки файлів ##################### # ЗАГАЛЬНІ ФУНКЦІЇ # ##################### def our_load(self,fname): print(fname) self.w.filemanager.super__load(fname) |
12. Додавання віджетів програмним способом
У деяких ситуаціях можливо додавати віджети лише за допомогою коду Python, а не використовувати редактор Qt Designer.
Під час програмного додавання віджетів QtVCP іноді потрібно виконати додаткові кроки.
Тут ми додамо індикатор швидкості шпинделя та світлодіодний індикатор швидкості до кута віджета вкладки. Qt Designer не підтримує додавання кутових віджетів до вкладок, але PyQt підтримує.
Це скорочений приклад з файлу обробника екрана QtAxis.
Спочатку нам потрібно імпортувати потрібні бібліотеки, якщо вони ще не імпортовані у файлі обробника:
-
QtWidgetsнадає нам доступ доQProgressBar, -
QColorдля LED color, -
StateLEDбібліотека QtVCP, яка використовується для створення світлодіода шпинделя на швидкості, -
Statusвикористовується для отримання інформації про стан LinuxCNC, -
Infoнадає нам інформацію про конфігурацію машини.
############################ # **** РОЗДІЛ ІМПОРТ **** # ############################ from PyQt5 import QtWidgets from PyQt5.QtGui import QColor from qtvcp.widgets.state_led import StateLED as LED from qtvcp.core import Status, Info
Status та InfoSTATUS та INFO ініціалізуються поза класом обробника, щоб бути глобальними посиланнями (без self. попереду):
########################################## # **** розділ створення екземплярів бібліотек **** # ########################################### STATUS = Status() INFO = Info()
STATUSДля індикатора швидкості обертання шпинделя нам потрібно знати поточну швидкість шпинделя. Для цього ми реєструємося за допомогою STATUS, щоб:
-
Зловити сигнал
фактичної-зміни-швидкості-шпинделя -
Виклик функції
self.update_spindle()
######################## # **** ІНІЦІАЛІЗАЦІЯ **** # ######################## # Віджети дозволяють отримати доступ до віджетів із файлів QtVCP. # На цьому етапі віджети та контакти HAL не інстанціюються. def __init__(self,halcomp,widgets,paths): self.hal = halcomp self.w = widgets self.PATHS = paths STATUS.connect('actual-spindle-speed-changed', \ lambda w,speed: self.update_spindle(speed))
Перед тим, як додавати до них нові елементи, потрібно переконатися, що віджети Qt Designer вже створені. Для цього додаємо виклик функції self.make_corner_widgets(), щоб створити додаткові віджети в потрібний момент, тобто в функції initialized__():
########################################## # Спеціальні функції, що викликаються з QtScreen # ########################################## # на цьому етапі: # екземпляри віджетів створено. # піни HAL зібрано, але HAL ще не готовий def initialized__(self): self.make_corner_widgets()
Добре, давайте напишемо функцію для створення віджетів та додамо їх до віджета вкладок. Ми припускаємо, що існує віджет вкладок, створений за допомогою Designer, під назвою rightTab.
Ми припускаємо, що існує віджет вкладок, створений за допомогою Qt Designer, під назвою rightTab.
##################### # загальні функції # ##################### def make_corner_widgets(self): # зробити зелений світлодіод, що показує швидкість шпинделя self.w.led = LED() # <1> self.w.led.setProperty('is_spindle_at_speed_status',True) # <2> self.w.led.setProperty('color',QColor(0,255,0,255)) # <3> self.w.led.hal_init(HAL_NAME = 'spindle_is_at_speed') # <4> # зробити планку швидкості шпинделя self.w.rpm_bar = QtWidgets.QProgressBar() # <5> self.w.rpm_bar.setRange(0, INFO.MAX_SPINDLE_SPEED) # <6> # контейнер w = QtWidgets.QWidget() # <7> w.setContentsMargins(0,0,0,6) w.setMinimumHeight(40) # макет hbox = QtWidgets.QHBoxLayout() # <8> hbox.addWidget(self.w.rpm_bar) # <9> hbox.addWidget(self.w.led) # <9> w.setLayout(hbox) # додати контейнер у кут віджета правої вкладки self.w.rightTab.setCornerWidget(w) # <10>
-
Це ініціалізує базовий віджет StateLed та використовує
self.w.ledяк посилання з цього моменту. -
Оскільки світлодіод стану може використовуватися для багатьох індикацій, ми повинні встановити властивість, яка позначає його як світлодіод, що вказує на швидкість шпинделя.
-
Це робить його зеленим, коли він увімкнений.
-
Це додатковий виклик функції, необхідний для деяких віджетів QtVCP.
ЯкщоHAL_NAMEпропущено, буде використаноobjectNameвіджета, якщо такий є.
Це надає спеціальним віджетам посилання на:-
self.HAL_GCOMP -
екземпляр компонента HAL
-
self.HAL_NAME -
Назва цього віджета у вигляді рядка
-
self.QT_OBJECT_ -
Екземпляр об’єкта PyQt цього віджета
-
self.QTVCP_INSTANCE_ -
Найвищий рівень батьківського елемента екрана
-
self.PATHS_ -
Екземпляр бібліотеки path у QtVCP
-
self.PREFS_ -
екземпляр додаткового файлу налаштувань
-
self.SETTINGS_ -
об’єкт
Qsettings
-
-
Ініціалізує PyQt5
QProgressBar. -
Встановлює максимальний діапазон індикатора прогресу на значення, вказане в
INI. -
Ми створюємо QWidget
Оскільки ви можете додати лише один віджет у кут вкладки, а нам потрібно два, ми повинні додати обидва в контейнер. -
додати QHBoxLayout до QWidget.
-
Потім ми додаємо наш індикатор QProgress та світлодіод до макета.
-
Нарешті, ми додаємо QWidget (з нашою індикатором QProgress та світлодіодом у ньому) до кута віджета вкладки.
STATUSТепер ми створюємо функцію, яка фактично оновлюватиме QProgressBar, коли STATUS оновлює швидкість шпинделя:
######################## # зворотні виклики від STATUS # ######################## def update_spindle(self, data): self.w.rpm_bar.setInvertedAppearance(bool(data<0)) # <1> self.w.rpm_bar.setFormat('{0:d} RPM'.format(int(data))) # <2> self.w.rpm_bar.setValue(abs(data)) # <3>
-
У цьому випадку ми вибрали відображення зліва направо або справа наліво, залежно від того, чи повертаємося ми за годинниковою стрілкою чи проти неї.
-
Це форматує текст у смузі.
-
Це встановлює довжину кольорової смуги.
13. Періодично оновлювати/читати об’єкти
Іноді потрібно оновлювати віджет або регулярно зчитувати значення, яке не охоплюється звичайними бібліотеками.
Тут ми оновлюємо світлодіод на основі спостережуваного виводу HAL кожні 100 мс.
Ми припускаємо, що у файлі інтерфейсу Qt Designer є світлодіод з назвою led.
Qhal для доступу до компонента HAL QtVCPУ розділі «IMPORT SECTION» додайте:
from qtvcp.core import Qhal
QhalУ розділі «INSTANTIATE LIBRARY» додайте:
QHAL = Qhal()
Тепер додайте/змініть ці розділи, щоб включити код, подібний до цього:
CYCLE_TIMEЗазвичай це кожен 100 мс.
######################## # **** ІНІЦІАЛІЗАЦІЯ **** # ######################## # widgets дозволяє доступ до віджетів з файлів QtVCP # на цьому етапі віджети та піни hal не створюютьсяd def __init__(self,halcomp,widgets,paths): self.hal = halcomp self.w = widgets self.PATHS = paths # зареєструвати функцію, яка буде викликатися через період CYCLE_TIME (зазвичай кожні 100 мс) STATUS.connect('periodic', lambda w: self.update_periodic())
##################### # загальні функції # ##################### def update_periodic(self): data = QHAL.getvalue('spindle.0.is-oriented') self.w.led.setState(data)
14. Зовнішнє керування за допомогою ZMQ
QtVCP може автоматично налаштувати обмін повідомленнями ZMQ для надсилання та/або отримання віддалених повідомлень від зовнішніх програм.
Він використовує шаблон обміну повідомленнями публікація/підписка від ZMQ.
Як завжди, подумайте про безпеку, перш ніж дозволяти програмам взаємодіяти через повідомлення.
14.1. Читання повідомлень ZMQ
Іноді хочеться керувати екраном за допомогою окремої програми.
У віджеті ScreenOptions ви можете вибрати властивість use_receive_zmq_option.
Ви також можете встановити цю властивість безпосередньо у файлі обробника, як у цьому прикладі.
Ми припускаємо, що віджет ScreenOptions називається screen_options у Qt Designer:
######################## # **** ІНІЦІАЛІЗАЦІЯ **** # ######################## # widgets дозволяє доступ до віджетів з файлів QtVCP # на цьому етапі віджети та піни hal не створені def __init__(self,halcomp,widgets,paths): # безпосередньо вибрати отримання повідомлень ZMQ self.w.screen_options.setProperty('use_receive_zmq_option',True)
Це дозволяє зовнішній програмі викликати функції у файлі обробника.
Давайте додамо спеціальну функцію для тестування. Вам потрібно буде запустити LinuxCNC з терміналу, щоб побачити надрукований текст.
##################### # загальні функції # ##################### def test_zmq_function(self, arg1, arg2): print('zmq_test_function called: ', arg1, arg2)
Ось приклад зовнішньої програми для виклику функції. Вона щосекунди чергує два набори даних. Запустіть її в окремому терміналі від LinuxCNC, щоб переглянути надіслані повідомлення.
#!/usr/bin/env python3 з імпорту часу сну import zmq import json context = zmq.Context() socket = context.socket(zmq.PUB) socket.bind("tcp://127.0.0.1:5690") topic = b'QtVCP' # попередньо створене повідомлення 1 # створює словник функції для виклику плюс будь-які аргументи x = { # <1> "FUNCTION": "test_zmq_function", "ARGS": [True,200] } # конвертувати в об'єкт JSON m1 = json.dumps(x) # повідомлення попередньої збірки 2 x = { # <1> "FUNCTION": "test_zmq_function", "ARGS": [False,0], } # конвертувати в об'єкт JSON m2 = json.dumps(x) if __name__ == '__main__': while True: print('send message 1') socket.send_multipart([topic, bytes((m1).encode('utf-8'))]) sleep(ms(1000)) print('send message 2') socket.send_multipart([topic, bytes((m2).encode('utf-8'))]) sleep(ms(1000))
-
Встановіть функцію для виклику та аргументи для надсилання цій функції.
Вам потрібно знати сигнатуру функції, яку ви хочете викликати. Також зверніть увагу, що повідомлення перетворюється на об’єкт JSON. Це пов’язано з тим, що ZMQ надсилає байтові повідомлення, а не об’єкти Python. json перетворює об’єкти Python на байти, які будуть перетворені назад після отримання.
14.2. Написання повідомлень ZMQ
Ви також можете спілкуватися із зовнішньою програмою з екрана.
У віджеті ScreenOptions ви можете вибрати властивість use_send_zmq_message. Ви також можете встановити цю властивість безпосередньо у файлі обробника, як у цьому прикладі.
Ми припускаємо, що віджет ScreenOptions називається screen_options у Qt Designer:
######################## # **** ІНІЦІАЛІЗАЦІЯ **** # ######################## # «widgets» дозволяє доступ до віджетів з файлів QtVCP # на цьому етапі віджети та піни hal ще не створені def __init__(self, halcomp,widgets,paths): # безпосередньо вибрати надсилання повідомлення ZMQ self.w.screen_options.setProperty('use_send_zmq_option',True)
Це дозволяє надсилати повідомлення до окремої програми.
Надіслане повідомлення залежатиме від того, чого очікує зовнішня програма.
Додамо спеціальну функцію для тестування.
Щоб побачити надрукований текст, вам потрібно буде запустити LinuxCNC з терміналу.
Також потрібно додати щось для виклику цієї функції, наприклад, клацання кнопки.
##################### # загальні функції # ##################### def send_zmq_message(self): # Це може бути будь-який об'єкт Python, який JSON може конвертувати message = {"name": "John", "age": 30} self.w.screen_options.send_zmq_message(message)
Ось приклад програми, яка отримає повідомлення та виведе його на термінал:
import zmq import json # ZeroMQ Context context = zmq.Context() # Визначте сокет за допомогою "Контексту" sock = context.socket(zmq.SUB) # Визначте підписку та повідомлення з темою для прийняття. topic = "" # всі теми sock.setsockopt_string(zmq.SUBSCRIBE, topic) sock.connect("tcp://127.0.0.1:5690") поки True: topic, message = sock.recv_multipart() print('{} sent message:{}'.format(topic,json.loads(message)))
15. Надсилання повідомлень у рядок стану або діалогові вікна сповіщень на робочому столі
Існує кілька способів повідомити інформацію користувачеві.
Рядок стану використовується для короткої інформації, яка відображається користувачеві.
|
Note
|
Не всі екрани мають рядок стану. |
self.w.statusbar.showMessage(message, timeout * 1000)
timeout вказується в секундах, і ми припускаємо, що statusbar — це ім’я віджета в наборі Qt Designer.
Ви також можете використовувати бібліотеку Status для надсилання повідомлення до бібліотеки notify, якщо вона увімкнена (зазвичай це встановлюється у віджеті ScreenOptions): Це надішле повідомлення до панелі стану та діалогового вікна сповіщень на робочому столі.
Повідомлення також записуються, доки користувач не видалить їх за допомогою елементів керування. Користувачі можуть відновити будь-які записані повідомлення.
Є кілька варіантів:
-
STATUS.TEMPORARY_MESSAGE -
Показувати повідомлення лише короткий час.
-
STATUS.OPERATOR_ERROR -
STATUS.OPERATOR_TEXT -
STATUS.NML_ERROR -
STATUS.NML_TEXT -
STATUS.emit('error', STATUS.OPERATOR_ERROR, 'message')
Ви можете надсилати повідомлення через функції операторських повідомлень LinuxCNC. Зазвичай вони перехоплюються системою сповіщень, тому еквівалентні вищезазначеному. Вони також будуть виведені на термінал.
ACTION.SET_DISPLAY_MESSAGE('MESSAGE') ACTION.SET_ERROR_MESSAGE('MESSAGE')
16. Зміни фокусу
Фокус використовується для спрямування дій користувача, таких як введення з клавіатури, до відповідного віджета.
fwidget = QtWidgets.QApplication.focusWidget() if fwidget is not None: print("focus widget class: {} name: {} ".format(fwidget, fwidget.objectName()))
# на цьому етапі: # екземпляри віджетів створено. # піни HAL зібрано, але HAL ще не готовий def initialized__(self): QtWidgets.QApplication.instance().event_filter.focusIn.connect(self.focusInChanged) ##################### # загальні функції # ##################### def focusInChanged(self, widget): if isinstance(widget.parent(),type(self.w.gcode_editor.editor)): print('G-code Editor') elif isinstance(widget,type(self.w.gcodegraphics)): print('G-code Display') elif isinstance(widget.parent(),type(self.w.mdihistory) ): print('MDI History')
Зверніть увагу, що ми іноді порівнюємо з widget, іноді з widget.parent().
Це тому, що деякі віджети QtVCP побудовані з кількох підвіджетів, і останні фактично отримують фокус; тому нам потрібно перевірити батьківський елемент цих підвіджетів.
В інших випадках фокус отримує головний віджет, наприклад, віджет відображення G-коду можна налаштувати так, щоб він приймав фокус. У цьому випадку в ньому немає підвіджетів, тому порівняння з widget.parent() дасть вам контейнер, що містить віджет G-коду.
17. Параметри часу завантаження командного рядка читання
Деякі панелі потребують інформації під час завантаження для налаштування/опцій. QtVCP задовольняє цю вимогу за допомогою опцій «-o».
Аргумент «-o» підходить для декількох відносно коротких опцій, які можна додати до командного рядка завантаження.
Для більш детальної інформації, ймовірно, краще прочитати файл INI або файл налаштувань.
У командному рядку можна використовувати кілька опцій «-o», тому їх необхідно декодувати.
«self.w.USEROPTIONS_» зберігатиме всі знайдені опції «-o» у вигляді списку рядків. Ви повинні проаналізувати та визначити, що приймається і що з цим робити.
def initialized__(self): # встановити номер камери за замовчуванням number = 0 # перевірити, чи є взагалі якісь опції -o if self.w.USEROPTIONS_ is not None: # якщо в режимі налагодження вивести параметри в термінал LOG.debug('cam_align user options: {}'.format(self.w.USEROPTIONS_)) # go через знайдені варіанти один за одним for num, i in enumerate(self.w.USEROPTIONS_): # якщо параметр -o містить 'size=', припускати, що це ширина та висота вікна # змінити ширину та висоту вікна за замовчуванням if 'size=' in self.w.USEROPTIONS_[num]: try: strg = self.w.USEROPTIONS_[num].strip('size=') arg = strg.split(',') self.w.resize(int(arg[0]),int(arg[1])) except Exception as e: print('Error with cam_align size setting:',self.w.USEROPTIONS_[num]) # # якщо параметр -o містить 'camnumber=', припускати, що це номер камери, який потрібно використовувати elif 'camnumber=' in self.w.USEROPTIONS_[num]: try: number = int(self.w.USEROPTIONS_[num].strip('camnumber=')) except Exception as e: print('Error with cam_align camera selection - not a number - using 0') # встановити номер камери або за замовчуванням, або, якщо опція -o змінила змінну 'number', на цей номер. self.w.camview._camNum = number
18. G-код для зчитування налаштувань Qt
Ось як створити програму O-Word, щоб прочитати запис файлу переваг QtDragon і додавати його як параметр G-коду.
Зкликання цього O-слову оновлює параметр toolToLoad
Це використовує "горячий коментар Python" для спілкування з вбудованим прикладом python.
Перегляньте розділ переробки документів для опису.
(filename myofile.ngc)
o<myofile> sub
;py,from interpreter import *
;py,import os
;py,from qtvcp.lib.preferences import Access
; find and print the preference file path
;py,CONFPATH = os.environ.get('CONFIG_DIR', '/dev/null')
; adjust for your preference file name
;py,PREFFILE = os.path.join(CONFPATH,'qtdragon.pref')
;py,print(PREFFILE)
; get an preference instance
;py,Pref = Access(PREFFILE)
; load a preference and print it
;py,this.params['toolToLoad']=Pref.getpref('Tool to load', 0, int,'CUSTOM_FORM_ENTRIES')
;py,print('Tool to load->:',this.params['toolToLoad'])
; return the value
o<myofile> endsub [#<toolToLoad>]
M2