Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Datavideo DVIP protocol #78

Merged
merged 19 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
110 changes: 98 additions & 12 deletions src/avx/devices/datavideo.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from avx.devices.serial.VISCACamera import VISCACamera
from avx.devices.Device import InvalidArgumentException
from avx.devices.net.dvip import DVIPCamera
from avx.devices.serial.VISCACamera import CameraSettingEnum, VISCACamera
from enum import Enum


class PTC150(VISCACamera):
class _PTC150Base(object):
'''
Datavideo PTC-150, with some variations on VISCA.
'''
Expand All @@ -11,19 +12,21 @@ class PTC150(VISCACamera):
MAX_TILT_SPEED = 0x18
MIN_ZOOM_SPEED = 0x01
MAX_ZOOM_SPEED = 0x07
MAX_PRESETS = 51 # as it's zero-indexed

def storePreset(self, preset):
if preset > 0 and preset <= 50:
self.sendVISCA([0x01, 0x04, 0x3F, 0x01, preset])
def setAperture(self, aperture):
if isinstance(aperture, Aperture):
av = aperture.code
else:
raise InvalidArgumentException("Preset out of range (1-50)")
av = aperture
self.sendVISCA([0x01, 0x04, 0x4D, # It's this byte that differs from Sony (0x4B) and docs
(av & 0xF000) >> 12,
(av & 0x0F00) >> 8,
(av & 0x00F0) >> 4,
(av & 0x000F)])

def recallPreset(self, preset):
if preset > 0 and preset <= 50:
self.sendVISCA([0x01, 0x04, 0x3F, 0x02, preset])
else:
raise InvalidArgumentException("Preset out of range (1-50)")

class PTC150(_PTC150Base, VISCACamera):
def tallyRed(self):
self.sendVISCA([0x01, 0x7E, 0x01, 0x0A, 0x00, 0x02, 0x00])

Expand All @@ -32,3 +35,86 @@ def tallyGreen(self):

def tallyOff(self):
self.sendVISCA([0x01, 0x7E, 0x01, 0x0A, 0x00, 0x03])


class PTC150_DVIP(_PTC150Base, DVIPCamera):
def _tally(self, red, green):
self.sendVISCA([0x01, 0x7E, 0x01, 0x0A, 0x00, 0x02 * red, 0x02 * green])

def tallyRed(self):
self._tally(True, False)

def tallyGreen(self):
self._tally(False, True)

def tallyOff(self):
self._tally(False, False)


class Aperture(CameraSettingEnum):
CLOSE = (0x00, "Closed")
F14 = (0x05, "F14")
F11 = (0x06, "F11")
F9_6 = (0x07, "F9.6")
F8 = (0x08, "F8")
F6_8 = (0x09, "F6.8")
F5_6 = (0x0A, "F5.6")
F4_8 = (0x0B, "F4.8")
F4 = (0x0C, "F4")
F3_4 = (0x0D, "F3.4")
F2_8 = (0x0E, "F2.8")
F2_4 = (0x0F, "F2.4")
F2 = (0x10, "F2")
F1_6 = (0x11, "F1.6")


class Shutter(CameraSettingEnum):
T1 = (0x00, "1s")
T2 = (0x01, "1/2s")
T3 = (0x02, "1/3s")
T6 = (0x03, "1/6s")
T12 = (0x04, "1/12s")
T25 = (0x05, "1/25s")
T50 = (0x06, "1/50s")
T75 = (0x07, "1/75s")
T100 = (0x08, "1/100s")
T120 = (0x09, "1/120s")
T150 = (0x0A, "1/150s")
T215 = (0x0B, "1/215s")
T300 = (0x0C, "1/300s")
T425 = (0x0D, "1/425s")
T600 = (0x0E, "1/600s")
T1000 = (0x0F, "1/1000s")
T1250 = (0x10, "1/1250s")
T1750 = (0x11, "1/1750s")
T2500 = (0x12, "1/2500s")
T3500 = (0x13, "1/3500s")
T6000 = (0x14, "1/6000s")
T10000 = (0x15, "1/10000s")


class Gain(CameraSettingEnum):
G_0 = (0x01, "0")
G_2 = (0x02, "2")
G_4 = (0x03, "4")
G_6 = (0x04, "6")
G_8 = (0x05, "8")
G_10 = (0x06, "10")
G_12 = (0x07, "12")
G_14 = (0x08, "14")
G_16 = (0x09, "16")
G_18 = (0x0A, "18")
G_20 = (0x0B, "20")
G_22 = (0x0C, "22")
G_24 = (0x0D, "24")
G_26 = (0x0E, "26")
G_28 = (0x0F, "28")


