subaru 1.0.0
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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +79 -0
- data/bin/subaru +13 -0
- data/lib/config_temp.yml +13 -0
- data/lib/subaru.rb +63 -0
- data/lib/subaru/d-xytronix.rb +155 -0
- data/lib/subaru/helper.rb +48 -0
- data/subaru.gemspec +17 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1dd0e11ac22422831de4f5b6852fd029792f48a7
|
4
|
+
data.tar.gz: 769c66c9d6da8deb6292eabe37f21d70f1703c24
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccffba240eb408226b094ed8dff4cf2f4d357864419cdbd497e7d90f47d56c64980a04493bbe4ba4395279205e44cd62bbda59c2bad1be601d0827810dfccb2f
|
7
|
+
data.tar.gz: 06a7b92779b99595e2328de537251c187ae90f38f8502f84719974daeb61ca345307202bfb411c4bef31ca4b00fcd97e828bc80b31e6620200ff365fdd35b200
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Ken J.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Subaru
|
2
|
+
|
3
|
+
A gem to run RESTful (kinda) API server to industrial relays.
|
4
|
+
|
5
|
+
The `subaru` command will launch a [Sinatra](http://www.sinatrarb.com) based server, functioning as a gateway to your web-enabled relays.
|
6
|
+
|
7
|
+
## Supported Relays
|
8
|
+
|
9
|
+
- Xytronix (a.k.a. ControlByWeb)
|
10
|
+
- WebRelay
|
11
|
+
- WebRelay-10
|
12
|
+
|
13
|
+
I welcome requests for other relay equipment. If development/evaluation units can be provided, that would increase the likelyhood of support.
|
14
|
+
|
15
|
+
## Requirements
|
16
|
+
|
17
|
+
- Ruby 2.0.0 <=
|
18
|
+
- [Kajiki](http://www.kenj.rocks/kajiki/) 1.1 <=
|
19
|
+
- Sinatra 1.4 <=
|
20
|
+
|
21
|
+
## Getting Started
|
22
|
+
|
23
|
+
### Install
|
24
|
+
|
25
|
+
```
|
26
|
+
$ gem install subaru
|
27
|
+
```
|
28
|
+
|
29
|
+
### Configure
|
30
|
+
|
31
|
+
Store configuration in a YAML file. If you need a template, just try to start Subaru without the `--config` option and it'll output an example.
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
---
|
35
|
+
:global:
|
36
|
+
:pretty_json: YES
|
37
|
+
:auth_tokens:
|
38
|
+
:any:
|
39
|
+
- abcd # This is the auth token. List as many as you want, or remove it to disable auth.
|
40
|
+
|
41
|
+
:devices:
|
42
|
+
factory: # This is the device name to use in the URL.
|
43
|
+
:definition: Xytronix::WebRelay
|
44
|
+
:url: http://192.168.0.10
|
45
|
+
:password: password
|
46
|
+
:read_timeout: 15
|
47
|
+
```
|
48
|
+
|
49
|
+
### Run
|
50
|
+
|
51
|
+
```
|
52
|
+
$ subaru start -c config.yml
|
53
|
+
```
|
54
|
+
|
55
|
+
### Consume
|
56
|
+
|
57
|
+
`GET` to read the state.
|
58
|
+
|
59
|
+
```
|
60
|
+
$ curl http://subaru/factory
|
61
|
+
{
|
62
|
+
"relay": "off",
|
63
|
+
"input": "off"
|
64
|
+
}
|
65
|
+
```
|
66
|
+
|
67
|
+
`PUT` to set the state.
|
68
|
+
|
69
|
+
```
|
70
|
+
$ curl -X PUT http://subaru/factory -d '{"relay":"on"}'
|
71
|
+
{
|
72
|
+
"relay": "on",
|
73
|
+
"input": "off"
|
74
|
+
}
|
75
|
+
```
|
76
|
+
|
77
|
+
## Security
|
78
|
+
|
79
|
+
For obvious reasons, you should not expose this server to the public. If so, at least protect behind a reverse proxy, like NGINX, and/or require auth over HTTPS.
|
data/bin/subaru
ADDED
data/lib/config_temp.yml
ADDED
data/lib/subaru.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'yaml'
|
3
|
+
require 'subaru/helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Subaru
|
7
|
+
|
8
|
+
# The Sinatra server
|
9
|
+
class Server < Sinatra::Base
|
10
|
+
|
11
|
+
def self.run!(opts = {})
|
12
|
+
unless opts[:config]
|
13
|
+
puts "Configuration file required. Example:\n\n"
|
14
|
+
puts IO.read(File.expand_path('../config_temp.yml', __FILE__))
|
15
|
+
abort
|
16
|
+
end
|
17
|
+
config = YAML.load_file(opts[:config])
|
18
|
+
super(bind: opts[:address], port: opts[:port], subaru_config: config)
|
19
|
+
end
|
20
|
+
|
21
|
+
helpers Helper
|
22
|
+
|
23
|
+
configure do
|
24
|
+
disable :static
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
halt 401 unless valid_token?(params[:auth])
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/:device' do |device|
|
32
|
+
dev = device_with_name(device)
|
33
|
+
halt 404 unless dev
|
34
|
+
res = dev.read
|
35
|
+
halt 500 unless res
|
36
|
+
json_with_object(res)
|
37
|
+
end
|
38
|
+
|
39
|
+
put '/:device' do |device|
|
40
|
+
dev = device_with_name(device)
|
41
|
+
halt 404 unless dev
|
42
|
+
request.body.rewind # in case someone already read it
|
43
|
+
res = dev.write(request.body.read)
|
44
|
+
halt 500 unless res
|
45
|
+
json_with_object(res)
|
46
|
+
end
|
47
|
+
|
48
|
+
not_found do
|
49
|
+
json_with_object({message: 'Not found.'})
|
50
|
+
end
|
51
|
+
|
52
|
+
error 401 do
|
53
|
+
json_with_object({message: 'Auth required.'})
|
54
|
+
end
|
55
|
+
|
56
|
+
error do
|
57
|
+
status 500
|
58
|
+
json_with_object({message: 'Sorry, internal error.'})
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'uri'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
|
6
|
+
module Subaru
|
7
|
+
|
8
|
+
module Definitions
|
9
|
+
|
10
|
+
module Xytronix
|
11
|
+
|
12
|
+
module Common
|
13
|
+
attr_accessor :options
|
14
|
+
|
15
|
+
# @param url [String] device url; e.g., `http://10.1.1.11:8888`.
|
16
|
+
# @param opts [Hash] the device options.
|
17
|
+
def initialize(opts = {})
|
18
|
+
@options = opts
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generate query string.
|
22
|
+
# @param query_hash [hash] resources and their state to set to.
|
23
|
+
# @return [String] query string.
|
24
|
+
def generate_query(query_hash)
|
25
|
+
return nil unless query_hash.class == Hash
|
26
|
+
h = {}
|
27
|
+
query_hash.each do |k, v|
|
28
|
+
case v
|
29
|
+
when "off"
|
30
|
+
h["#{k}State"] = 0
|
31
|
+
when "on"
|
32
|
+
h["#{k}State"] = 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return nil if h.empty?
|
36
|
+
return URI.encode_www_form(h)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Access the device.
|
40
|
+
# @param query [String] encoded query string.
|
41
|
+
# @return [Object] XML document returned from the device.
|
42
|
+
def send_request(query = nil)
|
43
|
+
uri = "#{@options[:url]}/stateFull.xml"
|
44
|
+
uri << "?#{query}" if query
|
45
|
+
o = {}
|
46
|
+
o[:http_basic_authentication] = [nil, @options[:password]] if @options[:password]
|
47
|
+
o[:read_timeout] = @options[:read_timeout] if @options[:read_timeout]
|
48
|
+
response = open(uri, o)
|
49
|
+
return REXML::Document.new(response.read)
|
50
|
+
rescue => e
|
51
|
+
puts e.message
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Search the XML document.
|
56
|
+
# @param doc [Object] XML document.
|
57
|
+
# @param resource [String] resource name to search.
|
58
|
+
# @param ch [Fixnum] resource channel number.
|
59
|
+
# @return [String] state of the resource; e.g., `on`, `off`.
|
60
|
+
def state_from_xml(doc, resource, ch = nil)
|
61
|
+
case REXML::XPath.first(doc, "//datavalues/#{resource}#{ch}state").text.to_i
|
62
|
+
when 0
|
63
|
+
s = "off"
|
64
|
+
when 1
|
65
|
+
s = "on"
|
66
|
+
else
|
67
|
+
s = "unknown"
|
68
|
+
end
|
69
|
+
return s
|
70
|
+
rescue => e
|
71
|
+
puts e.message
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
class WebRelay
|
78
|
+
include Common
|
79
|
+
|
80
|
+
# Read resources from device.
|
81
|
+
# @return [Hash] resources and their state.
|
82
|
+
def read
|
83
|
+
return parse_xml(send_request)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set resource state of a device.
|
87
|
+
# @param data [String] POST/PUT data.
|
88
|
+
# @return [Hash] resulting resources and their state.
|
89
|
+
def write(data)
|
90
|
+
q = generate_query(JSON.parse(data))
|
91
|
+
return nil unless q
|
92
|
+
return parse_xml(send_request(q))
|
93
|
+
rescue => e
|
94
|
+
puts e.message
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Parse XML document response.
|
99
|
+
# @param xml [Object] XML document.
|
100
|
+
# @return [Hash]
|
101
|
+
def parse_xml(xml)
|
102
|
+
results = {}
|
103
|
+
['relay', 'input'].each do |r|
|
104
|
+
results[r] = state_from_xml(xml, r)
|
105
|
+
end
|
106
|
+
return results
|
107
|
+
rescue => e
|
108
|
+
puts e.message
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
class WebRelay10
|
115
|
+
include Common
|
116
|
+
|
117
|
+
# Read resources from device.
|
118
|
+
# @return [Hash] resources and their state.
|
119
|
+
def read
|
120
|
+
return parse_xml(send_request)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Set resource state of a device.
|
124
|
+
# @param data [String] POST/PUT data.
|
125
|
+
# @return [Hash] resulting resources and their state.
|
126
|
+
def write(data)
|
127
|
+
q = generate_query(JSON.parse(data))
|
128
|
+
return nil unless q
|
129
|
+
return parse_xml(send_request(q))
|
130
|
+
rescue => e
|
131
|
+
puts e.message
|
132
|
+
return nil
|
133
|
+
end
|
134
|
+
|
135
|
+
# Parse XML document response.
|
136
|
+
# @param xml [Object] XML document.
|
137
|
+
# @return [Hash]
|
138
|
+
def parse_xml(xml)
|
139
|
+
results = {}
|
140
|
+
(1..10).each do |ch|
|
141
|
+
results["relay#{ch}"] = state_from_xml(xml, 'relay', ch)
|
142
|
+
end
|
143
|
+
return results
|
144
|
+
rescue => e
|
145
|
+
puts e.message
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'subaru/d-xytronix'
|
3
|
+
|
4
|
+
|
5
|
+
module Subaru
|
6
|
+
|
7
|
+
# Sinatra helper
|
8
|
+
module Helper
|
9
|
+
|
10
|
+
# Convert object into JSON.
|
11
|
+
def json_with_object(object, opts = {pretty: settings.subaru_config[:global][:pretty_json]})
|
12
|
+
return '{}' if object.nil?
|
13
|
+
if opts[:pretty] == true
|
14
|
+
opts = {
|
15
|
+
indent: ' ',
|
16
|
+
space: ' ',
|
17
|
+
object_nl: "\n",
|
18
|
+
array_nl: "\n"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
JSON.fast_generate(object, opts)
|
22
|
+
rescue => e
|
23
|
+
puts e.message
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validate auth token, if configured.
|
28
|
+
def valid_token?(token, method = :any)
|
29
|
+
tokens = settings.subaru_config[:global][:auth_tokens][method]
|
30
|
+
return true if tokens.nil?
|
31
|
+
tokens.include?(token)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create device object with name
|
35
|
+
# @param name [String]
|
36
|
+
# @return [Object]
|
37
|
+
def device_with_name(name)
|
38
|
+
c = settings.subaru_config[:devices][name]
|
39
|
+
return Object.const_get("Subaru::Definitions::#{c[:definition]}").new(c) if c
|
40
|
+
rescue => e
|
41
|
+
puts e.message
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
data/subaru.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "subaru"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.authors = ["Ken J."]
|
5
|
+
s.email = ["kenjij@gmail.com"]
|
6
|
+
s.description = %q{RESTful API to industrial web relays}
|
7
|
+
s.summary = %q{RESTful API server gem to industrial web relays.}
|
8
|
+
s.homepage = "https://github.com/kenjij/subaru"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split($/)
|
12
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
|
15
|
+
s.add_runtime_dependency "kajiki", "~> 1.1"
|
16
|
+
s.add_runtime_dependency "sinatra", "~> 1.4"
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: subaru
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ken J.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: kajiki
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.4'
|
41
|
+
description: RESTful API to industrial web relays
|
42
|
+
email:
|
43
|
+
- kenjij@gmail.com
|
44
|
+
executables:
|
45
|
+
- subaru
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- bin/subaru
|
52
|
+
- lib/config_temp.yml
|
53
|
+
- lib/subaru.rb
|
54
|
+
- lib/subaru/d-xytronix.rb
|
55
|
+
- lib/subaru/helper.rb
|
56
|
+
- subaru.gemspec
|
57
|
+
homepage: https://github.com/kenjij/subaru
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.4.3
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: RESTful API server gem to industrial web relays.
|
81
|
+
test_files: []
|