Add pylintrc file

Move client functions to vloed module,
simplify client file for newbee's,
Make sure the server module only starts operating when actually initialised
This commit is contained in:
Jan Klopper 2016-04-28 15:34:57 +02:00
parent 562ae26d7f
commit b9ba710848
3 changed files with 503 additions and 114 deletions

109
client.py
View file

@ -5,48 +5,15 @@ Inspired by the PixelFlut beamer on eth0:winter 2016 and
code from https://github.com/defnull/pixelflut/ code from https://github.com/defnull/pixelflut/
""" """
__version__ = 0.2 __version__ = 0.3
__author__ = "Jan Klopper <jan@underdark.nl>" __author__ = "Jan Klopper <jan@underdark.nl>"
MAX_PROTOCOL_VERSION = 1;
PROTOCOL_PREAMBLE = 'pixelvloed'
import socket
import struct
import random import random
import time from vloed import PixelVloedClient, NewMessage, RGBPixel
class MaxSizeList(list): def RandomFill(message, width, height):
def __init__(self, maxcount=100):
self.maxsize = maxcount
super( MaxSizeList, self ).__init__()
def append(self, item):
super( MaxSizeList, self ).append(item)
if self.__len__() == self.maxsize:
raise IndexError('max size reached')
def RGBPixel(x, y, r, g, b, a=None): # pylint: disable=C0103
"""Generates the packed data for a pixel"""
if a:
return struct.pack("<2H4B", x, y, r, g, b, a)
return struct.pack("<2H3B", x, y, r, g, b)
def SetRGBAMode(mode):
"""Generate the rgb/rgba bit"""
return struct.pack("<?", mode)
def SetVersionBit(protocol=1):
"""Generate the Version bit"""
return struct.pack("<B", protocol)
def RandomFill(width=640, height=480):
"""Generates a random number of pixels with a random color""" """Generates a random number of pixels with a random color"""
message = MaxSizeList(140) for pixel in xrange(0, random.randint(10, 140)):
message.append(SetRGBAMode(False))
message.append(SetVersionBit())
for pixel in xrange(0, random.randint(10, 100)):
pixel = RGBPixel(random.randint(0, width), pixel = RGBPixel(random.randint(0, width),
random.randint(0, height), random.randint(0, height),
random.randint(0, 255), random.randint(0, 255),
@ -57,57 +24,29 @@ def RandomFill(width=640, height=480):
except IndexError: except IndexError:
yield ''.join(message) yield ''.join(message)
message[2:] = [] message[2:] = []
message.append(pixel)
yield ''.join(message) yield ''.join(message)
def SendPacket(ipaddress, port, message): def RunClient():
"""Sends the message to the udp server"""
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.sendto(message, (ipaddress, port))
def DiscoverServers(discoveryport, timeout=5):
"""Discover servers that send out the pixelfvloed preample"""
DiscoverySock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
DiscoverySock.bind(('', discoveryport))
starttime = time.time()
servers = []
foundhash = {}
while (time.time() - timeout) < starttime:
data, addr = DiscoverySock.recvfrom(1024)
try:
if data.startswith(PROTOCOL_PREAMBLE):
dataset = data.split(' ')
if float(dataset[0].split(':')[1]) <= MAX_PROTOCOL_VERSION:
ip = dataset[1].split(':')[0]
port = int(dataset[1].split(':')[1])
width = int(dataset[2].split('*')[0])
height = int(dataset[2].split('*')[1])
if data not in foundhash:
newserver = {'ip': ip,
'port': port,
'width': width,
'height': height}
foundhash[data] = True
print 'New pixelvloed screen found: %r' % newserver
servers.append(newserver)
except:
pass
if servers:
return servers
return False
def main():
"""Discover the servers and start sending to the first one""" """Discover the servers and start sending to the first one"""
discoveryport = 5006
servers = False client = PixelVloedClient(True, # start as soon as we find a server
while servers == False: False, # show debugging output
servers = DiscoverServers(discoveryport) None, # ip of the server, None for autodetect
print 'displaying on %(ip)s:%(port)d, %(width)d*%(height)dpx' % servers[0] None, # port of the server None for autodetect
None, # Screen pixels wide, or Autodetect
None # Screen pixels height, or Autodetect
)
message = NewMessage() #create a new message that buffers the output etc
# loop the effect untill we cancel by pressing ctrl+c / exit the program
while True: while True:
for packet in RandomFill(servers[0]['width'], servers[0]['height']): # create a new message and send if everytime the buffer is full
time.sleep(0.01) # the width/height are read from the client's config
SendPacket(servers[0]['ip'], servers[0]['port'], packet) for packet in RandomFill(message, client.width, client.height):
# send the message we just filled with random pixelfs
client.SendPacket(packet)
if __name__ == '__main__': if __name__ == '__main__':
main() # if this script is called from the commandline, and thus not imported
# Start a client and start sending messages
RunClient()

311
pylintrc Normal file
View file

@ -0,0 +1,311 @@
# lint Python modules using external checkers.
#
# This is the main checker controling the other ones and the reports
# generation. It is itself both a raw checker and an astng checker in order
# to:
# * handle message activation / deactivation at the module level
# * handle some basic but necessary stats'data (number of classes, methods...)
#
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=CVS
ignore=.hg
# Pickle collected data for later comparisons.
persistent=yes
# Set the cache size for astng objects.
cache-size=500
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable only checker(s) with the given id(s). This option conflicts with the
# disable-checker option
#enable-checker=
# Enable all checker(s) except those with the given id(s). This option
# conflicts with the enable-checker option
#disable-checker=
# Enable all messages in the listed categories.
#enable-msg-cat=
# Disable all messages in the listed categories.
#disable-msg-cat=
# Enable the message(s) with the given id(s).
#enable-msg=
# Disable the message(s) with the given id(s).
# W0142: Used * or ** magic -- this is in fact a very Pythonic approach.
# E1103: %s %r has no %r member (but some types could not be inferred)
# This is generally not an actual problem, and causes false positives
disable-msg=W0403, W0142, E1103
disable=W0403, W0142, E1103
[REPORTS]
# set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=colorized
# Include message's id in output
include-ids=yes
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells wether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note).You have access to the variables errors warning, statement which
# respectivly contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (R0004).
evaluation=10.0 - (5.0 * error + warning + refactor + convention) / statement * 10
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (R0004).
comment=yes
# Enable the report(s) with the given id(s).
#enable-report=
# Disable the report(s) with the given id(s).
# R0801 is the "similar lines" report, which is not used.
disable-report=R0801
# checks for
# * unused variables / imports
# * undefined variables
# * redefinition of variable from builtins or from an outer scope
# * use of variable before assigment
#
[VARIABLES]
# Tells wether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching names used for dummy variables (i.e. not used).
dummy-variables-rgx=_.*$
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# try to find bugs in the code using type inference
#
[TYPECHECK]
# Tells wether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# When zope mode is activated, consider the acquired-members option to ignore
# access to some undefined attributes.
zope=no
# List of members which are usually get through zope's acquisition mecanism and
# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
acquired-members=REQUEST,acl_users,aq_parent
# checks for :
# * doc strings
# * modules / classes / functions / methods / arguments / variables name
# * number of arguments, local variables, branchs, returns and statements in
# functions, methods
# * required module attributes
# * dangerous default values as arguments
# * redefinition of function / method / class
# * uses of the global statement
#
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=__author__, __version__
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=_.*$
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=([A-Z_][a-zA-Z0-9_]{2,30})|(main)$
# Regular expression which should only match correct method names
method-rgx=([a-zA-Z_][a-zA-Z0-9_]{2,30})|(test.*)$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,n
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar
# List of builtins function names that should not be used, separated by a comma
bad-functions=filter,apply,input
# checks for
# * external modules dependencies
# * relative / wildcard imports
# * cyclic imports
# * uses of deprecated modules
#
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report R0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report R0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report R0402 must
# not be disabled)
int-import-graph=
# checks for :
# * methods without self as first argument
# * overridden methods signature
# * access only to existant members via self
# * attributes not defined in the __init__ method
# * supported interfaces implementation
# * unreachable code
#
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp,run
# checks for sign of poor/misdesign:
# * number of methods, attributes, local variables...
# * size, complexity of functions, methods
#
[DESIGN]
# Maximum number of arguments for function / method
max-args=8
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branchs=20
# Maximum number of statements in function / method body
max-statements=40
# Maximum number of parents for a class (see R0901).
max-parents=12
# Maximum number of attributes for a class (see R0902).
max-attributes=10
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# checks for:
# * warning notes in the code like FIXME, XXX
# * PEP 263: source code with non ascii character but no encoding declaration
#
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
# checks for :
# * unauthorized constructions
# * strict indentation
# * line length
# * use of <> instead of !=
#
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# checks for similarities and duplicated code. This computation may be
# memory / CPU intensive, so you should disable it if you experiments some
# problems.
#
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes

197
vloed.py
View file

@ -5,31 +5,32 @@ Inspired by the PixelFlut beamer on eth0:winter 2016 and
code from https://github.com/defnull/pixelflut/ code from https://github.com/defnull/pixelflut/
""" """
__version__ = 0.2 __version__ = 0.3
__author__ = "Jan Klopper <jan@underdark.nl>" __author__ = "Jan Klopper <jan@underdark.nl>"
import pygame import pygame
from pygame.locals import * from pygame import locals as pygamelocals
import struct import struct
import time import time
from gevent import spawn, socket, monkey import socket
from gevent import spawn, monkey
from gevent.server import DatagramServer from gevent.server import DatagramServer
from gevent.queue import Queue from gevent.queue import Queue
monkey.patch_all() monkey.patch_all()
UDP_IP = "127.0.0.1" UDP_IP = "127.0.0.1"
UDP_PORT= 5005 UDP_PORT = 5005
DISCOVER_PORT = 5006
PROTOCOL_VERSION = 1 PROTOCOL_VERSION = 1
MAX_PROTOCOL_VERSION = 1
PROTOCOL_PREAMBLE = "pixelvloed" PROTOCOL_PREAMBLE = "pixelvloed"
MAX_PIXELS = 140
def main():
"""Runs a pixelvloed server"""
PixelVloed(':%d' %(UDP_PORT)).serve_forever()
class Canvas(object): class Canvas(object):
"""PixelVloed server class""" """PixelVloed display class"""
def __init__(self, queue, width=1366, height=768, debug=True): def __init__(self, queue, width=1366, height=768, debug=False):
"""Init the pixelVloed server""" """Init the pixelVloed server"""
self.debug = debug self.debug = debug
self.pixeloffset = 2 self.pixeloffset = 2
@ -42,10 +43,11 @@ class Canvas(object):
self.canvas() self.canvas()
self.set_title() self.set_title()
self.queue = queue self.queue = queue
self.limit = 140 self.limit = MAX_PIXELS
self.broadcastSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.pixels = None
self.broadcastSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.broadcastsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.broadcastSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.broadcastsocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.broadcastsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@staticmethod @staticmethod
def set_title(text=None): def set_title(text=None):
@ -59,8 +61,8 @@ class Canvas(object):
"""Init the pygame canvas""" """Init the pygame canvas"""
pygame.init() pygame.init()
pygame.mixer.quit() pygame.mixer.quit()
flags = DOUBLEBUF self.screen = pygame.display.set_mode((self.width, self.height),
self.screen = pygame.display.set_mode((self.width, self.height), flags) pygamelocals.DOUBLEBUF)
def clear(self, r=0, g=0, b=0): # pylint: disable=C0103 def clear(self, r=0, g=0, b=0): # pylint: disable=C0103
""" Fill the entire screen with a solid colour (default: black)""" """ Fill the entire screen with a solid colour (default: black)"""
@ -115,7 +117,7 @@ class Canvas(object):
pixelcount = min(((len(data)-1) / pixellength), pixelcount = min(((len(data)-1) / pixellength),
self.limit) self.limit)
if self.debug: if self.debug:
print '%d pixels received, protocol V %d ' % (pixelcount, protocol) print '%d pixels received, protocol V %d' % (pixelcount, protocol)
for i in xrange(0, pixelcount): for i in xrange(0, pixelcount):
pixel = struct.unpack_from( pixel = struct.unpack_from(
packetformat, packetformat,
@ -124,36 +126,173 @@ class Canvas(object):
if self.debug: if self.debug:
print pixel print pixel
self.Pixel(*pixel) self.Pixel(*pixel)
except Exception as e: except Exception as error:
if self.debug: if self.debug:
# All exceptions will be printed, but won't result in a crash. # All exceptions will be printed, but won't result in a crash.
print e print error
# indicate that we have been drawing stuff # indicate that we have been drawing stuff
return True return True
def SendDiscoveryPacket(self): def SendDiscoveryPacket(self):
"""Lets send out our ip/port/resolution to any listening clients""" """Lets send out our ip/port/resolution to any listening clients"""
self.broadcastSocket.sendto( self.broadcastsocket.sendto(
'%s:%f %s:%d %d*%d' % (PROTOCOL_PREAMBLE, PROTOCOL_VERSION, '%s:%f %s:%d %d*%d' % (PROTOCOL_PREAMBLE, PROTOCOL_VERSION,
UDP_IP, UDP_PORT, UDP_IP, UDP_PORT,
self.width, self.height), self.width, self.height),
('<broadcast>', UDP_PORT+1)) ('<broadcast>', DISCOVER_PORT))
if self.debug: if self.debug:
print 'sending discovery packet' print 'sending discovery packet'
def __del__(self): def __del__(self):
"""Clean up any sockets we created""" """Clean up any sockets we created"""
self.broadcastSocket.close() self.broadcastsocket.close()
class PixelVloed(DatagramServer): class PixelVloedServer(DatagramServer):
"""PixelVloed server class""" """PixelVloed server class"""
queue = Queue()
pixelcanvas = Canvas(queue)
__request_processing_greenlet = spawn(pixelcanvas.CanvasUpdate)
def handle(self, data, address): def __init__(self, *args, **kwargs):
"""Is called by the DataGramServer whenever a package is received""" """Set up some vars for this instance"""
self.queue = Queue()
pixelcanvas = Canvas(self.queue)
__request_processing_greenlet = spawn(pixelcanvas.CanvasUpdate)
DatagramServer.__init__(self, *args, **kwargs)
def handle(self, data, _address):
"""Is called by the DataGramServer whenever an udp package is received"""
self.queue.put(data) self.queue.put(data)
class PixelVloedClient(object):
"""Sets up a client
Arguments:
firstserver: (bool) False, select the first server immediately
debug: (bool) False
ip: (str) None
port: (int) None
width: (int) 640
height: (int) 480
Listens for servers if no ip is given
"""
def __init__(self, firstserver=False, debug=False,
ip=None, port=None,
width=640, height=480):
self.sleep = 0.01
self.debug = debug
if not ip:
servers = False
while servers == False:
servers = self.DiscoverServers(firstserver)
self.ipaddress = servers[0]['ip']
self.port = servers[0]['port']
self.width = servers[0]['width']
self.height = servers[0]['height']
if self.debug:
print ('displaying on %(ip)s:%(port)d, %(width)d*%(height)dpx' %
servers[0])
else:
self.ipaddress = ip
self.port = port
self.width = width
self.height = height
if self.debug:
print ('displaying on %(ip)s:%(port)d, %(width)d*%(height)dpx' %
self)
self.sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
def Sleep(self, duration=None):
"""Sleeps the designated amount of time"""
time.sleep(duration if duration else self.sleep)
def SendPacket(self, message, sleep=True):
"""Sends the message to the udp server
Arguments:
message: (str, 140)
sleep: (bool) True, should the client sleep for a while?
"""
self.sock.sendto(message, (self.ipaddress, self.port))
if sleep:
self.Sleep()
@staticmethod
def DiscoverServers(returnfirst=False, timeout=5):
"""Discover servers that send out the pixelfvloed preample"""
discoverysock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
discoverysock.bind(('', DISCOVER_PORT))
starttime = time.time()
servers = []
foundhash = {}
while (time.time() - timeout) < starttime:
data, _addr = discoverysock.recvfrom(1024)
try:
if data.startswith(PROTOCOL_PREAMBLE):
dataset = data.split(' ')
if float(dataset[0].split(':')[1]) <= MAX_PROTOCOL_VERSION:
ipaddress = dataset[1].split(':')[0]
port = int(dataset[1].split(':')[1])
width = int(dataset[2].split('*')[0])
height = int(dataset[2].split('*')[1])
if data not in foundhash:
newserver = {'ip': ipaddress,
'port': port,
'width': width,
'height': height}
foundhash[data] = True
print 'New pixelvloed screen found: %r' % newserver
servers.append(newserver)
if returnfirst:
return servers
except:
pass
if servers:
return servers
return False
def NewMessage():
"""Creates a new message with the correct max size, rgb mode and version"""
message = MaxSizeList(MAX_PIXELS+2)
message.append(SetRGBAMode(False))
message.append(SetVersionBit())
return message
def RGBPixel(x, y, r, g, b, a=None): # pylint: disable=C0103
"""Generates the packed data for a pixel"""
if a:
return struct.pack("<2H4B", x, y, r, g, b, a)
return struct.pack("<2H3B", x, y, r, g, b)
def SetRGBAMode(mode):
"""Generate the rgb/rgba bit"""
return struct.pack("<?", mode)
def SetVersionBit(protocol=1):
"""Generate the Version bit"""
return struct.pack("<B", protocol)
class MaxSizeList(list):
"""A list that raises an indexError when it reaches the designated max size"""
def __init__(self, maxcount=100):
"""Inits a list with a maxcount
Arguments:
maxcount: (int) 100
"""
self.maxsize = maxcount
super(MaxSizeList, self).__init__()
def append(self, item):
"""Appends an item to the list"""
super(MaxSizeList, self).append(item)
if self.__len__() == self.maxsize:
raise IndexError('max size reached')
def RunServer():
"""Runs a pixelvloed server"""
PixelVloedServer('%s:%d' %(UDP_IP, UDP_PORT)).serve_forever()
if __name__ == '__main__': if __name__ == '__main__':
main() RunServer()