├── .gitignore ├── .travis.yml ├── Gruntfile.coffee ├── History.txt ├── LICENSE.txt ├── README.md ├── bin └── theta.coffee ├── lib └── theta.js ├── package.json ├── samples ├── audio_volume.js ├── battery.js ├── capture.js └── list.js ├── src └── theta.coffee └── tests └── test_theta.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *#* 3 | .DS_Store 4 | *.log 5 | node_modules 6 | tmp 7 | *.jpg 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | script: grunt build 5 | notifications: 6 | slack: 7 | secure: DMlLDkCKTjScM3IEPTSIiPfwGYgeSplp071dsV6jlrwXm/e6bxLBLQuFOOmsASE1/dh/eTDRbl0TTmNmxHPSEv9V/299054Sok44OSF+quIdY3wz4tUTN7GL6FwaQ8PeCPIfXnJCyuXVk9Niu876+eXb/rJwbkShMgz9Ucpr6Sc= 8 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (grunt) -> 4 | 5 | require 'coffee-errors' 6 | 7 | grunt.loadNpmTasks 'grunt-contrib-coffee' 8 | grunt.loadNpmTasks 'grunt-contrib-watch' 9 | grunt.loadNpmTasks 'grunt-coffeelint' 10 | grunt.loadNpmTasks 'grunt-simple-mocha' 11 | grunt.loadNpmTasks 'grunt-notify' 12 | 13 | grunt.registerTask 'build', [ 'coffeelint', 'coffee' ] 14 | grunt.registerTask 'test', [ 'build', 'simplemocha' ] 15 | grunt.registerTask 'default', [ 'build', 'watch' ] 16 | 17 | grunt.initConfig 18 | 19 | coffeelint: 20 | options: 21 | max_line_length: 22 | value: 119 23 | indentation: 24 | value: 2 25 | newlines_after_classes: 26 | level: 'error' 27 | no_empty_param_list: 28 | level: 'error' 29 | no_unnecessary_fat_arrows: 30 | level: 'ignore' 31 | dist: 32 | files: [ 33 | { expand: yes, cwd: 'src/', src: [ '**/*.coffee' ] } 34 | { expand: yes, cwd: 'tests/', src: [ '**/*.coffee' ] } 35 | ] 36 | 37 | coffee: 38 | dist: 39 | files: [{ 40 | expand: yes 41 | cwd: 'src/' 42 | src: [ '**/*.coffee' ] 43 | dest: 'lib/' 44 | ext: '.js' 45 | }] 46 | 47 | simplemocha: 48 | options: 49 | ui: 'bdd' 50 | reporter: 'spec' 51 | compilers: 'coffee:coffee-script' 52 | ignoreLeaks: no 53 | dist: 54 | src: [ 'tests/test_*.coffee' ] 55 | 56 | watch: 57 | options: 58 | interrupt: yes 59 | dist: 60 | files: [ '**/*.coffee' ] 61 | tasks: [ 'build' ] 62 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 0.3.5 2015-05-01 2 | 3 | * move coffee-script into devDependencies #10 4 | 5 | === 0.3.4 2015-04-08 6 | 7 | * bugfix "theta --volume (number)" validation #9 8 | 9 | === 0.3.3 2015-02-13 10 | 11 | * add getter "theta.isOpen" 12 | * add tests #8 13 | * run test locally 14 | * coffeelint on TravisCI 15 | 16 | === 0.3.2 2015-01-30 17 | 18 | * add method getPictureInfo(object_handle) #5 19 | * add command option "theta --info [object_handle]" 20 | * bugfix --battery option 21 | 22 | === 0.3.1 2015-01-30 23 | 24 | * update command help 25 | 26 | === 0.3.0 2015-01-30 27 | 28 | * merge --set/get_volume with --volume #6 29 | * update for ptp 2.1.0 #5 30 | 31 | 32 | === 0.2.0 2015-01-27 33 | 34 | * add method - theta.getProperty(code, data, callback) 35 | * add command 36 | * theta --get_volume 37 | * theta --set_volume [number] 38 | 39 | === 0.1.3 2015-01-20 40 | 41 | * bugfix: show error if --save option called, without --id option 42 | 43 | === 0.1.2 2015-01-20 44 | 45 | * add command "theta --delete [object_id]" #3 46 | * add method - theta.getPicture(object_id) #3 47 | 48 | === 0.1.1 2015-01-19 49 | 50 | * fix theta command help message 51 | 52 | === 0.1.0 2015-01-19 53 | 54 | * add "theta" command 55 | * add methods 56 | * theta.getPicture(object_id) #1 57 | * theta.listPictures(callback) #1 58 | * emit "objectAdded" event 59 | * install ptp from github.com/shokai/ptp.js #1 60 | 61 | === 0.0.1 2014-12-22 62 | 63 | * first release 64 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Sho Hashimoto 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ricoh-theta npm 2 | Node.js client for RICOH THETA - 360-degree camera 3 | 4 | [![Build Status](https://travis-ci.org/shokai/node-ricoh-theta.svg?branch=master)](https://travis-ci.org/shokai/node-ricoh-theta) 5 | 6 | - https://github.com/shokai/node-ricoh-theta 7 | - https://www.npmjs.com/package/ricoh-theta 8 | 9 | 10 | ## Install 11 | 12 | % npm install ricoh-theta 13 | 14 | 15 | ## theta command 16 | 17 | # global install 18 | % npm install ricoh-theta coffee-script -g 19 | 20 | % theta --help 21 | % theta --capture 22 | % theta --capture out.jpg 23 | % theta --list 24 | % theta --id [object_handle] --save out.jpg 25 | % theta --volume 0 26 | 27 | 28 | ## Usage 29 | 30 | ```javascript 31 | var fs = require('fs'); 32 | var Theta = require('ricoh-theta'); 33 | 34 | var theta = new Theta(); 35 | theta.connect('192.168.1.1'); 36 | 37 | // capture 38 | theta.on('connect', function(){ 39 | theta.capture(function(err){ 40 | if(err) return console.error(err); 41 | console.log('capture success'); 42 | }); 43 | }); 44 | 45 | // get picture 46 | theta.on('objectAdded', function(object_handle){ 47 | theta.getPicture(object_handle, function(err, picture){ 48 | fs.writeFile('tmp.jpg', picture, function(err){ 49 | console.log('picture saved => tmp.jpg'); 50 | theta.disconnect(); 51 | }); 52 | }); 53 | }); 54 | ``` 55 | 56 | ## Develop 57 | 58 | % npm run watch 59 | # or 60 | % npm run build 61 | 62 | ## Test 63 | 64 | % npm test 65 | 66 | 67 | ## Contributing 68 | 69 | 1. Fork it 70 | 2. Create your feature branch (`git checkout -b my-new-feature`) 71 | 3. Commit your changes (`git commit -am 'Add some feature'`) 72 | 4. Push to the branch (`git push origin my-new-feature`) 73 | 5. Create new Pull Request 74 | -------------------------------------------------------------------------------- /bin/theta.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | path = require 'path' 5 | 6 | optparse = require 'optparse' 7 | 8 | Theta = require path.resolve("#{__dirname}/../") 9 | theta = new Theta 10 | 11 | config = {} 12 | 13 | parser = new optparse.OptionParser [ 14 | ['-h', '--help', 'show help'] 15 | ['--capture [FILENAME]', 'take a picture'] 16 | ['--list', 'list pictures'] 17 | ['--handle [Object Handle]', 'specify picture by Object Handle'] 18 | ['--save [FILENAME]', 'save picture'] 19 | ['--delete [Object Handle]', 'delete a picture'] 20 | ['--info [Object Handle]', 'show picture info'] 21 | ['--battery', 'check battery level'] 22 | ['--volume [NUMBER]', 'get/set audio volume (0~100)'] 23 | ] 24 | 25 | parser.filter 'NUMBER', (v) -> 26 | return v unless v # allow empty argument 27 | if /^[1-9]?[0-9]*(\.\d+)?$/.test v 28 | return v - 0 29 | throw "invalid number - \"#{v}\"" 30 | 31 | parser.on 'help', -> 32 | package_json = require "#{__dirname}/../package.json" 33 | parser.banner = """ 34 | theta v#{package_json.version} - #{package_json.homepage} 35 | 36 | Usage: 37 | % theta --capture 38 | % theta --capture out.jpg 39 | % theta --list 40 | % theta --handle [object_handle] --save out.jpg 41 | % theta --delete [object_handle] 42 | % theta --info [object_handle] 43 | % theta --battery 44 | % theta --volume # get audio volume 45 | % theta --volume 20 # set audio volume 46 | % DEBUG=* theta --capture # print all debug messages 47 | """ 48 | console.log parser.toString() 49 | return process.exit 0 50 | 51 | savePicture = (object_handle, filename) -> 52 | theta.getPicture object_handle, (err, picture) -> 53 | if err 54 | console.error err 55 | return process.exit 1 56 | fs.writeFile filename, picture, (err) -> 57 | console.log "picture (Handle:#{object_handle}) saved => #{filename}" 58 | theta.disconnect() 59 | 60 | parser.on 'capture', (opt, filename) -> 61 | theta.connect() 62 | theta.once 'connect', -> 63 | theta.capture (err) -> 64 | if err 65 | console.error err 66 | return process.exit 1 67 | console.log 'capture success' 68 | unless filename 69 | return theta.disconnect() 70 | 71 | theta.once 'objectAdded', (object_handle) -> 72 | savePicture object_handle, filename 73 | 74 | parser.on 'list', -> 75 | theta.connect() 76 | theta.once 'connect', -> 77 | theta.listPictures (err, object_handles) -> 78 | console.log "Object Handles: #{JSON.stringify(object_handles)}" 79 | console.log "#{object_handles.length} pictures" 80 | theta.disconnect() 81 | 82 | parser.on 'handle', (opt, object_handle) -> 83 | config.object_handle = object_handle 84 | 85 | parser.on 'save', (opt, filename) -> 86 | unless typeof config.object_handle is 'string' 87 | console.error '"--handle=[object_handle]" option required' 88 | return process.exit 1 89 | theta.connect() 90 | theta.once 'connect', -> 91 | savePicture config.object_handle, filename 92 | 93 | parser.on 'delete', (opt, object_handle) -> 94 | theta.connect() 95 | theta.once 'connect', -> 96 | theta.deletePicture object_handle, (err) -> 97 | if err 98 | console.error err 99 | return process.exit 1 100 | console.log "delete #{object_handle} success" 101 | theta.disconnect() 102 | 103 | parser.on 'info', (opt, object_handle) -> 104 | theta.connect() 105 | theta.once 'connect', -> 106 | theta.getPictureInfo object_handle, (err, info) -> 107 | if err 108 | console.error err 109 | return process.exit 1 110 | console.log info 111 | theta.disconnect() 112 | 113 | parser.on 'battery', -> 114 | theta.connect() 115 | theta.once 'connect', -> 116 | theta.getBatteryLevel (err, res) -> 117 | if err 118 | console.error err 119 | return process.exit 1 120 | console.log "BatteryLevel: #{res.dataPacket.array[0]}" 121 | theta.disconnect() 122 | 123 | 124 | parser.on 'volume', (opt, volume) -> 125 | theta.connect() 126 | theta.once 'connect', -> 127 | if typeof volume is 'number' 128 | theta.setProperty 0x502C, volume, (err, res) -> 129 | if err 130 | console.error err 131 | return process.exit 1 132 | theta.getProperty 0x502C, (err, res) -> 133 | if err 134 | console.error err 135 | return process.exit 1 136 | console.log "AudioVolume: #{res.dataPacket.array[0]}" 137 | theta.disconnect() 138 | return 139 | theta.getProperty 0x502C, (err, res) -> 140 | if err 141 | console.error err 142 | return process.exit 1 143 | console.log "AudioVolume: #{res.dataPacket.array[0]}" 144 | theta.disconnect() 145 | return 146 | 147 | if process.argv.length < 3 148 | parser.on_switches.help.call() 149 | 150 | parser.parse process.argv 151 | -------------------------------------------------------------------------------- /lib/theta.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | var Theta, debug, events, ptp, 4 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | debug = require('debug')('ricoh-theta'); 8 | 9 | events = require('events'); 10 | 11 | ptp = require('ptp'); 12 | 13 | module.exports = Theta = (function(superClass) { 14 | extend(Theta, superClass); 15 | 16 | function Theta() { 17 | var code, fn, isOpen, name, ref; 18 | this.client = ptp; 19 | isOpen = false; 20 | this.__defineGetter__('isOpen', function() { 21 | return isOpen; 22 | }); 23 | this.client.onConnected = (function(_this) { 24 | return function() { 25 | debug('ptp connect'); 26 | isOpen = true; 27 | return _this.emit('connect'); 28 | }; 29 | })(this); 30 | this.client.onDisconnected = (function(_this) { 31 | return function() { 32 | debug('ptp disconnect'); 33 | isOpen = false; 34 | return _this.emit('disconnect'); 35 | }; 36 | })(this); 37 | this.client.onError = (function(_this) { 38 | return function(err) { 39 | debug("ptp error - " + err); 40 | return _this.emit('error'); 41 | }; 42 | })(this); 43 | ref = ptp.devicePropCodes; 44 | fn = (function(_this) { 45 | return function(name, code) { 46 | var method_name; 47 | method_name = "get" + (name[0].toUpperCase()) + name.slice(1); 48 | return _this[method_name] = function(callback) { 49 | if (callback == null) { 50 | callback = function() {}; 51 | } 52 | debug("get property \"" + method_name + "\" - code: " + code); 53 | return _this.getProperty(code, callback); 54 | }; 55 | }; 56 | })(this); 57 | for (name in ref) { 58 | code = ref[name]; 59 | fn(name, code); 60 | } 61 | } 62 | 63 | Theta.prototype.connect = function(host) { 64 | this.host = host != null ? host : '192.168.1.1'; 65 | this.client.host = this.host; 66 | this.client.clientName = 'ricoh-theta npm'; 67 | debug('connecting..'); 68 | this.client.connect(); 69 | this.client.onObjectAdded = (function(_this) { 70 | return function(res) { 71 | var object_handle, ref; 72 | object_handle = (ref = res.parameters) != null ? ref[1] : void 0; 73 | debug("objectAdded: " + object_handle); 74 | return _this.emit('objectAdded', object_handle); 75 | }; 76 | })(this); 77 | return this; 78 | }; 79 | 80 | Theta.prototype.disconnect = function() { 81 | return this.client.disconnect(); 82 | }; 83 | 84 | Theta.prototype.capture = function(callback) { 85 | if (callback == null) { 86 | callback = function() {}; 87 | } 88 | debug("request capture"); 89 | return this.client.capture({ 90 | onSuccess: function() { 91 | return callback(null); 92 | }, 93 | onFailure: function(err) { 94 | return callback(err || 'capture failed'); 95 | } 96 | }); 97 | }; 98 | 99 | Theta.prototype.getProperty = function(code, callback) { 100 | if (callback == null) { 101 | callback = function() {}; 102 | } 103 | debug("request getProperty(" + code + ")"); 104 | return this.client.getDeviceProperty({ 105 | code: code, 106 | onSuccess: function(res) { 107 | return callback(null, res); 108 | }, 109 | onFailure: function(err) { 110 | var name; 111 | name = ptp.devicePropCodes[code] || ("code:" + code); 112 | return callback(err || ("getting property \"" + name + "\" was failed")); 113 | } 114 | }); 115 | }; 116 | 117 | Theta.prototype.setProperty = function(code, data, callback) { 118 | if (callback == null) { 119 | callback = function() {}; 120 | } 121 | debug("request setProperty(" + code + ")"); 122 | return this.client.setDeviceProperty({ 123 | code: code, 124 | data: ptp.dataFactory.createDword(data), 125 | onSuccess: function(res) { 126 | return callback(null, res); 127 | }, 128 | onFailure: function(err) { 129 | var name; 130 | name = ptp.devicePropCodes[code] || ("code:" + code); 131 | return callback(err || ("setting property \"" + name + "\" was failed")); 132 | } 133 | }); 134 | }; 135 | 136 | Theta.prototype.getPicture = function(object_handle, callback) { 137 | if (callback == null) { 138 | callback = function() {}; 139 | } 140 | debug("request getPicture(" + object_handle + ")"); 141 | return this.client.getObject({ 142 | objectHandle: object_handle, 143 | onSuccess: function(res) { 144 | debug("getPicture(" + object_handle + ") done"); 145 | return callback(null, new Buffer(res.dataPacket.array)); 146 | }, 147 | onFailure: function(err) { 148 | debug("getPicture(" + object_handle + ") failed"); 149 | return callback(err || ("getPicture(" + object_handle + ") failed")); 150 | } 151 | }); 152 | }; 153 | 154 | Theta.prototype.getPictureInfo = function(object_handle, callback) { 155 | if (callback == null) { 156 | callback = function() {}; 157 | } 158 | debug("request getPictureInfo(" + object_handle + ")"); 159 | return this.client.getObjectInfo({ 160 | objectHandle: object_handle, 161 | onSuccess: function(res) { 162 | debug("getPictureInfo(" + object_handle + ") done"); 163 | return callback(null, res.objectInfo); 164 | }, 165 | onFailure: function(err) { 166 | debug("getPictureInfo(" + object_handle + ") failed"); 167 | return callback(err || ("getPictureInfo(" + object_handle + ") failed")); 168 | } 169 | }); 170 | }; 171 | 172 | Theta.prototype.listPictures = function(callback) { 173 | if (callback == null) { 174 | callback = function() {}; 175 | } 176 | debug("request pictures list"); 177 | return this.client.getObjectHandles({ 178 | storageId: 0xFFFFFFFF, 179 | objectFormatCode: 0x00000000, 180 | objectHandleOfAssociation: 0, 181 | onSuccess: function(res) { 182 | res.handles.shift(); 183 | debug("list " + res.handles.length + " pictures"); 184 | return callback(null, res.handles); 185 | }, 186 | onFailure: function(err) { 187 | debug("list pictures failed"); 188 | return callback(err || "list pictures failed"); 189 | } 190 | }); 191 | }; 192 | 193 | Theta.prototype.deletePicture = function(object_handle, callback) { 194 | if (callback == null) { 195 | callback = function() {}; 196 | } 197 | debug("request deletePicture(" + object_handle + ")"); 198 | return this.client.deleteObject({ 199 | objectHandle: object_handle, 200 | onSuccess: function(res) { 201 | debug("deletePicture(" + object_handle + ") done"); 202 | return callback(null); 203 | }, 204 | onFailure: function(err) { 205 | debug("deletePicture(" + object_handle + ") failed"); 206 | return callback(err || ("deletePicture(" + object_handle + ") failed")); 207 | } 208 | }); 209 | }; 210 | 211 | return Theta; 212 | 213 | })(events.EventEmitter); 214 | 215 | }).call(this); 216 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ricoh-theta", 3 | "version": "0.3.5", 4 | "description": "controll RICOH THETA 360-degree camera", 5 | "main": "lib/theta.js", 6 | "scripts": { 7 | "test": "grunt test", 8 | "build": "grunt build", 9 | "watch": "grunt" 10 | }, 11 | "bin": { 12 | "theta": "./bin/theta.coffee" 13 | }, 14 | "dependencies": { 15 | "debug": "*", 16 | "optparse": "*", 17 | "ptp": "^2.1" 18 | }, 19 | "devDependencies": { 20 | "coffee-script": "*", 21 | "coffee-errors": "*", 22 | "mocha": "*", 23 | "grunt": "*", 24 | "grunt-cli": "*", 25 | "grunt-simple-mocha": "*", 26 | "grunt-notify": "*", 27 | "grunt-contrib-coffee": "*", 28 | "grunt-coffeelint": "*", 29 | "grunt-contrib-watch": "*" 30 | }, 31 | "keywords": [ 32 | "ricoh", 33 | "theta", 34 | "camera" 35 | ], 36 | "author": "Sho Hashimoto ", 37 | "license": "MIT", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/shokai/node-ricoh-theta.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/shokai/node-ricoh-theta/issues" 44 | }, 45 | "homepage": "https://github.com/shokai/node-ricoh-theta" 46 | } 47 | -------------------------------------------------------------------------------- /samples/audio_volume.js: -------------------------------------------------------------------------------- 1 | var Theta = require(__dirname+'/../'); 2 | // var Theta = require('ricoh-theta'); 3 | 4 | var theta = new Theta(); 5 | theta.connect('192.168.1.1'); 6 | 7 | theta.on('connect', function(){ 8 | console.log('connect!!'); 9 | 10 | // https://developers.theta360.com/ja/docs/ptpip_reference/property/audio_volume.html 11 | theta.getProperty(0x502C, function(err, res){ 12 | if(err) return console.error(err); 13 | console.log("AudioVolume: "+res.dataPacket.array[0]); 14 | 15 | theta.setProperty(0x502C, 0, function(err, res){ // no sound 16 | if(err) return console.error(err); 17 | theta.disconnect(); 18 | }); 19 | 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /samples/battery.js: -------------------------------------------------------------------------------- 1 | var Theta = require(__dirname+'/../'); 2 | // var Theta = require('ricoh-theta'); 3 | 4 | var theta = new Theta(); 5 | theta.connect('192.168.1.1'); 6 | 7 | theta.on('connect', function(){ 8 | console.log('connect!!'); 9 | theta.getBatteryLevel(function(err, res){ 10 | if(err) return console.error(err); 11 | console.log("BatteryLevel: "+res.dataPacket.array[0]); 12 | theta.disconnect(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /samples/capture.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var Theta = require(__dirname+'/../'); 4 | // var Theta = require('ricoh-theta'); 5 | 6 | var theta = new Theta(); 7 | theta.connect('192.168.1.1'); 8 | 9 | theta.on('connect', function(){ 10 | console.log('connect!!'); 11 | theta.capture(function(err){ 12 | if(err) return console.error(err); 13 | console.log('capture success'); 14 | }); 15 | }); 16 | 17 | theta.on('objectAdded', function(object_handle){ 18 | console.log('getting picture..'); 19 | theta.getPicture(object_handle, function(err, picture){ 20 | fs.writeFile('tmp.jpg', picture, function(err){ 21 | console.log('picture saved => tmp.jpg'); 22 | theta.disconnect(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /samples/list.js: -------------------------------------------------------------------------------- 1 | var Theta = require(__dirname+'/../'); 2 | // var Theta = require('ricoh-theta'); 3 | 4 | var theta = new Theta(); 5 | theta.connect('192.168.1.1'); 6 | 7 | theta.on('connect', function(){ 8 | console.log('connect!!'); 9 | 10 | theta.listPictures(function(err, object_handles){ 11 | if(err) return console.error(err); 12 | console.log("Object Handles: " + JSON.stringify(object_handles)); 13 | console.log(object_handles.length + " pictures"); 14 | theta.disconnect(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/theta.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | debug = require('debug')('ricoh-theta') 4 | events = require 'events' 5 | ptp = require 'ptp' 6 | 7 | module.exports = class Theta extends events.EventEmitter 8 | 9 | constructor: -> 10 | @client = ptp 11 | isOpen = false 12 | @__defineGetter__ 'isOpen', -> isOpen 13 | 14 | @client.onConnected = => 15 | debug 'ptp connect' 16 | isOpen = true 17 | @emit 'connect' 18 | 19 | @client.onDisconnected = => 20 | debug 'ptp disconnect' 21 | isOpen = false 22 | @emit 'disconnect' 23 | 24 | @client.onError = (err) => 25 | debug "ptp error - #{err}" 26 | @emit 'error' 27 | 28 | ## register device property methods 29 | for name, code of ptp.devicePropCodes 30 | do (name, code) => 31 | method_name = "get#{name[0].toUpperCase()}#{name[1..-1]}" 32 | @[method_name] = (callback = ->) => 33 | debug "get property \"#{method_name}\" - code: #{code}" 34 | @getProperty code, callback 35 | 36 | connect: (@host='192.168.1.1') -> 37 | @client.host = @host 38 | @client.clientName = 'ricoh-theta npm' 39 | debug 'connecting..' 40 | @client.connect() 41 | @client.onObjectAdded = (res) => 42 | object_handle = res.parameters?[1] 43 | debug "objectAdded: #{object_handle}" 44 | @emit 'objectAdded', object_handle 45 | return @ 46 | 47 | disconnect: -> 48 | @client.disconnect() 49 | 50 | capture: (callback = ->) -> 51 | debug "request capture" 52 | @client.capture 53 | onSuccess: -> 54 | callback null 55 | onFailure: (err) -> 56 | callback err or 'capture failed' 57 | 58 | getProperty: (code, callback = ->) -> 59 | debug "request getProperty(#{code})" 60 | @client.getDeviceProperty 61 | code: code 62 | onSuccess: (res) -> 63 | callback null, res 64 | onFailure: (err) -> 65 | name = ptp.devicePropCodes[code] or "code:#{code}" 66 | callback err or "getting property \"#{name}\" was failed" 67 | 68 | setProperty: (code, data, callback = ->) -> 69 | debug "request setProperty(#{code})" 70 | @client.setDeviceProperty 71 | code: code 72 | data: ptp.dataFactory.createDword data 73 | onSuccess: (res) -> 74 | callback null, res 75 | onFailure: (err) -> 76 | name = ptp.devicePropCodes[code] or "code:#{code}" 77 | callback err or "setting property \"#{name}\" was failed" 78 | 79 | getPicture: (object_handle, callback = ->) -> 80 | debug "request getPicture(#{object_handle})" 81 | @client.getObject 82 | objectHandle: object_handle 83 | onSuccess: (res) -> 84 | debug "getPicture(#{object_handle}) done" 85 | callback null, new Buffer(res.dataPacket.array) 86 | onFailure: (err) -> 87 | debug "getPicture(#{object_handle}) failed" 88 | callback err or "getPicture(#{object_handle}) failed" 89 | 90 | getPictureInfo: (object_handle, callback = ->) -> 91 | debug "request getPictureInfo(#{object_handle})" 92 | @client.getObjectInfo 93 | objectHandle: object_handle 94 | onSuccess: (res) -> 95 | debug "getPictureInfo(#{object_handle}) done" 96 | callback null, res.objectInfo 97 | onFailure: (err) -> 98 | debug "getPictureInfo(#{object_handle}) failed" 99 | callback err or "getPictureInfo(#{object_handle}) failed" 100 | 101 | listPictures: (callback = ->) -> 102 | debug "request pictures list" 103 | @client.getObjectHandles 104 | storageId: 0xFFFFFFFF 105 | objectFormatCode: 0x00000000 106 | objectHandleOfAssociation: 0 107 | onSuccess: (res) -> 108 | res.handles.shift() 109 | debug "list #{res.handles.length} pictures" 110 | callback null, res.handles 111 | onFailure: (err) -> 112 | debug "list pictures failed" 113 | callback err or "list pictures failed" 114 | 115 | deletePicture: (object_handle, callback = ->) -> 116 | debug "request deletePicture(#{object_handle})" 117 | @client.deleteObject 118 | objectHandle: object_handle 119 | onSuccess: (res) -> 120 | debug "deletePicture(#{object_handle}) done" 121 | callback null 122 | onFailure: (err) -> 123 | debug "deletePicture(#{object_handle}) failed" 124 | callback err or "deletePicture(#{object_handle}) failed" 125 | -------------------------------------------------------------------------------- /tests/test_theta.coffee: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test' 2 | 3 | path = require 'path' 4 | assert = require 'assert' 5 | 6 | Theta = require path.resolve() 7 | 8 | describe 'instance of Theta', -> 9 | 10 | theta = new Theta() 11 | 12 | it 'should have method "connect"', -> 13 | assert.equal typeof theta['connect'], 'function' 14 | 15 | it 'should have method "disconnect"', -> 16 | assert.equal typeof theta['disconnect'], 'function' 17 | 18 | it 'should have getter "isOpen"', -> 19 | assert.equal typeof theta.__lookupGetter__('isOpen'), 'function' 20 | 21 | it 'should emit "connect" event', (done) -> 22 | theta.connect() 23 | theta.on 'connect', -> 24 | assert.equal theta.isOpen, true 25 | done() 26 | assert.equal theta.isOpen, false 27 | 28 | it 'should have method "capture"', -> 29 | assert.equal typeof theta['capture'], 'function' 30 | 31 | it 'should have method "getProperty"', -> 32 | assert.equal typeof theta['getProperty'], 'function' 33 | 34 | describe 'method "getProperty"', -> 35 | 36 | it 'should return property', (done) -> 37 | theta.getProperty 0x502C, (err, res) -> 38 | console.error(err) if err 39 | assert.equal typeof res.dataPacket?.array[0], 'number' 40 | done() 41 | 42 | it 'should have method "setProperty"', -> 43 | assert.equal typeof theta['setProperty'], 'function' 44 | 45 | it 'should have method "getPicture"', -> 46 | assert.equal typeof theta['getPicture'], 'function' 47 | 48 | it 'should have method "getPictureInfo"', -> 49 | assert.equal typeof theta['getPictureInfo'], 'function' 50 | 51 | it 'should have method "listPictures"', -> 52 | assert.equal typeof theta['listPictures'], 'function' 53 | 54 | describe 'method "listPictures"', -> 55 | 56 | it 'should return list of pictures', (done) -> 57 | theta.listPictures (err, object_handles) -> 58 | console.error err if err 59 | assert.equal object_handles instanceof Array, true 60 | done() 61 | 62 | it 'should have method "deletePicture"', -> 63 | assert.equal typeof theta['deletePicture'], 'function' 64 | 65 | describe 'method "disconnect"', -> 66 | 67 | it 'should close ptp-ip connection', (done) -> 68 | theta.disconnect() 69 | theta.once 'disconnect', -> 70 | assert.equal theta.isOpen, false 71 | done() 72 | --------------------------------------------------------------------------------