Advertisement
Guest User

Untitled

a guest
May 22nd, 2019
172
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.33 KB | None | 0 0
  1. import serial
  2. import time
  3. import serial.tools.list_ports
  4. from comtypes import CLSCTX_ALL
  5. from ctypes import cast, POINTER
  6. from pycaw.pycaw import ISimpleAudioVolume, AudioUtilities, IAudioEndpointVolume
  7.  
  8.  
  9. '''
  10. To do:
  11. '''
  12.  
  13.  
  14.  
  15. # All Setup
  16.  
  17. SERIALNUMBER = 494283 # This needs to be assigned to the CH340B so it knows what to connect to
  18. BAUD = 115200 # Needs to be the same as the board BAUDRATE
  19. HANDSHAKE = '@' # Probably not necessary to have a handshake procedure
  20.  
  21. # It simplifies things greatly by having all these as globals
  22. OPEN_SESSIONS = list() # Audio session objects
  23. SESSION_NAMES = list() # Cleaned names ready to be sent to the board
  24. SESSION_PIDS_GLOBAL = list()
  25.  
  26. AUDIO_ENDPOINT = None # Will eventually be used as a pointer to the audio endpoint
  27. SESSIONS_CHANGED_FLAG = True
  28.  
  29. IDENTS = ['z', 'z']
  30. CHANGE_MARKERS = ['#', '$']
  31. VOLUME_BREAK = '!'
  32. VOLUME_REQUEST = '^'
  33. EMPTY_NAME = '-----' # What will be printed on screen if there is no session to go there
  34.  
  35. VOLUMES = [0, 0, 0]
  36.  
  37. LASTVOLUME = [0, 0, 0] # Used as a check to reduce the amount of volume changes
  38.  
  39. VOLUME_POLL_TIME = 1 / 10 # Seconds between volume requests to the board, denominator is essentially a polling rate
  40. VOLUME_CHECKED_TIME = time.time()
  41. AUDIO_ENDPOINT_REFRESH = 5 # Seconds between each check to see if PC audio endpoint has changed
  42. ENDPOINT_CHECKED_TIME = time.time()
  43. SESSION_REFRESH = 2 # Seconds between check to see if the the open audio sessions has changed
  44. SESSION_CHECKED_TIME = time.time()
  45.  
  46.  
  47. '''
  48. Looks for and connects to the mixer via serial number programmed into CH340 chip
  49. Takes no arguments but uses globals of baudrate, serial number, and handshake character
  50. Sends the serial object to the global SERIAL_COM
  51. DONE
  52. '''
  53. def ConnectMixer():
  54.  
  55. global SERIAL_COM
  56.  
  57. print('Trying to connect')
  58.  
  59. confir = 'f' # Used to recieve handshake from board
  60. connected = False
  61. between_tries = 2 # Seconds between connection attempts
  62.  
  63.  
  64. while connected == False: # Keeps going until the board is connected
  65.  
  66. open_ports = serial.tools.list_ports.comports() # Gets a new list of com ports each time
  67.  
  68. for port in open_ports:
  69. device_serial = port.serial_number
  70.  
  71. try:
  72. device_serial = int(device_serial)
  73. if (SERIALNUMBER == device_serial): # If it is the mixer board
  74. print("Found board")
  75. com_channel = port.device # Grabs the com port address, ie 'COM3'
  76.  
  77. except TypeError:
  78. None
  79.  
  80.  
  81. try:
  82. SERIAL_COM = serial.Serial(com_channel, BAUD, timeout = 0) # Connects to port
  83. connected = True
  84. print("Connected")
  85.  
  86. except:
  87. print('Not connected trying again in %1.1f seconds' % between_tries )
  88. print('This may be because the board is already connected to some other serial communication')
  89. time.sleep(between_tries)
  90.  
  91.  
  92.  
  93. while (confir != HANDSHAKE):
  94.  
  95. print('Waiting for handshake')
  96. SERIAL_COM.write(HANDSHAKE.encode())
  97. time.sleep(0.1)
  98. confir = SERIAL_COM.read().decode()
  99. SERIAL_COM.reset_output_buffer() # Clears the handshake and rewrites it if there is no response
  100.  
  101.  
  102. SERIAL_COM.reset_output_buffer()
  103. SERIAL_COM.reset_input_buffer()
  104.  
  105. print('Recieved handshake')
  106.  
  107. return None
  108.  
  109.  
  110. '''
  111. Gets session objects list and a cleaned name list
  112. Removes any sessions without a name or process
  113. Takes no arguments
  114. Will set SESSIONS_CHANGED_FLAG if there is a change in the open audio sessions
  115. Returns none, but writes the lists to the global variables of OPEN_SESSIONS and SESSION_NAMES
  116. DONE
  117. '''
  118. def AudioSessions():
  119.  
  120. global OPEN_SESSIONS, SESSION_NAMES, SESSIONS_CHANGED_FLAG, SESSION_CHECKED_TIME, SESSION_PIDS_GLOBAL
  121.  
  122. current_sessions = AudioUtilities.GetAllSessions() # Gets a list of all current audio session objects
  123. cleaned_sessions = []
  124. session_pids = []
  125.  
  126. print(current_sessions, len(current_sessions))
  127.  
  128.  
  129. for session in current_sessions:
  130.  
  131. if (session.Process != None):
  132. cleaned_sessions.append(session)
  133. session_pids.append(session.Process.pid)
  134.  
  135. else:
  136.  
  137. None
  138.  
  139.  
  140. print(session_pids, SESSION_PIDS_GLOBAL)
  141. if session_pids != SESSION_PIDS_GLOBAL: # Will only change things if it is different to what is already there
  142.  
  143. print("Sessions changed")
  144. SESSIONS_CHANGED_FLAG = True
  145.  
  146. SESSION_PIDS_GLOBAL = session_pids
  147.  
  148. OPEN_SESSIONS.clear()
  149. SESSION_NAMES.clear()
  150.  
  151. for session in cleaned_sessions: # Steps through the audio sessions
  152.  
  153. name = session.Process.name() # Reads name of session
  154. name = name[0:-4] # Takes the .exe off the name
  155. name = name[: 16] # Ensures the name is not too long for the screen
  156. OPEN_SESSIONS.append(session.SimpleAudioVolume) # List of the session objects, these are what are used to change the volume
  157. SESSION_NAMES.append(name.capitalize()) # List of the session names, capitalized
  158.  
  159.  
  160. SESSION_CHECKED_TIME = time.time()
  161.  
  162. return None
  163.  
  164.  
  165. '''
  166. Goes through and sees if any volume values need to be changed and then changes them
  167. Will not change the volume if the new value is within 1% of the old one
  168. Each encoder step is 2% if RMOD_X2 is set (or 1% with RMOD_X1)
  169. Uses both global volume lists and the global idents list as well as the global AUDIO_ENDPOINT
  170. Returns none
  171. DONE
  172. '''
  173. def SetVolumes():
  174.  
  175. global LASTVOLUME
  176.  
  177. for channel in range(0, 3):
  178.  
  179. if (abs(LASTVOLUME[channel] - VOLUMES[channel]) > 1): # Try and reduce the amount of times the volume is changed
  180.  
  181. LASTVOLUME[channel] = VOLUMES[channel]
  182.  
  183. if (channel == 2): # Master volume
  184.  
  185. AUDIO_ENDPOINT.SetMasterVolumeLevelScalar(VOLUMES[2] / 100, None)
  186.  
  187. else:
  188.  
  189. id = IDENTS[channel]
  190.  
  191. if (id != 'z'):
  192. OPEN_SESSIONS[id].SetMasterVolume(VOLUMES[channel] / 100, None) # Volume value is 0 - 1
  193.  
  194. return None
  195.  
  196.  
  197. '''
  198. Changes the active programs being managed
  199. The channel for which the program is being changed
  200. Uses the global lists of session names and objects
  201. Changes the values in the global IDENTS list
  202. 'z' is used as an identifier for when there is less then 2 open sessions and the identifier is empty
  203. Returns none
  204. DONE
  205. '''
  206. def ChangeIdents(channel):
  207.  
  208. global IDENTS
  209.  
  210. print("Changing idents")
  211. if (len(SESSION_NAMES) == 0):
  212. IDENTS = ['z', 'z']
  213.  
  214. elif (len(SESSION_NAMES) == 1):
  215. IDENTS = [0, 'z']
  216.  
  217. else:
  218.  
  219. if (IDENTS[channel] == 'z'):
  220. IDENTS[channel] = 0 # If the ident was previously empty then gives it a channel to allow next part to work
  221.  
  222. id = (IDENTS[channel] + 1) % len(SESSION_NAMES) # Increments IDENT by 1
  223.  
  224. if (id == IDENTS[channel - 1]): # Checks if IDENT is already being used and if it is changes it
  225.  
  226. id = (IDENTS[channel] + 2) % len(SESSION_NAMES)
  227.  
  228.  
  229. IDENTS[channel] = id
  230.  
  231. print("Controlling")
  232. print(SESSION_NAMES)
  233. print(IDENTS)
  234.  
  235. return None
  236.  
  237.  
  238. '''
  239. Sends the name of the program and its volume to the board to be displayed
  240. Takes the channel being changed and a boolean of whether the change marker is expected by the board
  241. If the board requested the channel change then the marker prefix is unnecessary but if the PC has decided to change
  242. then the prefix is used to tell the board to expect a name + volume which is read from the session and put into VOLUMES
  243. Returns none
  244. DONE
  245. '''
  246. def SendSessionName(channel, marker_required):
  247.  
  248. global VOLUMES
  249.  
  250. if (marker_required):
  251.  
  252. SERIAL_COM.write(CHANGE_MARKERS[channel].encode())
  253.  
  254. id = IDENTS[channel]
  255.  
  256. if (id == 'z'):
  257.  
  258. SERIAL_COM.write(EMPTY_NAME.encode())
  259. SERIAL_COM.write(CHANGE_MARKERS[channel].encode())
  260. SERIAL_COM.write(str(VOLUMES[channel]).encode())
  261.  
  262.  
  263. else:
  264.  
  265. SERIAL_COM.write(SESSION_NAMES[id].encode())
  266.  
  267. SERIAL_COM.write(CHANGE_MARKERS[channel].encode())
  268.  
  269. VOLUMES[channel] = str((OPEN_SESSIONS[id].GetMasterVolume()) * 100)
  270.  
  271. SERIAL_COM.write(VOLUMES[channel].encode())
  272.  
  273.  
  274. SERIAL_COM.write(VOLUME_BREAK.encode())
  275.  
  276. return None
  277.  
  278.  
  279. '''
  280. If a new audio device is plugged in then there will be a change in the audio endpoint and this will need to be monitored for
  281. This function sees if the endpoint is the same as before
  282. If the endpoint has changed it sets it to whatever the PC is currently outputting to
  283. Also forces a volume change of the master volume if the endpoint changes because windows does not carry over the volume
  284. Returns none
  285. DONE
  286. '''
  287. def CheckAndSetAudioEndpoint():
  288.  
  289. global AUDIO_ENDPOINT, ENDPOINT_CHECKED_TIME
  290.  
  291. device = AudioUtilities.GetSpeakers()
  292. interface = device.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
  293. new_endpoint = cast(interface, POINTER(IAudioEndpointVolume))
  294.  
  295. if (new_endpoint != AUDIO_ENDPOINT):
  296.  
  297. AUDIO_ENDPOINT = new_endpoint
  298. AUDIO_ENDPOINT.SetMasterVolumeLevelScalar(VOLUMES[2] / 100, None)
  299.  
  300. ENDPOINT_CHECKED_TIME = time.time()
  301. return None
  302.  
  303.  
  304. '''
  305. Requests a list of volumes from the board using VOLUME_REQUEST
  306. Expects a list of 3 volumes between 0 and 100 prefixed, suffixed and broken by VOLUME_BREAK
  307. Writes these volumes into VOLUMES
  308. Returns True if volumes were recieved and false if some other command was recieved
  309. NEED TO GET TIMINGS
  310. DONE OTHERWISE
  311. '''
  312. def RequestVolumes():
  313.  
  314. global VOLUMES, VOLUME_CHECKED_TIME
  315.  
  316. VOLUME_CHECKED_TIME = time.time()
  317.  
  318. SERIAL_COM.write(VOLUME_REQUEST.encode())
  319. request_sent = time.time()
  320. while (SERIAL_COM.in_waiting == 0 and (time.time() - request_sent < 0.05)):
  321. None # Waiting for a response
  322.  
  323. volume_string = SERIAL_COM.readline().decode()
  324.  
  325. if (CHANGE_MARKERS[0] in volume_string):
  326.  
  327. print("From request")
  328. ChangeIdents(0)
  329.  
  330.  
  331. elif (CHANGE_MARKERS[1] in volume_string):
  332.  
  333. print("From request")
  334. print(volume_string)
  335. ChangeIdents(1)
  336.  
  337.  
  338. elif (HANDSHAKE in volume_string):
  339.  
  340. None
  341.  
  342. else:
  343.  
  344. try:
  345. volume_string = volume_string.strip(VOLUME_BREAK).split(VOLUME_BREAK)
  346. VOLUMES = [ int(volume) for volume in volume_string ]
  347. print(VOLUMES)
  348. return True
  349.  
  350. except:
  351. None
  352.  
  353.  
  354. return False
  355.  
  356.  
  357.  
  358. '''
  359. Will only run on first connection or if the board is disconnected and reconnected
  360. The loop will stay in this function (actually in ConnectMixer) until serial com is working
  361. Gets everything set up
  362. Returns None
  363. MAY NEED TO GET SOME TIMINGS
  364. DONE OTHERWISE
  365. '''
  366. def Initilize():
  367.  
  368. global SESSION_CHECKED_TIME, ENDPOINT_CHECKED_TIME, VOLUME_CHECKED_TIME
  369.  
  370. # Connects board and prepares sessions and endpoint
  371. ConnectMixer()
  372. AudioSessions()
  373. SESSION_REFRESH = time.time()
  374.  
  375. RequestVolumes() # This is basically only to get the master volume to then apply to the endpoint
  376. VOLUME_CHECKED_TIME = time.time()
  377.  
  378. CheckAndSetAudioEndpoint()
  379. ENDPOINT_CHECKED_TIME = time.time()
  380.  
  381. # Sets sessions being controlled and sends their names and current volumes
  382.  
  383. ChangeIdents(0)
  384. SendSessionName(0, True)
  385. ChangeIdents(1)
  386. SendSessionName(1, True)
  387.  
  388. return None
  389.  
  390.  
  391. '''
  392. Main loop function that will run constantly
  393. Needs to:
  394. check board is connected
  395. if disconnected to do initial setup again (This will also reconnect board)
  396. request volumes
  397. set volumes
  398. keep track of endpoints
  399. keep track of open sessions
  400. DONE
  401. '''
  402. def Main():
  403.  
  404. global SESSIONS_CHANGED_FLAG
  405.  
  406. try:
  407.  
  408. if (SERIAL_COM.in_waiting != 0):
  409.  
  410. print(SERIAL_COM.in_waiting)
  411.  
  412. data = SERIAL_COM.read().decode()
  413.  
  414. if (data == CHANGE_MARKERS[0]):
  415.  
  416. ChangeIdents(0)
  417. SendSessionName(0, False)
  418.  
  419. elif (data == CHANGE_MARKERS[1]):
  420.  
  421. ChangeIdents(1)
  422. SendSessionName(1, False)
  423.  
  424. else:
  425.  
  426. data = SERIAL_COM.readline()
  427. print(data)
  428.  
  429.  
  430. elif ((time.time() - VOLUME_CHECKED_TIME) >= VOLUME_POLL_TIME):
  431.  
  432. flag = RequestVolumes()
  433.  
  434. if (flag == True):
  435. SetVolumes()
  436.  
  437. elif ((time.time() - ENDPOINT_CHECKED_TIME) >= AUDIO_ENDPOINT_REFRESH):
  438.  
  439. CheckAndSetAudioEndpoint()
  440.  
  441. elif ((time.time() - SESSION_CHECKED_TIME) >= SESSION_REFRESH):
  442.  
  443. AudioSessions()
  444.  
  445. if (SESSIONS_CHANGED_FLAG == True):
  446.  
  447. # Sets sessions being controlled and sends their names and current volumes
  448. ChangeIdents(0)
  449. SendSessionName(0, True)
  450. ChangeIdents(1)
  451. SendSessionName(1, True)
  452.  
  453. SESSIONS_CHANGED_FLAG = False
  454.  
  455. else:
  456.  
  457. None
  458.  
  459.  
  460.  
  461. except serial.SerialException:
  462.  
  463. print("Serial error")
  464. Initilize()
  465. SESSIONS_CHANGED_FLAG = False
  466.  
  467. except Exception as e:
  468. print(e)
  469.  
  470. return None
  471.  
  472.  
  473. Initilize()
  474.  
  475. while True:
  476.  
  477. try:
  478. Main()
  479.  
  480. except Exception as e:
  481. print(e)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement