diff --git a/.gitignore b/.gitignore index bca4155..d3904c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ colors.txt node_modules/* +npm-debug.log diff --git a/.travis.yml b/.travis.yml index 7f4dc12..d85d56d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ node_js: - "0.10.30" before_install: script: - - "which node" + - "npm test" env: - NODE_ENV=test diff --git a/README.md b/README.md index a9c3f1e..f379c54 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A socket.io 1.0 client that connects to a [colorpicker-server](http://github.com Its main purpose is to write rgb color data to a colors.txt file for [halo](https://github.com/lordnibbler/halo) to read. ## Getting Started -You'll need a [colorpicker-server](http://github.com/lordnibbler/colorpicker-server) instance running before this client is useful: +You'll need a [colorpicker-server](http://github.com/lordnibbler/colorpicker-server) instance running before this client is useful. You can deploy this server to a free host like Heroku or Nodejitsu, or test locally. ```sh # set up the GUI and server @@ -22,3 +22,113 @@ npm start You should see a `connected to socket at http://127.0.0.1:1337` message. Browse to to use the GUI. + +## Configuring a Beaglebone Black with Angstrom Distro +You'll need to do three things: + +1. upgrade Node.js +2. configure `systemctl` to set up reliable network connectivity at boot +3. configure `systemctl` to start `colorpicker-beaglebone` Node service at boot + +### 1. Upgrade Node.js +I refer you to the [wonderful instructions at speakinbytes.com](http://speakinbytes.com/2013/12/update-beaglebone-black-angstrom-node-js-version/). I recommend the current stable version of node, currently 0.10.32. You'll need at LEAST 0.10.0. + +### 2. Network Connectivity +My findings have been that `connman` is very unreliable, so I fell back to using `/etc/network/interfaces`. To disable connman: + +```sh +systemctl disable connman.service + +# double check +systemctl status connman.service +``` + +Edit `/etc/network/interfaces` with your editor of choice. For ethernet/hardwired internet connectivity the only logic you need here is the loopback and the `eth0` configuration, but there's also some example wifi, USB, and bluetooth configs here: + +```sh +# /etc/network/interfaces +# configuration file for ifup(8), ifdown(8) + +# The loopback interface +auto lo +iface lo inet loopback + +# Wireless interfaces + iface wlan0 inet dhcp + wireless_mode managed + wireless_essid any + wpa-driver wext + wpa-conf /etc/wpa_supplicant.conf + + iface atml0 inet dhcp + +# Wired or wireless interfaces +auto eth0 +iface eth0 inet dhcp + +# Ethernet/RNDIS gadget (g_ether) +# ... or on host side, usbnet and random hwaddr +iface usb0 inet static + address 192.168.7.2 + netmask 255.255.255.0 + network 192.168.7.0 + gateway 192.168.7.1 + +# Bluetooth networking +iface bnep0 inet dhcp +``` + +If you want additional help setting up WiFi, [this article](http://octopusprotos.com/?p=37) is handy. + +After setting up your interfaces, configure a `systemctl` service to start ethernet connectivity at boot, touch a new file located at `/etc/systemd/system/net.service`: + +```sh +# /etc/systemd/system/net.service +[Unit] +Description=Network interfaces +Wants=network.target +Before=network.target +BindsTo=sys-subsystem-net-devices-eth0.device +After=sys-subsystem-net-devices-eth0.device + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/sh -c "sleep 5; ifup eth0" +ExecStop=/bin/sh -c "ifdown eth0" + +[Install] +WantedBy=multi-user.target +``` + +Then enable the service! + +```sh +systemctl enable net.service + +# double check +systemctl status net.service +``` + +You can check your connectivity using `ifconfig`. + +3. Starting `colorpicker-beaglebone` at boot + +To start this Beaglebone colorpicker client at boot, you can follow a similar approach to the network connectivity service. I have provided an example in the `/angstrom` directory of this repository. + +First, touch a new file at `/lib/systemd/system/colorpickerbeaglebone.service` (ensure your paths are correct, these are an example): + +```sh +# /lib/systemd/system/colorpickerbeaglebone.service +[Unit] +Description=colorpicker-beaglebone automatic start + +[Service] +WorkingDirectory=/home/root/colorpicker-beaglebone/angstrom +ExecStart=/home/root/colorpicker-beaglebone/angstrom/colorpickerbeaglebone.sh + +[Install] +WantedBy=multi-user.target +``` + +You can copy the contents of `/angstrom/colorpickerbeaglebone.sh` from this repo if you intend to use UART + Arduino to power the LEDs. diff --git a/angstrom/colorpickerbeaglebone.service b/angstrom/colorpickerbeaglebone.service new file mode 100644 index 0000000..a762387 --- /dev/null +++ b/angstrom/colorpickerbeaglebone.service @@ -0,0 +1,9 @@ +[Unit] +Description=Wifi Startup Retry + +[Service] +WorkingDirectory=/home/root/colorpicker-beaglebone/wifiboot +ExecStart=/home/root/colorpicker-beaglebone/wifiboot/wifiboot.sh + +[Install] +WantedBy=multi-user.target diff --git a/angstrom/colorpickerbeaglebone.sh b/angstrom/colorpickerbeaglebone.sh new file mode 100755 index 0000000..1c9e735 --- /dev/null +++ b/angstrom/colorpickerbeaglebone.sh @@ -0,0 +1,12 @@ +#!/bin/sh - +sleep 10 + +# enable the UART1 in the device tree +echo BB-UART1 > /sys/devices/bone_capemgr.9/slots + +# Set proper UART settings with STTY +stty -F /dev/ttyO1 speed 115200 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts + +# start the node client application +cd /home/root/colorpicker-beaglebone/ +NODE_ENV=production npm start diff --git a/wifiboot/cp_service b/angstrom/cp_service similarity index 100% rename from wifiboot/cp_service rename to angstrom/cp_service diff --git a/angstrom/halo-reduced.pl b/angstrom/halo-reduced.pl new file mode 100644 index 0000000..0dc237b --- /dev/null +++ b/angstrom/halo-reduced.pl @@ -0,0 +1,121 @@ +#!/usr/bin/perl + +# use Device::SerialPort; +use Time::HiRes qw(usleep time); +use POSIX ":sys_wait_h"; +#use LWP::Simple qw (get); + +# @FIXME missing this dir in github! +$cmddir = "/home/root/halo_git/commands/"; + +our $NUM_LIGHTS = 5; +my $SYSTEM_ON = 0; +my $LIVE_PREVIEW = 1; + +@oldRgb; +for($j = 0;$j < $NUM_LIGHTS; $j++){ + push(@{$oldRgb[$j]},(0,0,0,0)); +} + +# system("echo 20 > /sys/kernel/debug/omap_mux/uart1_rxd"); +# system("echo 0 > /sys/kernel/debug/omap_mux/uart1_txd"); +system("echo BB-UART1 > /sys/devices/bone_capemgr.9/slots"); +system("stty -F /dev/ttyO1 speed 115200 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts"); + +$|=1; + +# Opens file whose filename is given by EXPR (> /dev/tty01), and associates it with FILEHANDLE (SERIAL) +open(SERIAL, "> /dev/ttyO1"); + +sysopen(PREVIEW_DATA, "/home/root/colorpicker-beaglebone/colors.txt", O_RONLY) + or die "can't read pipe: $!"; + +{ + my $previous_default = select(STDOUT); # save previous default + select(SERIAL); + $|++; # autoflush STDERR, to be sure + select($previous_default); # restore previous default +} + +# set all black +for(my $address = 0; $address < $NUM_LIGHTS; $address ++){ + &sendColor($address,0,0,0,0); +} + +sub sendColor { + my($address,$r,$g,$b,$v)= @_; + $address = $address + 1; + print SERIAL "4,$address,$r,$g,$b,$v;"; +} + +sub turnOffAll { + while(waitpid(-1,WNOHANG ) >= 0) {} + for(my $address = 0; $address < $NUM_LIGHTS; $address ++){ + &sendColor($address,0,0,0,0); + @{$oldRgb[$address]} = (0,0,0,0); + } +} + + + +$start_time = time(); + +sub grabLiveData{ + # print @preview_data; + + $rin = ''; + vec($rin, fileno(PREVIEW_DATA), 1) = 1; + $nfound = select($rin, undef, undef, 0); # just check + if ($nfound) { + @processed_data = (); + while($color = ){ + #print $color; + #print "Glen\n"; + if($color =~ /.*END_LINE.*/){ + # print "END_FILE FOUND\n"; + last; + } + if($color =~ /([0-9]+)\,([0-9]+)\,([0-9]+)\,([0-9]+)/){ + # print "$1, $2, $3, $4 found\n"; + my @rgb = ($1,$2,$3,0); + push(@processed_data,[@rgb]); + } + } + + # close PREVIEW_DATA or die "bad netstat: $! $?"; + $previewLength = @processed_data; + if( $previewLength > 0){ + my $end_time = time(); + printf("%d %.6f\n", $previewLength,$end_time - $start_time); + $start_time = time(); + # print "$previewLength data chunks\n"; + $start =0; + if($previewLength > $NUM_LIGHTS){ + $start = $previewLength - $NUM_LIGHTS; + }else{ + $start = 0; + } + # print "i is $i, total size is $previewLength"; + $address = 0; + for($i = $start;$i< $previewLength; $i ++){ + my @rgb = @{$processed_data[$i]}; + # print @rgb; + # printf("address %d R= %d G=%d B=%d\n",$address,$rgb[0],$rgb[1],$rgb[2],$rgb[3]); + &sendColor($address,$rgb[0],$rgb[1],$rgb[2],$rgb[3]); + $address ++; + } + if($previewLength < $NUM_LIGHTS){ + while($address < $NUM_LIGHTS){ + &sendColor($address,0,0,0,0); + $address ++; + } + } + } + } +} + +while(1){ + if($LIVE_PREVIEW == 1){ + &grabLiveData(); + } +} diff --git a/wifiboot/last_ping.txt b/angstrom/last_ping.txt similarity index 100% rename from wifiboot/last_ping.txt rename to angstrom/last_ping.txt diff --git a/wifiboot/ping_test.py b/angstrom/ping_test.py similarity index 100% rename from wifiboot/ping_test.py rename to angstrom/ping_test.py diff --git a/package.json b/package.json index 5fa5e48..55dea66 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "colorpicker-beaglebone", - "version": "1.0.6", + "version": "1.1.0", "description": "A beaglebone Node.js client to listen to colorpicker, and pipe current color data into a writeStream.", "scripts": { "start": "coffee index.coffee", - "test": "npm test" + "test": "mocha" }, "repository": { "type": "git", @@ -16,13 +16,18 @@ "url": "https://github.com/Lordnibbler/colorpicker-beaglebone/issues" }, "dependencies": { - "log": "~1.4.0", "coffee-script": "~1.7", - "socket.io": "~1.0.6", - "socket.io-client": "~1.0.6" + "log": "~1.4.0", + "socket.io": "~1.1.0", + "socket.io-client": "~1.1.0" }, "engines": { - "node": "0.10.x", + "node": "0.10.32", "npm": "1.3.x" + }, + "devDependencies": { + "chai": "^1.9.1", + "mocha": "^1.21.4", + "sinon": "^1.10.3" } } diff --git a/src/server.coffee b/src/server.coffee index a7a2ca6..4eee408 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -4,37 +4,100 @@ logger = require './logger' FS = require 'fs' class Server + buffer: [] + ws: undefined + timer: undefined + constructor: (@host, @port, @options = {}) -> + # + # connect to kernel and socket.io + # + run: (callback) -> + @_setup_writestream() + @_setup_sio() + # # @return [String] URL based on config # - url: -> + _url: -> "http://#{ @host }#{ if @port? then ":#{@port}" else '' }/#{ @options.namespace }" # # connect socket.io client to url, bind to socket.io events and our custom events # - run: (callback) -> - logger.debug "Connecting to url #{@url()}" - socket = new io(@url(), {}) + _setup_sio: -> + socket = new io(@_url(), {}) + socket.on 'connect', => + logger.info "connected to socket at #{@_url()}" - socket.on 'connect', => console.log "connected to socket at #{@url()}" - socket.on 'connect_error', (obj) => console.log 'connect error', obj - socket.on 'disconnect', => console.log "socket at #{@url()} disconnected" - socket.on 'colorChanged', @_write_colors_data_to_file - socket.on 'colorSet', @_write_colors_data_to_file + # make an initial call to our recursive _write_pipe() method + @_write_pipe() + + socket.on 'connect_error', (obj) => logger.info 'connect error', obj + socket.on 'disconnect', => @_disconnected() + socket.on 'colorChanged', (data) => @_to_buffer(data) + socket.on 'colorSet', (data) => @_to_buffer(data) # - # write our preformatted backbone.js color data to colors.txt + # when a client disconnects, clear any timer in memory to avoid memory leak or multiple + # messages being sent in the future! # - _write_colors_data_to_file: (data) -> - logger.debug JSON.stringify(data, null, 2) + _disconnected: -> + logger.info "socket at #{@_url()} disconnected" + clearTimeout(@timer) if @timer? - ws = FS.createWriteStream("#{__dirname}/../colors.txt", { flags: "w+" }) - ws.write(data.color, (err, written) -> - throw err if err - ws.end() - ) + # + # create writestream to kernel at /dev/ttyO1 + # + _setup_writestream: -> + @ws = FS.createWriteStream (if process.env.NODE_ENV == 'production' then '/dev/ttyO1' else '/dev/null'), + flags: "w+" + + # + # write buffer directly to the kernel via /dev/ttyO1 pipe + # if buffer has content, at 30ms resolution. recursively call this function on success + # + _write_pipe: -> + @timer = setTimeout (=> + # recurse + if @buffer.length == 0 + @_write_pipe() + else + @ws.write(@buffer, (err, written) => + throw err if err + @buffer = '' + @_write_pipe() + ) + ), 30 + + # + # convert halo rgba string to a UART instruction + # @example + # _data_to_instruction({ color: '000,110,255,000\n255,000,000,000' }) + # # => '345,5,1,255,000,000;2,255,000,000,3,000,000,000,4,000,000,000,5,000,000,000;' + # + _data_to_instruction: (data) -> + # break colors string into array + colors = data.color.split '\n' + colors.pop() + + # remove `v` value from each color + colors = colors.map (c) -> c.slice(0,-4) + + # build our TTY instruction + instruction = '345,5,' + instruction += "#{i+1},#{color};" for color, i in colors + + # padding with black if necessary + instruction_count = (instruction.match(/;/g)||[]).length + instruction += "#{i+1},000,000,000;" for i in [instruction_count...5] + return instruction + + # + # write socket.io color data to buffer as UART instruction + # + _to_buffer: (data) -> + @buffer = @_data_to_instruction(data) module.exports = Server diff --git a/test/server_test.coffee b/test/server_test.coffee new file mode 100644 index 0000000..e21032f --- /dev/null +++ b/test/server_test.coffee @@ -0,0 +1,3 @@ +server = require '../src/server' + +describe 'Server', -> diff --git a/wifiboot.service b/wifiboot.service deleted file mode 100644 index 1c95343..0000000 --- a/wifiboot.service +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Wifi Startup Retry - -[Service] -WorkingDirectory=/home/root/wifiboot/ -ExecStart=/home/root/wifiboot/wifiboot.sh - -[Install] -WantedBy=multi-user.target diff --git a/wifiboot/wifiboot.service b/wifiboot/wifiboot.service deleted file mode 100644 index 1c95343..0000000 --- a/wifiboot/wifiboot.service +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Wifi Startup Retry - -[Service] -WorkingDirectory=/home/root/wifiboot/ -ExecStart=/home/root/wifiboot/wifiboot.sh - -[Install] -WantedBy=multi-user.target diff --git a/wifiboot/wifiboot.sh b/wifiboot/wifiboot.sh deleted file mode 100755 index a1dfd98..0000000 --- a/wifiboot/wifiboot.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -sleep 10 -#systemctl restart connman.service - -#python ping_test.py >/dev/null & - -cd /home/root/halo_git/ - -./Halo_Master.pl > /dev/null & - -cd /home/root/colorpicker-beaglebone/ - -NODE_ENV=production npm start -