ban 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/doc/ban.fzz ADDED
Binary file
@@ -0,0 +1,26 @@
1
+ require 'faye/websocket'
2
+ require 'eventmachine'
3
+ require 'json'
4
+
5
+ def rc_turn(state, address)
6
+ { "rc-turn-#{state}" => { 'address' => address } }.to_json
7
+ end
8
+
9
+ EM.run do
10
+ ws = Faye::WebSocket::Client.new('ws://localhost:8080/')
11
+ light = '11110D'
12
+
13
+ ws.on :open do |event|
14
+ puts "Connected"
15
+ ws.send(rc_turn(:on, light))
16
+ EM.add_timer(5) { ws.send(rc_turn(:off, light)) }
17
+ end
18
+
19
+ ws.on :message do |event|
20
+ p JSON.parse(event.data)
21
+ end
22
+
23
+ ws.on :close do |event|
24
+ puts "Connection closed"
25
+ end
26
+ end
data/ino.ini ADDED
@@ -0,0 +1,5 @@
1
+ [build]
2
+ board-model = uno
3
+
4
+ [upload]
5
+ board-model = uno
data/lib/ban.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'ban/version'
2
+ require 'logger'
3
+ require 'json'
4
+
5
+ # used gems
6
+ require 'eventmachine'
7
+ require 'em-websocket'
8
+ require 'arduino_firmata'
9
+ require 'thor'
10
+
11
+ module Ban
12
+ Logger = Logger.new(STDOUT)
13
+
14
+ # @param [Array<Fixnum>] data 7 bit encoded data from firmata (midi)
15
+ # @return [String] ascii
16
+ def self.decode7bit(data)
17
+ msg = ""
18
+ i = 0
19
+ while i < data.size
20
+ lsb, msb = data[i], data[i + 1]
21
+
22
+ if msb == 0
23
+ msg << lsb.chr
24
+ else
25
+ msg << (lsb + 0b10000000).chr
26
+ end
27
+ i += 2
28
+ end
29
+ msg
30
+ end
31
+
32
+ # @param [String] data the data to convert to midi 7 bit encoded string
33
+ # @return [Array<Fixnum>] array of midi bytes
34
+ def self.encode7bit(data)
35
+ msg = []
36
+ data.each_byte do |byte|
37
+ msg << (byte & 0b01111111) # LSB
38
+ msg << (byte >> 7 & 0b01111111) # MSB
39
+ end
40
+ msg
41
+ end
42
+ end
43
+
44
+ require 'ban/server'
45
+ require 'ban/event'
46
+ require 'ban/events/door_event'
47
+ require 'ban/events/ir_event'
48
+ require 'ban/events/rc_event'
49
+ require 'ban/board'
50
+ require 'ban/cli'
data/lib/ban/board.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Ban
2
+ class Board
3
+ include EventEmitter
4
+ SEND_RC_ON = 0x20
5
+ SEND_RC_OFF = 0x21
6
+ STRING_DATA = 0x71
7
+ EVENTS = {
8
+ DoorEvent::CMD => DoorEvent,
9
+ IrEvent::CMD => IrEvent,
10
+ RcEvent::CMD => RcEvent
11
+ }
12
+
13
+ def start(path)
14
+ @path = path
15
+ @firmata = ArduinoFirmata.connect @path, nonblock_io: true,
16
+ eventmachine: true
17
+ Logger.debug "Firmata Version: #{@firmata.version}"
18
+
19
+ arduino = self
20
+ @firmata.on :sysex do |cmd, data|
21
+ arduino.received_sysex(cmd, data)
22
+ end
23
+ end
24
+
25
+ def close
26
+ @firmata.close
27
+ end
28
+
29
+ def received_sysex(event_code, data)
30
+ if klass = EVENTS[event_code]
31
+ emit :event, klass.parse(data)
32
+ else
33
+ emit :error, msg: 'Unknown event!', event_code: event_code, data: data
34
+ end
35
+ end
36
+
37
+ def turn_on(address)
38
+ send_command(SEND_RC_ON, address)
39
+ end
40
+
41
+ def turn_off(address)
42
+ send_command(SEND_RC_OFF, address)
43
+ end
44
+
45
+ def send_command(command, data)
46
+ @firmata.sysex(STRING_DATA, Ban.encode7bit(command.chr + data))
47
+ end
48
+ end
49
+ end
data/lib/ban/cli.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Ban
2
+ class CLI < Thor
3
+ option :device, default: nil
4
+ option :port, type: :numeric, default: 8080
5
+ option :interface, type: :string, default: '0.0.0.0'
6
+ desc "server", "starts the ban server"
7
+ def server
8
+ device = options[:device]
9
+ interface = options[:interface]
10
+ port = options[:port]
11
+
12
+ EM.epoll
13
+ EM.run do
14
+ board = Board.new
15
+ server = Server.new
16
+
17
+ board.on :event do |event|
18
+ if event.valid?
19
+ Ban::Logger.debug "#{event.class}: #{event}"
20
+ server.broadcast event
21
+ end
22
+ end
23
+
24
+ server.on :command do |command|
25
+ Ban::Logger.debug "Received cmd #{command}"
26
+ if options = command['rc-turn-off']
27
+ Ban::Logger.debug "Turn off #{options['address']}"
28
+ board.turn_off(options['address'])
29
+ elsif options = command['rc-turn-on']
30
+ Ban::Logger.debug "Turn on #{options['address']}"
31
+ board.turn_on(options['address'])
32
+ end
33
+ end
34
+
35
+ trap(:INT) { board.close; EM.stop }
36
+ trap(:TERM) { board.close; EM.stop }
37
+
38
+ Ban::Logger.info "Connecting to #{device || 'auto'}"
39
+ board.start(device)
40
+ Ban::Logger.info "Starting WebSocket Server on #{interface}:#{port}"
41
+ server.start(interface, port)
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/ban/event.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Ban
2
+ class Event
3
+ def valid?
4
+ true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ module Ban
2
+ class DoorEvent < Event
3
+ CMD = 0x37
4
+
5
+ attr_reader :state
6
+
7
+ def self.parse(data)
8
+ new(Ban.decode7bit(data).downcase)
9
+ end
10
+
11
+ def initialize(state)
12
+ @state = state
13
+ end
14
+
15
+ def name
16
+ open? ? 'door-opened' : 'door-closed'
17
+ end
18
+
19
+ def to_hash
20
+ { 'state' => @state }
21
+ end
22
+
23
+ def open?
24
+ @state == 'open'
25
+ end
26
+
27
+ def to_s
28
+ "door: #{open? ? 'opened' : 'closed'}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Ban
2
+ class IrEvent < Event
3
+ CMD = 0x36
4
+
5
+ attr_reader :code
6
+
7
+ def self.parse(data)
8
+ new(Ban.decode7bit(data).to_i)
9
+ end
10
+
11
+ def initialize(code)
12
+ @code = code
13
+ end
14
+
15
+ def name
16
+ 'ir-received'
17
+ end
18
+
19
+ def hex
20
+ code.to_s(16)
21
+ end
22
+
23
+ def to_hash
24
+ { 'code' => @code, 'hex' => hex }
25
+ end
26
+
27
+ def to_s
28
+ "#{code} (0x#{hex})"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ module Ban
2
+ class RcEvent < Event
3
+ CMD = 0x35
4
+
5
+ attr_reader :decimal, :bits, :delay, :protocol
6
+
7
+ def self.parse(data)
8
+ new(*Ban.decode7bit(data).split('|').map(&:to_i))
9
+ end
10
+
11
+ def initialize(decimal, bits, delay, protocol)
12
+ @decimal, @bits, @delay, @protocol = decimal, bits, delay, protocol
13
+ end
14
+
15
+ def name
16
+ (tristate[-1] == 'F') ? 'rc-turned-on' : 'rc-turned-off'
17
+ end
18
+
19
+ def valid?
20
+ tristate
21
+ true
22
+ rescue ArgumentError
23
+ false
24
+ end
25
+
26
+ def to_hash
27
+ {
28
+ 'decimal' => decimal,
29
+ 'bits' => bits,
30
+ 'binary' => binary,
31
+ 'tristate' => tristate,
32
+ 'delay' => delay,
33
+ 'protocol' => protocol
34
+ }
35
+ end
36
+
37
+ def to_s
38
+ "decimal: #{decimal} (#{bits} bits) binary #{binary} " +
39
+ "tri-state: #{tristate} pulse length: #{delay} (ms) " +
40
+ "protocol: #{protocol}"
41
+ end
42
+
43
+ def binary
44
+ ("%#{bits}s" % decimal.to_s(2)).gsub(' ', '0')
45
+ end
46
+
47
+ def tristate
48
+ binary.gsub(/[0-9]{2}/) do |match|
49
+ if match == '00'
50
+ '0'
51
+ elsif match == '01'
52
+ 'F'
53
+ else
54
+ raise ArgumentError, "#{match} not applicable"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/ban/server.rb ADDED
@@ -0,0 +1,37 @@
1
+ module Ban
2
+ class Server
3
+ include EventEmitter
4
+ def initialize
5
+ @clients = []
6
+ end
7
+
8
+ def broadcast(event)
9
+ @clients.each do |client|
10
+ client.send({ event.name => event.to_hash }.to_json)
11
+ end
12
+ end
13
+
14
+ def start(interface, port)
15
+ @server = EM::WebSocket.run(host: interface, port: port) do |ws|
16
+ ws.onopen do |handshake|
17
+ Ban::Logger.debug "WebSocket connection open"
18
+ @clients << ws
19
+ end
20
+
21
+ ws.onclose do
22
+ Ban::Logger.debug "WebSocket connection closed"
23
+ @clients.delete(ws)
24
+ end
25
+
26
+ ws.onmessage do |command_json|
27
+ begin
28
+ command = JSON.parse(command_json)
29
+ emit :command, command
30
+ rescue JSON::ParserError
31
+ Ban::Logger.warn "Received invalid json: #{command_json}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Ban
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ban::DoorEvent do
4
+ context 'open' do
5
+ let(:data) { [79, 0, 80, 0, 69, 0, 78, 0] }
6
+
7
+ subject { described_class.parse(data) }
8
+
9
+ its(:name) { should == 'door-opened' }
10
+ its(:valid?) { should == true }
11
+ its(:to_hash) { should == { "state" => "open" } }
12
+ its(:to_s) { should == "door: opened" }
13
+ end
14
+
15
+ context 'close' do
16
+ let(:data) { [67, 0, 76, 0, 79, 0, 83, 0, 69, 0] }
17
+
18
+ subject { described_class.parse(data) }
19
+
20
+ its(:name) { should == 'door-closed' }
21
+ its(:valid?) { should == true }
22
+ its(:to_hash) { should == { "state" => "close" } }
23
+ its(:to_s) { should == "door: closed" }
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ban::IrEvent do
4
+ let(:data) { [51, 0, 48, 0, 54, 0, 56, 0, 57, 0] }
5
+
6
+ subject { described_class.parse(data) }
7
+
8
+ its(:name) { should == 'ir-received' }
9
+ its(:valid?) { should == true }
10
+ its(:to_hash) do
11
+ should == { "code" => 30689, "hex" => "77e1" }
12
+ end
13
+ its(:to_s) do
14
+ should == "30689 (0x77e1)"
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ban::RcEvent do
4
+ subject { described_class.parse(data) }
5
+
6
+ context 'valid' do
7
+ let(:data) do
8
+ [
9
+ 50, 0, 49, 0, 53, 0, 56, 0, 56, 0, 124, 0, 50, 0,
10
+ 52, 0, 124, 0, 51, 0, 50, 0, 50, 0, 124, 0, 49, 0
11
+ ]
12
+ end
13
+
14
+ its(:name) { should == 'rc-turned-off' }
15
+ its(:valid?) { should == true }
16
+ its(:to_hash) do
17
+ should == {
18
+ "decimal" => 21588,
19
+ "bits" => 24,
20
+ "binary" => "000000000101010001010100",
21
+ "tristate" => "0000FFF0FFF0",
22
+ "delay" => 322,
23
+ "protocol" => 1
24
+ }
25
+ end
26
+ its(:to_s) do
27
+ should == "decimal: 21588 (24 bits) binary 000000000101010001010100 " +
28
+ "tri-state: 0000FFF0FFF0 pulse length: 322 (ms) protocol: 1"
29
+ end
30
+ end
31
+
32
+ context 'invalid' do
33
+ let(:data) do
34
+ [
35
+ 50, 0, 49, 0, 53, 0, 56, 0, 30, 0, 124, 0, 50, 0,
36
+ 52, 0, 124, 0, 51, 0, 50, 0, 50, 0, 124, 0, 49, 0
37
+ ]
38
+ end
39
+
40
+ its(:valid?) { should == false }
41
+ end
42
+ end