geektimes

Офисный переключатель интернета

  • четверг, 23 октября 2014 г. в 03:10:39
http://habrahabr.ru/post/241245/


Провели нам в офис вторую линию интернета. Так как основная (дальше я буду называть её первой) хоть и хороша по-скорости, но ограничена по трафику. Вторая немного медленнее, но безлимитна. Днём вторая линия почти свободна и выдаёт хорошую скорость, поэтому и была выбрана основной для рабочего дня. К вечеру скорость сильно падает из-за нагрузки на канал и приходится переключаться на первую. Так бывает не всегда, но достаточно часто.
Потому возник вопрос в переключении линий. Роутером у нас трудится обычный компьютер с 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 штук. Удобно на случай если какая-то плата будет загублена в результате кривых рук при монтаже. Если кого заинтересовал этот девайс и есть желание собрать что-то подобное — пишите в личку, готов поделиться платами.