henryruhs / chroma-feedback

Turn your RGB powered hardware into a status indicator for continuous integration, continuous deployment and infrastructure monitoring

Home Page:https://chroma-feedback.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Razer Chroma consumer fails API call for failed status with certain devices

jqassar opened this issue · comments

Background:

When testing chroma-feedback with various output devices, I was able to successfully test status notifications using both the blink1 consumer. However, with my particular device (the newer version of the Razer Mamba Wireless mouse), the Razer Chroma consumer has slightly inconsistent outputs.

(IMPORTANT DISCLAIMER: Making this mouse work requires a custom build of openrazer which adds the device, located here: openrazer/openrazer@master...crian:feature_mamba_wireless)

With the Bamboo producer (fixed version 7.0.1-beta), the behavior I expect is:

  • Plan slug (ACME-BUILD), successful status: Solid green logo and scroll wheel.
  • Plan slug (ACME-BUILD), failed status: Pulsing red logo and scroll wheel.
  • Project slug (ACME), successful status: Solid green logo and scroll wheel.
  • Project slug (ACME), failed status: Pulsing red logo and scroll wheel.

In all cases, chroma-feedback gives a message: 'Setting Razer Mamba Wireless (Wired) to build (status)'.

Instead, we get the following:

  • Plan slug (ACME-BUILD), successful status: Solid green logo and scroll wheel.
  • Plan slug (ACME-BUILD), failed status: No lighting change or message.
  • Project slug (ACME), successful status: Solid green logo and scroll wheel.
  • Project slug (ACME), failed status: No lighting change or message.

This is happening (I think) because different types of Razer mice have different effects support.
When the build status is marked as 'failed', the current API calls pulsate_device, which the mouse doesn't seem to fully support:

def pulsate_device(device : Any, state : Dict[str, Any]) -> bool:
	if device.has('brightness'):
		device.brightness = state['brightness'][0]
	if device.fx.has('logo') and device.fx.has('scroll'):
                # The new Mamba has logo and scroll, but can't pulsate:
		return device.fx.misc.logo.pulsate(state['rgb'][0], state['rgb'][1], state['rgb'][2]) and device.fx.misc.scroll_wheel.pulsate(state['rgb'][0], state['rgb'][1], state['rgb'][2])
	return device.fx.breath_single(state['rgb'][0], state['rgb'][1], state['rgb'][2])

We can see that the Mamba does have logo and scroll, but it doesn't support the pulsate method. This can be verified using the following command with a supported openrazer build:

python3 -c "from openrazer.client import DeviceManager; a = DeviceManager(); print(a.devices[0].fx.has('scroll'));a.devices[0].fx.misc.logo.pulsate(0,255,0)"
True (but no logo change)

While it would straightforward to just change the method to use breath_single, the problem is that not all mice support that either. According to the driver on the branch:

Mice which support breath effects: Lancehead TE, Mamba Chroma (wireless and wired), Mamba TE, Orochi Wired, Naga Hex V2, Naga Chroma, Deathadder Elite, Diamondback Chroma, Mamba Wireless Receiver (2019 Mamba), Mamba Wireless Wired (2019 Mamba), Deathadder Essential.

Mice which support pulsate effects: Deathadder Chroma, Deathadder 2013, Abyssus V2, Deathadder 3500.

There is no crossover between these capabilities, so the simplest fix may be to test whether the device supports each method and return the proper effect. Testing for the existence of a logo and scroll wheel is not sufficient, it seems.

Further testing shows there might be an issue with the openrazer implementation that I need to explore further:

python3 -c "from openrazer.client import DeviceManager; a = DeviceManager(); print(a.devices[0].fx.has('scroll'));print(hasattr(a.devices[0].fx.misc.scroll_wheel, 'breath_single'));a.devices[0].fx.misc.scroll_wheel.breath_single(0,255,0)"
True (has a scroll wheel)
True (has a function under scroll_wheel named 'breath_single')
Result: scroll wheel pulsates green as expected

python3 -c "from openrazer.client import DeviceManager; a = DeviceManager(); print(a.devices[0].fx.has('scroll'));print(hasattr(a.devices[0].fx.misc.scroll_wheel, 'pulsate'));a.devices[0].fx.misc.scroll_wheel.pulsate(0,255,0)"
True (has a scroll wheel)
True (has a function under scroll_wheel named 'pulsate') <- I wasn't expecting this.
Result: Nothing

This might be a better test to work with. Calling fx._capabilities gives, among other items,

# Note these are all false...
'brightness': False, 
...
'lighting': True, 
'lighting_breath_single': False,
'lighting_breath_dual': False,
'lighting_breath_triple': False,
'lighting_breath_random': False,
...
'lighting_static': False,
...
'lighting_pulsate': False, 
...
# But note that these are properly true or false:
'lighting_led_matrix': True, 
'lighting_led_single': False,
'lighting_logo': True, 
'lighting_logo_brightness': True, 
'lighting_logo_pulsate': False, 
'lighting_logo_spectrum': True, 
'lighting_logo_static': True, 
'lighting_logo_none': True,
'lighting_logo_reactive': True,
'lighting_logo_wave': False,
'lighting_logo_breath_single': True, 
'lighting_logo_breath_dual': True,
'lighting_logo_breath_random': True,
... (repeat for lighting_scroll)

So, this tells us that the necessary tests should be:

def pulsate_device(device : Any, state : Dict[str, Any]) -> bool:
	if device.has('brightness'):
		device.brightness = state['brightness'][0]
	if device.fx.has('logo') and device.fx.has('scroll'):
                if device.fx.has('logo_pulsate') and device.fx.has('scroll_pulsate'):
        		return device.fx.misc.logo.pulsate(state['rgb'][0], state['rgb'][1], state['rgb'][2]) and device.fx.misc.scroll_wheel.pulsate(state['rgb'][0], state['rgb'][1], state['rgb'][2])
                return device.fx.misc.logo.breath_single(state['rgb'][0], state['rgb'][1], state['rgb'][2]) and device.fx.misc.scroll_wheel.breath_single(state['rgb'][0], state['rgb'][1], state['rgb'][2])
	return device.fx.breath_single(state['rgb'][0], state['rgb'][1], state['rgb'][2])

Note: This assumes that pulsate_device is always called, but it may be better to make an independent breath_device method and add functionality to determine what the device can support early in the process_devices method.

We could test for pulsate support and fallback to static when it is not present... what do you think?

If you take a look at my PR, I am testing whether the device supports the pulsate effect (I think this is for mice with RGB matrices), then the breath_single effect, and finally defaulting back to pulsate. That could default to static instead, but I kind of like the on/off effect for failed build status, so...

It appears that most devices in openrazer support one or the other (so it really sounds like it's a naming change more than anything).

Finally back from vacation... I added an code review for the PR. Looks very promising what you found out so far... the checks for effects are better than what I did.

Please apply the suggested changes - thanks!