Кручу, кручу, кручу, кручу педали, кручу

Дети подросли и оборвали провода на тренажере. Вело-табло перестало работать и крутить педали стало совсем не интересно. Я решил починить табло по-нашенски, по ios-овски.И проделал следующие шаги

примотал простейший BLE датчик к корпусу тренажера прилепил магнит к шатуну написал программу под iPad Далее чуть-чуть подробнее, со схемой, текстом, фото и видео.Cadence sensorimageРис. 1 Фотография датчика

Общая схема работы устройства простая — геркон реагирует на приближение магнита, замыкает цепь, BLE-датчик посылает сигнал о событии.

imageРис. 2 Схема счетчика обормотов

Для создания датчика необходимо купить следующие детали

BLE112 — блутуз-контроллер компании BlueGiga литиевую батарейку 3 вольта геркон (на схеме S1) сопротивление и два конденсатора черную коробочку и собрать согласно схеме.Общая стоимость устройства — менее $20.Размер датчика смотрите на рисунке 3, вес — 50 граммов.imageРис. 3 Размеры датчика

BLE112 необходимо запрограммировать следующим образом

Текст прошивки # Cadence sensor prototype dim tmp (12) dim counter dim result dim last dim sleep_counter dim awake dim connected

event system_boot (major, minor, patch, build, ll_version, protocol, hw) # call gap_set_mode (gap_general_discoverable, gap_undirected_connectable) # call sm_set_bondable_mode (1) # call hardware_set_soft_timer (32000×30, 0, 0) # Set pins P1_0, P1_1 as output to prevent current leak (BLE112_Datasheet.pdf section 2.1) call hardware_io_port_config_direction (1, 3)(result) call hardware_io_port_write (1, 3, 3)(result)

# # Pull P0 up and enable interrupts on P0_0 (on falling edge) #call hardware_io_port_config_pull (0, 0, 1)(result) call hardware_io_port_config_irq (0, 1, 0)(result) end

event hardware_soft_timer (handle) if connected = 0 then sleep_counter = sleep_counter + 1 if sleep_counter >= 2 then # go to sleep # disable timer call hardware_set_soft_timer (0, 0, 0) awake = 0 # disable BT broadcast call gap_set_mode (gap_non_discoverable, gap_non_connectable) end if else # read battery level call hardware_adc_read (15,3,0) end if end

event hardware_io_port_status (timestamp, port, irq, state) # Debounce filter: ignore events with rates > ~180 RPM if timestamp > (last + 10000) then if awake = 0 then call gap_set_mode (gap_general_discoverable, gap_undirected_connectable) #call sm_set_bondable_mode (1) call hardware_set_soft_timer (32000×60, 0, 0) # single shot sleep timer awake = 1 end if sleep_counter = 0 counter = counter + 1 result = timestamp >> 5 # S+C tmp (0:1) = $3 tmp (1:4) = counter tmp (5:2) = result tmp (7:2) = counter tmp (9:2) = result call attributes_write (xgatt_cadence, 0, 11, tmp (0:11)) end if last = timestamp end

event hardware_adc_result (input, value) #battery level reading received, store to gatt if input = 15 then call attributes_write (xgatt_battery, 0, 2, value) end if end

event connection_status (connection, flags, address, address_type, conn_interval, timeout, latency, bonding) connected = 1 end

event connection_disconnected (handle, result) call gap_set_mode (gap_general_discoverable, gap_undirected_connectable) connected = 0 end Магнит Магнит крепится к любой двигающейся части Вашего велосипеда, тренажера, шагожора и т.д. На рисунке 4 магнит в виде шайбы прилеплен к шаго-тренажеру.

imageРис. 4 Крепление магнита к шатуну

imageРис. 5 При приближении магнита к датчику, датчик срабатывает и посылает сигнал на iPad

При приближении к магниту геркон издает характерный щелчок — это полезно при отладке программы и проверки работоспособности устройства.

Приложение под iOS Приложение состоит из трех замечательных частейчасть первая — прием события от BLE часть вторая — расчет и отображение данных полета часть третья — 3D анимация Прием события от BLE Сканируем сигнал от BLE // // BTLE.m // doraPhone // // Created by Kirill Novichikhin on 2/5/13. // //

