// Using a template, with a constraint of IMessage allows us to pass the message without boxing reducing garbage generation private unsafe void ReadMessage <T>(DateTime now, Command command) where T : unmanaged, IMessage { int length = 0; try { while (length < sizeof(T)) { length += m_SerialPort.Read(m_ReadBuffer, length, sizeof(T) - length); } if (length != sizeof(T)) { throw new ArgumentException($"Message Length: {length}. Expected Length: {sizeof(T)}."); } } catch (Exception e) { AppLogging.DebugLogException(nameof(ReadMessage), e); Interlocked.Increment(ref m_ErrorCount); return; } T message = new T(); message.SetBytes(m_ReadBuffer); AppLogging.DebugLog(nameof(ReadMessage), command.ToString(), message.ToString()); Interlocked.Add(ref m_ReadBytes, length); m_LastMessageRead = now; m_MessageContext.Post(x => OnMessageRecieved?.Invoke(command, message), null); }
private void Connect(DateTime now) { string[] portNames = null; try { portNames = SerialPort.GetPortNames(); } catch { } if (portNames == null || portNames.Length == 0) { return; } foreach (string portName in portNames) { string firmware = ""; try { AppLogging.DebugLog(nameof(Connect), portName); m_SerialPort = new SerialPort(portName, 115200); m_SerialPort.ReadTimeout = k_ReadTimeout; m_SerialPort.WriteTimeout = k_WriteTimeout; m_SerialPort.Open(); m_SerialPort.DiscardInBuffer(); m_SerialPort.DiscardOutBuffer(); WriteMessage(now, Command.TEST); Thread.Sleep(20); Command command = (Command)m_SerialPort.ReadByte(); if (command != Command.TEST) { throw new InvalidOperationException($"Firmware Test reply failed. Reply: '{command}' Bytes: '{m_SerialPort.BytesToRead}'"); } firmware = m_SerialPort.ReadLine().Replace("\r", ""); if (!FirmwareVersions.IsCompatible(firmware)) { throw new ArgumentException($"Incompatible Firmware: '{firmware}'."); } m_SerialPort.DataReceived += Read; m_DeviceConnected = true; m_DeviceReady = true; m_MessageContext.Post(x => OnDeviceConnected?.Invoke(), null); m_LastMessageRead = now; return; } catch (Exception e) { AppLogging.DebugLogException(nameof(Connect), e); m_SerialPort.Close(); m_SerialPort.Dispose(); m_SerialPort = null; if (e is ArgumentException) // Incompatible Firmware { m_MessageContext.Post(x => OnFirmwareIncompatible?.Invoke(firmware), null); } } } }
private void WriteMessage(DateTime now, Command command, IMessage message = null) { m_WriteBuffer.SetLength(0); m_WriteBuffer.WriteByte((byte)command); message?.GetBytes(m_WriteBuffer); Interlocked.Add(ref m_WriteBytes, m_WriteBuffer.Length); // GetBuffer returns a reference to the underlying array, we can still use that after we reset the position if we store the length byte[] buffer = m_WriteBuffer.GetBuffer(); int length = (int)m_WriteBuffer.Length; AppLogging.DebugLog(nameof(WriteMessage), command.ToString(), message != null ? message.ToString() : ""); try { m_SerialPort.Write(buffer, 0, length); } catch (Exception e) { AppLogging.DebugLogException(nameof(WriteMessage), e); return; } m_LastMessageWrite = now; }
private void Read(DateTime now) { #if POLLING_SERIAL try { if (m_SerialPort == null || !m_SerialPort.IsOpen || m_SerialPort.BytesToRead <= 0) { return; } } catch { return; } #endif Command command; try { command = (Command)m_SerialPort.ReadByte(); } catch (Exception ex) { AppLogging.DebugLogException(nameof(Read), ex); Interlocked.Increment(ref m_ErrorCount); return; } Interlocked.Increment(ref m_ReadCount); Interlocked.Increment(ref m_ReadBytes); switch (command) { case Command.TEST: { try { var firmware = m_SerialPort.ReadLine().Replace("\r", ""); AppLogging.DebugLog(nameof(Read), command.ToString(), firmware); m_LastMessageRead = now; m_LastMessageWrite = now; } catch (Exception e) { AppLogging.DebugLogException(nameof(ReadMessage), e); Interlocked.Increment(ref m_ErrorCount); return; } } break; case Command.OK: { AppLogging.DebugLog(nameof(Read), command.ToString()); m_DeviceReady = true; m_LastMessageRead = now; m_LastMessageWrite = now; Write(m_LastMessageRead); } break; case Command.SETTINGS: ReadMessage <DeviceSettings>(now, command); break; case Command.SESSION_INFO: ReadMessage <SessionInfo>(now, command); break; case Command.CURRENT_SESSION: case Command.ALTERNATE_SESSION: case Command.PREVIOUS_SESSION: case Command.NEXT_SESSION: ReadMessage <SessionData>(now, command); break; case Command.VOLUME_CURR_CHANGE: case Command.VOLUME_ALT_CHANGE: case Command.VOLUME_PREV_CHANGE: case Command.VOLUME_NEXT_CHANGE: ReadMessage <VolumeData>(now, command); break; case Command.MODE_STATES: ReadMessage <ModeStates>(now, command); break; case Command.ERROR: case Command.NONE: case Command.DEBUG: Interlocked.Increment(ref m_ErrorCount); break; } }