diff --git a/app.js b/app.js index 8b21ae0..4b02b5a 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,7 @@ var Stack = require('stack'), var blog = Nog(__dirname); blog.warehouse(); -module.exports = Stack.compose( +module.exports = Stack( Creationix.postReceive("/post-receive", Path.join(__dirname, "/post-receive.sh"), blog.warehouse), blog ); diff --git a/articles/linux-joystick.json b/articles/linux-joystick.json new file mode 100644 index 0000000..b519bc8 --- /dev/null +++ b/articles/linux-joystick.json @@ -0,0 +1,7 @@ +{ + "author": "authors/creationix", + "title": "Linux Joysticks and Other System Devices in NodeJS", + "date": "Feb 7, 2012", + "tags": ["linux", "low-level", "games"], + "nodeVersion": "0.6.10" +} diff --git a/articles/linux-joystick.markdown b/articles/linux-joystick.markdown new file mode 100644 index 0000000..45944ef --- /dev/null +++ b/articles/linux-joystick.markdown @@ -0,0 +1,213 @@ +Everyone knows that NodeJS can be used to make über fast webservers. But did +you know that it's good for low level tasks too? In this article we'll write a +joystick driver for Linux. Technically it's not that low level, the kernel is +handling the hard bits for us, but the results are still very cool. + +![f310](/linux-joystick/f310.png) + +Those who have seen my past experiments with [SDL][] and [OpenGL][] in NodeJS know that I +love to give demos where I hook up a USB gamepad to a NodeJS server and do +something cool with it. The problem was that I needed C++ bindings to libSDL to +be able to talk to the gamepad. + +It turns out I was wrong and (on Linux systems at least) it's trivial to read +directly from the system device file and parse the protocol. + +## On Reading the Device + +One nice thing about Unix systems is that *everything* is a file. Folders are +files. Processes are represented as files. And USB gamepads are represented as +files! So to test this theory, I ran `cat` on `/dev/input/js0` which is the +representation of my first joystick. I then moved the joystick around and stuff +got emitted. + +![/dev/input/js0](/linux-joystick/js0.png) + +Hot dog, we're in business! Now if only there was a document that explained +what all that binary nonesense meant. + +-------------------------------------------------------------------------------- + +## Parsing the Output + +After some brief searching online, I discovered that this is the Linux input +joystick api documented at . + +In particular, the section about the format of the binary stuff that was getting +emitted by the file when I moved the joystick says that I need to: + + struct js_event e; + read (fd, &e, sizeof(struct js_event)); + +where js_event is defined as + + struct js_event { + __u32 time; /* event timestamp in milliseconds */ + __s16 value; /* value */ + __u8 type; /* event type */ + __u8 number; /* axis/button number */ + }; + +Clearly this is meant for C programmers, but using this information from a +NodeJS program isn't hard. We have to calculate the `sizeof(struct js_event)` by +hand. It's 8 bytes. And we don't want to use a blocking read, but luckilly a +non-blocking read works fine too. + +Let's write a small program that constantly reads 8 byte chunks from the file. + + #@git://github.com/nodebits/linux-joystick.git#read-loop.js,3-15 + +Running that and moving the joystick around gives me somewhat structured data: + + event + event + event + event + +I know from the kernel documentation that the first four bytes are a timestamp. +I can see from the output that it's little endian (the first byte changes very +fast, the last doesn't change at all). From the NodeJS docs, I see that we need +[Buffer.readUInt32LE][]. + +The next two bytes are the value as a signed 16 bit integer. For this we need +[Buffer.readInt16LE][]. I assume the same endianess for everything else. It's +rarely mixed within a single struct. + +Then the last two values are regular unsigned 8 bit integers. I can use +[Buffer.readUInt8][] or just use the normal `[]` access that buffers always +provided. + +Updating the example, we add the following parse function: + + #@git://github.com/nodebits/linux-joystick.git#read-loop2.js,3-10 + +Which outputs lines like: + + { time: 453074028, value: -9797, type: 2, number: 0 } + +## Making it Developer Friendly + +Ok, so we've gone from raw binary blobs to some nice integers in a json object. +But we can do better. For example, the value is a 16 bit signed integer. A +float from -1 to 1 might be nice, might not. Also, what does type 2 mean anyway? +Going back to the kernel docs, we read that the possible values of `type` are: + + #define JS_EVENT_BUTTON 0x01 /* button pressed/released */ + #define JS_EVENT_AXIS 0x02 /* joystick moved */ + #define JS_EVENT_INIT 0x80 /* initial state of device */ + +> As mentioned above, the driver will issue synthetic `JS_EVENT_INIT` ORed +> events on open. That is, if it's issuing a `INIT BUTTON` event, the +> current type value will be + + int type = JS_EVENT_BUTTON | JS_EVENT_INIT; /* 0x81 */ + +So to make things easier on the user, we can parse out this information as well +and set the string `button` or `axis` for type. Also we'll add a `init` +property if that bit is set. + +With these changes the new parse function looks like: + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,4-15 + +## Objectifying the Code + +The other problem with out code is it's a nested mess and makes some inflexible +assumptions like which joystick to open and throws on all errors. We can create +a Joystick constructor class that is reusable. + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,18-27 + +This constructor inherits from `EventEmitter` and is thus an emitter itself. I +wanted errors to be routed to an `error` event instead of littering all my +callbacks. The wrap function seen here is a small utility to both bind the +method to this instance and route the error parameter to the `error` event. + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,30-37 + +With this framework in place, implementing `onOpen` is very straightforward: + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,39-42 + +Once the file is open and we have a valid file descriptor, all that's left is +the recursive read loop. It's implemented as: + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,44-53 + +Remember that the `onRead` and `onOpen` prototype functions are wrapped and +bound to the instance. That's why I'm able to use them directly in place of the +callback. This is an example of how smart use of the language can make async +callbacks beautiful. + +All that's left is to provide a way to eventually close this resource. So we'll +add a simple close function. + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,55-58 + +## Using the API + +Now that we have this nice shiny API, how is it used? Quite simply in fact: + + #@git://github.com/nodebits/linux-joystick.git#joystick.js,63-65 + +When run on my local machine, I get the following output: + + $ node joystick.js + { time: 454750604, + value: 0, + number: 0, + init: true, + type: 'button', + id: 0 } + { time: 454750608, + value: 0, + number: 1, + init: true, + type: 'button', + id: 0 } + { time: 454750612, + value: 0, + number: 0, + init: true, + type: 'axis', + id: 0 } + { time: 454750612, + value: 0, + number: 1, + init: true, + type: 'axis', + id: 0 } + { time: 454752448, value: 337, number: 0, type: 'axis', id: 0 } + { time: 454752456, value: 1689, number: 0, type: 'axis', id: 0 } + { time: 454752464, value: 2364, number: 0, type: 'axis', id: 0 } + { time: 454752480, value: 3715, number: 0, type: 'axis', id: 0 } + { time: 454752488, value: 4391, number: 0, type: 'axis', id: 0 } + { time: 454752496, value: 5067, number: 0, type: 'axis', id: 0 } + ... + +## Going on From Here + +There are many places you can go on from here. I will note that this code +probably won't run on your web server unless you happen to have an USB gamepad +or joystick plugged into it. It will however run on your Linux desktop or +laptop. Often the accelerometer in a laptop is exposed as a joystick in Linux. + +In addition this isn't limited to joysticks, any special device on your system +is open to being read from NodeJS. No special binary add-ons are required. Just +read up on the documentation of the protocol and implement it in javascript. One +of my first NodeJS projects was implementing the PostgreSQL wire protocol in +pure JS. I was able to query my database without using any C++. + +The world is wide open with possibilities. Don't feel limited by your lack of +ability or desire to program in C++. A great many things can be done in pure +JavaScript. NodeJS provides an amazing amount of system primitives used to write +many types of software. + +Happy coding! + +[SDL]: https://github.com/creationix/node-sdl +[OpenGL]: https://github.com/creationix/webgl-sdl +[Buffer.readUInt32LE]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readUInt32LE +[Buffer.readInt16LE]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readInt16LE +[Buffer.readUInt8]: http://nodemanual.org/latest/nodejs_ref_guide/buffer.html#Buffer.readUInt8 + diff --git a/articles/wiki-challenge.json b/articles/wiki-challenge.json new file mode 100644 index 0000000..a6220a5 --- /dev/null +++ b/articles/wiki-challenge.json @@ -0,0 +1,7 @@ +{ + "author": "authors/creationix", + "title": "Introducing The Great Wiki Challenge", + "date": "Jan 23, 2012", + "tags": ["express", "contest", "wiki", "sample-app"], + "nodeVersion": "0.6.8" +} diff --git a/articles/wiki-challenge.markdown b/articles/wiki-challenge.markdown new file mode 100644 index 0000000..46e003f --- /dev/null +++ b/articles/wiki-challenge.markdown @@ -0,0 +1,139 @@ +In celebration of the official launch of Nodebits.org ("[Our Commitment to the Node.js Community][]"), we’re introducing Nodebits’ first official contest – The Great Wiki Challenge. + +## The Rules + +Ok, the goal of this contest is simple. Take this basic wiki (explained below) and turn it into something beautiful. The sky is the limit. You can add whatever features you think make for an awesome wiki. + +The completed entry needs to be runnable in the [Cloud9][] development environment. Simply start by clicking on any of the "Edit in Cloud9" links on the code snippets and this git repo will automatically be cloned into your personal c9.io environment (don't worry, it's free). From there you can develop your additions and test your code. + +### Submitting an Entry + +The due date for entries is the close of Node Summit. Your entry must be made before **Monday, January 30th, at 12:01am PST (-8 GMT)**. Simply tag your git repository with `wiki-contest` and tweet a link to your c9.io project to @nodebits. + +### Judging + +We will judge entries on the following criteria: + + - *Creativeness* - Make it something neat. Think outside the box. Have fun. + - *Beauty* - Both the final interface and the source code that generates it should be pleasing to the eye. + - *Usefulness* - What good is a tool if it's not useful to anyone. Make it actually useful too. + - *Simplicity* - This is an often overlooked trait in software. Yes it often competes with the other goals, but it's a goal that must always be kept in mind. The code shouldn't be any more complicated than necessary. The user interface should be simple *and* easy to understand. + +### Prizes + +I'm sure all of you don't need a prize to help motivate you in this endeavor, but I think prized make the competition funner. + +I've always loved tinkering with small machines and making them do interesting things. Laptops and desktops are cool, but there is only so much they can do in the physical world. We will be giving away [BeagleBone][] kits to the winners along with a slew of fun hardware add-ons to make really cool NodeJS powered robots. + +If you enjoy using Cloud9 for developing your awesome wiki, the same environment (albeit a slightly older version) is used in the bundled SDK of the beaglebone. Node and a full IDE is run from an embedded device that fits into a used altoids can. + +Enter for the glory, enter for the fun, enter to challenge yourself and learn more node! + +----------------------- + +## The Express Route to Building a Apps + +Ok, now let's dig into the actual code that will be the base for your entry. + +The wiki is powered by a hip new server-side framework known as [Express][]. This framework, which I helped with by co-authoring the [Connect][] middleware system back in the summer of 2010, makes it very easy write route based HTTP applications. Express has a view render system built in and some nice helper functions that make life easier for the typical web developer. + +The first step for rapid development is to use the `express` command line script. To install it simple install express globally with [npm][]. + + npm install -g express + +Once this is done, you will have a executable named `express` in the same path as where your `node` binary was. Now running `express wiki` generates a basic app structure as follows: + + wiki/ + ├── app.js + ├── package.json + ├── public + │   ├── images + │   ├── javascripts + │   └── stylesheets + │   └── style.css + ├── routes + │   └── index.js + └── views + ├── index.jade + └── layout.jade + +To run this basic app, simple tell npm to install your dependencies: + + cd wiki + npm install + +And then run the `server.js` file. You should see a nice welcome to express page when http://localhost:3000/ is opened in a browser. + +For more information on using [Express][], see it's webpage or join the express mailing list on google groups. + +## Back to the Wiki + +I won't walk you through all the steps I used to build this simple express based wiki. It would make this article long and boring. But I will quickly show you around the code. + +To follow along with a live running app, click on and any of the "Run in Cloud9" links to clone this repo to your personal c9.io environment. Once cloned, run `npm install` in the console at the bottom to install the needed dependencies. Then run the `app.js` script. + +The first interesting file is the `app.js` main script. Near the bottom of this script is a place to declare your routes. I have four simple routes as follows: + + #@git://github.com/nodebits/wiki-challenge.git#app.js,32-35 + +The root route is where most people will enter the website and simple redirects to the home page of the wiki. Then there are three routes for doing crud operations on wiki pages. We want to view, edit, and save these pages. + +In this default structure for express, the routes are in a different file at `routes/index.js`. Let's look at the defined routes one at a time. + +First is the route to redirect `/` to `/home`. + + #@git://github.com/nodebits/wiki-challenge.git#routes/index.js,5-7 + +### Loading Pages + +That was easy. Now how about viewing a page? Well, first we need to know how to load a page from the database before we can render it to the browser. In this wiki, the "database" is a folder with markdown files in it. Since both the "view" and "edit" actions will need to load the same data, this is extracted out into it's own file simply called `db.js`. + +This is how a wiki page is loaded from the disk: + + #@git://github.com/nodebits/wiki-challenge.git#db.js,12-51 + +It's a pretty good chunk of code, so I'm glad it wasn't repeated twice in the two controllers. In essence it loads the markdown from disk, parses the markdown, extracts the title from the first header and then renders what's left to HTML. If the file doesn't exist, it generates a placeholder instead of telling the browser there is no such file. + +Then back in the controller we use this function to load data and send it out to the view. + + #@git://github.com/nodebits/wiki-challenge.git#routes/index.js,10-15 + +Since the properties given us in the database are the same properties expected in the view, they cal be passed through unmodified. How convenient! + +Here is the jade template used to render views: + + #@git://github.com/nodebits/wiki-challenge.git#views/view.jade + +Editing is much the same. + + #@git://github.com/nodebits/wiki-challenge.git#routes/index.js,18-23 + +It just uses a different jade template. + + #@git://github.com/nodebits/wiki-challenge.git#views/edit.jade + +### Saving Pages + +What good is a wiki if you can't edit and create pages. Did you notice the properties of the form on line 2 of the `edit.jade` template? + + #@git://github.com/nodebits/wiki-challenge.git#views/edit.jade,2 + +What that means in the browser (I had to look this up to remember) is when the user clicks submit, a POST request will be made to the wiki page's url with an old-school form-encoded body. + +Express handles all the details of buffering the body chunks and parsing the form-encoded fields for us since the generated `app.js` file had this line in it. + + #@git://github.com/nodebits/wiki-challenge.git#app.js,17 + +All that's left is to write a route that handles the POST request, saves the markdown to disk and redirects to the view page. + + #@git://github.com/nodebits/wiki-challenge.git#routes/index.js,26-31 + +And with that we have a wiki! + +Now go forth and make it better. I'm excited to see what everyone comes up with. + +[Our Commitment to the Node.js Community]: http://cloud9ide.posterous.com/our-commitment-to-the-nodejs-community +[Cloud9]: http://c9.io/ +[BeagleBone]: http://beagleboard.org/bone +[Express]: http://expressjs.org/ +[Connect]: http://senchalabs.github.com/connect/ diff --git a/resources/footer-trans.png b/resources/footer-trans.png new file mode 100644 index 0000000..0b10cca Binary files /dev/null and b/resources/footer-trans.png differ diff --git a/resources/linux-joystick/f310.png b/resources/linux-joystick/f310.png new file mode 100644 index 0000000..2f7581b Binary files /dev/null and b/resources/linux-joystick/f310.png differ diff --git a/resources/linux-joystick/js0.png b/resources/linux-joystick/js0.png new file mode 100644 index 0000000..8035939 Binary files /dev/null and b/resources/linux-joystick/js0.png differ diff --git a/resources/style.css b/resources/style.css index 5ed8559..8a65a4f 100644 --- a/resources/style.css +++ b/resources/style.css @@ -191,6 +191,10 @@ img { box-shadow: 0px 0px 2px #888; border-radius: 3px; } +.article p img { + float: left; + margin: 0 5px 5px 0; +} .article pre { padding: 8px !important; background-color: #222; @@ -251,7 +255,7 @@ img { padding-right: 10px; } .article .content { - min-height: 90px; + min-height: 150px; } .article .ace_editor:before, .article .ace_editor:after { @@ -352,29 +356,45 @@ img { } .footer { font-family: Georgia, serif; - background: #383838 url("footer.jpg") bottom repeat-x; - color: #757575; + background: url("footer-trans.png") bottom repeat-x; + color: #fff; + font-size: 11px; + padding: 0 25px; + font-family: arial; + height: 148px; + margin-bottom: 40px; + width: 948px; +} +.footer .bold-text { font-size: 12px; font-weight: bold; - margin-bottom: 40px; - -webkit-box-shadow: 0 3px 5px 0 #000; - -moz-box-shadow: 0 3px 5px 0 #000; - -webkit-box-shadow: 0 3px 5px 0 #000; - -moz-box-shadow: 0 3px 5px 0 #000; - box-shadow: 0 3px 5px 0 #000; } .footer p { margin: 0; + line-height: 18px; +} +.footer a { + color: #cae994; + text-decoration: none; +} +.footer a:hover, +.footer a:active { + color: #cae994; + text-decoration: underline; } .footer .sponsor { - padding: 20px 40px 30px; - width: 470px; + margin-right: 280px; + padding: 12px 0 0; + width: 240px; display: inline-block; } +.footer .sponsor img { + margin: 5px 0; +} .footer .copyright { - padding: 20px; - width: 400px; display: inline-block; + width: 420px; + text-shadow: 0 1px 1px #48484a; } #dsq-footer { display: none; diff --git a/resources/style.styl b/resources/style.styl index 0fcfc0d..882cb90 100644 --- a/resources/style.styl +++ b/resources/style.styl @@ -199,6 +199,10 @@ img { box-shadow: 0px 0px 2px #888; border-radius: 3px; } + img { + float:left; + margin: 0 5px 5px 0; + } } pre { @@ -256,7 +260,7 @@ img { padding-right: 10px; } .content { - min-height: 90px; + min-height: 150px; } /*Put a clearfix hack on ace_editor so it doesn't have float issues*/ /*TODO: Fix in ace or snippet tool*/ @@ -359,26 +363,43 @@ img { .footer { body-font() - background: #383838 url(footer.jpg) bottom repeat-x; - color: #757575; - font-size: 12px; - font-weight: bold; + background: url(footer-trans.png) bottom repeat-x; + color: #ffffff; + font-size: 11px; + padding: 0 25px; + font-family: arial; + height: 148px; margin-bottom: 40px; - -webkit-box-shadow: 0 3px 5px 0 #000000; - -moz-box-shadow: 0 3px 5px 0 #000000; - box-shadow: 0 3px 5px 0 #000000; + width: 948px; + .bold-text { + font-size: 12px; + font-weight: bold; + } p { margin: 0; + line-height: 18px; + } + a { + color: #cae994; + text-decoration: none; + } + a:hover, a:active { + color: #cae994; + text-decoration: underline; } .sponsor { - padding: 20px 40px 30px; - width: 470px; + margin-right: 280px; + padding: 12px 0 0; + width: 240px; display: inline-block; + img { + margin: 5px 0; + } } .copyright { - padding: 20px; - width: 400px; display: inline-block; + width: 420px; + text-shadow: 0 1px 1px #48484a; } } diff --git a/server.js b/server.js index 5194841..266b22f 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,24 @@ -var Http = require('http'), - Stack = require('stack'), - Creationix = require('creationix'); +var HTTP = require('http'); +var FS = require('fs'); -var port = process.env.PORT || 8080; +var handle = require('./app'); -Http.createServer(Stack( - Creationix.log(), - require('./app') -)).listen(port); -console.log("Server listening at http://localhost:%s/", port); +// Detect if we're running as root or not +var isRoot = !process.getuid(); +// Set some common variables +var PORT = process.env.PORT || 8000; +HTTP.createServer(handle).listen(PORT); +process.title ="nodebits.org"; +console.log("Server %s listening at http://localhost" + (PORT === 80 ? "" : ":" + PORT) + "/", process.title); + +if (isRoot) { + // Lets change to the owner of this file, whoever that may be + var stat = FS.statSync(__filename); + console.log("Changing gid to " + stat.gid); + process.setgid(stat.gid); + console.log("Changing uid to " + stat.uid); + process.setuid(stat.uid); +} diff --git a/templates/articleindex.html b/templates/articleindex.html index c2161a7..8095c46 100644 --- a/templates/articleindex.html +++ b/templates/articleindex.html @@ -31,6 +31,19 @@ {render("footer", this)} + diff --git a/templates/footer.html b/templates/footer.html index 9217394..7b81714 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,11 +1,11 @@ diff --git a/templates/frontindex.html b/templates/frontindex.html index 55e5b5b..1df41f2 100644 --- a/templates/frontindex.html +++ b/templates/frontindex.html @@ -50,6 +50,19 @@

{title}

{render("footer", this)} + diff --git a/templates/sidebar.html b/templates/sidebar.html index 3b0a198..1f34a7a 100644 --- a/templates/sidebar.html +++ b/templates/sidebar.html @@ -2,7 +2,7 @@

About Nodebits.org

diff --git a/templates/snippet.html b/templates/snippet.html index dcc7f30..09d8dc8 100644 --- a/templates/snippet.html +++ b/templates/snippet.html @@ -1,4 +1,4 @@ \ No newline at end of file