#import «BTLE.h» #import «AppDelegate.h»

static CBUUID *kServiceCbuuidCadence, *kServiceDeviceInfo, *kCharacteristicDeviceModel, *kCharacteristicDeviceSerial, *kCharacteristicCadence ;

static const char* cbCentralStateNames[] = { «CBCentralManagerStateUnknown», «CBCentralManagerStateResetting», «CBCentralManagerState», «CBCentralManagerStateUnauthorized», «CBCentralManagerStatePoweredOff», «CBCentralManagerStatePoweredOn» };

static const char* btleStateName (int state) { const char* stateName = «INVALID»; if (state >= 0 && state < sizeof(cbCentralStateNames)/sizeof(const char*)) { stateName = cbCentralStateNames[state]; } return stateName; }

@implementation BTLE

+ (void)initialize { kServiceCbuuidCadence = [CBUUID UUIDWithString:@»1816»]; kServiceDeviceInfo = [CBUUID UUIDWithString:@»180A»]; kCharacteristicDeviceModel = [CBUUID UUIDWithString:@»2A24»]; kCharacteristicDeviceSerial = [CBUUID UUIDWithString:@»2A25»]; kCharacteristicCadence = [CBUUID UUIDWithString:@»2A5B»]; }

— (void)startScan { if (![self isLECapableHardware]) { return; } [_manager scanForPeripheralsWithServices:@[kServiceCbuuidCadence] options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @YES}]; NSLog (@«Started BLE scan»); }

— (void)stopScan { [_manager stopScan]; }

— (void)centralManagerDidUpdateState:(CBCentralManager *)central { NSLog (@«New Bluetooth state: %s», btleStateName (central.state)); switch (central.state) { case CBCentralManagerStatePoweredOn: [self startScan]; break; case CBCentralManagerStateResetting: case CBCentralManagerStateUnauthorized: case CBCentralManagerStateUnknown: case CBCentralManagerStateUnsupported: case CBCentralManagerStatePoweredOff: break; } }

