diff --git a/.gitignore b/.gitignore index 00cbbdf..14a53fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Application data +config.json + # Logs logs *.log diff --git a/README.md b/README.md index f2cea6a..de957fd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ NEEDS BETTER README! +Make sure you are on a recent nodejs version. Raspbian has old version. Use nodesource ;). + npm install node pixdisp.js diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..0c163cc --- /dev/null +++ b/config.example.json @@ -0,0 +1,14 @@ +{ + "bindAddr": "0.0.0.0", + "bindPort": 8080, + + "driver": "dummy", + + "matrix": { + "width": 16, + "height": 16, + "brightness": 1, + "flipHorizontal": false, + "flipVertical": false + } +} \ No newline at end of file diff --git a/controllers/apicontroller.js b/controllers/apicontroller.js new file mode 100644 index 0000000..c7866e4 --- /dev/null +++ b/controllers/apicontroller.js @@ -0,0 +1,67 @@ +"use strict"; + +class ApiController { + + constructor( server, driver ) { + + this.server = server; + this.driver = driver; + + this.server.post( { path: '/api/writecanvas' }, this.writecanvas.bind( this ) ); + this.server.get( { path: '/api/write' }, this.write.bind( this ) ); + this.server.get( { path: '/api/setpixel/:x/:y/:r/:g/:b' }, this.setPixel.bind( this ) ); + + } + + writecanvas( request, resource, next ) { + + this.driver.clearMatrix(); + + resource.setHeader( 'Access-Control-Allow-Origin', '*' ); + + let data = JSON.parse( request.params.data ); + + for ( let obj of data) { + this.driver.setPixel( obj.y, obj.x, obj.r, obj.g, obj.b ); + } + + let buffer = this.driver.getBuffer(); + this.driver.write( buffer ); + + resource.json( 200, { 'msg': 'Ok' } ); + return next(); + + } + + write( request, resource, next ) { + + resource.setHeader( 'Access-Control-Allow-Origin', '*' ); + + let buffer = this.driver.getBuffer(); + this.driver.write( buffer ); + + resource.json( 200, { 'msg': 'Ok' } ); + return next(); + + } + + setPixel( request, resource, next ) { + + resource.setHeader( 'Access-Control-Allow-Origin', '*' ); + + this.driver.setPixel( + request.params.x, + request.params.y, + request.params.r, + request.params.g, + request.params.b + ); + + resource.json( 200, { 'msg': 'Ok' } ); + return next(); + + } + +} + +exports.ApiController = ApiController; \ No newline at end of file diff --git a/drivers/driver.js b/drivers/driver.js index 8f1b3d2..e3bb655 100644 --- a/drivers/driver.js +++ b/drivers/driver.js @@ -10,6 +10,9 @@ class Driver { this.setSize( 16, 16 ); this.setBrightness( 1 ); + this.flipHorizontal = false; + this.flipVertical = false; + } /** @@ -19,7 +22,7 @@ class Driver { this.width = w; this.height = h; - this.createMatrix(); + this.clearMatrix(); } /** @@ -84,6 +87,8 @@ class Driver { * @return {Buffer} */ getBuffer() { + this.flipMatrix( this.flipHorizontal, this.flipVertical ); + let buffer = new Buffer( this.width * this.height * 3 ); let size = this.getSize(); @@ -100,22 +105,38 @@ class Driver { return buffer; } + /** + * Write output to the device. Implement at driver level. + */ write( buffer ) { console.log( 'Driver should implement this' ); } + /** + * Flip the matrix along it's axis. + */ + flipMatrix( horizontal, vertical ) { + if ( horizontal === true ) { + for ( let i = 0; i < this.matrix.length; i++ ) { + this.matrix[ i ].reverse(); + } + } + + if ( vertical === true ) { + this.matrix.reverse(); + } + } + /** * Create the internal frame */ - createMatrix() { + clearMatrix() { this.matrix = []; - let size = this.getSize(); - - for ( let y = 0; y < size.height; y++ ) { + for ( let y = 0; y < this.height; y++ ) { this.matrix.push( [] ); - for ( let x = 0; x < size.width; x++ ) { + for ( let x = 0; x < this.width; x++ ) { this.matrix[ y ].push( { r: 0, g: 0, diff --git a/drivers/driverfactory.js b/drivers/driverfactory.js new file mode 100644 index 0000000..d0eabd3 --- /dev/null +++ b/drivers/driverfactory.js @@ -0,0 +1,40 @@ +"use strict"; + +class DriverFactory { + constructor() { + } + + createFromConfig( config ) { + + let driver; + + switch ( config.driver.toLowerCase() ) { + + default: + console.warn( "Unknown driver defaulting to Dummy" ); + + case "dummy": + let { Dummy } = require( './dummy' ); + driver = new Dummy(); + break; + + case "pimoroniunicorn": + let { PimoroniUnicorn } = require( './pimoroniunicorn' ); + driver = new PimoroniUnicorn(); + break; + + } + + driver.setSize( config.matrix.width, config.matrix.height ); + driver.setBrightness( config.matrix.brightness ); + + driver.flipHorizontal = config.matrix.flipHorizontal; + driver.flipVertical = config.matrix.flipVertical; + + return driver; + + } + +} + +exports.DriverFactory = DriverFactory; \ No newline at end of file diff --git a/drivers/dummy.js b/drivers/dummy.js new file mode 100644 index 0000000..f633df6 --- /dev/null +++ b/drivers/dummy.js @@ -0,0 +1,15 @@ +"use strict"; + +let { Driver } = require( './driver' ); + +class Dummy extends Driver { + constructor() { + super(); + } + + write( buffer ) { + console.log( buffer ); + } +} + +exports.Dummy = Dummy; \ No newline at end of file diff --git a/drivers/unicornhathd.js b/drivers/pimoroniunicorn.js similarity index 82% rename from drivers/unicornhathd.js rename to drivers/pimoroniunicorn.js index 8c6879a..7de5bd7 100644 --- a/drivers/unicornhathd.js +++ b/drivers/pimoroniunicorn.js @@ -2,7 +2,7 @@ let { Driver } = require( './driver' ); -class UnicornHatHD extends Driver { +class PimoroniUnicorn extends Driver { constructor() { super(); @@ -26,4 +26,4 @@ class UnicornHatHD extends Driver { } } -exports.UnicornHatHD = UnicornHatHD; \ No newline at end of file +exports.PimoroniUnicorn = PimoroniUnicorn; \ No newline at end of file diff --git a/pixdisp.js b/pixdisp.js index 8287f79..aa24a7b 100644 --- a/pixdisp.js +++ b/pixdisp.js @@ -1,18 +1,36 @@ 'use strict'; -let { UnicornHatHD } = require( './drivers/unicornhathd' ); +let fs = require( 'fs' ); +let config; +let api; + +if ( fs.existsSync( 'config.json' ) ) { + let contents = fs.readFileSync( 'config.json' ); + config = JSON.parse( contents ); +} else { + let contents = fs.readFileSync( 'config.example.json' ); + config = JSON.parse( contents ); +} + +let driver; +let { DriverFactory } = require( './drivers/driverfactory' ); +let driverFactory = new DriverFactory(); + +let { ApiController } = require( './controllers/apicontroller' ); let restify = require( 'restify' ); -let bindAddr = '0.0.0.0'; -let bindPort = 8080; +let bindAddr = config.bindAddr; +let bindPort = config.bindPort; let server = restify.createServer( { handleUpgrades: true } ); server.use( restify.plugins.queryParser() ); -server.use( restify.plugins.bodyParser() ); +server.use( restify.plugins.bodyParser( { + mapParams: true +} ) ); server.use( restify.plugins.jsonp() ); server.use( restify.plugins.gzipResponse() ); server.use( restify.plugins.throttle( @@ -23,6 +41,11 @@ server.use( restify.plugins.throttle( } )); +driver = driverFactory.createFromConfig( config ); +driver.write( driver.getBuffer() ); + +api = new ApiController( server, driver ); + server.get(/.*/, restify.plugins.serveStatic({ 'directory': 'www', @@ -37,12 +60,4 @@ server.listen( bindPort, bindAddr, function() { console.log( '%s listening at %s ', server.name , server.url ); console.log( 'Ready.' ); - // Some test code - let driver = new UnicornHatHD(); - driver.setPixel( 1, 1, 255, 0, 0 ); - driver.setPixel( 5, 5, 255, 0, 255 ); - - let buffer = driver.getBuffer(); - driver.write( buffer ); - -}); \ No newline at end of file +}); diff --git a/www/index.html b/www/index.html index 2742b49..69a74c9 100644 --- a/www/index.html +++ b/www/index.html @@ -19,10 +19,7 @@
- Load current - Capture camera - Browse history - Submit + Submit

