diff --git a/firmware/README.md b/firmware/README.md index 0b29108..ce31d95 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -12,7 +12,7 @@ Build environment tools: ### Generate image ```bash -esptool.py --chip esp32 merge_bin -o ./public/image.bin --flash_mode dio --flash_size 4MB 0x1000 ./firmware/.pio/build/esp32dev/bootloader.bin 0x8000 ./firmware/.pio/build/esp32dev/partitions.bin 0x10000 ./firmware/.pio/build/esp32dev/firmware.bin +esptool.py --chip esp32 merge_bin -o image.bin --flash_mode dio --flash_size 4MB 0x1000 .pio/build/esp32dev/bootloader.bin 0x8000 .pio/build/esp32dev/partitions.bin 0x10000 .pio/build/esp32dev/firmware.bin ``` ### Flash Image diff --git a/firmware/include/http.h b/firmware/include/http.h index 73a0b7e..534db53 100644 --- a/firmware/include/http.h +++ b/firmware/include/http.h @@ -3,7 +3,7 @@ #include -void handleHttpRequest(WiFiClient &client); +void handleHttpRequest(WiFiClient& client); void httpSetup(); void httpLoop(); diff --git a/firmware/include/improv.h b/firmware/include/improv.h index dbd3a9f..55cbe5b 100644 --- a/firmware/include/improv.h +++ b/firmware/include/improv.h @@ -8,7 +8,7 @@ extern ImprovWiFi improvSerial; void wifiSetup(); void wifiLoop(); void onImprovWiFiErrorCb(ImprovTypes::Error err); -void onImprovWiFiConnectedCb(const char *ssid, const char *password); -bool connectWifi(const char *ssid, const char *password); +void onImprovWiFiConnectedCb(const char* ssid, const char* password); +bool connectWifi(const char* ssid, const char* password); #endif diff --git a/firmware/lib/ddc/ddc.cpp b/firmware/lib/ddc/ddc.cpp index f0d3b26..50d2548 100644 --- a/firmware/lib/ddc/ddc.cpp +++ b/firmware/lib/ddc/ddc.cpp @@ -1,23 +1,18 @@ #include "Arduino.h" #include "ddc.h" -DDC::DDC() -{ -} +DDC::DDC() {} -bool DDC::begin() -{ +bool DDC::begin() { Wire.begin(); Wire.beginTransmission(_I2CAddress); - if (Wire.endTransmission() == 0) - { + if (Wire.endTransmission() == 0) { return true; } return false; } -void DDC::setVCP(byte op, uint16_t value) -{ +void DDC::setVCP(byte op, uint16_t value) { Wire.beginTransmission(_I2CAddress); Wire.write(0x51); Wire.write(0x84); @@ -25,18 +20,19 @@ void DDC::setVCP(byte op, uint16_t value) Wire.write(op); Wire.write(byte(value >> 8)); Wire.write(byte(value)); - Wire.write((_I2CAddress << 1) ^ 0x51 ^ 0x84 ^ 0x03 ^ op ^ byte(value >> 8) ^ byte(value)); // XOR checksum. Include all bytes sent, including slave address! + Wire.write((_I2CAddress << 1) ^ 0x51 ^ 0x84 ^ 0x03 ^ op ^ byte(value >> 8) ^ + byte(value)); // XOR checksum. Include all bytes sent, including slave address! Wire.endTransmission(); } -uint16_t DDC::getVCP(byte op) -{ +uint16_t DDC::getVCP(byte op) { Wire.beginTransmission(_I2CAddress); Wire.write(0x51); Wire.write(0x82); Wire.write(0x01); Wire.write(op); - Wire.write((_I2CAddress << 1) ^ 0x51 ^ 0x82 ^ 0x01 ^ op); // XOR checksum. Include all bytes sent, including slave address! + Wire.write((_I2CAddress << 1) ^ 0x51 ^ 0x82 ^ 0x01 ^ + op); // XOR checksum. Include all bytes sent, including slave address! Wire.endTransmission(); delay(40); Wire.requestFrom(_I2CAddress, (uint8_t)12); @@ -52,50 +48,168 @@ uint16_t DDC::getVCP(byte op) return (response[8] << 8) + response[9]; } -void DDC::setBrightness(int value) -{ - if (value > 100) - { +void DDC::setBrightness(int value) { + if (value > 100) { value = 100; } setVCP(0x10, value); } -uint16_t DDC::getBrightness() -{ +uint16_t DDC::getBrightness() { return getVCP(0x10); } // 0x01:VGA, 0x03:DVI, 0x0f:DP -void DDC::setSource(uint16_t value) -{ +void DDC::setSource(uint16_t value) { setVCP(0x60, value); } -uint16_t DDC::getSource() -{ +uint16_t DDC::getSource() { return getVCP(0x60); } // true:on, false:suspend -void DDC::setPower(bool value) -{ - if (value) - { +void DDC::setPower(bool value) { + if (value) { setVCP(0xD6, 0x01); - } - else - { + } else { setVCP(0xD6, 0x03); } } -bool DDC::getPower() -{ +bool DDC::getPower() { byte power = getVCP(0xD6); - if (power == 0x01) - { + if (power == 0x01) { return true; } return false; } + +void DDC::getEDID(uint8_t* edid) { + for (int i = 0; i < 128; i += 16) { + Wire.beginTransmission(0x50); + Wire.write(i); + Wire.endTransmission(); + Wire.requestFrom(0x50, 16); + for (int j = 0; j < 16 && Wire.available(); j++) { + edid[i + j] = Wire.read(); + } + } +} + +String DDC::getMfg() { + uint8_t edid[128]; + getEDID(edid); + + char mfg[4] = {0}; + uint8_t byte1 = edid[8]; + uint8_t byte2 = edid[9]; + + mfg[0] = ((byte1 & 0x7C) >> 2) + '@'; + mfg[1] = ((byte1 & 0x03) << 3) + ((byte2 & 0xE0) >> 5) + '@'; + mfg[2] = (byte2 & 0x1F) + '@'; + + return String(mfg); +} + +String DDC::getModel() { + uint8_t edid[128]; + getEDID(edid); + + for (int i = 54; i < 128; i += 18) { + if (edid[i] == 0x00 && edid[i + 1] == 0x00 && edid[i + 2] == 0x00 && edid[i + 3] == 0xFC) { + String model = ""; + for (int j = i + 5; j < i + 18; j++) { + if (edid[j] == 0x0A || edid[j] == 0x20) { + break; + } + model += (char)edid[j]; + } + return model; + } + } + + return ""; +} + +String DDC::getProduct() { + uint8_t edid[128]; + getEDID(edid); + + uint16_t productCode = (edid[11] << 8) | edid[10]; + + return String(productCode); +} + +String DDC::getProductSerial() { + uint8_t edid[128]; + getEDID(edid); + + for (int i = 54; i < 128; i += 18) { + if (edid[i] == 0x00 && edid[i + 1] == 0x00 && edid[i + 2] == 0x00 && edid[i + 3] == 0xFF) { + String serial = ""; + for (int j = i + 5; j < i + 18; j++) { + if (edid[j] == 0x0A || edid[j] == 0x20) { + break; + } + serial += (char)edid[j]; + } + return serial; + } + } + + return ""; +} + +String DDC::getSerial() { + uint8_t edid[128]; + getEDID(edid); + + uint32_t serialNumber = + (edid[0x0F] << 24) | (edid[0x0E] << 16) | (edid[0x0D] << 8) | edid[0x0C]; + + char hexString[11]; + sprintf(hexString, "0x%08X", serialNumber); + + return String(hexString); +} + +uint32_t DDC::getSerialDecimal() { + uint8_t edid[128]; + getEDID(edid); + + uint32_t serialNumber = + (edid[0x0F] << 24) | (edid[0x0E] << 16) | (edid[0x0D] << 8) | edid[0x0C]; + + return serialNumber; +} + +uint16_t DDC::getYear() { + uint8_t edid[128]; + getEDID(edid); + + uint8_t yearOffset = edid[17]; + return 1990 + yearOffset; +} + +uint8_t DDC::getWeek() { + uint8_t edid[128]; + getEDID(edid); + + return edid[16]; +} + +String DDC::getVCP() { + uint16_t version = getVCP(0xDF); + uint8_t major = (version >> 8) & 0xFF; + uint8_t minor = version & 0xFF; + + String versionString = String(major) + "." + String(minor); + + if (versionString == "2.0" || versionString == "2.1" || versionString == "3.0" || + versionString == "2.2") { + return versionString; + } else { + return "Detection failed"; + } +} diff --git a/firmware/lib/ddc/ddc.h b/firmware/lib/ddc/ddc.h index 669a578..d6400a2 100644 --- a/firmware/lib/ddc/ddc.h +++ b/firmware/lib/ddc/ddc.h @@ -4,8 +4,7 @@ #include #include "Arduino.h" -class DDC -{ +class DDC { public: DDC(); bool begin(); @@ -17,6 +16,16 @@ class DDC uint16_t getSource(); void setPower(bool value); bool getPower(); + void getEDID(uint8_t* edid); + String getMfg(); + String getModel(); + String getProduct(); + String getProductSerial(); + String getSerial(); + uint32_t getSerialDecimal(); + uint16_t getYear(); + uint8_t getWeek(); + String getVCP(); private: uint8_t _I2CAddress = 0x37; diff --git a/firmware/src/http.cpp b/firmware/src/http.cpp index a4a4aab..087b459 100644 --- a/firmware/src/http.cpp +++ b/firmware/src/http.cpp @@ -5,13 +5,23 @@ extern WiFiServer server; extern DDC ddc; +extern bool ddcConnected; char linebuf[80]; int charcount = 0; -void handleSetBrightness(String request, WiFiClient& client); +void handleDisplays(WiFiClient& client); +void handleSetSmooth(WiFiClient& client, int startValue, int endValue); +void handleSmoothStatus(WiFiClient& client); void handleGetBrightness(WiFiClient& client); -void handleSetSource(String request, WiFiClient& client); +void handleSetBrightness(WiFiClient& client, int value); +void handleSetSource(WiFiClient& client, int value); +void handleDDCStatus(WiFiClient& client); +void handleDisplayPower(WiFiClient& client); +void handleDisplayPowerValue(WiFiClient& client); +void handleVersion(WiFiClient& client); +void handleUpdate(WiFiClient& client); +void handleFullUpdate(WiFiClient& client); void httpSetup() { server.begin(); @@ -39,85 +49,280 @@ void handleHttpRequest(WiFiClient& client) { if (c == '\n' && currentLineIsBlank) { String request = String(linebuf); - if (request.startsWith("GET /brightness?value=")) { - handleSetBrightness(request, client); - } else if (request.startsWith("GET /brightness")) { + + for (int i = 0; i < request.length(); i++) { + request[i] = tolower(request[i]); + } + + if (request.startsWith("get /displays")) { + handleDisplays(client); + } else if (request.startsWith("get /smooth/1/brightness/")) { + int startIndex = request.indexOf("/smooth/1/brightness/") + + String("/smooth/1/brightness/").length(); + int endIndex = request.indexOf("/", startIndex); + + String brightnessValueOneStr = request.substring(startIndex, endIndex); + int brightnessValueOne = brightnessValueOneStr.toInt(); + + startIndex = endIndex + 1; + endIndex = request.indexOf(" ", startIndex); + String brightnessValueTwoStr = request.substring(startIndex, endIndex); + int brightnessValueTwo = brightnessValueTwoStr.toInt(); + + handleSetSmooth(client, brightnessValueOne, brightnessValueTwo); + } else if (request.startsWith("get /smooth/1")) { + handleSmoothStatus(client); + } else if (request.startsWith("get /1/brightness/")) { + int startIndex = + request.indexOf("/1/brightness/") + String("/1/brightness/").length(); + int endIndex = request.indexOf(" ", startIndex); + + if (endIndex > startIndex) { + String brightnessValueStr = request.substring(startIndex, endIndex); + int brightnessValue = brightnessValueStr.toInt(); + handleSetBrightness(client, brightnessValue); + } else { + handleGetBrightness(client); + } + } else if (request.startsWith("get /1/brightness")) { handleGetBrightness(client); - } else if (request.startsWith("GET /set_source?value=")) { - handleSetSource(request, client); + } else if (request.startsWith("get /1/input_source/")) { + int startIndex = + request.indexOf("/1/input_source/") + String("/1/input_source/").length(); + int endIndex = request.indexOf(" ", startIndex); + + String sourceValueStr = request.substring(startIndex, endIndex); + int sourceValue = (int)strtol(sourceValueStr.c_str(), NULL, 0); + handleSetSource(client, sourceValue); + } else if (request.startsWith("get /1")) { + handleDDCStatus(client); + } else if (request.startsWith("get /display-power/")) { + handleDisplayPowerValue(client); + } else if (request.startsWith("get /display-power")) { + handleDisplayPower(client); + } else if (request.startsWith("get /version")) { + handleVersion(client); + } else if (request.startsWith("get /update")) { + handleUpdate(client); + } else if (request.startsWith("get /full-update")) { + handleFullUpdate(client); } else { - client.println("HTTP/1.1 404 Not Found"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("HTTP/1.1 400 Error"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 5"); client.println(); - client.println( - "{\"status\":\"error\", \"message\":\"endpoint doesn't exist\"}"); + client.println("Error"); } break; } + + if (c == '\n') { + currentLineIsBlank = true; + } else if (c != '\r') { + currentLineIsBlank = false; + } } } delay(1); client.stop(); } -void handleSetBrightness(String request, WiFiClient& client) { - int valueStart = request.indexOf('=') + 1; - int valueEnd = request.indexOf(' ', valueStart); - if (valueEnd == -1) { - valueEnd = request.length(); +void handleDisplays(WiFiClient& client) { + if (!ddcConnected) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 143"); + client.println(); + client.println("No displays found."); + client.println("Is DDC/CI enabled in the monitor's on screen display?"); + client.println("Run \"ddcutil environment\" to check for system configuration problems."); + return; } - String valueStr = request.substring(valueStart, valueEnd); - int brightnessValue = valueStr.toInt(); - if (brightnessValue >= 0 && brightnessValue <= 100) { - ddc.setBrightness(brightnessValue); + String mfg = ddc.getMfg(); + String model = ddc.getModel(); + String productCode = ddc.getProduct(); + String productSerial = ddc.getProductSerial(); + uint32_t serialDecimal = ddc.getSerialDecimal(); + String serial = ddc.getSerial(); + uint16_t year = ddc.getYear(); + uint8_t week = ddc.getWeek(); + String vcpVersion = ddc.getVCP(); + + String responseContent; + responseContent += "Display 1\n"; + responseContent += " I2C bus: /dev/i2c-2\n"; + responseContent += " EDID synopsis:\n"; + responseContent += " Mfg id: " + mfg + "\n"; + responseContent += " Model: " + model + "\n"; + responseContent += " Product code: " + productCode + "\n"; + responseContent += " Serial number: " + productSerial + "\n"; + responseContent += + " Binary serial number: " + String(serialDecimal) + " (" + serial + ")\n"; + responseContent += + " Manufacture year: " + String(year) + ", Week: " + String(week) + "\n"; + responseContent += " VCP version: " + vcpVersion + "\n"; + responseContent += "\n"; + + int contentLength = responseContent.length(); + + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.print("Content-Length: "); + client.println(contentLength); + client.println(); + client.print(responseContent); +} + +void handleSetSmooth(WiFiClient& client, int startValue, int endValue) { + int step = (startValue < endValue) ? 1 : -1; + int currentValue = startValue; + + while (currentValue != endValue) { + ddc.setBrightness(currentValue); + currentValue += step; + delay(50); + } + + ddc.setBrightness(endValue); + + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} + +void handleSmoothStatus(WiFiClient& client) { + if (ddcConnected) { client.println("HTTP/1.1 200 OK"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 2"); client.println(); - client.println("{\"status\":\"success\", \"brightness\":" + String(brightnessValue) + "}"); + client.println("OK"); } else { - client.println("HTTP/1.1 400 Bad Request"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("HTTP/1.1 400 Error"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 5"); client.println(); - client.println("{\"status\":\"error\", \"message\":\"brightness has to be from 0-100\"}"); + client.println("Error"); } } +void handleSetBrightness(WiFiClient& client, int value) { + ddc.setBrightness(value); + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} + void handleGetBrightness(WiFiClient& client) { - int currentBrightness = ddc.getBrightness(); + if (!ddcConnected) { + client.println("HTTP/1.1 400 Error"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 5"); + client.println(); + client.println("Error"); + return; + } + + int brightness = ddc.getBrightness(); + client.println("HTTP/1.1 200 OK"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.print("Content-Length: "); + client.println(String(brightness).length()); client.println(); - client.println("{\"status\":\"success\", \"current_brightness\":" + String(currentBrightness) + - "}"); + client.println(brightness); } -void handleSetSource(String request, WiFiClient& client) { - int valueStart = request.indexOf('=') + 1; - int valueEnd = request.indexOf(' ', valueStart); - if (valueEnd == -1) { - valueEnd = request.length(); - } - String valueStr = request.substring(valueStart, valueEnd); - unsigned int sourceValue = (unsigned int)strtol(valueStr.c_str(), NULL, 16); +void handleSetSource(WiFiClient& client, int value) { + ddc.setSource(value); + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} - if (sourceValue == 0x0f || sourceValue == 0x10 || sourceValue == 0x11 || sourceValue == 0x12) { - ddc.setSource(sourceValue); +void handleDDCStatus(WiFiClient& client) { + if (ddcConnected) { client.println("HTTP/1.1 200 OK"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 2"); client.println(); - client.println("{\"status\":\"success\", \"source\":\"0x" + String(sourceValue, HEX) + - "\"}"); + client.println("OK"); } else { - client.println("HTTP/1.1 400 Bad Request"); - client.println("Content-Type: application/json"); - client.println("Connection: close"); + client.println("HTTP/1.1 400 Error"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 5"); client.println(); - client.println("{\"status\":\"error\", \"message\":\"invalid source value\"}"); + client.println("Error"); } } + +// macOS Lunar Pi specific placeholders + +void handleDisplayPower(WiFiClient& client) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 16"); + client.println(); + client.println("display_power=1"); +} + +void handleDisplayPowerValue(WiFiClient& client) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} + +void handleVersion(WiFiClient& client) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 3"); + client.println(); + client.println("1.0"); +} + +void handleUpdate(WiFiClient& client) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} + +void handleFullUpdate(WiFiClient& client) { + client.println("HTTP/1.1 200 OK"); + client.println("Connection: keep-alive"); + client.println("X-Powered-By: Kemal"); + client.println("Content-Type: text/html"); + client.println("Content-Length: 0"); +} diff --git a/firmware/src/improv.cpp b/firmware/src/improv.cpp index 29a81b8..77dba38 100644 --- a/firmware/src/improv.cpp +++ b/firmware/src/improv.cpp @@ -4,7 +4,7 @@ #include "../include/improv.h" #include "../lib/improv/ImprovWiFiLibrary.h" -WiFiServer server(80); +WiFiServer server(3485); ImprovWiFi improvSerial(&Serial); Preferences preferences; @@ -44,7 +44,8 @@ void onImprovWiFiConnectedCb(const char* ssid, const char* password) { preferences.putString("password", password); server.begin(); - MDNS.begin("hardwareddc"); + MDNS.begin("ddcutil"); + MDNS.addService("_ddcutil", "_tcp", 3485); } bool connectWifi(const char* ssid, const char* password) { @@ -55,7 +56,8 @@ bool connectWifi(const char* ssid, const char* password) { } if (WiFi.status() == WL_CONNECTED) { - MDNS.begin("hardwareddc"); + MDNS.begin("ddcutil"); + MDNS.addService("_ddcutil", "_tcp", 3485); } return WiFi.status() == WL_CONNECTED; @@ -71,6 +73,7 @@ void connectToSavedWiFi(const char* ssid, const char* password) { if (WiFi.status() == WL_CONNECTED) { server.begin(); - MDNS.begin("hardwareddc"); + MDNS.begin("ddcutil"); + MDNS.addService("_ddcutil", "_tcp", 3485); } } diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 29148d4..20bbd35 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -1,16 +1,32 @@ +#include #include "../include/http.h" #include "../include/improv.h" #include "../lib/ddc/ddc.h" DDC ddc; +bool ddcConnected = false; -void brightnessSetup() { - Serial.begin(9600); +void ddcSetup() { + Serial.begin(115200); while (!ddc.begin()) { - Serial.print(F("DDC error. Trying again in 5 sec.\n")); + Serial.print(F("DDC didn't connect, wait 5 sec\n")); delay(5000); } - Serial.print(F("DDC found.\n")); + Serial.print(F("DDC found\n")); + ddcConnected = true; + + String mfg = ddc.getMfg(); + String model = ddc.getModel(); + String product = ddc.getProduct(); + String serial = ddc.getSerial(); + uint32_t serialDecimal = ddc.getSerialDecimal(); + String year = String(ddc.getYear()); + + MDNS.addServiceTxt("_ddcutil", "_tcp", "1:year", year.c_str()); + MDNS.addServiceTxt("_ddcutil", "_tcp", "1:serial", String(serialDecimal).c_str()); + MDNS.addServiceTxt("_ddcutil", "_tcp", "1:product", product.c_str()); + MDNS.addServiceTxt("_ddcutil", "_tcp", "1:model", model.c_str()); + MDNS.addServiceTxt("_ddcutil", "_tcp", "1:mfg", mfg.c_str()); } void setup() { @@ -18,7 +34,7 @@ void setup() { wifiSetup(); if (improvSerial.isConnected()) { httpSetup(); - brightnessSetup(); + ddcSetup(); } } diff --git a/ui/README.md b/ui/README.md index e9d3f87..686755d 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1,11 +1,21 @@ # User Interface -HardwareDDC's goal is to provide and easy and convenient way to adjust monitor settings. Therefore, I prioritized an efficient macro-based UX and have not yet release a GUI. +HardwareDDC's goal is to provide and easy and convenient way to adjust monitor settings. Therefore, I prioritized an efficient macro-based UX and have not yet released a first-party GUI. Currently, HardwareDDC is controlled with hotkeys sending API requests. ## macOS +### Lunar + +Lunar, _the defacto app for controlling monitors_, can be used as HardwareDDC's interface instead of Hammerspoon for those preferring a GUI. + +1. Install [Lunar](https://static.lunar.fyi/releases/Lunar.dmg) +2. Disable all controls aside from _Network (Raspberry Pi)_ +3. Reset _Network Control_ + +### Hammerspoon + 1. Install [Hammerspoon](https://www.hammerspoon.org) 2. Add [hardware-ddc.lua](./macos/hardware-ddc.lua) to your Hammerspoon config `~/.hammerspoon/init.lua` @@ -54,11 +64,6 @@ _Edit macros in [hardware-ddc.ahk](./windows/hardware-ddc.ahk)_ - ^⌥4 = 100% - ^⌥5 = 0% -