— (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { NSLog (@«Discovered services for peripheral»); for (CBService* s in peripheral.services) { NSLog (@«Service: %@», s.UUID); } for (CBService* s in peripheral.services) { if ([s.UUID isEqual: kServiceDeviceInfo]) { NSLog (@«Device info service found»); [peripheral discoverCharacteristics:[NSArray arrayWithObjects: kCharacteristicDeviceModel, kCharacteristicDeviceSerial, nil] forService: s]; } else if ([s.UUID isEqual: kServiceCbuuidCadence]) { NSLog (@«Cadence service found»); [peripheral discoverCharacteristics:@[kCharacteristicCadence] forService: s]; } } }

— (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if ([service.UUID isEqual: kServiceCbuuidCadence]) { for (CBCharacteristic* c in service.characteristics) { if ([c.UUID isEqual: kCharacteristicCadence]) { NSLog (@«Found characteristic: Cadence»); [peripheral setNotifyValue: YES forCharacteristic: c]; } else { NSLog (@«Discovered unsupported characteristic %@», c.UUID); } } } else if ([service.UUID isEqual: kServiceDeviceInfo]) { for (CBCharacteristic* c in service.characteristics) { NSLog (@«Discovered characteristic %@», c.UUID); if ([c.UUID isEqual: kCharacteristicDeviceModel] || [c.UUID isEqual: kCharacteristicDeviceSerial]) { [peripheral readValueForCharacteristic: c]; } } } else { NSLog (@«ERROR: got characteristics for service %@ — was not requesting those», service.UUID); return; } }

— (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog (@«Connected peripheral %@», peripheral);

AppDelegate *appRoot = (AppDelegate *)[[UIApplication sharedApplication] delegate];

// TODO appRoot.isConnected = true;

// FIXME: delegate needs to be set to blePeripheral peripheral.delegate = self; [peripheral discoverServices:@[kServiceCbuuidCadence, kServiceDeviceInfo]]; }

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if ([characteristic.UUID isEqual: kCharacteristicCadence]) { NSData* data = characteristic.value; AppDelegate *appRoot = (AppDelegate *)[[UIApplication sharedApplication] delegate]; // TODO appRoot.serial = _serial; appRoot.model = _model; [appRoot performSelectorOnMainThread:@selector (newCadenceMeasurement:) withObject: data waitUntilDone: NO]; } else if ([characteristic.UUID isEqual: kCharacteristicDeviceModel]) { NSString* model = [[NSString alloc] initWithData: characteristic.value encoding: NSUTF8StringEncoding]; NSLog (@«Device model: %@», _model); _model = model; } else if ([characteristic.UUID isEqual: kCharacteristicDeviceSerial]) { // // Convert to a hex string _serial = [[NSString alloc] initWithData: characteristic.value encoding: NSUTF8StringEncoding];

NSLog (@«Device serial: %@», _serial); } else { NSLog (@«ERROR: unexpected BLE Notify: %@ %@=%@», peripheral, characteristic.UUID, characteristic.value); } }

 — (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { AppDelegate *appRoot = (AppDelegate *)[[UIApplication sharedApplication] delegate]; appRoot.isConnected = false; self.peripheral = nil;

// BLEPeripheral* blePeripheral = [_peripherals ensurePeripheral: peripheral]; NSLog (@«Disconnected from %@ (%@)», peripheral.name, error.description); }

— (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { if (self.peripheral == nil) { [central connectPeripheral: peripheral options: nil]; NSLog (@«Connecting to \»%@\», peripheral.name); self.peripheral = peripheral; } }

/* Uses CBCentralManager to check whether the current platform/hardware supports Bluetooth LE. An alert is raised if Bluetooth LE is not enabled or is not supported. */  — (BOOL)isLECapableHardware { BOOL result = FALSE; BOOL unknownState = NO; NSString * errorString = nil; int state = [_manager state]; switch (state) { case CBCentralManagerStateUnsupported: errorString = @«The platform/hardware doesn’t support Bluetooth Low Energy.»; break; case CBCentralManagerStateUnauthorized: errorString = @«The app is not authorized to use Bluetooth Low Energy.»; break; case CBCentralManagerStatePoweredOff: errorString = @«Bluetooth is currently powered off.»; break; case CBCentralManagerStatePoweredOn: result = TRUE; case CBCentralManagerStateUnknown: default: unknownState = YES; errorString = @«Unknown state»; ; //result = FALSE; } const char* stateName = btleStateName (state); NSLog (@«Central manager state: %s (%u)», stateName, state); if (! result && ! unknownState) { UIAlertView *alert = [[UIAlertView alloc] init]; alert.message = errorString; [alert addButtonWithTitle:@«OK»]; [alert show]; } return result; }

— (id)init { _queue = dispatch_queue_create («ru.intersofteurasia.do-ra.ble», NULL); _manager = [[CBCentralManager alloc] initWithDelegate: self queue:_queue]; return self; }

— (void)dealloc { [self stopScan]; } @end

Анимация Быстро делаем трассу — мост в Крым. Кто-бы не владел Крымом — мост нужен. Длина 6.2 км. Ширина 10 метров. Я сделал 256 асфальтовых полигонов длиной 2 метра и столько же травы по обочинам (рисунок 6)imageРисунок 6. МостАнимация соперника.Соперник взят с Тур Де Франс. Ян Ульрих. Достаточно 4-ех кадров для анимации Яна. 4 кадра на 1 оборот педалей. Качество не ахти, программа была сделана за день, поэтому без изысков.imageРисунок 7. Ян Ульрих

Анимация себя — это святое.Основное время ушло на себя. Я прислонил велосипед в угол офиса и взгромоздился на него, изображая движение.imageРисунок 8. Я в офисе на велосипеде.

16 раз равномерно крутанул педали — сделал 16 кадров, почистил в фотошопе, склеил анимацию. После редактирования осталось 12 кадров на 1 оборот педалей.

Для интереса пришлось размножить Яна Ульриха до 50 копий и программа завершена.Замечу, пока отлаживался — накачал ляхи.

Полезное приложение, скажу Вам, только начинаешь гонку и уже не остановиться.

В заключении 45-секундное видео, как это работат

[embedded content]

Извиняюсь за вертикальное видео, зато видно, что снималось на 5-ый iPhone).

Всем спасибо. Крутите педали.

© Habrahabr.ru