-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathArduinoSerialCommunication.cs
More file actions
307 lines (286 loc) · 9.41 KB
/
ArduinoSerialCommunication.cs
File metadata and controls
307 lines (286 loc) · 9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#if UNITY_STANDALONE_WIN && NET_4_6
using System.IO.Ports;
#endif
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Diagnostics;
using System;
using System.Threading;
using System.Collections;
namespace Wrj
{
public class ArduinoSerialCommunication : MonoBehaviour
{
#if UNITY_STANDALONE_WIN && NET_4_6
[SerializeField]
private BaudRates BaudRate = BaudRates._9600;
[SerializeField]
private string deviceName = "Arduino";
[SerializeField]
[Range(-1, 256)]
private int portNumber = -1;
[SerializeField]
private bool logReceivedMessages = true;
[SerializeField]
private OnStringEvent[] onStringEvents;
private string _rawData = string.Empty;
private uint _dataIndex = 0;
public string RawData { get { return _rawData; } }
public uint DataIndex { get { return _dataIndex; } }
public delegate void SerialEvent(string data);
public SerialEvent OnSerialEvent;
private Dictionary<string, string> portAssignments = new Dictionary<string, string>();
private string comPort;
private volatile bool _keepListening = false;
SerialPort port;
Thread serialPortListenerThread;
private readonly Queue<Action> _delegateQueue = new Queue<Action>();
public enum BaudRates
{
_300 = 300, _600 = 600, _1200 = 1200, _2400 = 2400, _4800 = 4800, _9600 = 9600,
_14400 = 14400, _19200 = 19200, _28800 = 28800, _38400 = 38400, _57600 = 57600, _115200 = 115200
}
/// Static Singleton behavior
protected static ArduinoSerialCommunication _instance;
public static ArduinoSerialCommunication Instance
{
get
{
if (_instance == null)
{
UnityEngine.Debug.LogError("ArduinoSerialCommunication not instantiated!");
}
return _instance;
}
}
void Awake()
{
if (_instance == null)
{
_instance = this;
}
else
{
Destroy(this);
}
}
void Start()
{
PopulatePortList();
if (portNumber >= 0 && PortExists(portNumber))
{
comPort = "COM" + portNumber;
Connect();
}
else if ((comPort = GetFirstPortMatchingName(deviceName)) != null)
{
Connect();
}
else
{
UnityEngine.Debug.LogWarning("Arduino Not Found");
}
OnSerialEvent += HandleSerialString;
}
private void OnDestroy()
{
StopListening();
}
private void OnDisable()
{
StopListening();
}
void Connect()
{
StopListening();
Utils.SafeTry(() => InitializeArduino(comPort, (int)BaudRate));
_keepListening = true;
serialPortListenerThread = new Thread(RecieveDataInHelperThread);
serialPortListenerThread.Start();
}
private void StopListening()
{
_keepListening = false;
if (serialPortListenerThread != null && serialPortListenerThread.IsAlive)
{
serialPortListenerThread.Join(200);
}
ClosePort();
serialPortListenerThread = null;
}
public void ClosePort()
{
UnityEngine.Debug.Log("Close port");
Utils.SafeTry(() => port?.Close());
}
void InitializeArduino(string listeningPort, int baudRate)
{
UnityEngine.Debug.LogFormat("Connecting to Arduino on port {0} with a baudrate of: {1}.", listeningPort, baudRate);
Utils.SafeTry(() =>
{
port = new SerialPort(listeningPort, baudRate);
port.Parity = Parity.None;
port.StopBits = StopBits.One;
port.DataBits = 8;
port.Handshake = Handshake.None;
port.Open();
});
}
void RecieveDataInHelperThread()
{
try
{
while (_keepListening && port != null && port.IsOpen)
{
string str = port.ReadLine();
if (!string.IsNullOrWhiteSpace(str))
{
_rawData = str;
_dataIndex++;
if (OnSerialEvent != null)
{
// Notify on main thread...
Enqueue(ActionWrapper(() => OnSerialEvent(str)));
}
}
}
}
catch (Exception)
{
// Swallow read exceptions during shutdown/disconnect.
}
}
// Send text to the serial port.
public void SendData(string str)
{
Utils.SafeTry(() =>
{
port.Write(str);
});
}
public void SendDataAsLine(string str)
{
Utils.SafeTry(() =>
{
port.WriteLine(str);
});
}
void Update()
{
lock (_delegateQueue)
{
while (_delegateQueue.Count > 0)
{
_delegateQueue.Dequeue().Invoke();
}
}
}
private void PopulatePortList()
{
// Remove existing port entries
portAssignments.Clear();
// Run PowerShell process with Get-WMIObject Win32_SerialPort
// Request DeviceID (port) and Description (device name)
Process process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = "powershell.exe";
process.StartInfo.Arguments = "Get-WMIObject Win32_SerialPort | Select-Object DeviceID,Description";
process.Start();
// Read each line of the output
string lineOut;
while ((lineOut = process.StandardOutput.ReadLine()) != null)
{
// If it contains "COM" parse out the int for the port number
// and save the remainder of the line as the description
if (lineOut.Contains("COM"))
{
int firstSpacePos = lineOut.IndexOf(' ');
string port = lineOut.Substring(0, firstSpacePos).Trim();
string desc = lineOut.Substring(firstSpacePos).Trim();
// Add to the port list
portAssignments.Add(desc, port);
}
}
}
// Return the first port found with a name that matches a
// specified substring.
private string GetFirstPortMatchingName(string contains)
{
foreach (KeyValuePair<string, string> entry in portAssignments)
{
if (entry.Key.Contains(contains))
{
return entry.Value;
}
}
return null;
}
private bool PortExists(int portNum)
{
return (portAssignments.ContainsValue("COM" + portNum));
}
void Enqueue(IEnumerator action)
{
lock (_delegateQueue)
{
_delegateQueue.Enqueue(() =>
{
StartCoroutine(action);
});
}
}
IEnumerator ActionWrapper(Action action)
{
action();
yield return null;
}
private void HandleSerialString(string message)
{
foreach (var onStringEvent in onStringEvents)
{
if (onStringEvent.Message == message)
{
bool invoked = onStringEvent.Invoke();
if (logReceivedMessages)
{
if (invoked)
{
UnityEngine.Debug.Log($"Invoked command for message: {message}");
}
else
{
UnityEngine.Debug.Log($"Debounced command for message: {message}");
}
}
}
}
}
[System.Serializable]
class OnStringEvent
{
[SerializeField]
private string message;
[SerializeField]
private float debounceTime = 0.1f;
[SerializeField]
private UnityEvent command;
private float lastInvokeTime = float.NegativeInfinity;
public string Message { get { return message; } }
public bool Invoke()
{
float timeSinceLastInvoke = Time.time - lastInvokeTime;
if (timeSinceLastInvoke < debounceTime)
{
return false;
}
lastInvokeTime = Time.time;
command.Invoke();
return true;
}
}
#endif
}
}