subraizada3 / 27gn950controller

Control bias lighting on the LG 27GN950 monitor
Other
63 stars 12 forks source link

Add video sync mode #1

Closed joenye closed 2 years ago

joenye commented 3 years ago

First - thank you so much for writing this program. It's a big help for me.

I see a TODO mentioning it, but do you have any plans to implement video sync mode? It's unfortunate that I don't have a way to provide this information unless I'm on Windows.

ACastanza commented 3 years ago

Honestly, I PyUSB should be able to run on windows, so if this can be made to work it'd be an awesome alternative to LG's terrible UltraGear software!

subraizada3 commented 3 years ago

It's great to hear that this is useful to other people. I have Wireshark USB packet dumps saved from video sync in Windows, so I'll examine those and try to get it implemented tonight.

subraizada3 commented 3 years ago

I added a 'setVideoSync' command to the CLI. This will switch the monitor into video sync mode, like the set1-4 and setPeaceful/setDynamic command switch into those modes.

The LEDs will automatically switch out of video sync mode into a 'disabled' sort of state if you don't feed it data for a few seconds. Setting into another mode will fix it.

I haven't fully figured out how the colors are encoded. This is the raw HID data that's being sent - each set of three lines sets the ring to a different configuration. You can copy all three lines into your clipboard and paste them into the CLI to set the configuration. Copy/pasting one line at a time won't work; the timing matters with this. Entering just one or two lines does nothing; all three data packets must be sent for the monitor to update.

I'll spend some more time doing more USB sniffing and trying to decipher this. Once that's done it's a matter of taking a X11 screen capture under Python and converting the screen color to this format. A Windows screen capture backend could also be nice to have in the future.

5343c1029100ff8000ff8000ff8000ff8000000000454545515151ff8000ff8000ff8000ff8000ff8000ff8000ff8000ff8000ff80006363634444443e3e3e00
0000ff8000ff8000ff8000ff8000ff80004e4e4e00142800101f000e1d000e1d001125001b3700172e00122400132600112300234400336300264d001c380014
2c232323001226343434001f41001f415c5c5cff8000624544000000000000000000000000000000000000000000000000000000000000000000000000000000

5343c1029100ffffffffffffffffffffffffffffff000000ff8000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0000000005d5d5dff
ffffffffffffffffffffffffffffffffffff8000001e3c00182f00152c00152c001a38002852002245001b36001c39001a340000000040ff000000002a54001e
42353535001b394e4e4e002e62002e62ff8000ffffff084544000000000000000000000000000000000000000000000000000000000000000000000000000000

5343c1029100ffffffffffffffffffffffffffffffff8000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000ff8000ff
ffffffffffffffffffffffffffffffffffffffff002950002243001f40001c3d00224a00000000316300234800254c0022450040ff0080ff0040ff0000000028
5746464600234b0000000040ff0040ffffffffffffffd54544000000000000000000000000000000000000000000000000000000000000000000000000000000

5343c1029100ffffffffffffffffffffffffffffffff8000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000ff8000ff
ffffffffffffffffffffffffffffffffffffffff00284e001d38001831001a3700224a00316300295300234800254c0023460040ff0080ff0040ff0000000028
57001f4600254f0000000040ff0040ffffffffffffff914544000000000000000000000000000000000000000000000000000000000000000000000000000000
koshisan commented 3 years ago

As for the color encoding: Do you remember what colors you were displaying? A full white/black/red/green/blue could maybe help to sort this out. I have been fiddeling around with some Tuya LED devices the other day and they use hex-encoded HSV values...

subraizada3 commented 3 years ago

That seems similar to the colors that the other commands accept. I'll take a further look at this when I have some time.

begner commented 2 years ago

Regarding Screen Capture:

Why dont use Hyperion Protocol "simply" provide a "Hyperion Server"? Normally Hyperion is used for Ambilight DYI (which is a kind of "Video Sync")

Doing that this way, you would cirumvent the whole "grabbing/color extraction" action.

