В этот раз изложу своеобразный «багфикс» для девайса из предыдущей статьи.

Взглянув на получившийся велосипед пришла идея, исправить на сколько это возможно недостатки. А именно:

  • расширить возможности удаленного управления;
  • снизить себестоимость;
  • придумать что-нибудь с передачей видео, что б было хоть немного комфортней работать удаленно.

Сразу скажу с видео особо ничего придумать так и не получилось, придется довольствоваться тем что есть, но не будем о грустном.

И так приступим, с чистыми руками, холодной головой и горячим сердцем. Компоненты кардинально поменялись вместо Raspberry PI будем использовать Orange PI а вместо Arduino UNO только ту самую микросхему(Atmega16u2) о которой шла речь в прошлой статье.

Количество составных частей не поменялось:

1. UVC плата видеозахвата:



2. VGA to AV Конвертер:



Экспериментировал с другим конвертером, вот с этим:



Но чудо не произошло, качество видео не улучшилось, а даже наоборот.

3. Atmega16u2:



4. Orange PI в моем случае модель Orange pi PC:



Пару слов об этом микро компьютере. Основан он на процессоре Allwinner H3 (неплохой дешевый 4х-ядерный процессор на архитектуре АРМ), 1гигабайт оперативной памяти 3 USB, HDMI и так далее, полное описание не сложно найти в интернете.

Подготовительный этап


Устанавливаем операционную систему на Orangepi


Вот ссылка на форум, в этой ветке форума много полезной информации, только конечно все на английском, нас интересует дистрибутив OrangePI-PC_Ubuntu_Vivid_Mate.img, скачать его можно с mega.nz или Google Drive. Находим в шапке строчку Download from Mega or Google Drive переходим по нужной ссылке, качаем.

Процедура переноса образа на флешку такая же как и для распберри, на винде можно воспользоваться win32 disk imager writer на линуксе командой dd.

Небольшое отступление. Orange pi и подобные микрокомпьютеры построенные на китайских процессорах Allwinner довольно сильно уступают распберри не по железу(hardware), а по софту(software), точнее по возможностям и «законченности» ОС, если так можно выразиться. Урезанное ядро, не оптимизированная файловая система, огромное количество багов, это далеко не все проблемы. Такая ситуация возникла из за того что производитель процессоров изначально ориентировался на планшеты(может даже смартфоны) под управлением андроид, и с точки зрения производителя экономической целесообразности в полноценном линукс дистрибутиве видимо не было. Линуксовое ядро было урезано по самые помидоры, из полного жизни превратилось в овоща, способного только на примитивные действия. Долгое время существовали дистрибутивы линукс на ядре от андроида, вероятно эдакий дешевый маркетинговый ход, и так бы оно и продолжалось если б не энтузиасты.

Но вернемся к нашим баранам. Дистрибутив записан, система запущена.

Настраиваем motion


Переходим в консоль. Пользователь orangepi или root пароль orangepi

Обновим пакеты:

sudo apt-get update && sudo apt-get upgrade –y

Устанавливаем моушен


Все настройки как в предыдущей статье:

Фрагмент статьи
sudo apt-get install motion -y

Редактируем конфиг автозапуска:

sudo nano /etc/default/motion

В строке start_motion_daemon ставим ‘yes’. Сохраняем изменения Ctrl + x, y, Enter.

Редактируем конфиг самого motion(а):

sudo nano /etc/motion/motion.conf

Меняем значения параметров как указано далее:

Параметр определяет запуск приложения в качестве службы:

daemon on

Эти параметры определяют разрешение передаваемого изображения, смысла ставить большее разрешение нет, т.к. захват видео ограничен стандартами PAL или SECAM, разрешение коих 720х576. Это кстати досадный недостаток, но об этом позднее.

width 800
height 600

Частота захвата кадров:

framerate 25

Отключаем сохранение скриншотов:

output_normal off

Качество передачи изображения:

webcam_quality 100

Частота передачи кадров:

webcam_maxrate 25

Отмена ограничения на подключение с других ip

webcam_localhost off

Сохраняем изменения Ctrl + x, y, Enter.

Консоль пока не закрываем.

Подготовка к компиляции


Для передачи управления напишем программу, ниже будет все описано подробней, пока же сделаем основные приготовления.

