はじめに
前回動かしたサーボモーターを Apple Watch から動かしたいと思います。
新品価格 |
ラズパイは Raspberry Pi 3 Model B です。
その他必要なやつ
オスメスジャンバ線3本を使いました。
配線
前回と同じです。
- ジャンバ線1のオスをモーターの茶色に挿す。
- ジャンバ線1のメスをラズパイの 5V 横の GND に挿す。
- ジャンバ線2のオスをモーターの黄色に挿す。
- ジャンバ線2のメスをラズパイの GPIO14 に挿す。
- ジャンバ線3のオスをモーターの赤色に挿す。
- ジャンバ線3のメスをラズパイの 5V に挿す。
ラズパイ側の設定
コードを書いていきます(なんとなく pipenv 使います)。
-
SSH 接続する。
-
必要なものを入れていく(実行時にパーミッションエラーになったので sudo でやってます。よくわかってない。。。)。
1234567891011121314151617181920212223# BLEに必要なやつ$ sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev# ディレクトリを作成$ mkdir ble_servo# ディレクトリに移動$ cd ble_servo# python3系でpipenv初期化$ pipenv --python 3# RPi.GPIOインストール$ sudo pipenv install RPi.GPIO# pyblenoインストール$ sudo pipenv install pybleno# servo.pyファイル作成$ touch servo.py# servo.pyファイル編集$ vim servo.py -
i と入力し入力モードにする。
-
下記ソースをコピペする。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293from pybleno import *import RPi.GPIO as GPIOimport timeimport sysimport signalbleno = Bleno()class ServoCharacteristic(Characteristic):def __init__(self):Characteristic.__init__(self, {'uuid': 'ff11','properties': ['read', 'write'],'value': None})self._value = 0self._updateValueCallback = Nonedef move(self):GPIO.setmode(GPIO.BCM)GPIO.setup(14, GPIO.OUT)servo = GPIO.PWM(14, 50)servo.start(0.0)servo.ChangeDutyCycle(2.5)time.sleep(0.6)servo.ChangeDutyCycle(10.0)def onWriteRequest(self, data, offset, withoutResponse, callback):self._value = dataprint('EchoCharacteristic - %s - onWriteRequest: value = %s' % (self['uuid'], [hex(c) for c in self._value]))self.move()if self._updateValueCallback:self._updateValueCallback(self._value)callback(Characteristic.RESULT_SUCCESS)def onReadRequest(self, offset, callback):print('onReadRequest')callback(Characteristic.RESULT_SUCCESS, self._value)def onSubscribe(self, maxValueSize, updateValueCallback):print('onSubscribe')self._updateValueCallback = updateValueCallbackdef onUnsubscribe(self):print('onUnsubscribe')self._updateValueCallback = Nonedef onStateChange(state):print('stateChange: ' + state)if (state == 'poweredOn'):bleno.startAdvertising('Servo', ['FF10'])else:bleno.stopAdvertising()bleno.on('stateChange', onStateChange)servoCharacteristic = ServoCharacteristic()def onAdvertisingStart(error):print('advertisingStart: ' + ('error ' + error if error else 'success'))if not error:bleno.setServices([BlenoPrimaryService({'uuid': 'FF10','characteristics': [servoCharacteristic]})])bleno.on('advertisingStart', onAdvertisingStart)bleno.start()# Ctrl+CによってSIGINTシグナルが送信された時のハンドラ。終了前にGPIO.cleanupを呼び出すdef handler(signum, frame):print('Signal handler called with signal', signum)bleno.stopAdvertising()bleno.disconnect()GPIO.cleanup()sys.exit(0)# ハンドラの登録signal.signal(signal.SIGINT, handler)while True:time.sleep(0.1)
Watch側の設定
watch 側のコードを書いていきます(L チカやってたので名称がそのままになってます)。
- Xcode の新規プロジェクト作成で「Watch App」を選択する。
- Storyboard で
InterfaceController
を下記のように設定する。
- Watch Kit の info.plist に
NSBluetoothAlwaysUsageDescription
キーを追加する。 -
InterfaceController.swift に下記をコピペする。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121import WatchKitimport Foundationimport CoreBluetoothclass InterfaceController: WKInterfaceController {private var centralManager: CBCentralManager!private var peripheral: CBPeripheral?private var lightCharacteristic: CBCharacteristic?@IBOutlet private weak var ledSwitch: WKInterfaceSwitch!override func awake(withContext context: Any?) {centralManager = CBCentralManager(delegate: self, queue: nil)}@IBAction private func scanForPeripherals() {centralManager.scanForPeripherals(withServices: nil, options: nil)}@IBAction private func connectPeripheral(_ sender: Any) {if let peripheral = peripheral {centralManager.connect(peripheral, options: nil)}}@IBAction private func switchLED(_ value: Bool) {guard let peripheral = peripheral,let lightCharacteristic = lightCharacteristic else {ledSwitch.setOn(!value)return}var intValue: CUnsignedInt = value ? 1 : 0print("write characteristic and \(value)")let data = Data(bytes: &intValue, count: 1)peripheral.writeValue(data, for: lightCharacteristic, type: .withResponse)}}extension InterfaceController: CBCentralManagerDelegate {func centralManagerDidUpdateState(_ central: CBCentralManager) {switch central.state {case .unknown:print("state: unknown")case .resetting:print("state: resetting")case .unsupported:print("state: unsupported")case .unauthorized:print("state: unauthorized")case .poweredOff:print("state: poweredOff")case .poweredOn:print("state: poweredOn")@unknown default:assertionFailure("想定外")}}func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,advertisementData: [String : Any], rssi RSSI: NSNumber) {guard let peripheralName = peripheral.name else {return}print("peripheral: \(peripheralName)")if peripheralName == "raspberrypi" {centralManager.stopScan()self.peripheral = peripheral}}func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {print("connected")peripheral.delegate = selfperipheral.discoverServices(nil)}func centralManager(_ central: CBCentralManager,didFailToConnect peripheral: CBPeripheral, error: Error?) {print("fail connect")}}extension InterfaceController: CBPeripheralDelegate {func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {guard let services = peripheral.services else {if let error = error {print("error: \(error)")}return}print("Found \(services.count) services: \(services)")for service in services {print(service.uuid.uuidString)if service.uuid.uuidString == "FF10" {print("discovering characteristics")peripheral.discoverCharacteristics(nil, for: service)}}}func peripheral(_ peripheral: CBPeripheral,didDiscoverCharacteristicsFor service: CBService, error: Error?) {guard let characteristics = service.characteristics else {if let error = error {print("error in dicovercharc: \(error)")}return}print("Found \(characteristics) characteristics! : \(characteristics)")for characteristic in characteristics {if characteristic.uuid.uuidString == "FF11" {self.lightCharacteristic = characteristic}}}}
起動
ラズパイ側で下記コマンドを実行する!
1 |
sudo pipenv run python3 servo.py |
Watch のアプリを起動して Scan ボタン押下、Connect ボタン押下、スイッチ操作でモーターが動きます!
ちょっと工夫
よくあるモーターを動かして電気を消灯したりするやつをやってみたい!と思いやってみました。
追加で用意するもの。
- そのへんにあった 100 t ハンマーの上の部分
- 綿棒
- シャーペンの消しゴム
- セロハンテープ
完成品(ポイントは力を面から点にしているとこです)。
watch 操作 -> サーボモーターが動く -> エンター押下 -> Slackにメッセージ送信
これはもうwatchからslackに送信したと言っても過言ではない😇 pic.twitter.com/HfFj5ku2BC
— am10 (@am103141592) March 18, 2021
おわりに
これで Apple Watch(iPhone でも可)からモーターを動かすことができるようになりました。遊びの幅が広がりそうだけど何も思いつかない。。。
コメント
記事の内容、とても参考になりました。node.jsでサーボモーターが動かなくて困っていましたのでとても助かりました。
ただ、分からないことがありまして…、ラズパイ側のコード(sarvo.py)をラズパイ起動時に自動実行できますでしょうか。pipenvで書かれたコード、いろいろ試したのですが、自動実行がうまくいきませんでした…。
もし、すでに実装されているようでしたら、教えて頂きたいです。宜しくお願い致します。
Masakiさん
コメントありがとうございます!
起動時に自動実行はまだやったことありませんが下記記事は参考にならないでしょうか?
[Qiita-ラズパイ起動時にスクリプトを実行したい](https://qiita.com/ikemura23/items/6f9adce99a3db555a0e4)
このシェルスクリプトの中身を書き換えれば動きそうな気がします!
早速のご返事ありがとうございます。
自動起動させることができました。
仮想環境が入ると、/rc.localやsystemdで、試していたのですがうまくできず困っていました。(直接、パイソンを動かそうとしていました。)
もういっそうのこと、「ターミナルを自動起動させて、そこで、自動でフォルダ移動とプログラムの実行を行えないかな」という、初心者的思考で調べを進めると、シェルに行きつきました。(実際、初心者ですので…シェルも今日知りました。(・_・;))
今回は、こちらを参考に、ターミナルを起動する方法で自動起動させました。
(https://qiita.com/tonosamart/items/f59daa481f90c85a8a99)
ご指摘頂いた方法ですと、ターミナルは開かれないのかな?でしたら、そちらのほうが良いかもですね。