RICOH Theta S に Pythonistaからアクセスしたい

この記事は [Pythonista Advent Calendar 2017](https://qiita.com/advent-calendar/2017/pythonista) の8日目の記事です。
Theta S に Pythonista 経由でアクセスして写真を撮る方法を紹介します。

## 📷 概要
iPhoneを持ち歩くようになってから、コンデジを連れ出す機会が少なくなりましたが、
ThetaやGoProなど単機能特化型カメラはiPhoneでカバーできない写真が撮れるので、よく持ち歩いています。

Theta Sには公式のアプリもあって使いやすいのですが、
ファイル共有に難があり、写真の加工は他のアプリに任せる必要があることの2点に、不便さを感じます。
そこで、PythonistaとTheta APIを使って必要最低限の機能を実装して見ました。

APIは下記を参考にしています。
https://developers.theta360.com/ja/docs/v2.1/api_reference/


## 🎒 準備
* Pythonista 
* RICOH Theta S以降 (無印のThetaとapiが異なります)

最近発売されたTheta Vも同じ構成で動くと思います。
**requests** モジュールに依存しますが、親切な Pythonista にはプリインストールされているので、
準備はこれだけ。


## 💻 コードの中身
コードを書き始める前に、流れを把握します。

### 静止画を1枚撮るまでの流れ
1. APIバージョンの指定
2. 撮影前状態の取得
3. プロパティの取得・設定
4. 静止画撮影
5. ファイル保存確認
6. ファイル取得

https://developers.theta360.com/ja/docs/v2.1/api_reference/getting_started.html

例えば、静止画のプロパティ設定をPOSTするには、

```bash:property
POST /osc/commands/execute
{
    "name": "camera.setOptions",
    "parameters": {
        "options": {
            "fileFormat": {
                "type": "jpeg",
                "width": 2048,
                "height": 1024
            }
        }
    }
}
```

と書きますが、これを Python と request で書くと

```python:property
import request

param = {
    "name": "camera.setOptions",
    "parameters": {
        "options": {
            "fileFormat": {
                "type": "jpeg",
                "width": 2048,
                "height": 1024
            }
        }
    }
}
request.post("http://192.168.1.1:80/osc/commands/execute", data=param) 
```

になります。

### ラッピングしたコード
流れに沿って、Python でラップしていきます。

```python:wrapper

import io
import json
import inspect

import requests
from PIL import Image

cam = 'http://192.168.1.1:80'
url = cam+'/osc/'
exe = url+'commands/execute'


def info():
	r=requests.get(url+inspect.currentframe().f_code.co_name)
	return r.json()
	
	
def state():
	r=requests.post(url+inspect.currentframe().f_code.co_name)
	return r.json()
		
		
def startSession():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name),
'parameters':{}}
	
	r=requests.post(exe, data=json.dumps(j))
	return r.json()
	

def closeSession(id):
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name),
'parameters':{'sessionId':'{}'.format(id)}}
	
	r=requests.post(exe, data=json.dumps(j))
	return r.json()
	
	
def setOptionsid(id):
	j={'name': 'camera.setOptions',
	'parameters':{'sessionId':id,'options':{'clientVersion':2}}}
	r=requests.post(exe,data=json.dumps(j))
	return r.json()
	
	
def getCameraOption():
	j={'name': 'camera.{}'.format(inspect.currentframe().f_code.co_name),
	'parameters':{'optionNames':['fileFormat','fileFormatSupport']}}
	r=requests.post(exe,data=json.dumps(j))
	return r.json()


def listFiles():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
	r=requests.post(exe,data=json.dumps(j))
	return r.json()
	

def getLivePreview():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
	r=requests.post(exe,data=json.dumps(j))
	return r.json()
	

def setOptions(type,width,height):
	j={'name': 'camera.{}'.format(inspect.currentframe().f_code.co_name),
	'parameters':{'options':{'fileFormat':{'type':type,'width':width,'height':height}}}}
	
	r=requests.post(exe, data=json.dumps(j))
	return r.json()
	
	
def takePicture():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
	
	r=requests.post(exe, data=json.dumps(j))
	return r.json()
	

def startCapture():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
	
	r=requests.post(exe,data=json.dumps(j))
	return r.json()
	
			
def stopCapture():
	j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
	r=requests.post(exe,data=json.dumps(j))
	return r.json()
			

def checkForUpdates(fig):
	j={'stateFingerprint':fig}
	r=requests.post(url+inspect.currentframe().f_code.co_name, data=json.dumps((j)))
	return r.json()
	
	
def reset():
	r=requests.post(exe+inspect.currentframe().f_code.co_name)
	
	
def image():
	while len(state()['state']['_latestFileUrl']) > 0:
		break
	
	r = requests.get(state()['state']['_latestFileUrl'])
	return Image.open(io.BytesIO(r.content))
	
	
def main():
	
	# session
	d = startSession()
	if d['state'] == 'error':
		return closeSession('SID_001')
		
	# api and options
	k=d['results']['sessionId']
	r=setOptionsid(k)
	
	while r['state'] == 'done':
		break	
	setOptions('jpeg',2048,1024)

	# take pictures
	d = checkForUpdates(state()['fingerprint'])
	tmp = d['stateFingerprint']
	
	t = takePicture()
	while tmp != state()['fingerprint']:
		break

	# image
	img=image()
	
	# session
	closeSession(k)
	
	return img

		
if __name__ == '__main__':
	main()
```
ui モジュールでカメラ設定を入力したり、撮った写真を PIL で画像加工したりする前の
最小限のコードになりました。


## 📚 まとめ
バッテリー同梱型の Python を生活に持ち込んで、日常の小さな問題を解決できると楽しいです。

今回はカメラのコントロールにiPhoneとPythonistaを使って見ましたが、
IOT 機器のコントロールに応用出来るだろうし、Pythonista には Bluetooth モジュールが入っているので、
iPhoneを汎用リモコン化することもできそうですね。機会があれば試して見たいです。

最後までお読みいただきありがとうございました。どなたかの参考になれば嬉しいです。