Устанавливаем все необходимое для компиляции, наша программа будет на С:

sudo apt-get install build-essential -y

Устанавливаем библиотеку ncurses, при помощи нее будем захватывать значения клавиш:

sudo apt-get install ncurses -y

Делаем себя владельцем serial порта, чтоб была возможность записи:

sudo chown orangepi /dev/ttyS2

Открываем в редакторе конфиг загрузки модулей ядра:

sudo nano /etc/modules

Раскомментируем строку gpio-sunxi. Этим действием мы активируем gpio, точнее модуль для работы с ним, зачем расскажу позднее.

Редактируем автозапуск:

sudo nano /etc/rc.local

добавить перед строкой exit следующее chown -R orangepi /sys/devices/platform/*. Эта команда сделает юзера orangepi владельцем виртуальных файлов которые отвечают за взаимодействие с портом GPIO.

По идее правильней создать группу, назначить права, добавить пользователя в группу, но это все ж демонстрационная модель поэтому(вспоминается персонаж из известного мультика), и так сойдет. Конечно вам я не в коем случае не запрещаю сделать все как надо.

Сохраняем изменения и перезагрузимся:

sudo reboot

Отвлекемся немного на аппаратную часть.

Atmega16u2


Для передачи управления решено было использовать микросхему Atmega16u2 напрямую без участия лишних микроконтроллеров и ArduinoIDE, а исполняемый код запустим, как вы конечно догадались, прямо на микрокомпьютере.

Разберем логику работы ардуины в предыдущей статье.

Значения клавиш поступали по сериал порту на микроконтроллер Atmega328p(большая продолговатая микросхема на Arduino UNO) который из массивов указанных в HIDKeyboard.h передавал коды клавиш, так называемые «USB HID keyboard keycodes» микроконтроллеру Atmega16u2(опять же по сериал порту) который в свою очередь отправлял по USB целевому компьютеру. Serial > Atmega328p > Serial > Atmega16u2 > USB

В общем Atmega328p играл роль посредника, ну и конечно логично его убрать, что мы и сделаем, для этого перепишем код который был для ардуины под Orangepi, а Atmega16u2 подключим напрямую.

Естественно голую микросхему использовать не получится, нужна хотя бы минимальная обвязка.
Советую ознакомиться с обзором по этой ссылке.Для наших целей прекрасно подходит вариант с питанием по USB


Как видите схема очень простая с ней справится даже новичок, элементов не много можно обойтись и навесным монтажом, только лучше зафиксировать кварцевый генератор что б не болтался в воздухе. Генератор кстати должен быть 16 мегагерц если вдруг не понятно из схемы. Да и не обращайте внимания на то что схема для ATmega32u2 она подходит для ATmega16u2 и для ATmega8u2 тоже.

Если имеется плата Arduino UNO можно использовать ее, подключаться следует к контактам tx — № 0, rx -№ 1 и желательно убрать микруху Atmega328p что б не потребляла питание впустую и не засоряла эфир serial порта.

Про логические уровни надеюсь напоминать не надо, все также как в предыдущей статье, serial port Orange PI аналогично Raspberry PI работает на 3.3 вольта и необходимо согласование напряжений.

Для прошивки ATmega16u2 нужно замкнуть на секунду ногу Reset с землей и залить прошивку программой Flip

Подробно описывать не буду, уже было в прошлый раз. На этом подготовка окончена перейдем к делу.

Пишем программу


Для передачи клавиш и удаленного управления конвертером напишем небольшую программу.

Теория


Сначала был порыв просто портировать исходники от UNO-HIDKeyboard-Library, это библиотека для Arduino IDE(файлы HIDKeyboard.cpp и HIDKeyboard.h) которую использовали в прошлый раз, но функциональности для задумки оказалось не достаточно, так почему бы не написать свою программу.

Представляю вашему вниманию Linux-Remote-HIDKeyboard, название так себе, но нужно же было как то назвать, далее по тексту LRHIDKeyboard.cpp или rkeysend(скомпилированный бинарник).

Состоять исходники будут из 2х файлов LRHIDKeyboard.cpp и HIDKeyboard.h. HIDKeyboard.h это все тот же заголовочный файл из библиотеки UNO-HIDKeyboard-Library. В этом файле хранятся массивы для преобразования в «USB HID keyboard keycodes» только я взял на себя смелость подправить ошибки и расширить массивы, совместимость с HIDKeyboard.cpp сохранена, так что если понадобится можно использовать и для ардуины.

USB HID keyboard keycodes — это коды клавиш предаваемые по USB, в подробностях можно прочитать здесь (pdf).

В заголовочном файле HIDKeyboard.h два массива первый для определения обычных клавиш, второй для клавиш с зажатым shift. Десятичное число получаемое при захвате из терминала сравнивается с номером элемента массива и заменяется на этот элемент который выглядит примерно так «0x7f»(шестнадцатеричное число). Пример клавиша «a» имеет значение «4» в десятичном представлении или «04» шестнадцатеричном, клавиша «ESCAPE» «41» в десятичном или «29» в шестнадцатеричном, на микросхему отправляется шестнадцатеричное число обозначающее нажатие какой либо клавиши.

Практика


Открываем консоль Orangepi создаем каталог где будет храниться программа например так:

cd ~
mkdir PROG
cd PROG

Cоздаем файлы HIDKeyboard.h и LRHIDKeyboard.cpp:

touch HIDKeyboard.h
touch LRHIDKeyboard.cpp

Открываем в редакторе HIDKeyboard.h:

nano HIDKeyboard.h

Копируем содержимое листинга:

Листинг HIDKeyboard.h
#ifndef HIDKeyboard_h
#define HIDKeyboard_h

//#include "Arduino.h"

/****************************************************************************
 * SPECIAL CHARACTER DEFINES
 *
 * These are the HID values for keys that do not output characters
 *
 ****************************************************************************/

// HID Values of Function Keys
#define F1 0x3a
#define F2 0x3b
#define F3 0x3c
#define F4 0x3d
#define F5 0x3e
#define F6 0x3f
#define F7 0x40
#define F8 0x41
#define F9 0x42
#define F10 0x43
#define F11 0x44
#define F12 0x45

// HID Values of Special Keys
#define ENTER 0x28
#define ESCAPE 0x29
#define BACKSPACE 0x2a
#define TAB 0x2b
#define SPACEBAR 0x2c
#define CAPSLOCK 0x39
#define PRINTSCREEN 0x46
#define SCROLLLOCK 0x47
#define PAUSE 0x48
#define INSERT 0x49
#define HOME 0x4a
#define PAGEUP 0x4b
#define DELETE 0x4c
#define END 0x4d
#define PAGEDOWN 0x4e
#define RIGHTARROW 0x4f
#define LEFTARROW 0x50
#define DOWNARROW 0x51
#define UPARROW 0x52

// HID Values of Keypad Keys
#define NUMLOCK 0x53
#define KEYPADSLASH 0x54
#define KEYPADSTAR 0x55
#define KEYPADMINUS 0x56
#define KEYPADPLUS 0x57
#define KEYPADENTER 0x58
#define KEYPAD1 0x59
#define KEYPAD2 0x5a
#define KEYPAD3 0x5b
#define KEYPAD4 0x5c
#define KEYPAD5 0x5d
#define KEYPAD6 0x5e
#define KEYPAD7 0x5f
#define KEYPAD8 0x60
#define KEYPAD9 0x61
#define KEYPAD0 0x62
#define KEYPADPERIOD 0x63

// HID Values of System Keys
#define KEYBOARDAPPLICATION 0x65
#define KEYBOARDPOWER 0x66
#define VOLUMEMUTE 0x7f
#define VOLUMEUP 0x80
#define VOLUMEDOWN 0x81

// Common-use modifiers
#define LCTRL 0x01
#define SHIFT 0x02
#define ALT 0x04
#define GUI 0x08


/****************************************************************************
 *
 * ASCII->HID LOOKUP TABLE
 *	
 *	Taken from the HID Table definition at 
 * 		http://www.usb.org/developers/devclass_docs/Hut1_11.pdf
 * 	
 *  This array maps the ASCII value of a type-able character to its 
 *  corresponding HID value. 
 *
 *	Example:
 *		'a' = ASCII value 97 = HID value 0x04
 * 		HIDTable['a'] = HIDTable[97] = 0x04
 *
 * 	NOTE:
 *		"Shift Modified" HID values are the same as the non Shift-Modified values
 * 		for any given character, e.g. the HID value for '2' is equal to the 
 *		HID value for '@'. The Shift-Modified value is sent by setting the
 *		modifier value (buf[0]) to the corresponding modifier value in the 
 *    modifier table. 
 *
 ****************************************************************************/
const static uint8_t HIDTable[] =  {
0x00, // 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x2b, 0x28, // 10
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, // 30
0x00, 0x2c, 0x1e, 0x34, 0x20, 0x21, 0x22, 0x24, 0x34, 0x26, // 40
0x27, 0x25, 0x2e, 0x36, 0x2d, 0x37, 0x38, 0x27, 0x1e, 0x1f, // 50
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x33, 0x33, 0x36, // 60
0x2e, 0x37, 0x38, 0x1f, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // 70
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, // 80
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 90
0x2f, 0x31, 0x30, 0x23, 0x2d, 0x35, 0x04, 0x05, 0x06, 0x07, // 100
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, // 110
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, // 120
0x1c, 0x1d, 0x2f, 0x31, 0x30, 0x35, 0x4c, 0x00, 0x00, 0x00, // 130
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 140
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 150
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 160
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 170
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 180
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 190
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 200
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 210
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 220
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 230
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 240
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 250
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x52, 0x50, // 260
0x4f, 0x4a, 0x2a, 0x00, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, // 270
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x00, 0x00, 0x00, 0x00, // 280
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 290
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 300
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 310
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 320
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, // 330
0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x4b, 0x00, // 340
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 350
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, // 360
};


/****************************************************************************
 * 
 * ASCII->MODIFIER LOOKUP TABLE
 * 
 * 	Looks up whether or not the HID report should use the SHIFT modifier. 
 * 
 * 	Example:
 *		The character '2' and the character '@' have different ASCII values but
 * 		the same HID value. This table uses the ASCII value to determine if 
 *		we should hold shift while sending the key. e.g.:
 *
 *			HIDTable['2'] = 0x1f  and  modifierTable['2'] = 0 
 *			HIDTable['@'] = 0x1f  and  modifierTable['@'] = SHIFT
 *
 *  There's probaly a better way to do this, but it's functional.
 *
 ****************************************************************************/
const static uint8_t modifierTable[] = {
0x00, // 0
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 					// 10
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 					// 20
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 					// 30
 0x00, 0x00, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, 0x00, SHIFT, 	// 40
 SHIFT, SHIFT, SHIFT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 				// 50
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, SHIFT, 0x00, SHIFT, 				// 60
 0x00, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, 	// 70
 SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, // 80
 SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, SHIFT, // 90
 0x00, 0x00, 0x00, SHIFT, SHIFT, 0x00, 0x00, 0x00, 0x00, 0x00, 				// 100
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 					// 110
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 					// 120
 0x00, 0x00, SHIFT, SHIFT, SHIFT, SHIFT, 0x00 // 127
};


class HIDKeyboard
{
	public:
		// Constructor
		HIDKeyboard();
		// Public functions
		void begin(); // Starts the required serial communication (9600 baud)
		void pressKey(uint8_t modifier, uint8_t key); // Looks up key in HIDTable and sends with a modifier
		void pressKey(uint8_t key); // Sends key report without modifier (modifier = 0)
		void pressSpecialKey(uint8_t modifier, uint8_t specialKey); // Sends specialKey with a modifier
		void pressSpecialKey(uint8_t specialKey); // Sends specialKey without modifier
		void releaseKey(); // Releases keys (clears key and modifier)
		void print(char* sequence); // Prints string <sequence>
		void println(char* sequence); // Prints string <sequence> followed by a carriage return
	private:
		// HID report buffer
		uint8_t buf[8]; // In report, buf[0] = modifier and buf[2] = key HID value
};

#endif


сохраняем Ctrl + x, y, Enter

И снова:

nano LRHIDKeyboard.cpp

Копируем содержимое листинга:

Листинг LRHIDKeyboard.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <time.h>
#include <ncurses.h>

#include "HIDKeyboard.h"

char myport[] = "/dev/ttyS2"; //указываем нужный serial port можно менять прямо тут

