This article documents my findings of analyzing the Pentax K-1 WiFi protocol. The camera has built-in WiFi functionality. An app on a phone (Android and iOS) allows one to remote control the camera and browse images. Here, I want to figure out how it's implemented.
The first step to analyzing the protocol is to be able to intercept the communication between the app and the camera. Therefore, I installed the Image Sync app on my phone and extracted the APK from it.
In short, install ADB on a computer, connect the phone with USB debugging enabled and run the following command:
adb shell 'pm list packages -f com.ricohimaging.imagesync'
It should output the path of the apk, as in:
package:/data/app/com.ricohimaging.imagesync-2/base.apk=com.ricohimaging.imagesync
Now, download that file to the current directory:
adb pull /data/app/com.ricohimaging.imagesync-2/base.apk .
See the second half of http://android.stackexchange.com/a/75436/34272 for good instructions.
ARC Welder is a Chrome app that provides an Android runtime environment inside Chrome. Install that and load the base.apk
downloaded from the phone.
Next, enable WiFi on the camera and connect the computer to that network. Start wireshark to generate a trace of the connection and then start the Ricoh app in Chrome.
The app talks HTTP to the camera. First, it gets general information about the camera's capabilities and then switches to a WebSocket connection to stream requests/responses. Here's an overview of the end points it exports.
GET /v1/props HTTP/1.1
The first request is sent to /v1/props
. It returns a Json file describing the camera:
{"errCode": 200, "errMsg": "OK", "resoList": [ "1080x720", "720x480"], "movieResoList": [ "1280x720", "720x404"], "WBModeList": [ "auto", "multiAuto", "daylight", "shade", "cloud", "daylightFluorescent", "dayWhiteFluorescent", "coolWhiteFluorescent", "warmWhiteFluorescent", "tungsten", "cte", "manual1", "manual2", "manual3", "colorTemp1", "colorTemp2", "colorTemp3"], "stillSizeList": [ "L3", "L2", "L1", "M3", "M2", "M1", "S3", "S2", "S1", "XS3", "XS2", "XS1"], "movieSizeList": [ "FHD30p", "FHD25p", "FHD24p", "HD60p", "HD50p", "FHD60i", "FHD50i"], "shootModeList": [ "single", "continuousH", "continuousM", "continuousL", "self12s", "self2s", "selfCotinuous", "remocon", "remocon3s", "remoconContinous", "bracket", "bracketSelf", "bracketRemocon", "multiExp", "multiExpContinuous", "multiExpSelf", "multiExpRemocon", "interval", "intervalSelf", "intervalRemocon", "intervalComp", "intervalCompSelf", "intervalCompRemocon", "intervalMovie", "intervalMovieSelf", "intervalMovieRemocon", "starstream", "starstreamSelf", "starstreamRemocon", "mirrorUp", "mirrorUpRemcon"], "effectList": [ "cim_natural", "cim_bright", "cim_portrait", "cim_landscape", "cim_vibrant", "cim_radiant", "cim_muted", "cim_bleachBypass", "cim_reversal", "cim_monochrome", "cim_crossProcess", "cim_auto", "cim_flat"], "filterList": ["off", "dfl_extractColor", "dfl_replaceColor", "dfl_toyCamera", "dfl_retro", "dfl_highContrast", "dfl_shading", "dfl_negaPosi", "dfl_solidMonoColor", "dfl_hardMonochrome", "hdr_auto", "hdr_mode1", "hdr_mode2", "hdr_mode3", "hdr_modeA"], "exposureModeList": [ "P", "SV", "TV", "AV", "TAV", "M", "B", "X", "U1", "U2", "U3", "U4", "U5", "auto", "gps", "movie"], "avList": [ "4.0", "4.5", "5.0", "5.6", "6.3", "7.1", "8.0", "9.0", "10", "11", "13", "14", "16", "18", "20", "22", "25", "29"], "tvList": [ "30.1", "25.1", "20.1", "15.1", "13.1", "10.1", "8.1", "6.1", "5.1", "4.1", "3.1", "25.10", "2.1", "16.10", "13.10", "1.1", "8.10", "6.10", "5.10", "4.10", "3.10", "1.4", "1.5", "1.6", "1.8", "1.10", "1.13", "1.15", "1.20", "1.25", "1.30", "1.40", "1.50", "1.60", "1.80", "1.100", "1.125", "1.160", "1.200", "1.250", "1.320", "1.400", "1.500", "1.640", "1.800", "1.1000", "1.1250", "1.1600", "1.2000", "1.2500", "1.3200", "1.4000", "1.5000", "1.6400", "1.8000"], "svList": ["auto", "100", "200", "400", "800", "1600", "3200", "6400", "12800", "25600", "51200", "102400", "204800"], "xvList": [ "+5.0", "+4.7", "+4.3", "+4.0", "+3.7", "+3.3", "+3.0", "+2.7", "+2.3", "+2.0", "+1.7", "+1.3", "+1.0", "+0.7", "+0.3", "0.0", "-0.3", "-0.7", "-1.0", "-1.3", "-1.7", "-2.0", "-2.3", "-2.7", "-3.0", "-3.3", "-3.7", "-4.0", "-4.3", "-4.7", "-5.0"], "exposureModeOption": "", "state": "idle", "av": "4.0", "tv": "4.1", "sv": "200", "xv": "0.0", "WBMode": "auto", "shootMode": "single", "exposureMode": "U1", "stillSize": "L3", "movieSize": "FHD30p", "effect": "cim_natural", "filter": "off", "timerExposure": false, "focused": false, "focusCenters": [], "focusEffectiveArea": [70,64], "focusMode": "af", "model": "PENTAX K-1", "firmwareVersion": "01.40", "macAddress": "AC:3F:A4:(hidden)", "serialNo": "(hidden)", "channelList": [0,1,2,3,4,5,6,7,8,9,10,11], "hot": false, "battery": 100, "stillFormatList": [ "jpeg", "pef", "rawpef"], "storages" : [ {"name":"sd1", "equipped": true, "writable": true, "available": true, "active": true, "format": "rawpef", "reservePriority": "high", "remain": 1177 } , {"name":"sd2", "equipped": false, "writable": false, "available": false, "active": false, "format": "rawpef", "reservePriority": "low", "remain": 0 } ], "ssid": "PENTAX_(hidden)", "key": "(hidden)", "channel": "6", "slotMode": "reserve", "liveState": "idle"}
GET /v1/changes HTTP/1.1
This command is used to upgrade the connection to websocket.
GET /v1/photos?storage=sd1 HTTP/1.1
List the photos on SD1.
{"errCode": 200, "errMsg": "OK", "dirs": [ { "name": "100_1112", "files": [ "IMGP1774.JPG", "IMGP1774.PEF"] }, { "name": "101_1113", "files": [ "IMGP1775.JPG", "IMGP1775.PEF"] }, { "name": "102_1117", "files": [ "IMGP1856.JPG"] } ] }
GET /v1/photos/102_1117/IMGP1856.JPG?size=thumb&storage=sd1 HTTP/1.1
Reads a specific thumbnail image from the SD card. The camera replies with a HTTP 200 OK and sends a jpeg encoded image.
GET /v1/photos/102_1117/IMGP1857.JPG?size=view&storage=sd1 HTTP/1.1
Reads a larger resolution image from the SD card.
GET /v1/photos/102_1117/IMGP1857.JPG/info?storage=sd1 HTTP/1.1
Get meta information on an image.
{"errCode": 200, "errMsg": "OK", "captured": true, "dir": "102_1117", "file": "IMGP1857.JPG", "av": "4.0", "sv": "200", "xv": "0", "tv": "1.8", "orientation": 1, "cameraModel": "PENTAX K-1", "latlng": "47.000000,8.000000", "datetime": "2016-11-17T23:00:00"}
GET /v1/liveview HTTP/1.1
Seems to initiate live viewing. It didn't really work in the app.
POST /v1/lens/focus HTTP/1.1
Parameters are pos=52,52
. I think they refer to where the focus point is set. The camera sends the following reply:
{"errCode": 200, "errMsg": "OK", "focused": true, "focusCenters": [], "focusEffectiveArea": [70,64]}
POST /v1/camera/shoot HTTP/1.1
Parameters are af=off
. It shoots a picture.
{"errCode": 200, "errMsg": "OK", "focused": true, "focusCenters": [], "focusEffectiveArea": [70,64], "captured": false}
PUT /v1/params/camera HTTP/1.1
Sets various camera paramters. Here are some examples:
sv=auto xv=0.0
Without form encoded data, it replies:
{"errCode": 200, "errMsg": "OK", "avList": [ "4.0", "4.5", "5.0", "5.6", "6.3", "7.1", "8.0", "9.0", "10", "11", "13", "14", "16", "18", "20", "22", "25", "29"], "tvList": [ "30.1", "25.1", "20.1", "15.1", "13.1", "10.1", "8.1", "6.1", "5.1", "4.1", "3.1", "25.10", "2.1", "16.10", "13.10", "1.1", "8.10", "6.10", "5.10", "4.10", "3.10", "1.4", "1.5", "1.6", "1.8", "1.10", "1.13", "1.15", "1.20", "1.25", "1.30", "1.40", "1.50", "1.60", "1.80", "1.100", "1.125", "1.160", "1.200", "1.250", "1.320", "1.400", "1.500", "1.640", "1.800", "1.1000", "1.1250", "1.1600", "1.2000", "1.2500", "1.3200", "1.4000", "1.5000", "1.6400", "1.8000"], "svList": ["auto", "100", "200", "400", "800", "1600", "3200", "6400", "12800", "25600", "51200", "102400", "204800"], "xvList": [ "+5.0", "+4.7", "+4.3", "+4.0", "+3.7", "+3.3", "+3.0", "+2.7", "+2.3", "+2.0", "+1.7", "+1.3", "+1.0", "+0.7", "+0.3", "0.0", "-0.3", "-0.7", "-1.0", "-1.3", "-1.7", "-2.0", "-2.3", "-2.7", "-3.0", "-3.3", "-3.7", "-4.0", "-4.3", "-4.7", "-5.0"], "exposureModeOption": "", "state": "idle", "av": "4.0", "tv": "1.40", "sv": "800", "xv": "0.0", "WBMode": "auto", "shootMode": "single", "exposureMode": "U1", "stillSize": "L3", "movieSize": "FHD30p", "effect": "cim_natural", "filter": "off", "timerExposure": false}
GET /v1/photos/latest/info?storage=sd1 HTTP/1.1
Gets information about the latest picture. Camera replies
{"errCode": 200, "errMsg": "OK", "captured": true, "dir": "102_1117", "file": "IMGP1857.JPG", "av": "4.0", "sv": "200", "xv": "0", "tv": "1.8", "orientation": 1, "cameraModel": "PENTAX K-1", "latlng": "47.000000,8.000000", "datetime": "2016-11-17T23:20:17"}