diff --git a/README.md b/README.md index 9e9d834..3fe764b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Besides recalling a pre-made preset, this plugin supports a few more control ope This plugin requires the camera to support Visca over IP via UDP. It follows the specification as designed by Sony and also supports the PTZOptics variant of Visca. -This plugin is confirmed to work with at Avonic, BZB Gear, Everet, GlowStream, JVC, PTZOptics and Zowietek cameras. +This plugin is confirmed to work with at Avonic, Canon, BZB Gear, Everet, GlowStream, JVC, PTZOptics and Zowietek cameras. Others may work as well. Also visit https://obsproject.com/forum/resources/control-visca-over-ip-based-cameras.1173/ for more information. diff --git a/libvisca.lua b/libvisca.lua index 0cb53ed..17830c7 100644 --- a/libvisca.lua +++ b/libvisca.lua @@ -273,6 +273,7 @@ Visca.compatibility = { -- When the first preset is 1, leave it nil (or set to 0). -- When the first preset is 0, set the offset to 1. -- The preset recalled at the camera is 'preset - ' + pantilt_pan_bytes = 4 -- The number of bytes used for the pan argument in absolute pan/tilt commands } @@ -394,6 +395,7 @@ end function Visca.PayloadReply:get_inquiry_data_for(inquiry_payload) local _,_,category,inquiry_command = unpack(inquiry_payload) local data = {} + local unsupported_nr_of_arguments = false if category == Visca.categories.interface then if inquiry_command == Visca.inquiry_commands.software_version then @@ -409,38 +411,82 @@ function Visca.PayloadReply:get_inquiry_data_for(inquiry_payload) rom_version = bit.lshift(self.arguments[5] or 0, 8) + (self.arguments[6] or 0), max_nr_sockets = bit.band(self.arguments[7] or 0, 0x0F), } + else + unsupported_nr_of_arguments = true end end elseif category == Visca.categories.camera then if inquiry_command == Visca.inquiry_commands.color_gain then - data = { - color_level = bit.band(self.arguments[4] or 0, 0x0F) - } + if self.argument_cnt == 4 then + data = { + color_level = bit.band(self.arguments[4] or 0, 0x0F) + } + else + unsupported_nr_of_arguments = true + end elseif inquiry_command == Visca.inquiry_commands.brightness_position then - data = { - brightness = bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + - bit.band(self.arguments[4] or 0, 0x0F), - } + if self.argument_cnt == 4 then + data = { + brightness = bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + + bit.band(self.arguments[4] or 0, 0x0F), + } + else + unsupported_nr_of_arguments = true + end elseif inquiry_command == Visca.inquiry_commands.zoom_position then - data = { - zoom = bit.lshift(bit.band(self.arguments[1] or 0, 0x0F), 12) + - bit.lshift(bit.band(self.arguments[2] or 0, 0x0F), 8) + - bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + - bit.band(self.arguments[4] or 0, 0x0F), - } + if self.argument_cnt == 4 then + data = { + zoom = bit.lshift(bit.band(self.arguments[1] or 0, 0x0F), 12) + + bit.lshift(bit.band(self.arguments[2] or 0, 0x0F), 8) + + bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + + bit.band(self.arguments[4] or 0, 0x0F), + } + else + unsupported_nr_of_arguments = true + end end elseif category == Visca.categories.pan_tilter then if inquiry_command == Visca.inquiry_commands.pantilt_position then - data = { - pan = bit.lshift(bit.band(self.arguments[1] or 0, 0x0F), 12) + - bit.lshift(bit.band(self.arguments[2] or 0, 0x0F), 8) + - bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + - bit.band(self.arguments[4] or 0, 0x0F), - tilt = bit.lshift(bit.band(self.arguments[5] or 0, 0x0F), 12) + - bit.lshift(bit.band(self.arguments[6] or 0, 0x0F), 8) + - bit.lshift(bit.band(self.arguments[7] or 0, 0x0F), 4) + - bit.band(self.arguments[8] or 0, 0x0F) - } + if self.argument_cnt == 8 then + data = { + pantilt_pan_bytes = 4, + pan = bit.lshift(bit.band(self.arguments[1] or 0, 0x0F), 12) + + bit.lshift(bit.band(self.arguments[2] or 0, 0x0F), 8) + + bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 4) + + bit.band(self.arguments[4] or 0, 0x0F), + tilt = bit.lshift(bit.band(self.arguments[5] or 0, 0x0F), 12) + + bit.lshift(bit.band(self.arguments[6] or 0, 0x0F), 8) + + bit.lshift(bit.band(self.arguments[7] or 0, 0x0F), 4) + + bit.band(self.arguments[8] or 0, 0x0F) + } + elseif self.argument_cnt == 9 then + data = { + pantilt_pan_bytes = 5, + pan = bit.lshift(bit.band(self.arguments[1] or 0, 0x0F), 16) + + bit.lshift(bit.band(self.arguments[2] or 0, 0x0F), 12) + + bit.lshift(bit.band(self.arguments[3] or 0, 0x0F), 8) + + bit.lshift(bit.band(self.arguments[4] or 0, 0x0F), 4) + + bit.band(self.arguments[5] or 0, 0x0F), + tilt = bit.lshift(bit.band(self.arguments[6] or 0, 0x0F), 12) + + bit.lshift(bit.band(self.arguments[7] or 0, 0x0F), 8) + + bit.lshift(bit.band(self.arguments[8] or 0, 0x0F), 4) + + bit.band(self.arguments[9] or 0, 0x0F) + } + else + unsupported_nr_of_arguments = true + end + end + end + + if next(data) == nil then + if Visca.debug then + if unsupported_nr_of_arguments then + print(string.format("Unsupported number of arguments received for inquiry %d (%d)", + inquiry_command, self.argument_cnt)) + else + print(string.format("Unsupported inquiry type received (%d) for command category %d", + inquiry_command, category)) + end end end @@ -1347,23 +1393,44 @@ function Visca.Connection:Cam_PanTilt_Absolute(speed, pan, tilt) local msg = Visca.Message.new() msg.payload_type = Visca.payload_types.visca_command - msg.payload = { - Visca.packet_consts.req_addr_base + bit.band(Visca.default_camera_nr or 1, 0x0F), - Visca.packet_consts.command, - Visca.categories.pan_tilter, - Visca.commands.pantilt_absolute, - speed, -- Pan speed - speed, -- Tilt speed - bit.band(bit.rshift(pan, 12), 0x0F), - bit.band(bit.rshift(pan, 8), 0x0F), - bit.band(bit.rshift(pan, 4), 0x0F), - bit.band(pan, 0x0F), - bit.band(bit.rshift(tilt, 12), 0x0F), - bit.band(bit.rshift(tilt, 8), 0x0F), - bit.band(bit.rshift(tilt, 4), 0x0F), - bit.band(tilt, 0x0F), - Visca.packet_consts.terminator - } + if not self.compatibility.pantilt_pan_bytes or self.compatibility.pantilt_pan_bytes == 4 then + msg.payload = { + Visca.packet_consts.req_addr_base + bit.band(Visca.default_camera_nr or 1, 0x0F), + Visca.packet_consts.command, + Visca.categories.pan_tilter, + Visca.commands.pantilt_absolute, + speed, -- Pan speed + speed, -- Tilt speed + bit.band(bit.rshift(pan, 12), 0x0F), + bit.band(bit.rshift(pan, 8), 0x0F), + bit.band(bit.rshift(pan, 4), 0x0F), + bit.band(pan, 0x0F), + bit.band(bit.rshift(tilt, 12), 0x0F), + bit.band(bit.rshift(tilt, 8), 0x0F), + bit.band(bit.rshift(tilt, 4), 0x0F), + bit.band(tilt, 0x0F), + Visca.packet_consts.terminator + } + elseif self.compatibility.pantilt_pan_bytes == 5 then + msg.payload = { + Visca.packet_consts.req_addr_base + bit.band(Visca.default_camera_nr or 1, 0x0F), + Visca.packet_consts.command, + Visca.categories.pan_tilter, + Visca.commands.pantilt_absolute, + speed, -- Pan/Tilt speed + 00, -- Fixed + bit.band(bit.rshift(pan, 16), 0x0F), + bit.band(bit.rshift(pan, 12), 0x0F), + bit.band(bit.rshift(pan, 8), 0x0F), + bit.band(bit.rshift(pan, 4), 0x0F), + bit.band(pan, 0x0F), + bit.band(bit.rshift(tilt, 12), 0x0F), + bit.band(bit.rshift(tilt, 8), 0x0F), + bit.band(bit.rshift(tilt, 4), 0x0F), + bit.band(tilt, 0x0F), + Visca.packet_consts.terminator + } + end return self:send(msg) end diff --git a/obs-visca-control.lua b/obs-visca-control.lua index 81e7f38..9885089 100644 --- a/obs-visca-control.lua +++ b/obs-visca-control.lua @@ -501,6 +501,10 @@ local function open_visca_connection(camera_id) table.insert(ptz_vals, "Zoom: n/a (-)") end + if reply_data.pantilt_pan_bytes then + connection:set_compatibility({pantilt_pan_bytes = reply_data.pantilt_pan_bytes}) + end + for scene_name, source_name, source_settings, _ in get_plugin_settings_from_scene(plugin_scene_type.Preview, camera_id) do if source_settings then diff --git a/test/libvisca_test.lua b/test/libvisca_test.lua index 9a6d9b3..02267ef 100644 --- a/test/libvisca_test.lua +++ b/test/libvisca_test.lua @@ -221,6 +221,17 @@ function test_pantilt() lunit.assert_equal(23, t_size) lunit.assert_equal(Visca.limits.PAN_MIN_SPEED, string.byte(t_data, 13)) lunit.assert_equal(Visca.limits.PAN_MIN_SPEED, string.byte(t_data, 14)) + lunit.assert_equal(0x0F, string.byte(t_data, 15)) + lunit.assert_equal(0x0C, string.byte(t_data, 16)) + + clear_transmission_queue(connection) + connection:set_compatibility({pantilt_pan_bytes = 5}) + t_size, t_data = connection:Cam_PanTilt_Absolute(0, 0xABCDE, 0x1234) + lunit.assert_equal(24, t_size) + lunit.assert_equal(Visca.limits.PAN_MIN_SPEED, string.byte(t_data, 13)) + lunit.assert_equal(0, string.byte(t_data, 14)) + lunit.assert_equal(0x0A, string.byte(t_data, 15)) + lunit.assert_equal(0x04, string.byte(t_data, 23)) end function test_zoom() @@ -335,6 +346,24 @@ function test_reply_parsing_inquiry_brightnes() lunit.assert_equal(0x69, msg_inq_brightness_data.brightness) end +function test_reply_parsing_inquiry_pt_position() + local msg_inq_pt4_position = Visca.Message.new():from_data("\x90\x50\x0A\x0B\x0C\x0D\x01\x02\x03\x04\xFF"):dump("msg_inq_pt4_position") + lunit.assert_not_nil(msg_inq_pt4_position.message.reply) + local msg_inq_pt4_position_data = msg_inq_pt4_position.message.reply:get_inquiry_data_for({0,0,Visca.categories.pan_tilter,Visca.inquiry_commands.pantilt_position}) + lunit.assert_not_nil(msg_inq_pt4_position_data) + lunit.assert_equal(4, msg_inq_pt4_position_data.pantilt_pan_bytes) + lunit.assert_equal(0xABCD, msg_inq_pt4_position_data.pan) + lunit.assert_equal(0x1234, msg_inq_pt4_position_data.tilt) + + local msg_inq_pt5_position = Visca.Message.new():from_data("\x90\x50\x01\x02\x03\x04\x05\x0A\x0B\x0C\x0D\xFF"):dump("msg_inq_pt5_position") + lunit.assert_not_nil(msg_inq_pt5_position.message.reply) + local msg_inq_pt5_position_data = msg_inq_pt5_position.message.reply:get_inquiry_data_for({0,0,Visca.categories.pan_tilter,Visca.inquiry_commands.pantilt_position}) + lunit.assert_not_nil(msg_inq_pt5_position_data) + lunit.assert_equal(5, msg_inq_pt5_position_data.pantilt_pan_bytes) + lunit.assert_equal(0x12345, msg_inq_pt5_position_data.pan) + lunit.assert_equal(0xABCD, msg_inq_pt5_position_data.tilt) +end + function test_inquiry() lunit.assert_true(connection:set_mode(Visca.modes.generic))