Browse Source

add proof of concept client

Thomas B 2 months ago
parent
commit
b89f5a75f8
6 changed files with 194 additions and 46 deletions
  1. 1
    0
      .gitignore
  2. 61
    0
      client/brightness.py
  3. 75
    0
      client/ddc.py
  4. 0
    45
      client/fetch.py
  5. 56
    0
      client/lux.py
  6. 1
    1
      client/plot.sh

+ 1
- 0
.gitignore View File

@@ -4,3 +4,4 @@
4 4
 sensor/build
5 5
 sensor/sensor.*
6 6
 sensor/compile_commands.json
7
+__pycache__

+ 61
- 0
client/brightness.py View File

@@ -0,0 +1,61 @@
1
+#!/usr/bin/env python
2
+
3
+import lux
4
+import ddc
5
+import time
6
+
7
+filter_fact = 0.9
8
+
9
+# out = out_b + out_a * in_a * (in_b + in)
10
+c_in = 1.0, -40.0, # in_a, in_b
11
+calibration = {
12
+    "HPN:HP 27xq:CNK1072BJY": [
13
+        1.0, 30.0, # out_a, out_b
14
+    ],
15
+
16
+    "MSI:MSI G27CQ4:": [
17
+        1.0, 20.0, # out_a, out_b
18
+    ],
19
+}
20
+
21
+def cal(v, c):
22
+    return c[1] + c[0] * c_in[0] * (c_in[1] + v)
23
+
24
+def filter_lux(old, new):
25
+    return (old * filter_fact) + (new * (1.0 - filter_fact))
26
+
27
+def lux_to_disp(name, val):
28
+    if name in calibration:
29
+        val = cal(int(val), calibration[name])
30
+    else:
31
+        raise ValueError("no calibration for \"{}\"".format(name))
32
+    val = int(val)
33
+    return min(max(val, 0), 100)
34
+
35
+if __name__ == "__main__":
36
+    usb = lux.usb_init()
37
+    lux.check_connection(usb)
38
+
39
+    disps = ddc.ddc_detect()
40
+    brightness = lux.read_brightness(usb)
41
+    print("Brightness:", brightness)
42
+
43
+    for d in disps:
44
+        d["prev"] = ddc.ddc_get(d["id"])
45
+        print("Display \"{}\" at {}".format(d["name"], d["prev"]))
46
+
47
+    print()
48
+    print("Starting main loop")
49
+    print()
50
+
51
+    while True:
52
+        brightness = filter_lux(brightness, lux.read_brightness(usb))
53
+
54
+        for d in disps:
55
+            val = lux_to_disp(d["name"], brightness)
56
+            if val != d["prev"]:
57
+                d["prev"] = val
58
+                print("Setting \"{}\" to {}".format(d["name"], val))
59
+                ddc.ddc_set(d["id"], val)
60
+
61
+        time.sleep(1.0)

+ 75
- 0
client/ddc.py View File

@@ -0,0 +1,75 @@
1
+#!/usr/bin/env python
2
+
3
+import subprocess
4
+
5
+def ddc_detect():
6
+    r = subprocess.run(["ddcutil", "detect", "-t"], capture_output=True)
7
+    if r.returncode != 0:
8
+        raise ValueError("ddcutil returned {}".format(r.returncode))
9
+
10
+    out = []
11
+
12
+    displays = r.stdout.decode("utf-8").split("\n\n")
13
+    for disp in displays:
14
+        data = disp.split("\n")
15
+        field = {}
16
+
17
+        if len(data) < 4:
18
+            continue
19
+
20
+        # Display X
21
+        num = data[0].split()
22
+        if num[0] != "Display":
23
+            #raise ValueError("unexpected identifier (\"{}\" != \"Display\")".format(num[0]))
24
+            continue
25
+        field["id"] = num[1]
26
+
27
+        # Monitor: name ...
28
+        name = data[3].split()
29
+        if name[0] != "Monitor:":
30
+            #raise ValueError("unexpected identifier (\"{}\" != \"Monitor:\")".format(name[0]))
31
+            continue
32
+        field["name"] = ' '.join(name[1:])
33
+
34
+        out.append(field)
35
+
36
+    return out
37
+
38
+def ddc_get(dev):
39
+    r = subprocess.run(["ddcutil", "-d", str(dev), "-t", "getvcp", "10"], capture_output=True)
40
+    if r.returncode != 0:
41
+        raise ValueError("ddcutil returned {}".format(r.returncode))
42
+
43
+    s = r.stdout.decode("utf-8").split()
44
+    if (s[0] != "VCP") or (s[1] != "10") or (s[2] != "C") or (s[4] != "100"):
45
+        raise ValueError("unexpected identifier")
46
+
47
+    return int(s[3])
48
+
49
+def ddc_set(dev, val):
50
+    val = int(val)
51
+    if (val < 0) or (val > 100):
52
+        raise ValueError("out of range")
53
+
54
+    r = subprocess.run(["ddcutil", "-d", str(dev), "-t", "setvcp", "10", str(val)], capture_output=True)
55
+    if r.returncode != 0:
56
+        raise ValueError("ddcutil returned {}".format(r.returncode))
57
+
58
+if __name__ == "__main__":
59
+    devs = ddc_detect()
60
+    for dev in devs:
61
+        print("Display:", dev["id"], dev["name"])
62
+
63
+        b = ddc_get(dev["id"])
64
+        print("Brightness:", b)
65
+
66
+        new = 50
67
+        if b == 50:
68
+            new = 60
69
+        print("Setting to {}...".format(new))
70
+        ddc_set(dev["id"], new)
71
+
72
+        b = ddc_get(dev["id"])
73
+        print("Brightness:", b)
74
+
75
+        print()

