iPhoneからBLEのCurrent Time Serviceで現在時刻を取得する（Pythonista編）

#概要

元は M5Stack で現在時刻を取得する方法として NTP ではなく BLE で取得する方法を探していたところ Current Time Service というのがありました。とりあえず Pythonista で試し返ってくる時刻データの内容を確認するためのサンプルプログラムです。

これでできるなら M5Stack/M5StickC でも同様にできるだろう、と始めたのですが、１週間ほど格闘することになりました。結果的に取得はできたので、それについてはこちら。
[iPhoneからBLEのCurrent Time Serviceで現在時刻を取得する（M5StickC編）](https://qiita.com/inasawa/items/677addc9d966b01e71c2)

##環境

- [Pythonista3 v3.2](http://omz-software.com/pythonista/)
- iPhone 6 iOS 12.4.2 (BLE Server)
- iPad mini 4 iOS 13.1.2 (BLE Client)

##実行例

iPad上でPythonistaを実行し、iPhoneの時刻をBLE Current Time Serviceで取得
Pythonista実行時コンソール出力

```console:
Scanning for peripherals...
Discovered peripheral: MyiPhone (..........)
Connected: MyiPhone
Discovering services...
CTS Service found: 1805
CTS Characteristics found: 2A2B
2019-10-04 19:02:36.281 (Fri) Adjust Reason: 2
2019-10-04 19:02:37.300 (Fri) Adjust Reason: 2
2019-10-04 19:02:38.292 (Fri) Adjust Reason: 2
2019-10-04 19:02:39.312 (Fri) Adjust Reason: 2
2019-10-04 19:02:40.300 (Fri) Adjust Reason: 2
2019-10-04 19:02:41.320 (Fri) Adjust Reason: 2
2019-10-04 19:02:42.312 (Fri) Adjust Reason: 2
　　　　　：　　　　　：　　　　　：
```

#Pythonista3プログラム

iPhoneの前後左右の傾きに応じて、左右の車輪を動かすための指示をBLE経由で送信する仕組みです。

```python:BLE_Current_Time_Service.py
import time
import cb
import struct

CTS_DEVICE_NAME         = 'MyiPhone'
CTS_SERVICE_UUID        = '1805'
CTS_CHARACTERISTIC_UUID = '2A2B'
DAY_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

class MyCentralManagerDelegate (object):
    def __init__(self):
        self.peripheral = None
        self.characteristic = None

    def did_discover_peripheral(self, p):
        if p.name and CTS_DEVICE_NAME in p.name and not self.peripheral:
            # Keep a reference to the peripheral, so it doesn't get garbage-collected:
            print('Discovered peripheral: %s (%s)' % (p.name, p.uuid))
            self.peripheral = p
            cb.connect_peripheral(self.peripheral)
            cb.stop_scan()

    def did_connect_peripheral(self, p):
        print('Connected: %s' % p.name)
        print('Discovering services...')
        p.discover_services()

    def did_fail_to_connect_peripheral(self, p, error):
        print('Failed to connect: %s' % (error,))

    def did_disconnect_peripheral(self, p, error):
        print('Disconnected, error: %s' % (error,))
        self.peripheral = None
        self.characteristic = None
        cb.scan_for_peripherals()

    def did_discover_services(self, p, error):
        for s in p.services:
            if CTS_SERVICE_UUID in s.uuid:
                print('CTS Service found: %s' % (s.uuid,))
                p.discover_characteristics(s)

    def did_discover_characteristics(self, s, error):
        if CTS_SERVICE_UUID in s.uuid:
            for c in s.characteristics:
                if CTS_CHARACTERISTIC_UUID in c.uuid:
                    print('CTS Characteristics found: %s' % (c.uuid,))
                    self.characteristic = c

    def did_update_value(self, c, error):
        if CTS_CHARACTERISTIC_UUID == c.uuid:
            # 10 byte data
            # byte 0,1 Year (byte1*256+byte0)
            # byte 2   Month
            # byte 3   Day
            # byte 4   Hours
            # byte 5   Minutes
            # byte 6   Seconds
            # byte 7   Day of Week (Monday = 1, ... Sunday = 7)
            # byte 8   1/256th of a second (milliseconds = byte8*1000/256)
            # byte 9   Adjust Reason
            #          bit 0: Manual time update
            #          bit 1: External reference time update
            #          bit 2: Change of time zone
            #          bit 3: Change of DST (daylight savings time)
            data = struct.unpack('HBBBBBBBB', c.value)
            print('{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d} ({:s}) Adjust Reason: {:d}'.format(
                  *data[:6], int(data[7]*1000/256), DAY_OF_WEEK[data[6]-1], data[8]))

    def get_current_time(self):
        if self.peripheral is not None and self.characteristic is not None:
            self.peripheral.read_characteristic_value(self.characteristic)
            return True
        return False


cb_delegate = MyCentralManagerDelegate()
print('Scanning for peripherals...')
cb.set_central_delegate(cb_delegate)
cb.scan_for_peripherals()

# Keep the connection alive until the 'Stop' button is pressed:
try:
    while True:
        cb_delegate.get_current_time()
        time.sleep(1)
except KeyboardInterrupt:
    # Disconnect everything:
    cb.reset()
```