Browse Source

checks colors on page load

kyle 1 year ago
parent
commit
28ebbdb7d2
1 changed files with 177 additions and 30 deletions
  1. 177 30
      web/index.php

+ 177 - 30
web/index.php

@@ -1,34 +1,70 @@
 <?php
-// check for a POST request and use curl to send data to the esp8266
+/*  wrapper for curl, sends a single parameter to the esp8266, no attempt is made to interpret
+ *  user data, except that we protect curl from possible injection.  Because protection is heavy
+ *  handed, end-user data can easily be mangled, as in, if it isn't formatted right it is going to
+ *  be forced through a correctly formatted die
+ */
+function curl($postParam, $len, $route, $varName = "var") {
+    $uri = "http://rain-gutter-rgb.lan.rome7.com/{$route}";
+    $unsafeUserData = "";
+    // start with an empty, off, or "zeroed" buffer
+    for($i = $len; $i--;) $unsafeUserData .= "0";
+    // [!] protect curl from injection, only allow hexidecimal digits 0..9a..fA..F]
+    for($i = $len; $i--; ) $unsafeUserData[$i] = (isset($_POST[$postParam][$i]) && ctype_xdigit($_POST[$postParam][$i]))
+        ? $_POST[$postParam][$i]
+        : '0';
+    $ch = curl_init();
+    error_log("sending [{$postParam}] '{$unsafeUserData}' to {$uri}");
+    curl_setopt($ch, CURLOPT_URL, $uri);
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, "{$postParam}={$unsafeUserData}");
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    $out = curl_exec($ch);
+    curl_close($ch);
+    return $out;
+}
+
+/*  check for a POST request and use curl to send data to the esp8266
+ *  default response is http bad request
+ *  passthru contains a simple description of the esp8266 api
+ */
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-    $code = 400;
-    $msg = strval($code) . " Bad Request";
-    //error_log("post: " . print_r($_POST, true));
-    if(isset($_POST["color"])) {
-        // adject code and message when valid post is provided
-        $code = 200;
-        $msg = strval($code) . " OK";
-        // start with off as default
-        $color = "000000";
-        // [!] protect curl from injection
-        for($i = 6; $i--; ) $color[$i] = (isset($_POST["color"][$i]) && ctype_xdigit($_POST["color"][$i]))
-            ? $_POST["color"][$i]
-            : '0';
+    $code = 400; $out = "";
+    $passthru = array(
+        // POST_VARIABLE => [buffer-length, api-endpoint]
+        "color" => array(6, "rgb"),
+        "brightness" => array(2, "brightness")
+    ); 
+    // execute the first matching command
+    foreach($passthru as $k => $v) {
+        if(!isset($_POST[$k])) continue; $out = curl($k, $v[0], $v[1]); $code = 200; break; }
+    // set headers
+    header("HTTP/1.0 " . strval($code) . (200 === $code ?" OK" : " Bad Request"));
+    $GLOBALS["http_response_code"] = $code;
+    //http_response_code($code);
+    exit("{$out}");
+} /* else GET request, check for quick response parameters -- do not generate whole page */ {
+    if(isset($_GET['req'])) {
+        // default is to get color as decimal [0..255] values R,G,B
+        $req = "dec";
+        switch($_GET['req']) {
+            case "color": $req = "dec"; break;
+            case "brightness": $req = "brightness"; break;
+            case "correction": $req = "cfg"; break;
+        }
+        $uri = "http://rain-gutter-rgb.lan.rome7.com/{$req}";
+        // send GET request to the esp8266
         $ch = curl_init();
-        curl_setopt($ch, CURLOPT_URL,"http://rain-gutter-rgb.lan.rome7.com/rgb");
-        curl_setopt($ch, CURLOPT_POST, 1);
-        curl_setopt($ch, CURLOPT_POSTFIELDS, "color={$color}");
+        error_log("sending [get: {$req}] via {$uri}");
+        curl_setopt($ch, CURLOPT_URL, $uri);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
         $out = curl_exec($ch);
         curl_close($ch);
+        exit($out);
     }
-    // set headers
-    header("HTTP/1.0 {$msg}");
-    $GLOBALS["http_response_code"] = $code;
-    //http_response_code($code);
-    exit("{$out}");
 }
 
+// otherwise - generate a whole web-page
 echo "<html>";
 echo "<body>";
 // either use mixbox non-commercial use under the CC BY-NC 4.0 license
@@ -64,6 +100,66 @@ console.log(color);
 </script>
 ";
 