Hyperion Project: https://hyperion-project.org/ Windows-Screen-Capture: https://sabaatworld.github.io/HyperionScreenCap/ x11-Capture: https://github.com/kevinlekiller/Hyperion_Grabber_X11_QT

jiunbae commented 2 years ago

First, thank you very much for making such a great program.

Are there currently any updates for multi-monitor video sync?

subraizada3 commented 2 years ago

No, there's currently no video sync support at all.

I haven't had the time or motivation to work on this. I only use static color mode on my monitor. If anyone has a PR for video sync I'm willing to integrate it, but until then video sync probably won't be implemented anytime soon.

jiunbae commented 2 years ago

I don't know much about USB communication, but I'll try it if I have time.

Thanks again for making a great program.

subraizada3 commented 2 years ago

I figured out how to send video sync data messages. However, I wasn't able to figure out what algorithm LG is using to provide the checksums in each message. Created issue #7, once that's resolved video sync will be ready.

imkebe commented 2 years ago

Great work @subraizada3 ! I found some old code using pyUSB and switched to hidapi by my own... And I found that you already did that :) I own a LG 38GN950-B and I can confirm that it works as well.

However I use MacOs so it forced the usage of hidapi (hid devices access is forbiden using way pyUSB works).

Overall I was planing to (wondering if capable) integrate with Prismatic API. Prismatic already provide advanced screen grabbing capabilities OS independent and python wrapper for it's telnet API. Only thing is to figure out how to control each led and stream it to devices.

How do you sniff usb commands? I was thinking about VM with Windows with USB passthrough to try this.

subraizada3 commented 2 years ago

Good to hear that it also works with the 38GN950.

How do you sniff usb commands? I was thinking about VM with Windows with USB passthrough to try this.

The LG software (both OSC and UltraGear Control Center) won't detect the monitor in a VM with USB passthrough. Not sure why. I booted to a 'real' Windows installation and used Wireshark with USBpcap (included as an option in the Wireshark installer). You can use a filter like:

usb.dst=="1.17.2" and frame.len==91

The exact number (1.17.2) will depend on the topology of your USB bus. When you send commands via the LG software you'll need to see in Wireshark which src/dst it's generating packets to.

Then with the filter in place, you can view the packets and look at the "Leftover Capture Data" field which contains the 64-byte HID data.

Only thing is to figure out how to control each led and stream it to devices.

This is already implemented in lib27gn950.py, you can do send_command(control_commands['color_video_sync']) followed by periodic send_video_sync_data. The LG software sends new data every 0.04 seconds.

Here's a test program which will flash the LED ring to random colors. The issue I'm facing right now is that some combinations of colors cause the monitor to basically 'crash' - it gets stuck on that pattern, then reverts to dynamic mode and stops accepting new commands or new color data. Recovering from that mode requires restarting the monitor with the joystick under the screen.

For example, in this test program, it'll strobe between random colors for most of the LEDs in the ring, and use fixed colors for a few of the LEDs. But if you set the 40th LED to always be red (ff0000) then you will experience the freezing behavior.

Or if you uncomment the line at the bottom which makes the entire LED ring red (colors = ['ff0000']*48) that'll fail too. And it's not something specific to red, you can change it to full blue and it'll still fail.

#!/usr/bin/env python3

import hid
import random
import string
from sys import exit
from time import sleep
from lib27gn950 import *

monitors = find_monitors()
with hid.Device(path=monitors[0]['path']) as dev:
    send_command(control_commands['color_dynamic'], dev)
    sleep(2)
    send_command(control_commands['color_video_sync'], dev)
    sleep(0.1)

    count = 0
    while True:
        count += 1
        sleep(0.06)

        colors = []
        for i in range(48):
            c = ''.join(random.choices(string.hexdigits[:-6], k=6))
            colors.append(c)

        colors[ 0] = 'ff0000'
        colors[ 5] = 'ff0000'
        colors[10] = 'ff0000'
        colors[15] = 'ff0000'
        colors[30] = 'ff0000'
        colors[35] = 'ff0000'
        colors[40] = 'ffffff' # if this is red, it fails
        colors[41] = 'ff0000'
        colors[45] = 'ff0000'

        #colors = ['ff0000']*48

        #print(colors)
        send_video_sync_data(colors, dev)
