Skip to content

Commit

Permalink
Merge pull request #78 from jamesremuscat/feature/datavideo-dvip
Browse files Browse the repository at this point in the history
Support Datavideo DVIP protocol
  • Loading branch information
jamesremuscat committed Oct 24, 2018
2 parents 793bb79 + 3395c88 commit 6597080
Show file tree
Hide file tree
Showing 9 changed files with 503 additions and 202 deletions.
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

0 comments on commit 6597080

Please sign in to comment.