Да, я каждый день на созвонах в Zoom. Да, у меня есть ребёнок, который часто вмешивается в эти звонки. Алгоритм выдачи рекламы Instagram, ты победил, мне нужна эта кнопка.
Но у меня есть предубеждения. В Instagram рекламируется проект с Kickstarter. К тому же, я не хочу делать свой вклад в доходы от рекламы Facebook, даже нажимая на этот ролик. Выражение Дженни Оделл «Бесплатных часов не бывает» полностью описывает мою точку зрения на качество продуктов в рекламе Instagram. Кроме того, мой лимит на финансирование проектов с Kickstarter практически исчерпался.
Я поддерживал множество проектов на Kickstarter, и иногда результат этих проектов напоминал мне гамбургер, который Майкл Дуглас получил в фильме «С меня хватит!».
Так что давайте соберём такую кнопку сами.
Первое, о чём стоит задуматься: какую кнопку мне будет приятно нажимать?
Я люблю клавиатуры с переключателями Cherry MX. Существует три типа механических переключателей: линейные, тактильные и кликающие. Линейный — простейший тип переключателя, перемещающийся вверх-вниз почти без обратной связи. У тактильных переключателей в середине хода есть выступ, позволяющий почувствовать, когда произошло нажатие клавиши. А кликающие переключатели имеют более сильную тактильную обратную связь И производят слышимый клик при нажатии.
В обычной ситуации мы бы купили тестер переключателей, чтобы разобраться, какой тип нам отзывается, а ещё опросили бы своих коллег, какой звук должна издавать клавиатура, чтобы они нас не убили. Но мы посередине пандемии COVID, поэтому коллег рядом нет! Выберем переключатель Cherry MX Blue с удобной тактильной обратной связью, который при этом чрезвычайно громкий. На сайте Cherry MX этот переключатель называют «кликающим и заметным», но это ещё очень слабо сказано.
Выглядит красиво, но можно придумать и кое-что получше. Если мне приятно нажимать переключатель Cherry MX Blue, то не будет ли ещё приятнее нажимать комически большой Cherry MX Blue?
И это Novelkeys Big Switch.
Он в 4 раза больше по каждой из размерностей и в 64 раза больше по объёму, чем обычный переключатель. У него даже есть огромный колпачок!
К сожалению Большой Переключатель не продаётся в корпусе, поэтому мне пришлось воспользоваться 3D-печатью. Красивый корпус нашёлся на Thingiverse: NovelKeys Big Switch Case. Всегда стоит поискать ремиксы на случай, если кто-нибудь решил усовершенствовать исходный дизайн. В данном случае нашёлся ремикс, в который добавлен отсек для Pro Micro, а переключатель устанавливается более плотно, поэтому я напечатал его.
Теперь, когда у нас есть корпус, нужна плата, которую мы вставим в него и прикрепим к переключателю.
У Pro Micro есть чип ATmega32U4, позволяющий эмулировать устройство USB HID, например, USB-клавиатуру. К тому же, эта плата имеет маленький размер.
В нижней части Большого Переключателя есть два металлических контакта.
При нажатии клавиши внутри переключателя происходит замыкание цепи между этими двумя контактами.
Взглянем на расположение контактов Pro Micro:
Можно подключить GND к одному металлическому контакту, а Pin 2 — ко второму. Pin 2 — это контакт цифрового ввода-вывода, который считывает HIGH, когда клавиша нажата, и LOW, когда нет.
Было бы ещё здорово иметь какой-нибудь наглядный индикатор состояния Mute, поэтому можно добавить светодиод.
Я заказал светодиод размером 10 мм:
И резистор на 220 ом:
Длинная нога светодиодов подключается к PWR, а короткая — к GND. Мы вставим резистор между длинной ногой и другим контактом, чтобы снизить величину тока. Я выбрал Pin 9 в нижней части платы. Короткую ногу я соединил с GND. Мне показалась полезной эта страница о светодиодах и резисторах.
Между платой и переключателем я припаял такой провод 20 AWG:
В результате получилась вот такая конструкция:
Просто запихнём всё это в наш напечатанный корпус:
Теперь нужно написать код.
Я начал с кода, который Sparkfun написала для создания огромной Кнопки Сохранения, и немного его изменил.
Принцип заключается в следующем: при нажатии клавиши она посылает сочетание горячих клавиш Zoom включения и отключения звука (на Mac это Cmd-Shift-A). Нужно будет изменить настройки Zoom, чтобы это сочетание клавиш распознавалось, даже когда Zoom находится не в фокусе. Поставим флажок Enable Global Shortcut:
Также мы хотим включать или выключать светодиод после каждого нажатия на клавишу. Я решил, что светодиод будет аналогом лампочки «В эфире» — когда синий светодиод горит, звук у меня включен и люди слышат, что я говорю.
Но если просто включать-выключать светодиод при каждом нажатии на клавишу, то как сохранять синхронизацию с состоянием Mute в самом Zoom?
Удобство Pro Micro заключается в том, что она имеет и последовательное подключение. Обычно оно используется для печати отладочной информации в Arduino IDE, но мы можем использовать его для обеспечения синхронизации с состоянием включения звука в Zoom.
Вот код, который мы загружаем в саму Pro Micro:
#include "Keyboard.h"
// OS parameters
typedef enum {
LINUX,
WINDOWS,
MAC
} os_types;
// Change this to your operating system
const os_types OS = MAC;
// Pins
const int btn_pin = 2;
const int led_pin = 9;
// Constants
const int debounce_delay = 50; // ms
// Globals
int btn_state = HIGH;
int btn_prev = HIGH;
unsigned long last_debounce_time = 0;
int os_ctrl;
int led_state = LOW;
void setup() {
Serial.begin(57600); // opens serial port, sets data rate to 57600 bps
// Set up LED and button pins
pinMode(btn_pin, INPUT_PULLUP); // Set the button as an input
pinMode(led_pin, OUTPUT);
digitalWrite(led_pin, led_state);
// Begin keyboard
Keyboard.begin();
// Switch to correct control/command key
switch(OS){
case LINUX:
case WINDOWS:
os_ctrl = KEY_LEFT_CTRL;
break;
case MAC:
os_ctrl = KEY_LEFT_GUI;
break;
default:
os_ctrl = KEY_LEFT_CTRL;
break;
}
// Get initial timestamp
Serial.println("started");
}
void loop() {
// Read current state of the button
int btn_read = digitalRead(btn_pin);
// Remember when the button changed states
if ( btn_read != btn_prev ) {
last_debounce_time = millis();
}
// Wait before checking the state of the button again
if ( millis() > (last_debounce_time + debounce_delay) ) {
if ( btn_read != btn_state ) {
btn_state = btn_read;
if ( btn_state == LOW ) {
// Send cmd+shift+a
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press(os_ctrl);
Keyboard.press('a');
delay(100);
Keyboard.releaseAll();
Serial.println("pressed");
if (led_state == LOW) {
led_state = HIGH;
} else {
led_state = LOW;
}
digitalWrite(led_pin, led_state);
}
}
}
// Remember the previous button position for next loop()
btn_prev = btn_read;
if (Serial.available() > 0) {
String incomingString = Serial.readStringUntil('\n');
if (incomingString == "muted") {
led_state = LOW;
} else if (incomingString == "unmuted") {
led_state = HIGH;
}
digitalWrite(led_pin, led_state);
}
}
Дальше мы можем добавить Applescript, сообщающий о текущем состоянии Zoom. Я нашёл Zoom-плагин для устройства Streamdeck, содержавший исходный Applescript, и изменил его так, чтобы он сообщал о том, открыт ли Zoom, и каково состояние его звука. Также я модифицировал скрипт, чтобы он выводил JSON.
set zoomStatus to "closed"
set muteStatus to "disabled"
tell application "System Events"
if exists (window 1 of process "zoom.us") then
set zoomStatus to "open"
tell application process "zoom.us"
if exists (menu bar item "Meeting" of menu bar 1) then
set zoomStatus to "call"
if exists (menu item "Mute audio" of menu 1 of menu bar item "Meeting" of menu bar 1) then
set muteStatus to "unmuted"
else
set muteStatus to "muted"
end if
end if
end tell
end if
end tell
copy "{\"mute\":\"" & (muteStatus as text) & "\",\"status\":\"" & (zoomStatus as text) & "\"}" to stdout
Если теперь мы запустим его во время звонка в Zoom, то он будет выводить примерно такое:
$ osascript get-zoom-status.scpt
{"mute":"muted","status":"call"}
Затем я написал небольшое приложение на Node, используемое в качестве посредника между Pro Micro и этим скриптом:
const { exec } = require('child_process');
const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
const port = new SerialPort('/dev/tty.usbmodemHIDPC1', {
baudRate: 57600
});
var checkStatus = function() {
console.log('Checking status...');
exec('osascript get-zoom-status.scpt', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
var status = JSON.parse(stdout);
if (status.mute == 'unmuted') {
port.write('unmuted');
} else {
port.write('muted');
}
});
}
const parser = port.pipe(new Readline({ delimiter: '\r\n' }))
parser.on('data', function (data) {
if (data == "pressed") {
console.log('Button pressed.');
checkStatus();
}
})
checkStatus();
setInterval(checkStatus, 30000);
Этот скрипт выполняет две задачи. При нажатии кнопки он отправляет плате Pro Micro через последовательный порт команду «нажато», при этом запускается Applescript для определения текущего состояния звука в Zoom. Затем он отправляет плате Pro Micro команду «звук выключен» или «звук включен», благодаря чему светодиод переключается в соответствующее состояние. Также я создал таймер, запускающий скрипт через каждые 30 секунд на случай, если я случайно отключу или включу звук через интерфейс Zoom, а не через кнопку, в противном случае состояние будет обновляться только при нажатии кнопки.
Вот как выглядит кнопка при использовании в звонке через Zoom:
Пожалуйста, поддержите мой Kickstarter — шучу, у меня нет никакого Kickstarter, но, надеюсь, теперь вы сможете создать такую кнопку сами.
На правах рекламы
Закажите сервер и сразу начинайте работать! Создание VDS любой конфигурации в течение минуты. Эпичненько :)
ionicman
Дожили.
Вместо обычного Digispark с Attiny, который включается в USB и эмулирует hid, чел заюзал ATmega32U4!
А чего сразу не Rasberry?
Можно бы было и вообще PC в ITX собрать, и его заюзать, че мелочиться-то…
Я уж молчу, что можно было-бы просто замэппить нужную клавишу с обычной клавиатуры и написать очень простой хук, который при нажатии на нее, отсылал бы нужное сочетание.
Andrey_Epifantsev
Так вроде там пробел звук включает. Достаточно держать микрофон выключенным, а когда говоришь нажимать пробел.
numark
Верно. Но это не сработает, если окно Zoom не активно.
Зачастую все параллельно работают, а зумец сбоку висит где-то.
Iv38
ATmega32U4 хороша тем, что у неё уже есть аппаратный USB. На тиньке надо эмулировать USB-HID, на что её возможностей едва хватает. А здесь ещё обратный канал по USB-UART. Хотя, учитывая что всё равно требуется софтовая поддержка со стороны компьютера, можно было использовать только USB-UART без эмуляции HID.
xsevenbeta
Посыл правильный, но высказан не корректно и токсично. Легко отбить охоту писать не только у автора, но и у всех кто ваш коммент прочитает.
Komrus
Ну… Последние несколько тысяч лет люди высказывают
соплеменникамколлегам свои замечания о неправильных действиях этих самыхсоплеменниковколлег в жёсткой и резкой форме как раз с целью отбить охоту делать неправильно.Раньше ещё и сопровождали постукиванием по разным частям тела, чтобы доходило быстрее.
Вначале отошли от такой
замечательнойпрактики, а теперь и простой комментарий токсичным считают :)nikitsenka
A почему бы и не на Raspberry? это же не массовое и даже не серийное производство. Поставил поигрался, непонравилось — забрал комплектующие в другой проект. Может вообще проект в реале не понравится. На одном распбери зеро я могу 100 таких проектов сделать. Не для того чтобы юзать, а просто так пазырить
dimaaannn
Зачем ужиматься, если бюджет позволяет поставить хоть ту же raspberry pico?
Если в тираже это играет существенную роль, то для самоделок — это вообще копейки. И главный вопрос — с чего бы автор должен учитывать ваши личные мировоззрения?