//использование кнопок управлениея конвертером можно менять прямо тут
char nul[] = "";
char menu[] = "/sys/class/gpio_sw/PA14/data"; //меню
char zoom[] = "/sys/class/gpio_sw/PD14/data"; //зуум
char kup[] = "/sys/class/gpio_sw/PC4/data"; //верх
char kdn[] = "/sys/class/gpio_sw/PC7/data"; //низ
char klf[] = "/sys/class/gpio_sw/PG8/data"; //лево
char krt[] = "/sys/class/gpio_sw/PA21/data"; //право

uint8_t buf[8] = {
	0 }; /* Keyboard report buffer */

int open_port(char *devname) //открываем serial port
{
  int fd;
  fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);
  if(fd == -1) //проверка
  {
    printw("Port is not open! %s" , devname);
    return -1;
  }
  else
  {
    fcntl(fd, F_SETFL, 0);
    printw("Open port %s", devname);
  }
//настройка порта
  struct termios port_settings;
  cfsetispeed(&port_settings, B9600);
  cfsetospeed(&port_settings, B9600);
  port_settings.c_cflag &= ~PARENB;
  port_settings.c_cflag &= ~CSTOPB;
  port_settings.c_cflag &= ~CSIZE;
  port_settings.c_cflag |= CS8;
  tcsetattr(fd, TCSANOW, &port_settings);
  return(fd);
}

int alt_check(int check) //Если последовательность с ALT, возвращает символ следующий за ALT,в противном случае "0"
{
  if(check == 27)
  {
    nodelay(stdscr,TRUE);
    check = getch();
    if (check == -1) return ESCAPE; //Если данных не последовало значит Esc
    nodelay(stdscr,FALSE);
    return check;
  }
  //else return 0;
  return 0;
}

int hot_key(int fd, int alt, int vchar) //комбинации 
{
  if(alt != 0)
  {
    if (alt == 330) //Нажато "Alt" + "Delete" отправка Ctrl + Alt + Delete
    {
      vchar = 0;
      buf[0] = 0x04|0x01;
      buf[2] = HIDTable[alt];
      write(fd, buf, 8);
      return 0;
    }
    if (alt == 114) //Нажато "Alt" + "r" отправка Win + r
    {
      vchar = 0;
      buf[0] = 0x08;
      buf[2] = HIDTable[alt];
      write(fd, buf, 8);
      return 0;
    }
    if (alt == 52) //Нажато "Alt" + "4" отправка Alt + F4
    {
      vchar = 0;
      buf[0] = 0x04;
      buf[2] = 0x3d;
      write(fd, buf, 8);
      return 0;
    }
    if (alt == 116) //Нажато "Alt" + "t" отправка Alt + Tab
    {
      vchar = 0;
      buf[0] = 0x04;
      buf[2] = 0x2b;
      write(fd, buf, 8);
      return 0;
    }
    if (alt == 44) //Нажато "Alt" + "," отправка Alt + Shift
    {
      vchar = 0;
      buf[0] = 0x04|0x02;
      write(fd, buf, 8);
      return 0;
    }
    if (alt == 46) //Нажато "Alt" + "." отправка Ctrl + Shift
    {
      vchar = 0;
      buf[0] = 0x01|0x02;
      write(fd, buf, 8);
      return 0;
    }
  }
  else
  {
        if (vchar == 17) //выход по нажатию "Ctrl" + "q"
    {
      endwin();
      exit(0);
    }

    //Управление конвертером 
    *nul = '\0';
    if (vchar == 28) strcpy(nul, menu); // "Ctrl" + "\"
    if (vchar == 31) strcpy(nul, zoom); // "Ctrl" + "/"
    if (vchar == 566) strcpy(nul, kup); // "Ctrl" + "up"
    if (vchar == 525) strcpy(nul, kdn); // "Ctrl" + "down"
    if (vchar == 545) strcpy(nul, klf); // "Ctrl" + "left"
    if (vchar == 560) strcpy(nul, krt); // "Ctrl" + "right"

    FILE *f = fopen(nul, "w"); //Открываем файл

    if (f != '\0')
    {
      fwrite("1", 1, 1, f);
      int wrt = fclose(f); //Пишем в файл
      usleep(70000);
      if (wrt == 0)
      {
        FILE *f = fopen(nul, "w");
        fwrite("0", 1, 1, f);
        fclose(f);
      }
    else printf("error write file:  %s", nul);
    return 0;
    }
    return vchar;
  }
}

