forked from dotnet/iot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FirmataCommandSequence.cs
263 lines (228 loc) · 8.33 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
// 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 = FirmataCommand.START_SYSEX)
{
_sequence = new List<byte>()
{
(byte)command
};
}
/// <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));
}
}
/// <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);
}
}
}
}