\documentclass[a4paper,12pt]{extarticle}
\usepackage{/home/eddy/ed}
\usepackage{afterpage}
\usepackage{longtable}

\def\t#1{\texttt{#1}}
\def\look#1{(см.\,стр.\,\pageref{#1}, п.\,\ref{#1})}
\nocolon

\author{Емельянов Э.~В. \and Фатхуллин Т.~А.}
\title{Система управления оптоволоконным спектрографом 1-м телескопа}
\begin{document}
\maketitle\tableofcontents
\section{Описание}
Основой системы управления прибором является мини-компьютер под управлением ОС Gentoo Linux, расположенного
на подвесной части. В подвесной части расположены три шаговых двигателя (управляются по CAN-шине): управление
позиционированием волокна и подфокусировкой, а также камера подсмотра волокна. В стационарной части размещены
система калибровочной засветки (может управляться как отдельно по USB, так и с общего интерфейса по CAN-шине),
управление кросс-дисперсором и фокусировкой камеры.

Система управления состоит из трех демонов: \t{canserver}~-- работа с устройствами на CAN-шине через USB-CAN
переходник, \t{loccorr}~-- анализ изображений и управление процессом коррекции положения объекта на волокне и
\t{spec\_server}~-- сервер веб-интерфейса системы управления.

