Как я софт на c# писал

Январь 14, 2017

Начал я учить летом шарп. Пробовал еще в 2013 бесплатные ролики. А еще раньше в 2006 в пту писал управление релешками с кнопок. У меня тогда была самопальная плата с виртуальным усб на меге8, которая работала как ком порт и плата с 8 реле от платы контроллера с89 для визуального программирования. Плата древняя и мне ее подарил один инторнетный знакомый.

А в этот раз был заказ сделать устройство, чтобы 1-4 spdif потока можно было подключать на 3 устройства, которые преобразуют этот поток и шлют по инторнету или исдн линии в другую студию радио или звукозаписи.
Я сначала подумал, что можно легко сделать на электронных коммутаторах, нашел их, но так лень было делать плату. Решил поискать готовое и нашел что надо.

remote_i

single-bnc-matrix

Причем коммутировать надо с любого рабочего места оператора по сети и чтобы оно работало независимо от инторнета.
Цена этой штуки оказалась 1150 евров и заказчику не понравилась. А ведь это готовый корпус, все сделано хорошо.

У него оказывается уже был простой переключатель при помощи реле и тумблеров, но надо было расширить на компутеры, а в программировании он не понимает.
Когда он сказал, что сигнал и на реле нормально работает без сбоев, то я предложил купить плату с сетевыми реле, которые я до этого смотрел, т.к. хотел сделать открывание замка по клику на кнопку на панели браузера. В ту контору очень часто заходят люди и раньше надо было бегать к домофону, а потом я сделал открывание с исдн телефонов, но возле телефонов не всегда есть люди и можно было сделать с браузера или с мабилы в виде виджета. Но потом шеф решил запилить кодовый замок на входе.

Самое интересное тут в том, что все складывается так, чтобы я продолжал делать интересное и задуманное мной дело. Поэтому мне “сверху” подогнали этот заказ. А вместе с ним заказчик спросил 5 компутеров б\у. И тут я вспомнил, что у меня возле стола стоит мой старый компутер и я все никак не выставлю его на продажу.
А еще до этого я думал купить родителям электронную книгу, потому что прошлая купленная мной была с серым экраном. Я заходил к родителям и еще глянул книгу, т.к. отец говорит, что она глючить начала с сенсорным экраном. А там сам экран уже полосами пошел и ничего не видно. Поэтому я пришел домой с разу заказал покетбук бейсик 2, без плюшек + чехол. Как раз был конец ноября  и в это время быдло очухивается после 11.11, когда понабрали ненужного гавна и решили сдать обратно. А вскрытые коробки уже не продаются как новый товар и имеют скидки чуть ли не 50%. Вот я такую и купил вместе с чехлом за 50 евров.
И в эти же выходные мне как раз звонил заказчик, но меня не было дома, поэтому позвонил в понедельник. А компутер я ему отдал в среду за 50 евров.
Т.е. если деньги отдать на годное дело, то они тут же возвращаются обратно. Только инициатором отдачи должен быть ты сам.
Таким макаром я избавился от неножного ящика, купил книгу (старая как раз в понедельник совсем сдохла) и не потерял ни цента.

Для продолжения банкета я еще получил веб реле, которое заказчик сам купил в Болгарии.
product_207

thumb_32

Все обошлось в 100 евров, правда контроллер несколько не тот оказался. У производителя много всяких таких плат. Тот контроллер выдавал на запрос

http://192.168.1.100/current_state.xml

image

А установить значение реле можно запросом

http://192.168.1.100/current_state.xml?pw=admin&Relay1=1

Довольно все просто по сравнению с дешевой китайской платой за 50 евров.
5698b729a6423

 

В этой китайской плате протокол бинарный и возни там много с ним.
В купленный контроллер 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 реле для других нужд. Тогда заказчик утвердил еще один вход и я отправил ему на согласование схему. 

board

Теперь стоит главная задача заказчика: один вход может подключаться к 1 выходу или более. Но нельзя 2 входа подключить к одному выходу, т.к. сигнал цифровой.

Я впечатлился первой картинкой готового прибора, где были чекбоксы на вебморде.

matrix-switcher

 

Только в этом приборе можно было как угодно соединять. И тут меня заклинило на этих чекбоксах. Надо написать код, чтобы только из одной колонки можно было нажать. Т.е. при нажатии на один чекбокс, все остальные в этой колонке становятся неактивными.

При этом кто-то другой может переключить состояние и надо обновить состояние чекбоксов на первом компутере. Это было настолько гиморно и заняло кучу времени и кода, но не было доведено до конца, т.к. я гуглил всякие решения по опросу контролов на форме в цикле. Хотя можно было бы опять же сделать тупо - у каждого реле есть свой снмп адрес. Скопипастить 15 раз и готово. Быстро и тупо.

Но надо все делать круто и правильно. Должен быть способ. Потом начали лезть в голову мысли из быдловуза про матрицы. Но я так и не понял, как тут что из матриц поможет, особенно если я уже ничего не помню кроме транспонирования и только потому, что у меня в голове образ остался, когда матрицу за край берут и перетаскивают как бы, что она отражается или что-то типа того.

Вобщем тут я что-то приуныл и валялся на диване.

На хабре и в других всяких сцайтах пишут, что типа программировать это легко и даже детей надо учить аж с первого класса. Только все это программирование обычно сводится к уровню hellow world, миганию светодиодом или передвижению картинки  с котиком при нажатии кнопок.

Никто еще из этих популяризаторов не показал реально практичный софт, который был мог написать посетитель таких курсов или читатель уроков.

Программирование - это очень сложно. Это требует кучу времени, а времени дают мало. Нужно укладываться в сроки, а сроки эти нельзя оценить. Удовольствие от программирования можно получать только в своих проектах или когда это не основной доход и время не ограничено.

В остальном это унылое занятие, ничем не лучше других унылых работ.

Значит гуглил я решения, гуглил и наткнулся на радиокнопки в примере цикличного опроса контролов. И тут мне как по башке ударили и пришло озарение, что радиокнопки можно выбрать только одну из. В шарпе надо поместить их в группу для этого.

image

Таким макаром решился вопрос об автоматическом ограничении и недопущении неправильной работы проги или пользователя.

Теперь опять пришлось гуглить, чтобы узнать, почему прошлый пример опроса перестал работать. Оказывается в данном случае я добавил кнопки в группу и они же стали невидимы с формы.

Теперь надо делать так:

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 радиокнопок и надо сопоставить кнопки с нужными реле согласно картинке.

board

 

У многих контролов есть свойство 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:

категория: админство

ответить

Авторизация только через loginza.

Yandex Google Вконтакте Mail.ru Twitter Loginza MyOpenID OpenID