imkebe commented 2 years ago

I confirm - same behaviour here. I'll try to do some tests to debug this from my point of view.

imkebe commented 2 years ago

Black (#000000) for led #40 also freezes the ring but it gives blue color. However I was able to recover few times without reseting the screen. As last resort it may be needed to find closest safe color and just map problematic ones respectively.

Overall I synced it with Prismatic/Lightpack using sleep(0.01) sync and 10ms screen grabbing interval and I must say that it works very well while watching 4k HDR movie. I'll do some cleanup and sent a MR later.

However I don't like unfinished bussines so we need to investigate why the heck it's so wacky.

imkebe commented 2 years ago

There is some kind of buffer overflow when using extreeme color comopnent values.

I observe that problem occurs for different leds with different color components. it's safe to assume that we can't set values below 0x01. Example.

colors = ['000001']*48
colors[19] = '0001ff'

gives blue

colors = ['000001']*48
colors[19] = '0000ff'

gives green

same is with colors[38] (it's a twice of 19)

colors = ['000001']*48
        colors[20] = 'ffff02'
        colors[40] = 'ffff02'

However when you set base black color as #010101 everything seems to be fine

P.S. in addition import rich as it provides nice terminal coloring features for below test

#!/usr/bin/env python3

from unittest.util import _count_diff_all_purpose
import hid
from rich import print
import random
import string
from sys import exit
from time import sleep
from lib27gn950 import *

monitors = find_monitors()

def rgb_to_hex(rgb):
    return '%02x%02x%02x' % rgb

BASE_BLACK = '010101'

with hid.Device(path=monitors[0]['path']) as dev:
    send_command(control_commands['color_dynamic'], dev)
    sleep(1)
    send_command(control_commands['color_video_sync'], dev)

    while True:

        # quit()
        colors = []
        colors = ['ffffff']*48

        for rgb in range(0,4):
            if rgb == 1:
                col = 'ff0101'
            elif rgb == 2:
                col = '01ff01'
            elif rgb == 3:
                col = '0101ff'
            else:
                col = 'ffffff'

            for bri in range(1,12, 3):
                send_command(brightness_commands[bri],dev)
                print (":sun: {}".format(bri), end ='\n')
                for y in range(0,48):
                    colors = [BASE_BLACK]*48
                    colors[y] = col
                    sleep(0.01)
                    print ("[#{}]█[/#{}]".format(col,col), end ='')
                    send_video_sync_data(colors, dev)
                print("\n")
            print("\n")

        colors = [BASE_BLACK]*48
        for r in range(1,256,8):
            for g in range(1,256,8):
                for b in range(1,256,8):
                    #sleep(0.01)
                    #print (r,g,b)
                    colorcheck = rgb_to_hex((r,g,b))
                    colors[5] = colorcheck
                    colors[40] = colorcheck
                    colors[38] = colorcheck
                    colors[19] = colorcheck
                    print("[#{}]█[/#{}]".format(colorcheck,colorcheck),end='')
                    if 255 == b:
                        print ("\n")
                    send_video_sync_data(colors, dev)

So, yes. It turns out that we can't turn off any of the leds. Just put them into minimal state.

subraizada3 commented 2 years ago

Thanks for the help with that, imkebe.

I added a patch to the send_video_sync_data function which implements the 'every channel must be at least 0x01' workaround. It does a bit of string manipulation, but it's still fast enough to not show up on the first page of my system monitor with a 40ms interval (so under 1% CPU usage).

If you set the entire screen to red, USB packet capture does show that the LG software sets the LEDs to [ff0000]*48... but this looks like a 'good enough' workaround, especially since we have limited debug capability in the montior.

Now that the library has video sync fully implemented and working, I'm marking this issue as done. Created a new issue for 'implement a video sync GUI.'

Also, feel free to submit a pull request for your Prismatic work, if that's something you want to have included in this repository once it's written.