Skip to content

Instantly share code, notes, and snippets.

@pklaus
Last active June 2, 2020 22:36
Show Gist options
  • Save pklaus/56ee78d26593d33d906c to your computer and use it in GitHub Desktop.
Save pklaus/56ee78d26593d33d906c to your computer and use it in GitHub Desktop.
Easterntimes Tech Wired Gaming Mouse T1

Easterntimes Tech - Wired Gaming Mouse T1

A script to change settings of the Wired Gaming Mouse T1 when running Linux.

#!/usr/bin/env python
"""
Script to control the
Easterntimes Tech
Wired Gaming Mouse T1
via PyUSB.
"""
import sys, argparse, textwrap, logging
import usb.core, usb.util
logger = logging.getLogger(name=__name__)
class WiredGamingMouseT1(object):
idVendor = 0x04d9
idProduct = 0xfc07
brightness_map = {'bright': 3, 'medium': 2, 'dim': 1, 'off': 0}
breathing_map = {'fast': 1, 'medium': 3, 'slow': 6, 'off': 0}
def __init__(self):
# Device Initialization
dev = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
try:
if dev.is_kernel_driver_active(2):
dev.detach_kernel_driver(2)
except usb.core.USBError as e:
msg = """
Could not interact with the device. This tool needs to be run as root
or you need to set the right permissions on the usb device:
sudo chmod 666 /dev/bus/usb/{bus:03d}/{address:03d}
Or you can let udev configure the permissions automatically by putting this rule:
SUBSYSTEM=="usb", ATTR{{idVendor}}=="04d9", ATTR{{idProduct}}=="fc07", MODE="0666", SYMLINK+="wgmt1"
into the file /etc/udev/rules.d/99-wgmt1.rules.
"""
sys.exit(textwrap.dedent(msg.format(bus=dev.bus, address=dev.address)))
usb.util.claim_interface(dev, 2)
self.dev = dev
def send_ctrl_msg(self, data, big=False):
logger.debug('sending: ' + repr(data))
val = 0x0303 if big else 0x0302
self.dev.ctrl_transfer(0x21, 9, val, 2, data)
def set_color(self, rgb, brightness='bright', breathing='off'):
""" send a URB message via PyUSB and change the LED color """
r, g, b = (255 - col for col in rgb)
brightness, breathing = self.sanitize_brightness_breathing(brightness, breathing)
msg = bytes([0x2, 0x4, r, g, b, brightness, breathing, 0, 0, 0, 0, 0, 0, 0, 0, 0])
self.send_ctrl_msg(msg)
def set_profile(self, profile):
""" send a URB message via PyUSB and change the profile """
assert profile in range(0, 5)
msg = bytes([2,2,0x43,0,1,0,0xfa,0xfa,profile,0,0,0,0,0,0,0])
self.send_ctrl_msg(msg)
msg = bytes([2,1,1,profile,0,0,0,0,0,0,0,0,0,0,0,0])
self.send_ctrl_msg(msg)
def set_cpi(self, profile, cpi_steps):
"""
cpi_steps must be an iterable of length 5
each consisting of a number going from 0 to 16
"""
assert len(cpi_steps) == 5
msg = [3,2,0x4f,2,0x2a,0,0xfa,0xfa,5,profile]
for cpi_step in cpi_steps:
if cpi_step in range(0, 17):
enable = 1
else:
enable = 0
cpi_step = 0
msg += [enable,cpi_step,0,cpi_step,0,0,0,0]
msg += [0] * 14
msg = bytes(msg)
self.send_ctrl_msg(msg, big=True)
logger.info('You need to switch to the profile now to enable the new settings.')
def sanitize_brightness_breathing(self, brightness, breathing):
if type(brightness) == str:
brightness = self.brightness_map[brightness]
if type(breathing) == str:
breathing = self.breathing_map[breathing]
assert brightness in (0,1,2,3)
assert breathing in (0,1,3,6)
return brightness, breathing
def set_brightness_breathing(self, profile, brightness, breathing):
""" """
brightness, breathing = self.sanitize_brightness_breathing(brightness, breathing)
msg = bytes([2,2,0xf1,profile,6,0,0xfa,0xfa,0xf1,0xf0,0,brightness,breathing,0,0,0])
logger.debug('Sending this now: ' + repr(msg))
self.send_ctrl_msg(msg)
logger.info('You need to switch to the profile now to enable the new settings.')
def cpi_steps(string):
"""
argparse type definition to enter CPI steps.
"""
try:
steps = [int(part) for part in string.split(',')]
assert len(steps) == 5
return steps
except:
raise argparse.ArgumentTypeError('Not a proper CPI steps value.')
def hex_rgb(string):
"""
argparse type definition to enter RGB
values as a 3- or 6- digit hex number
"""
try:
if len(string) == 3:
string = string[0] * 2 + string[1] * 2 + string[2] * 2
assert len(string) == 6
r, g, b = string[0:2], string[2:4], string[4:6]
return (int(col, 16) for col in (r, g, b))
except:
raise argparse.ArgumentTypeError('Not a proper RGB hex value.')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--debug', action='store_true')
profile_parser = argparse.ArgumentParser(add_help=False)
profile_parser.add_argument('profile', type=int, choices=range(0,5), help='Profile')
subparsers = parser.add_subparsers(dest='action', metavar='<action>', help="Action to perform:")
# set-color
action_desc = 'Set the LED color of the mouse'
set_color_parser = subparsers.add_parser('col',
description=action_desc, help=action_desc)
set_color_parser.add_argument('color', type=hex_rgb, metavar='RRGGBB', help='New LED color')
set_color_parser.add_argument('--brightness', choices=WiredGamingMouseT1.brightness_map.keys(), default='bright', help='Brightness')
set_color_parser.add_argument('--breathing', choices=WiredGamingMouseT1.breathing_map.keys(), default='off', help='Breathing')
# switch-profile
action_desc = 'Switch to a different mouse profile'
switch_profile_parser = subparsers.add_parser('sp',
description=action_desc, parents=[profile_parser], help=action_desc)
# cpi-steps
action_desc = 'Set the CPI (counts-per-inch) steps'
switch_profile_parser = subparsers.add_parser('cs',
description=action_desc, parents=[profile_parser], help=action_desc)
switch_profile_parser.add_argument('steps', type=cpi_steps,
help='Comma separated list of CPI steps for the specified profile. Default: 2,3,6,13,16. Use -1 to disable a step.')
# brightness-breathing
action_desc = 'Change the brightness & breathing settings'
brightness_breathing_parser = subparsers.add_parser('bb',
description=action_desc, parents=[profile_parser], help=action_desc)
brightness_breathing_parser.add_argument('brightness', choices=WiredGamingMouseT1.brightness_map.keys(), help='Brightness')
brightness_breathing_parser.add_argument('breathing', choices=WiredGamingMouseT1.breathing_map.keys(), help='Breathing')
args = parser.parse_args()
if not args.action: parser.error("Please choose and action.")
level = logging.DEBUG if args.debug else logging.INFO
logging.basicConfig(format='%(levelname)s: %(message)s', level=level)
t1 = WiredGamingMouseT1()
if args.action == 'col': t1.set_color(args.color, args.brightness, args.breathing)
if args.action == 'sp': t1.set_profile(args.profile)
if args.action == 'cs': t1.set_cpi(args.profile, args.steps)
if args.action == 'bb': t1.set_brightness_breathing(args.profile, args.brightness, args.breathing)
if __name__ == "__main__": main()
@JKawmi
Copy link

