Офісний перемикач інтернету


Провели нас в офіс другу лінію інтернету. Так як основна (далі я буду називати її першої) хоч і гарна по-швидкості, але обмежена по трафіку. Друга трохи повільніше, але безлимитна. Вдень друга лінія майже вільна і видає гарну швидкість, тому і була обрана основною для робочого дня. До вечора швидкість сильно падає з-за навантаження на канал і доводиться перемикатися на першу. Так буває не завжди, але досить часто.
Тому виникло питання в перемиканні ліній. Роутером у нас трудиться звичайний комп'ютер з 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="ТАК"

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/БОД-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/python env

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/python env

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:="НІ"}
: ${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="ТАК"


Висновок
В результаті вийшло веселе, але функціональний пристрій. Тепер будь-який співробітник знаходиться ближче до сервера може переключити активний канал в разі будь-яких проблем. Так само світлодіоди показують який з каналів на даний момент активний. Він різного кольору (зелений і червоний), так що з далека видно яким підключенням користуємося.
І у висновку хочу сказати, що у мене ще залишилися плати під це пристрій, так як при замовленні менше 10 ціна все одно не змінюється, так що замовив відразу 10 штук. Зручно на випадок якщо якась плата буде втрачена в результаті кривих рук при монтажі. Якщо когось зацікавив цей девайс і є бажання зібрати щось подібне — пишіть в лічку, готовий поділитися платами.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.