using System; using Lers; using Lers.Poll; using System.Collections.Generic; namespace Vkt5_RemoteConsole { delegate void VktDataReadEventHandler(byte[] dataByte); enum Vkt5Key: byte { Right = 0, Left = 1, Down = 2, Up = 3, Tab = 4, Enter = 5, Back = 6, Menu = 7, LongMenuPress = 9 } /// /// Класс для отправки запросов и считывания ответов от ECL 300 /// ВНИМАНИЕ! После считывания ответа перед повторным запросом к прибору нужно выждать 400мс /// class Vkt5Console { #region События и методы для протоколирования /// /// Вызвается при ошибке выполнения запроса /// public event EventHandler RequestError; private void CallRequestError() { if (this.RequestError != null) this.RequestError(this, EventArgs.Empty); } /// /// Событие поднимается когда необходимо запротоколировать ошибку чтения данных /// public event WriteLogEventHandler WriteLog; private void LogMessage(string msg) { if (this.WriteLog != null) { this.WriteLog(Importance.Info, msg); } } private void LogWarning(string msg) { if (this.WriteLog != null) { this.WriteLog(Importance.Warn, msg); } } private void LogError(string msg) { if (this.WriteLog != null) { this.WriteLog(Importance.Error, msg); } } #endregion /// /// Сетевой адрес прибора /// public int NetworkAddress { get; set; } /// /// Конструктор /// /// public Vkt5Console(RemoteConsole remoteConsole) { this.NetworkAddress = 0; this.remoteConsole = remoteConsole; this.remoteConsole.DeviceDataReceived += new EventHandler(remoteConsole_DeviceDataReceived); } /// /// Сброс состояния объекта в исходную позицию /// public void Reset() { this.handlers.Clear(); } /// /// Отправляет команду на чтение буфера экрана устройства /// /// public void ReadScreenBuffer(VktDataReadEventHandler readHandler) { byte[] rq = PackRequest(new byte[] { 0x03, 0x0C, 0x00, 0x00, 0x00 }); SendRequest(rq, 38, readHandler); } /// /// Отправляет в устройство команду нажатия клавишы /// /// /// public void SendKeyCode(Vkt5Key key, VktDataReadEventHandler readHandler) { byte[] rq = PackRequest(new byte[] { 0x10, 0x0D, 0x00, 0x00, 0x01, 0x01, (byte)key }); SendRequest(rq, 8, readHandler); } /// /// Формирует пакет с запросом прибору /// /// /// private byte[] PackRequest(byte[] rqData) { List request = new List(); // Сетевой адрес request.Add((byte)this.NetworkAddress); // Пакет с запросом request.AddRange(rqData); ushort crc = ModBusCrc.Calc(request); request.Add((byte)(crc & 0x00FF)); request.Add((byte)((crc & 0xFF00) >> 8)); return request.ToArray(); } /// /// Отправка запроса прибору /// /// /// /// private async void SendRequest(byte[] request, int responseLength, VktDataReadEventHandler readHandler) { var cmd = new CommandSettings("", 1000, responseLength, 10); this.handlers.AddLast(readHandler); this.responseLength = responseLength; try { await remoteConsole.SendCommandAsync(request, cmd, 0); } catch (Exception e) { // При ошибке отправки данных отключаемся от устройства LogError("Ошибка чтения данных. " + e.Message); } } private async void remoteConsole_DeviceDataReceived(object sender, DeviceDataEventArgs args) { // Проверим, что консоль подключена к прибору if (!this.remoteConsole.IsConnected) { return; } // Получаем обработчик запроса VktDataReadEventHandler currentHandler = this.handlers.First.Value; this.handlers.RemoveFirst(); // Проверим длину считанного пакета if (args.Response.Length < 5) { // Повторяем запрос LogError("Неверная длина ответа прибора. Ожидалось не менее 5 байт, получено " + args.Response.Length); CallRequestError(); // Отключаемся try { await remoteConsole.DisconnectAsync(); } catch (LersException exc) { LogError("Ошибка отключения удалённого пульта. " + exc.Message); } return; } // Проверим контрольную сумму ushort calcCs = ModBusCrc.Calc(args.Response, 0, args.Response.Length - 2); ushort recvCrc = BitConverter.ToUInt16(args.Response, args.Response.Length - 2); if (calcCs != recvCrc) { LogWarning("Не сходится контрольная сумма ответа"); // Повторяем запрос SendRequest(args.Request, this.responseLength, currentHandler); return; } // Проверим сетевой адрес if (args.Response[0] != (byte)this.NetworkAddress) { LogWarning("В ответе указан неверный контрольный адрес"); // Повторяем запрос SendRequest(args.Request, this.responseLength, currentHandler); return; } // Возвращаем считанные прибором данные if (currentHandler != null) { byte[] rspData = new byte[args.Response.Length - 5]; Array.Copy(args.Response, 3, rspData, 0, rspData.Length); currentHandler(rspData); } } /// /// Объект для отправки запросов и считывания ответов от удалённого прибора /// private RemoteConsole remoteConsole = null; /// /// Длина ответа на запрос /// private int responseLength = 0; /// /// Список обработчиков ответов /// LinkedList handlers = new LinkedList(); } class ModBusCrc { /// /// Вычисляет контрольную сумму для переданного блока данных, согласно /// спецификации протокола ModBus (Modicon Modbus Protocol Reference Guide) /// /// Для расчета контрольной суммы используется модифицированный CRC-16-IBM с полиномом 0xA001. /// В отличие от стандартного алгоритма CRC-16-IBM, начальное значение устанавливается равным 0xFFFF /// /// Массив байт, по которому нужно посчитать CRC /// Начальный индекс в массиве байт /// Количество байт, начиная со start, используемых в расчете контрольной суммы /// Значение контрольной суммы public static ushort Calc(IList input, int start, int length) { int i, j; if (length > input.Count) throw new Exception(String.Format("При расчете контрольной суммы по блоку данных длиной {0} байт, указана длина {1} байт", input.Count, length)); // Для хранения CRC используем тип int, чтобы не заниматься приведением типов // постоянно, а сделать это только при выходе // При расчете CRC значение никогда не выйдет за границы 2-х младших байт (сдвигаем всегда вправо) // поэтому при операциях сдвига, сдвиг знакового бита переменной crc не обрабатываем int crc = 0xFFFF; // обнуляем 2 старших байта for (i = start; i < start + length; i++) { crc = (ushort)crc & 0xFF00 + (ushort)(crc & 0x00FF) ^ input[i]; for (j = 0; j < 8; j++) { if ((crc & 0x01) > 0) { // Сдвигаем вправо на 1 бит и XOR-им полиномом 0xA001 crc = (crc >> 1) ^ 0xA001; } else { // Сдвигаем вправо на 1 бит crc = (crc >> 1); } } } // Возвращаем 2 младших байта return (ushort)crc; } /// /// Вычисляет контрольную сумму для переданного блока данных, согласно /// спецификации протокола ModBus (Modicon Modbus Protocol Reference Guide) /// /// Для расчета контрольной суммы используется модифицированный CRC-16-IBM с полиномом 0xA001. /// В отличие от стандартного алгоритма CRC-16-IBM, начальное значение устанавливается равным 0xFFFF /// /// Массив байт, по которому нужно посчитать CRC /// Значение контрольной суммы public static ushort Calc(IList input) { return ModBusCrc.Calc(input, 0, input.Count); } } }