Началось собственно все со SCADA системы. Повезло нам получить проект на разработку и внедрение SCADA для торгового центра. Ну как повезло… В общем разработка этой системы отдельная история, которую, если читателю будет интересно, я расскажу и поделюсь набитыми шишками. Да и система еще полностью не принята, потому пока не будем светить подробностями.
Кратко:
— контроль температур в помещениях;
— централизованное управление вентиляцией по расписанию;
— управление подпиткой ГВС;
— учет воды и электричества;
— sms уведомления.
В этой статье об электричестве.
С выбором модели электрики слушали, потому тут особых проблем не испытал. Решил остановиться на Меркурии. Для 3х фаз – 234тые (электрики купили 230) для однофазной сети 206тая модель. Далее получилось, что электрики насовали по всему ТЦ только трехфазные счетчики. Ну мне только меньше проблем. Хотя не пойму зачем.
Программировал я до этого в основном ПЛК и небольшие скрипты на C#. Но тут решил, что смогу сам сделать энергоучет (хотелось опыта в программировании набраться). Погорячился, конечно.
Идея была такова:
— опросчик-сервер ведет энергоучет в базу данных;
— SCADA система отвечает за визуализацию
Способ опроса
Опрос реализовал просто – в бесконечном цикле по одному порту RS485. Вообще для работы системы диспетчеризации подобрал MOXA UPORT 1650-16. Под опрос отдал только один порт, но чтобы не создавать звезду (для RS-485 это не желательно) воспользовался повторителем. Странно, что буржуйских повторителей RS-485 с большим количеством входов не нашлось. Однако нашлась отечественная штуковина Тахион ПРТ-1/8(12) на 12 портов. Если получиться – выложу видео работы всей связки. Работает хорошо. Только к MOXA UPORT 1650-16 есть претензии, но это уже другая история.
Разработка самого опросчика
Про паттерны проектирования даже не слышал. Потому наделал ошибок сразу, т.к. увлекся наследованием (и не особо грамотно). По умным книжкам – надо было применять композицию. В дальнейшем надо будет переработать всю библиотеку.
Цепочка наследования получилась такая:
MeterDevice -> Mercury230 -> Mercury230_DatabaseSignals
MeterDevice – общий класс для всех счетчиков. Реализует обмен по COM порту c Modbus подобным протоколом;
Mercury230 – класс с набором функций опроса для конкретного счетчика;
Mercury230_DatabaseSignals – класс с конкретными параметрами счетчика (токи, напряжения и т.д.) и функцией их обновления. Наследование в нем было не удобным решением. Т.к. потом хлебнул проблем с сериализацей и десериализацией объектов. Но этот класс так прочно влез в код, что отступать было нельзя.
Ключевым в функциях опроса решил сделать структуру RXmes. В ней хранить результат ответа и сам массив байтов ответа. Любая функция опроса (например, запрос серийного номера) оперирует внутри себя этой структурой:
public enum error_type : int { none = 0,
AnswError = -5, // вернул один или несколько ошибочных ответов
CRCErr = -4,
NoAnsw = -2, // ничего не ответил на запрос после коннекта связи
WrongId = -3, // серийный номер не соответствует
NoConnect = -1 // отсутствие ответа
};
public struct RXmes
{
public error_type err;
public byte[] buff;
public byte[] trueCRC;
public void testCRC()
{
err = error_type.CRCErr;
if (buff.Length < 4)
{
err = error_type.CRCErr;
return;
}
byte[] newarr = buff;
Array.Resize(ref newarr, newarr.Length - 2);
byte[] trueCRC = Modbus.Utility.ModbusUtility.CalculateCrc(newarr);
if ((trueCRC[1] == buff.Last()) && (trueCRC[0] == buff[(buff.Length - 2)]))
{
err = error_type.none;
}
}
public void ReadArr(byte[] b)
{
buff = b;
testCRC();
}
}
public RXmes SendCmd(byte[] data)
{
RXmes RXmes_ = new RXmes();
byte[] crc = Modbus.Utility.ModbusUtility.CalculateCrc(data);
Array.Resize(ref data, data.Length + 2);
data[data.Length - 2] = crc[0];
data[data.Length - 1] = crc[1];
rs_port.Write(data, 0, data.Length);
System.Threading.Thread.Sleep(timeout);
if (rs_port.BytesToRead > 0)
{
byte[] answer = new byte[(int)rs_port.BytesToRead];
rs_port.Read(answer, 0, rs_port.BytesToRead);
RXmes_.ReadArr(answer);
if (RXmes_.err == error_type.none)
{
DataTime_last_contact = DateTime.Now;
}
return RXmes_;
}
RXmes_.err = error_type.NoConnect;
return RXmes_;
}
Таким образом, функция получения серийного номера счетчика Меркурий 230 получилась такая:
public byte[] GiveSerialNumber()
{
byte[] mes = {address, 0x08 , 0};
RXmes RXmes = SendCmd(mes);
if (RXmes.err == error_type.none) {
byte[] bytebuf = new byte[7];
Array.Copy(RXmes.buff, 1, bytebuf, 0, 7);
return bytebuf;
}
return null;
}
Кому интересно посмотреть другие функции – можно посмотреть исходники.
Протокол связи со SCADA
Изначально протокол простого TCP севера был простенький. Ответ SCADе по TCP выглядел для Меркурия 230того так.
«type=mеrc230*add=23*volt=1:221-2:221-3:221*cur=1:1.2-2:1.2-3:1.2»
Скадой данные парсились и выводились на иконку соответствующего счетчика
Все было бы хорошо, но заказчик решил (и уперся рогом), что ему все данные нужны в табличном виде. Да еще и захотел задавать лимиты всех параметров во время работы. А выход за лимиты должен индицироваться.
Порвав на себе волосы, т.к. SCADA табличные данные отображать не умела, сел писать отдельную программку для визуализации.
Свой протокол уже становился особо неудобным, т.к. количество параметров вырастало. Например, для тока появились верхний придел, состояние аварии, гистерезис включения аварии.
Получилось, что для параметров сформировался отдельный класс:
public MetersParameter() {
minalarm = false;
maxalarm = false;
}
public MetersParameter(float min, float max, float hist, float scalefactor = 1)
{
MinValue = min;
MaxValue = max;
Hist = hist;
minalarm = false;
maxalarm = false;
ScalingFactor = scalefactor;
}
public string alias{set; get;}
public float MaxValue { set; get; }
public float MinValue { set; get; }
public float ScalingFactor { set; get; } // коэффициент масштабирования. К примеру Коэффициент трансформации по току
public float Hist { set; get; }
private bool minalarm;
private bool maxalarm;
public bool ComAlarm { get { return MinValueAlarm || MaxValueAlarm ; } }
public virtual bool MinValueAlarm { get{
return minalarm;
} }
public virtual bool MaxValueAlarm { get{
return maxalarm;
} }
public virtual void RefreshData()
{
if (null != ParametrUpdated)
{
ParametrUpdated();
}
if ((MinValue == 0) && (MaxValue == 0))
{
return;
}
float calc_par = parametr * ScalingFactor;
if (calc_par < (MinValue - Hist))
{
minalarm = true;
}
if (calc_par > (MinValue + Hist))
{
minalarm = false;
}
if (calc_par < (MaxValue - Hist))
{
maxalarm = false;
}
if (calc_par > (MaxValue + Hist))
{
maxalarm = true;
}
}
float parametr;
public bool UseScaleForInput = false;
public virtual float Value {
set{
parametr = UseScaleForInput ? value / (ScalingFactor <= 0 ? 1 : ScalingFactor) : value;
RefreshData();
}
get
{
return parametr * ScalingFactor;
}
}
public void CopyLimits(MetersParameter ext_par)
{
this.MinValue = ext_par.MinValue;
this.MaxValue = ext_par.MaxValue;
this.Hist = ext_par.Hist;
}
}
Тут выручила сериализация объектов. Попробовав Байтовую, XML и JSON сериализацию, было решено остановиться на JSON (DataContractJsonSerializer). Она удобно читалась глазом, объем данных получался меньше XML. И вообще DataContractJsonSerializer прощал отсутствие конструктора без аргументов. Это значительно упрощало жизнь.
База данных
Конечно, важнейшим моментом было — запись показаний счётчиков. Т.к. Scada система работала с MySql, то и опросчик было решено завязывать с ней. Тут особых проблем не было.
Вопрос был только один – «какие данные записывать?», т.к. вариантов счетчик дает не мало. Собственно коды для запроса:
public enum peroidQuery : byte
{
afterReset = 0x0,
thisYear = 1,
lastYear = 2,
thisMonth = 3, thisDay = 4, lastDay = 5,
thisYear_beginning = 9,
lastYear_beginning = 0x0A,
thisMonth_beginning = 0x0B,
thisDay_beginning = 0x0C,
lastDay_beginning = 0x0D
}
Изначально было решено записывать потребление за месяц и за сутки. Вдобавок, было реализован простенький механизм снятия показаний по месяцам за год. И контроль наличия этих данных. Если данных не хватало – они дописывались.
Но уже ближе к концу я получаю требование, чтобы в отчетах всегда было написаны показания на начало и на конец периода. Людям, нанятым на обслуживание, не нравилась одна колонка «Потреблено». Пришлось добавлять считывание на начало месяца и суток.
Итог
Получилась немного костыльная, но все же рабочая программа учета электроэнергии. На данный момент программка опрашивает около 70 счетчиков. Консольное приложение крутится на сервере, а клиентская часть работает на АРМе пользователя.
Исходник опросчика выкладываю на GitHub. Ссылку на клиентскую часть постараюсь выложить позже.
P.S. Про сходство протокола Меркурия 230 и СЭТ-4тм
Если кто не сталкивался. То есть такой Завод им. Фрунзе (в Нижнем Новгороде). И счетчики у них работают с очень похожим протоколом. Пробегался по мануалам обоих – один в один. Но, слышал, что в протоколах есть какие-то различия (пока не вдавался). Жаль, что на руках нет СЭТа.
Ноги сходства растут из того, что Меркурии разработаны бывшими работниками Фрунзе. Такие дела. Странно, почему на слуху больше Меркурий.
Комментарии (17)
Romer
16.06.2017 09:55Поздравляю с дебютом! Приятно видеть появление статей тех, кто работает на твоем решении.
Насчет статьи про Scada+ — будет очень полезно, со своей стороны постараюсь не остаться в долгу и обещаю бонусы!
По некоторым моментам, которые прочитал в статье применительно к скаде (например про табличный вывод) — напишу уже Вам напрямую, есть отработанные решения как это сделать средствами скады.levWi
16.06.2017 16:06В том то и проблема — вы как разработчик лучше понимаете, как рантайм SCADA+ работает. Знаете подводные камни. А я как с черным ящиком работаю, боюсь чего-то добавлять — чтобы система валиться не начала.
В любом случае от заказчика уже неприятный осадок остался. Не хочу им больше ничего делать. Пускай так сидят.
Постараюсь расписать что мне понравилось, что нет (с точки зрения пользователя). Благо довелось запустить проект с MasterSCADA — есть с чем сравнить.
ajratr
16.06.2017 15:47Скажите пожалуйста, а сколько времени ушло на разработку?
levWi
16.06.2017 15:51Сложно сказать. ТЦ с 14го года строится… Параллельно мы разные объекты делали.
Наверно чистого времени 2-3 месяца по АСКУЭ… По SCADе дольше, и не всегда по нашей винеajratr
16.06.2017 16:45А почему не воспользовались готовым решением?
Та же Simple Scada с OPC сервером предлагается по очень привлекательной цене.levWi
16.06.2017 18:34Не видел их цеников. 9 параметров (ток напряжение косинус) на счетчик * 70 счетчиков это уже 630 точек.
Тогда уже надо брать готовую программу учета и крутить ее отдельно — мне кажется дешевле будет.andranick
16.06.2017 18:56Ну как бы ток, напряжение и косинус относятся к оперативным параметрам (измерениям текущим), их малоинтересно опрашивать, если в цепочке последовательно 70 счетчиков, слишком большой период опроса. Тогда уж, возможно, интереснее вытягивать данные, которые интегрируются самим счетчиком за определенный период — профили мощности за 30 минут, потребленная активная/реактивная энергия накопительным итогом (со сброса), на начало месяца, с начала месяца и т.п. — эти параметры можно вычитывать достаточно долго и для целей технического учета электроэнергии этого достаточно. Есть проект, в котором наверх тянутся и оперативные, и интегральные параметры. Так заказчиком оперативные используются только как «для справки», в основном используются профили мощности для контроля потребления и параметры энергии — для генерации отчетов.
Так что точек может быть и значительно больше 630.levWi
16.06.2017 19:31Тут тоже только для справки. Забавно что с нас эти параметры требовали. Мы сделали. Но ими не пользуются сейчас. Пока что только отчёты генерируют
Siemargl
16.06.2017 23:02SCADA и АИИСКУЭ ортогональны. Совмещать одно с другим неудобно.
Потому СКАДы не предназначены для задач учета, и наоборот.
Igorjan
Судя по отсутствию жалоб на бумажную волокиту, учет не коммерческий?
Проверяется ли разница в показаниях на вводе и сумм у конечных потребителей?
по «звездатору» — использовали такой
levWi
Вот за такой девайс — спасибо. Не находил.
Если под коммерческой понимается выставление счетов арендаторам, то коммерческий. Уже генерацию делать можно по потребителям и с привязкой к «административным зонам» (как я их называю). Генерирую им в формате *.html
Требование к проверкам разницы ввода и конечных потребителей было. Только мне до сих пор не могут дать «архитектуру» сети. ТЦ как бы уже работает. Только что откуда запитано не совсем понятно (подрядчика, который монтировал прогнали, да и исполнительную документацию нормальную не получили). Пока думаю, как расчет сделать гибко.
Похожая проблема была с водоучетом. Но с ним уже сделал им и контроль суммы. Правда выданная мне формула от «главного инженера» объекта настолько глупая, что всегда будет давать 0 кубов разницы.
andranick
Вот как бы не советую использовать словосочетание «коммерческий учет». Технический. А то к системам АСКУЭ и требования нехилые, и спрос не детский. А АСТУЭ — для внутреннего потребления заказчика, если его все устраивает, — наздоровье, это дело только заказчика. Типа чтоб для съема показаний по счетчикам «бегунка» не посылать.
levWi
Значит АСТУЭ. Подходит