Удалённое оборудование ====================== .. _remote: В основе диспетчера использован обмен переменными по сети между расчётными задачами. Используя тот же принцип, можно обмениваться данными с реальным оборудованием, как будто это задача, работающая в реальном времени. Разберём этот случай на примере разработанного в аэрокосмическом университете стенда для обучения проектированию систем ориентации космических аппаратов. По этой теме была написана статья в журнале "Приборы и техника эксперимента"; она находится в `репозитории `_, для детальной информации об этом стенде, пожалуйста, прочитайте её. В статье описан простой и недорогой, лёгкий в повторении и работе стенд для удалённого обучения проектированию систем ориентации космических аппаратов. Стенд построен на микрокомпьютере Raspberry Pi, датчиковый состав стенда аналогичен аппарату формата Кубсат. Динамические параметры могут в некоторых пределах варьироваться. Алгоритм управления выполняется удалённо на компьютере студента, в среде Python. Возможности стенда позволяют изучить и отладить базовые алгоритмы управления угловой скоростью и положением космического аппарата для одномерного случая. Структурная схема представлена на рисунке; она полностью повторяет структуру системы управления с обратной связью. Система разделена на две части: клиентскую и серверную. .. image:: img/TestbedStructure.png :alt: Структурная схема Первую, ядро системы управления (регулятор, устройство сравнения, наблюдатель и задатчик), должен реализовать студент на своём компьютере в виде управляющей задачи. Серверная часть, выполняющая команды клиента, содержит объект управления (тело аппарата), исполнительные органы, датчики и управляющий всем этим интерфейсный контроллер. Код сервера представляет собой типовую задачу, просто в коде имеются обращения к реальной аппаратуре. Датчиковый состав стенда соответствует космическим аппаратам формата Кубсат: гироскоп и акселерометр на MPU-9250, магнитометры AK8963 и HMC5883L (между собой отличаются точностью и уровнем шума). Гироскоп используется для измерения угловой скорости, а магнитометры - для ориентирования относительно магнитного поля Земли. Датчики выдают трехкомпонентные вектора в приборной системе координат, и именно ими мы будем обмениваться между управляющей задачей и задачей работающей на микрокомпьютере стенда. Файл с базой данных (и списком задач) должны быть идентичны на управляющем компьютере и на RaspberryPi: :: class DataBase(): # --- общие переменные --- t = 0. # время dt = 0.1 # шаг задач tmax = 20. # время моделирования cmd = 0 # команда всем задачам u = 1.0 # float [-1..+1] - управление вентиляторами (коэфф. ШИМ) # трехкомпонентные вектора из float w = [0.]*3 # wx, wy, wz [deg/s] a = [0.]*3 # ax, ay, az [g] m = [0.]*3 # mx, my, mz [mT] m2 = [0.]*3 # m2x, m2y, m2z [mT] (QMC5883) # Список задач Tasks = { 'Controller':{'Keys':'t,cmd,dt,u,w,m,m2', 'Addr':('192.168.1.15', 6500)}, 'RemoteSys': {'Keys':'t,cmd,dt,u,w,m,m2', 'Addr':('192.168.1.5', 6523)}, } Из особенностей здесь мы видим только жесткое задание адресов в списке задач, все остальное полностью стандартное. Адреса необходимо указывать, потому что у нас стенд имеет свой собственный адрес - теперь задачи работают на разных машинах. .. note:: Адреса должны быть доступны между собой. Например, если мы укажем в задаче, которая расположена на нашем персональном компьютере (Controller) его локальный адрес 127.0.0.1 (localhost), то возникнет ошибка доступа (OSError: [Errno 22] Invalid argument). Дело в том, что из внутренней сети localhost невозможно найти путь до 192.168.1.5 (см. обсуждение проблемы на `StackOverflow `_). Нужно указывать полные сетевые адреса для всех задач. Понятно, что адреса могут быть и в глобальной сети, но они должны быть жёстко прикреплены к удалённой системе (белый IP). Рассмотрим сначала управляющую задачу. Управляющая задача ~~~~~~~~~~~~~~~~~~ :: import libKernel from libGraph2D import Plot2D import time import db class Controller(libKernel.TaskTemplate): """ управление """ def Setup(self): self.Dt = 0.0 self.plot1 = Plot2D(DB=self) self.plot1.Setup('t', [['w'], ['Dt']]) # ['m', 'm2'], def Initialize(self): self.plot1.Initialize() # инициализируем график self.t0 = time.time() self.tStart = time.time() def Run(self): """ просто отображаем данные датчиков """ t = time.time() self.Dt = t - self.t0 self.plot1.Run() self.t0 = t def Finalize(self): self.plot1.Finalize() print('Общее время работы:', time.time() - self.tStart) #% Главный цикл model = Controller(TaskList=db.Tasks, DB=db.DataBase(), isSheduler=True, isRealTime=True) model.Manager.Loop() Эта задача выполняет две функции: 1. Она отображает данные измерений гироскопа (угловую скорость), каждую итерацию по времени. Как строить графики подробно рассмотрено в примере 2.3 (см. задачу tPlot2D.py, я просто взял её за основу). 2. Производит расчет фактического интервала времени, который получается при обмене данными с удалённой системой. Смотрите, в классе Controller заводим атрибут Dt (метод Setup) и указываем его при создании экземпляра графика, чтобы тот его отрисовывал. Затем в методе Run берётся текущий момент времени, рассчитывается Dt и отправляется на график. Время начала эксперимента t0 запоминается при инициализации задачи, а затем от него рассчитывается разница между текущим и начальным временем. Также обратите внимание, что при создании объекта задачи (model) указано, что задача работает в реальном времени (isRealTime=True). При этом диспетчер привязывает модельное время к реальному времени. Пример графика приведён ниже. Видим, что шаг в реальном времени примерно 0,1 сек. В текущей версии программы меньше этого интервала мне получить не удалось. И это время получено при обмене в локальной сети, при обмене через интернет оно получалось порядка 0,25 с. .. image:: img/Figure_1_Controller.png :alt: График Задача на удалённой системе ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Код: :: import libKernel9 import pigpio from mpu9250 import mpu9250 import qmc5883l as qmc import db clip = lambda n, minn, maxn: max(min(maxn, n), minn) # Fan control pins number (Broadcom) https://pinout.xyz/#s cwPin = 13 # clockwise fan ccPin = 12 # conuterclockwise fan freq = 1000 # frequency, Hz MEG = 1000000 # 1M, for duty cycle class RemoteSys(libKernel9.TaskTemplate): """ Интерфейс к датчикам и вентиляторам стенда """ def Setup(self): self.pi = pigpio.pi() # pi accesses the local Pi's GPIO if not self.pi.connected: print('[!] can\'t connect with pigpio!') exit() self.pi.hardware_PWM(ccPin, freq, 0) # stop fans... self.pi.hardware_PWM(cwPin, freq, 0) self.imu = mpu9250() self.compass = qmc.QMC5883L() def Initialize(self): self.pi.hardware_PWM(ccPin, freq, 0) # stop fans... self.pi.hardware_PWM(cwPin, freq, 0) def Run(self): """ работа с датчиками """ self.pi.hardware_PWM(ccPin, freq, int(MEG*clip(-self.u, 0., 1.))) self.pi.hardware_PWM(cwPin, freq, int(MEG*clip(self.u, 0., 1.))) self.w = list(self.imu.gyro) self.a = list(self.imu.accel) self.m = list(self.imu.mag) self.m2 = list(self.compass.measure()) # m2x, m2y, m2z [mT] (QMC5883) def Finalize(self): self.pi.hardware_PWM(ccPin, freq, 0) # stop fans... self.pi.hardware_PWM(cwPin, freq, 0) #% Главный цикл model = RemoteSys(TaskList=db.Tasks, DB=db.DataBase()) model.Manager.Loop() В этом коде есть несколько моментов, связанных с работой с реальным железом RaspberryPi: 1. Библиотека pigpio, которая работает с дискретными выводами и ШИМ выходами RaspberryPi. 2. Библиотека датчика MPU-9250 (акселерометр, гироскоп, магнитометр) 3. Библиотека работы с магнитометром QMC5883L. В методе Setup создаются объекты, которые работают с датчиками и ШИМ. Соответственно, при инициализации уровень ШИМ сигнала устанавливается равным нулю (чтобы остановить вентиляторы), при финализации происходит тоже самое. В методе Run менеджер данных как обычно устанавливает значение атрибутов, которые пришли при синхронизации базы данных от управляющей задачи. В частности, приходит атрибут u, который задаёт уровень ШИМ-сигнала, который мы отправим на выводы контроллера. В четырех последних строках метода Run мы видим, что считываются показания с датчиков (гироскоп, акселерометр и оба магнитометра) и записываются в соответствующие атрибуты нашего класса, которые, как настроено в списке задач, передадутся потом основной задачи при синхронизации БД. Симулятор удалённой системы ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Предыдущий пример вы не сможете запустить, если у вас не будет включенного стенда. Можно конечно написать мне на емейл, и я его включу чтобы вы могли попробовать. Но проще всего использовать симулятор - задачу, которая симулирует поведение удалённой системы - стенда. Описание - в работе..