__all__ = [
PTC150,
PTC150_DVIP,
Aperture,
Shutter,
Gain
]
19 changes: 3 additions & 16 deletions src/avx/devices/net/Tivo.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
from avx.devices import Device
from socket import socket
from avx.devices.net import TCPDevice


class Tivo(Device):
class Tivo(TCPDevice):
'''
A networked TiVo device. Developed against Virgin Media UK's TiVo boxes.
'''

socket = None

def __init__(self, deviceID, ipAddress, port=31339, **kwargs):
super(Tivo, self).__init__(deviceID, **kwargs)
self.ipAddress = ipAddress
self.port = port

def initialise(self):
self.socket = socket()
self.socket.settimeout(5)
self.socket.connect((self.ipAddress, self.port))

def send(self, message):
if not self.socket:
self.initialise()
self.socket.sendall(message)
super(Tivo, self).__init__(deviceID, ipAddress, port, **kwargs)

def sendIRCode(self, ircode):
self.send('IRCODE %s\r' % ircode)
Expand Down
63 changes: 63 additions & 0 deletions src/avx/devices/net/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from avx.devices.Device import Device
from threading import Thread
from time import sleep

import socket


class TCPDevice(Device):
def __init__(self, deviceID, ipAddress, port, **kwargs):
super(TCPDevice, self).__init__(deviceID, *kwargs)
self.remote = (ipAddress, port)
self._recv_thread = None
self._connect_thread = None
self._isConnected = False
self.socket = None

def initialise(self):
self.socket = socket.socket()
self.socket.settimeout(1)
self._run_recv_thread = True

if not (self._connect_thread and self._connect_thread.is_alive()) and not self._isConnected:
self._connect_thread = Thread(target=self._connect)
self._connect_thread.daemon = True
self._connect_thread.start()

def _connect(self):
while not self._isConnected and self._run_recv_thread:
try:
self.socket.connect(self.remote)
if not (self._recv_thread and self._recv_thread.is_alive()):
self._recv_thread = Thread(target=self._receive)
self._recv_thread.daemon = True
self._recv_thread.start()
self._isConnected = True
except socket.error:
self.log.warn("Could not connect to {}, will retry.".format(self.remote))
sleep(5)

def deinitialise(self):
self._run_recv_thread = False
self._isConnected = False
if self.socket:
self.socket.close()

def _receive(self):
while self._run_recv_thread:
try:
data = self.socket.recv(4096)
if data == '':
self.log.warn("Connection closed - attempting to reconnect")
sleep(5)
self.initialise()
else:
self.on_receive(data)
except socket.timeout:
pass

def send(self, data):
if self.socket:
self.socket.send(data)
else:
self.log.warn("Tried to send data without a socket: {}".format(data))
71 changes: 71 additions & 0 deletions src/avx/devices/net/dvip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'''
Created on 21 Mar 2018
@author: james
'''
from avx.devices.net import TCPDevice
from avx.devices.serial.SerialDevice import SerialDevice
from avx.devices.serial.VISCACamera import VISCACommandsMixin
from threading import Event, Lock, ThreadError


def _split_response(response_bytes):
packets = []

remaining = map(ord, response_bytes[2:])
while True:
try:
idx = remaining.index(0xFF)
packet, remaining = remaining[0:idx + 1], remaining[idx + 1:]
packets.append(packet)
except ValueError:
return packets


class DVIPCamera(TCPDevice, VISCACommandsMixin):

ACK_TIMEOUT = 0.25 # If we don't get an ack in this time, we're probably not getting one.

def __init__(self, deviceID, ipAddress, port=5002, **kwargs):
super(DVIPCamera, self).__init__(deviceID, ipAddress, port, **kwargs)
self._ack = Event()
self._await_response = Lock()
self._response_received = Event()
self._last_response = None

def on_receive(self, data):
self.log.debug("Received: {}".format(data.encode('hex_codec')))
packets = _split_response(data)
for packet in packets:
response_type = (packet[1] & 0x70) >> 4
if response_type in [4, 5, 6]: # 4 = ack, 5 = response, 6 = nack
try:
self._ack.set()
except ThreadError:
pass
if response_type == 5:
self._last_response = packet
self._response_received.set()

def sendVISCA(self, data):
self._ack.clear()
data_bytes = [0x81] + data + [0xFF]
length = len(data_bytes) + 2

self.send(
SerialDevice.byteArrayToString([
length >> 8,
length & 0xFF
] + data_bytes)
)
self._ack.wait(self.ACK_TIMEOUT)

def getVISCA(self, commandBytes):
with self._await_response:
self._response_received.clear()
self.sendVISCA(commandBytes)
self._response_received.wait()
response = self._last_response
self._last_response = None
self._response_received.clear()
return response