|
@@ -1,34 +1,70 @@
|
|
<?php
|
|
<?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') {
|
|
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();
|
|
$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);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
$out = curl_exec($ch);
|
|
$out = curl_exec($ch);
|
|
curl_close($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 "<html>";
|
|
echo "<body>";
|
|
echo "<body>";
|
|
// either use mixbox non-commercial use under the CC BY-NC 4.0 license
|
|
// either use mixbox non-commercial use under the CC BY-NC 4.0 license
|
|
@@ -64,6 +100,66 @@ console.log(color);
|
|
</script>
|
|
</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
|
|
// render a color selection wheel
|
|
echo "
|
|
echo "
|
|
<script>
|
|
<script>
|
|
@@ -71,7 +167,7 @@ var canvas = document.createElement('canvas');
|
|
// google chrome performance flag
|
|
// google chrome performance flag
|
|
//var context = canvas.getContext('2d');
|
|
//var context = canvas.getContext('2d');
|
|
var context = canvas.getContext('2d', { willReadFrequently: true });
|
|
var context = canvas.getContext('2d', { willReadFrequently: true });
|
|
-var w = 200; var h = 200;
|
|
|
|
|
|
+var w = 400; var h = 400;
|
|
canvas.width = w;
|
|
canvas.width = w;
|
|
canvas.height = h;
|
|
canvas.height = h;
|
|
|
|
|
|
@@ -126,6 +222,9 @@ tableBodyRow.appendChild(selected);
|
|
|
|
|
|
document.body.appendChild(table);
|
|
document.body.appendChild(table);
|
|
|
|
|
|
|
|
+// set the inital value of the selected color cell to the esp8266 lights
|
|
|
|
+getColor(selected);
|
|
|
|
+
|
|
// listen to events from canvas
|
|
// listen to events from canvas
|
|
function pick(event, destination, cb = false) {
|
|
function pick(event, destination, cb = false) {
|
|
const bounding = canvas.getBoundingClientRect();
|
|
const bounding = canvas.getBoundingClientRect();
|
|
@@ -137,6 +236,7 @@ function pick(event, destination, cb = false) {
|
|
|
|
|
|
const rgb = 'rgb(' + data[0] + ', ' + data[1] + ', ' + data[2] + ')';
|
|
const rgb = 'rgb(' + data[0] + ', ' + data[1] + ', ' + data[2] + ')';
|
|
let off = 0 === (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.style.background = off ?'rgb(211, 211, 211)' :rgb;
|
|
destination.textContent = 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
|
|
// add a callback to the click event to send rgb value to esp8266
|
|
echo "
|
|
echo "
|
|
<script>
|
|
<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 sg(x) { return Math.round((x*x)/255); }
|
|
function callback(red, green, blue) {
|
|
function callback(red, green, blue) {
|
|
console.log('logged', red, green, blue);
|
|
console.log('logged', red, green, blue);
|
|
post(
|
|
post(
|
|
/* url */ '',
|
|
/* url */ '',
|
|
|
|
+ /* key */ 'color',
|
|
sg(red).toString(16).padStart(2,'0') +
|
|
sg(red).toString(16).padStart(2,'0') +
|
|
sg(green).toString(16).padStart(2,'0') +
|
|
sg(green).toString(16).padStart(2,'0') +
|
|
sg(blue).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>
|
|
</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 "</body>";
|
|
echo "</html>";
|
|
echo "</html>";
|