Created
February 7, 2014 10:07
-
-
Save japboy/8860091 to your computer and use it in GitHub Desktop.
WIP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
# 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