Skip to content

Commit

Permalink
blink1-tiny-server improvements (#38)
Browse files Browse the repository at this point in the history
* blink1-tiny-server: Use mongoose cs_time() as a cross-platform granular time().

This reverts most of the Windows compatibility added in commit
937579b, except for turning off
stdout buffering. It also allows the cache_flush functions to be
simplified and folded into a single function.

* blink1-tiny-server: Add support for serving an index file from document_root

* blink1-tiny-server: Only seed random number generator once at startup

* blink1 URL API demos: Add an index file and simple demo

* blink1-demo-party: Fix typo and trailing whitespace

* blink1-demo-colorpicker: Add multi device/led and response status support

* blink1-demos: Add mobile support
  • Loading branch information
normanr authored Nov 2, 2020
1 parent 937579b commit f167eac
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 71 deletions.
64 changes: 30 additions & 34 deletions server/blink1-tiny-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

#include "blink1-lib.h"

#include "wincompat.h"

//#include "dict.h"
#include "Dictionary.h"
#include "Dictionary.c"
Expand All @@ -40,14 +38,16 @@ const char* blink1_server_version = BLINK1_VERSION;

typedef struct cache_info_ {
blink1_device* dev; // device, if opened, NULL otherwise
struct timeval atime; // time last used
double atime; // time last used
} cache_info;

static const struct timeval idle_atime = { 1 /*sec*/};
static double idle_atime = 1.0 /* seconds */;
static cache_info cache_infos[cache_max];

static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;
static const char* const index_files[] = { "index.html", "index.htm" };
static bool serve_index = false;

DictionaryRef patterndict;
DictionaryCallbacks patterndictc;
Expand All @@ -59,7 +59,7 @@ typedef struct _url_info
} url_info;

// FIXME: how to make Emacs format these better?
url_info supported_urls[]
static const url_info supported_urls[]
= {
{"/blink1/", "simple status page"},
{"/blink1/id", "get blink1 serial number"},
Expand Down Expand Up @@ -114,7 +114,7 @@ void usage()
);
}

void cache_flush_all();
void cache_flush(int idle_threshold);

