Провели нам в офис вторую линию интернета. Так как основная (дальше я буду называть её первой) хоть и хороша по-скорости, но ограничена по трафику. Вторая немного медленнее, но безлимитна. Днём вторая линия почти свободна и выдаёт хорошую скорость, поэтому и была выбрана основной для рабочего дня. К вечеру скорость сильно падает из-за нагрузки на канал и приходится переключаться на первую. Так бывает не всегда, но достаточно часто.
Потому возник вопрос в переключении линий. Роутером у нас трудится обычный компьютер с FreeBSD на борту. Городить хитрую логику проверки скорости канала смысла не было, к тому же нужна была индикация активного подключения. Настроив переключение каналов на консольные команды с помощью sh скриптов в папке /bin, возникли 2 проблемы:
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("%sn" % 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 startedn")
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 %sn" % 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 filen"
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 скрипт:
#!/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 штук. Удобно на случай если какая-то плата будет загублена в результате кривых рук при монтаже. Если кого заинтересовал этот девайс и есть желание собрать что-то подобное — пишите в личку, готов поделиться платами.
Автор: exp131