forked from dotnet/iot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFirmataCommandSequence.cs
286 lines (248 loc) · 9.1 KB
/
FirmataCommandSequence.cs
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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Iot.Device.Arduino
{
/// <summary>
/// A firmata command sequence
/// Intended to be changed to public visibility later
/// </summary>
public class FirmataCommandSequence : IEquatable<FirmataCommandSequence>
{
private const int InitialCommandLength = 32;
/// <summary>
/// Start of sysex command byte. Used as start byte for almost all extended commands.
/// </summary>
public const byte StartSysex = (byte)FirmataCommand.START_SYSEX;
/// <summary>
/// End of sysex command byte. Must end all sysex commands.
/// </summary>
public const byte EndSysex = (byte)FirmataCommand.END_SYSEX;
private List<byte> _sequence;
/// <summary>
/// Create a new command sequence
/// </summary>
/// <param name="command">The first byte of the command</param>
internal FirmataCommandSequence(FirmataCommand command)
{
_sequence = new List<byte>()
{
(byte)command
};
}
internal FirmataCommandSequence(FirmataCommand command, int pin)
{
if (pin > 15)
{
throw new ArgumentOutOfRangeException(nameof(pin), "Shorthand commands can only be used with pin numbers <= 15");
}
_sequence = new List<byte>()
{
(byte)(((byte)command) | pin)
};
}
/// <summary>
/// Create a new sysex command sequence. The <see cref="StartSysex"/> byte is added automatically.
/// </summary>
public FirmataCommandSequence()
: this(FirmataCommand.START_SYSEX)
{
}
/// <summary>
/// The current sequence
/// </summary>
public IReadOnlyList<byte> Sequence => _sequence;
/// <summary>
/// The current length of the sequence
/// </summary>
public int Length => _sequence.Count;
internal byte[] InternalSequence => _sequence.ToArray();
/// <summary>
/// Decode an uint from packed 7-bit data.
/// This way of encoding uints is only used in extension modules.
/// </summary>
/// <param name="data">Data. 5 bytes expected</param>
/// <param name="fromOffset">Start offset in data</param>
/// <returns>The decoded unsigned integer</returns>
/// <exception cref="InvalidDataException">The received data is invalid</exception>
public static UInt32 DecodeUInt32(ReadOnlySpan<byte> data, int fromOffset)
{
for (int i = 0; i < 5; i++)
{
// Bit 7 of the data must always be 0, or there's either a communication problem or a protocol mismatch
if ((data[fromOffset + i] & 0x80) != 0)
{
throw new InvalidDataException("An invalid byte was received. The message was probably corrupted");
}
}
Int32 value = data[fromOffset];
value |= data[fromOffset + 1] << 7;
value |= data[fromOffset + 2] << 14;
value |= data[fromOffset + 3] << 21;
value |= data[fromOffset + 4] << 28;
return (UInt32)value;
}
/// <summary>
/// Decode an int from packed 7-bit data.
/// This way of encoding uints is only used in extension modules.
/// </summary>
/// <param name="data">Data. 5 bytes expected</param>
/// <param name="fromOffset">Start offset in data</param>
/// <returns>The decoded number</returns>
public static Int32 DecodeInt32(ReadOnlySpan<byte> data, int fromOffset)
{
return (Int32)DecodeUInt32(data, fromOffset);
}
/// <summary>
/// Decodes a 14-bit integer into a short
/// </summary>
/// <param name="data">Data array</param>
/// <param name="idx">Start offset</param>
/// <returns></returns>
public static short DecodeInt14(byte[] data, int idx)
{
return (short)(data[idx] | data[idx + 1] << 7);
}
/// <summary>
/// Send an Uint32 as 5 x 7 bits. This form of transmitting integers is only supported by extension modules
/// </summary>
/// <param name="value">The 32-Bit value to transmit</param>
public void SendUInt32(UInt32 value)
{
byte[] data = new byte[5];
data[0] = (byte)(value & 0x7F);
data[1] = (byte)((value >> 7) & 0x7F);
data[2] = (byte)((value >> 14) & 0x7F);
data[3] = (byte)((value >> 21) & 0x7F);
data[4] = (byte)((value >> 28) & 0x7F);
_sequence.AddRange(data);
}
/// <summary>
/// Send an Int32 as 5 x 7 bits. This form of transmitting integers is only supported by extension modules
/// </summary>
/// <param name="value">The 32-Bit value to transmit</param>
public void SendInt32(Int32 value)
{
SendUInt32((uint)value);
}
/// <summary>
/// Add a byte to the command sequence
/// </summary>
/// <param name="b">The byte to add</param>
public void WriteByte(byte b)
{
_sequence.Add(b);
}
/// <summary>
/// Add a sequence of bytes to the command sequence. The bytes must be encoded already.
/// </summary>
/// <param name="bytesToSend">The raw block to send</param>
public void Write(byte[] bytesToSend)
{
_sequence.AddRange(bytesToSend);
}
/// <summary>
/// Add a sequence of bytes to the command sequence. The bytes must be encoded already.
/// </summary>
/// <param name="bytesToSend">The raw block to send</param>
/// <param name="startIndex">Start index</param>
/// <param name="length">Number of bytes to send</param>
public void Write(byte[] bytesToSend, int startIndex, int length)
{
for (int i = startIndex; i < startIndex + length; i++)
{
_sequence.Add(bytesToSend[i]);
}
}
internal bool Validate()
{
if (Length < 2)
{
return false;
}
if (Sequence[0] == (byte)FirmataCommand.START_SYSEX && Sequence[Sequence.Count - 1] != (byte)FirmataCommand.END_SYSEX)
{
return false;
}
return true;
}
/// <summary>
/// Encodes a set of bytes with 7 bits and adds them to the sequence. Each input byte is encoded in 2 bytes.
/// </summary>
/// <param name="values">Binary data to add</param>
public void WriteBytesAsTwo7bitBytes(ReadOnlySpan<byte> values)
{
for (int i = 0; i < values.Length; i++)
{
_sequence.Add((byte)(values[i] & (uint)sbyte.MaxValue));
_sequence.Add((byte)(values[i] >> 7 & sbyte.MaxValue));
}
}
/// <summary>
/// Write a packed Int14 to the stream. This is used to write an integer of up to 14 bits.
/// </summary>
/// <param name="value">The value to write. Only the 14 least significant bits are transmitted</param>
public void SendInt14(int value)
{
WriteByte((byte)(value & 0x7F));
WriteByte((byte)((value >> 7) & 0x7F));
}
/// <inheritdoc/>
public override string ToString()
{
StringBuilder b = new StringBuilder();
int maxBytes = Math.Min(Length, 32);
for (int i = 0; i < maxBytes; i++)
{
b.Append($"{_sequence[i]:X2} ");
}
if (maxBytes < Length)
{
b.Append("...");
}
return b.ToString();
}
/// <inheritdoc />
public bool Equals(FirmataCommandSequence? other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _sequence.Equals(other._sequence) && Length == other.Length;
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((FirmataCommandSequence)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return (_sequence.GetHashCode() * 397);
}
}
}
}