All files / src/commands command.ts

70.15% Statements 47/67
43.48% Branches 10/23
52.94% Functions 9/17
69.7% Lines 46/66
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 1607x           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");
	}
};