2014-03-15 19:57:14 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of sensors2osc package
#
# sensors2osc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# sensors2osc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with sensors2osc. If not, see <http://www.gnu.org/licenses/>.
#
# found the mjpeg part here, thanks for the nice code :)
# http://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/
# the osc integration stuff is implemented by me
#
# Copyright (C) 2014 Stefan Kögl
from __future__ import absolute_import
2014-04-27 13:44:38 +00:00
from BaseHTTPServer import BaseHTTPRequestHandler , HTTPServer
from chaosc . argparser_groups import *
from chaosc . lib import logger , resolve_host
from datetime import datetime
from operator import attrgetter
from PyQt4 import QtGui , QtCore
from PyQt4 . QtCore import QBuffer , QByteArray , QIODevice
from SocketServer import ThreadingMixIn , ForkingMixIn
2014-04-28 05:20:28 +00:00
2014-04-24 07:34:37 +00:00
import logging
2014-03-13 03:22:06 +00:00
import numpy as np
2014-03-27 13:06:54 +00:00
import os . path
2014-03-13 03:22:06 +00:00
import pyqtgraph as pg
2014-04-27 13:44:38 +00:00
import Queue
import re
import select
import socket
import threading
import time
2014-04-24 07:34:37 +00:00
2014-04-27 13:44:38 +00:00
2014-03-13 03:22:06 +00:00
try :
2014-04-23 16:35:15 +00:00
from chaosc . c_osc_lib import OSCMessage , decode_osc
2014-03-13 03:22:06 +00:00
except ImportError as e :
2014-04-27 13:44:38 +00:00
logging . exception ( e )
2014-04-23 16:35:15 +00:00
from chaosc . osc_lib import OSCMessage , decode_osc
2014-03-13 03:22:06 +00:00
2014-03-15 19:57:14 +00:00
class OSCThread ( threading . Thread ) :
def __init__ ( self , args ) :
super ( OSCThread , self ) . __init__ ( )
2014-03-16 10:35:23 +00:00
self . args = args
2014-03-15 19:57:14 +00:00
self . running = True
2014-03-16 10:35:23 +00:00
2014-04-08 07:34:17 +00:00
self . client_address = resolve_host ( args . client_host , args . client_port , args . address_family )
2014-03-25 21:26:19 +00:00
self . chaosc_address = chaosc_host , chaosc_port = resolve_host ( args . chaosc_host , args . chaosc_port , args . address_family )
2014-03-16 10:35:23 +00:00
2014-03-25 21:26:19 +00:00
self . osc_sock = socket . socket ( args . address_family , 2 , 17 )
2014-04-08 07:34:17 +00:00
self . osc_sock . bind ( self . client_address )
2014-03-15 19:57:14 +00:00
self . osc_sock . setblocking ( 0 )
2014-04-28 05:20:28 +00:00
logger . info ( " %s : starting up osc receiver on ' %s : %d ' " ,
2014-04-08 07:34:17 +00:00
datetime . now ( ) . strftime ( " %x %X " ) , self . client_address [ 0 ] , self . client_address [ 1 ] )
2014-03-16 10:35:23 +00:00
2014-04-27 13:44:38 +00:00
self . subscribe_me ( )
2014-03-16 10:35:23 +00:00
def subscribe_me ( self ) :
2014-04-28 05:20:28 +00:00
logger . info ( " %s : subscribing to ' %s : %d ' with label %r " , datetime . now ( ) . strftime ( " %x %X " ) , self . chaosc_address [ 0 ] , self . chaosc_address [ 1 ] , self . args . subscriber_label )
2014-03-16 10:35:23 +00:00
msg = OSCMessage ( " /subscribe " )
2014-04-08 07:34:17 +00:00
msg . appendTypedArg ( self . client_address [ 0 ] , " s " )
msg . appendTypedArg ( self . client_address [ 1 ] , " i " )
2014-03-16 10:35:23 +00:00
msg . appendTypedArg ( self . args . authenticate , " s " )
if self . args . subscriber_label is not None :
msg . appendTypedArg ( self . args . subscriber_label , " s " )
self . osc_sock . sendto ( msg . encode_osc ( ) , self . chaosc_address )
def unsubscribe_me ( self ) :
if self . args . keep_subscribed :
return
2014-04-28 05:20:28 +00:00
logger . info ( " %s : unsubscribing from ' %s : %d ' " , datetime . now ( ) . strftime ( " %x %X " ) , self . chaosc_address [ 0 ] , self . chaosc_address [ 1 ] )
2014-03-16 10:35:23 +00:00
msg = OSCMessage ( " /unsubscribe " )
2014-04-08 07:34:17 +00:00
msg . appendTypedArg ( self . client_address [ 0 ] , " s " )
msg . appendTypedArg ( self . client_address [ 1 ] , " i " )
2014-03-16 10:35:23 +00:00
msg . appendTypedArg ( self . args . authenticate , " s " )
self . osc_sock . sendto ( msg . encode_osc ( ) , self . chaosc_address )
2014-03-15 19:57:14 +00:00
def run ( self ) :
while self . running :
2014-04-12 07:44:48 +00:00
try :
2014-04-27 13:44:38 +00:00
reads , writes , errs = select . select ( [ self . osc_sock ] , [ ] , [ ] , 0.005 )
2014-04-16 23:55:00 +00:00
except Exception , e :
2014-04-27 13:44:38 +00:00
logging . exception ( e )
2014-04-12 07:44:48 +00:00
pass
2014-03-15 19:57:14 +00:00
else :
2014-04-16 23:55:00 +00:00
if reads :
try :
osc_input , address = self . osc_sock . recvfrom ( 8192 )
osc_address , typetags , messages = decode_osc ( osc_input , 0 , len ( osc_input ) )
2014-04-24 07:34:37 +00:00
queue . put_nowait ( ( osc_address , messages ) )
2014-04-16 23:55:00 +00:00
except Exception , e :
2014-04-28 05:20:28 +00:00
logger . info ( e )
2014-04-16 23:55:00 +00:00
2014-04-27 13:44:38 +00:00
self . unsubscribe_me ( )
self . osc_sock . close ( )
2014-04-28 05:20:28 +00:00
logger . info ( " OSCThread is going down " )
2014-03-15 19:57:14 +00:00
queue = Queue . Queue ( )
2014-03-23 11:37:31 +00:00
class Actor ( object ) :
2014-04-27 13:44:38 +00:00
def __init__ ( self , name , num_data , color , ix , max_actors , actor_height ) :
2014-03-23 11:37:31 +00:00
self . name = name
self . num_data = num_data
2014-04-27 13:44:38 +00:00
self . color = color
self . ix = ix
self . max_actors = max_actors
self . actor_height = actor_height
self . updated = 0
self . offset = ix * actor_height
self . data = np . array ( [ self . offset ] * num_data )
self . head = 0
self . pre_head = 0
self . plotItem = pg . PlotCurveItem ( pen = pg . mkPen ( color , width = 3 ) , name = name )
self . plotPoint = pg . ScatterPlotItem ( pen = pg . mkPen ( " w " , width = 5 ) , brush = pg . mkBrush ( color ) , size = 5 )
2014-04-03 15:44:36 +00:00
2014-03-27 13:06:54 +00:00
def __str__ ( self ) :
2014-04-27 13:44:38 +00:00
return " <Actor name: %r , active= %r , position= %r > " % ( self . name , self . active , self . head )
2014-04-03 15:44:36 +00:00
2014-03-27 13:06:54 +00:00
__repr__ = __str__
2014-03-23 11:37:31 +00:00
2014-04-27 13:44:38 +00:00
def add_value ( self , value ) :
dp = self . head
self . data [ dp ] = value / self . max_actors + self . offset
self . pre_head = dp
self . head = ( dp + 1 ) % self . num_data
self . updated + = 1
def fill_missing ( self , count ) :
dp = self . head
for i in range ( count ) :
self . data [ dp ] = self . offset
dp = ( dp + 1 ) % self . num_data
self . updated + = 1
self . pre_head = ( dp - 1 ) % self . num_data
self . head = dp
def render ( self ) :
self . plotItem . setData ( y = self . data , clear = True )
self . plotPoint . setData ( x = [ self . pre_head ] , y = [ self . data [ self . pre_head ] ] )
2014-03-23 11:37:31 +00:00
class EkgPlot ( object ) :
def __init__ ( self , actor_names , num_data , colors ) :
2014-04-12 07:44:48 +00:00
self . plot = pg . PlotWidget ( )
2014-04-27 13:44:38 +00:00
#self.plot.setConfigOptions(antialias=True)
2014-03-23 11:37:31 +00:00
self . plot . hide ( )
2014-04-12 07:44:48 +00:00
self . plot . showGrid ( False , False )
2014-03-23 11:37:31 +00:00
self . plot . setYRange ( 0 , 255 )
self . plot . setXRange ( 0 , num_data )
2014-04-24 07:34:37 +00:00
self . plot . resize ( 768 , 576 )
2014-03-23 11:37:31 +00:00
ba = self . plot . getAxis ( " bottom " )
bl = self . plot . getAxis ( " left " )
ba . setTicks ( [ ] )
bl . setTicks ( [ ] )
2014-04-27 13:44:38 +00:00
ba . hide ( )
bl . hide ( )
2014-03-23 11:37:31 +00:00
self . active_actors = list ( )
self . actors = dict ( )
self . lengths1 = [ 0 ]
self . num_data = num_data
2014-04-27 13:44:38 +00:00
self . max_value = 255
self . max_actors = len ( actor_names )
self . actor_height = self . max_value / self . max_actors
for ix , ( actor_name , color ) in enumerate ( zip ( actor_names , colors ) ) :
self . add_actor ( actor_name , num_data , color , ix , self . max_actors , self . actor_height )
2014-03-23 11:37:31 +00:00
self . set_positions ( )
self . ekg_regex = re . compile ( " ^/(.*?)/ekg$ " )
self . ctl_regex = re . compile ( " ^/plot/(.*?)$ " )
self . updated_actors = set ( )
2014-04-27 13:44:38 +00:00
def add_actor ( self , actor_name , num_data , color , ix , max_actors , actor_height ) :
actor_obj = Actor ( actor_name , num_data , color , ix , max_actors , actor_height )
2014-03-23 11:37:31 +00:00
self . actors [ actor_name ] = actor_obj
self . plot . addItem ( actor_obj . plotItem )
self . plot . addItem ( actor_obj . plotPoint )
self . active_actors . append ( actor_obj )
def set_positions ( self ) :
for ix , actor_obj in enumerate ( self . active_actors ) :
2014-04-27 13:44:38 +00:00
actor_obj . plotItem . setPos ( 0 , ix * 2 )
actor_obj . plotPoint . setPos ( 0 , ix * 2 )
2014-03-23 11:37:31 +00:00
def active_actor_count ( self ) :
2014-04-27 13:44:38 +00:00
return self . max_actors
def new_round ( self ) :
for ix , actor in enumerate ( self . active_actors ) :
actor . updated = 0
def update_missing_actors ( self ) :
liste = sorted ( self . active_actors , key = attrgetter ( " updated " ) )
max_values = liste [ - 1 ] . updated
if max_values == 0 :
# handling no signal
for actor in self . active_actors :
actor . add_value ( 0 )
return
for ix , actor in enumerate ( self . active_actors ) :
diff = max_values - actor . updated
if diff > 0 :
for i in range ( diff ) :
actor . add_value ( 0 )
2014-03-23 11:37:31 +00:00
def update ( self , osc_address , value ) :
res = self . ekg_regex . match ( osc_address )
if res :
actor_name = res . group ( 1 )
actor_obj = self . actors [ actor_name ]
2014-04-27 13:44:38 +00:00
actor_obj . add_value ( value )
2014-03-23 11:37:31 +00:00
2014-04-27 13:44:38 +00:00
def render ( self ) :
for ix , actor in enumerate ( self . active_actors ) :
actor . render ( )
2014-03-23 11:37:31 +00:00
2014-03-13 03:22:06 +00:00
class MyHandler ( BaseHTTPRequestHandler ) :
2014-03-15 19:57:14 +00:00
2014-03-13 03:22:06 +00:00
def do_GET ( self ) :
2014-03-20 03:05:01 +00:00
2014-03-13 03:22:06 +00:00
try :
self . path = re . sub ( ' [^.a-zA-Z0-9] ' , " " , str ( self . path ) )
if self . path == " " or self . path == None or self . path [ : 1 ] == " . " :
2014-03-23 11:37:31 +00:00
self . send_error ( 403 , ' Forbidden ' )
2014-03-13 03:22:06 +00:00
if self . path . endswith ( " .html " ) :
2014-03-27 13:06:54 +00:00
directory = os . path . dirname ( os . path . abspath ( __file__ ) )
data = open ( os . path . join ( directory , self . path ) , " rb " ) . read ( )
2014-03-13 03:22:06 +00:00
self . send_response ( 200 )
2014-04-03 15:44:36 +00:00
self . send_header ( ' Content-type ' , ' text/html ' )
2014-03-13 03:22:06 +00:00
self . end_headers ( )
2014-03-27 13:06:54 +00:00
self . wfile . write ( data )
2014-03-15 19:57:14 +00:00
elif self . path . endswith ( " .mjpeg " ) :
2014-03-23 11:37:31 +00:00
self . thread = thread = OSCThread ( self . server . args )
thread . daemon = True
thread . start ( )
2014-04-27 13:44:38 +00:00
actor_names = [ " bjoern " , " uwe " , " merle " ]
2014-03-23 11:37:31 +00:00
num_data = 100
colors = [ " r " , " g " , " b " ]
2014-03-26 13:25:24 +00:00
qtapp = QtGui . QApplication ( [ ] )
2014-03-23 11:37:31 +00:00
plotter = EkgPlot ( actor_names , num_data , colors )
2014-04-03 15:44:36 +00:00
self . send_response ( 200 )
2014-04-27 13:44:38 +00:00
self . send_header ( " Content-Type " , " multipart/x-mixed-replace; boundary=--2342 " )
2014-04-03 15:44:36 +00:00
self . end_headers ( )
2014-03-26 13:25:24 +00:00
event_loop = QtCore . QEventLoop ( )
2014-04-27 13:44:38 +00:00
last_frame = time . time ( ) - 1.0
frame_rate = 13.0
frame_length = 1. / frame_rate
plotter . new_round ( )
2014-03-13 03:22:06 +00:00
while 1 :
2014-03-26 13:25:24 +00:00
event_loop . processEvents ( )
qtapp . sendPostedEvents ( None , 0 )
2014-03-15 19:57:14 +00:00
while 1 :
try :
osc_address , args = queue . get_nowait ( )
2014-04-27 13:44:38 +00:00
plotter . update ( osc_address , args [ 0 ] )
2014-03-15 19:57:14 +00:00
except Queue . Empty :
break
2014-03-23 11:37:31 +00:00
2014-04-27 13:44:38 +00:00
now = time . time ( )
delta = now - last_frame
if delta > frame_length :
plotter . update_missing_actors ( )
plotter . render ( )
exporter = pg . exporters . ImageExporter . ImageExporter ( plotter . plot . plotItem )
exporter . parameters ( ) [ ' width ' ] = 768
img = exporter . export ( toBytes = True )
buffer = QBuffer ( )
buffer . open ( QIODevice . WriteOnly )
img . save ( buffer , " JPG " )
JpegData = buffer . data ( )
self . wfile . write ( " --2342 \r \n Content-Type: image/jpeg \r \n Content-length: %d \r \n \r \n %s \r \n \r \n \r \n " % ( len ( JpegData ) , JpegData ) )
last_frame = now
plotter . new_round ( )
#JpegData = None
#buffer = None
#img = None
#exporter = None
time . sleep ( 0.01 )
except ( KeyboardInterrupt , SystemError ) , e :
raise e
2014-03-25 21:26:19 +00:00
except IOError , e :
2014-04-17 13:33:21 +00:00
if e [ 0 ] in ( 32 , 104 ) :
if hasattr ( self , " thread " ) and self . thread is not None :
2014-04-16 23:55:00 +00:00
self . thread . running = False
self . thread . join ( )
2014-04-17 13:33:21 +00:00
self . thread = None
2014-04-16 23:55:00 +00:00
else :
2014-04-28 05:20:28 +00:00
pass
2014-03-13 03:22:06 +00:00
2014-03-15 19:57:14 +00:00
class JustAHTTPServer ( HTTPServer ) :
pass
2014-03-13 03:22:06 +00:00
2014-03-11 17:35:45 +00:00
def main ( ) :
2014-04-03 15:44:36 +00:00
arg_parser = ArgParser ( " ekgplotter " )
2014-04-08 07:34:17 +00:00
arg_parser . add_global_group ( )
client_group = arg_parser . add_client_group ( )
arg_parser . add_argument ( client_group , ' -x ' , " --http_host " , default = " :: " ,
2014-03-25 21:26:19 +00:00
help = ' my host, defaults to " :: " ' )
2014-04-08 07:34:17 +00:00
arg_parser . add_argument ( client_group , ' -X ' , " --http_port " , default = 9000 ,
2014-03-16 10:35:23 +00:00
type = int , help = ' my port, defaults to 9000 ' )
2014-04-03 15:44:36 +00:00
arg_parser . add_chaosc_group ( )
2014-04-08 07:34:17 +00:00
arg_parser . add_subscriber_group ( )
2014-04-03 15:44:36 +00:00
args = arg_parser . finalize ( )
2014-03-15 19:57:14 +00:00
2014-04-28 05:20:28 +00:00
2014-03-25 21:26:19 +00:00
http_host , http_port = resolve_host ( args . http_host , args . http_port , args . address_family )
2014-03-23 11:37:31 +00:00
server = JustAHTTPServer ( ( http_host , http_port ) , MyHandler )
2014-03-25 21:26:19 +00:00
server . address_family = args . address_family
2014-03-23 11:37:31 +00:00
server . args = args
2014-04-28 05:20:28 +00:00
logger . info ( " %s : starting up http server on ' %s : %d ' " ,
2014-03-23 11:37:31 +00:00
datetime . now ( ) . strftime ( " %x %X " ) , http_host , http_port )
try :
2014-03-13 03:22:06 +00:00
server . serve_forever ( )
except KeyboardInterrupt :
2014-04-28 05:20:28 +00:00
logger . info ( ' ^C received, shutting down server ' )
2014-03-13 03:22:06 +00:00
server . socket . close ( )
2014-03-25 21:26:19 +00:00
sys . exit ( 0 )
2014-03-13 03:22:06 +00:00
if __name__ == ' __main__ ' :
main ( )