public static byte[] OpenCnlReq(int devAddr, string uroven, string pass) { byte[] temp_pass = new byte[4]; byte[] buf_pass = Encoding.ASCII.GetBytes(pass); for (int f = 0; f < 6; f++) { Array.Copy(buf_pass, f, temp_pass, 0, 1); int temp_int = BitConverter.ToInt32(temp_pass, 0) - 48; temp_pass = BitConverter.GetBytes(temp_int); Array.Copy(temp_pass, 0, buf_pass, f, 1); } byte[] openCnl = new byte[11]; openCnl[0] = (byte)devAddr; openCnl[1] = 0x01; //команда запроса на открытие канала openCnl[2] = Convert.ToByte(Convert.ToInt16(uroven)); // Ввод уровня доступа пока без проверки, по умолчанию 1.... 0x01; //Уровень доступа 1 Array.Copy(buf_pass, 0, openCnl, 3, 6); res = CrcFunc.CalcCRC16(openCnl, 9); //получить контрольную сумму openCnl[openCnl.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки openCnl[openCnl.Length - 1] = (byte)(res / 256); return(openCnl); }
public static byte[] DataReq(int devAddr, string Param, int bwri) { byte[] data = new byte[6]; int bwrim = bwri & 0xf0; data[0] = (byte)devAddr; data[1] = 0x08; //команда чтения зафиксированных данных if (Param == "14h") { data[2] = 0x14; data[3] = (byte)bwri; //параметр зафиксированных данных } else { if (bwrim == 0xf0) { data[1] = 0x05; // Переход на функцию 0x05 для чтения энергии от сброса при использовании чтения счетчика кодом 0x08 и параметром 0x16 data[2] = 0x00; data[3] = Convert.ToByte(bwri & 0x0f); // Запись # тарифа } else { data[2] = 0x16; data[3] = Convert.ToByte(bwri); // Запись BWRI кода } } res = CrcFunc.CalcCRC16(data, 4); //получить контрольную сумму data[data.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки data[data.Length - 1] = (byte)(res / 256); return(data); }
public static byte[] ReadRomReq(int devAddr, int energy, int numRom, int startAddr, int Quantity) { byte NumRom = 0; byte Energy = 0; byte[] readrom = new byte[8]; if (numRom == 3) { Energy = (byte)((energy & 0x07) << 4); NumRom = startAddr > 0xffff ? (byte)((numRom & 0x0f) | 0x80) : (byte)(numRom & 0x0f); } else { Energy = 0x00; } readrom[0] = (byte)devAddr; readrom[1] = 0x06; // 2.4 Ускоренный режим чтения по физическим адресам памяти readrom[2] = (byte)(NumRom | Energy); // Вид энергии, номер памяти readrom[3] = (byte)(startAddr / 256); // Старший байт адреса TEST readrom[4] = (byte)(startAddr % 256); // Младший байт адреса TEST readrom[5] = (byte)Quantity; // количество байт TEST res = CrcFunc.CalcCRC16(readrom, 6); //получить контрольную сумму readrom[readrom.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки readrom[readrom.Length - 1] = (byte)(res / 256); return(readrom); }
public static ushort res; //резервирование ответа контрольной суммы public static byte[] TestCnlReq(int devAddr) { byte[] testCnl = new byte[4]; testCnl[0] = (byte)devAddr; testCnl[1] = 0x00; //команда запроса на тестирование канала res = CrcFunc.CalcCRC16(testCnl, 2); //получить контрольную сумму testCnl[testCnl.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки testCnl[testCnl.Length - 1] = (byte)(res / 256); return(testCnl); }
public static byte[] KuiReq(int devAddr) { byte[] kui = new byte[5]; kui[0] = (byte)devAddr; kui[1] = 0x08; // 2.3 Запрос на чтение параметров kui[2] = 0x02; // 2.3.3 Прочитать коэффициент трансформации счетчика res = CrcFunc.CalcCRC16(kui, 3); //получить контрольную сумму kui[kui.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки kui[kui.Length - 1] = (byte)(res / 256); return(kui); }
public static byte[] FixDataReq(int devAddr) { byte[] fixData = new byte[5]; fixData[0] = (byte)devAddr; fixData[1] = 0x03; //команда записи fixData[2] = 0x08; //параметр фиксации данных res = CrcFunc.CalcCRC16(fixData, 3); //получить контрольную сумму fixData[fixData.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки fixData[fixData.Length - 1] = (byte)(res / 256); return(fixData); }
public static byte[] CurTimeReq(int devAddr) { byte[] curtime = new byte[5]; curtime[0] = (byte)devAddr; curtime[1] = 0x04; // 2.1 Запросы на чтение массивов времен (код 0x04) curtime[2] = 0x00; // Запрос на чтение текущего времени (параметр 0x00) res = CrcFunc.CalcCRC16(curtime, 3); //получить контрольную сумму curtime[curtime.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки curtime[curtime.Length - 1] = (byte)(res / 256); return(curtime); }
public static byte[] InfoReq(int devAddr) { byte[] info = new byte[5]; info[0] = (byte)devAddr; info[1] = 0x08; // 2.3.2 Ускоренный режим чтения индивидуальных параметров info[2] = 0x01; // Серийный номер, дата выпуска, версия ПО, вариант исполнения res = CrcFunc.CalcCRC16(info, 3); //получить контрольную сумму info[info.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки info[info.Length - 1] = (byte)(res / 256); return(info); }
public static byte[] EnergyPReq(int devAddr, int tarif) { byte[] energy = new byte[6]; energy[0] = (byte)devAddr; energy[1] = 0x05; // 2.2 Запросы на чтение массивов регистров накопленной энергии energy[2] = 0x60; // Параметр чтения накопленной энергии A+ от сброса по фазам energy[3] = (byte)tarif; // Номер тарифа res = CrcFunc.CalcCRC16(energy, 4); //получить контрольную сумму energy[energy.Length - 2] = (byte)(res % 256); //Добавить контрольную сумму к буферу посылки energy[energy.Length - 1] = (byte)(res / 256); return(energy); }
/// <summary> /// Отправка команды без параметров /// </summary> /// <param name="devAddr"></param> /// <param name="Com"></param> /// <param name="Data"></param> /// <returns></returns> public static byte[] WriteComReq(int devAddr, int Com, byte[] Data = null) { int cnt = 2; if (Data != null) { cnt = 2 + Data.Length; } byte[] com = new byte[cnt + 2]; com[0] = (byte)devAddr; com[1] = (byte)Com; // Отправка команды без параметров if (Data != null) { Array.Copy(Data, 0, com, 2, Data.Length); // Копируем блок данных при его наличии } res = CrcFunc.CalcCRC16(com, cnt); // получить контрольную сумму com[com.Length - 2] = (byte)(res % 256); // добавить контрольную сумму к буферу посылки com[com.Length - 1] = (byte)(res / 256); return(com); }
/// <summary> /// Отправка команды с параметрами /// </summary> /// <param name="devAddr"></param> /// <param name="Com"></param> /// <param name="Par"></param> /// <param name="Data"></param> /// <returns></returns> public static byte[] WriteCompReq(int devAddr, int Com, int Par, byte[] Data = null) //, bool dataYes = false { int cnt = 3; if (Data != null) { cnt = 3 + Data.Length; } byte[] comp = new byte[cnt + 2]; comp[0] = (byte)devAddr; comp[1] = (byte)Com; // Отправка команды c параметром comp[2] = (byte)Par; // Отправка команды c параметром if (Data != null) { Array.Copy(Data, 0, comp, 3, Data.Length); // Копируем блок данных при его наличии } res = CrcFunc.CalcCRC16(comp, cnt); // получить контрольную сумму comp[comp.Length - 2] = (byte)(res % 256); // добавить контрольную сумму к буферу посылки comp[comp.Length - 1] = (byte)(res / 256); return(comp); }
public override void SendCmd(Command cmd) { base.SendCmd(cmd); lastCommSucc = false; bool WriteOk = false; // Идентификатор успешной записи mask_ch_wr = 0; // переменная для параметра MASK_CH записи данных каналов (Регистратор импульсов) mask_chv_wr = 0; // Переменная для параметра MASK_CH записи Веса импульсов (Регистратор импульсов) byte cmdCode = 0x00; // переменная для байта запроса CmdCode - параметр F протокола (номера для записи) byte[] byteData = new byte[1]; // Буфер для значения переменной double cmdVal = cmd.CmdVal; int cmdNum = cmd.CmdNum; int cmdCnl = ActiveCmd[cmdNum]; // Чтение индекса команды по ключу из Словаря if (cmd.CmdTypeID == BaseValues.CmdTypes.Standard) { byte[] cmdcode = ScadaUtils.HexToBytes(devTemplate.CmdGroups[cmdCnl].CmdCode, true); // Чтение строки HEX из параметра CmdCode cmdCode = cmdcode[0]; string cmdtype = devTemplate.CmdGroups[cmdCnl].CmdType; // Чтение строки Типа переменной команды // Определив диапазон проверяем к какому из них относятся Текущие параметры и Веса импульса для составления маски if ((cmdNum >= startCnl && cmdNum <= maxch) || (cmdNum >= startCnlv && cmdNum <= maxchv)) { if ((cmdNum >= startCnl && cmdNum <= maxch) && !(cmdNum >= startCnlv && cmdNum <= maxchv)) { mask_ch_wr = BitFunc.SetBit(mask_ch_wr, cmdNum - startCnl, true); // Если каналы относятся к Текущим данным, то формируем маску для записи маски текущих данных } else { mask_chv_wr = BitFunc.SetBit(mask_chv_wr, cmdNum - startCnlv, true); // Иначе для записи маски Весов импульсов } } if (cmdtype == "uint16") { Array.Resize(ref byteData, 2); byteData = BitConverter.GetBytes(Convert.ToUInt16(cmdVal)); } else if (cmdtype == "float") { Array.Resize(ref byteData, 4); byteData = BitConverter.GetBytes(Convert.ToSingle(cmdVal)); } else if (cmdtype == "double") { Array.Resize(ref byteData, 8); byteData = BitConverter.GetBytes(cmdVal); } else if (cmdtype == "DateTime") { Array.Resize(ref byteData, 6); DateTime dateTime = DateTime.FromOADate(cmdVal); byteData[0] = Convert.ToByte(dateTime.Year - 2000); byteData[1] = Convert.ToByte(dateTime.Month); byteData[2] = Convert.ToByte(dateTime.Day); byteData[3] = Convert.ToByte(dateTime.Hour); byteData[4] = Convert.ToByte(dateTime.Minute); byteData[5] = Convert.ToByte(dateTime.Second); } if (cmdCode == 0x0B) { Array.Resize(ref byteData, 8); // Увеличить размер буфера до 8 байт записываемого параметра F=0x0B PARAM_VAL_NEW } Buf_Out(cmdCnl, cmdCode, byteData, false); // отправить в функцию Номер индекса команды управления и Байт запроса Connection.Write(buf_out, 0, buf_out.Length, CommUtils.ProtocolLogFormats.Hex, out logText); //послать запрос в порт ExecWriteToLog(logText); // вывести запрос в Журнал линии связи readcnt = Connection.Read(buf_in, 0, buf_in.Length, ReqParams.Timeout, CommUtils.ProtocolLogFormats.Hex, out logText); //считать значение из порта ExecWriteToLog(logText); // вывести запрос в Журнал линии связи // Проверка выполнения команды прибором - определяется по ответу прибора на запись команды if (readcnt == buf_in.Length || readcnt == 11) { crc = CrcFunc.CalcCRC16(buf_in, readcnt); // Рассчет CRC16 полученного ответа, при совпадении должно вернуть 0 при расчете CRC16(Modbus) и полного буфера вместе с CRC byte fCode = buf_in[4]; // Чтение кода команды F Array.Copy(buf_in, readcnt - 4, byteIDres, 0, 2); if (!(crc == 0 & fCode != 0 & byteID.SequenceEqual(byteIDres))) // Проверка CRC, параметра F и ID запроса { if (crc != 0) { ExecWriteToLog(CommPhrases.ResponseCrcError); } else if (fCode == 0) { string err = Error_code(buf_in[6]); ExecWriteToLog(CommPhrases.IncorrectCmdData + " - " + err); // При некорректном запросе F будет равен 0x00 } else if (!byteID.SequenceEqual(byteIDres)) { ExecWriteToLog("ID ответа не совпадает с ID запроса"); // При несовпадении ID } FinishRequest(); } else { if (fCode == 0x03 || fCode == 0x08) { byte[] maskchRes = new byte[4]; Array.Copy(buf_in, 6, maskchRes, 0, 4); if (maskch.SequenceEqual(maskchRes)) { WriteOk = true; } } if (fCode == 0x05) { if (buf_in[6] != 0) { WriteOk = true; } } if (fCode == 0x0B) { UInt16 Result_WR = BitConverter.ToUInt16(buf_in, 6); if (Result_WR == 0) { WriteOk = true; } } if (WriteOk) { lastCommSucc = true; string nameCnl = ActiveCnl.Find(c => c.Cnl == cmdNum).Name; ExecWriteToLog($"Запись команды {nameCnl} - " + CommPhrases.ResponseOK); } else { ExecWriteToLog(CommPhrases.WriteDataError); } FinishRequest(); } } else { if (readcnt == 0) { ExecWriteToLog(CommPhrases.ResponseError); // Нет ответа по Timeout - Ошибка связи! } else { ExecWriteToLog(CommPhrases.IncorrectResponseLength); // Некорректная длина ответа } FinishRequest(); } } else { WriteToLog(CommPhrases.IllegalCommand); } CalcCmdStats(); }
// Сессия опроса ------------------------------------------------------------------------------------------------------------------------------------ public override void Session() { base.Session(); // Опрос должен происходить согласно активности списка запросов по Словарю ActiveSnd if (!fileyes) // Если конфигурация не была загружена, выставляем все теги в невалидное состояние и выходим { InvalidateCurData(); return; } for (int i = 0; i < ActiveSnd.Count; i++) { int sndCnt_ = ActiveSnd.Values.ElementAt(i); // Выполняем запросы поочередно по индексам из словаря Активных запросов byte[] sndcode = ScadaUtils.HexToBytes(devTemplate.SndGroups[sndCnt_].SndCode, true); // Чтение строки HEX из параметра SndCode sndcode_ = sndcode[0]; // ------------------ Тут вызвать формирование буфера запроса -------------------------------------- Buf_Out(sndCnt_, sndcode_, null, true); // отправить в функцию Номер и Байт запроса if (lastCommSucc) { lastCommSucc = false; int tryNum = 0; // Счетчик для корректных ответов // Выполняем опрос если был загружен файл конфигурации while (RequestNeeded(ref tryNum)) { Connection.Write(buf_out, 0, buf_out.Length, CommUtils.ProtocolLogFormats.Hex, out logText); //послать запрос в порт ExecWriteToLog(logText); // вывести запрос в Журнал линии связи readcnt = Connection.Read(buf_in, 0, buf_in.Length, ReqParams.Timeout, CommUtils.ProtocolLogFormats.Hex, out logText); //считать значение из порта ExecWriteToLog(logText); // вывести запрос в Журнал линии связи // ------------------------------Тут проверка на корректность ответа - ID запроса и CRC ------------------------------------------------------------------- var valCnt_ = devTemplate.Values.FindIndex(x => x.ValCnt == ActiveSnd.ElementAt(i).Key); // Разбираем ответ поочередно по индексам из Списка Активных запросов if (readcnt == buf_in.Length || readcnt == 11) { crc = CrcFunc.CalcCRC16(buf_in, readcnt); // Рассчет CRC16 полученного ответа, при совпадении должно вернуть 0 при расчете CRC16(Modbus) и полного буфера вместе с CRC byte fCode = buf_in[4]; Array.Copy(buf_in, readcnt - 4, byteIDres, 0, 2); if (!(crc == 0 & fCode != 0 & byteID.SequenceEqual(byteIDres))) // Проверка CRC, параметра F и ID запроса { if (crc != 0) { ExecWriteToLog(CommPhrases.ResponseCrcError); } else if (fCode == 0) { string err = Error_code(buf_in[6]); ExecWriteToLog(CommPhrases.IncorrectCmdData + " - " + err); // При некорректном запросе F будет равен 0x00 } else if (!byteID.SequenceEqual(byteIDres)) { ExecWriteToLog("ID ответа не совпадает с ID запроса"); // При несовпадении ID } FinishRequest(); invalidData(valCnt_); // выставить сигналы в невалидное состояние } else { int index_bufin = 6; // Индекс первой переменной в ответе прибора for (int sig = 0; sig < devTemplate.Values[valCnt_].Vals.Count; sig++) // Разбор по количеству переменных Vals в ответе { if (devTemplate.Values[valCnt_].Vals[sig].SigActive) // Если переменная активна, читаем и разбираем ее { string sig_type = devTemplate.Values[valCnt_].Vals[sig].SigType; // читаем тип переменной double range = devTemplate.Values[valCnt_].Vals[sig].Range; // читаем множитель (мало ли, вдруг пригодится) :) int k = ActiveCnl.Find(s => s.Cnl == devTemplate.Values[valCnt_].Vals[sig].SigCnl).IdxTag; // Находим в списке Индекс переменной и Указываем индекс Тега if (sig_type == "float") { SetCurData(k, BitConverter.ToSingle(buf_in, index_bufin) * range, 1); // Конвертируем буфер байт в переменную float } else if (sig_type == "double") { SetCurData(k, BitConverter.ToDouble(buf_in, index_bufin) * range, 1); // Конвертируем буфер байт в переменную double } else if (sig_type == "uint16") { SetCurData(k, BitConverter.ToUInt16(buf_in, index_bufin) * range, 1); // Конвертируем буфер байт в переменную UInt16 } else if (sig_type == "uint32") { SetCurData(k, BitConverter.ToUInt32(buf_in, index_bufin) * range, 1); // Конвертируем буфер байт в переменную UInt32 } else if (sig_type == "DateTime") // Определяем системное время и конвертируем в double для Scada { if (!myTagId.ContainsKey(devTemplate.Values[valCnt_].Vals[sig].SigCnl)) // Указываем номер сигнала для преобразования в текстовую строку { // в окне Данных КП Коммуникатора myTagId.Add(devTemplate.Values[valCnt_].Vals[sig].SigCnl, "DateTime"); } int year = Convert.ToInt32(buf_in[index_bufin]) + 2000; // Читаем из ответа переменные года int month = Convert.ToInt32(buf_in[index_bufin + 1]); // месяца int day = Convert.ToInt32(buf_in[index_bufin + 2]); // дня int hour = Convert.ToInt32(buf_in[index_bufin + 3]); // часа int minute = Convert.ToInt32(buf_in[index_bufin + 4]); // минут int second = Convert.ToInt32(buf_in[index_bufin + 5]); // секунд DateTime dateTime = new DateTime(year, month, day, hour, minute, second); // формируем переменную времени в формате DateTime SetCurData(k, dateTime.ToOADate(), 1); } if (devTemplate.Values[valCnt_].ValCnt == xValCnt01 || devTemplate.Values[valCnt_].ValCnt == xValCnt07) { if (sig_type == "float" || sig_type == "uint32") // Найденная ошибка, не учитывалось увеличение индекса при наличии uint32 переменной { index_bufin = index_bufin + 4; // Увеличиваем индекс переменной для следующего текущего параметра для float } else if (sig_type == "double") { index_bufin = index_bufin + 8; // Увеличиваем индекс переменной для следующего текущего параметра для double } } } } ExecWriteToLog(CommPhrases.ResponseOK); lastCommSucc = true; FinishRequest(); } } else { if (readcnt == 0) { ExecWriteToLog(CommPhrases.ResponseError); // Нет ответа по Timeout - Ошибка связи! } else { ExecWriteToLog(CommPhrases.IncorrectResponseLength); // Некорректная длина ответа } FinishRequest(); invalidData(valCnt_); // выставить сигналы в невалидное состояние } // завершение запроса tryNum++; } } } CalcSessStats(); // расчёт статистики }
// --------------------------------------------- Формирование буфера для команд чтения и команд записи private void Buf_Out(int Num, byte Fcode, byte[] bData, bool read) // формирование буфера отправки в порт Num = Номер индекса запроса или команды { // Fcode = параметр команды SndCode или CmdCode, read = true - чтение, выполняются запросы Snd или read = false, выполняются команды if (read) { // Тут собраны команды чтения if (Fcode == 0x01 || Fcode == 0x07) // Если код равен F=0x01 - Текущие параметры или F=0x07 - Вес омпульсов { Array.Resize(ref buf_out, 14); // Меняем размер буфера для запроса Текущих параметров и Веса импульсов if (Fcode == 0x01) { maskch = BitConverter.GetBytes(mask_ch); // запись битовой маски Текущих параметров в массив байт Array.Resize(ref buf_in, col * res_ch + 10); // длина ответа 4 * n каналов (или 8 * n каналов) + 10 байт } else if (Fcode == 0x07) { maskch = BitConverter.GetBytes(mask_chv); // запись битовой маски Веса импульсов в массив байт Array.Resize(ref buf_in, 4 * res_chv + 10); // длина ответа 4 * n каналов + 10 байт } Array.Copy(maskch, 0, buf_out, 6, maskch.Length); // Копирование маски в буфер запроса } else if (Fcode == 0x04) // Если код равен F=0x04 - Системное время { Array.Resize(ref buf_out, 10); // Меняем размер буфера для запроса Системного времмени Array.Resize(ref buf_in, 16); // длина ответа 16 байт } else if (Fcode == 0x0A) // Если код равен F=0x0A - Параметры прибора { Array.Resize(ref buf_out, 12); // Меняем размер буфера для запроса Параметров прибора byte[] snddata = ScadaUtils.HexToBytes(devTemplate.SndGroups[Num].SndData, true); // Чтение строки HEX из параметра SndData buf_out[6] = snddata[0]; // требуется 1 байт, код параметра buf_out[7] = 0x00; // второй байт будет со значением 0 Array.Resize(ref buf_in, 18); // длина ответа 18 байт } } else // Тут собраны команды записи { if (Fcode == 0x03 || Fcode == 0x08) // Если код равен F=0x03 – код функции записи текущих показаний { // Или F=0x08 - Вес импульса для Регистраторов импульса Array.Resize(ref buf_out, 0x0E + bData.Length); // Меняем размер буфера для запроса Текущих параметров maskch = Fcode == 0x03 ? BitConverter.GetBytes(mask_ch_wr) : BitConverter.GetBytes(mask_chv_wr); // запись битовой маски редактируемого канала в массив байт Array.Copy(maskch, 0, buf_out, 6, maskch.Length); // Копирование маски в буфер запроса Array.Copy(bData, 0, buf_out, 10, bData.Length); // Копируем значение cmdVal в буфер запроса Array.Resize(ref buf_in, 14); // длина ответа 14 байт } else if (Fcode == 0x05) // Запись времени в прибор { Array.Resize(ref buf_out, 10 + bData.Length); Array.Copy(bData, 0, buf_out, 6, bData.Length); Array.Resize(ref buf_in, 14); // длина ответа 14 байт } else if (Fcode == 0x0B) // Запись параметров в прибор { Array.Resize(ref buf_out, 12 + bData.Length); Array.Copy(bData, 0, buf_out, 8, bData.Length); byte[] cmddata = ScadaUtils.HexToBytes(devTemplate.CmdGroups[Num].CmdData, true); // Чтение строки HEX из параметра SndData buf_out[6] = cmddata[0]; // требуется 1 байт, код параметра buf_out[7] = 0x00; // второй байт будет со значением 0 Array.Resize(ref buf_in, 12); // длина ответа 12 байт } } buf_out[4] = Fcode; // Копируем в буфер код запроса F buf_out[5] = Convert.ToByte(buf_out.Length); // Запись длины массива запроса - параметр L idBCD = BitConverter.GetBytes(ConvFunc.DecToBCD(Address)); // Преобразование адреса в BCD формат ConvFunc.Reverse_array(idBCD, false); // Переворот буфера старшим байтом вперед Array.Copy(idBCD, 0, buf_out, 0, idBCD.Length); // Копирование адреса в буфер запроса byteID = BitConverter.GetBytes((ushort)rnd.Next(1, 65535)); // Сформировать случайный ID запроса buf_out[buf_out.Length - 4] = byteID[0]; buf_out[buf_out.Length - 3] = byteID[1]; crc = CrcFunc.CalcCRC16(buf_out, buf_out.Length - 2); // Рассчет контрольной суммы CRC16 buf_out[buf_out.Length - 2] = (byte)(crc % 256); // Запись младшего байта контрольной суммы в буфер buf_out[buf_out.Length - 1] = (byte)(crc / 256); // Запись старшего байта контрольной суммы в буфер }