1 / 7
USB Volume, Brightness, and Battery Controller (WIP) — zdjęcie 1
1 / 7

USB Volume, Brightness, and Battery Controller (WIP)

Zamów wydruk tego modelu

Obliczymy ostateczną cenę na podstawie wybranego pliku. Sprawdzisz ją i zaakceptujesz przed płatnością.

Zapisane na tym urządzeniu.

USB Volume, Brightness, and Battery Controller and Monitor (WIP)

Ocena
0 / 5
Polubienia
1
Pobrania
8
Wydruki
0
Wyświetlenia
108
Oceny
0

Aktualizacja u źródła:

Informacje o modelu

Źródło
Printables
Kategoria
Computers
Licencja
Creative Commons — Attribution — Noncommercial

USB volume, brightness, and battery controller/monitor that was developed to be used on a GPD Pocket 4 while gaming in tablet mode with a telescopic controller.

This is a work in progress. Here are some known issues:

  • Both the notch on the lid and the hole it goes into on the side of the main body (above the USB-C cutout) may need to be filed down a little to fit.
  • The battery monitor also requires a small PowerShell script to feed estimated battery charge data to the serial port of the device so the data can be displayed on the OLED. However, the PowerShell script can sometimes fail to properly identify the port number based on the device's hardware ID. This is because the KB2040 has two USB modes and the hardware ID can be different when the device is plugged in.

Any assistance with fixing/improving the code below would be greatly appreciated. Please pay attention to any error messages generated by PowerShell or CircuitPython as they will likely contain suggestions on how to fix the issue.

Skills required:

  • Installing CircuitPython and necessary libraries on an KB2040 microcontroller
  • Soldering
  • Debugging/troubleshooting PowerShell and CircuitPython code

Parts List:

Hardware prep:

  • Snip off one end of the STEMMAQT/Qwiic cable and solder the wires to the OLED as shown in the photo in images section.
  • Cover back of OLED with some Kapton tape to prevent possible shorting
  • Pins 0 and 1 are both ground and will need four thin wires soldered to them. Two to each pin to keep things simple. Two of these ground wires will go to each of the rotary encoders. One will go to the middle pin on the side with three pins, and the other will go to either of the two pins on the side with two pins (the push-button). 
  • The rest of the pins should be soldered as follows:
    • Pin 2 and Pin 3 to the two pins on the three-pin side of the brightness encoder
    • Pin 4 to the remaining pin on the two-pin side of the brightness encoder (the push-button)
    • Pin 5 and Pin 6 to the two pins on the three-pin side of the volume encoder
    • Pin 7 to the remaining pin on the two-pin side of the volume encoder (the push-button)
  • As long as the ground pins are connected properly the rest of the pins can be changed around in the code below.
  • Once the pins are soldered it is recommended to bend them inward and cover the exposed pins with some Kapton tape to prevent shorting.

Software prep:

CircuitPython code:

import time
import board
import busio
import digitalio
import rotaryio
import usb_hid
import usb_cdc
import adafruit_ssd1306
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

Setup I2C for OLED (SCL = D13, SDA = D12)

i2c = busio.I2C(board.D13, board.D12) display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)

Setup HID and Serial

consumer_control = ConsumerControl(usb_hid.devices) keyboard = Keyboard(usb_hid.devices) serial = usb_cdc.console

Setup Rotary Encoders (only second one works)

encoder_brightness = rotaryio.IncrementalEncoder(board.D8, board.D9) button_brightness_mute = digitalio.DigitalInOut(board.D4) button_brightness_mute.direction = digitalio.Direction.INPUT button_brightness_mute.pull = digitalio.Pull.UP

encoder_vol = rotaryio.IncrementalEncoder(board.D5, board.D6) button_vol_mute = digitalio.DigitalInOut(board.D7) button_vol_mute.direction = digitalio.Direction.INPUT button_vol_mute.pull = digitalio.Pull.UP

last_position_vol = encoder_vol.position last_position_brightness = encoder_brightness.position

Clear OLED and display initial message

display.fill(0) display.text("Waiting for host...", 0, 0, 1) display.show()

print("KB2040 HID & OLED Ready")

while True: # Read Serial Data and Update OLED if serial.in_waiting > 0: received_data = serial.read(serial.in_waiting).decode().strip() print(f"BATT: {received_data}%")

    # Update OLED Display
    display.fill(0)
    display.text("BATT: " + received_data[:20] + "%", 0, 0, 1, size=2)
    display.show()