diff --git a/www/js/app.js b/www/js/app.js index 7ad164c..66d8849 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -18,6 +18,7 @@ class App holder.appendChild( canvas ); canvas.addEventListener( 'click', this.handleCanvas ); + document.getElementById( 'submitImage' ).addEventListener( 'click', this.submitCanvas ); this.canvas = canvas; this.context = canvas.getContext( '2d' ); @@ -118,6 +119,42 @@ class App } + submitCanvas( ev ) { + + if ( ev.preventDefault ) { + ev.preventDefault(); + } + + let imageData = app.context.getImageData( 0, 0, app.canvas.width, app.canvas.height ); + let request = new XMLHttpRequest(); + + request.open( 'POST', '/api/writecanvas', true ); + request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' ); + + let obj = []; + for ( let x = 0; x < app.canvas.width; x++ ) { + for ( let y = 0; y < app.canvas.height; y++ ) { + let index = ( y * app.canvas.width + x ) * 4; + let r = imageData.data[ index ]; + let g = imageData.data[ index + 1 ]; + let b = imageData.data[ index + 2 ]; + + if ( r > 0 || g > 0 || b > 0 ) { + obj.push( { + x: x, + y: y, + r: r, + g: g, + b: b + } ) + } + } + } + + obj = encodeURI( 'data=' + JSON.stringify( obj ) ); + request.send( obj ); + } + } let app = new App(); \ No newline at end of file