/
Vape.m
474 lines (408 loc) · 18.1 KB
/
Vape.m
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
//
// Vape.m
// VapeLog
//
// Copyright (c) 2014 Sasquatch Junior. All rights reserved.
//
#import "Vape.h"
@implementation Vape
{
CBCentralManager *_centralManager;
CBPeripheral *_discoveredPeripheral;
dispatch_source_t _loggerTimer;
dispatch_source_t _batteryStateTimer;
dispatch_source_t _heaterStateTimer;
}
#pragma mark - UUIDs
const NSString *UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE = @"00000001-4C45-4B43-4942-265A524F5453";
const NSString *UUID_CURRENT_TEMPERATURE = @"00000011-4C45-4B43-4942-265A524F5453";
const NSString *UUID_TARGET_TEMPERATURE = @"00000021-4C45-4B43-4942-265A524F5453";
const NSString *UUID_BOOSTER_TEMPERATURE = @"00000031-4C45-4B43-4942-265A524F5453";
const NSString *UUID_BATTERY_CAPACITY = @"00000041-4C45-4B43-4942-265A524F5453";
const NSString *UUID_LED_BRIGHTNESS = @"00000051-4C45-4B43-4942-265A524F5453";
const NSString *UUID_DEVICE_INFO_SERVICE = @"00000002-4C45-4B43-4942-265A524F5453";
const NSString *UUID_USAGE_TIME = @"00000012-4C45-4B43-4942-265A524F5453";
const NSString *UUID_MODEL = @"00000022-4C45-4B43-4942-265A524F5453";
const NSString *UUID_FIRMWARE = @"00000032-4C45-4B43-4942-265A524F5453";
const NSString *UUID_SERIAL_NUMBER = @"00000052-4C45-4B43-4942-265A524F5453";
const NSString *UUID_DIAGNOSTICS_SERVICE = @"00000003-4C45-4B43-4942-265A524F5453";
const NSString *UUID_OPERATING_TIME = @"00000013-4C45-4B43-4942-265A524F5453";
const NSString *UUID_POWER_ON_TIME = @"00000023-4C45-4B43-4942-265A524F5453";
const NSString *UUID_CHARGER_STATUS = @"000000A3-4C45-4B43-4942-265A524F5453";
const NSString *UUID_VOLTAGE_ACCU = @"000000B3-4C45-4B43-4942-265A524F5453";
const NSString *UUID_VOLTAGE_MAINS = @"000000C3-4C45-4B43-4942-265A524F5453";
const NSString *UUID_VOLTAGE_HEATING = @"000000D3-4C45-4B43-4942-265A524F5453";
const NSString *UUID_CURRENT_ACCU = @"000000E3-4C45-4B43-4942-265A524F5453";
const NSString *UUID_TEMPERATURE_PT1000 = @"00000103-4C45-4B43-4942-265A524F5453";
const NSString *UUID_FULL_CHARGE_CAPACITY = @"00000143-4C45-4B43-4942-265A524F5453";
const NSString *UUID_REMAIN_CHARGE_CAPACITY = @"00000153-4C45-4B43-4942-265A524F5453";
const NSString *UUID_DISCHARGE_CYCLES = @"00000163-4C45-4B43-4942-265A524F5453";
const NSString *UUID_CHARGE_CYCLES = @"00000173-4C45-4B43-4942-265A524F5453";
const NSString *UUID_DESIGN_CAPACITY_ACCU = @"00000183-4C45-4B43-4942-265A524F5453";
#pragma mark -
- (instancetype)init
{
self = [super init];
if (self) {
// Setup Bluetooth Manager
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
[_centralManager scanForPeripheralsWithServices:@[] options:options];
}
return self;
}
- (void)dealloc
{
// Be a good citizen and disconnect from device
[_centralManager cancelPeripheralConnection:_discoveredPeripheral];
}
#pragma mark - Logging
- (void)logToPath:(NSString *)logPath
{
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
if (_batteryLevel && _currentTemp) {
// Only log if we have data
NSString *logStr = [NSString stringWithFormat:@"%@,%@,%@,%@\n",[dateFormatter stringFromDate:[NSDate date]],@([_currentTemp floatValue] / 10.0f), _batteryLevel,@([_setTemp intValue] /10)];
NSFileHandle *logHandle = [NSFileHandle fileHandleForUpdatingAtPath:logPath];
[logHandle seekToEndOfFile];
[logHandle writeData:[logStr dataUsingEncoding:NSUTF8StringEncoding]];
[logHandle closeFile];
}
// Log every second
_loggerTimer = CreateDispatchTimer(1.000f, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self logToPath:logPath];
});
}
- (void)stopLog
{
if (_loggerTimer) {
dispatch_source_cancel(_loggerTimer);
_loggerTimer = nil;
}
}
#pragma mark - CBCentralManagerDelegate
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
_discoveredPeripheral = peripheral;
_discoveredPeripheral.delegate = self;
[_discoveredPeripheral discoverServices:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
if ([peripheral.name isEqualToString:@"STORZ&BICKEL"] && !_discoveredPeripheral) {
#ifdef DEBUG
NSLog(@"Vape Found");
#endif
_discoveredPeripheral = peripheral;
[_centralManager connectPeripheral:peripheral options:nil];
[_centralManager stopScan];
}
}
- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)aPeripheral error:(NSError *)error
{
NSLog(@"%@", error);
}
#pragma mark - CBPeripheralDelegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
_discoveredPeripheral = peripheral;
NSArray *services = @[[CBUUID UUIDWithString:(NSString *)UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE],
[CBUUID UUIDWithString:(NSString *)UUID_DEVICE_INFO_SERVICE],
[CBUUID UUIDWithString:(NSString *)UUID_DIAGNOSTICS_SERVICE]];
for (CBService *service in _discoveredPeripheral.services) {
if ([services containsObject:service.UUID]) {
// Read needed services
[_discoveredPeripheral discoverCharacteristics:nil forService:service];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSArray *tempBattSerivceChars = @[[CBUUID UUIDWithString:(NSString *)UUID_CURRENT_TEMPERATURE],
[CBUUID UUIDWithString:(NSString *)UUID_TARGET_TEMPERATURE],
[CBUUID UUIDWithString:(NSString *)UUID_BOOSTER_TEMPERATURE],
[CBUUID UUIDWithString:(NSString *)UUID_BATTERY_CAPACITY]];
NSArray *deviceInfoServiceChars = @[[CBUUID UUIDWithString:(NSString *)UUID_SERIAL_NUMBER]];
NSArray *diagnosticsServiceChars = @[[CBUUID UUIDWithString:(NSString *)UUID_POWER_ON_TIME],
[CBUUID UUIDWithString:(NSString *)UUID_CHARGER_STATUS],
[CBUUID UUIDWithString:(NSString *)UUID_CURRENT_ACCU]];
if ([[CBUUID UUIDWithString:(NSString *)UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE] isEqualTo:service.UUID]) {
for (CBCharacteristic* charateristic in service.characteristics) {
if ([tempBattSerivceChars containsObject:charateristic.UUID]) {
[_discoveredPeripheral readValueForCharacteristic:charateristic];
}
}
} else if ([[CBUUID UUIDWithString:(NSString *)UUID_DEVICE_INFO_SERVICE] isEqualTo:service.UUID]) {
for (CBCharacteristic* charateristic in service.characteristics) {
if ([deviceInfoServiceChars containsObject:charateristic.UUID]) {
[_discoveredPeripheral readValueForCharacteristic:charateristic];
}
}
} else if ([[CBUUID UUIDWithString:(NSString *)UUID_DIAGNOSTICS_SERVICE] isEqualTo:service.UUID]) {
for (CBCharacteristic* charateristic in service.characteristics) {
if ([diagnosticsServiceChars containsObject:charateristic.UUID]) {
[_discoveredPeripheral readValueForCharacteristic:charateristic];
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(@"%@",[error localizedDescription]);
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
// Serial Number
if ([[CBUUID UUIDWithString:(NSString *)UUID_SERIAL_NUMBER] isEqualTo:characteristic.UUID]) {
NSString *sn = [self getStringValueFromData:characteristic.value];
NSRange stringRange = {0, MIN([sn length], [sn length] - 2)};
stringRange = [sn rangeOfComposedCharacterSequencesForRange:stringRange];
_serialNumber = [sn substringWithRange:stringRange];
if ([_delegate respondsToSelector:@selector(serialNumberDidUpdate)]) {
[_delegate serialNumberDidUpdate];
}
#ifdef DEBUG
NSLog(@"Serial #:%@",_serialNumber);
#endif
}
// Set Temp
else if ([[CBUUID UUIDWithString:(NSString *)UUID_TARGET_TEMPERATURE] isEqualTo:characteristic.UUID]) {
if (!_setTemp) {
_setTemp = @([[self getNumberFromData:characteristic.value] doubleValue] / 10.0);
if ([_delegate respondsToSelector:@selector(setTempDidUpdate)]) {
[_delegate setTempDidUpdate];
}
} else {
_setTemp = @([[self getNumberFromData:characteristic.value] doubleValue] / 10.0);
}
#ifdef DEBUG
NSLog(@"Set Temp:%@",_setTemp);
#endif
}
// Booster Temp
else if ([[CBUUID UUIDWithString:(NSString *)UUID_BOOSTER_TEMPERATURE] isEqualTo:characteristic.UUID]) {
if (!_boostTemp) {
_boostTemp = @([[self getNumberFromData:characteristic.value] doubleValue] / 10.0);
if ([_delegate respondsToSelector:@selector(boostDidUpdate)]) {
[_delegate boostDidUpdate];
}
} else {
_boostTemp = @([[self getNumberFromData:characteristic.value] doubleValue] / 10.0);
}
#ifdef DEBUG
NSLog(@"Boost Temp:%@",_boostTemp);
#endif
}
// Battery
else if ([[CBUUID UUIDWithString:(NSString *)UUID_BATTERY_CAPACITY] isEqualTo:characteristic.UUID]) {
#ifdef DEBUG
if (_batteryLevel.intValue != [[self getNumberFromData:characteristic.value] intValue]) {
NSLog(@"Battery Level:%@",_batteryLevel);
}
#endif
_batteryLevel = [self getNumberFromData:characteristic.value];
if ([_delegate respondsToSelector:@selector(batteryLevelDidUpdate)]) {
[_delegate batteryLevelDidUpdate];
}
// Turn on notifications
if (!characteristic.isNotifying) {
[_discoveredPeripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
// Temperature
else if ([[CBUUID UUIDWithString:(NSString *)UUID_CURRENT_TEMPERATURE] isEqualTo:characteristic.UUID]) {
_currentTemp = @([[self getNumberFromData:characteristic.value] doubleValue] / 10);
if ([_delegate respondsToSelector:@selector(tempDidUpdate)]) {
[_delegate tempDidUpdate];
}
// High Temp "Breaker". Lower set temp if device is overheating
if (_currentTemp.doubleValue > 220.0) {
[self writeTemperatureChange:@(1800)];
[self writeBoostChange:@(0)];
// Call set temp and booster change callbacks
if ([_delegate respondsToSelector:@selector(setTempDidUpdate)]) {
[_delegate setTempDidUpdate];
}
if ([_delegate respondsToSelector:@selector(boostDidUpdate)]) {
[_delegate boostDidUpdate];
}
}
// Turn on notifications
if (!characteristic.isNotifying) {
[_discoveredPeripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
// Battery State
else if ([[CBUUID UUIDWithString:(NSString *)UUID_CHARGER_STATUS] isEqualTo:characteristic.UUID]) {
NSNumber *t = [self getNumberFromData:characteristic.value];
if ([t intValue] == 2) {
#ifdef DEBUG
if (!_charging) {
NSLog(@"Charging");
}
#endif
_charging = YES;
} else {
#ifdef DEBUG
if (_charging) {
NSLog(@"Not Charging");
}
#endif
_charging = NO;
}
if ([_delegate respondsToSelector:@selector(chargeStatusDidUpdate)]) {
[_delegate chargeStatusDidUpdate];
}
_batteryStateTimer = CreateDispatchTimer(2.000f, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_discoveredPeripheral readValueForCharacteristic:characteristic];
});
}
// Heater
else if ([[CBUUID UUIDWithString:(NSString *)UUID_CURRENT_ACCU] isEqualTo:characteristic.UUID]) {
NSNumber *deviceOn = [self getNumberFromData:characteristic.value];
if ([deviceOn intValue] >= 5000) {
#ifdef DEBUG
if (!_heating) {
NSLog(@"Heating");
}
#endif
_heating = YES;
} else {
#ifdef DEBUG
if (_heating) {
NSLog(@"Not Heating");
}
#endif
_heating = NO;
}
if ([_delegate respondsToSelector:@selector(heatingStatusDidUpdate)]) {
[_delegate heatingStatusDidUpdate];
}
_heaterStateTimer = CreateDispatchTimer(2.000f, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_discoveredPeripheral readValueForCharacteristic:characteristic];
});
}
// Power On Time
else if ([[CBUUID UUIDWithString:(NSString *)UUID_POWER_ON_TIME] isEqualTo:characteristic.UUID]) {
_powerOnTime = [self getNumberFromData:characteristic.value];
if ([_delegate respondsToSelector:@selector(powerOnTimeDidUpdate)]) {
[_delegate powerOnTimeDidUpdate];
}
#ifdef DEBUG
NSLog(@"Device Runtime:%@",_powerOnTime);
#endif
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error
{
}
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error
{
if (!error) {
// Update the object's state to match the vape on tem changes
if ([[CBUUID UUIDWithString:(NSString *)UUID_TARGET_TEMPERATURE] isEqualTo:characteristic.UUID] || [[CBUUID UUIDWithString:(NSString *)UUID_BOOSTER_TEMPERATURE] isEqualTo:characteristic.UUID]) {
[_discoveredPeripheral readValueForCharacteristic:characteristic];
}
}
}
#pragma mark - Helpers for accessing BT data
- (NSNumber *)getNumberFromData:(NSData *)data
{
return [NSNumber numberWithUnsignedInt:*(const UInt32 *)[data bytes]];
}
- (NSString *)getStringValueFromData:(NSData *)data
{
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
#pragma mark - Update Device
- (void)writeTemperatureChange:(NSNumber *)newTemp
{
NSNumber *t = newTemp;
// Write new temperature to device
if ([newTemp isKindOfClass:[NSTimer class]]) {
t = @([[(NSTimer *)newTemp userInfo] intValue] * 10);
}
if (400 > t.intValue > 2100) {
// Temp too high.
for (CBService *s in _discoveredPeripheral.services) {
if ([[CBUUID UUIDWithString:(NSString *)UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE] isEqualTo:s.UUID]) {
for (CBCharacteristic *c in s.characteristics)
if ([[CBUUID UUIDWithString:(NSString *)UUID_TARGET_TEMPERATURE] isEqualTo:c.UUID]) {
NSLog(@"%@ is too high",t);
[_delegate setTempDidUpdate];
}
}
}
} else {
UInt16 v = CFSwapInt16HostToLittle(t.intValue);
NSData *d = [[NSData alloc] initWithBytes:&v length:sizeof(v)];
for (CBService *s in _discoveredPeripheral.services) {
if ([[CBUUID UUIDWithString:(NSString *)UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE] isEqualTo:s.UUID]) {
for (CBCharacteristic *c in s.characteristics)
if ([[CBUUID UUIDWithString:(NSString *)UUID_TARGET_TEMPERATURE] isEqualTo:c.UUID]) {
#ifdef DEBUG
NSLog(@"Changing temp to:%@",t);
#endif
[_discoveredPeripheral writeValue:d forCharacteristic:c type:CBCharacteristicWriteWithResponse];
}
}
}
}
}
- (void)writeBoostChange:(NSNumber *)newBoost
{
// Write new boost change to device
NSNumber *t = newBoost;
// Write new temperature to device
if ([newBoost isKindOfClass:[NSTimer class]]) {
t = @([[(NSTimer *)newBoost userInfo] intValue] * 10);
}
UInt16 v = CFSwapInt16HostToLittle(t.intValue);
NSData *d = [[NSData alloc] initWithBytes:&v length:sizeof(v)];
for (CBService *s in _discoveredPeripheral.services) {
if ([[CBUUID UUIDWithString:(NSString *)UUID_TEMPERATURE_AND_BATTERY_CONTROL_SERVICE] isEqualTo:s.UUID]) {
for (CBCharacteristic *c in s.characteristics)
if ([[CBUUID UUIDWithString:(NSString *)UUID_BOOSTER_TEMPERATURE] isEqualTo:c.UUID]) {
#ifdef DEBUG
NSLog(@"Changing boost to:%@",t);
#endif
[_discoveredPeripheral writeValue:d forCharacteristic:c type:CBCharacteristicWriteWithResponse];
}
}
}
}
// Create a timer with a block
dispatch_source_t CreateDispatchTimer(double interval, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
#pragma mark -
- (NSNumber *)getPowerOnTime
{
// Fetch new power on time
for (CBService *s in _discoveredPeripheral.services) {
if ([[CBUUID UUIDWithString:(NSString *)UUID_DIAGNOSTICS_SERVICE] isEqualTo:s.UUID]) {
for (CBCharacteristic *c in s.characteristics)
if ([[CBUUID UUIDWithString:(NSString *)UUID_POWER_ON_TIME] isEqualTo:c.UUID]) {
[_discoveredPeripheral readValueForCharacteristic:c];
}
}
}
return _powerOnTime;
}
@end