Skip to content

Instantly share code, notes, and snippets.

@micolous
Last active January 30, 2021 03:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micolous/f03267195203cb47832096bf238893bb to your computer and use it in GitHub Desktop.
Save micolous/f03267195203cb47832096bf238893bb to your computer and use it in GitHub Desktop.
/*
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;
}
#!/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