# Check Volume Encoder
position_vol = encoder_vol.position
if position_vol > last_position_vol:
    print("Volume Up")
    consumer_control.send(ConsumerControlCode.VOLUME_INCREMENT)
elif position_vol < last_position_vol:
    print("Volume Down")
    consumer_control.send(ConsumerControlCode.VOLUME_DECREMENT)
last_position_vol = position_vol

# Check Mute Button for Volume
if not button_vol_mute.value:
    print("Mute Toggle")
    consumer_control.send(ConsumerControlCode.MUTE)
    time.sleep(0.3)  # Debounce

# Check Brightness Encoder
position_brightness = encoder_brightness.position
if position_brightness > last_position_brightness:
    print("Brightness Up")
    consumer_control.send(ConsumerControlCode.BRIGHTNESS_INCREMENT)
elif position_brightness < last_position_brightness:
    print("Brightness Down")
    consumer_control.send(ConsumerControlCode.BRIGHTNESS_DECREMENT)
last_position_brightness = position_brightness

# Check Button for Brightness Encoder (Send Ctrl+Alt+Del)
if not button_brightness_mute.value:
    print("Sending Ctrl+Alt+Del")
    keyboard.press(Keycode.CONTROL, Keycode.ALT, Keycode.DELETE)
    keyboard.release_all()
    time.sleep(0.3)  # Debounce

time.sleep(0.05)  # Keep loop responsive

PowerShell Script (Work-in-Progress):

*Requires running PowerShell scripts to be unrestricted on the host device. Also needs to be run from an elevated prompt or PowerShell ISE.

# Check if the script is running with administrator privileges
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    # If not, relaunch the script with administrator privileges
    if ([int](Get-Process -Id $PID | Select-Object -ExpandProperty MainWindowHandle) -ne 0) {
        $arguments = "& '" + $myinvocation.MyCommand.Path + "'"
        Start-Process powershell.exe -Verb runAs -ArgumentList $arguments
        exit
    }
}

The rest of your script goes here

It will only execute if the script is running as administrator

Write-Host "Script is running as administrator"

Add your script's functionality below this line

#Hardware ID of KB2040 $searchString = "USB\VID_239A&PID_8105&MI_00" #Hardware ID of KB2040 $baudRate = 9600;

#Get port number based on hardware ID $picoPort = Get-WmiObject Win32_PnPEntity | Where-Object {$_.PNPDeviceID -like "$searchString"} | Select-Object -ExpandProperty Name

#Substring port number to only what is needed if ($picoPort) { Write-Host "COM Port found: $picoPort" #For single-digit ports if ($picoPort.length -eq 24) { $picoPort = $picoPort.Substring(19,4) } #For two-digit ports if ($picoPort.length -eq 25) { $picoPort = $picoPort.Substring(19,5) } } else { #Second hardware ID of KB2040 $searchString = "USB\VID_239A&PID_8106&MI_00" #Hardware ID of KB2040

#Get port number $picoPort = Get-WmiObject Win32_PnPEntity | Where-Object {$_.PNPDeviceID -like "$searchString"} | Select-Object -ExpandProperty Name

#Substring port number to only what is needed if ($picoPort) { Write-Host "COM Port found: $picoPort" #For single-digit ports if ($picoPort.length -eq 24) { $picoPort = $picoPort.Substring(19,4) } #For two-digit ports if ($picoPort.length -eq 25) { $picoPort = $picoPort.Substring(19,5) } } else { Write-Host "No COM port found with the specified hardware ID substring." } }

try { $port = New-Object System.IO.Ports.SerialPort($picoPort, $baudRate) if ($port.IsOpen) { $port.Close() $port.Dispose() Write-Host "$picoPort closed successfully." } else { Write-Host "$picoPort is not open." } } catch { Write-Host "Error closing " + $picoPort + ": $($_.Exception.Message)" } finally { if ($port -ne $null -and $port.IsOpen) { $port.Close() # Ensure it's closed in case of error during opening $port.Dispose() } }

#Create new port object $port = new-Object System.IO.Ports.SerialPort $picoPort,$baudRate,None,8,one #Assert Data Terminal Ready $port.DtrEnable = $true

#Wait 5 seconds Start-Sleep -s 5

#Open the port $port.open() if ($port.IsOpen) { Write-Host "$picoPort is open."

#Continuous loop
while (1 -gt 0) {

    #Get current battery level and write to port
    $port.Write((Get-WmiObject Win32_Battery).EstimatedChargeRemaining)

#Wait 5 seconds
Start-Sleep -s 5 
}

}

$port.Close() $port.Dispose()

END

Tagi