Skip to content

Instantly share code, notes, and snippets.

@kota65535
Created November 29, 2015 06:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kota65535/1c92a56242f848ccc813 to your computer and use it in GitHub Desktop.
Save kota65535/1c92a56242f848ccc813 to your computer and use it in GitHub Desktop.
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