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 | 7x
7x
7x
7x
7x
7x
7x
7x
76x
76x
76x
76x
76x
76x
76x
73x
76x
76x
76x
32x
32x
32x
32x
175x
167x
147x
147x
147x
20x
20x
20x
32x
76x
76x
35x
202x
202x
76x
10x
76x
76x
1x
7x
7x
1x
1x
| import { CommandEncoder, IntegerSize, CommandValue } from '../utils/encoder';
import { VelocityMaxValue } from './control';
import {Buffer as Buffer3} from 'buffer/';
import {Buffer} from 'buffer'
export enum CommandType {
/**
* This command reads a BLE characteristic and returns the parsed response.
*/
read = "read",
/**
* This command writes to a BLE characteristic and returns void after the write acknowledgement
*/
write = "write",
/**
* This command is the most complicated. It both writes and and listens to a characteristic, expecting the char to
* notify once the requested action is completed. It will return a parsed response from the notification body.
*/
writeAndListen = "write_listen",
/**
* This command is special for DFU. It subscribes to notifications, writes and then immediately returns succesful
* because the shade is rebooting.
*/
dfu = "dfu",
/**
* This command enables direct reads and writes to any supported BLE service/characteristic.
*/
passthru = "passthru"
}
export class Command<ReturnType> {
name: string = "GenericCommand";
/// Commands are processed differently depending on type
commandType: CommandType;
/// BLE service/char UUIDs
service: string;
characteristic: string;
/// A convenient way to write a (and more importantly read back) bytes of different sizes
encoder: CommandEncoder = new CommandEncoder();
constructor(service: string, characteristic: string, fn: (encoder: CommandEncoder) => void, commandType: CommandType = CommandType.write) {
this.service = service;
this.characteristic = characteristic;
this.commandType = commandType;
/// Add custom bytes
fn(this.encoder);
/// If this command has a header, update its length byte with the correct size
if ( this.encoder.hasHeader ) {
this.encoder.updateLength();
}
}
/**
* Set the sequence byte
*/
setSequence = (seq: number) => {
this.encoder.setSequence(seq);
};
/**
* This hash is really useful for say, keeping track of unqiue commands in a dictionary or set.
*/
toHash = (): string => {
return `${this.service}_${this.characteristic}_${this.encoder.getSequence()}`
};
/**
* Returns a an array of UInt8 values, ready to be sent over the wire to a shade
*/
toData = (): Array<any> => {
const buffer = new ArrayBuffer(this.encoder.byteCount());
const view = new DataView(buffer);
let i = 0;
for (let value of this.encoder.values) {
if ( value === undefined ) { continue; }
switch (value.size) {
case IntegerSize.Eight:
view.setUint8(i, value.value);
i = i + 1;
break;
case IntegerSize.Sixteen:
view.setUint16(i, value.value, true);
i = i + 2;
break;
default: break;
}
}
return Array.from(new Uint8Array(buffer));
};
/**
* Returns a hex string representation
*/
toString = (): string => {
return this.encoder.values.reduce((sum: string, next: CommandValue): string => {
return `${sum} ${next.value.toString(16).toUpperCase()}`
}, "");
};
/**
* For example if you have a command consisting of [ Uint8, Uint8, Uint16 ], this function will print 3 values.
* This is in contrast to a command like toData(), which will output 4 values consisting of [ Uint8, Uint8, Uint8, Uint8 ]
*/
toValues = (): number[] => {
return this.encoder.values.filter((value) => {
return value !== undefined;
}).map((value) => value.value);
};
/**
* For example if you have a command consisting of [ Uint8, Uint8, Uint16 ], this function will print 3 values.
* This is in contrast to a command like toData(), which will output 4 values consisting of [ Uint8, Uint8, Uint8, Uint8 ]
*/
toSizedValues = (): CommandValue[] => {
return this.encoder.values;
};
/**
* For example if you have a command consisting of [ Uint8, Uint8, Uint16 ], this function will print 3 values.
* This is in contrast to a command like toData(), which will output 4 values consisting of [ Uint8, Uint8, Uint8, Uint8 ]
*/
toHexValues = (): string[] => {
return this.encoder.values.filter((value) => {
return value !== undefined;
}).map((value) => toHexValue(value.value));
};
/**
* Subclasses which require response parsing should override this function
*/
parseResponse = (buffer: any /*Buffer 3rd party problem*/ ): ReturnType => {
throw new Error("Called abstract super class method directly. Don't do that");
}
}
const toHexValue = (value: number):string => {
const string = value.toString(16).toUpperCase();
return string.length % 2 == 0 ? string : '0' + string;
};
export interface BasicResponse {
responseMarker: number; // UInt8
errorCode: number; // UInt8
sequence: number; // UInt8
length: number; // UInt8
data: any; // length determined by property above
}
export const parseCommandResponseErrorCode = (code: number): Error|undefined => {
switch (code) {
case 0x00: return undefined;
case 0x81: return Error("General Error");
case 0x82: return Error("Servo Error");
case 0x8A: return Error("Authentication Error");
case 0x8B: return Error("Authorization Error (Permissions)");
case 0x8C: return Error("Unimplemented or Unsupported feature");
case 0x8D: return Error("Authorization Error - Command not permitted");
case 0x8E: return Error("Invalid command for subsystem");
case 0x8F: return Error("Bad command format");
case 0xEE: return Error("Severe Error");
default: return Error("Unknown Error");
}
};
|