Как я софт на c# писал
Январь 14, 2017
Начал я учить летом шарп. Пробовал еще в 2013 бесплатные ролики. А еще раньше в 2006 в пту писал управление релешками с кнопок. У меня тогда была самопальная плата с виртуальным усб на меге8, которая работала как ком порт и плата с 8 реле от платы контроллера с89 для визуального программирования. Плата древняя и мне ее подарил один инторнетный знакомый.
А в этот раз был заказ сделать устройство, чтобы 1-4 spdif потока можно было подключать на 3 устройства, которые преобразуют этот поток и шлют по инторнету или исдн линии в другую студию радио или звукозаписи.
Я сначала подумал, что можно легко сделать на электронных коммутаторах, нашел их, но так лень было делать плату. Решил поискать готовое и нашел что надо.
Причем коммутировать надо с любого рабочего места оператора по сети и чтобы оно работало независимо от инторнета.
Цена этой штуки оказалась 1150 евров и заказчику не понравилась. А ведь это готовый корпус, все сделано хорошо.
У него оказывается уже был простой переключатель при помощи реле и тумблеров, но надо было расширить на компутеры, а в программировании он не понимает.
Когда он сказал, что сигнал и на реле нормально работает без сбоев, то я предложил купить плату с сетевыми реле, которые я до этого смотрел, т.к. хотел сделать открывание замка по клику на кнопку на панели браузера. В ту контору очень часто заходят люди и раньше надо было бегать к домофону, а потом я сделал открывание с исдн телефонов, но возле телефонов не всегда есть люди и можно было сделать с браузера или с мабилы в виде виджета. Но потом шеф решил запилить кодовый замок на входе.
Самое интересное тут в том, что все складывается так, чтобы я продолжал делать интересное и задуманное мной дело. Поэтому мне “сверху” подогнали этот заказ. А вместе с ним заказчик спросил 5 компутеров б\у. И тут я вспомнил, что у меня возле стола стоит мой старый компутер и я все никак не выставлю его на продажу.
А еще до этого я думал купить родителям электронную книгу, потому что прошлая купленная мной была с серым экраном. Я заходил к родителям и еще глянул книгу, т.к. отец говорит, что она глючить начала с сенсорным экраном. А там сам экран уже полосами пошел и ничего не видно. Поэтому я пришел домой с разу заказал покетбук бейсик 2, без плюшек + чехол. Как раз был конец ноября и в это время быдло очухивается после 11.11, когда понабрали ненужного гавна и решили сдать обратно. А вскрытые коробки уже не продаются как новый товар и имеют скидки чуть ли не 50%. Вот я такую и купил вместе с чехлом за 50 евров.
И в эти же выходные мне как раз звонил заказчик, но меня не было дома, поэтому позвонил в понедельник. А компутер я ему отдал в среду за 50 евров.
Т.е. если деньги отдать на годное дело, то они тут же возвращаются обратно. Только инициатором отдачи должен быть ты сам.
Таким макаром я избавился от неножного ящика, купил книгу (старая как раз в понедельник совсем сдохла) и не потерял ни цента.
Для продолжения банкета я еще получил веб реле, которое заказчик сам купил в Болгарии.
Все обошлось в 100 евров, правда контроллер несколько не тот оказался. У производителя много всяких таких плат. Тот контроллер выдавал на запрос
http://192.168.1.100/current_state.xml
А установить значение реле можно запросом
http://192.168.1.100/current_state.xml?pw=admin&Relay1=1
Довольно все просто по сравнению с дешевой китайской платой за 50 евров.
В этой китайской плате протокол бинарный и возни там много с ним.
В купленный контроллер xml не поддерживал. Но вместо него был snmp, а так же можно было делать веб запросы.
А вот в ответ он выдавал готовый код на яваскрипте вместо json.
var IO=new Array (0x00,0x00,0x00,0x0030,0x005E,0x0056,0x0054,0x00D4,0x0135,0x011B,0x00C9)
var IS=new Array (0xFF,0xFF,0x00)
var N=new Array ("P3.1","P3.2","P3.3","P3.4","P3.5","P3.6","P3.7","P3.8","P5.1","P5.2","P5.3","P5.4","P5.5","P5.6","P5.7","P5.8","ADC.1","ADC.2","ADC.3","ADC.4","ADC.5","ADC.6","ADC.7","ADC.8","Port 1","Port 2","Port 3","Port 4","Port 5","Port 6","Port 7","Port 8")
Первые 2 байта это состояние 2 по 8 реле, 3й не указано и остальные значения - значения 6 каналов ацп.
Опять косяк. Можно конечно вытащить 2 несчастных байта регулярками, но это будет быдлокод. У нас же есть снмп. Я его ковырял еще в 2003 году, когда купил первый роутер и поставил софт для мониторинга трафика по портам. Так же сканировал в то время сеть телекома и находил даже виндовс сервер 2000 с агентом снмп. Обычно он не ставится по умолчанию. Агент показывает логины в системе, а это уже 95% успешного взлома, что я было сделано. А так же поломан принтсервер в одной конторе, где я учился.
Так что я подумал, что грех не сделать софт на шарпе, хотя я хотел сделать на хтмл и яваскрипте, но внутри конторы заказчика нет вебсервера, а на хостинге он отказался, т.к. должно работать даже без инторнета.
Плата управляет реле через отдельные идентификаторы 1.3.6.1.4.1.19865.1.2.1.1.0 - 1.3.6.1.4.1.19865.1.2.1.8.0
или же можно сразу включить несколько реле при помощи 1.3.6.1.4.1.19865.1.2.1.33.0 для первых восьми реле и 1.3.6.1.4.1.19865.1.2.2.33.0 для остальных (с 9 по 16).
Изначально была матрица 4 входа на 3 выхода и требовало 12 реле, а есть платы только с 8 и 16. Поэтому с сказал, что можно еще сделать про запас один вход или выход и еще останется 1-2 реле для других нужд. Тогда заказчик утвердил еще один вход и я отправил ему на согласование схему.
Теперь стоит главная задача заказчика: один вход может подключаться к 1 выходу или более. Но нельзя 2 входа подключить к одному выходу, т.к. сигнал цифровой.
Я впечатлился первой картинкой готового прибора, где были чекбоксы на вебморде.
Только в этом приборе можно было как угодно соединять. И тут меня заклинило на этих чекбоксах. Надо написать код, чтобы только из одной колонки можно было нажать. Т.е. при нажатии на один чекбокс, все остальные в этой колонке становятся неактивными.
При этом кто-то другой может переключить состояние и надо обновить состояние чекбоксов на первом компутере. Это было настолько гиморно и заняло кучу времени и кода, но не было доведено до конца, т.к. я гуглил всякие решения по опросу контролов на форме в цикле. Хотя можно было бы опять же сделать тупо - у каждого реле есть свой снмп адрес. Скопипастить 15 раз и готово. Быстро и тупо.
Но надо все делать круто и правильно. Должен быть способ. Потом начали лезть в голову мысли из быдловуза про матрицы. Но я так и не понял, как тут что из матриц поможет, особенно если я уже ничего не помню кроме транспонирования и только потому, что у меня в голове образ остался, когда матрицу за край берут и перетаскивают как бы, что она отражается или что-то типа того.
Вобщем тут я что-то приуныл и валялся на диване.
На хабре и в других всяких сцайтах пишут, что типа программировать это легко и даже детей надо учить аж с первого класса. Только все это программирование обычно сводится к уровню hellow world, миганию светодиодом или передвижению картинки с котиком при нажатии кнопок.
Никто еще из этих популяризаторов не показал реально практичный софт, который был мог написать посетитель таких курсов или читатель уроков.
Программирование - это очень сложно. Это требует кучу времени, а времени дают мало. Нужно укладываться в сроки, а сроки эти нельзя оценить. Удовольствие от программирования можно получать только в своих проектах или когда это не основной доход и время не ограничено.
В остальном это унылое занятие, ничем не лучше других унылых работ.
Значит гуглил я решения, гуглил и наткнулся на радиокнопки в примере цикличного опроса контролов. И тут мне как по башке ударили и пришло озарение, что радиокнопки можно выбрать только одну из. В шарпе надо поместить их в группу для этого.
Таким макаром решился вопрос об автоматическом ограничении и недопущении неправильной работы проги или пользователя.
Теперь опять пришлось гуглить, чтобы узнать, почему прошлый пример опроса перестал работать. Оказывается в данном случае я добавил кнопки в группу и они же стали невидимы с формы.
Теперь надо делать так:
foreach (GroupBox gb in Controls.OfType<GroupBox>())
{
foreach (RadioButton rb in gb.Controls.OfType<RadioButton>())
{
доступ к кнопкам через rb
}
}
В чем тут сложность была для меня?
Я думал, что уже есть экземпляр формы или где-то там короче вызывается конструктор формы.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Значит можно обратиться к Form1.groupbox1.radiobutton1, но так не получалось и надо было использовать указатель this. Но и с ним тоже не получалось из нагугленных примеров.
Зато в цикле foreach создается новый объект gb типа Groupbox и потом уже парсится на радиокнопки. Только непонятно, откуда новый объект знает о радиокнопках? Нигде нет указания на конкретную форму. А если форм таких несколько будет? Вот в этом и сложность программирования - что непонятно как делать что-то. Я бы ни в жизнь не догадался так написать.
Другая сложность - готовая библиотека snmp. Протокол оказался совсем не simple, как говорит его название. Это с виду все просто, а под капотом там много чего скрыто.
Библиотеку я нашел, попробовал тестовый код. https://www.snmpsharpnet.com/?page_id=114
Не работает и выдает необработанное исключение ошибки сокета. Гугл говорит, что это может быть все что угодно.
Стал я отладчиком смотреть, может быть адрес неправильно строкой указал, а надо было подгонять в правильном формате каком. Вроде в переменных все правильно содержится.
Потом все же решил подправить строку
pdu.VbList.Add(new Oid("1.3.6.1.2.1.67.1.1.1.1.6.0"), new UInteger32(101));
new UInteger32() надо было поменять на new Integer32(101), хотя какая разница, если все равно я туда записывал 1 или 0?
Из-за этого плата отвечала как-то криво и с точки зрения кода происходила ошибка соединения. Уж не знаю насколько там глубоко предусмотрены оишбки, но 2 часа потратил только на поиск этого глюка.
Следущий глюк - нигде не указано, что плата не может обработать 2 запроса подряд.
Я же делал так:
pdu.VbList.Add(new Oid(".1.3.6.1.4.1.19865.1.2.1.33.0"), new Integer32(lobyte));
pdu.VbList.Add(new Oid(".1.3.6.1.4.1.19865.1.2.1.33.0"), new Integer32(hibyte));
А потом вызывал метод отправки и отправлялся только один параметр на первые 8 реле. Почему нельзя было сделать, чтобы за один раз отправлялось 16 бит? Косяк разработчиков. На это тоже убил несколько часов, пришлось делать еще один проект консольный и на нем отлаживать, чтобы другой код основного проекта не мешал. Пришлось еще делать одну проверку, если вдруг связи нет и не пришел ответ от платы, то выполнять второй запрос нет смысла, что сделало код некрасивым.
И это еще не все.
Теперь надо сделать 2 вещи: при отправке у нас 2 раздельных байта, но на форме не 2 по 8 радиокнопок и надо сопоставить кнопки с нужными реле согласно картинке.
У многих контролов есть свойство Tag. Я каждой радиокнопке дал номер от 0 до 14 и потом получал эти номера.
Для этого у нас есть 2 глобальные переменные.
public int data = 0;
public int mask;
RadioButton rb = (RadioButton)sender;
int rbnum = Convert.ToUInt16(rb.Tag); // получаем номер бита из свойства Tag
string grpBoxName = rb.Parent.Name; // получаем имя groupbox для определения группы и использования маски
Номера радиокнопок - это номера битов в 16 битной переменной data, которая хранит общее состояние всех 16 реле.
Как получить и установить только нужный бит в нужной колонке? Быдлоперебор бит с 0 до 4, с 5 до 9 и с 10 до 14. Опять быдлокод некрасивый.
И тут приходит на помощь опыт работы с мк, где очень много манипуляций с битами.
Алгоритм такой: т.к. у нас в каждой колонке может быть только один бит, то мы можем обнулить сразу все биты в колонке без разбора и потом установить один, взятый из свойства Tag.
Поэтом у нас и есть глобальная переменная mask, которая будет иметь маску для сброса по каждой колонке. Теперь осталось определить, из какой колонки радиокнопка была нажата.
Надо сказать, что изначально я использовал событие CheckedChanged, но оно так же срабатывало, когда приходил новый пакет с изменениями и код менял состояние, включая или отключая радиокнопку, что являлось событием и срабатывал обработчик кода. Поэтому надо использовать собитие не на изменение состояния, а на клик.
Вобщем у нас есть 3 группы с кнопками и мы можем по имени группы узнать, из какой именно эта кнопка.
Для этого есть 2 параметра, которые передаются обработчику событий
private void Radiobuttons_CheckedChanged(object sender, EventArgs e)
sender содржит в себе информацию о кнопке.
RadioButton rb = (RadioButton)sender;
А так же у этой кнопки есть родительский элемент - groupbox.
Вот так мы получаем имя родительского элемента.
string grpBoxName = rb.Parent.Name;
И самый трюк с масками.
// маски по 5 нулей для каждого выхода для сброса старого бита (hack - не нужна проверка radiobuttons.checked)
if (grpBoxName == "gbC11") mask = 0xFFE0;
if (grpBoxName == "gbCent3") mask = 0xFC1F;
if (grpBoxName == "gbTokyo") mask = 0x83FF;
data = data & mask; // обнуляем все биты выхода по маске для установки одного нового бита из Tag
data = data | (1 << rbnum); // устанавливаем один новый бит из Tag на весь байт + сохраняем старые биты других выходов
mysnmp.snmpsend("private", data);
Потом отправляем 16 бит в наш метод. Сначала я делал статический класс и думал просто тупо отправил данные и забыл, но там еще был ответ и этот ответ надо показывать при исключении в статусбаре и код, который отправляется в плату.
А так же не включать кнопку, если связь вдруг пропала, т.к. может быть ситуация, что связь пропала, а человек уверен, что он включил нужный режим.
Такая организация кода делает независимым 16е реле, которое в любое время можно задействовать с небольшой правкой кода, если вдруг клиент потом захочет что-то еще.
А что при приеме?
Таймер раз в 5 секунд запрашивает состояние всех реле, получает данные в переменную data. Потом в цикле тупо ставятся или убираются точки в кнопках при помощи функции setRadioButtonsState(data);.
private void setRadioButtonsState(int _data)
{
foreach (GroupBox gb in Controls.OfType<GroupBox>())
{
foreach (RadioButton rb in gb.Controls.OfType<RadioButton>())
{
int bitnum = Int32.Parse((string)rb.Tag);
if ((_data & (1 << bitnum)) != 0)
rb.Checked = true;
else
rb.Checked = false;
}
}
}
Я уже описывал выше перебор элементов, если они находятся не на форме, а в контейнере.
Как выглядит работа с снмп?
Для этого я сделал отдельный динамический класс.
В нем есть 2 геттера.
public string Status { get { return _status; } }
public SnmpV1Packet Response { get { return _response; } }
На вход метода отправки приходит 16 битное число и надо разбить его на 2 байта.
public void snmpsend(string _passw, Int32 _data)
{
Int32 lobyte, hibyte;
lobyte = (Int32)(_data & 0x000000FF);
hibyte = (Int32)(_data >> 8);
Дальше уже отправляется один байт, если ответ пришел, то удаляется из списка пакетов первый пакет, добавляется снова, но с другим снмп адресом и значением второго байта и опять отправляется. Потом меняется статус и ответ.
Ну и метод для приема 2х пакетов.
public int snmprecieve(string _passr)
{
SnmpV1Packet result;
Int32 hibyte = 0x0, lobyte = 0x0;
int resultdata = 0;
AgentParameters param = new AgentParameters(new OctetString(_passr));
param.Version = SnmpVersion.Ver1;
UdpTarget target = new UdpTarget((IPAddress)new IpAddress(_ip), 161, 200, 1);
Pdu pdu = new Pdu(PduType.Get);
try
{
pdu.VbList.Add("1.3.6.1.4.1.19865.1.2.1.33.0"); //lobyte
result = (SnmpV1Packet)target.Request(pdu, param);
if (result != null)
{
lobyte = (Integer32)result.Pdu.VbList[0].Value;
pdu.VbList.Clear();
pdu.VbList.Add("1.3.6.1.4.1.19865.1.2.2.33.0"); //hibyte
result = (SnmpV1Packet)target.Request(pdu, param);
hibyte = (Integer32)result.Pdu.VbList[0].Value;
_status = "OK";
_response = result;
return resultdata = hibyte << 8 | lobyte;
}
return 0;
}
catch (SnmpException )
{
_status = "Error - check IP or community string.";
_response = null;
return resultdata = 0;
}
}
В конце собираем слово из 2х байт и возвращаем.
Таким макаром софт обеспечивает защиту от неправильного включения, не дает пользователю совершить действие в случае пропажи связи, не показывает состояние кнопок при отсутствии связи. Показывает в статусе ошибку или успешное действие путем зеленого ОК или красного сообщения, а так же меняющихся цифр в десятиричном формате.
Софт не имеет настроек и дополнительных файлов. Оказывается в шарпе можно засунуть длл в ехе при помощи одного плагина для иде. Получился один ехе файл размером около 500к ценой в 300 евров. Можно было бы уменьшить размер, если из файла иконок выкинуть иконки ненужных разрешений, т.к. используется только одно 32х32 вроде. Хотя если в проводнике будет вид файлов в виде больших иконок, то иконка будет с квадратиками. А так все четко выглядит.
На написание ушло больше месяца в общей сложности, т.к. приступы быдлокодинга сопровождались приступами лени, которые были вызваны отсутствием знаний, как решить вопрос возникший вопрос. Через какое-то время ответ сам приходит внезапно. Поэтому никогда не понимал, как люди могут каждый день по 8 часов писать код. Явно не могут и делают что-то еще или тупо просирают время на сайтах. При подходе к дате сдачи проекта - все начинают быстро делать нелюбимую работу и делают ее кое как. Потому столько быдлокода.
Так что программировать можно только когда свободен от рамок времени и выбираеш проект по своему вкусу. Я вот терпеть не могу игры. Зато люблю софт для работы с железом или сетями.
Tags: быдлокодинг
категория: админство