Created
November 29, 2015 06:30
-
-
Save kota65535/1c92a56242f848ccc813 to your computer and use it in GitHub Desktop.
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
require 'frisky/ssdp' | |
require 'httpclient' | |
require 'nokogiri' | |
require 'json' | |
require 'bindata' | |
require 'benchmark' | |
require 'logger' | |
require 'byebug' | |
module Logging | |
# This is the magical bit that gets mixed into your classes | |
def log | |
Logging.logger | |
end | |
# Global, memoized, lazy initialized instance of a logger | |
def self.logger | |
@logger ||= Logger.new(STDOUT) | |
end | |
end | |
class LiveviewImagePayload < BinData::Record | |
endian :big | |
uint32 :start_code, :assert => 0x24356879 | |
uint24 :payload_data_size_wo_padding | |
uint8 :padding_size | |
uint32 | |
uint8 :flag | |
uint920 | |
string :jpeg_data, :length => :payload_data_size_wo_padding | |
string :padding_data, :length => :padding_size | |
end | |
class LiveviewFrameInformationPayload < BinData::Record | |
endian :big | |
uint32 :start_code, :assert => 0x24356879 | |
uint24 :payload_data_size_wo_padding | |
uint8 :padding_size | |
uint16 :frame_information_data_version | |
uint16 :frame_count | |
uint16 :single_frame_data_size | |
uint912 | |
uint32 :top_left_corner_position | |
uint32 :bottom_right_corner_position | |
uint8 :category | |
uint8 :status | |
uint8 :additional_status | |
uint40 | |
string :padding_data, :length => :padding_size | |
end | |
class LiveviewPacket < BinData::Record | |
endian :big | |
uint8 :start_byte, :assert => 0xFF | |
uint8 :payload_type, :assert => lambda { value == 0x01 or value == 0x02 } | |
uint16 :sequence_number | |
uint32 :time_stamp | |
choice :payload, :selection => :payload_type do | |
liveview_image_payload 0x01 | |
liveview_frame_information_payload 0x02 | |
end | |
end | |
class APIInfo | |
attr_accessor :name | |
attr_accessor :versions | |
attr_accessor :service_types | |
def initialize(name, versions, service_types) | |
@name = name | |
@versions = [versions] | |
@service_types = [service_types] | |
end | |
def has_multi_versions | |
@versions.length > 1 | |
end | |
def has_multi_service_types | |
@service_types.length > 1 | |
end | |
end | |
class Camera | |
include Logging | |
SSDP_SEARCH_TARGET = 'urn:schemas-sony-com:service:ScalarWebAPI:1' | |
SSDP_SEARCH_RETRY = 3 | |
SSDP_RETRY_INTERVAL = 5 | |
class ServerNotCompatibleError < StandardError; end | |
def initialize() | |
# SSDPで所定のデバイスを検索 | |
try = 1 | |
while true do | |
response = Frisky::SSDP.search SSDP_SEARCH_TARGET | |
if ! response.empty? | |
break | |
elsif try < SSDP_SEARCH_RETRY | |
try += 1 | |
log.warn "SSDP discover failed, retrying... (#{try}/#{SSDP_SEARCH_RETRY})" | |
sleep(SSDP_RETRY_INTERVAL) | |
else | |
log.fatal "The device not found." | |
exit 3 | |
end | |
end | |
# pp response | |
log.info 'SSDP discover succeeded.' | |
# デバイスデスクリプション取得 | |
@cli = HTTPClient.new | |
dd = @cli.get_content(response[0][:location]) | |
# puts dd | |
# puts 'Got the device descripton.' | |
# デバイスデスクリプション解析 | |
dd_xml = Nokogiri::XML(dd) | |
if dd_xml.nil? | |
log.fatal 'Failed to parse XML.' | |
exit 1 | |
end | |
dd_xml.remove_namespaces! | |
camera_name = dd_xml.css('device friendlyName') | |
services = dd_xml.css('device X_ScalarWebAPI_Service') | |
@services = [] | |
@endpoints = {} | |
services.each do |sv| | |
service_type = sv.css('X_ScalarWebAPI_ServiceType').inner_text | |
@services << service_type | |
@endpoints[service_type] = File.join(sv.css('X_ScalarWebAPI_ActionList_URL').inner_text, service_type) | |
end | |
@endpoints['liveview'] = dd_xml.css('device X_ScalarWebAPI_LiveView_URL').inner_text | |
log.info "Model-name: #{camera_name}" | |
pp @endpoints | |
log.info 'Got endpoint URLs.' | |
unless call_api('camera', 'getApplicationInfo', [], 1, '1.0')['result'][1] >= '2.0.0' | |
raise ServerNotCompatibleError 'API version of the server < 2.0.0.' | |
end | |
make_api_list | |
pp @versions | |
pp @apis | |
end | |
private | |
def make_api_list() | |
@apis = {} | |
for s in ['camera', 'system', 'avContent'] do | |
versions = call_api(s, 'getVersions', [], 1, '1.0')['result'][0].sort.reverse | |
for v in versions do | |
results = call_api(s, 'getMethodTypes', [v], 1, '1.0')['results'] | |
if results | |
for r in results do | |
name = r[0].to_sym | |
if @apis.has_key?(name) | |
@apis[name].versions << v unless @apis[name].versions.index(v) | |
@apis[name].service_types << s unless @apis[name].service_types.index(s) | |
else | |
@apis[name] = APIInfo.new(r[0], v, s) | |
end | |
end | |
end | |
end | |
end | |
end | |
def call_api(service_type, method, params, id, version) | |
request = { | |
'method' => method, | |
'params' => params, | |
'id' => id, | |
'version' => version | |
} | |
response = @cli.post_content(@endpoints[service_type], request.to_json) | |
JSON.parse(response) | |
end | |
def call_api_async(service_type, method, params, id, version) | |
request = { | |
'method' => method, | |
'params' => params, | |
'id' => id, | |
'version' => version | |
} | |
conn = @cli.post_async(@endpoints[service_type], request.to_json) | |
while true | |
break if conn.finished? | |
sleep 1 | |
end | |
response = conn.pop.content.read | |
JSON.parse(response) | |
end | |
class ServiceTypeNotGiven < StandardError; end | |
def check_args(method, **args) | |
if @apis and @apis.has_key? method | |
id = args.key?(:id) ? args[:id] : 1 | |
if args.key? :version | |
version = args[:version] | |
else | |
if @apis[method].has_multi_versions | |
log.warn "Using newest version '%s' for method '%s'." % [ @apis[method].versions[0], method ] | |
end | |
version = @apis[method].versions[0] | |
end | |
if args.key? :service | |
service = args[:service] | |
else | |
if @apis[method].has_multi_service_types | |
strs = @apis[method].service_types.map {|item| "'#{item}'"} | |
raise ServiceTypeNotGiven, "The method '%s' must be specified service type from %s." % [ method, strs.join(' or ') ] | |
end | |
service = @apis[method].service_types[0] | |
end | |
return service, id, version | |
else | |
raise NoMethodError | |
end | |
end | |
public | |
def method_missing(method, params=[], **args) | |
begin | |
service, id, version = check_args(method, **args) | |
rescue NoMethodError | |
super | |
else | |
response = call_api(service, @apis[method].name, params, id, version) | |
return response | |
end | |
end | |
def getEvent(params=[true], **args) | |
begin | |
service, id, version = check_args(__method__, **args) | |
rescue NoMethodError | |
super | |
else | |
if params[0] | |
response = call_api_async(service, @apis[__method__].name, params, id, version) | |
else | |
response = call_api(service, @apis[__method__].name, params, id, version) | |
end | |
return response | |
end | |
end | |
def start_liveview_thread(image_dir, image_num=nil, callback=nil) | |
liveview_url = startLiveview()['result'][0] | |
liveview_dir = image_dir | |
unless File.directory? liveview_dir | |
Dir.mkdir liveview_dir | |
end | |
th = Thread.new do | |
buffer = "" | |
obj = nil | |
total_time = 0 | |
count = 0 | |
@cli.get_content(liveview_url) do |chunk| | |
buffer << chunk | |
log.debug "start--------------------buffer.length=%d" % [ buffer.length ] | |
begin | |
time_sec = Benchmark.realtime do | |
obj = LiveviewPacket.read(buffer) | |
File.write("#{liveview_dir}/#{obj.sequence_number}.jpg", obj.payload.jpeg_data) | |
end | |
rescue EOFError => e | |
# simply read more data | |
rescue ValidityError => e | |
# clear buffer and retry | |
buffer = "" | |
else | |
log.debug "sequence = #{obj.sequence_number}" | |
log.debug "data_size = #{obj.payload.payload_data_size_wo_padding}" | |
log.debug "pad_size = #{obj.payload.padding_size}" | |
log.info "wrote '#{liveview_dir}/#{obj.sequence_number}.jpg' in %.2f ms." % [ time_sec * 1000 ] | |
log.debug "end----------------------" | |
callback if callback | |
buffer = "" | |
total_time += time_sec | |
count += 1 | |
if image_num and count >= image_num | |
break | |
end | |
end | |
end | |
stopLiveview | |
log.info "Liveview thraed finished. average write time: %.2f ms" % [total_time / count * 1000] | |
end | |
th | |
end | |
def get_all_contents(type=nil) | |
if getCameraFunction()['result'][0] != 'Contents Transfer' | |
setCameraFunction ['Contents Transfer'] | |
getEvent | |
end | |
scheme = getSchemeList()['result'][0][0]['scheme'] | |
source = getSourceList([{'scheme' => scheme}])['result'][0][0]['source'] | |
count = getContentCount([{'uri' => source, 'target' => 'all', 'view' => 'flat'}])['result'][0]['count'] | |
contents = [] | |
for i in 0..(count/100) do | |
start = i * 100 | |
cnt = start + 100 < count ? start+100 : count-start | |
contents += getContentList([{'uri' => source, 'stIdx' => start, 'cnt' => cnt, 'type' => type, 'view' => 'flat', 'sort' => 'descending'}])['result'][0] | |
end | |
for c in contents do | |
puts c['content']['original'][0]['fileName'] | |
puts c['createdTime'] | |
puts c['content']['original'][0]['url'] | |
puts c['content']['thumbnailUrl'] | |
end | |
contents | |
end | |
end | |
require 'open3' | |
require 'pp' | |
SSID = 'DIRECT-5HH5:HDR-AZ1' | |
PASS = '9gkH5XW8' | |
DEV='wlan1' | |
WIFI_CONNECT_CMD="sudo bash wificonn_linux.sh #{DEV} #{SSID} #{PASS}" | |
SSDP_ADD_ROUTE_CMD="sudo bash ssdp_addroute_linux.sh #{DEV}" | |
# Wi-Fi接続スクリプト呼び出し | |
Open3.popen2e(WIFI_CONNECT_CMD) do |i, oe, w| | |
oe.each do |line| | |
puts line | |
end | |
if w.value != 0 | |
exit 1 | |
end | |
end | |
# SSDP準備 | |
Open3.popen2e(SSDP_ADD_ROUTE_CMD) do |i, oe, w| | |
oe.each do |line| | |
puts line | |
end | |
if w.value != 0 | |
exit 2 | |
end | |
end | |
cam = Camera.new() | |
pp cam.getStorageInformation | |
pp cam.getCameraFunction | |
cam.get_all_contents | |
# th = cam.start_liveview_thread 'liveview_images', 20 | |
# th.join |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment