浏览代码

Generalize by introducing ITimeProvider interface

master
The6P4C 7 年前
父节点
当前提交
5c32cabb46
共有 7 个文件被更改,包括 229 次插入58 次删除
  1. +26
    -0
      ThunderboltTimeSync/FormMain.Designer.cs
  2. +31
    -21
      ThunderboltTimeSync/FormMain.cs
  3. +3
    -0
      ThunderboltTimeSync/FormMain.resx
  4. +3
    -1
      ThunderboltTimeSync/ThunderboltTimeSync.csproj
  5. +44
    -0
      ThunderboltTimeSync/TimeProviders/ITimeProvider.cs
  6. +70
    -36
      ThunderboltTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs
  7. +52
    -0
      ThunderboltTimeSync/TimeProviders/Thunderbolt/ThunderboltTimeProvider.cs

+ 26
- 0
ThunderboltTimeSync/FormMain.Designer.cs 查看文件

@@ -24,6 +24,9 @@
/// </summary>
private void InitializeComponent() {
this.labelTimestamps = new System.Windows.Forms.Label();
this.statusStrip = new System.Windows.Forms.StatusStrip();
this.latestLogMessage = new System.Windows.Forms.ToolStripStatusLabel();
this.statusStrip.SuspendLayout();
this.SuspendLayout();
//
// labelTimestamps
@@ -34,21 +37,44 @@
this.labelTimestamps.Size = new System.Drawing.Size(486, 358);
this.labelTimestamps.TabIndex = 0;
//
// statusStrip
//
this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.latestLogMessage});
this.statusStrip.Location = new System.Drawing.Point(0, 336);
this.statusStrip.Name = "statusStrip";
this.statusStrip.Size = new System.Drawing.Size(486, 22);
this.statusStrip.TabIndex = 1;
this.statusStrip.Text = "statusStrip1";
//
// latestLogMessage
//
this.latestLogMessage.Name = "latestLogMessage";
this.latestLogMessage.Size = new System.Drawing.Size(66, 17);
this.latestLogMessage.Text = "{RUNTIME}";
//
// FormMain
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(486, 358);
this.Controls.Add(this.statusStrip);
this.Controls.Add(this.labelTimestamps);
this.Name = "FormMain";
this.Text = "Thunderbolt Time Sync";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormMain_FormClosing);
this.statusStrip.ResumeLayout(false);
this.statusStrip.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.Label labelTimestamps;
private System.Windows.Forms.StatusStrip statusStrip;
private System.Windows.Forms.ToolStripStatusLabel latestLogMessage;
}
}


+ 31
- 21
ThunderboltTimeSync/FormMain.cs 查看文件

@@ -1,10 +1,22 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Drawing;
using System.IO.Ports;
using System.Windows.Forms;
using ThunderboltTimeSync.Devices.Thunderbolt;
using ThunderboltTimeSync.TimeProviders;
using ThunderboltTimeSync.TimeProviders.Thunderbolt;

namespace ThunderboltTimeSync {
public partial class FormMain : Form {
private static readonly Dictionary<LogLevel, Color> LOG_LEVEL_TO_COLOR = new Dictionary<LogLevel, Color>() {
{ LogLevel.Info, Color.Black },
{ LogLevel.Warning, Color.Yellow },
{ LogLevel.Error, Color.Red }
};

private ITimeProvider timeProvider;

public FormMain() {
// Check for admin rights
// If running as admin:
@@ -19,31 +31,29 @@ namespace ThunderboltTimeSync {

InitializeComponent();

ThunderboltSerialPort tbsp = new ThunderboltSerialPort(new SerialPort("COM8"));

tbsp.PacketReceived += (ThunderboltPacket packet) => {
if (packet.IsPacketValid) {
if (packet.ID == 0x8F && packet.Data.Count == 17 && packet.Data[0] == 0xAB) {
int timeOfWeek = packet.Data[1] << 24 | packet.Data[2] << 16 | packet.Data[3] << 8 | packet.Data[4];
ushort weekNumber = (ushort) (packet.Data[5] << 8 | packet.Data[6]);
short utcOffset = (short) (packet.Data[7] << 8 | packet.Data[8]);

// Current epoch for GPS week numbers is the morning of 22/8/1999
DateTime dateTime = new DateTime(1999, 8, 22, 0, 0, 0);
latestLogMessage.Text = "";

dateTime = dateTime.AddDays(7 * weekNumber);
dateTime = dateTime.AddSeconds(timeOfWeek);
ThunderboltSerialPort thunderboltSerialPort = new ThunderboltSerialPort(new SerialPort("COM3"));
timeProvider = new ThunderboltTimeProvider(thunderboltSerialPort);

dateTime = dateTime.AddSeconds(-utcOffset);
timeProvider.TimeAvailable += (DateTime dateTime) => {
Invoke(new Action(() => {
labelTimestamps.Text += string.Format("{0} {1} @ {2}\n", dateTime.ToLongDateString(), dateTime.ToLongTimeString(), DateTime.Now.ToLongTimeString());
}));
};

labelTimestamps.Invoke(new Action(() => {
labelTimestamps.Text += string.Format("{0} {1}\n", dateTime.ToLongDateString(), dateTime.ToLongTimeString());
}));
}
}
timeProvider.Log += (string message, LogLevel logLevel) => {
Invoke(new Action(() => {
latestLogMessage.Text = message;
latestLogMessage.ForeColor = LOG_LEVEL_TO_COLOR[logLevel];
}));
};

tbsp.Open();
timeProvider.Start();
}

private void FormMain_FormClosing(object sender, FormClosingEventArgs e) {
timeProvider.Stop();
}
}
}

+ 3
- 0
ThunderboltTimeSync/FormMain.resx 查看文件

@@ -117,4 +117,7 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="statusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

+ 3
- 1
ThunderboltTimeSync/ThunderboltTimeSync.csproj 查看文件

@@ -55,7 +55,9 @@
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SystemTimeUtils.cs" />
<Compile Include="ThunderboltSerialPort.cs" />
<Compile Include="TimeProviders\ITimeProvider.cs" />
<Compile Include="TimeProviders\Thunderbolt\ThunderboltSerialPort.cs" />
<Compile Include="TimeProviders\Thunderbolt\ThunderboltTimeProvider.cs" />
<EmbeddedResource Include="FormMain.resx">
<DependentUpon>FormMain.cs</DependentUpon>
</EmbeddedResource>


+ 44
- 0
ThunderboltTimeSync/TimeProviders/ITimeProvider.cs 查看文件

@@ -0,0 +1,44 @@
using System;

namespace ThunderboltTimeSync.TimeProviders {
/// <summary>
/// Called when the time provider has a new time and date available.
/// </summary>
/// <param name="time">The current time and date, according to the time provider.</param>
public delegate void TimeAvailableEventHandler(DateTime dateTime);

/// <summary>
/// Called when the time provider wishes to log a message or an error.
/// </summary>
/// <param name="message">The message for the log.</param>
/// <param name="isError">True if the message constitutes an error, false otherwise.</param>
public delegate void LogEventHandler(string message, LogLevel logLevel);

public enum LogLevel {
Info,
Warning,
Error
}

public interface ITimeProvider {
/// <summary>
/// Called when the time provider has a new time and date available.
/// </summary>
event TimeAvailableEventHandler TimeAvailable;

/// <summary>
/// Called when the time provider wishes to log a message or an error.
/// </summary>
event LogEventHandler Log;

/// <summary>
/// Begins providing time information by firing the TimeAvailable event, and log information through the Log event.
/// </summary>
void Start();

/// <summary>
/// Stops providing time information through the TimeAvailable event, and log information through the Log event.
/// </summary>
void Stop();
}
}

ThunderboltTimeSync/ThunderboltSerialPort.cs → ThunderboltTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs 查看文件

@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Threading;

namespace ThunderboltTimeSync {
namespace ThunderboltTimeSync.Devices.Thunderbolt {
public class ThunderboltPacket {
/// <summary>
/// The validity of the packet.
@@ -45,6 +47,9 @@ namespace ThunderboltTimeSync {

private SerialPort serialPort;

private bool running;
private Thread readThread;

/// <summary>
/// A delegate which is called when a full packet is received over the serial port.
/// </summary>
@@ -58,7 +63,7 @@ namespace ThunderboltTimeSync {

/// <summary>
/// Creates an instance of the ThunderboltSerialPort class, which processes serial data from a Thunderbolt and
/// The serial port passed into the function must not be opened, or have a delegate already attached to the DataReceived event.
/// The serial port passed into the function must not be opened.
/// </summary>
/// <param name="serialPort">The serial port on which to communicate with the Thunderbolt.</param>
public ThunderboltSerialPort(SerialPort serialPort) {
@@ -66,14 +71,28 @@ namespace ThunderboltTimeSync {
inPacket = false;

this.serialPort = serialPort;
serialPort.DataReceived += DataReceived;

readThread = new Thread(ReadSerialPort);
}

/// <summary>
/// Begins processing serial data.
/// Begins processing serial data and firing PacketReceived events.
/// </summary>
public void Open() {
running = true;

serialPort.Open();
readThread.Start();
}

/// <summary>
/// Stops processing serial data and firing PacketReceived events.
/// </summary>
public void Close() {
running = false;

readThread.Join();
serialPort.Close();
}

/// <summary>
@@ -110,6 +129,9 @@ namespace ThunderboltTimeSync {
}

private void ProcessPacket() {
List<string> byteStrings = packetBuffer.Select(x => string.Format("{0:X2}", x)).ToList();
Debug.WriteLine(string.Join(" ", byteStrings));

byte id = packetBuffer[1];

// Grab only the data - not the first [DLE]<id> or the last [DLE][ETX]
@@ -131,43 +153,55 @@ namespace ThunderboltTimeSync {
// to bring the decoder back into sync with the true packets. It's probably only an issue when the Thunderbolt is initially plugged in,
// as that's probably the only time we'd see malformed packets on the serial port, since we could connect in the middle of a packet.
// Consider if this is really an issue, and think of a better way of decoding the packets to avoid this.
private void DataReceived(object sender, SerialDataReceivedEventArgs e) {
int possibleCurrentByte;

while ((possibleCurrentByte = serialPort.ReadByte()) != -1) {
// Once we're sure the byte that was read wasn't -1 (which signifies the end of the read), we're safe to cast to a byte
byte currentByte = (byte) possibleCurrentByte;

if (inPacket) {
packetBuffer.Add(currentByte);

// Check buffer length to ensure we've reached a plausible end of packet.
// 5 bytes is [DLE]<id><1 byte of data>[DLE][ETX]
// Must check if previous character is a [DLE], otherwise an ETX with a malformed and unstuffed [DLE] will cause issues
if (currentByte == CHAR_ETX && packetBuffer.Count >= 5 && packetBuffer[packetBuffer.Count - 2] == CHAR_DLE) {
int numberOfPrecedingDLEs = 0;

// Count number of DLEs, excluding the first two bytes (initial DLE and id)
for (int i = 2; i < packetBuffer.Count; ++i) {
if (packetBuffer[i] == CHAR_DLE) {
++numberOfPrecedingDLEs;
private void ReadSerialPort() {
while (running) {
int possibleCurrentByte = serialPort.ReadByte();
if (possibleCurrentByte != -1) {
// There aren't any packets this long, but during a corrupted or malformed packet the buffer can get quite large before the error
// is fixed somehow. To prevent the almost 20 second delay between time packets that can be caused by some malformed packets,
// just reset everything if the buffer's getting too long.
// We should make sure the user knows, so send a invalid packet to them.
if (packetBuffer.Count >= 128) {
packetBuffer.Clear();
inPacket = false;

PacketReceived?.Invoke(new ThunderboltPacket(false, 0, new List<byte>(), new List<byte>()));
}

// Once we're sure the byte that was read wasn't -1 (which signifies the end of the read), we're safe to cast to a byte
byte currentByte = (byte) possibleCurrentByte;

if (inPacket) {
packetBuffer.Add(currentByte);

// Check buffer length to ensure we've reached a plausible end of packet.
// 5 bytes is [DLE]<id><1 byte of data>[DLE][ETX]
// Must check if previous character is a [DLE], otherwise an ETX with a malformed and unstuffed [DLE] will cause issues
if (currentByte == CHAR_ETX && packetBuffer.Count >= 5 && packetBuffer[packetBuffer.Count - 2] == CHAR_DLE) {
int numberOfPrecedingDLEs = 0;

// Count number of DLEs, excluding the first two bytes (initial DLE and id)
for (int i = 2; i < packetBuffer.Count; ++i) {
if (packetBuffer[i] == CHAR_DLE) {
++numberOfPrecedingDLEs;
}
}
}

// Odd number (greater than zero) of DLEs means the ETX does in fact signify the end of the packet
if (numberOfPrecedingDLEs % 2 == 1 && numberOfPrecedingDLEs > 0) {
ProcessPacket();
// Odd number (greater than zero) of DLEs means the ETX does in fact signify the end of the packet
if (numberOfPrecedingDLEs % 2 == 1 && numberOfPrecedingDLEs > 0) {
ProcessPacket();

packetBuffer.Clear();
inPacket = false;
packetBuffer.Clear();
inPacket = false;
}
}
}
} else {
// A DLE received when not currently in a packet signifies the beginning of a packet
if (currentByte == CHAR_DLE) {
packetBuffer.Add(currentByte);
} else {
// A DLE received when not currently in a packet signifies the beginning of a packet
if (currentByte == CHAR_DLE) {
packetBuffer.Add(currentByte);

inPacket = true;
inPacket = true;
}
}
}
}

+ 52
- 0
ThunderboltTimeSync/TimeProviders/Thunderbolt/ThunderboltTimeProvider.cs 查看文件

@@ -0,0 +1,52 @@
using System;
using ThunderboltTimeSync.Devices.Thunderbolt;

namespace ThunderboltTimeSync.TimeProviders.Thunderbolt {
class ThunderboltTimeProvider : ITimeProvider {
private ThunderboltSerialPort thunderboltSerialPort;

public event TimeAvailableEventHandler TimeAvailable;
public event LogEventHandler Log;

/// <summary>
/// Creates an instance of the ThunderboltTimeProvider class, which provides time information through the ITimeProvider interface.
/// The ThunderboltSerialPort instance passed into this function must not be open.
/// </summary>
/// <param name="thunderboltSerialPort">The ThunderboltSerialPort instance to use when communicating with the Thunderbolt.</param>
public ThunderboltTimeProvider(ThunderboltSerialPort thunderboltSerialPort) {
this.thunderboltSerialPort = thunderboltSerialPort;

thunderboltSerialPort.PacketReceived += PacketReceived;
}

public void Start() {
thunderboltSerialPort.Open();
}

public void Stop() {
thunderboltSerialPort.Close();
}

private void PacketReceived(ThunderboltPacket packet) {
if (packet.IsPacketValid) {
if (packet.ID == 0x8F && packet.Data.Count == 17 && packet.Data[0] == 0xAB) {
int timeOfWeek = packet.Data[1] << 24 | packet.Data[2] << 16 | packet.Data[3] << 8 | packet.Data[4];
ushort weekNumber = (ushort) (packet.Data[5] << 8 | packet.Data[6]);
short utcOffset = (short) (packet.Data[7] << 8 | packet.Data[8]);

// Current epoch for GPS week numbers is the morning of 22/8/1999
DateTime dateTime = new DateTime(1999, 8, 22, 0, 0, 0);

dateTime = dateTime.AddDays(7 * weekNumber);
dateTime = dateTime.AddSeconds(timeOfWeek);

dateTime = dateTime.AddSeconds(-utcOffset);

TimeAvailable?.Invoke(dateTime);
}
} else {
Log?.Invoke("An invalid packet was received.", LogLevel.Warning);
}
}
}
}

正在加载...
取消
保存