Skip to content

Commit

Permalink
[Windows] Updates and support, work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
vjandrea committed Jul 7, 2023
1 parent 16d7065 commit 236f784
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 155 deletions.
Binary file modified .gitignore
Binary file not shown.
65 changes: 49 additions & 16 deletions monitor-qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@
from PyQt5.QtCore import Qt
import signal
import argparse
import ifaddr

from prodj.core.prodj import ProDj
from prodj.gui.gui import Gui


def arg_size(value):
number = int(value)
if number < 1000 or number > 60000:
raise argparse.ArgumentTypeError("%s is not between 1000 and 60000".format(value))
return number
number = int(value)
if number < 1000 or number > 60000:
raise argparse.ArgumentTypeError("%s is not between 1000 and 60000".format(value))
return number


def arg_layout(value):
if value not in ["xy", "yx", "xx", "yy", "row", "column"]:
raise argparse.ArgumentTypeError("%s is not a value from the list xy, yx, xx, yy, row or column".format(value))
return value
if value not in ["xy", "yx", "xx", "yy", "row", "column"]:
raise argparse.ArgumentTypeError("%s is not a value from the list xy, yx, xx, yy, row or column".format(value))
return value


parser = argparse.ArgumentParser(description='Python ProDJ Link')
provider_group = parser.add_mutually_exclusive_group()
Expand All @@ -28,26 +32,54 @@ def arg_layout(value):
parser.add_argument('--color-preview', action='store_true', help='Show NXS2 colored preview waveforms')
parser.add_argument('--color-waveform', action='store_true', help='Show NXS2 colored big waveforms')
parser.add_argument('-c', '--color', action='store_true', help='Shortcut for --color-preview and --color-waveform')
parser.add_argument('-q', '--quiet', action='store_const', dest='loglevel', const=logging.WARNING, help='Only display warning messages', default=logging.INFO)
parser.add_argument('-d', '--debug', action='store_const', dest='loglevel', const=logging.DEBUG, help='Display verbose debugging information')
parser.add_argument('--dump-packets', action='store_const', dest='loglevel', const=0, help='Dump packet fields for debugging', default=logging.INFO)
parser.add_argument('--chunk-size', dest='chunk_size', help='Chunk size of NFS downloads (high values may be faster but fail on some networks)', type=arg_size, default=None)
parser.add_argument('-q', '--quiet', action='store_const', dest='loglevel', const=logging.WARNING,
help='Only display warning messages', default=logging.INFO)
parser.add_argument('-d', '--debug', action='store_const', dest='loglevel', const=logging.DEBUG,
help='Display verbose debugging information')
parser.add_argument('--dump-packets', action='store_const', dest='loglevel', const=0,
help='Dump packet fields for debugging', default=logging.INFO)
parser.add_argument('--chunk-size', dest='chunk_size',
help='Chunk size of NFS downloads (high values may be faster but fail on some networks)',
type=arg_size, default=None)
parser.add_argument('-f', '--fullscreen', action='store_true', help='Start with fullscreen window')
parser.add_argument('-l', '--layout', dest='layout', help='Display layout, values are xy (default), yx, xx, yy, row or column', type=arg_layout, default="xy")
parser.add_argument('-l', '--layout', dest='layout',
help='Display layout, values are xy (default), yx, xx, yy, row or column', type=arg_layout,
default="xy")

args = parser.parse_args()

# TODO : check the number of network interfaces
adapters = ifaddr.get_adapters()
adaptersList = ()
interfaceIndex = 0
# TODO : if > 1, list all interfaces with their IP and an index, and request the user to select the good interface
if len(list(adapters)) > 1:
for index, adapter in enumerate(adapters):
adaptersList = adaptersList + (adapter.name, )
for ip in adapter.ips:
if ip.is_IPv4:
print(str(index) + ") " + adapter.nice_name + " %s/%s" % (ip.ip, ip.network_prefix))
# TODO: read the index chosen by the user, if valid persist the interface name in a property
interfaceIndex = int(input('Please select the network interface used by Pioneer ProDj Link\n'))
network_interface = adaptersList[interfaceIndex]
else:
network_interface = list(adapters)[0].name