JKawmi commented Dec 31, 2015

Hey! sorry for the noob question but i'm getting:
Traceback (most recent call last):
File "./wgmt1.py", line 12, in
import usb.core, usb.util
ImportError: No module named usb.core
What am i doing wrong?

@pklaus
Copy link
Author

pklaus commented Jan 14, 2016

Hi @F0rce,
you need to install PyUSB:

pip install --upgrade https://github.com/walac/pyusb/archive/master.zip

In addition, you need to install libusb1 from your Linux distribution's package manager.

@OliverMead
Copy link

Hi, I'm getting the following error when I try to run it

Command:

sudo ./TECKNET_M008_MOUSE_SETTINGS.py col 00ff00

Error:

File "./TECKNET_M008_MOUSE_SETTINGS.py", line 30, in __init__
    if dev.is_kernel_driver_active(2):
AttributeError: 'NoneType' object has no attribute 'is_kernel_driver_active'

I am running Ubuntu 15.10 with both PyUSB and libusb-1.0-0 installed, do you know what I have done wrong?

@xadhoom
Copy link

xadhoom commented Jan 19, 2016

Probably your usb vendorID or productID are different, check them out using lsusb or dmesg and update the py script.
I'm trying to run this stuff on a T3 version of the same mouse, but no luck, I'm missing the usb HID "device".... meh
I'll check in spare time...

@Conobi
Copy link

Conobi commented Jun 30, 2016

image
I get these errors when i try the script, i don't understand why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment