Updated README and improved multimonitor support
This commit is contained in:
parent
bc4bc9afdb
commit
caa16c27d5
8 changed files with 199 additions and 33 deletions
142
README.md
142
README.md
|
|
@ -1,7 +1,139 @@
|
|||
# ddc-mqtt
|
||||
# ddc-mqtt - A simple software Display Input switch for DDC-supporting monitors
|
||||
|
||||
Setup:
|
||||
[](https://www.buymeacoffee.com/moimartb)
|
||||
|
||||
0. Connect a device where to run this to a DDC enabled port of your monitor— I use a raspberry pi with a GN95C Samsung display
|
||||
1. create a config.yml (copy it from rename_to_config.yml) for your mqtt credentials and monitor setup (Use ddcutil to get inputs and codes)
|
||||
2. docker-compose up -d
|
||||
- Do you have a displays with multiple inputs?
|
||||
- Would you like to be able to switch inputs without the need of meddling with clunky OSD menus and akwardly-positioned control 'nipples'?
|
||||
- Does this happen to you even with fancy, expensive displays like the Neo G9 line from Samsung?
|
||||
- Can you imagine KVM scenarios based on home assistant scenarios?
|
||||
|
||||
Then this software is for you!!!
|
||||
|
||||
You only need a Linux machine connected to the display, a bit of knowledge on your monitor's support of DDC capabilities and this will
|
||||
create a device for your
|
||||
|
||||
## Display's DDC capabilities
|
||||
|
||||
First of all, you'd need some info about your display about DDC capabilities wrt display inputs. You'd need the *ddcutil* tool to figure
|
||||
things out. For example: this is what my G95NC Samsung Neo G9 Reports:
|
||||
|
||||
```
|
||||
$ ddcutil capabilities
|
||||
Model: FALCON
|
||||
MCCS version: 2.0
|
||||
Commands:
|
||||
Op Code: 01 (VCP Request)
|
||||
Op Code: 02 (VCP Response)
|
||||
Op Code: 03 (VCP Set)
|
||||
Op Code: 07 (Timing Request)
|
||||
Op Code: 0C (Save Settings)
|
||||
Op Code: E3 (Capabilities Reply)
|
||||
Op Code: F3 (Capabilities Request)
|
||||
VCP Features:
|
||||
Feature: 02 (New control value)
|
||||
Feature: 04 (Restore factory defaults)
|
||||
Feature: 05 (Restore factory brightness/contrast defaults)
|
||||
Feature: 08 (Restore color defaults)
|
||||
Feature: 10 (Brightness)
|
||||
Feature: 12 (Contrast)
|
||||
Feature: 14 (Select color preset)
|
||||
Values:
|
||||
05: 6500 K
|
||||
08: 9300 K
|
||||
0b: User 1
|
||||
0c: User 2
|
||||
Feature: 16 (Video gain: Red)
|
||||
Feature: 18 (Video gain: Green)
|
||||
Feature: 1A (Video gain: Blue)
|
||||
Feature: 52 (Active control)
|
||||
Feature: 60 (Input Source)
|
||||
Values:
|
||||
01: VGA-1
|
||||
03: DVI-1
|
||||
04: DVI-2
|
||||
11: HDMI-1
|
||||
12: HDMI-2
|
||||
0f: DisplayPort-1
|
||||
10: DisplayPort-2
|
||||
Feature: 62 (Audio speaker volume)
|
||||
Feature: 8D (Audio Mute)
|
||||
Feature: FF (Manufacturer specific feature)
|
||||
```
|
||||
|
||||
We'll focus on the **Feature: 60 (Input Source)** ... I have good news and bad news:
|
||||
|
||||
1. Good news: The display supports changing inputs with DDC!
|
||||
2. Bad news: **The inputs and the values are, for the most part, are bullsh\*t!!**
|
||||
|
||||
Because of this, we will need to figure out the actual input values for each actual input of the monitor.
|
||||
|
||||
You can use **ddcutil** again to figure them out like so:
|
||||
|
||||
```
|
||||
# change the last number to figure the actual values that change the input. Have in mind these are hexadecimal values you'll need to convert to integers
|
||||
$ sudo ddcutil setvcp 0x60 0x01
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Once you have the values for the inputs you want to control, you can fill out the config.yml inside the folder ddc-mqtt that you'd need for the software to run. For my display this would be:
|
||||
|
||||
```
|
||||
mqtt:
|
||||
username: #your mqtt server's username
|
||||
password: #your mqtt server's password
|
||||
host: 1.2.3.4 #your mqtt server's host
|
||||
port: 1883
|
||||
display:
|
||||
- id: 1 #display to control. if only a single monitor, this must be 1
|
||||
inputs:
|
||||
HDMI1: 5 # key: value — you can change the name of the input. The codes here are as integers, not hexadecimal
|
||||
HDMI2: 6
|
||||
HDMI3: 7
|
||||
DP: 15
|
||||
interval: 20
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
You might want to use the docker container that comes with the code. If you use a Raspberry Pi you just do:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And if your config is well formed, that should be it!
|
||||
|
||||
If you don't use a Raspberry Pi, you might need to pay attention to the *docker-compose.yml* in the root folder as we are
|
||||
forwarding the i2c devices for the ddcutil library to access.
|
||||
|
||||
```
|
||||
devices:
|
||||
- "/dev/i2c-20:/dev/i2c-20"
|
||||
- "/dev/i2c-21:/dev/i2c-21" #these might be different in your machine
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If your MQTT server is shared or is part of a *Home Assistant* setup, the usage is simple. You'll get a device with switches to change inputs and that's all!
|
||||
|
||||
'
|
||||
|
||||
If you want to use it MQTT RAW!!! you'd have access to the entities in the following paths:
|
||||
|
||||
```
|
||||
Subscribe to kikkei/display-kvm/{number_of_display}/{input_name_in_the_config}/state -> with values 'true' or 'false' to check the state
|
||||
Publish 'ON' to kikkei/display-kvm/{number_of_display}/{input_name_in_the_config}/command for activation -> no need to send 'OFF'; switching is taken care of
|
||||
|
||||
Example: kikkei/display-kvm/1/HDMI1/state
|
||||
```
|
||||
|
||||
## Observations
|
||||
|
||||
This software is polling at an interval to re-check of the state of the active input— just in case you switch it manually...
|
||||
|
||||
This interval can be configured in the config.yml with the key interval. It is optional and by default is 20 seconds...
|
||||
|
||||
I coded this to support multiple monitors but I have not tested it at all... contributions are very welcome! :)
|
||||
|
||||
**Hope it works for you as well as it works for me!**
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
device = {
|
||||
"identifiers": ["Kikkei Labs Display KVM"],
|
||||
"name": "Display KVM",
|
||||
"name": "Display KVM #?",
|
||||
"model": "Kikkei-display-kvm-0",
|
||||
"manufacturer": "Kikkei Labs",
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ device = {
|
|||
|
||||
display_device = {
|
||||
"identifiers": ["Input"],
|
||||
"name": "Display Input KVM",
|
||||
"name": "Display Input KVM #?",
|
||||
"model": "Kikkei-display-kvm",
|
||||
"manufacturer": "Kikkei Labs",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ mqtt:
|
|||
host:
|
||||
port: 1883
|
||||
display:
|
||||
id: 1
|
||||
inputs:
|
||||
hdmi1: 5
|
||||
hdmi2: 6
|
||||
hdmi3: 7
|
||||
dp: 15
|
||||
|
||||
- id: 1
|
||||
inputs:
|
||||
HDMI1: 5
|
||||
HDMI2: 6
|
||||
HDMI3: 7
|
||||
DP: 15
|
||||
interval: 20
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class Service:
|
|||
config["mqtt"]["host"],
|
||||
config["mqtt"]["port"])
|
||||
|
||||
poll_interval = 20 if "interval" not in config else config["interval"]
|
||||
|
||||
self.mqtt.delegate = self
|
||||
|
||||
self.inputs = {}
|
||||
|
|
@ -30,27 +32,24 @@ class Service:
|
|||
|
||||
print(display_data)
|
||||
|
||||
self.inputs = {
|
||||
display_data['id']: {
|
||||
"switches": []
|
||||
}
|
||||
}
|
||||
self.inputs = {}
|
||||
|
||||
for input_name, input_code in display_data['inputs'].items():
|
||||
self.create_display_switch(display_data['id'],input_name,input_code)
|
||||
for display in display_data:
|
||||
self.inputs[display['id']] = { "switches": [] }
|
||||
for input_name, input_code in display['inputs'].items():
|
||||
self.create_display_switch(display['id'],input_name,input_code)
|
||||
|
||||
self.timer = Timer(30, self)
|
||||
self.timer = Timer(poll_interval, self)
|
||||
self.update_inputs_states()
|
||||
|
||||
def update_inputs_states(self):
|
||||
input_code = simpleddc.show_input()
|
||||
|
||||
print(f"Input code is {input_code}")
|
||||
|
||||
for display_id in self.inputs.keys():
|
||||
input_code = simpleddc.show_input(int(display_id))
|
||||
|
||||
print(f"Input code is {input_code}")
|
||||
|
||||
for entry in self.inputs[display_id]["switches"]:
|
||||
if entry["code"] == input_code:
|
||||
print("Found {}".format(entry["topic"]))
|
||||
self.mqtt.client.publish(entry["topic"], "true")
|
||||
entry["state"] = True
|
||||
else:
|
||||
|
|
@ -101,7 +100,7 @@ class Service:
|
|||
config["state_topic"] = config["state_topic"].replace("?", input_name)
|
||||
|
||||
device = display_device.copy()
|
||||
device["name"] = device["name"].replace("#", input_name)
|
||||
device["name"] = device["name"].replace("?", input_name)
|
||||
device["model"] = "{}-{}".format(device["model"],display_id)
|
||||
config["device"] = device
|
||||
|
||||
|
|
|
|||
BIN
hass_display_kvm.jpg
Normal file
BIN
hass_display_kvm.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -7,6 +7,7 @@
|
|||
#include <ddcutil_status_codes.h>
|
||||
|
||||
DDCA_Display_Handle * open_first_display_by_dlist();
|
||||
DDCA_Display_Handle * open_display_by_dlist(unsigned int display);
|
||||
DDCA_Status switch_input(DDCA_Display_Handle* handle, uint8_t input);
|
||||
uint8_t show_any_value(
|
||||
DDCA_Display_Handle dh,
|
||||
|
|
|
|||
|
|
@ -19,9 +19,16 @@ static PyObject* switch_to_input(PyObject* self, PyObject* args) {
|
|||
}
|
||||
|
||||
static PyObject* show_input(PyObject* self, PyObject* args) {
|
||||
DDCA_Display_Handle* handle = open_first_display_by_dlist();
|
||||
uint8_t* display = malloc(sizeof(uint8_t));
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i", display))
|
||||
*display = 0;
|
||||
|
||||
DDCA_Display_Handle* handle = open_display_by_dlist(*display);
|
||||
int result = show_any_value(handle,DDCA_NON_TABLE_VCP_VALUE, 0x60);
|
||||
ddca_close_display(handle);
|
||||
|
||||
free(display);
|
||||
return PyLong_FromLong(result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
ddca_rc_desc(status_code)); \
|
||||
} while(0)
|
||||
|
||||
DDCA_Display_Handle * open_first_display_by_dlist() {
|
||||
DDCA_Display_Handle * open_first_display_by_dlist()
|
||||
{
|
||||
printf("Check for monitors using ddca_get_displays()...\n");
|
||||
DDCA_Display_Handle dh = NULL;
|
||||
|
||||
|
|
@ -20,13 +21,39 @@ DDCA_Display_Handle * open_first_display_by_dlist() {
|
|||
|
||||
if (dlist->ct == 0) {
|
||||
printf(" No DDC capable displays found\n");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
DDCA_Display_Info * dinf = &dlist->info[0];
|
||||
DDCA_Display_Ref * dref = dinf->dref;
|
||||
printf("Opening display %s\n", dinf->model_name);
|
||||
printf("Model: %s\n", dinf->model_name);
|
||||
//printf("Model: %s\n", dinf->mmid.model_name);
|
||||
|
||||
DDCA_Status rc = ddca_open_display2(dref, false, &dh);
|
||||
if (rc != 0) {
|
||||
DDC_ERRMSG("ddca_open_display2", rc);
|
||||
}
|
||||
}
|
||||
ddca_free_display_info_list(dlist);
|
||||
return dh;
|
||||
}
|
||||
|
||||
DDCA_Display_Handle * open_display_by_dlist(unsigned int display)
|
||||
{
|
||||
DDCA_Display_Handle dh = NULL;
|
||||
|
||||
// Inquire about detected monitors.
|
||||
DDCA_Display_Info_List* dlist = NULL;
|
||||
ddca_get_display_info_list2(
|
||||
false, // don't include invalid displays
|
||||
&dlist);
|
||||
|
||||
if (dlist->ct == 0 || dlist->ct < display) {
|
||||
printf(" No DDC capable displays found\n");
|
||||
} else {
|
||||
DDCA_Display_Info * dinf = &dlist->info[display - 1];
|
||||
DDCA_Display_Ref * dref = dinf->dref;
|
||||
printf("Opening display %s\n", dinf->model_name);
|
||||
printf("Model: %s\n", dinf->model_name);
|
||||
|
||||
DDCA_Status rc = ddca_open_display2(dref, false, &dh);
|
||||
if (rc != 0) {
|
||||
DDC_ERRMSG("ddca_open_display2", rc);
|
||||
|
|
|
|||
Loading…
Reference in a new issue