int send_key(int fd, int altkey, int getkey) //Отправка нажатий
{
  if (altkey == ESCAPE)
  {
    buf[0] = 0x00;
    buf[2] = 0x29;
    write(fd, buf, 8);
    return 1;
  }
  if (altkey != 0)
  {
    buf[0] = 0x04;
    buf[2] = HIDTable[altkey];
    write(fd, buf, 8);
    return 1;
  }
  else
  {
    if (getkey == 0) return 0;
    //printf("KEY NAME : %s - %d\n", keyname(getkey),getkey); //Для отладки
    buf[0] = modifierTable[getkey];
    buf[2] = HIDTable[getkey];
    write(fd, buf, 8);
    return 1;
  }
  return(-1);
}

int release_key(int fd) // Отпустить нажатые клавиши
{
  buf[0] = 0;
  buf[2] = 0;
  write(fd, buf, 8);
}

int main(void)
{
  initscr();
  keypad(stdscr,TRUE); //для захвата спец символов
  raw(); //для захвата комбинаций
  int fd = open_port(myport);
  printw("\nHOT KEYS: Alt + Delete = Ctrl + Alt + Delete   Alt + r = Win + r   Alt + 4 = Alt + F4   Alt + t = Alt + Tab   Alt + , = Alt + Shift   Alt + . = Ctrl + Shift");
  printw("\nGPIO: MENU = Ctrl + \\  ZOOM = Ctrl + /  UP = Ctrl + up  DOWN = Ctrl + down  LEFT = Ctrl + left  RIGHT = Ctrl + right");
  printw("\n Exit = Ctrl + q");
  move(10,1);
  refresh();
    while (fd != -1)
    {
      int pkey = getch();
      int alt = alt_check(pkey);
      int hot = hot_key(fd, alt, pkey);
      send_key(fd, alt, hot);
      release_key(fd);
    }
  endwin();
}


Сохраняем Ctrl + x, y, Enter.

Компилируем


g++ LRHIDKeyboard.cpp -o rkeysend -lncurses

rkeysend это название получаемого в процессе компиляции исполняемого файла
-lncurses подключение при компиляции библиотеки ncurses, используется для захвата клавиш в терминале и передаче текстовых сообщений в тот же терминал.

Если компиляция прошла успешно, можно приступать к GPIO портам.

Управление конвертером


«Свежескомпилированная» программа может не только передавать клавиши но и управлять конвертером. Объясню зачем. Во первых можно настраивать изображение, яркость, контрастность, резкость и т.п. Во вторых, я уже писал про разрешение изображения получаемого с конвертера = 720х576. Так вот, у конвертера есть режим отображения только части изображения, при этом разрешение не меняется, другими словами можно рассмотреть боле мелкие детали изображения использовав этот режим.

Управление конвертером будет происходить по средством GPIO. GPIO как вы конечно уже знаете general-purpose input/output, или если по-русски, мне понравилось довольно точное определение из википедии «интерфейс ввода/вывода общего назначения» эдакий способ микрокомпьютера взаимодействовать с внешним миром.

Подключение Orangepi PC к конвертеру


И так Соединяем конвертер с GPIO. На конвертере имеются физические кнопки управления: лево, право, низ, верх, menu и zoom. Подключены они по следующему принципу как на картинке. Иллюстрация отображает только принцип работы кнопок.


На конвертере имеется порт на котором он меряет напряжение и при соответствии с нужным уровнем выполняет действие: открывает меню, увеличивает изображение передвигает изображение и т.д.

Для управления кнопками я использовал биполярные транзисторы с оптическим входом АОТ128А:


Эмиттер и коллектор транзистора припаиваем к земле и контакту кнопки. Катод транзистора подключаем(припаиваем) на землю. Анод транзистора подключаем к нужному порту GPIO через резистор на 33 Ома.


Если будете что-то менять, помните:

Управляющее напряжение на аноде данного транзистора не более 1,6В, при превышении этого значения транзистор начинает сильно греться и может произойти тепловой пробой. Источник питания для Orangepi и для конвертера желательно использовать один и тот же, иначе из-за разности потенциалов картина может измениться.

