Last active
January 30, 2021 03:28
-
-
Save micolous/f03267195203cb47832096bf238893bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Input Pull-up Serial + Debounce | |
This example demonstrates the use of pinMode(INPUT_PULLUP). It reads a digital | |
input on pin 2 and prints the results to the Serial Monitor. | |
The circuit: | |
- momentary switch attached from pin 2 to ground | |
- built-in LED on pin 13 | |
Unlike pinMode(INPUT), there is no pull-down resistor necessary. An internal | |
20K-ohm resistor is pulled to 5V. This configuration causes the input to read | |
HIGH when the switch is open, and LOW when it is closed. | |
created 14 Mar 2012 | |
by Scott Fitzgerald | |
modified Jan 2021 by Michael Farrell <micolous+git@gmail.com> | |
This example code is in the public domain. | |
http://www.arduino.cc/en/Tutorial/InputPullupSerial | |
*/ | |
const int buttonPin = 2; // the number of the pushbutton pin | |
const int ledPin = 13; // the number of the LED pin | |
int ledState = HIGH; // the current state of the output pin | |
int buttonState; // the current reading from the input pin | |
int lastButtonState = LOW; // the previous reading from the input pin | |
// the following variables are unsigned longs because the time, measured in | |
// milliseconds, will quickly become a bigger number than can be stored in an int. | |
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled | |
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers | |
void setup() { | |
//start serial connection | |
Serial.begin(9600); | |
//configure pin 2 as an input and enable the internal pull-up resistor | |
pinMode(buttonPin, INPUT_PULLUP); | |
pinMode(ledPin, OUTPUT); | |
} | |
void loop() { | |
//read the pushbutton value into a variable | |
int sensorVal = digitalRead(2); | |
// check to see if you just pressed the button | |
// (i.e. the input went from LOW to HIGH), and you've waited long enough | |
// since the last press to ignore any noise: | |
// If the switch changed, due to noise or pressing: | |
if (sensorVal != lastButtonState) { | |
// reset the debouncing timer | |
lastDebounceTime = millis(); | |
} | |
if ((millis() - lastDebounceTime) > debounceDelay) { | |
// whatever the reading is at, it's been there for longer than the debounce | |
// delay, so take it as the actual current state: | |
// if the button state has changed: | |
if (sensorVal != buttonState) { | |
buttonState = sensorVal; | |
// Keep in mind the pull-up means the pushbutton's logic is inverted. It goes | |
// HIGH when it's open, and LOW when it's pressed. | |
Serial.print(buttonState == LOW ? "1" : "0"); | |
ledState = buttonState == LOW; | |
} | |
} | |
// set the LED: | |
digitalWrite(ledPin, ledState); | |
// save the reading. Next time through the loop, it'll be the lastButtonState: | |
lastButtonState = sensorVal; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
footswitch_client | |
Copyright 2021 Michael Farrell <micolous+git@gmail.com> | |
SPDX-License-Identifier: Apache-2.0 | |
This listens to a serial device for two possible bytes: | |
0: the footswitch is released | |
1: the footswitch is pressed | |
The hardware side of this uses an Arduino Nano to report on the state of | |
shorting one of it's digital GPIO pins to GND, with some de-bounce logic in | |
there. | |
This software only runs on Windows. Sorry :( | |
This listens to the serial device, and will press a key (F13, set in KEY | |
constant) while the footswitch is held. You set this in something like Discord | |
as your push-to-talk key. | |
If you also have pycaw with a small patch (still working on upstreaming this), | |
it will also control a microphone device of your choice (MICROPHONE constant). | |
This has some additional, slower de-bounce logic so that it only mutes your | |
microphone HOLD_TIME_SEC seconds after you release the footswitch. | |
On the Audio-Technica USB microphone, it constantly emits a monitor output on | |
the headphone jack which follows the mute state of the microphone level. When | |
that's muted, the LED turns amber, and you can't hear yourself anymore through | |
the monitor output. When it's unmuted, the LED turns green, and you can hear | |
yourself once again. | |
Having discrete microphone control means: | |
1. Apps that don't support push-to-talk now get push-to-talk! | |
2. You won't hear yourself on the monitor output whenever you're not holding | |
the footswitch, so aren't talking without PTT pressed. | |
If you don't have a microphone with monitor output, or don't have pycaw, you | |
don't need this -- it's all optional. | |
""" | |
import sched | |
import serial | |
import threading | |
import time | |
import win32api | |
import win32con | |
try: | |
from pycaw import pycaw | |
except ImportError: | |
pycaw = None | |
MICROPHONE = 'AT USB Microphone' | |
PORT = 'hwgrep://USB-SERIAL CH340' | |
KEY = win32con.VK_F13 | |
HOLD_TIME_SEC = .25 # for microphone mute only | |
class MicController: | |
"""Controller for the microphone device. | |
This operates a background thread to de-bounce mute events. This lets you | |
release the footswitch, and it doesn't actually mute until HOLD_TIME_SEC | |
seconds have passed. | |
If the footswitch is pressed again before the release timer, the scheduled | |
mute operation is aborted. | |
""" | |
def __init__(self): | |
self._sched = sched.scheduler() | |
self.keep_running = True | |
self.thread = None | |
self._release_at = 0 | |
self._dev = None | |
if pycaw is None or not MICROPHONE: | |
# Pycaw not available, just do nothing... | |
print('Microphone support disabled.') | |
return | |
mic_found = False | |
for device in pycaw.AudioUtilities.GetAllDevices(): | |
name = device.FriendlyName | |
if name is None: | |
continue | |
if name.startswith(MICROPHONE): | |
print(f'Controlling microphone: {name}') | |
self._dev = device | |
break | |
if self._dev is None: | |
print(f'Microphone not found: {MICROPHONE}') | |
def unmute(self): | |
"""Unmutes the microphone, and cancels any pending mute operation.""" | |
self._release_at = 0 | |
self._set_device_mute(False) | |
def mute(self): | |
"""Mutes the microphone after HOLD_TIME_SEC seconds.""" | |
release_at = time.monotonic_ns() | |
self._sched.enter(HOLD_TIME_SEC, 1, self._actual_mute, (release_at,)) | |
self._release_at = release_at | |
def _actual_mute(self, release_at): | |
if self._release_at == release_at: | |
self._release_at = 0 | |
self._set_device_mute(True) | |
def _set_device_mute(self, state): | |
if self._dev is None: | |
return | |
try: | |
self._dev.EndpointVolume.SetMute(state, None) | |
except Exception as e: | |
print(f'Error setting mute state: {e}') | |
def _worker(self): | |
while self.keep_running: | |
self._sched.run() | |
time.sleep(HOLD_TIME_SEC) | |
def start(self): | |
"""Spawns a thread to de-bounce mute operations.""" | |
if self._dev is None: | |
return | |
self.thread = threading.Thread(target=self._worker) | |
self.thread.start() | |
def stop(self): | |
"""Stops the thread that handles de-bounces mute operations.""" | |
if self.thread is None: | |
return | |
self.keep_running = False | |
self.thread.join() | |
self.thread = None | |
def pumpit(): | |
s = None | |
mic_controller = MicController() | |
mic_controller.start() | |
try: | |
s = serial.serial_for_url(PORT, timeout=1) | |
print('Waiting for events...') | |
# wait for events... | |
while True: | |
m = s.read(1) | |
if m == b'': | |
# no event, keep waiting... | |
continue | |
elif m == b'0': | |
# release | |
win32api.keybd_event(KEY, 0, win32con.KEYEVENTF_KEYUP, 0) | |
mic_controller.mute() | |
elif m == b'1': | |
# press | |
win32api.keybd_event(KEY, 0, 0, 0) | |
mic_controller.unmute() | |
else: | |
# unexpected input | |
print(f'Unexpected input: 0x{m[0]:02x}') | |
break | |
finally: | |
if s is not None: | |
s.close() | |
mic_controller.stop() | |
def main(): | |
while True: | |
try: | |
pumpit() | |
except KeyboardInterrupt: | |
raise | |
except Exception as e: | |
# ignore | |
print(f'Error: {e}') | |
time.sleep(1.) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment