Провели нам в офис вторую линию интернета. Так как основная (дальше я буду называть её первой) хоть и хороша по-скорости, но ограничена по трафику. Вторая немного медленнее, но безлимитна. Днём вторая линия почти свободна и выдаёт хорошую скорость, поэтому и была выбрана основной для рабочего дня. К вечеру скорость сильно падает из-за нагрузки на канал и приходится переключаться на первую. Так бывает не всегда, но достаточно часто.Потому возник вопрос в переключении линий. Роутером у нас трудится обычный компьютер с FreeBSD на борту. Городить хитрую логику проверки скорости канала смысла не было, к тому же нужна была индикация активного подключения. Настроив переключение каналов на консольные команды с помощью sh скриптов в папке /bin, мы столкнулись с двумя проблемами:
1. Доступ к консоли только у админа, а он не всегда на месте, да и дёргать ради переключения каналов тоже не очень удобно.
2. Нет индикации актвного, на данный момент, подключения.
Поэтому было принято решение сделать переключатель физическим и снабдить индикаторами.
Проект
Из имеющегося на данный момент в наличии железа, а именно кучки AVR Atmega 8A и FT232RL, было решено сделать переключатель, который подключается к USB порту и простым переключением тумблера меняет активный канал на другой. А так же светодиодом показывает тот, который сейчас выбран.
Логика работы устройства очень проста:
Несколько раз в секунду atmega проверяет состояние входов на порту C и передаёт это состояние через UART в виде символа A для первого канала и B для второго. К UART МК подключен преобразователь на FT232RL, который пересылае этот символ через USB в виртуальный COM порт сервера. На сервере работает простой демон написанный на питоне, который в случае изменения канала, выполняет команду переключения (смена шлюза по-умолчанию и статичных маршрутов, но это уже выходит за рамки данной статьи).
Демон запускается вместе с системой, поэтому для него написан rc скрипт.
Но обо всём по-порядку.
Подготовка
Исходя из задачи в пакете DipTrace была сделана схема:

U1 это FT232RL, U3 — Atmega 8.
S1 — тумблер переключения, который так же переключает состояние светодиода-индикатора активного канала.
Примечание: уже после сборки узнал про свинью, которую FTDI подложила всем пользователям их чипов, так что в следующий раз дважды задумаюсь над применением их продукции. Так как отличить оригинал от подделки практически невозможно, то самым лучшим будет не играть в эту лотерею. Но так как в наличии осталось несколько их чипов, то решил делать всё-таки на них. Забегая вперёд скажу, что пляски с драйверами избежать не удалось, но в итоге удалось заставить эти чипы работать. C FreeBSD же проблем не возникло, так как там старый драйвер.
По этой схеме была разведена плата под имеющийся корпус:

Далее встал вопрос как делать плату. Можно, конечно, старым дедовским ЛУТом. Но есть способ лучше: заказать у китайцев. На хабре достаточно подробно описывался процесс заказа, и в почта России тоже частенько стала радовать своей работой.
DipTrace умеет делать экспорт в формат Gerber, поэтому проблем с заказом не возникло.
Сборка
Через пару недель получив посылку с платами на почте, можно приступать к сборке. Так как схема очень проста, то и проблем со сборкой не возникло:


Замечу только, что SMD компоненты паял с помощью паяльной пасты и фена. Так удобнее и получается намного качественней чем паяльником. Особенно при пайке такой мелочи как FT232.
Настройка
Во FreeBSD есть драйвер для чипов FTDI, поэтому проблем с подключением не возникло. Единственно, что потребовалось включить загрузку модуля ядра. В файле /boot/loader.conf прописать:
uftdi_load="YES"
Atmega была прошита с помощью avrdude, пропатченным для работы с ft232, прямо через USB подключение.
Исходник прошивки
/* * net_switch.c * * Created: 09.09.2014 10:07:41 * Author: exp131 */ #define F_CPU 1000000UL // Частота 1МГц, внутренний кварц #define BAUD 2400 // Скорость UART, 2400 хватает за глаза #define MYUBRR F_CPU/16/BAUD-1 #include <avr/io.h> #include <avr/interrupt.h> // Так как не использую файл с заголовками, то описываю функцию тут void ReportStatus(); // Обработчик прерывания по таймеру ISR(TIMER0_OVF_vect) { ReportStatus(); // Вызываем отправку состояния переключателя } // Функция проверки входов 0 и 1, порта С и выдача в UART соответствующего символа void ReportStatus() { cli(); // Отключим прерывания на время передачи unsigned char a; if((PINC & (1<<PINC0)) && (!(PINC & (1<<PINC1)))) // Если на С0 лог. 1 и на С1 лог 0, то a = 'A'; // состояние А else a = 'B'; // иначе, состояние В // Непосредственно посылаем байт в порт while(!(UCSRA & (1<<UDRE))); UDR = a; sei(); // возвращаем прерывания } //Инициализация void init(void) { // Настройки UART, включена передача и установлена скорость 2400 UCSRB = (1<<TXEN); unsigned int ubrr = MYUBRR; UBRRH = (unsigned char)(ubrr >> 8); UBRRL = (unsigned char)ubrr; // Таймер, задаём максимальный делитель и включаем прерывание по переполнению. TCCR0 = (1<<CS02)|(1<<CS00); TIMSK = (1<<TOIE0); sei(); } int main(void) { // Тут инициализируемся init(); while(1) // и запускаем основной цикл { } }
В качестве базы для демона нашёл в Сети класс на питоне. К сожалению ссылка на источник не сохранилась, просто приведу тут код.
Код класса демона
#!/usr/bin/env python import sys, os, time, atexit from signal import SIGTERM class Daemon: def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile def demonize(self): try: pid = os.fork() if pid > 0: sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) os.chdir("/") os.setsid() os.umask(0) sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile, 'w+').write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) def start(self): try: pf = file(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "Pidfile %s already exists. Deamon already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) self.demonize() self.run() def stop(self): try: pf = file(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "Pidfile %s does not exists. Daemon is not running?\n" sys.stderr.write(message % self.pidfile) return try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def restart(self): self.stop() self.start() def run(self): """ Need to be overriden """
На базе этого класса был написан простой демон который слушает указанный в конфиге порт и выполняет, в зависимости от состояния преключателя, либо команду А, либо команду В.
Конфиг
[global] port=/dev/cuaU1 rate=2400 log=/var/log/net_switch.log cmdA=/bin/vist cmdB=/bin/unico
А вот и код самого демона:
Код демона
#!/usr/bin/env python import sys, os, time, serial, ConfigParser from daemon import Daemon class NetSwitch(Daemon): def run(self): file(self.logfile, 'a+').write("Net switch started\n") while True: ser = serial.Serial(self.port, self.rate, timeout=1) x = ser.read() if not x == self.state: self.state = x if x == 'A': os.system(self.cmdA) else: os.system(self.cmdB) file(self.logfile, 'a+').write("State changed %s\n" % x) time.sleep(0.2) def loadConfig(self, configPath): try: config = ConfigParser.RawConfigParser() config.read(configPath) self.port = config.get('global', 'port') self.rate = config.getint('global', 'rate') self.logfile = config.get('global','log') self.cmdA = config.get('global', 'cmdA') self.cmdB = config.get('global', 'cmdB') self.state = 'A' return True except: return False if __name__ == "__main__": daemon = NetSwitch('/var/run/net_switch.pid') if len(sys.argv) == 2: if 'start' == sys.argv[1]: print "Usage: %s start path_to_config" % sys.argv[0] elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) elif len(sys.argv) == 3: if 'start' == sys.argv[1]: configPath = sys.argv[2] if daemon.loadConfig(configPath): daemon.start() else: print "Unable to load config file\n" else: print "Usage %s start path_to_config" % sys.argv[0] else: print "Usage: %s start|stop|restart" % sys.argv[0] sys.exit(2)
И в заключении, для того, чтобы демон стартовал вместе с системой, был написан rc скрипт:
RC скрипт для запуска
#!/bin/sh . /etc/rc.subr name=net_switch rcvar=`set_rcvar` #reading the config load_rc_config $name : ${net_switch_enable:="NO"} : ${net_switch_config:="/usr/local/etc/net_switch/config.conf"} pidfile="/var/run/net_switch.pid" command="/usr/local/sbin/${name}.py" start_cmd="start_cmd" stop_cmd="stop_cmd" restart_cmd="restart_cmd" start_cmd() { ${command} start ${net_switch_config} } stop_cmd() { ${command} stop } restart_cmd() { ${command} restart } run_rc_command "$1"
Для того, чтобы демон мог стартовать вместе с загрузкой системы, нужно в /etc/rc.conf добавить строку:
net_switch_enable="YES"
Заключение
В результате получилось забавное, но функциональное устройство. Теперь любой сотрудник находящийся ближе к серверу может переключить активный канал в случае каких-либо проблем. Так же светодиоды показывают какой из каналов на данный момент активен. Он разного цвета (зеленый и красный), так что из далека видно каким подключением пользуемся.
И в заключении хочу сказать, что у меня ещё остались платы под это устройство, так как при заказе меньше 10 цена всё равно не меняется, так что заказал сразу 10 штук. Удобно на случай если какая-то плата будет загублена в результате кривых рук при монтаже. Если кого заинтересовал этот девайс и есть желание собрать что-то подобное — пишите в личку, готов поделиться платами.