blink1_device* cache_getDeviceById(uint32_t id)
{
Expand All @@ -127,7 +127,7 @@ blink1_device* cache_getDeviceById(uint32_t id)
if( !dev ) {
dev = blink1_openById(id);
if( !dev ) {
cache_flush_all();
cache_flush(0);
blink1_enumerate();
dev = blink1_openById(id);
if( !dev ) {
Expand All @@ -151,36 +151,22 @@ void cache_return_internal( blink1_device* dev )
int i = blink1_getCacheIndexByDev(dev);
// printf("cache_return_internal: %p at %d\n", dev, i);
if( i>=0 ) {
gettimeofday(&cache_infos[i].atime, NULL);
cache_infos[i].atime = cs_time();
}
else {
blink1_close(dev);
}
}

void cache_flush_all()
{
int count = blink1_getCachedCount();
for( int i=0; i< count; i++ ) {
if( cache_infos[i].dev ) {
// printf("cache_flush_all: id=%d handle=%p atime=%ld.%06ld\n", i, cache_infos[i].dev, cache_infos[i].atime.tv_sec, cache_infos[i].atime.tv_usec);
blink1_close(cache_infos[i].dev)
timerclear(&cache_infos[i].atime);
}
}
}

void cache_flush_unused()
void cache_flush(int idle_threshold)
{
struct timeval deadline;
gettimeofday(&deadline, NULL);
timersub(&deadline, &idle_atime, &deadline);
double deadline = cs_time() - idle_threshold;
int count = blink1_getCachedCount();
for( int i=0; i< count; i++ ) {
if( cache_infos[i].dev && timercmp(&cache_infos[i].atime, &deadline, <) ) {
// printf("cache_flush_unused: id=%d handle=%p atime=%ld.%06ld\n", i, cache_infos[i].dev, cache_infos[i].atime.tv_sec, cache_infos[i].atime.tv_usec);
if( cache_infos[i].dev && cache_infos[i].atime < deadline ) {
// printf("cache_flush: id=%d handle=%p atime=%.3f\n", i, cache_infos[i].dev, cache_infos[i].atime);
blink1_close(cache_infos[i].dev)
timerclear(&cache_infos[i].atime);
cache_infos[i].atime = 0;
}
}
}
Expand Down Expand Up @@ -294,7 +280,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
}

// parse URI requests
if( mg_vcmp( uri, "/") == 0 ) {
if( mg_vcmp( uri, "/") == 0 && !serve_index ) {
sprintf(status, "Welcome to %s api server. "
"All URIs start with '/blink1'. \nSupported URIs:\n", blink1_server_name);
for( int i=0; i< sizeof(supported_urls)/sizeof(url_info); i++ ) {
Expand All @@ -308,9 +294,9 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
}
else if( mg_vcmp( uri, "/blink1/id") == 0 ||
mg_vcmp( uri, "/blink1/id/") == 0 ||
mg_vcmp( uri, "/blink1/enumerate") == 0 ) {
mg_vcmp( uri, "/blink1/enumerate") == 0 ) {
sprintf(status, "blink1 id");
cache_flush_all();
cache_flush(0);
int c = blink1_enumerate();

sprintf(tmpstr,"[");
Expand Down Expand Up @@ -446,7 +432,6 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
sprintf(status, "blink1 random");
if( count==0 ) { count = 1; }
if( millis==0 ) { millis = 200; }
srand( time(NULL) * getpid() );
blink1_device* dev = cache_getDeviceById(id);
for( int i=0; i<count; i++ ) {
uint8_t r = rand() % 255;
Expand Down Expand Up @@ -506,7 +491,8 @@ int main(int argc, char *argv[]) {
const char *err_str;

setbuf(stdout,NULL); // turn off stdout buffering for Windows

srand( time(NULL) * getpid() );

mg_mgr_init(&mgr, NULL);

patterndictc = DictionaryStandardStringCallbacks();
Expand Down Expand Up @@ -541,6 +527,15 @@ int main(int argc, char *argv[]) {
break;
case 'd':
s_http_server_opts.document_root = optarg;
char path[MAX_PATH_SIZE];
cs_stat_t st;
for( int i=0; i< sizeof(index_files)/sizeof(char* const); i++ ) {
snprintf(path, sizeof(path), "%s/%s", s_http_server_opts.document_root, index_files[i]);
if( mg_stat(path, &st) == 0 ) {
serve_index = true;
break;
}
}
break;
case 'h':
usage();
Expand Down Expand Up @@ -568,12 +563,13 @@ int main(int argc, char *argv[]) {
printf("%s version %s: running on port %s \n",
blink1_server_name, blink1_server_version, s_http_port);
if( s_http_server_opts.document_root ) {
printf(" serving static HTML %s\n", s_http_server_opts.document_root);
printf(" serving static HTML %s (with%s root index)\n",
s_http_server_opts.document_root, serve_index ? "" : "out");
}

for (;;) {
mg_mgr_poll(&mgr, 1000);
cache_flush_unused();
cache_flush(idle_atime);
}
mg_mgr_free(&mgr);

Expand Down
5 changes: 5 additions & 0 deletions server/html/blink1-demo-colorpicker/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width,maximum-scale=1">
<title>blink(1) URL API demo colorpicker</title>

<script type="module" src="script.js"></script>
Expand All @@ -16,6 +18,9 @@ <h1>blink(1) URL API demo colorpicker</h1>
<p>For more tools, see <a href="https://github.com/todbot/blink1-tool">blink1-tool</a>
</p>

<select id=leds></select>
<div>Status: <span id="status"> not connected</span></div>

<div><canvas width="300" height="255" id="canvas_picker"></canvas></div>
<div>HEX: <input type="text" id="hex"></input></div>
<div>RGB: <input type="text" id="rgb"></input></div>
Expand Down
58 changes: 55 additions & 3 deletions server/html/blink1-demo-colorpicker/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
let canvas = document.getElementById('canvas_picker').getContext('2d');
let rgbinput = document.getElementById('rgb');
let hexinput = document.getElementById('hex');
let leds = document.getElementById('leds');
let status = document.getElementById('status');
let canvas_picker = document.getElementById('canvas_picker');
canvas_picker.addEventListener('click', handleClick);
canvas_picker.addEventListener('mousemove', handleClick);
canvas_picker.addEventListener('touchmove', handleClick);
document.addEventListener('DOMContentLoaded', handleDOMContentLoaded);

// create an image object and set its source
let img = new Image();
Expand All @@ -19,6 +23,39 @@ img.addEventListener('load', function() {
canvas.drawImage(img,0,0);
});

async function handleDOMContentLoaded() {
try {
const response = await fetch('/blink1/id');
if (!response.ok) {
throw new Error('HTTP error! status: ' + response.status);
}
const results = await response.json()
const serialnums = results.blink1_serialnums;
for (const i in serialnums) {
const serialnum = serialnums[i];
const option = document.createElement('option');
option.value = 'id=' + serialnum;
option.text = 'serial: ' + serialnum;
leds.add(option);
if (serialnum && serialnum[0] > '1') {
for (let ledn = 1; ledn <= 2; ledn++) {
const option = document.createElement('option');
option.value = 'id=' + serialnum + '&ledn=' + ledn;
option.text = 'serial: ' + serialnum + ', only led #' + ledn;
leds.add(option);
}
}
}
if( serialnums && serialnums.length > 0 ) {
status.textContent = "connected";
} else {
status.textContent = "no devices";
}
} catch (error) {
console.error('handleDOMContentLoaded: failed:', error);
}
}

// http://www.javascripter.net/faq/rgbtohex.htm
function rgbToHex(R,G,B) { return toHex(R)+toHex(G)+toHex(B); }
function toHex(n) {
Expand All @@ -30,8 +67,17 @@ function toHex(n) {

async function handleClick(event) {
// get click coordinates in image space
const x = event.offsetX;
const y = event.offsetY;
const [x, y] = function() {
if (event.type == "touchmove") {
event.preventDefault();
const boundingClientRect = event.target.getBoundingClientRect()
return [
event.changedTouches[0].clientX - boundingClientRect.x,
event.changedTouches[0].clientY - boundingClientRect.y,
];
}
return [event.offsetX, event.offsetY];
}();
// get image data and RGB values
const img_data = canvas.getImageData(x, y, 1, 1).data;
const r = img_data[0];
Expand All @@ -52,6 +98,7 @@ async function fadeToColor([r, g, b], fadeMillis, ledn ) {
const f = (x) => 256 * (x / 256) ** 2; // gamma of 0.5

const searchParams = new URLSearchParams(location.search)
new URLSearchParams(leds.value).forEach((value, key) => searchParams.set(key, value));
searchParams.set('rgb', rgbToHex(f(r), f(g), f(b)))
if (!searchParams.has('time')) searchParams.set('time', fadeMillis / 1000)
if (!searchParams.has('ledn')) searchParams.set('ledn', ledn)
Expand All @@ -75,7 +122,12 @@ async function fetchResource() {
if ( !resource ) return;

try {
await fetch(resource);
const response = await fetch(resource);
if (!response.ok) {
throw new Error('HTTP error! status: ' + response.status);
}
const results = await response.json()
status.textContent = results.status;
} catch (error) {
console.error('fadeToColor: failed:', error);
}
Expand Down
3 changes: 2 additions & 1 deletion server/html/blink1-demo-party/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width,maximum-scale=1">
<title>blink(1) URL API demo party</title>

<script type="module" src="script.js"></script>
Expand All @@ -15,7 +17,6 @@ <h1>blink(1) URL API demo party</h1>
<p>For more tools, see <a href="https://github.com/todbot/blink1-tool">blink1-tool</a>
</p>


<p>Click the 'Start' button to start the party. <br/>
Click 'Stop' to be a party pooper.</p>

Expand Down
2 changes: 1 addition & 1 deletion server/html/blink1-demo-party/script.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

// Part of https://todbot.github.io/blink1-webhid/
// Part of https://todbot.github.io/blink1-tool/

document.getElementById('start-button').addEventListener('click', handleClick);
document.getElementById('stop-button').addEventListener('click', handleClickStop);
Expand Down
34 changes: 34 additions & 0 deletions server/html/blink1-demo-simplest/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width,maximum-scale=1">
<title>blink(1) URL API demo simplest </title>
</head>
<body>
<h1>blink(1) URL API demo simplest</h1>

<p>This demo is for the
<a href="https://blink1.thingm.com">blink(1) USB notification light</a></p>
<p>For more details on URL API, see the
<a href="https://github.com/todbot/blink1/blob/master/docs/app-url-api.md">documentation </a> </p>
<p>For more tools, see <a href="https://github.com/todbot/blink1-tool">blink1-tool</a>
</p>

<p>Click the links below to turn blink(1) the specified color<p>

<ul>
<li><a target=api href="/blink1/on">On</a></li>
<li><a target=api href="/blink1/red">Red</a></li>
<li><a target=api href="/blink1/fadeToRGB?rgb=ffff00">Yellow</a></li>
<li><a target=api href="/blink1/green">Green</a></li>
<li><a target=api href="/blink1/fadeToRGB?rgb=00ffff">Cyan</a></li>
<li><a target=api href="/blink1/blue">Blue</a></li>
<li><a target=api href="/blink1/fadeToRGB?rgb=ff00ff">Purple</a></li>
<li><a target=api href="/blink1/random">Random</a></li>
<li><a target=api href="/blink1/off">Off</a></li>
</ul>
<iframe name=api width=390 height=210 src=/blink1/id></iframe>

</body>
</html>
53 changes: 53 additions & 0 deletions server/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,maximum-scale=2">
<link rel="stylesheet" type="text/css" media="screen" href="https://pages-themes.github.io/slate/assets/css/style.css">
<title>blink1-tool</title>
</head>
<body>
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
<a id="forkme_banner" href="https://github.com/todbot/blink1-tool">View on GitHub</a>
<h1 id="project_title">blink1-tool</h1>
</header>
</div>

<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<h1 id="blink1-tool">blink(1) URL API examples</h1>

<p>Example of using the Fetch API to talk to <a href="https://blink1.thingm.com/">blink(1) USB RGB LED notification lights</a>.</p>

<p>For more details on the URL API, see <a href="https://github.com/todbot/blink1/blob/master/docs/app-url-api.md">documentation</a></p>

<p>To see the source visit: <a href="https://github.com/todbot/blink1-tool/tree/master/server/html">https://github.com/todbot/blink1-tool/tree/master/server/html</a></p>

<p>List of examples:</p>
<ul>
<li><a href="./blink1-demo-simplest/">blink1-demo-simplest</a> – bare-bones example of lighting a blink(1)</li>
<li><a href="./blink1-demo-party/">blink1-demo-party</a> – slightly more complex example</li>
<li><a href="./blink1-demo-colorpicker/">blink1-demo-colorpicker</a> – color picker with fast updates to blink(1)</li>
</ul>

<p>Pre-requisites for these examples, you will need:</p>
<ul>
<li>blink(1) USB LED</li>
<li>A browser that supports <a href="https://caniuse.com/fetch">Fetch</a></li>
<li>Run <code class="language-plaintext highlighter-rouge">blink1-tiny-server -d /path/to/blink1-tool/server/html</code></li>
<li>Visit: <a href="http://localhost:8000">http://localhost:8000</a></li>
</ul>

</section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">blink1-tool maintained by <a href="https://github.com/todbot">todbot</a></p>
</footer>
</div>
</body>
</html>
Loading

0 comments on commit f167eac

Please sign in to comment.