Удалённое оборудование¶
В основе диспетчера использован обмен переменными по сети между расчётными задачами. Используя тот же принцип, можно обмениваться данными с реальным оборудованием, как будто это задача, работающая в реальном времени.
Разберём этот случай на примере разработанного в аэрокосмическом университете стенда для обучения проектированию систем ориентации космических аппаратов.
По этой теме была написана статья в журнале «Приборы и техника эксперимента»; она находится в репозитории, для детальной информации об этом стенде, пожалуйста, прочитайте её.
В статье описан простой и недорогой, лёгкий в повторении и работе стенд для удалённого обучения проектированию систем ориентации космических аппаратов. Стенд построен на микрокомпьютере Raspberry Pi, датчиковый состав стенда аналогичен аппарату формата Кубсат. Динамические параметры могут в некоторых пределах варьироваться. Алгоритм управления выполняется удалённо на компьютере студента, в среде Python. Возможности стенда позволяют изучить и отладить базовые алгоритмы управления угловой скоростью и положением космического аппарата для одномерного случая.
Структурная схема представлена на рисунке; она полностью повторяет структуру системы управления с обратной связью. Система разделена на две части: клиентскую и серверную.
Первую, ядро системы управления (регулятор, устройство сравнения, наблюдатель и задатчик), должен реализовать студент на своём компьютере в виде управляющей задачи.
Серверная часть, выполняющая команды клиента, содержит объект управления (тело аппарата), исполнительные органы, датчики и управляющий всем этим интерфейсный контроллер. Код сервера представляет собой типовую задачу, просто в коде имеются обращения к реальной аппаратуре.
Датчиковый состав стенда соответствует космическим аппаратам формата Кубсат: гироскоп и акселерометр на 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)},
}
Из особенностей здесь мы видим только жесткое задание адресов в списке задач, все остальное полностью стандартное.
Адреса необходимо указывать, потому что у нас стенд имеет свой собственный адрес - теперь задачи работают на разных машинах.
Примечание
Адреса должны быть доступны между собой.
Например, если мы укажем в задаче, которая расположена на нашем персональном компьютере (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()
Эта задача выполняет две функции:
Она отображает данные измерений гироскопа (угловую скорость), каждую итерацию по времени.
Как строить графики подробно рассмотрено в примере 2.3 (см. задачу tPlot2D.py, я просто взял её за основу).
Производит расчет фактического интервала времени, который получается при обмене данными с удалённой системой.
Смотрите, в классе Controller заводим атрибут Dt (метод Setup) и указываем его при создании экземпляра графика, чтобы тот его отрисовывал.
Затем в методе Run берётся текущий момент времени, рассчитывается Dt и отправляется на график.
Время начала эксперимента t0 запоминается при инициализации задачи, а затем от него рассчитывается разница между текущим и начальным временем.
Также обратите внимание, что при создании объекта задачи (model) указано, что задача работает в реальном времени (isRealTime=True). При этом диспетчер привязывает модельное время к реальному времени.
Пример графика приведён ниже. Видим, что шаг в реальном времени примерно 0,1 сек. В текущей версии программы меньше этого интервала мне получить не удалось. И это время получено при обмене в локальной сети, при обмене через интернет оно получалось порядка 0,25 с.
Задача на удалённой системе¶
Код:
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 мы видим, что считываются показания с датчиков (гироскоп, акселерометр и оба магнитометра) и записываются в соответствующие атрибуты нашего класса, которые, как настроено в списке задач, передадутся потом основной задачи при синхронизации БД.
Симулятор удалённой системы¶
Предыдущий пример вы не сможете запустить, если у вас не будет включенного стенда. Можно конечно написать мне на емейл, и я его включу чтобы вы могли попробовать. Но проще всего использовать симулятор - задачу, которая симулирует поведение удалённой системы - стенда.
Описание - в работе..