- -- ^F1- = -6% -- ^F2 = +6% - **Input Source:** - ^⌥h1 HDMI 1 @@ -77,17 +82,17 @@ This is coming soon, but you're a Linux user, you can figure it out. Specify brightness percentage: ```bash -curl -X GET "http://hardwareddc.local/brightness?value=" +curl -X GET "http://ddcutil.local:3485/1/brightness/" ``` Get current brightness: ```bash -curl -X GET "http://hardwareddc.local/brightness" +curl -X GET "http://ddcutil.local:3485/1/brightness" ``` Change input source: ```bash -curl -X GET "http://hardwareddc.local/set_source?value=" +curl -X GET "http://ddcutil.local:3485/1/input_source/" ``` diff --git a/ui/macos/hardware-ddc.lua b/ui/macos/hardware-ddc.lua index 090b9b1..fe3dcd2 100644 --- a/ui/macos/hardware-ddc.lua +++ b/ui/macos/hardware-ddc.lua @@ -1,5 +1,5 @@ function setSource(value, name) - local url = "http://hardwareddc.local/set_source?value=" .. value + local url = "http://ddcutil.local:3485/1/input_source/" .. value hs.http.asyncGet(url, nil, function(status, body, headers) end) end @@ -21,7 +21,7 @@ function setHDMI2() end function setBrightness(value) - local url = "http://hardwareddc.local/brightness?value=" .. value + local url = "http://ddcutil.local:3485/1/brightness/" .. value hs.http.asyncGet(url, nil, function(status, body, headers) end) end @@ -47,8 +47,8 @@ function setBrightness100() end function getBrightness(callback) - hs.http.asyncGet("http://hardwareddc.local/brightness", nil, function(_, body) - local brightness = hs.json.decode(body).current_brightness + hs.http.asyncGet("http://ddcutil.local:3485/1/brightness", nil, function(status, body) + local brightness = tonumber(body) callback(brightness) end) end diff --git a/ui/windows/hardware-ddc.ahk b/ui/windows/hardware-ddc.ahk index 175b726..0969177 100644 --- a/ui/windows/hardware-ddc.ahk +++ b/ui/windows/hardware-ddc.ahk @@ -1,40 +1,17 @@ -setSource(value) { - url := "http://hardwareddc.local/set_source?value=" value - Http := ComObjCreate("WinHttp.WinHttpRequest.5.1") - Http.Open("GET", url, false) - Http.Send() +setSource(source) { + url := "http://ddcutil.local:3485/1/input_source/" source + HttpRequest(url) } setBrightness(value) { - url := "http://hardwareddc.local/brightness?value=" value - Http := ComObjCreate("WinHttp.WinHttpRequest.5.1") - Http.Open("GET", url, false) - Http.Send() + url := "http://ddcutil.local:3485/1/brightness/" value + HttpRequest(url) } -getBrightness() { - url := "http://hardwareddc.local/brightness" - Http := ComObjCreate("WinHttp.WinHttpRequest.5.1") - Http.Open("GET", url, false) - Http.Send() - response := Http.ResponseText - RegExMatch(response, """current_brightness"":\s*(\d+)", match) - return match1 -} - -adjustBrightness(delta) { - currentBrightness := getBrightness() - newBrightness := currentBrightness + delta - newBrightness := Max(0, Min(100, newBrightness)) - setBrightness(newBrightness) -} - -increaseBrightness() { - adjustBrightness(6) -} - -decreaseBrightness() { - adjustBrightness(-6) +HttpRequest(url) { + WebRequest := ComObjCreate("MSXML2.XMLHTTP") + WebRequest.Open("GET", url, false) + WebRequest.Send() } ^#!1::setSource(0x0f) @@ -46,7 +23,4 @@ decreaseBrightness() { ^!2::setBrightness(50) ^!3::setBrightness(75) ^!4::setBrightness(100) -^!5::setBrightness(0) - -^f1::decreaseBrightness() -^f2::increaseBrightness() \ No newline at end of file +^!5::setBrightness(0) \ No newline at end of file diff --git a/web-flasher/public/firmware.bin b/web-flasher/public/firmware.bin index 4821aac..97a7bac 100644 Binary files a/web-flasher/public/firmware.bin and b/web-flasher/public/firmware.bin differ