print(network_interface)


logging.basicConfig(level=args.loglevel, format='%(levelname)-7s %(module)s: %(message)s')

prodj = ProDj()
prodj.data.pdb_enabled = args.enable_pdb
prodj.data.dbc_enabled = args.enable_dbc
if args.chunk_size is not None:
prodj.nfs.setDownloadChunkSize(args.chunk_size)
prodj.nfs.setDownloadChunkSize(args.chunk_size)
app = QApplication([])
gui = Gui(prodj, show_color_waveform=args.color_waveform or args.color, show_color_preview=args.color_preview or args.color, arg_layout=args.layout)
gui = Gui(prodj, show_color_waveform=args.color_waveform or args.color,
show_color_preview=args.color_preview or args.color, arg_layout=args.layout)
if args.fullscreen:
gui.setWindowState(Qt.WindowFullScreen | Qt.WindowMaximized | Qt.WindowActive)
gui.setWindowState(Qt.WindowFullScreen | Qt.WindowMaximized | Qt.WindowActive)

pal = app.palette()
pal.setColor(QPalette.Window, Qt.black)
Expand All @@ -59,8 +91,9 @@ def arg_layout(value):
pal.setColor(QPalette.Disabled, QPalette.ButtonText, Qt.gray)
app.setPalette(pal)

signal.signal(signal.SIGINT, lambda s,f: app.quit())
signal.signal(signal.SIGINT, lambda s, f: app.quit())

prodj.set_network_interface(network_interface)
prodj.set_client_keepalive_callback(gui.keepalive_callback)
prodj.set_client_change_callback(gui.client_change_callback)
prodj.set_media_change_callback(gui.media_callback)
Expand Down
280 changes: 147 additions & 133 deletions prodj/core/prodj.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,140 +12,154 @@
from prodj.network import packets
from prodj.network import packets_dump


class OwnIpStatus(Enum):
notNeeded = 1,
waiting = 2,
acquired = 3
notNeeded = 1,
waiting = 2,
acquired = 3


class ProDj(Thread):
def __init__(self):
super().__init__()
self.cl = ClientList(self)
self.data = DataProvider(self)
self.vcdj = Vcdj(self)
self.nfs = NfsClient(self)
self.keepalive_ip = "0.0.0.0"
self.keepalive_port = 50000
self.beat_ip = "0.0.0.0"
self.beat_port = 50001
self.status_ip = "0.0.0.0"
self.status_port = 50002
self.need_own_ip = OwnIpStatus.notNeeded
self.own_ip = None

def start(self):
self.keepalive_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.keepalive_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.keepalive_sock.bind((self.keepalive_ip, self.keepalive_port))
logging.info("Listening on {}:{} for keepalive packets".format(self.keepalive_ip, self.keepalive_port))
self.beat_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.beat_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.beat_sock.bind((self.beat_ip, self.beat_port))
logging.info("Listening on {}:{} for beat packets".format(self.beat_ip, self.beat_port))
self.status_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.status_sock.bind((self.status_ip, self.status_port))
logging.info("Listening on {}:{} for status packets".format(self.status_ip, self.status_port))
self.socks = [self.keepalive_sock, self.beat_sock, self.status_sock]
self.keep_running = True
self.data.start()
self.nfs.start()
super().start()

def stop(self):
self.keep_running = False
self.nfs.stop()
self.data.stop()
self.vcdj_disable()
self.join()
self.keepalive_sock.close()
self.beat_sock.close()

def vcdj_set_player_number(self, vcdj_player_number=5):
logging.info("Player number set to {}".format(vcdj_player_number))
self.vcdj.player_number = vcdj_player_number
#self.data.dbc.own_player_number = vcdj_player_number

