Energy metering as part of SCADA shopping center system
I’ll tell you how I wrote a console program for taking readings from the Mercury 230 counters in a shopping center. Link to the source at the end of the article.
Actually it all started with a SCADA system. We were lucky to get a project for the development and implementation of SCADA for a shopping center. Well, how lucky ... In general, the development of this system is a separate story, which, if the reader is interested, I will tell and share the stuffed cones.
Briefly:
- temperature control in rooms;
- centralized ventilation control on a schedule;
- DHW recharge control;
- metering of water and electricity;
This article is about electricity.
The electricians listened with the choice of the model, because here I did not experience any special problems. I decided to stay on Mercury. For 3 phases - 234th (electricians bought 230) for a single-phase network, the 206th model. Further, it turned out that electricians pushed only three-phase meters throughout the shopping center. Well, I only have less problems. Although I do not understand why.
I previously programmed mainly PLC and small scripts in C #.
The idea was this:
- the interrogator-server keeps energy records in the database;
- SCADA system is responsible for visualization
The survey was implemented simply - in an infinite loop on one RS485 port. In general, for the operation of the dispatching system, I selected MOXA UPORT 1650-16. For the survey I gave only one port, but in order not to create a star (for RS-485 this is not desirable) I used a repeater. It is strange that bourgeois repeaters RS-485 with a large number of inputs were not found. However, there was a domestic contraption Tachyon PRT-1/8 (12) for 12 ports. If it works out, I'll post a video of the work of the whole bunch. Works good. Only MOXA UPORT 1650-16 there are complaints, but that's another story.
I have not even heard about design patterns. Because I made mistakes right away, because carried away by inheritance (and not very competently). According to smart books - it was necessary to apply the composition. In the future, it will be necessary to rework the entire library.
The inheritance chain is as follows:
MeterDevice is a common class for all counters. Implements exchange via COM port with Modbus similar protocol;
Mercury230 - a class with a set of polling functions for a specific counter;
Mercury230_DatabaseSignals - a class with specific counter parameters (currents, voltages, etc.) and the function of updating them. Inheritance in it was not a convenient solution. Because then he drank problems with serializing and deserializing objects. But this class got into the code so firmly that it was impossible to backtrack.
The key to the polling functions was to make the RXmes structure . Store the result of the response and the array of response bytes in it. Any polling function (for example, requesting a serial number) operates within itself with this structure:
Thus, the function of obtaining the serial number of the Mercury 230 counter is as follows:
Who cares to see other functions - you can see the source.
Initially, the simple TCP protocol of the north was simple. SCADe's TCP response looked like this for Mercury 230th.
"Type = merc230 * add = 23 * volt = 1: 221-2: 221-3: 221 * cur = 1: 1.2-2: 1.2-3: 1.2"
Scadu data was parsed and displayed on the icon of the corresponding counter
Everything would be fine , but the customer decided (and rested his horn) that he needed all the data in a table form. Yes, and I wanted to set the limits of all parameters during operation. And going beyond the limits should be indicated.
because SCADA did not know how to display tabular data; I sat down to write a separate program for visualization.
Your protocol has already become especially inconvenient, because the number of parameters grew. For example, for the current appeared upper chapel, accident status, hysteresis of the accident.
It turned out that a separate class was formed for the parameters:
Here the serialization of objects came to the rescue. Having tried Byte, XML and JSON serialization, it was decided to stop on JSON (DataContractJsonSerializer). It was easy to read by eye, the amount of data obtained was less than XML. In general, the DataContractJsonSerializer forgave the lack of a constructor with no arguments. This greatly simplified life.
Of course, the most important moment was - recording meter readings. Because Scada system worked with MySql, then the questionnaire was decided to tie with it. There were no special problems.
The question was only one - “what data to record?”, Because The counter gives quite a few options. Actually the codes for the request:
Initially, it was decided to record consumption per month and per day. In addition, a simple mechanism for taking readings by months for a year was implemented. And control the availability of this data. If there was not enough data, they were appended.
At the moment, the program polls about 70 counters. The console application runs on the server, and the client part runs on the user's workstation.
I post the source of the questionnaire on GitHub . I’ll try to post a link to the client part later.
If anyone did not come across. That is, such a plant to them. Frunze (in Nizhny Novgorod). And their counters work with a very similar protocol. I went over the manuals of both - one to one. But, I heard that there are some differences in the protocols (I haven’t gone into it yet). It is a pity that there is no SET on hand.
Feet of similarity grows from the fact that Mercury was developed by former Frunze workers. So it goes. It is strange why there is more Mercury on hearing.
Actually it all started with a SCADA system. We were lucky to get a project for the development and implementation of SCADA for a shopping center. Well, how lucky ... In general, the development of this system is a separate story, which, if the reader is interested, I will tell and share the stuffed cones.
Briefly:
- temperature control in rooms;
- centralized ventilation control on a schedule;
- DHW recharge control;
- metering of water and electricity;
This article is about electricity.
The electricians listened with the choice of the model, because here I did not experience any special problems. I decided to stay on Mercury. For 3 phases - 234th (electricians bought 230) for a single-phase network, the 206th model. Further, it turned out that electricians pushed only three-phase meters throughout the shopping center. Well, I only have less problems. Although I do not understand why.
I previously programmed mainly PLC and small scripts in C #.
The idea was this:
- the interrogator-server keeps energy records in the database;
- SCADA system is responsible for visualization
Polling method
The survey was implemented simply - in an infinite loop on one RS485 port. In general, for the operation of the dispatching system, I selected MOXA UPORT 1650-16. For the survey I gave only one port, but in order not to create a star (for RS-485 this is not desirable) I used a repeater. It is strange that bourgeois repeaters RS-485 with a large number of inputs were not found. However, there was a domestic contraption Tachyon PRT-1/8 (12) for 12 ports. If it works out, I'll post a video of the work of the whole bunch. Works good. Only MOXA UPORT 1650-16 there are complaints, but that's another story.
Development of the questionnaire itself
I have not even heard about design patterns. Because I made mistakes right away, because carried away by inheritance (and not very competently). According to smart books - it was necessary to apply the composition. In the future, it will be necessary to rework the entire library.
The inheritance chain is as follows:
MeterDevice -> Mercury230 -> Mercury230_DatabaseSignals
MeterDevice is a common class for all counters. Implements exchange via COM port with Modbus similar protocol;
Mercury230 - a class with a set of polling functions for a specific counter;
Mercury230_DatabaseSignals - a class with specific counter parameters (currents, voltages, etc.) and the function of updating them. Inheritance in it was not a convenient solution. Because then he drank problems with serializing and deserializing objects. But this class got into the code so firmly that it was impossible to backtrack.
The key to the polling functions was to make the RXmes structure . Store the result of the response and the array of response bytes in it. Any polling function (for example, requesting a serial number) operates within itself with this structure:
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_;
}
Thus, the function of obtaining the serial number of the Mercury 230 counter is as follows:
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;
}
Who cares to see other functions - you can see the source.
Communication Protocol with SCADA
Initially, the simple TCP protocol of the north was simple. SCADe's TCP response looked like this for Mercury 230th.
"Type = merc230 * add = 23 * volt = 1: 221-2: 221-3: 221 * cur = 1: 1.2-2: 1.2-3: 1.2"
Scadu data was parsed and displayed on the icon of the corresponding counter
Everything would be fine , but the customer decided (and rested his horn) that he needed all the data in a table form. Yes, and I wanted to set the limits of all parameters during operation. And going beyond the limits should be indicated.
because SCADA did not know how to display tabular data; I sat down to write a separate program for visualization.
Your protocol has already become especially inconvenient, because the number of parameters grew. For example, for the current appeared upper chapel, accident status, hysteresis of the accident.
It turned out that a separate class was formed for the parameters:
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;
}
}
Here the serialization of objects came to the rescue. Having tried Byte, XML and JSON serialization, it was decided to stop on JSON (DataContractJsonSerializer). It was easy to read by eye, the amount of data obtained was less than XML. In general, the DataContractJsonSerializer forgave the lack of a constructor with no arguments. This greatly simplified life.
Database
Of course, the most important moment was - recording meter readings. Because Scada system worked with MySql, then the questionnaire was decided to tie with it. There were no special problems.
The question was only one - “what data to record?”, Because The counter gives quite a few options. Actually the codes for the request:
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
}
Initially, it was decided to record consumption per month and per day. In addition, a simple mechanism for taking readings by months for a year was implemented. And control the availability of this data. If there was not enough data, they were appended.
Total
At the moment, the program polls about 70 counters. The console application runs on the server, and the client part runs on the user's workstation.
I post the source of the questionnaire on GitHub . I’ll try to post a link to the client part later.
PS About the similarity of the protocol of Mercury 230 and SET-4tm
If anyone did not come across. That is, such a plant to them. Frunze (in Nizhny Novgorod). And their counters work with a very similar protocol. I went over the manuals of both - one to one. But, I heard that there are some differences in the protocols (I haven’t gone into it yet). It is a pity that there is no SET on hand.
Feet of similarity grows from the fact that Mercury was developed by former Frunze workers. So it goes. It is strange why there is more Mercury on hearing.