+ 0
- 45
client/fetch.py View File

@@ -1,45 +0,0 @@
1
-#!/usr/bin/env python
2
-
3
-import usb.core
4
-import usb.util
5
-import time
6
-
7
-CUSTOM_RQ_ECHO = 0 # send back wValue and wIndex, for testing comms reliability
8
-CUSTOM_RQ_RESET = 1 # reset to bootloader
9
-CUSTOM_RQ_GET = 2 # get ldr value
10
-
11
-max_comm_retries = 5;
12
-
13
-def is_target_device(dev):
14
-    if dev.manufacturer == "xythobuz.de" and dev.product == "AutoBrightness":
15
-        return True
16
-    return False
17
-
18
-# find our device
19
-dev = usb.core.find(idVendor=0x16c0, idProduct=0x05dc, custom_match=is_target_device)
20
-
21
-# was it found?
22
-if dev is None:
23
-    raise ValueError('Device not found')
24
-
25
-# configure the device
26
-dev.set_configuration()
27
-
28
-def read_val(req, w, a=0, b=0):
29
-    for attempts in range(0, max_comm_retries):
30
-        try:
31
-            r = dev.ctrl_transfer(usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, req, a, b, w)
32
-            return int.from_bytes(r, "little")
33
-        except usb.core.USBError as e:
34
-            if attempts >= (max_comm_retries - 1):
35
-                raise e
36
-
37
-# check for proper connectivity
38
-if read_val(CUSTOM_RQ_ECHO, 4, 42, 23) != (42 | (23 << 16)):
39
-    raise ValueError("invalid echo response")
40
-
41
-# repeatedly print value
42
-while True:
43
-    v = read_val(CUSTOM_RQ_GET, 2)
44
-    print(time.time(), ";", v, flush=True)
45
-    time.sleep(0.25)

+ 56
- 0
client/lux.py View File

@@ -0,0 +1,56 @@
1
+#!/usr/bin/env python
2
+
3
+import usb.core
4
+import usb.util
5
+import time
6
+
7
+CUSTOM_RQ_ECHO = 0 # send back wValue and wIndex, for testing comms reliability
8
+CUSTOM_RQ_RESET = 1 # reset to bootloader
9
+CUSTOM_RQ_GET = 2 # get ldr value
10
+
11
+max_comm_retries = 5;
12
+
13
+def is_target_device(dev):
14
+    if dev.manufacturer == "xythobuz.de" and dev.product == "AutoBrightness":
15
+        return True
16
+    return False
17
+
18
+def usb_init():
19
+    # find our device
20
+    dev = usb.core.find(idVendor=0x16c0, idProduct=0x05dc, custom_match=is_target_device)
21
+
22
+    # was it found?
23
+    if dev is None:
24
+        raise ValueError('Device not found')
25
+
26
+    # configure the device
27
+    dev.set_configuration()
28
+
29
+    return dev
30
+
31
+def read_val(dev, req, w, a=0, b=0):
32
+    for attempts in range(0, max_comm_retries):
33
+        try:
34
+            r = dev.ctrl_transfer(usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, req, a, b, w)
35
+            return int.from_bytes(r, "little")
36
+        except usb.core.USBError as e:
37
+            if attempts >= (max_comm_retries - 1):
38
+                raise e
39
+
40
+def check_connection(dev):
41
+    # check for proper connectivity
42
+    if read_val(dev, CUSTOM_RQ_ECHO, 4, 42, 23) != (42 | (23 << 16)):
43
+        raise ValueError("invalid echo response")
44
+
45
+def read_brightness(dev):
46
+    return read_val(dev, CUSTOM_RQ_GET, 2)
47
+
48
+if __name__ == "__main__":
49
+    dev = usb_init()
50
+    check_connection(dev)
51
+
52
+    # repeatedly print value
53
+    while True:
54
+        v = read_brightness(dev)
55
+        print(time.time(), ";", v, flush=True)
56
+        time.sleep(0.25)

+ 1
- 1
client/plot.sh View File

@@ -5,4 +5,4 @@
5 5
 # enter script directory
6 6
 cd "$(dirname "$0")"
7 7
 
8
-./fetch.py | ttyplot -t "Brightness"
8
+./lux.py | ttyplot -t "Brightness"

Loading…
Cancel
Save