eddy_em: (Default)
eddy_em ([personal profile] eddy_em) wrote2020-11-20 04:45 pm

Сервер для пусироботовских драйверов

Допилил базовую часть сервера. Теперь нужно еще добавить кое-какой небольшой функционал, и можно будет пилить "морду".

Наслушавшись от народа, как USB'шные железяки имеют свойства "отваливаться" (хотя, на БТА уже пару лет работает термомониторинг главного зеркала, таких проблем не было), добавил возможность переподключаться в случае чего (главное — не втыкать в USB других устройств с такими же VID/PID, как у CAN-трансивера; в моем случае это — устройства, являющиеся PL2303 или выдающие себя за них). Если что-то пошло не так, в течение пяти секунд сервер будет пытаться переподключиться. Если же это не удастся, он благополучно сдохнет, и "родительский надзор", заметив это, перезапустит его заново...
Просто сделать сервер для конкретной железяки не хотелось: потому что в этом случае достаточно сложно будет это к другой железке приспособить, да и теряется возможность мониторинга CAN-шины. Поэтому перво-наперво я обеспечил возможность, не мешая основным потокам, мониторить сообщения в "чистом CAN" или CANopen, а также что-нибудь в шину отправлять.
Сообщения передаются в текстовой форме через сокет. Для обеспечения безопасности сокет открыт только локально. А уже "мастер-сервер" будет проверять доступ и т.п. Ну или можно по SSH команды отправлять неткатом.
Как и в случае микроконтроллеров, я заложил возможность работы в консоли нетката, поэтому есть команды help.
Скажем, если просто набрать help, получим список команд. Пока они такие:
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`

Формат передаваемых сообщений простой: первым словом идет имя потока или какое-то ключевое слово (от имени потока отличается наличием знака ">" в конце, поэтому потоки лучше так же не именовать). За ним идет перечисление параметр=значение, разделенные пробелами.
Т.к. клиентов должно быть немного (один-два), то обычный poll() отлично справляется с мониторингом файловых дескрипторов сокетов. Если клиент "отвалился", то на его место в массиве структур поллинга втыкается последний клиент. И так далее...
Чтобы обеспечить гибкость относительно разнообразных железяк, каждым шаговым двигателем управляет один поток. Новый поток нужно "зарегистрировать" командой register. Ее параметры: имя потока (должно быть уникальным, иначе в ответ получи ошибку), идентификатор потока (классический CANbus ID, который поток будет слушать, скажем, если нам нужно подключиться к CANopen устройству с NodeID=10, то ID=0x58A) и роль потока.
Скажем, зарегистрируем два двигателя "x" и "y":
register x 0x58a stepper
OK
x maxspeed=OK
register y 0x58b stepper   
OK
y maxspeed=OK

Сообщение X maxspeed=OK вылезает из-за того, что сразу же после запуска потока соответствующий контроллер инициализируется значением максимальной скорости (потом его можно поменять).
Командой list можно посмотреть, какие потоки запущены:
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

(последнее сообщение, наверное, в дальнейшем уберу).
Чтобы посмотреть, какие роли могут быть назначены потокам, можно дать команду threads:
threads
role> canopen NodeID index subindex [data] - raw CANOpen commands with `index` and `subindex` to `NodeID`
role> emulation (list) - stepper emulation
role> raw ID [DATA] - raw CANbus commands to raw `ID` with `DATA`
role> stepper (list) - simple stepper motor: no limit switches, only goto

(возможно, логичней ее будет в roles переименовать).
canopen дает возможность работать с CAN-шиной в режиме CANopen, посылая туда пакеты и читая, что приходит (контроль отправленных пакетов я пока не добавлял; но можно и это сделать). raw дает доступ к "чистой" CAN-шине. Еще надо будет добавить turret и linear — турель и линейную подвижку (с концевиками).
Чтобы посмотреть, какие команды можно послать определенному потоку (они посылаются при помощи mesg имя сообщение), посылаем ему команду help:
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

Скажем, нужна нам полная информация о шаговике, отправляем команду info:
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

Хотим переехать в абсолютное положение, пожалуйста:
mesg x absmove 3200
OK
x abssteps=OK
mesg x status
OK
x devstatus=0
x curpos=3200
x errstatus=0

Если во время движения попробовать дать новую команду, придет сообщение об ошибке, например:
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'

Здесь их два, т.к. команда relmove сначала пытается задать направление движения, а затем — указать, сколько шагов надо проехать.
Все вот эти "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

x abssteps=OK
x rotdir=OK
x relsteps=OK

Так как в С нет ООПщины, а все эти турели и т.п. железки по сути — наследники "простого шаговика", то анализ команды пользователя происходит постепенно: сначала она прогоняется через "базовый анализатор", а если тот ничего не нашел, то анализируется уже по специфичным командам. Например, если добавить концевики, получим турель для одного концевика или линейную подвижку для двух (в принципе, у линейной тоже может быть лишь один концевик, но ее положение задается отсчетами шагов, а у турели — номером позиции, т.е. понадобятся дополнительные команды).