\section{Базовые демоны системы управления}
\subsection{Демон \t{loccorr}}
\subsubsection{Описание}
Данный демон\footnote{\url{https://github.com/eddyem/astrovideoguide_v3/tree/main/LocCorr}} предназначен для анализа
изображений, определения координат центроидов и выдачи управляющих сигналов на корректирующую аппаратуру.

Для сборки утилиты используется \t{cmake}: в рабочей директории проекта создается поддиректория, далее в ней
необходимо запустить \t{cmake ..}, после чего~-- \t{make} и \t{su -c "make install"}. Утилита имеет следующие зависимости:
\begin{description}
\item[libusefull\_macros] библиотека\footnote{\url{https://github.com/eddyem/snippets\_library}} с функциями парсинга
командной строки, отладочными макросами, логгированием и т.п.;
\item[libcfitsio] для работы с FITS-файлами;
\item[libflycapture и libflycapture-c] поддержка светоприемников Grasshopper;
\item[набор библиотек pylonbase] поддержка светоприемников Basler.
\end{description}

В качестве входных изображений могут быть файлы формата FITS, JPEG или PNG (посредством \t{inotify} производится
мониторинг обновления одного файла либо появления новых файлов в указанной директории), CMOS-светоприемники
Basler или Grasshopper. Возможно добавление других источников, для этого необходимо в директории проекта создать
заголовочный файл и файл исходных текстов наподобие \t{basler.h} и \t{basler.c}, где будут реализованы обработчики
функций структуры \t{camera} (файл \t{cameracapture.c}):
\begin{description}
    \item[disconnect] отключить камеру;
    \item[connect] подключить камеру и произвести базовые настройки;
    \item[capture] захватить изображение (возвращается указатель на структуру \t{Image}, выделенную в этой функции);
    \item[setbrightness] установить настройку <<яркость>> (если таковая имеется);
    \item[setext] установить экспозицию (в миллисекундах);
    \item[setgain] установить усиление (в дБ, если таковое имеется);
    \item[getmaxgain] получить предельное значение усиления  (если таковое имеется);
    \item[setgeometry] установить геометрию кадра (ширина, высота, смещение по X и Y);
    \item[getgeomlimits] получить предельные значения геометрии и шаг изменения.
\end{description}

Алгоритм обработки изображений следующий. После считывания очередного изображения опционально выполняется
его медианная фильтрация. Далее строится гистограмма, по которой (в точке перегиба после первого максимума)
определяется уровень фона. Изображение бинаризуется по найденному фону. Производится заданное количество
эрозий и дилатаций для очистки шума. Находятся и нумеруются 4-связные области. Каждая область проверяется на
соотношение сторон описывающего прямоугольника и занимаемую площадь (они должны лежать в заданных рамках).
Если после этого все еще обнаружено больше одного объекта, объекты сортируются по яркости либо близости к
координатам оптического волокна. После обработки определенного количества изображений выполняется вычисление
средних координат центроида звезды. Если среднеквадратичное отклонение не превышает пяти пикселей, определен
модуль коррекции и его состояние позволяет выполнять коррекцию, посылаются команды коррекции положения.

Работа модуля коррекции (на основе контроллеров шаговых двигателей от фирмы PusiRobo) описана в \t{pusirobo.c}.
Принимаемые команды передаются через открытый сокет демону \t{canserver}~\look{canserverdaemon}.
Возможно подключение любого другого модуля коррекции, для этого необходимо реализовать описанные в \t{improc.h}
поля структуры \t{steppersproc}:
\begin{description}
    \item[proc\_corr] выполнить коррекцию положения звезды на заданное количество пикселей;
    \item[stepstatus] получить состояние шаговых двигателей;
    \item[setstepstatus] установить новое состояние;
    \item[movefocus] переместить фокусер на заданное количество шагов;
    \item[moveByU] переместить корректор на заданное количество шагов по оси U;
    \item[moveByV] переместить корректор на заданное количество шагов по оси V;
    \item[relay] выполнить команду на стационарной части (включить\slash выключить реле, установить значение ШИМ,
                    считать состояние кнопок и т.п.);
    \item[stepdisconnect] отключиться от устройства коррекции.
\end{description}

\subsubsection{Аргументы командной строки демона}
\begin{description}
    \item[-A, --maxarea=arg]
максимальная площадь (в пикселях) объекта (по умолчанию~150000);
    \item[-C, --canport=arg]
порт локального сервера \t{canserver} (по умолчанию~4444);
    \item[-D, --ndilat=arg]
количество дилатаций при обработке изображения (по умолчанию~2);
    \item[-E, --neros=arg]
количество эрозий при обработке изображения (по умолчанию~2);
    \item[-H, --height=arg]
высота рабочего участка изображения;
    \item[-I, --minarea=arg]
минимальная площадь (в пикселях) объекта (по умолчанию~400);
    \item[-L, --logXY=arg]
файл для логгирования вычисленных координат центроидов;
    \item[-N, --naverage=arg]
количество изображений для вычисления средних координат центроида (от~1 до~25);
    \item[-P, --pidfile=arg]
файл с PID сервера (по умолчанию \t{/tmp/loccorr.pid});
    \item[-T, --intthres=arg]
порог яркости изображений при сортировке ($(I_1-I_2)/(I_1+I_2)$, по умолчанию~0.01)
    \item[-W, --width=arg]
ширина рабочего участка изображения;
    \item[-X, --xtarget=arg]
координата~$X$ цели (оптоволокна, щели);
    \item[-Y, --ytarget=arg]
координата~$Y$ цели;
    \item[-b, --blackp=arg]
доля пикселей с низкой интенсивностью, которая будет отброшена при эквализации гистограммы
    (если включена эквализация обработанного кадра);
    \item[-c, --confname=arg]
имя файла конфигурации (по умолчанию \t{loccorr.conf});
    \item[-e, --equalize]
выполнять эквализацию обработанного кадра;
    \item[-h, --help]
вызвать данную справку;
    \item[-i, --input=arg]
название объекта для мониторинга новых изображений (имя файла или директории,
                    <<grasshopper>> или <<basler>> при захвате с КМОП-камеры);
    \item[-j, --jpegout=arg]
название файла, куда будет записано обработанное изображение (по умолчанию
                    \t{./outpWcrosses.jpg});
    \item[-l, --logfile=arg]
файл, в который будет вестись логгирование;
    \item[-p, --proc=arg]
имя модуля процессинга коррекций (<<pusirobo>>);
    \item[-v, --verbose]
повысить уровень информативности логгирования (каждый~\t{-v} повышает на~1);
    \item[-x, --xoff=arg]
сдвиг по оси~$X$ рабочего участка изображения;
    \item[-y, --yoff=arg]
сдвиг по оси~$Y$ рабочего участка изображения;
    \item[--ioport=arg]
номер порта сокета для подключения управления (по умолчанию 12345);
    \item[--maxexp=arg]
максимальная экспозиция (в миллисекундах, по умолчанию 500);
    \item[--minexp=arg]
минимальная экспозиция (в миллисекундах, по умолчанию 0.001).
\end{description}

\subsubsection{Конфигурационные параметры}
В файле конфигурации хранятся базовые параметры настроек демона. В случае, если в аргументах командной строки
введены другие значения, нежели в настройках, преимущество будет за первыми. В случае отсутствия и
конфигурационного файла, и аргументов, будут использоваться значения по умолчанию. При завершении работы в
конфигурационный файл сохраняются текущие значения (в т.ч. полученные в результате указания пользователем во
время работы).

Конфигурационный файл может иметь следующую структуру (после поля \t{\#} указан комментарий):
\begin{verbatim}
maxarea = 10000     # максимальная площадь объекта
minarea = 100       # минимальная площадь объекта
minwh = 0.800       # минимальное отношение ширины к высоте объекта
maxwh = 1.300       # максимальное отношение ширины к высоте объекта
ndilat = 3          # количество дилатаций
neros = 3           # количество эрозий
xoffset = 528       # горизонтальное смещение изображения
yoffset = 0         # вертикальное смещение изображения
width = 1000        # ширина изображения
height = 800        # высота изображения
equalize = 1        # эквализация результата
expmethod = 0       # метод вычисления экспозиции (0-авто, 1-вручную)
naverage = 1        # количество усреднений
umax = 24000        # максимальное абсолютное значение отсчетов по U
vmax = 24000        # максимальное абсолютное значение отсчетов по U
focmax = 32000      # макимальное значение отсчетов F
focmin = -32000     # минимальное значение отсчетов F
stpservport = 4444  # порт сервера корректора
Kxu = 71.987        # коэффициенты перевода
Kyu = 54.506        #     координат изображения
Kxv = 22.750        #     в шаги моторов
Kyv = -90.607       #     по осям U и V
xtarget = 1170.900  # координаты центра
ytarget = 480.300   #     цели
eqthrowpart = 0.900 # доля отброшенных пикселей при эквализации
minexp = 50.000     # минимальная экспозиция
maxexp = 1000.000   # максимальная экспозиция
fixedexp = 1000.000 # экспозиция, введенная вручную
intensthres = 0.010 # порог интенсивностей при сортировке
gain = 36.000       # усиление, введенное вручную
brightness = 0.000  # яркость
starssort = 0       # метод сортировки звезд (0-по расстоянию до цели,
                                          1-по интенсивности)
medfilt = 0         # медианная фильтрация
medseed = 3         # радиус медианной фильтрации
fixedbg = 0         # 1-не считать фон автоматически
fbglevel = 100      # установленный вручную уровень фона
\end{verbatim}

\subsubsection{Сетевой протокол}
В целях безопасности сетевое соединение для подключения клиентов открывается исключительно на локальном
компьютере. Для осуществления безопасных удаленных подключений можно выполнить проброс портов посредством
ssh.

<<Общение>> клиента с сервером выполняется по текстовому протоколу. В простейшем случае можно подключиться к
указанному в параметрах командной строки порту при помощи утилиты \t{nc} и вручную передавать команды. Полный
список команд с пояснениями появляется в ответ на запрос \t{help}:
{\small
\begin{verbatim}
maxarea=newval - maximal area (in square pixels) of recognized star image
                      (from 4 to 2.5e+06)
minarea=newval - minimal area (in square pixels) of recognized star image
                      (from 4 to 2.5e+06)
minwh=newval - minimal value of W/H roundness parameter (from 0.3 to 1)
maxwh=newval - maximal value of W/H roundness parameter (from 1 to 3)
ndilat=newval - amount of dilations on binarized image (from 1 to 100)
neros=newval - amount of erosions after dilations (from 1 to 100)
xoffset=newval - X offset of subimage (from 0 to 10000)
yoffset=newval - Y offset of subimage (from 0 to 10000)
width=newval - subimage width (from 0 to 10000)
height=newval - subimage height (from 0 to 10000)
equalize=newval - make histogram equalization (from 0 to 1)
expmethod=newval - exposition method: 0 - auto, 1 - fixed (from 0 to 1)
naverage=newval - calculate mean position by N images (from 1 to 25)
umax=newval - maximal value of steps on U semi-axe (from 100 to 50000)
vmax=newval - maximal value of steps on V semi-axe (from 100 to 50000)
focmax=newval - maximal focus position in microsteps (from 2.22045e-16 to 64000)
focmin=newval - minimal focus position in microsteps (from -64000 to -2.22045e-16)
stpservport=newval - port number of steppers' server (from 0 to 65536)
Kxu=newval - dU = Kxu*dX + Kyu*dY (from -5000 to 5000)
Kyu=newval - dU = Kxu*dX + Kyu*dY (from -5000 to 5000)
Kxv=newval - dV = Kxv*dX + Kyv*dY (from -5000 to 5000)
Kyv=newval - dV = Kxv*dX + Kyv*dY (from -5000 to 5000)
xtarget=newval - X coordinate of target position (from 1 to 10000)
ytarget=newval - Y coordinate of target position (from 1 to 10000)
eqthrowpart=newval - a part of low intensity pixels to throw away when histogram equalized
                      (from 0 to 0.9)
minexp=newval - minimal exposition time (from 0 to 4001)
maxexp=newval - maximal exposition time (from 0 to 4001)
fixedexp=newval - fixed (in manual mode) exposition time (from 0.1 to 4001)
intensthres=newval - threshold by total object intensity when sorting = |I1-I2|/(I1+I2)
                      (from 4.44089e-16 to 1)
gain=newval - gain value in manual mode (from 0 to 100)
brightness=newval - brightness value (from 0 to 10)
starssort=newval - stars sorting algorithm: by distance from target (0) or by intensity (1)
                      (from 0 to 1)
medfilt=newval - use median filter (from 0 to 1)
medseed=newval - median filter radius (from 1 to 7)
fixedbg=newval - don't calculate background, use fixed value instead (from 0 to 1)
fbglevel=newval - fixed background level (from 0 to 250)
help - List avaiable commands
settings - List current configuration
canbus - Get status of CAN bus server
imdata - Get image data (status, path, FPS, counter)
stpstate=newval - Set given steppers' server state
focus=newval - Move focus to given value
moveU=newval - Relative moving by U axe
moveV=newval - Relative moving by V axe
relay=newval - Send relay commands (Rx=0/1, PWMX=0..255)
\end{verbatim}}

Часть команд является сеттерами различных параметров конфигурации. Формат сеттеров: <<параметр=значение>>.
В случае, если команда--сеттер принята успешно, возвращается ответ~\t{OK} (однако, это не гарантирует ее
выполнения). Если же команда--сеттер имеет неправильное (например, выходящие за допустимые диапазоны) значение
или же неизвестна, возвращается~\t{FAILED}.

Полностью весь список конфигурационных  параметров в формате JSON можно получить по запросу \t{settings}.
Ответом будет строка вида
\begin{verbatim}
{ "messageid": "settings", "maxarea": 10000, "minarea": 100, "minwh": 0.800,
"maxwh": 1.300, "ndilat": 3, "neros": 3, "xoffset": 528, "yoffset": 0, "width":
1000, "height": 800, "equalize": 1, "expmethod": 0, "naverage": 1, "umax":
24000, "vmax": 24000, "focmax": 32000, "focmin": -32000, "stpservport": 4444,
"Kxu": 71.987, "Kyu": 54.506, "Kxv": 22.750, "Kyv": -90.607, "xtarget":
1043.600, "ytarget": 417.100, "eqthrowpart": 0.900, "minexp": 50.000, "maxexp":
1000.000, "fixedexp": 2000.000, "intensthres": 0.010, "gain": 36.000,
"brightness": 0.000, "starssort": 0, "medfilt": 0, "medseed": 3, "fixedbg": 0,
"fbglevel": 100 }
\end{verbatim}

Общим для каждой JSON-строки является параметр \t{messageid}, характеризующий содержащуюся в строке
информацию.

Запрос \t{canbus} возвращает JSON-строку вида:
\begin{verbatim}
{ "messageid": "canbus", "status": "ready", "Umotor": { "status": "stopping",
"position": 0 }, "Vmotor": { "status": "stopping", "position": 0 }, "Fmotor":
{ "status": "stopping", "position": 0 }, "relay": 0, "PWM0": 0, "PWM1": 0, "PWM2":
0, "button0": 0, "button1": 0, "button2": 0, "button3": 0 }
\end{verbatim}
Здесь отображается состояние устройств (\t{status}): \t{disconnected}~-- соединение с сервером отсутствует, \t{ready}~--
пассивное состояние, \t{setup}~-- юстировка осей~$U$ и~$V$, \t{gotomiddle}~-- перемещение подвижки и фокусера в
изначальное положение, \t{findtarget}~-- определение центра волокна (при обратной засветке), \t{fixing}~-- корректор
работает, \t{fixoutofrange}~-- корректор не может работать, т.к. объект вышел за допустимые диапазоны его
перемещения. У состояний \t{setup} и \t{gotomiddle} есть дополнительный параметр~--- текущее действие (например,
\t{waituv0}~-- ожидание перемещения подвижек в крайнее отрицательное положение до упора).
Каждому мотору соответствует свое поле значений: \t{Umotor}, \t{Vmotor} и \t{Fmotor}. Значения этих полей: \t{status}~--
\t{stopping} или \t{moving}; \t{position}~-- текущее положение в шагах. Параметр \t{relay} описывает состояние обоих реле:
0~-- оба отключены, 1~-- включено первое, 2~-- включено второе, 3~-- включены оба.
Параметр \t{PWMx} характеризует заполнение ШИМ канала \t{x} ($x=0\div3$): от~0 (нет сигнала) до~255 (полный сигнал).
Параметры \t{buttonx} ($x=0\div3$) характеризуют состояние соответствующей кнопки: 0~-- события отсутствуют, 1~--
кнопка была нажата в течение более 9~мс, 2~-- кнопка была нажата в течение более 199~мс, 3~-- кнопка была отпущена.

Запрос \t{imdata} возвращает состояние граббера:
\begin{verbatim}
{ "messageid": "imdata", "camstatus": "disconnected", "impath":
"/dev/shm/image.jpg", "imctr": 0, "fps": 0.000, "expmethod": "auto",
"exposition": 100, "gain": 0, "brightness": 0, "xcenter": -1.0, "ycenter": -1.0 }
\end{verbatim}
\t{camstatus} может принимать значения \t{disconnected} (камера отключена), \t{watch directory} или \t{watch file}
(мониторинг файловой системы), \t{connected} (камера подключена).
\t{impath} содержит полный путь к сохраняемому обработанному файлу.
\t{imctr}~-- счетчик отснятых изображений.
\t{fps}~-- среднее количество обработанных кадров в секунду.
\t{expmethod}~-- метод вычисления экспозиции (\t{auto} или \t{manual}).
\t{exposition}~-- длительность последней экспозиции (в миллисекундах).
\t{gain}~-- текущее значение усиления (в дБ).
\t{brightness}~-- текущий уровень яркости.
\t{xcenter} и \t{ycenter}~-- координаты последнего вычисленного центроида (или $-1.0$, если центроид вычислить не
удалось).

Сеттер \t{stpstate} позволяет задать требуемый режим работы: \t{relax} или \t{disconnect}~-- выйти в режим бездействия,
\t{middle}~-- вывести подвижки в среднее положение, \t{setup}~-- начать процесс калибровки, \t{fix}~-- включить режим
коррекции.

Сеттеры \t{focus}, \t{moveU} и \t{moveV} дают возможность перемещать подвижки фокуса и корректора вдоль
соответствующей координаты. Между ними есть разница: \t{focus} принимает \ж абсолютное\н значение (в шагах), а
\t{moveU} и \t{moveV}~--- относительные значения. Если требуемое значение за диапазоном перемещения, будет
возвращено \t{FAILED}.


\subsubsection{Калибровка корректора положения звезды}
Калибровка выполняется автоматически в режиме \t{stpstate=setup}. Для начала ее проведения необходимо установить
все подвижки в среднее положение, навести телескоп на относительно яркую звезду, сфокусироваться телескопом,
установить звезду как можно более близко к метке рабочего оптоволокна и включить процедуру калибровки.
Процедура заключается в следующем:
\begin{enumerate}
    \item обе координаты выставляются в среднее положение;
    \item\label{V0} подвижка по оси~$U$ выставляется в крайнее <<отрицательное>> положение и запоминаются
    усредненные координаты звезды;
    \item\label{Vmax} подвижка по оси~$U$ выставляется в крайнее <<положительное>> положение и запоминаются
    усредненные координаты звезды;
    \item аналогично пунктам~\ref{V0} и~\ref{Vmax} определяются крайние координаты по оси~$V$;
    \item после измерения координат звезды во всех крайних положениях подвижек коррекции вычисляются
    коэффициенты преобразования координат.
\end{enumerate}

\subsubsection{Коэффициенты преобразования координат}
\def\D#1{\Delta{}#1{}}
\def\Vv#1,#2\relax{\begin{pmatrix}#1\\#2\end{pmatrix}}
\def\V#1{\Vv#1\relax}
Система координат корректора, $(U, V)$ априори считается неортогональной. Кроме того, по каждой из осей может быть
свой масштабирующий коэффициент. Для преобразования смещений в координатном пространстве изображения,
$(\D{x}, \D{y})$, в шаги корректора положения, $(\D{u}, \D{v})$, необходимо найти коэффициенты матрицы:
$$\V{\D{u},\D{v}}=
\begin{pmatrix}
K_{xu} & K_{yu}\\
K_{xv} & K_{yv}
\end{pmatrix} \V{\D{x},\D{y}}.
$$

Непосредственно измерить эти коэффициенты нельзя, т.к. при калибровке в процессе экрана получаются другие
коэффициенты:
$$\V{\D{x},\D{y}}=
\begin{pmatrix}
    K_{ux} & K_{vx}\\
    K_{uy} & K_{vy}
\end{pmatrix} \V{\D{u},\D{v}}.
$$

Искомые коэффициенты связаны с данными обратным преобразованием:
$$
\begin{pmatrix}
    K_{xu} & K_{yu}\\
    K_{xv} & K_{yv}
\end{pmatrix} =
\begin{pmatrix}
    K_{ux} & K_{vx}\\
    K_{uy} & K_{vy}
\end{pmatrix}^{-1}.
$$

Введем коэффициенты пропорциональности: $K_U=\frc{\D{u}}{\D{r}}$, $K_V=\frc{\D_{v}}{\D{r}}$, где
$\D{r}=\sqrt{\D{x}^2+\D{y}^2}$. Пусть ось~$U$ наклонена по отношению к оси~$X$ под углом~$\alpha$, а ось~$V$~--- под
углом~$\beta$. В этом случае получим:
$$
\V{\D{x},\D{y}}=
\begin{pmatrix}
    \cos\alpha & \cos\beta\\
    \sin\alpha & \sin\beta
\end{pmatrix} \V{\frc{\D{u}}{K_U},\frc{\D{v}}{K_V}}=
\begin{pmatrix}
    \frc{\cos\alpha}{K_U} & \frc{\cos\beta}{K_V} \\
    \frc{\sin\alpha}{K_U} & \frc{\sin\beta}{K_V}
\end{pmatrix} \V{\D{u},\D{v}}.
$$
Отсюда, обратная матрица
\begin{equation}
\begin{pmatrix}
    K_{xu} & K_{yu}\\
    K_{xv} & K_{yv}
\end{pmatrix} =
\begin{pmatrix}
    \frc{\cos\alpha}{K_U} & \frc{\cos\beta}{K_V} \\
\frc{\sin\alpha}{K_U} & \frc{\sin\beta}{K_V}
\end{pmatrix}^{-1}=
K
\begin{pmatrix}
    K_U\sin\beta    & -K_U\cos\beta \\
    -K_V\sin\alpha & K_V\cos\alpha
\end{pmatrix},
\label{maineq}
\end{equation}
где
$$
K = \frac{1}{\cos\alpha\sin\beta - \sin\alpha\cos\beta}.
$$

Для калибровки выставим корректор в центральное положение, а затем поочередно передвинем его в крайние
положения по обеим осям. Таким образом, смещению по оси~$U$ на $N_U$~шагов будут соответствовать изменения
координат~$\D{y_u}$ и~$\D{x_u}$, а смещению по оси~$V$ на $N_V$~шагов~---~$\D{x_v}$ и~$\D{y_v}$.
Коэффициенты пропорциональности определим как
$$K_U=\frac{N_U}{\D{r_u}}, K_V=\frac{N_V}{\D{r_v}},
\quad\text{где}\quad   \D{r_u}=\sqrt{\D{x_u}^2+\D{y_u}^2}, \D{r_v} = \sqrt{\D{x_v}^2+\D{y_v}^2}.
$$
А тригонометрические функции направляющих углов~--- как
$$
\cos\alpha=\frac{\D{x_u}}{\D{r_u}},\quad \sin\alpha=\frac{\D{y_u}}{\D{r_u}},\qquad
\cos\beta=\frac{\D{x_v}}{\D{r_v}},\quad \sin\beta=\frac{\D{y_v}}{\D{r_v}}.
$$
В результате вычислим искомые коэффициенты по уравнению~\eqref{maineq}.

\subsubsection{Коррекция телескопом}
В случае, если запрос \t{canbus} возвращает состояние \t{fixoutofrange}, необходимо сделать коррекцию телескопом для
возвращения объекта в допустимую область. В данный момент автоматическая коррекция не реализована. Для работы
данного модуля необходимо выполнить процедуру калибровки, аналогичную калибровке локального корректора
положения звезды, чтобы вычислить коэффициенты перехода от экранных координат к экваториальным.

\subsection{Демон \t{canserver}}\label{canserverdaemon}
Обеспечивает работу\footnote{\url{https://github.com/eddyem/pusirobot/tree/master/canserver}} с устройствами,
подключенными по CAN-шине к преобразователю USB--CAN~\look{canusb}.
Устройство преобразователя можно определить по его PID/VID либо файлу устройства. Демон обеспечивает
резервирование: можно подключить два одинаковых устройства преобразователя к общей CAN-шине, вставив их в
разные порты USB. В случае, если базовое устройство перестанет корректно работать, автоматически произойдет
переподключение к резервному.

Компилируется при помощи \t{cmake}. Из внешних зависимостей имеет лишь библиотеку
\t{libusefull\_macros}\footnote{\url{https://github.com/eddyem/snippets\_library}} .

\subsubsection{Аргументы командной строки}

\begin{description}

    \item[-P, --pid=arg] PID устройства преобразователя;
     \item[-V, --vid=arg] VID устройства;
     \item[-e, --echo] включить опцию <<эха>> введенных пользователем команд;
     \item[-i, --device=arg] название файла устройства преобразователя;
     \item[-h, --help] отобразить данную справку;
     \item[-l, --logfile=arg] сохранять логи в указанный файл;
     \item[-p, --port=arg] использовать для подключения указанный сетевой порт;
     \item[-s, --speed=arg] скорость соединения по CAN-шине (в бодах);
     \item[-v, --verbose] повысить уровень подробностей логгирования (каждая \t{-v} повышает уровень на 1);
     \item[--pidfile=arg] имя PID-файла процесса (по умолчанию: \t{/tmp/canserver.pid}).

\end{description}

\subsubsection{Сетевой протокол}
В целях безопасности сетевое соединение для подключения клиентов открывается исключительно на локальном
aкомпьютере. Для осуществления безопасных удаленных подключений можно выполнить проброс портов
посредством~ssh.
Протокол~--- текстовый. Для получения базовой справки можно выполнить запрос \t{help}:
\begin{verbatim}
help> help - show help
help> list - list all threads
help> mesg NAME MESG - send message `MESG` to thread `NAME`
help> register NAME ID ROLE - register new thread with `NAME`, raw receiving `ID`
                    running thread `ROLE`
help> threads - list all possible threads with their message format
help> unregister NAME - kill thread `NAME`
\end{verbatim}

Формат передаваемых сообщений: первым словом идет имя потока или определенное ключевое слово (от имени потока
отличается наличием знака ">" в конце, поэтому не стоит именовать потоки подобным образом). Далее следует
непосредственно команда \t{параметр=значение}. В случае неправильно введенной команды последует ответ сервера:
\t{Wrong command}, если же команда принята, ответом будет~\t{OK} с возможными дальнейшими сообщениями.

Чтобы обеспечить гибкость работы с разнообразными устройствами, подключенными к CAN-шине, каждым устройством
управляет один поток. Новый поток нужно <<зарегистрировать>> командой \t{register}. Ее параметры: имя потока
(должно быть уникальным, иначе в ответ получим ошибку), идентификатор потока (классический CANbus ID, который
поток будет слушать, скажем, если нам нужно подключиться к CANopen устройству с NodeID=10, то ID=0x58A) и роль
потока.

Например, зарегистрируем в системе два двигателя с именами <<x>> и <<y>>:
\begin{verbatim}
register x 0x58a stepper
OK
x maxspeed=OK
register y 0x58b stepper
OK
y maxspeed=OK
\end{verbatim}

После регистрации устройства все принимаемые пакеты с указанным для него идентификатором будут перенаправлены
в поток, обслуживающий это устройство. Соответственно, в зависимости от <<роли>> устройства, поступающие данные
будут обрабатываться и сообщаться клиенту.

Командой \t{list} можно посмотреть, какие потоки запущены:
\begin{verbatim}
list
thread> name='x' role='stepper' ID=0x58A
thread> name='y' role='stepper' ID=0x58B
thread> Send message 'help' to threads marked with (args) to get commands list
\end{verbatim}

Список всех возможных <<ролей>> можно получить при помощи команды \t{threads}:
\begin{verbatim}
role> canopen NodeID index subindex [data] - raw CANOpen commands with `index`
and `subindex` to `NodeID`
role> emulation (args) - stepper emulation
role> raw ID [DATA] - raw CANbus commands to raw `ID` with `DATA`
role> stepper (args) - simple stepper motor: no limit switches, only goto
\end{verbatim}

<<Роль>> \t{canopen} дает возможность работать с CAN-шиной в режиме CANopen, посылая туда пакеты и читая
ответные данные. <<Роль>> \t{raw} дает доступ к <<чистой>> CAN-шине. В случае указания нулевого идентификатора,
будут приниматься все команды, передающиеся по CAN-шине, поэтому удобно использовать \t{raw} с нулевым
идентификатором для полного мониторинга сети.

Команда \t{mesg} позволяет отправлять сообщения указанным потокам.  Чтобы посмотреть, какие команды принимает
конкретный поток, посылаем ему сообщение \t{help}:
\begin{verbatim}
mesg x help
OK
x> COMMAND   NARGS   MEANING
x> stop        0     stop motor and clear errors
x> status      0     get current position and status
x> relmove     1     relative move
x> absmove     1     absolute move to position arg
x> enable      1     enable (!0) or disable (0) motor
x> setzero     0     set current position as zero
x> maxspeed    1     set/get maxspeed (get: arg==0)
x> info        0     get motor information
\end{verbatim}

Например, для получения полной информации о драйвере~<<x>>, отправим команду~\t{info}:
\begin{verbatim}
mesg x info
OK
x errstatus=0
x devstatus=0
x curpos=0
x enable=1
x microsteps=32
x extenable=0
x maxspeed=3200
x maxcurnt=600
x gpioval=8191
x rotdir=1
x relsteps=0
x abssteps=0
\end{verbatim}

Здесь перечислены: флаги ошибок, состояние устройства, текущее положение (в микрошагах), активность обмоток
двигателя, количество микрошагов в одном шаге, останов двигателя по внешнему концевику, максимальная скорость (в
микрошагах в секунду), максимальный ток (в микроамперах), текущее значение на пинах GPIO, направление вращения,
последнее заданное относительное положение (в микрошагах), последнее заданное абсолютное положение (в
микрошагах).

Для перемещения в заданное абсолютное положение дадим команду~\t{absmove}:
\begin{verbatim}
mesg x absmove 3200
OK
x abssteps=OK
mesg x status
OK
x devstatus=0
x curpos=3200
x errstatus=0
\end{verbatim}

В случае ненулевого значения~\r{errstatus}, сбросить флаги ошибок можно командой~\t{stop}.

Если во время движения попробовать дать новую команду движения без команды~\t{stop}, придет сообщение об ошибке:
\begin{verbatim}
mesg x relmove 1000
OK
x abortcode='0x8000022' error='Data cannot be transferred or stored to the
                     application because of the present device state'
x abortcode='0x8000022' error='Data cannot be transferred or stored to the
application because of the present device state'
\end{verbatim}
Т.о., следует проверять все сообщения, т.к. ответ~\t{OK} лишь подтверждает правильность введенной команды и
возможность ее отправки соответствующему устройству.  В конкретном случае сообщений об ошибке два,
т.к.~\t{relmove} сначала пытается задать направление движения, а затем~--- указать, сколько шагов надо проехать.

Ответы~\t{OK} на запросы видит лишь тот клиент, который отправлял данные запросы. Ответы от сервера отправляются
всем подключенным клиентам.


\subsection{Демон \t{spec\_server}}
\input{spec_server.tex}

\subsection{Интерфейс наблюдателя}
\input{webinterface.tex}

\section{Электронные компоненты}
\subsection{Преобразователь USB--CAN}\label{canusb}
Данный преобразователь\footnote{\url{https://github.com/eddyem/stm32samples/tree/master/F0-nolib/usbcan}}
разработан на основе микроконтроллера STM32F042C6T6. При подключении к ПК эмулирует преобразователь
USB--UART PL2303, так что нет необходимости настраивать нестандартное устройство. Кроме того, такой подход
позволяет после конфигурации устройства (например, при помощи \t{stty}) работать с ним напрямую из bash-скриптов
без утилит вроде screen и т.п.
\subsubsection{Протокол последовательного интерфейса}
Используется текстовый протокол с односимвольными командами. При вводе неправильной команды (например, \t{?}),
пользователь получает справку:
\begin{verbatim}
'a' - add ID to ignore list (max 10 IDs)
'b' - reinit CAN with given baudrate
'd' - delete ignore list
'f' - add/delete filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]
'F' - send/clear flood message: F ID byte0 ... byteN
'I' - reinit CAN
'l' - list all active filters
'o' - turn LEDs OFF
'O' - turn LEDs ON
'p' - print ignore buffer
'P' - pause/resume in packets displaying
'R' - software reset
's/S' - send data over CAN: s ID byte0 .. byteN
'T' - get time from start (ms)
\end{verbatim}

По умолчанию устройство работает на скорости 100\,кбод. Если необходима другая скорость CAN, следует сразу после
включения задать ее командой~\t{b}.

Численные данные выводятся в шестнадцатеричном формате, а ввод позволяют делать в двоичном (например,
\t{0b110011}), восьмеричном (например, \t{0123}), десятичном или шестнадцатеричном (например, \t{0xdeadbeef}).

Реализован программный <<черный список>>, позволяющий игнорировать до десяти идентификаторов, а также
аппаратные фильтры STM32. Командой~\t{l} можно получить информацию о всех активных фильтрах. По умолчанию их
два:
\begin{verbatim}
Filter 0, FIFO0 in MASK mode: ID=0x01, MASK=0x01
Filter 1, FIFO1 in MASK mode: ID=0x00, MASK=0x01
\end{verbatim}
Один фильтр размещает пакеты с нечетными идентификаторами в FIFO0, другой размещает четные в FIFO1. Удалив
оба этих фильтра (\t{f 0}, \t{f 1}), можно установить необходимые фильтры (на конкретный список идентификаторов или
же по маске). Например, \t{f 0 0 M 0 0x3fc} настраивает фильтр номер~0 на FIFO0 для идентификаторов с~0 по~7 (т.е.
младшие 3 бита), а \t{f 1 1 I 11 15 20 30} настраивает фильтр~1 на FIFO1 для списка идентификаторов.

Команды~\t{o} и~\t{O} позволяют отключать или включать диагностические светодиоды (один светодиод, LED1,
постоянно горит при стабильной связи; второй, LED0, вспыхивает в момент получения сообщений, прошедших
фильтрацию).

При помощи команды~\t{s} можно отправлять данные в шину. После команды указывается идентификатор сообщения за
которым следуют от нуля до восьми байт данных. В случае ошибки выводятся соответствующие сообщения, например,
если на шине нет ни одного устройства, либо настроена неправильная скорость, получим:
\begin{verbatim}
s 123 0b1010
Message parsed OK

Too much errors, restarting CAN!
Receive error counter: 0
Transmit error counter: 96
Last error code: Ack error
Error counter limit
\end{verbatim}

Прошедшие фильтрацию принятые сообщения отображаются в формате \t{\# ID [data]}: после символа решетки следует
идентификатор сообщения, за которым перечисляются данные тела сообщения (если длина данных больше нуля).

Приостановить\slash возобновить отображение пришедших сообщений можно при помощи команды~\t{P}.

Команда~\t{F} позволяет отправлять в сеть каждые 5\,мс заданное сообщение.

\subsection{Модуль управления нагрузкой}
Модуль\footnote{\url{https://github.com/eddyem/stm32samples/tree/master/F0-nolib/usbcan_relay}}
разработан на основе микроконтроллера STM32F042C6T6 и по сути является <<наследником>> преобразователя
USB--CAN. Помимо приема команд по USB или CAN для работы с нагрузкой, модуль может выступать и в роли
преобразователя USB--CAN.

CAN--идентификатор устройства задается переключателями на плате (возможно задать ID от~0 до~255). Скорость
интерфейса конфигурируется USB-командой~\t{C}, по умолчанию составляет 250~кбод.

\subsubsection{Протокол последовательного интерфейса}
\begin{verbatim}
'0' - turn relay0 on(1) or off(0)
'1' - turn relay1 on(1) or off(0)
'a' - add ID to ignore list (max 10 IDs)
'A' - get ADC values @ all 4 channels
'b' - get buttons' state
'C' - reinit CAN with given baudrate
'd' - delete ignore list
'f' - add/delete filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]
'F' - send/clear flood message: F ID byte0 ... byteN
'I' - read CAN ID
'l' - list all active filters
'm' - get MCU temp & Vdd
'o' - turn nth LED OFF
'O' - turn nth LED ON
'p' - print ignore buffer
'P' - pause/resume in packets displaying
'R' - software reset
's/S' - send data over CAN: s ID byte0 .. byteN
'T' - get time from start (ms)
'w' - get PWM settings
'W' - set PWM @nth channel (ch: 0..2, PWM: 0..255)
\end{verbatim}
Команды включения\slash выключения реле: состоят из номера реле и аргумента (0/1), например, \t{0 1} включит реле
номер~0.

Считать информацию со всех четырех каналов (два внешних, температура МК и Vdd) можно при помощи команды~\t{A}.

Команда~\t{b} позволяет получить информацию о состоянии кнопок, ответ выводится в виде человекочитаемой строки,
например: \t{The key 2 is released at 145623} (т.е. в условное время145623\,мс кнопка номер~2 была отпущена).
Нумерация начинается с нуля.

Команда~\t{I} отображает установленный переключателями идентификатор CAN.

При помощи команды~\t{o x} можно отключить питание с внешнего светодиода номер~\t{x} (от~0 до~2). Командой~\t{O x}
можно подать на него питание.

Команда~\t{w} позволяет получить данные ШИМ со всех трех каналов (от~0 до~2), а команда~\t{W x val}~--- установить
на канале с номером~\t{x} значение заполнения ШИМ~\t{val} (от 0~-- сигнал отсутствует до 255~-- максимальный сигнал).

В отличие от преобразователя USB--CAN, данное устройство не позволяет изменять фильтр номер~0: в момент старта
он настраивается на приме сообщений с идентификатором, соответствующим выставленному при помощи
переключателей на плате модуля.

\subsubsection{Протокол интерфейса CAN}
Если устройство получает на свой идентификатор пустое сообщение, оно отправляет его обратно (<<ping>>). У
сообщения с ненулевой длиной анализируется байт 0 пришедших данных (это~--- команда). Команда может быть одна
из:
\begin{verbatim}
typedef enum{
    CAN_CMD_PING,   // ping (without setter)
    CAN_CMD_RELAY,  // relay get/set
    CAN_CMD_PWM,    // PWM get/set
    CAN_CMD_ADC,    // get ADC (without setter)
    CAN_CMD_MCU,    // MCU T and Vdd
    CAN_CMD_LED,    // LEDs get/set
    CAN_CMD_BTNS,   // get Buttons state (without setter)
    CAN_CMD_TMS,    // get time from start (in ms)
    CAN_CMD_ERRCMD, // wrong command
    CAN_CMD_SETFLAG = 0x80 // command is setter
} CAN_commands;
\end{verbatim}

Старший бит команды (\t{CAN\_CMD\_SETFLAG})  является маркером того, что команда~--- сеттер. Без этого маркера
команда рассматривается как геттер. Т.е. номер команды определяется выражением \t{byte[0]\&0x7f}.

Начиная с номера~8 (\t{CAN\_CMD\_ERRCMD})  команда считается ошибочной, в этом случае в сеть отправляется пакет
с длиной, равной единице, где нулевой байт данных содержит код~\t{CAN\_CMD\_ERRCMD}.

Идентификатор ответного сообщения совпадает с идентификатором устройства.

Большинство команд передает данные в формате little endian, кроме состояния АЦП.
\begin{description}
\item[CAN\_CMD\_ADC]~--- получить значение всех каналов АЦП. Поле данных в байтах $1\div6$ содержит смешанные
по 12\,бит показания АЦП (каналы 5\,В, 12\,В и внутренние: температура МК и Vdd).
\item[CAN\_CMD\_BTNS]~--- получить параметры кнопок. В байте \verb'data[1]' указан номер кнопки, \verb'data[2]'~-- ее
состояние (0~-- не нажата, 1~-- была нажата больше 9\,мс и меньше 200\,мс, 2~-- была нажата больше 200\,мс, 3~-- была
отпущена), \verb'data[4..7]' (для выравнивания)~-- время наступления событий в условных мс от запуска МК или
переполнения счетчика времени (\t{uint32\_t}, little endian).
\item[CAN\_CMD\_LED]~--- установить или получить состояние светодиодов. В случае сеттера \verb'data[1]' входящего
потока задает состояние соответствующего светодиода (если n-й бит установлен, светодиод включается, нет~-- гаснет),
в ответе \verb'data[1]' указывает на текущее состояние светодиодов (аналогично сеттеру).
\item[CAN\_CMD\_MCU]~--- температура МК и значение Vdd: в \verb'data[2,3]' содержится температура МК
($T\cdot10\degr{}C$, little endian \t{int16\_t}), в полях \verb'data[4,5]'~--- значение Vdd ($V\cdot100$, little endian \t{uint16\_t}).
\item[CAN\_CMD\_PWM] позволяет задавать и читать заполнение соответствующих ШИМ каналов, поля у сеттера и
геттера аналогичны: \verb'data[1..3]'~--- соответствующее значение заполнения ($0\div255$) ШИМ для каналов $0\div2$.
\item[CAN\_CMD\_RELAY] управляет реле, поля сеттеров и геттеров аналогичны: биты~0 и~1 в \verb'data[1]' отражают
состояние соответствующего реле (1~-- включено, 0~-- выключено).
\item[CAN\_CMD\_TMS] содержит в полях \verb'data[4..7]' время Tms~--- условное время в миллисекундах с момента
запуска микроконтроллера (little endian \t{uint32\_t}).
\end{description}

\section{Запуск утилит в автоматическом режиме}
Управляющий узлами спектрографа мини-ПК установлен на подвесной части. При подключении к новой сети
необходимо сначала сконфигурировать сетевой интерфейс. По умолчанию LAN1 настроен на IP \t{192.168.3.226/23} с
шлюзом \t{192.168.2.11} и DNS \t{192.168.2.111}. Интерфейс LAN2 настроен на \t{10.0.0.1/8} и может использоваться для
диагностических целей. Пароль корневого пользователя: \t{root@spec}, пароль пользователя \t{eddy}, под которым
запускаются сервисы: \t{eddy@spec}.

Для того, чтобы сервисы запускались автоматически, в \t{/etc/local.d} находится скрипт \t{utils.start}:
\begin{verbatim}
#!/bin/sh
CONFDIR="/etc/spectrograph"
LOGDIR="/var/log/spectrograph"
sudo -u eddy /home/eddy/Doc/Pusirobo/canserver/mk/canserver  \
                -l ${LOGDIR}/canserver.log -vv -s 250 2>/dev/null >/dev/null &
sleep 1
sudo -u eddy /home/eddy/Doc/LocCorr_new/mk/loccorr -vv -i basler  \
                -p pusirobo -c ${CONFDIR}/loccorr.conf -L
${LOGDIR}/XY.log -l ${LOGDIR}/loccorr.log -j /dev/shm/image.jpg  \
                2>/dev/null >/dev/null  &
sleep 1
sudo -u eddy /home/timur/PROGRAMS/C++/build-INASAN-SPEC/spec_server  \
                ${CONFDIR}/server-config.dat 2>/dev/null >/dev/null &
\end{verbatim}

Конфигурационные файлы находятся в каталоге \t{/etc/spectrograph}, логгирование производится в каталог
\t{/var/log/spectrograph}. Для ротации логов настроен logrotate. Его конфигурация располагается в файле
\t{/etc/logrotate.d/spectrograph}:
\begin{verbatim}
/var/log/spectrograph/*.log {
    size 1M
    rotate 10
    compress
    notifempty
    missingok
}
\end{verbatim}

\end{document}