+// common functions
+echo "
+<script>
+// using the newer fetch API, not sure if this is worse than AJAX
+async function post(url = '', kw = 'key', data = 'data') {
+    // to use _POST superblogal, send body as http multipart/form-data
+    let fd = new FormData();
+    fd.append(kw, data);
+    return await fetch(url, { method: 'POST', body: fd });
+}
+/*  I assume http get requests may use default
+ *  [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
+ *  parameters.
+ */
+function get(arg) { return fetch('?' + new URLSearchParams({req:arg})).then((v) => v.text()); }
+function getColor(selectedObj = null) {
+    // get the color from the esp8266, returns as decimal [0..255] R,G,B
+    let dec = get('color');
+    // split into red, green, and blue components:
+    let rgb = dec.then((v) => { let a = v.split(',').map((i) => parseInt(i, 10)); return {red: a[0], green: a[1], blue: a[2]}; });
+    rgb.then((v) => console.log('esp8266 color: ', v));
+    // by default, return as Promise that will resolve to rgb object { red: x, green: y, blue: z }
+    if(null === selectedObj) return rgb;
+    // otherwise set backround and text of the selected color object
+    rgb.then((v) => {
+        let a = [v.red, v.green, v.blue];
+        color = 'rgb(' + a.join(',') + ')';
+        let off = a.every((x) => 0 === x);
+        // use gray as a placeholder for 'lights off'
+        selectedObj.style.background = off ?'rgb(211, 211, 211)' :color;
+        selectedObj.textContent = color;
+    });
+}
+function getBrightness(sliderObj = null) {
+    // get the brightness from the esp8266, returns as decimal [0..15]
+    let brightness = get('brightness').then((v) => parseInt(v,10));
+    brightness.then((i) => { console.log('esp8266 brightness: ', i); });
+    // by default, return as Promise that will resolve to integer
+    if(null === sliderObj) return brightness;
+    // otherwise set slider object's value property to brightness when the promise resolves
+    brightness.then((i) => { sliderObj.value = i; });
+}
+function getCorrection() {
+    /*  get the correction config from the esp8266, returns as cfg [ setting:[0..1], ... setting_n:[0..1]]
+     *  settings currently include:
+     *      quickGamma - corrects for human perception using quadratic function
+     *      sCurveGamma - corrects for human perception using sigmoid function
+     *      colorCorrection - LED manufacturer correction, differences between doping used for different colors
+     *
+     *  + quickGamma and sCurveGamma can be turned on or off and are are mutually
+     *    exclusive to eachother
+     *  + colorCorrection may be turned on or off, and can be applied to either
+     *    gamma correction schemes
+     */
+    var cfg = get('correction');
+    return cfg;
+}
+</script>
+";
+
 // render a color selection wheel
 echo "
 <script>
@@ -71,7 +167,7 @@ var canvas = document.createElement('canvas');
 // google chrome performance flag
 //var context = canvas.getContext('2d');
 var context = canvas.getContext('2d', { willReadFrequently: true });
-var w = 200; var h = 200;
+var w = 400; var h = 400;
 canvas.width = w;
 canvas.height = h;
 
@@ -126,6 +222,9 @@ tableBodyRow.appendChild(selected);
 
 document.body.appendChild(table);
 
+// set the inital value of the selected color cell to the esp8266 lights
+getColor(selected);
+
 // listen to events from canvas
 function pick(event, destination, cb = false) {
   const bounding = canvas.getBoundingClientRect();
@@ -137,6 +236,7 @@ function pick(event, destination, cb = false) {
 
   const rgb = 'rgb(' + data[0] + ', ' + data[1] + ', ' + data[2] + ')';
   let off = 0 === (data[0] | data[1] | data[2]);
+  // use 211,211,211 (gray) as a placeholder for 'lights turned off'
   destination.style.background = off ?'rgb(211, 211, 211)' :rgb;
   destination.textContent = rgb;
 
@@ -155,18 +255,12 @@ canvas.addEventListener('click', (event) => pick(event, selected));
 // add a callback to the click event to send rgb value to esp8266
 echo "
 <script>
-// using the newer fetch API, not sure if this is worse than AJAX
-async function post(url = '', color = '000000') {
-    // to use _POST superblogal, send body as http multipart/form-data
-    let fd = new FormData();
-    fd.append('color', color);
-    return await fetch(url, { method: 'POST', body: fd });
-}
 function sg(x) { return Math.round((x*x)/255); }
 function callback(red, green, blue) {
     console.log('logged', red, green, blue);
     post(
         /* url */ '',
+        /* key */ 'color',
         sg(red).toString(16).padStart(2,'0') +
         sg(green).toString(16).padStart(2,'0') +
         sg(blue).toString(16).padStart(2,'0')
@@ -178,5 +272,58 @@ canvas.addEventListener('click', (event) => pick(event, selected, callback));
 </script>
 ";
 
+/*  make a brightness slider, esp8266 takes 0..15 levels of brightness
+ *  zero is off and should be ignored
+ *
+ *  currently brightness control is very simple and operates without
+ *  modifying end-user color choices
+ *
+ *  tldr;  users have 24bit color selectivity, in hardware we have a
+ *  little over 1.5 bytes per color giving us somewhere near 37bit color
+ *  selectivity.  so a user can select 0a3bcc as their color choise and
+ *  we can have thousands of shades of colors that are close to that.
+ *  The power section of the esp8266 is not optimal so the esp8266 will
+ *  limit running the LEDs at maximum current.  This limits the color-space
+ *  by about 20%.  The way we implement brightness is to cut give the end
+ *  user 24-bits of color selectivity, that fits into 1/20th of the color-space
+ *  then we can multiply those values linearly 1..15 before we hit that 80%
+ *  power limit.  because we are amplifying colors evenly the selected output
+ *  color will not change.
+ */
+echo "
+<script>
+var slider = document.createElement('input');
+slider.type = 'range';
+slider.min = 1;
+slider.max = 15;
+slider.value = 7;
+slider.step = 1;
+document.body.appendChild(slider);
+
+// set the inital value of the slider to whatever the current brightness value
+getBrightness(slider);
+
+function slid(event, cb = false) {
+  if(false !== cb) cb(slider.value);
+  console.log('changed slider to ' + slider.value);
+  return 'brightness(' + slider.value + ')';
+}
+
+slider.addEventListener('change', (event) => slid(event));
+</script>
+";
+// add a callback to the off-click event on the slider
+echo "
+<script>
+function slidercb(valu) {
+    console.log('logged', valu);
+    post('', 'brightness', parseInt(valu, 10).toString(16).padStart(2,'0')).then((data) => { console.log(data, data.text()); });
+}
+slider.removeEventListener('change', (event) => slid(event));
+slider.addEventListener('change', (event) => slid(event, slidercb));
+
+</script>
+";
+
 echo "</body>";
 echo "</html>";