def vcdj_enable(self):
self.vcdj_set_iface()
self.vcdj.start()

def vcdj_disable(self):
self.vcdj.stop()
self.vcdj.join()

def vcdj_set_iface(self):
if self.own_ip is not None:
self.vcdj.set_interface_data(*self.own_ip[1:4])

def run(self):
logging.debug("starting main loop")
while self.keep_running:
rdy = select(self.socks,[],[],1)[0]
for sock in rdy:
if sock == self.keepalive_sock:
data, addr = self.keepalive_sock.recvfrom(128)
self.handle_keepalive_packet(data, addr)
elif sock == self.beat_sock:
data, addr = self.beat_sock.recvfrom(128)
self.handle_beat_packet(data, addr)
elif sock == self.status_sock:
data, addr = self.status_sock.recvfrom(256)
self.handle_status_packet(data, addr)
self.cl.gc()
logging.debug("main loop finished")

def handle_keepalive_packet(self, data, addr):
#logging.debug("Broadcast keepalive packet from {}".format(addr))
try:
packet = packets.KeepAlivePacket.parse(data)
except Exception as e:
logging.warning("Failed to parse keepalive packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
# both packet types give us enough information to store the client
if packet["type"] in ["type_ip", "type_status", "type_change"]:
self.cl.eatKeepalive(packet)
if self.own_ip is None and len(self.cl.getClientIps()) > 0:
self.own_ip = guess_own_iface(self.cl.getClientIps())
if self.own_ip is not None:
logging.info("Guessed own interface {} ip {} mask {} mac {}".format(*self.own_ip))
def __init__(self):
super().__init__()
self.cl = ClientList(self)
self.data = DataProvider(self)
self.vcdj = Vcdj(self)
self.nfs = NfsClient(self)
self.network_interface = None
self.keepalive_ip = "0.0.0.0"
self.keepalive_port = 50000
self.beat_ip = "0.0.0.0"
self.beat_port = 50001
self.status_ip = "0.0.0.0"
self.status_port = 50002
self.need_own_ip = OwnIpStatus.notNeeded
self.own_ip = None

def start(self):
self.keepalive_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.keepalive_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# TODO : list all interfaces
# self.keepalive_sock.setsockopt(socket.SOL_SOCKET, IN.SO_BINDTODEVICE)
self.keepalive_sock.bind((self.keepalive_ip, self.keepalive_port))
logging.info("Listening on {}:{} for keepalive packets".format(self.keepalive_ip, self.keepalive_port))
self.beat_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# TODO: bind interface to socket
# self.beat_sock.setsockopt(socket.SOL_SOCKET, 25, self.network_interface) # TypeError("a bytes-like object is required, not 'str'")
# self.beat_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, self.network_interface) # AttributeError("module 'socket' has no attribute 'SO_BINDTODEVICE'")


self.beat_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.beat_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.beat_sock.bind((self.beat_ip, self.beat_port)) # PermissionError(13, 'An attempt was made to access a socket in a way forbidden by its access permissions', None, 10013, None)
logging.info("Listening on {}:{} for beat packets".format(self.beat_ip, self.beat_port))
self.status_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.status_sock.bind((self.status_ip, self.status_port))
logging.info("Listening on {}:{} for status packets".format(self.status_ip, self.status_port))
self.socks = [self.keepalive_sock, self.beat_sock, self.status_sock]
self.keep_running = True
self.data.start()
self.nfs.start()
super().start()

def stop(self):
self.keep_running = False
self.nfs.stop()
self.data.stop()
self.vcdj_disable()
self.join()
self.keepalive_sock.close()
self.beat_sock.close()

def vcdj_set_player_number(self, vcdj_player_number=5):
logging.info("Player number set to {}".format(vcdj_player_number))
self.vcdj.player_number = vcdj_player_number
# self.data.dbc.own_player_number = vcdj_player_number

def vcdj_enable(self):
self.vcdj_set_iface()
packets_dump.dump_keepalive_packet(packet)

def handle_beat_packet(self, data, addr):
#logging.debug("Broadcast beat packet from {}".format(addr))
try:
packet = packets.BeatPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse beat packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
if packet["type"] in ["type_beat", "type_mixer"]:
self.cl.eatBeat(packet)
packets_dump.dump_beat_packet(packet)

def handle_status_packet(self, data, addr):
#logging.debug("Broadcast status packet from {}".format(addr))
try:
packet = packets.StatusPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse status packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
self.cl.eatStatus(packet)
packets_dump.dump_status_packet(packet)

# called whenever a keepalive packet is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_keepalive_callback(self, cb=None):
self.cl.client_keepalive_callback = cb

# called whenever a status update of a known client is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_change_callback(self, cb=None):
self.cl.client_change_callback = cb

# called when a player media changes
# arguments of cb: this clientlist object, player_number, changed slot
def set_media_change_callback(self, cb=None):
self.cl.media_change_callback = cb
self.vcdj.start()

def vcdj_disable(self):
self.vcdj.stop()
self.vcdj.join()

def vcdj_set_iface(self):
if self.own_ip is not None:
self.vcdj.set_interface_data(*self.own_ip[1:4])

def run(self):
logging.debug("starting main loop")
while self.keep_running:
rdy = select(self.socks, [], [], 1)[0]
for sock in rdy:
if sock == self.keepalive_sock:
data, addr = self.keepalive_sock.recvfrom(128)
self.handle_keepalive_packet(data, addr)
elif sock == self.beat_sock:
data, addr = self.beat_sock.recvfrom(128)
self.handle_beat_packet(data, addr)
elif sock == self.status_sock:
data, addr = self.status_sock.recvfrom(256)
self.handle_status_packet(data, addr)
self.cl.gc()
logging.debug("main loop finished")

def handle_keepalive_packet(self, data, addr):
# logging.debug("Broadcast keepalive packet from {}".format(addr))
try:
packet = packets.KeepAlivePacket.parse(data)
except Exception as e:
logging.warning("Failed to parse keepalive packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
# both packet types give us enough information to store the client
if packet["type"] in ["type_ip", "type_status", "type_change"]:
self.cl.eatKeepalive(packet)
if self.own_ip is None and len(self.cl.getClientIps()) > 0:
self.own_ip = guess_own_iface(self.cl.getClientIps())
if self.own_ip is not None:
logging.info("Guessed own interface {} ip {} mask {} mac {}".format(*self.own_ip))
self.vcdj_set_iface()
packets_dump.dump_keepalive_packet(packet)

def handle_beat_packet(self, data, addr):
# logging.debug("Broadcast beat packet from {}".format(addr))
try:
packet = packets.BeatPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse beat packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
if packet["type"] in ["type_beat", "type_mixer"]:
self.cl.eatBeat(packet)
packets_dump.dump_beat_packet(packet)

def handle_status_packet(self, data, addr):
# logging.debug("Broadcast status packet from {}".format(addr))
try:
packet = packets.StatusPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse status packet from {}, {} bytes: {}".format(addr, len(data), e))
packets_dump.dump_packet_raw(data)
return
self.cl.eatStatus(packet)
packets_dump.dump_status_packet(packet)

# called whenever a keepalive packet is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_keepalive_callback(self, cb=None):
self.cl.client_keepalive_callback = cb

# called whenever a status update of a known client is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_change_callback(self, cb=None):
self.cl.client_change_callback = cb

# called when a player media changes
# arguments of cb: this clientlist object, player_number, changed slot
def set_media_change_callback(self, cb=None):
self.cl.media_change_callback = cb

def set_network_interface(self, network_interface=None):
self.network_interface = network_interface
Loading

0 comments on commit 236f784

Please sign in to comment.