Подключаем аноды транзисторов к следующим портам GPIO: PA14 – меню, PD14 – зум, PC4 – верх, PC7 – низ, PG8 – лево, PA21 – право.

Сериал порт для подключения микросхемы ATmega16u2 расположен на Портах 11(RX) и 13(TX). Подключаем как указано на изображении

Что ж теперь все подключено. Сначала хотел сделать управление конвертером на одном полевом транзисторе с использованием единственного порта, но как-то не задалось, пульсация ШИМ передается на транзистор и в итоге сопротивление на нем начинает плавать, есть варианты схем сглаживающих фильтров но это сильно бы усложнило конструкцию. Еще есть вариант использовать цифровой потенциометр через SPI или I2C, тут уже все в ваших руках, дерзайте.

Запуск


Проверим все ли подключено как надо:

  1. Orangepi по GPIO к конвертеру
  2. Конвертер по VGA к Компьютеру(slave)
  3. AV выход конвертера на AV вход платы видео захвата
  4. Плата видео захвата к USB Orangepi
  5. Orangepi к микросхеме ATmega8U2/16U2/32U2
  6. Микросхема ATmega8U2/16U2/32U2 по USB к компьютеру(slave)
  7. Компьютер(master) по SSH к Orangepi

Запускаем программу


Ha Orangepi запускаем скомпилированную программу, должны появиться надписи с указанием открытого serial порта и комбинациями клавиш. Примерно как-то так.



Имеются сочетания клавиш для вызова диспетчера задач или смены раскладки, например чтобы нажать на подконтрольном компьютере «Ctrl + Alt + Del» в терминале нужно ввести «Alt+Del», возможные комбинации пишутся при запуске программы сверху окна терминала.

Видео как и в прошлой статье передает motion, доступно по адресу orangepi:8081. Что ж на этом все, вся необходимая информация для повторения опыта изложена, миссия выполнена.

Подведем итоги:


Передача видео


Видео так и осталось очень низкого качества из-за того что передается по AV входу, но теперь мы имеем удаленное управление конвертером и если выбрать нужный пункт в меню — можно смотреть экран по частям в том же разрешении.

Есть возможные варианты решения проблемы с качеством видео, только у них есть один большой недостаток это цена:

1. Velocap U2m usb 2.0 hdmi
2. UVC USB 3.0 HDMI

Эти варианты я не пробовал, ибо дорого, возможно кто-то из прочитавших статью рискнет.
Вероятно можно сэкономить на плате микрокомпьютера поменять Orangepi PC на Orangepi one или Orangepi Zero, его стоимость примерно 500руб. Но с Orangepi Zero могут возникнуть проблемы, там другой процессор, соответственно другой дистрибутив с другим ядром, в котором могут отсутствовать нужные драйвера и модули.Потенциал GPIO не раскрыт полностью, например можно еще подключить реле для замыкания контактов Power и Reset на материнской плате.

Себестоимость


VGA to AV конвертер примерно 700руб.
Плата видео захвата примерно 500руб.
Orangepi PC примерно 1000руб.
ATmega8U2/16U2/32U2 примерно 150руб.

Итого: 2350руб.

Ну вот и подошла эта длинная статья к концу, спасибо всем кто дочитал. Надеюсь на практике у вас все получится. Хоть мне и не удалось осуществить всего задуманного, однако результат получился интересным, особенно за такую цену. Конечно получившийся девайс требует доработки, но думаю все же имеет право на существование. Отдаю все свои наработки как есть, без каких либо гарантий, в хорошие руки. Спасибо за внимание!
Поделиться с друзьями
-->

Комментарии (3)


  1. DaylightIsBurning
    10.04.2017 19:13

    IP KVM за ~40$ — это круто. Жаль что Full-HD вариант дешевле простого готового IP-KVM не получается :(. Мб существуют варианты более дешевых diy карт захвата?


    1. DaylightIsBurning
      10.04.2017 19:18
      +1

      вот нашел такую штуку, 70$ — всё же немного дешевле.


      1. Mak2k2
        11.04.2017 12:00

        Да, может сработать но только с raspberry и еще один момент смущает, выдержка из мануала:

        — the output resolution of your HDMI signal must be 1080p25. No other resolution is supported