Skip to content

Instantly share code, notes, and snippets.

@japboy
Created February 7, 2014 10:07
Show Gist options
  • Save japboy/8860091 to your computer and use it in GitHub Desktop.
Save japboy/8860091 to your computer and use it in GitHub Desktop.
WIP
###
# Ricoh Theta 360 event-driven controll implementation on Node.js
# written by @japboy (http://github.com/japboy)
# distributed under the Unlicense (http://unlicense.org/)
#
# Useful information:
# http://mobilehackerz.jp/contents/Review/RICOH_THETA/WiFi_Control
#
# Implementation references:
# https://gist.github.com/kioku-systemk/7710990
# https://gist.github.com/tako2/7734472
#
# PTP/IP whitepaper:
# http://www.cipa.jp/ptp-ip/documents_e/CIPA_DC-005_Whitepaper_ENG.pdf
# http://www.cipa.jp/ptp-ip/documents_j/CIPA_DC-005_Whitepaper_JPN.pdf
#
# PTP/IP specification:
# http://www.cipa.jp/std/documents/e/DC-X005.pdf
###
'use strict'
events = require 'events'
fs = require 'fs'
net = require 'net'
os = require 'os'
path = require 'path'
bp = require 'bufferpack'
# Packet types
PTP_InitCommandReq = 1
PTP_InitCommandAck = 2
PTP_InitEventReq = 3
PTP_InitEventAck = 4
PTP_OpReqPacket = 6
PTP_OpResPacket = 7
PTP_EventPacket = 8
PTP_DataPacket = 10
PTP_EndDataPacket = 12
# Operations
PTP_OC_GetDeviceInfo = parseInt 0x1001, 10
PTP_OC_OpenSession = parseInt 0x1002, 10
PTP_OC_CloseSession = parseInt 0x1003, 10
PTP_OC_GetStorageIDs = parseInt 0x1004, 10
PTP_OC_GetStorageInfo = parseInt 0x1005, 10
PTP_OC_GetNumObjects = parseInt 0x1006, 10
PTP_OC_GetObjectHandles = parseInt 0x1007, 10
PTP_OC_GetObjectInfo = parseInt 0x1008, 10
PTP_OC_GetObject = parseInt 0x1009, 10
PTP_OC_GetThumb = parseInt 0x100A, 10
PTP_OC_DeleteObject = parseInt 0x100B, 10
PTP_OC_InitiateCapture = parseInt 0x100E, 10
PTP_OC_GetDevicePropDesc = parseInt 0x1014, 10
PTP_OC_GetDevicePropValue = parseInt 0x1015, 10
PTP_OC_SetDevicePropValue = parseInt 0x1016, 10
# Events
PTP_EC_ObjectAdded = parseInt 0x4002, 10
PTP_EC_DevicePropChanged = parseInt 0x4006, 10
PTP_EC_StoreFull = parseInt 0x400a, 10
PTP_EC_CaptureComplete = parseInt 0x400d, 10
# Device properties
PTP_DPC_BatteryLevel = parseInt 0x5001, 10
PTP_DPC_ExposureBiasCompensation = parseInt 0x5010, 10
PTP_DPC_DateTime = parseInt 0x5011, 10
PTP_DPC_CaptureDelay = parseInt 0x5012, 10
# PTP/IP class
class PTP_IP extends events.EventEmitter
constructor: (host, name, guid) ->
@host = host
@port = 15740
@name = name
@guid = guid
@command_sock = null
@event_sock = null
@transaction_id = 0
@handles = null
@object = ''
error: (err) =>
console.log '[ERR]', err
init_command_ack: (payload) =>
session_id = bp.unpack '<L', payload, 0
@emit 'InitCommandAck', session_id
init_event_ack: (payload) =>
@emit 'InitEventAck'
operation_response_packet: (payload) =>
code = bp.unpack '<H', payload, 0
transaction_id = bp.unpack '<L', payload, 2
params = bp.unpack '<LLLLL', payload, 6
switch code
when PTP_OC_GetDeviceInfo then console.log 'uh'
when PTP_OC_OpenSession then console.log 'uh'
when PTP_OC_CloseSession then console.log 'uh'
when PTP_OC_GetStorageIDs then console.log 'uh'
when PTP_OC_GetStorageInfo then thenconsole.log 'uh'
when PTP_OC_GetNumObjects then console.log 'uh'
when PTP_OC_GetObjectHandles then console.log 'uh'
when PTP_OC_GetObjectInfo then console.log 'uh'
when PTP_OC_GetObject then console.log 'uh'
when PTP_OC_GetThumb then console.log 'uh'
when PTP_OC_DeleteObject then thenconsole.log 'uh'
when PTP_OC_InitiateCapture then console.log 'uh'
when PTP_OC_GetDevicePropDesc then console.log 'uh'
when PTP_OC_GetDevicePropValue then console.log 'uh'
when PTP_OC_SetDevicePropValue then console.log 'uh'
capture_complete: (payload, params) =>
console.log 'CaptureComplete', payload, params
device_prop_changed: (payload, params) =>
console.log 'DevicePropChanged', payload, params
object_added: (payload, params) =>
handle = params[0]
store_full: (payload, params) =>
console.log 'StoreFull', payload, params
event_packet: (payload) =>
code = bp.unpack('<H', payload, 0)[0]
transaction_id = bp.unpack('<I', payload, 2)[0]
params = bp.unpack '<LLL', payload, 6
switch code
when PTP_EC_ObjectAdded then @object_added payload, params
when PTP_EC_DevicePropChanged then @device_prop_changed payload, params
when PTP_EC_StoreFull then @store_full payload, params
when PTP_EC_CaptureComplete then @capture_complete payload, params
data_packet: (payload) =>
header = bp.unpack '<L(transaction_id)H(length)', payload, 0
@object += payload.slice 4
data: (buf) =>
header = bp.unpack '<L(length)L(type)', buf
payload = buf.slice(8)
switch header.type
when PTP_InitCommandAck then @init_command_ack payload
when PTP_InitEventAck then @init_event_ack payload
when PTP_OpResPacket then @operation_response_packet payload
when PTP_EventPacket then @event_packet payload
when PTP_DataPacket then @data_packet payload
when PTP_EndDataPacket then @data_packet payload
console.log '[RECV]', header.type, payload
send_packet: (sock, command_id, payload) =>
header = bp.pack '<LL', [4+4+payload.length, command_id]
packet = Buffer.concat [header, payload]
sock.write packet
console.log '[SEND]', packet
send_operation_request: (operation_code, params...) =>
payload = bp.pack '<LHLLLLLL', [1, operation_code, @transaction_id].concat(params)
@send_packet @command_sock, PTP_OpReqPacket, payload
@transaction_id += 1
init_command_request: =>
payload = bp.pack '<16BL', [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,1]
@send_packet @command_sock, PTP_InitCommandReq, payload
init_event_request: (session_id) =>
payload = bp.pack '<L', [session_id]
@send_packet @event_sock, PTP_InitEventReq, payload
open_connection: =>
@once 'InitCommandAck', (session_id) =>
# Event socket
@event_sock = new net.Socket()
@event_sock.on 'error', @error
@event_sock.on 'data', @data
@event_sock.connect @port, @host, =>
@init_event_request session_id
@once 'InitEventAck', =>
console.log '[MSG] Both of command and event sessions are initialized.'
# Command socket
@command_sock = new net.Socket()
@command_sock.on 'error', @error
@command_sock.on 'data', @data
@command_sock.connect @port, @host, @init_command_request
close_connection: =>
return unless @command_sock
@command_sock.end()
@command_sock = null
get_device_info: =>
open_session: =>
@send_operation_request PTP_OC_OpenSession
get_storage_ids: (callback) =>
@on 'OperationResponsePacket', callback
@send_operation_request PTP_OC_GetStorageIDs
get_storage_info: =>
get_num_objects: =>
get_object_handles: (storage_id, callback) =>
@on 'OperationResponsePacket', callback
obj_format = 0
parent_obj = 0
@send_operation_request PTP_OC_GetObjectHandles, storage_id, obj_format, parent_obj
get_object_info: (handle, callback) =>
@on 'OperationResponsePacket', callback
@send_operation_request PTP_OC_GetObjectInfo, handle
get_object: (handle, callback) =>
@on 'OperationResponsePacket', => callback
@send_operation_request PTP_OC_GetObject, handle
get_thumb: =>
set_device_prop_value: =>
initiate_capture: =>
print_packet: (packet) =>
print_args: (args) =>
# Theta 360 class
class Theta360 extends PTP_IP
constructor: ->
host = '192.168.1.1'
name = 'THETA'
guid = 'mendokusai'
super host, name, guid
save: =>
ids = @get_storage_ids()
handles = @get_object_handles ids[0] if ids
obj_idx = handles.length - 1
obj_info = @get_object_info @handles[obj_idx]
img = @get_object obj_idx
open: =>
@on 'ObjectAdded', @save
@open_connection()
close: =>
@close_connection()
set_ev_shift: (ev_shift) =>
# EV shifts: 2000,1700,1300,1000,700,300,0,-300,-700,-1000,-1300,-1700,-2000
@ptpip.set_device_prop_value 0x5010, @ptpip.pack_int16(ev_shift)
# Run
do ->
theta = new Theta360()
theta.open()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment