import wx import os import time import random import ctypes # needed to talk to dlportio.dll import winsound # needed for MyBeep import win32gui import win32api import serial MAINFRAME = 0 WINAMP = 0 CONTROLS = 0 STATISTICS = 0 LOG = 0 FEATURES = 0 PARALLELPORT = 0 PICTURES = 0 class Winamp: # wonder why win32 imports dont define these WM_COMMAND = 0x0111 WM_USER = 0x400 hWinamp = 0 winamp_commands = { 'prev' :40044, 'next' :40048, 'play' :40045, 'pause' :40046, 'stop' :40047, 'fadeout' :40157, 'forward' :40148, 'rewind' :40144, 'raisevol':40058, 'lowervol':40059} def start(self): # start Winamp unless already running if( not FEATURES.Winamp.GetValue() ): return try: self.hWinamp = win32gui.FindWindow('Winamp v1.x', None) except Exception: os.spawnv(os.P_NOWAIT,'c:\\program files\\winamp\\winamp.exe',("",)) time.sleep(5) try: self.hWinamp = win32gui.FindWindow('Winamp v1.x', None) except Exception: self.hWinamp = 0 return -1 return 0 def command(self, sCommand): if( not FEATURES.Winamp.GetValue() ): return if self.winamp_commands.has_key(sCommand): return win32api.SendMessage(self.hWinamp, self.WM_COMMAND, self.winamp_commands[sCommand], 0) else: raise 'NoSuchWinampCommand' def usercommand(self, id, data=0): if( not FEATURES.Winamp.GetValue() ): return return win32api.SendMessage(self.hWinamp, self.WM_USER, data, id) def getPlayingStatus(self): if( not FEATURES.Winamp.GetValue() ): return "not supported" "returns the current status string which is one of 'playing', 'paused' or 'stopped'" iStatus = self.usercommand(104) if iStatus == 1: return 'playing' elif iStatus == 3: return 'paused' else: return 'stopped' def setVolume(self, iVolumeLevel): if( not FEATURES.Winamp.GetValue() ): return "sets the volume to number specified (range is 0 to 255)" return self.usercommand(122, iVolumeLevel) class MyBeep(): def Play(self,value): winsound.MessageBeep(( winsound.MB_ICONASTERISK, # 0 winsound.MB_ICONEXCLAMATION, # 1 winsound.MB_ICONHAND, # 2 Critical Stop winsound.MB_ICONQUESTION, # 3 winsound.MB_OK)[value]) # 4 class ParallelPort(): # 1234 5678 output bit number assignments on CS box # 3=monitor # 4=arm lock # 5&6 = ESTIM def __init__(self, base=0x378): self.port = ctypes.windll.dlportio # load up the .dll functions self.base = base self.value = self.port.DlPortReadPortUchar( self.base ) def outp(self,value): self.value = value self.port.DlPortWritePortUchar( self.base, self.value) def inp(self): # ...1 2... input bits: buttons are pushed if bits are high return self.port.DlPortReadPortUchar( self.base + 1 ) def ESTIMOn(self): self.value |= 0x0C self.port.DlPortWritePortUchar( self.base, self.value) CONTROLS.et312.on() def ESTIMOff(self): self.value &= 0xFF - 0x0C self.port.DlPortWritePortUchar( self.base, self.value) CONTROLS.et312.off() def ArmUnlock(self): self.value |= 0x10 self.port.DlPortWritePortUchar( self.base, self.value) CONTROLS.ArmUnlock.SetLabel("Lock Arm") CONTROLS.ArmUnlock.SetValue(1) wx.LogMessage( "Arm Tube Unlocked" ) def ArmLock(self): self.value &= 0xFF - 0x10 self.port.DlPortWritePortUchar( self.base, self.value) CONTROLS.ArmUnlock.SetLabel("Unlock Arm") CONTROLS.ArmUnlock.SetValue(0) wx.LogMessage( "Arm Tube Locked" ) def MonitorOn(self): self.value |= 0x20 self.port.DlPortWritePortUchar( self.base, self.value) def MonitorOff(self): self.value &= 0xFF - 0x20 self.port.DlPortWritePortUchar( self.base, self.value) def Buttons(self): buttons = 0 if( FEATURES.RiderControl.GetSelection() == 1 ): raw = self.port.DlPortReadPortUchar( self.base + 1 ) if( raw & 0x10 ): buttons += 1 if( raw & 0x08 ): buttons += 2 return buttons def ArmUnlocked(self): if( self.value & 0x10 ): return 1 return 0 class Ask(): global MAINFRAME, CONTROLS, PARALLELPORT, PICTURES when = 0 # 0=text is pending >0 means when to dosplay text dwell = 0 # count down to clearing text mySec = 0 buttons = 0 def __init__(self, parent): self.parent = parent self.beep = MyBeep() MAINFRAME.SecondCallback( self.OnSecond ) # determine if and when to display the text def NewPic(self): if( not self.when and FEATURES.RiderControl.GetSelection() == 1 and CONTROLS.sliderOdds.GetValue() >= random.randrange(1,100,1) and CONTROLS.sliderPic.GetValue() >= 10): self.when = random.randrange(3, CONTROLS.sliderPic.GetValue()-3, 1) self.mySec = 0 def Message(self, text, zap = 0): if( not self.dwell ): if( zap ): PARALLELPORT.ESTIMOn() self.beep.Play(2) else: self.beep.Play(1) self.dwell = 2 PICTURES.ShowText(text, x=-1) return 1 else: return 0 # message was unable to be displayed def OnSecond(self, sec): # display next picture when time expires self.mySec += 1 # keep seconds up to date if( self.dwell > 0 ): self.dwell -= 1 if( not self.dwell ): PICTURES.ClearText() PARALLELPORT.ESTIMOff() if( self.mySec == self.when ): if( PARALLELPORT.Buttons() ): self.beep.Play(2) PICTURES.ShowText("PUSH BUTTONS ONLY WHEN ASKED", color="Red", size=18, x=-1) PARALLELPORT.ESTIMOn() CONTROLS.punish() STATISTICS.FailuresToObey() self.when = 0 self.dwell = 2 return if( random.random() > 0.5 ): text = "PUSH BUTTON ONE" self.buttons = 1 else: text = "PUSH BUTTON TWO" self.buttons = 2 PICTURES.ShowText(text, x=-2) STATISTICS.ButtonText() elif( self.when and self.mySec == self.when + 1 ): if( PARALLELPORT.Buttons() == self.buttons ): self.beep.Play(1) PICTURES.ShowText("VERY GOOD", color="Green", size=18, x=-1) self.when = 0 self.dwell = 2 CONTROLS.reward() elif( self.when and self.mySec == self.when + 2 ): if( PARALLELPORT.Buttons() == self.buttons ): self.beep.Play(1) PICTURES.ShowText("GOOD", color="Green", size=18, x=-1) else: self.beep.Play(2) PICTURES.ShowText("FAILURE TO OBEY", color="Red", size=18, x=-1) PARALLELPORT.ESTIMOn() STATISTICS.FailuresToObey() CONTROLS.punish() self.dwell = 2 self.when = 0 class MyMinutes(): def __init__(self): self.min = 0 # elasped minutes self.sec = 0; def tick(self): # call once per minute self.sec += 1 # tick is called each second if( self.sec >= 60 ): self.sec = 0 self.min += 1 return self.min # return elasped minutes def clear(self): # start counting over self.min = 0 self.sec = 0 def now(self): # get current time return (self.min, self.sec) class MySlideControl(wx.Panel): def __init__(self, parent, label='', x=0, y=0, cur=1, min=1, max=100): wx.Panel.__init__(self, parent, -1, pos=(x,y), size=(355,100 )) self.SetBackgroundColour( (220,220,250) ) self.SetForegroundColour( "Black" ) wx.StaticBox(self, -1, label, (5,2), size=(345,92) ) self.slider = wx.Slider(self, -1, cur, min, max, point=(20, 20), size=(310, 45), style=wx.SL_LABELS ) def GetValue(self): return self.slider.GetValue() def SetValue(self, x, min= 0): if( x < min ): x = min if( x < self.slider.GetMin() ): x = self.slider.GetMin if( x > self.slider.GetMax() ): x = self.slider.GetMax self.slider.SetValue(x) class MySlideGauge(wx.Panel): # send serial commands to Venus control box def __init__(self, parent, label='', x=0, y=0, cur=1, min=1, max=100): wx.Panel.__init__(self, parent, -1, pos=(x,y), size=(355,100 )) self.SetBackgroundColour( (220,220,250) ) self.SetForegroundColour( "Black" ) self.parent = parent self.min = min box = wx.StaticBox(self, -1, label, (7,2), size=(345,92) ) self.slider = wx.Slider(self, -1, cur, min, max, point=(20, 20), size=(310, 45), style=wx.SL_LABELS ) self.Bind(wx.EVT_SCROLL, self.OnSlider, self.slider) self.gauge = wx.Gauge(self, -1, cur*100, pos=(28, 75), size=(294, 10) ) self.minutes = 0 def GetValue(self): return self.slider.GetValue() def SetGauge(self,value): if( value > self.gauge.GetRange() ): value = self.gauge.GetRange() self.gauge.SetValue(value*100) # assuming Gauge measures time in minutes, set current value def SetGaugeTime(self, (minutes, seconds)): value = 100.0 * (minutes + (seconds / 60.0)) if( value > self.gauge.GetRange() ): value = self.gauge.GetRange() self.gauge.SetValue(value) def OnSlider(self, event): self.gauge.SetRange(self.slider.GetValue() * 100) class Venus2000(): openned = 0 def __init__(self, parent): self.parent = parent self.ser = serial.Serial() self.ser.baudrate = 9600 self.ser.timeout = 0.1 self.p = 0 def open(self): if not self.openned and FEATURES.Venus.GetValue(): self.ser.port = FEATURES.VenusPort.GetValue() self.ser.open() self.openned = 1 def pause(self): if( self.openned): self.ser.write("p1\r") self.p = 1 def run(self): if( self.openned): self.ser.write("p0\r") self.p = 0 def close(self): if( self.openned): self.ser.close() def running(self): # return true if running (not paused) return (1 - self.p) class ET312(): debug = 0 openned = 0 def __init__(self, parent): self.parent = parent self.ser = serial.Serial() self.ser.baudrate = 19200 self.ser.timeout = 0.1 def open(self): if not self.openned and FEATURES.ET312.GetValue(): self.ser.port = FEATURES.ET312Port.GetValue() self.ser.open() self.openned = 0 self.mod = 0 for i in range(0,5): self.send("\x00") r = self.ser.read(1) self.hex( r ) if( len(r) == 1 and r[0] == "\x07" ): self.openned += 1 else: self.openned = 0 if( self.openned >= 3 ): if( self.debug ): wx.LogMessage( "ET312 responed to HELLO message, trying to sync" ) self.send("\x2F\x00") r = self.ser.read(3) self.hex( r ) if( len(r) == 3 ): sum = (ord(r[0]) + ord(r[1]) ) & 255 if( r[0] == "\x21" and sum == ord(r[2]) ): self.mod = ord( r[1] ) ^ 0x55 if( self.debug ): wx.LogMessage( "ET312 sync'd, mode char is: %02X" % self.mod ) LOG.message( "ET312 was successfully openned" ) f = open("mod", "w" ) f.write( chr(self.mod) ) f.close() return LOG.error( "ET312 failed to sync" ) self.openned = 0 self.ser.close() return if( self.debug ): wx.LogMessage( "ET312 hello message failed, try MOD file" ) f = open("mod", "r" ) self.mod = ord( f.read( 1 ) ) f.close() if( self.debug ): wx.LogMessage( "ET312 mode char from file is: %02X" % self.mod ) self.openned = 0 for i in range(0,10): self.send("\x00") r = self.ser.read(1) self.hex( r ) if( (len(r) > 0) and (r[0] == '\x07') ): self.openned = 1 break if( not self.openned ): self.ser.close() LOG.error( "ET312 not found, no response" ) return v = self.get_routine() #get routine number if( v < 0 ): LOG.error( "ET312 sent a bad response" ) self.openned = 0 return if( self.debug ): wx.LogMessage( "ET312 openned using MOD file" ) LOG.message( "ET312 was successfully openned" ) return def get_routine(self): # 76=waves 77=stroke 78=climb 79=combo 7A=intense 7B=rhythm 7C=audio1 # 7D=audio2 7E=audio3 7F=split 80=random1 81=random2 82=toggle 83=orgasm # 84=torment 85=phase1 86=phase2 87=phase3 88=user1 89=user2 8A=user3 self.send( "\x3c\x40\x7b" ) #get routine number r = self.ser.read(3) self.hex( r ) return self.check( r ) def set_level(self, x): if( not self.openned ): return self.send( "\x4D\x40\xA5" + chr( 128 + x ) ) r = self.ser.read(1) if( len(r) != 1 or r[0] != '\x06' ): wx.LogMessage( "ET312 ACK missing" ) self.send( "\x4D\x41\xA5" + chr( 128 + x ) ) r = self.ser.read(1) if( len(r) != 1 or r[0] != '\x06' ): wx.LogMessage( "ET312 ACK missing" ) def on(self): if( not self.openned ): return x = CONTROLS.sliderET312.GetValue() self.set_level( x ) def off(self): if( not self.openned ): return self.set_level( 0 ) def adjust(self): if( not self.openned ): return LOG.message( "set ET312 to intense, with levels at 0" ) self.set_level( 40 ) LOG.message( "set ET312 levels to mild pulses" ) self.send( "\x4D\x40\xA5" + chr( 128 + 0 ) ) self.set_level( 0 ) def send(self, msg): lst = list( msg ) # create LIST from string sum = 0 db = "Sending to ET312: " for i in range( len(msg) ): x = ord( lst[i] ) sum = (sum + x) & 255 db += "%02x " % x x = x ^ self.mod lst[i] = chr( x ) if( len(msg) > 1 ): db += "%02x" % sum sum = sum ^ self.mod lst.append( chr(sum ) ) m = ''.join(lst) # create STRING from LIST, with no seperator self.ser.write( m ) if( self.debug ): wx.LogMessage( db ) def hex( self, msg ): if( not self.debug ): return db = "Received from ET312: " for i in range( len(msg) ): x = ord( msg[i] ) db += "%02x " % x wx.LogMessage( db ) def check(self, msg): # verify a 3 byte received message if( len(msg) != 3 ): return -1 b1 = ord( msg[0] ) b2 = ord( msg[1] ) b3 = ord( msg[2] ) sum = ( b1 + b2 ) & 255 if( b1 != 0x22 or sum != b3 ): return -1 return b2 class MainWindow(wx.Frame): mysec = 0 SecondCallbacks = [] def __init__(self, parent, id, title): global WINAMP, PARALLELPORT wx.Frame.__init__(self, parent, id, title, size=(800,675)) # A Statusbar in the bottom of the window self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount( 2 ) self.Show(True) self.timer = timer = wx.Timer(self, -1) self.Bind(wx.EVT_TIMER, self.OnTimer, timer) timer.Start(20) wx.EVT_CLOSE(self, self.OnClose) WINAMP = Winamp() PARALLELPORT = ParallelPort() # A one second timer that is shared by the system # To receive a call each second, register a routine by calling SecondCallback() def OnTimer(self, event): sec = int( time.time() ) if( sec != self.mysec ): # one second ticks self.mysec = sec for x in self.SecondCallbacks: x(sec) # Call this method to register a routine so it gets a call one per second def SecondCallback(self, routine): if( routine not in self.SecondCallbacks ) : self.SecondCallbacks.append(routine) def OnClose(self, event): self.timer.Stop() WINAMP.command("stop") PARALLELPORT.MonitorOff() FEATURES.config.WriteInt("/LastRun/Features/Venus", FEATURES.Venus.GetValue()) FEATURES.config.Write("/LastRun/Features/VenusPort", FEATURES.VenusPort.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/VenusPause", FEATURES.VenusPause.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/VenusPauseStrokes", FEATURES.VenusPauseStrokes.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/SelfStart", FEATURES.SelfStart.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/ET312", FEATURES.ET312.GetValue()) FEATURES.config.Write("/LastRun/Features/ET312Port", FEATURES.ET312Port.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/ET312odds", FEATURES.ET312odds.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/ET312level", FEATURES.ET312level.GetValue()) FEATURES.config.WriteInt("/LastRun/Features/RiderControl", FEATURES.RiderControl.GetSelection()) self.Destroy() class ControlsPanel(wx.Panel): global MAINFRAME, STATISTICS, FEATURES, PARALLELPORT, WINAMP, PICTURES mySec = -1 # force an initualization run = 0 # indicate which phase of operation is current unlock = 0 # user has choice of unlocking himself LastTime = 0 NOT_RUNNING = 1 FADE_IN = 2 def __init__(self, parent, id): wx.Panel.__init__(self, parent, -1) self.parent = parent gbs = self.gbs = wx.GridBagSizer(5, 50) # left side controls self.sliderPic = MySlideGauge(self, x=20, y=20, cur=20, min=1, max=60, label='Number of Seconds Each Picture is Displayed') self.sliderFade = MySlideGauge(self, x=20, y=135, cur=30, min=4, max=60, label='Number of Seconds for Fade In/Out') self.sliderOdds = MySlideControl(self, x=20, y=250, cur=30, min=0, max=100, label='Odds for Prompting Rider Control Input') self.sliderET312 = MySlideControl(self, x=20, y=365, cur=50, min=0, max=127, label='ET312 electric stimulation level') # right side controls self.sliderRun = MySlideGauge(self, x=410, y=20, cur=20, min=1, max=60, label='Number of Minutes of Picture Displaying') self.sliderPause = MySlideGauge(self, x=410, y=135, cur=10, min=1, max=60, label='Number of Minutes of Pausing') self.sliderUnlock = MySlideGauge(self, x=410, y=250, cur=60, min=1, max=480, label='Number of Minutes before Unlock') self.Ask = Ask(self) self.et312 = ET312(self) b = wx.Button(self, -1, "(RE)START", (20, 540)) self.Bind(wx.EVT_BUTTON, self.OnClickRun, b) b.SetToolTipString("Push to (re)start the system ") b = wx.Button(self, -1, "STOP", (120, 540)) self.Bind(wx.EVT_BUTTON, self.OnClickStop, b) b.SetToolTipString("Push to stop the system") b = wx.Button(self, -1, "EXIT", (220, 540)) self.Bind(wx.EVT_BUTTON, self.OnClickExit, b) b.SetToolTipString("Push to Exit the Program") self.ArmUnlock = wx.ToggleButton(self, -1, "Unlock Arm", (20, 500)) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnArm, self.ArmUnlock) self.Open_ET312 = wx.Button(self, -1, "Open ET312", (120, 500)) self.Bind(wx.EVT_BUTTON, self.OnOpen_ET312, self.Open_ET312) self.Adjust_ET312 = wx.Button(self, -1, "Adjust ET312", (220, 500)) self.Bind(wx.EVT_BUTTON, self.OnAdjust_ET312, self.Adjust_ET312) def punish(self): if( FEATURES.ET312odds.GetValue() ): self.sliderOdds.SetValue(self.sliderOdds.GetValue() + 1) if( FEATURES.ET312level.GetValue() ): self.sliderET312.SetValue(self.sliderET312.GetValue() + 1) def reward(self): if( FEATURES.ET312odds.GetValue() ): self.sliderOdds.SetValue(self.sliderOdds.GetValue() - 1, min=1) if( FEATURES.ET312level.GetValue() ): self.sliderET312.SetValue(self.sliderET312.GetValue() - 1) def Start(self): MAINFRAME.SecondCallback( self.OnSecond ) self.myMinute = MyMinutes(); self.myUnlock = MyMinutes(); def OnClickExit(self, event): MAINFRAME.Close(True) # Close the frame. self.parent.Venus2000.close() def OnClickStop(self, event): self.run = self.NOT_RUNNING self.mySec = -1 self.sliderFade.SetGauge(0) self.sliderPic.SetGauge(0) # adjust picture timer back to zero self.sliderRun.SetGauge(0) # adjust run timer back to zero WINAMP.command("pause") PICTURES.HideImages() PARALLELPORT.MonitorOff() self.parent.Venus2000.pause() wx.LogMessage( "Stop button pushed" ) def OnClickRun(self, event): self.run = self.FADE_IN self.mySec = -1 self.sliderFade.SetGauge(0) self.sliderPic.SetGauge(0) # adjust picture timer back to zero self.sliderRun.SetGauge(0) # adjust run timer back to zero self.sliderPause.SetGauge(0) self.sliderUnlock.SetGauge(0) unlock = 0 # user has choice of unlocking himself STATISTICS.NewStart() self.parent.Venus2000.open() wx.LogMessage( "(re)Start button pushed" ) def OnArm(self,event): v = self.ArmUnlock.GetValue() if( v ): PARALLELPORT.ArmUnlock() else: PARALLELPORT.ArmLock() def OnOpen_ET312(self,event): self.et312.open() def OnAdjust_ET312(self,event): self.et312.adjust() def Initial(self, minute, sec): # STEP 0 if( not sec ): MAINFRAME.SetStatusText( "", 1 ) if( FEATURES.SelfStart.GetValue() and PARALLELPORT.Buttons() == 3 ): PARALLELPORT.ArmUnlock() return # return letting mySec count up else: self.mySec = -1 # prevent mySec from counting up if sec > 10: PARALLELPORT.ArmLock() self.OnClickRun(0) return def NotRunning(self, minute, sec): # STEP 1 MAINFRAME.SetStatusText( "", 1 ) return def FadeIn(self,minute, sec): # STEP 2 if( not sec ): WINAMP.start() WINAMP.setVolume( 0 ) WINAMP.command("play") STATISTICS.NewCycle() MAINFRAME.SetStatusText( "Fade In", 1 ) if( sec > self.sliderFade.GetValue() ): self.sliderFade.SetGauge(0) # adjust fade timer back to zero self.parent.Venus2000.run() self.mySec = -1 # signal first cycle to next section self.run += 1 else: self.sliderFade.SetGauge(sec) p = 1.0 * sec / self.sliderFade.GetValue() if( p > 0.33 ): PARALLELPORT.MonitorOn() if( p > 0.66 ): PICTURES.ShowImages() WINAMP.setVolume( int(p * 64) ) def Running(self, minute, sec): # STEP 3 if( not sec ): # first cycle self.myMinute.clear() # start minute timer over PICTURES.NextPicture() STATISTICS.NextPicture() self.Ask.NewPic() MAINFRAME.SetStatusText( "Rinning", 1 ) elif( minute >= self.sliderRun.GetValue() # if run time's up and sec >= self.sliderPic.GetValue() ): # and current picture's time is up self.sliderPic.SetGauge(0) # adjust picture timer back to zero self.sliderRun.SetGauge(0) # adjust run timer back to zero PICTURES.NextPicture() # picture for fadeout-pause-fadein STATISTICS.NextPicture() self.mySec = -1 # signal first cycle to next section self.run += 1 # indicate next section is to get control else: self.sliderPic.SetGauge(sec) # adjust picture display timer self.sliderRun.SetGaugeTime( self.myMinute.now() ) # adjust run timer if( not self.parent.Venus2000.running() ): STATISTICS.VenusPauseTime() if( sec >= self.sliderPic.GetValue() ): # if current picture time's up PICTURES.NextPicture() STATISTICS.NextPicture() self.Ask.NewPic() self.mySec = 0 # signal first cycle to next section self.sliderPic.SetGauge(0) # adjust picture display timer else: if( FEATURES.VenusPause.GetValue() and PARALLELPORT.Buttons() == 3 ): if( self.parent.Venus2000.running() ): if(self.Ask.Message( "Pausing Venus 2000", zap=1) ): self.parent.Venus2000.pause() else: if(self.Ask.Message( "Restarting Venus 2000") ): self.parent.Venus2000.run() def FadeOut(self,minute, sec): # STEP 4 if( self.unlock ): if(PARALLELPORT.Buttons() == 3 ): PARALLELPORT.ArmUnlock() FEATURES.ArmUnlock.SetValue(1) PICTURES.ShowText( "You have been released", x=200, y=200) time.sleep(30) PARALLELPORT.ArmLock() FEATURES.ArmUnlock.SetValue(0) self.parent.parent.Close(True) # Close the frame. self.unlock = 0 return self.unlock -= 1 if( not self.unlock ): PICTURES.ClearText() self.myUnlock.clear() if( not sec ): self.parent.Venus2000.pause() MAINFRAME.SetStatusText( "Fade Out", 1 ) (m,s) = self.myUnlock.now() if( m >= self.sliderUnlock.GetValue() ): PICTURES.ShowText( "If you wish to be released, push both\n" "both buttons now, to extend your session\n" "do nothing. You have 5 seconds to decide.", x=200, y=200) self.unlock = 5 return if( sec > self.sliderFade.GetValue() ): WINAMP.command("pause") self.sliderFade.SetGauge(0) # adjust fade timer back to zero self.mySec = -1 # signal first cycle to next section self.run += 1 else: self.sliderFade.SetGauge(sec) p = 1.0 * sec / self.sliderFade.GetValue() if( p > 0.33 ): PICTURES.HideImages() if( p > 0.66 ): PARALLELPORT.MonitorOff() WINAMP.setVolume( 64 - int(p*64) ) def Pausing(self, minute, sec): # STEP 5 if( not sec ): # first cycle self.myMinute.clear() # start minute timer over self.LastAsk = 0 MAINFRAME.SetStatusText( "Pausing", 1 ) elif( sec # display at max for at least one second and minute >= self.sliderPause.GetValue() ): # last cycle self.sliderPause.SetGauge(0) # adjust pause timer back to zero self.mySec = -1 # signal first cycle to next section self.run = self.FADE_IN # indicate next section is to get control else: # other cycles if( FEATURES.VenusPauseStrokes.GetValue() and PARALLELPORT.Buttons() == 3 ): if( self.LastTime and self.LastTime < 25): self.Ask.Message( "Requested too soon", zap=1) # message will not be displayed else: self.parent.Venus2000.run() STATISTICS.VenusPauseStrokes() self.LastTime = 30 if( self.LastTime ): self.LastTime -= 1 if( self.parent.Venus2000.running() and (self.LastTime < 25)): self.parent.Venus2000.pause() self.sliderPause.SetGaugeTime( self.myMinute.now() ) # adjust pause timer def OnSecond(self,sec): # display next picture when time expires self.mySec += 1 # keep seconds up to date minute = self.myMinute.tick() # keep minute timer up to date self.myUnlock.tick() # keep unlock timer up to date self.sliderUnlock.SetGaugeTime( self.myUnlock.now() ) STATISTICS.ExecTime() ( self.Initial, self.NotRunning, self.FadeIn, self.Running, self.FadeOut, self.Pausing)[ self.run ](minute, self.mySec) class DirectoriesPanel(wx.Panel): hetDesc = '------------------Hetrosexual Pictures--------------------------' gayDesc = '---------------------Gay Pictures-------------------------------' het = ['Bishop Classics', 'Bishop Panels', 'Blow Jobs', 'Bondage', # 0-15 'Girls', 'Girl on Girl', 'Girls with Toys', 'Vallejo', 'BDSM', 'Drawings', 'Sex', 'Girls with Hair', 'Centerfolds', '', '', 'Share'] gay = ['BDSM', 'Blow Jobs', 'Piss', 'Sex', # 16-31 'Boys', 'Puppy', 'Spanking', 'Fisting', 'Drawings', '', '', '', '', '', '', ''] dirs = [r'Het\bishop', r'Het\bishop\panels', r'Het\BJs', r'Het\bondage', r'Het\girls', r'Het\Girl_girl', r'Het\girl_toys', r'vallejo', r'Het\BDSM', r'Het\drawings', r'Het\sex', r'Het\girl_hair', r'Het\Centerfolds', r'', r'', r'share', r'gay\BDSM', r'gay\BJs', r'gay\piss', r'gay\sex', r'gay\boys', r'gay\puppy', r'gay\spanking', r'gay\fisting', r'gay\drawings', r'', r'', r'', r'', r'', r'', r'' ] cb = range(32) # create LIST to hold checkbox objects change = 1 # true if a change has been made to the check boxes def __init__(self, parent, id): wx.Panel.__init__(self, parent, -1) self.parent = parent gbs = self.gbs = wx.GridBagSizer(5, 50) gbs.Add( wx.StaticText(self, -1, self.hetDesc), (2,2), (1,4), wx.ALIGN_CENTER | wx.ALL, 5) gbs.Add( wx.StaticText(self, -1, self.gayDesc), (10,2), (1,4), wx.ALIGN_CENTER | wx.ALL, 5) for x in range(0,4): for y in range(0,4): inx = x + 4*y cb = wx.CheckBox(self, -1, self.het[inx] ) self.cb[inx] = cb self.Bind(wx.EVT_CHECKBOX, self.OnCheckBox, cb) gbs.Add( cb, (y+3,x+2) ) cb = wx.CheckBox(self, -1, self.gay[inx] ) self.cb[inx+16] = cb self.Bind(wx.EVT_CHECKBOX, self.OnCheckBox, cb) gbs.Add( cb, (y+11,x+2) ) self.OnClickFavs(0) b = wx.Button(self, -1, "CLEAR ALL", (20, 80)) self.Bind(wx.EVT_BUTTON, self.OnClickClear, b) b.SetToolTipString("Push to Clear All Checkboxes") gbs.Add( b, (16,2) ) b = wx.Button(self, -1, "GIRL FAVS", (20, 80)) self.Bind(wx.EVT_BUTTON, self.OnClickFavs, b) b.SetToolTipString("Push to Set Favorites") gbs.Add( b, (16,3) ) b = wx.Button(self, -1, "DEFAULTS", (20, 80)) self.Bind(wx.EVT_BUTTON, self.OnClickDefs, b) b.SetToolTipString("Push to Set Defaults") gbs.Add( b, (16,4) ) self.SetSizerAndFit(gbs) # build a list of all JPGs in all selected directories, if a change has occurred def BuildFileList(self): files = [] # start with an empty list of files dlist = [] # start with an empty list of directories for x in range(32): # build a directory list if( self.cb[x].IsChecked() and self.dirs[x] ): dlist.append( self.dirs[x] ) for d in dlist: # build a file list dir = os.path.join( r'pictures', d) for f in os.listdir( dir ): if( f.endswith( r'.jpg' )): files.append( os.path.join(dir, f) ) self.change = 0 # show no changes since last file list rebuild return files def OnCheckBox(self, event): self.change = 1 def OnClickClear(self, event): for cbx in self.cb: cbx.SetValue(0) self.change = 1 def OnClickFavs(self, event): for x in range(16): if( x in [2, 4, 5, 6, 10, 12]): self.cb[x].SetValue(1) else: self.cb[x].SetValue(0) self.change = 1 def OnClickDefs(self, event): for x in range(16): if( x in [0]): self.cb[x].SetValue(1) else: self.cb[x].SetValue(0) self.change = 1 def HasChanged(self): return self.change class PicturesPanel(wx.Panel): files = [] shown = 0 # setup the notebook page as well as the frame on the second monitor def __init__(self, parent, id): self.parent = parent wx.Panel.__init__(self, parent, -1) self.SetBackgroundColour( "Black" ); self.Image = wx.StaticBitmap(self, bitmap=wx.EmptyBitmap(800, 600)) self.Text = wx.StaticText(self, -1, "") self.Text.Show(0) self.Viewer = wx.Frame(parent, -1, '', pos=(1400,0), size=(800,600), style=wx.SIMPLE_BORDER, name="Picture Viewer") self.Viewer.SetBackgroundColour( "Black" ); self.Viewer.Image = wx.StaticBitmap(self.Viewer, bitmap=wx.EmptyBitmap(800, 600)) self.Viewer.Show(1) self.Viewer.Text = wx.StaticText(self.Viewer, -1, "") self.Viewer.Text.Show(0) self.shown = 1 # get next picture and display it both places def NextPicture(self): if( self.parent.directories.HasChanged() ): self.files = self.parent.directories.BuildFileList() if( len( self.files) < 10 ): return; f = random.sample( self.files, 1 ) # ramdomly select the next picture MAINFRAME.SetStatusText( f[0], 0 ) jpg = wx.Image( f[0], wx.BITMAP_TYPE_JPEG) w = (800 - jpg.GetWidth()) / 2 h = (600 - jpg.GetHeight()) / 2 jpg.Resize((800,600),(w,h), 0, 0, 0) # add borders if needed, center in area self.Image.SetBitmap( wx.BitmapFromImage( jpg )) # place on notebook page self.Viewer.Image.SetBitmap( wx.BitmapFromImage( jpg )) # place on second monitor's frame # show both pictures, unless already shown def ShowImages(self): if( not self.shown ): self.shown = 1 self.Image.Show(1) self.Viewer.Image.Show(1) # hide both pictures, display a back screen (assuming text is also off) def HideImages(self): if( self.shown ): self.shown = 0 self.Image.Show(0) self.Viewer.Image.Show(0) # display text of the given font size, and color def ShowText(self, words, size=12, color="White", x=-1, y=0): L = 0.8 * size * len(words) if( x == -1 ): # default positioning x = int( 400.0 - ( L/2.0) ) y = 300 if( x == -2 ): # random positioning x = int( 25 + (750-L)*random.random()) y = int( 50 + 470*random.random()) font = wx.Font(size, wx.MODERN, wx.NORMAL, wx.BOLD) self.Text.SetFont( font ); self.Text.SetForegroundColour( color ); self.Text.SetLabel( words ) self.Text.MoveXY( x, y) self.Text.Show( 1 ) self.Viewer.Text.SetFont( font ); self.Viewer.Text.SetForegroundColour( color ); self.Viewer.Text.SetLabel( words ) self.Viewer.Text.MoveXY( x, y) self.Viewer.Text.Show( 1 ) # remove text, if any from the screens def ClearText(self): self.Text.Show(0) self.Viewer.Text.Show(0) class StatisticsPanel(wx.Panel): headers = ('Item Description', 'This Cycle', 'Since Start', 'All Starts') ## description conversion -values- handles items = [ [ 'Program Execution Time', 1, 0,0,0, 0,0,0], [ 'Number of Pictures', 0, 0, 0, 0, 0,0,0 ], [ 'Number of Button Prompts', 0, 0, 0, 0, 0,0,0 ], [ 'Number of Failures to Obey', 0, 0, 0, 0, 0,0,0 ], [ 'Venus Pauses While Running Time', 1, 0, 0, 0, 0,0,0 ], [ 'Venus Pause Strokes Requested', 0, 0, 0, 0, 0,0,0 ] ] def __init__(self, parent, id): wx.Panel.__init__(self, parent, -1) self.parent = parent hfont = wx.Font(9, wx.FONTFAMILY_MODERN,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_BOLD, underline=True) ifont = wx.Font(9, wx.FONTFAMILY_MODERN,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL, underline=False) gbs = self.gbs = wx.GridBagSizer(5, 50) for i in range( len(self.headers) ): t = wx.StaticText(self, -1, self.headers[i] ) t.SetFont( hfont ) if i == 0: gbs.Add( t, (1,1), (1,2), wx.ALIGN_LEFT | wx.ALL, 5) else: gbs.Add( t, (1,(i+2)), (1,1), wx.ALIGN_LEFT | wx.ALL, 5) for i in range( len(self.items) ): t = wx.StaticText(self, -1, self.items[i][0]) t.SetFont( ifont ) gbs.Add( t, (i+2,1), (1,2), wx.ALIGN_LEFT | wx.ALL, 5) for j in (2,3,4): s = self.convert( i, j ) st = self.items[i][j+3] = wx.StaticText(self, -1, s) gbs.Add( st, (i+2,j+1), (1,1), wx.ALIGN_RIGHT | wx.ALL, 5) self.SetSizerAndFit(gbs) def number( self, v ): s = "%d" % v return s.rjust(6) def time( self, v): h = v / 3600 x = v % 3600 m = x / 60 s = x % 60 return "%02d:%02d:%02d" % (h,m,s) def convert( self, i, j ): return (self.number, self.time)[self.items[i][1]](self.items[i][j]) def NewCycle( self ): # call in FadeIn for i in range( len(self.items) ): self.items[i][2] = 0 s = self.convert( i, 2 ) self.items[i][5].SetLabel( s ) def NewStart( self ): # Call when Restart button pushed for i in range( len(self.items) ): self.items[i][2] = 0 self.items[i][3] = 0 s = self.convert(i, 2) # zero string is the same for both self.items[i][5].SetLabel( s ) self.items[i][6].SetLabel( s ) def ExecTime( self ): i = 0 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) def NextPicture( self ): i = 1 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) def ButtonText( self ): i = 2 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) def FailuresToObey( self ): i = 3 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) def VenusPauseTime( self ): i = 4 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) def VenusPauseStrokes( self ): i = 5 for j in (2,3,4): self.items[i][j] += 1 self.items[i][j+3].SetLabel( self.convert(i, j) ) class FeaturesPanel(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, -1) self.parent = parent self.config = wx.Config(appName="ControlledSensations") gbs = self.gbs = wx.GridBagSizer(5, 50) self.Venus = wx.CheckBox(self, -1, "Venus 2000 is Connected via Serial Port:" ) v = self.config.ReadInt("/LastRun/Features/Venus", defaultVal=1) self.Venus.SetValue(v) gbs.Add( self.Venus, (2, 2) ) v = self.config.Read("/LastRun/Features/VenusPort", defaultVal="COM4") self.VenusPort = wx.TextCtrl(self, value=v) gbs.Add( self.VenusPort, (2,3)) self.VenusPause = wx.CheckBox(self, -1, "Rider is Allowed to Pause Venus" ) v = self.config.ReadInt("/LastRun/Features/VenusPause", defaultVal=1) self.VenusPause.SetValue(v) gbs.Add( self.VenusPause, (3, 2) ) self.VenusPauseStrokes = wx.CheckBox(self, -1, "Rider can Request Strokes During Pause" ) v = self.config.ReadInt("/LastRun/Features/VenusPauseStrokes", defaultVal=1) self.VenusPauseStrokes.SetValue(v) gbs.Add( self.VenusPauseStrokes, (4, 2) ) self.SelfStart = wx.CheckBox(self, -1, "Rider can Start the Session Using Buttons" ) v = self.config.ReadInt("/LastRun/Features/SelfStart", defaultVal=1) self.SelfStart.SetValue(v) gbs.Add( self.SelfStart, (5, 2) ) self.ET312 = wx.CheckBox(self, -1, "ET312 is Connected via Serial Port:" ) v = self.config.ReadInt("/LastRun/Features/ET312", defaultVal=1) self.ET312.SetValue(v) gbs.Add( self.ET312, (7, 2) ) v = self.config.Read("/LastRun/Features/ET312Port", defaultVal="COM1") self.ET312Port = wx.TextCtrl(self, value=v) gbs.Add( self.ET312Port, (7,3)) self.ET312odds = wx.CheckBox(self, -1, "Automatically Adjust ET312 odds" ) v = self.config.ReadInt("/LastRun/Features/ET312odds", defaultVal=1) self.ET312odds.SetValue(v) gbs.Add( self.ET312odds, (8, 2) ) self.ET312level = wx.CheckBox(self, -1, "Automatically Adjust ET312 level" ) v = self.config.ReadInt("/LastRun/Features/ET312level", defaultVal=1) self.ET312level.SetValue(v) gbs.Add( self.ET312level, (9, 2) ) t = wx.StaticText(self, -1, label="Rider has the Following Type of Input Device:" ) gbs.Add( t, (11, 2) ) self.RiderControl = wx.Choice(self, -1, choices=("none", "2 buttons") ) v = self.config.ReadInt("/LastRun/Features/RiderControl", defaultVal=0) self.RiderControl.SetSelection(v) gbs.Add( self.RiderControl, (11, 3) ) self.Winamp = wx.CheckBox(self, -1, "Use Winamp for music:" ) v = self.config.ReadInt("/LastRun/Features/Winamp", defaultVal=1) self.Winamp.SetValue(v) gbs.Add( self.Winamp, (12, 2) ) self.SetSizerAndFit(gbs) class LogPanel(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, -1) self.parent = parent font = wx.Font(9, wx.FONTFAMILY_MODERN,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL, underline=False) t = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE, size=(790,590) ) t.SetFont( font ) self.DEFAULT_LOG_TARGET = wx.Log.SetActiveTarget( wx.LogTextCtrl( t ) ) def error(self, msg): x = wx.Log.SetActiveTarget( self.DEFAULT_LOG_TARGET ); wx.LogError( msg ) wx.Log.SetActiveTarget( x ) def message(self, msg): x = wx.Log.SetActiveTarget( self.DEFAULT_LOG_TARGET ); wx.LogMessage( msg ) wx.Log.SetActiveTarget( x ) class MainNoteBook(wx.Notebook): def __init__(self, parent, id): global FEATURES, CONTROLS, STATISTICS, PICTURES, LOG wx.Notebook.__init__(self, parent, id, size=(21,21), style= wx.BK_DEFAULT #wx.BK_TOP #wx.BK_BOTTOM #wx.BK_LEFT #wx.BK_RIGHT # | wx.NB_MULTILINE ) self.parent = parent FEATURES = FeaturesPanel(self,-1) self.AddPage(FEATURES, "Features") CONTROLS = ControlsPanel(self,-1) self.AddPage(CONTROLS, "Controls") self.directories = DirectoriesPanel(self,-1) self.AddPage(self.directories, "Directories") PICTURES = PicturesPanel(self, -1) self.AddPage(PICTURES, "Pictures") STATISTICS = StatisticsPanel(self, -1) self.AddPage(STATISTICS, "Statistics") LOG = LogPanel(self, -1) self.AddPage(LOG, "Log") wx.LogMessage( "Controlled Sensations Application Started" ) self.Venus2000 = Venus2000(self) PARALLELPORT.ArmLock() CONTROLS.Start() #---------------------------------------------------------------------------- app = wx.PySimpleApp() MAINFRAME=MainWindow(None, wx.ID_ANY, 'Controlled Sensations - Teasing Strokes') notebook = MainNoteBook(MAINFRAME, wx.ID_ANY) app.MainLoop()