oschii_ruby 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.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +80 -0
- data/lib/oschii/cloud.rb +294 -0
- data/lib/oschii/device.rb +286 -0
- data/lib/oschii/http_device.rb +154 -0
- data/lib/oschii/osc_monitor.rb +57 -0
- data/lib/oschii/serial_device.rb +197 -0
- data/lib/oschii/servlets.rb +44 -0
- data/lib/oschii/session.rb +7 -0
- data/lib/oschii/version.rb +3 -0
- data/lib/oschii.rb +56 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bb0e8b35b66541a40236ea7a4595518d8a9d6727105c2543e74ca5d6fbb8443f
|
4
|
+
data.tar.gz: 1d9c121b90502130c86056f9818dca4b6a81fe9485734e257e9f261891bbe6f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8b35574e7713cdc5bf13d0e9710894818bc48c72a7a86ade3c6b8688a4449548a2106b01adeb52ac99585f7356f195b4c90999029a0b2aca3c1c6f3d170fb0c0
|
7
|
+
data.tar.gz: 57a436a356063c353d9e8f5a5a18014fce3aa2016b689f50b7daef79cf3efcba1a643d6081fb5669a81e0ddcbb8230735575439e3d1db2dba915d8c13dadb106
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
oschii_ruby (0.0.1)
|
5
|
+
activesupport (~> 7.1.2)
|
6
|
+
faye-websocket (~> 0.11.3)
|
7
|
+
osc-ruby (~> 1.1.4)
|
8
|
+
rest-client (~> 2.1.0)
|
9
|
+
rubyserial (~> 0.6.0)
|
10
|
+
webrick (~> 1.8.1)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: http://rubygems.org/
|
14
|
+
specs:
|
15
|
+
activesupport (7.1.2)
|
16
|
+
base64
|
17
|
+
bigdecimal
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
+
connection_pool (>= 2.2.5)
|
20
|
+
drb
|
21
|
+
i18n (>= 1.6, < 2)
|
22
|
+
minitest (>= 5.1)
|
23
|
+
mutex_m
|
24
|
+
tzinfo (~> 2.0)
|
25
|
+
base64 (0.2.0)
|
26
|
+
bigdecimal (3.1.5)
|
27
|
+
byebug (11.1.3)
|
28
|
+
concurrent-ruby (1.2.2)
|
29
|
+
connection_pool (2.4.1)
|
30
|
+
domain_name (0.6.20231109)
|
31
|
+
drb (2.2.0)
|
32
|
+
ruby2_keywords
|
33
|
+
eventmachine (1.2.7)
|
34
|
+
faye-websocket (0.11.3)
|
35
|
+
eventmachine (>= 0.12.0)
|
36
|
+
websocket-driver (>= 0.5.1)
|
37
|
+
ffi (1.16.3)
|
38
|
+
http-accept (1.7.0)
|
39
|
+
http-cookie (1.0.5)
|
40
|
+
domain_name (~> 0.5)
|
41
|
+
i18n (1.14.1)
|
42
|
+
concurrent-ruby (~> 1.0)
|
43
|
+
make_menu (0.0.3)
|
44
|
+
tty-screen (~> 0.8.2)
|
45
|
+
mime-types (3.5.1)
|
46
|
+
mime-types-data (~> 3.2015)
|
47
|
+
mime-types-data (3.2023.1205)
|
48
|
+
minitest (5.20.0)
|
49
|
+
mutex_m (0.2.0)
|
50
|
+
netrc (0.11.0)
|
51
|
+
osc-ruby (1.1.4)
|
52
|
+
rest-client (2.1.0)
|
53
|
+
http-accept (>= 1.7.0, < 2.0)
|
54
|
+
http-cookie (>= 1.0.2, < 2.0)
|
55
|
+
mime-types (>= 1.16, < 4.0)
|
56
|
+
netrc (~> 0.8)
|
57
|
+
ruby2_keywords (0.0.5)
|
58
|
+
rubyserial (0.6.0)
|
59
|
+
ffi (~> 1.9, >= 1.9.3)
|
60
|
+
tty-screen (0.8.2)
|
61
|
+
tzinfo (2.0.6)
|
62
|
+
concurrent-ruby (~> 1.0)
|
63
|
+
webrick (1.8.1)
|
64
|
+
websocket-driver (0.7.6)
|
65
|
+
websocket-extensions (>= 0.1.0)
|
66
|
+
websocket-extensions (0.1.5)
|
67
|
+
|
68
|
+
PLATFORMS
|
69
|
+
arm64-darwin-21
|
70
|
+
|
71
|
+
DEPENDENCIES
|
72
|
+
byebug
|
73
|
+
make_menu
|
74
|
+
oschii_ruby!
|
75
|
+
|
76
|
+
RUBY VERSION
|
77
|
+
ruby 2.7.7p221
|
78
|
+
|
79
|
+
BUNDLED WITH
|
80
|
+
2.3.26
|
data/lib/oschii/cloud.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'osc-ruby'
|
3
|
+
require 'osc-ruby/em_server'
|
4
|
+
require 'socket'
|
5
|
+
require 'webrick'
|
6
|
+
require_relative 'servlets'
|
7
|
+
|
8
|
+
module Oschii
|
9
|
+
class Cloud
|
10
|
+
PING_PORT = 3333
|
11
|
+
PING_ADDR = '/hello_oschii'
|
12
|
+
RESPONSE_PORT = 3333
|
13
|
+
RESPONSE_ADDR = '/i_am_oschii'
|
14
|
+
HTTP_PORT = 8000
|
15
|
+
|
16
|
+
def initialize(silent: false)
|
17
|
+
@osc_server = OSC::EMServer.new(RESPONSE_PORT)
|
18
|
+
@http_server = WEBrick::HTTPServer.new(Port: HTTP_PORT)
|
19
|
+
|
20
|
+
@devices = {}
|
21
|
+
@silent = silent
|
22
|
+
|
23
|
+
start_listening
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :osc_server, :http_server, :devices, :silent
|
27
|
+
|
28
|
+
def find_serial(port = nil)
|
29
|
+
return ::Oschii::SerialDevice.new(port) if port
|
30
|
+
|
31
|
+
puts "\n~~> Scanning Serial Ports...."
|
32
|
+
|
33
|
+
9.times do |i|
|
34
|
+
port = "/dev/ttyUSB#{i}"
|
35
|
+
print "\n~~> #{port}: "
|
36
|
+
begin
|
37
|
+
node = ::Oschii::SerialDevice.new(port)
|
38
|
+
name = "serial_#{node.name.downcase}".to_sym
|
39
|
+
if (existing_node = devices[name.downcase.to_sym])
|
40
|
+
existing_node.refresh
|
41
|
+
else
|
42
|
+
devices[name] = node
|
43
|
+
node.refresh
|
44
|
+
end
|
45
|
+
puts "'#{node.name}'"
|
46
|
+
rescue ::Oschii::DeviceUnavailable => e
|
47
|
+
puts e.message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
print_devices
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_listening
|
57
|
+
osc_server.add_method RESPONSE_ADDR do |message|
|
58
|
+
payload = message.to_a.first
|
59
|
+
begin
|
60
|
+
json = JSON.parse(payload)
|
61
|
+
name = json['name']
|
62
|
+
ip = json['ip']
|
63
|
+
rescue JSON::ParserError
|
64
|
+
name = payload.split(':').last.strip
|
65
|
+
ip = message.ip_address
|
66
|
+
end
|
67
|
+
|
68
|
+
if (device = devices[name.downcase.to_sym])
|
69
|
+
puts "\n==> '#{name}' is back\n" unless silent
|
70
|
+
device.refresh
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
device = ::Oschii::HttpDevice.new(ip)
|
74
|
+
# add_http_handlers device
|
75
|
+
puts "\n==> '#{name}' connected @ #{device.ip_address}\n" unless silent
|
76
|
+
devices[name.downcase.to_sym] = device
|
77
|
+
rescue Errno::ECONNREFUSED => e
|
78
|
+
puts "\n!!! Failed connecting to #{name} on #{ip}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
save_device device.ip
|
82
|
+
end
|
83
|
+
|
84
|
+
Thread.new do
|
85
|
+
puts "~~> Starting OSC Server on port #{RESPONSE_PORT}" unless silent
|
86
|
+
osc_server.run
|
87
|
+
end
|
88
|
+
|
89
|
+
trap 'INT' do http_server.shutdown end
|
90
|
+
|
91
|
+
Thread.new do
|
92
|
+
puts "~~> Starting HTTP Server on port #{HTTP_PORT}" unless silent
|
93
|
+
http_server.start
|
94
|
+
end
|
95
|
+
|
96
|
+
http_server.mount '/api/devices', GetDevices, self
|
97
|
+
http_server.mount '/api/refresh', RefreshCloud, self
|
98
|
+
end
|
99
|
+
|
100
|
+
def wait_for(oschii_name, timeout: 300)
|
101
|
+
print "==> Waiting for '#{oschii_name}'.... "
|
102
|
+
find_devices
|
103
|
+
oschii = nil
|
104
|
+
started = Time.now
|
105
|
+
while oschii.nil? && Time.now - started <= timeout
|
106
|
+
oschii = get oschii_name
|
107
|
+
end
|
108
|
+
if oschii
|
109
|
+
puts 'FOUND :D'
|
110
|
+
else
|
111
|
+
puts 'NOT FOUND :('
|
112
|
+
end
|
113
|
+
oschii
|
114
|
+
end
|
115
|
+
|
116
|
+
def restart
|
117
|
+
devices.each do |_name, oschii|
|
118
|
+
Thread.new { oschii.restart }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
attr_reader :monitor
|
123
|
+
|
124
|
+
def monitor_osc(*address_list, max: 100)
|
125
|
+
@monitor ||= OscMonitor.new self
|
126
|
+
|
127
|
+
address_list = [address_list] unless address_list.is_a?(Array)
|
128
|
+
|
129
|
+
address_list.each do |address|
|
130
|
+
monitor.add address, max: max
|
131
|
+
end
|
132
|
+
|
133
|
+
monitor.run
|
134
|
+
end
|
135
|
+
|
136
|
+
def capture_osc(address, simple: true, &block)
|
137
|
+
address = "/#{address}" if address[0] != '/'
|
138
|
+
|
139
|
+
osc_server.add_method address do |message|
|
140
|
+
value = simple ? message.to_a.first&.to_i : message.to_a
|
141
|
+
if block_given?
|
142
|
+
yield value
|
143
|
+
else
|
144
|
+
puts "--> OSC #{address} [ #{value} ]"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
BASE_IP_FILE = '.base_ip'.freeze
|
150
|
+
|
151
|
+
def base_ip=(value)
|
152
|
+
File.write BASE_IP_FILE, value unless value.nil?
|
153
|
+
@base_ip = value
|
154
|
+
end
|
155
|
+
|
156
|
+
def base_ip
|
157
|
+
return @base_ip if @base_ip
|
158
|
+
|
159
|
+
ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
|
160
|
+
|
161
|
+
addr = ip.ip_address.to_s
|
162
|
+
|
163
|
+
puts "Local IP: #{addr}"
|
164
|
+
|
165
|
+
@base_ip = addr.split('.')[0..-2].join('.')
|
166
|
+
|
167
|
+
# if File.exists? BASE_IP_FILE
|
168
|
+
# @base_ip = File.read(BASE_IP_FILE).strip
|
169
|
+
# else
|
170
|
+
# @base_ip = '192.168.1'
|
171
|
+
# File.write BASE_IP_FILE, @base_ip
|
172
|
+
# end
|
173
|
+
# @base_ip = @base_ip[0..-2] if @base_ip[-1] == '.'
|
174
|
+
# @base_ip
|
175
|
+
end
|
176
|
+
|
177
|
+
def find_devices(echo: false)
|
178
|
+
@devices = {}
|
179
|
+
saved = saved_devices
|
180
|
+
saved.each do |ip|
|
181
|
+
begin
|
182
|
+
print "\r#{ip}" if echo
|
183
|
+
|
184
|
+
client = OSC::Client.new(ip, PING_PORT)
|
185
|
+
client.send(OSC::Message.new(PING_ADDR, 1))
|
186
|
+
rescue => e
|
187
|
+
# puts e.message
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
Thread.new do
|
192
|
+
(1..254).each do |i|
|
193
|
+
begin
|
194
|
+
ip = "#{base_ip}.#{i}"
|
195
|
+
|
196
|
+
next if saved.include? ip
|
197
|
+
|
198
|
+
print "\r#{ip}" if echo
|
199
|
+
|
200
|
+
client = OSC::Client.new(ip, PING_PORT)
|
201
|
+
client.send(OSC::Message.new(PING_ADDR, 1))
|
202
|
+
|
203
|
+
sleep 0.08
|
204
|
+
rescue => e
|
205
|
+
# puts e.message
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
puts if echo
|
211
|
+
end
|
212
|
+
|
213
|
+
def populate(echo: false)
|
214
|
+
find_devices(echo: echo)
|
215
|
+
|
216
|
+
start_waiting = Time.now
|
217
|
+
while Time.now - start_waiting < 3
|
218
|
+
sleep 0.2
|
219
|
+
end
|
220
|
+
|
221
|
+
print_devices unless silent
|
222
|
+
self
|
223
|
+
end
|
224
|
+
|
225
|
+
def print_devices
|
226
|
+
puts
|
227
|
+
puts self.to_s
|
228
|
+
puts
|
229
|
+
puts
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
return '' if devices.empty?
|
234
|
+
|
235
|
+
result = ''
|
236
|
+
|
237
|
+
conn_width = 0
|
238
|
+
name_width = 0
|
239
|
+
devices.each do |name, device|
|
240
|
+
if device.is_a? ::Oschii::SerialDevice
|
241
|
+
conn_width = device.serial_port.size if device.serial_port.size > conn_width
|
242
|
+
elsif device.is_a? ::Oschii::HttpDevice
|
243
|
+
conn_width = device.ip.size if device.ip.size > conn_width
|
244
|
+
end
|
245
|
+
name_width = name.size if name.size > name_width
|
246
|
+
end
|
247
|
+
|
248
|
+
sorted = devices.to_a.sort_by { |name, _node| name }.to_h
|
249
|
+
|
250
|
+
result += "\e[1m#{'Name'.ljust(name_width + 3)}#{'Connection'.ljust(conn_width + 3)}#{'Version'}\e[22m"
|
251
|
+
sorted.each do |_name, device|
|
252
|
+
connection = if device.is_a? ::Oschii::SerialDevice
|
253
|
+
device.serial.ljust(conn_width)
|
254
|
+
elsif device.is_a? ::Oschii::HttpDevice
|
255
|
+
device.ip.ljust(conn_width)
|
256
|
+
else
|
257
|
+
""
|
258
|
+
end
|
259
|
+
result += "\n#{device.name.ljust(name_width)} #{connection} #{device.version}"
|
260
|
+
end
|
261
|
+
result += "\n"
|
262
|
+
result
|
263
|
+
end
|
264
|
+
|
265
|
+
def get(name)
|
266
|
+
devices[name.to_s.downcase.to_sym]
|
267
|
+
end
|
268
|
+
|
269
|
+
def inspect
|
270
|
+
"<#{self.class.name} #{local_ip_address} devices: #{devices.size}>"
|
271
|
+
end
|
272
|
+
|
273
|
+
def local_ip_address
|
274
|
+
ip = Socket.ip_address_list.detect { |intf| intf.ipv4_private? }
|
275
|
+
ip.ip_address
|
276
|
+
end
|
277
|
+
|
278
|
+
def saved_devices
|
279
|
+
@saved_devices ||= if File.exists? '.saved_devices'
|
280
|
+
File.read('.saved_devices').split("\n")
|
281
|
+
else
|
282
|
+
[]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def save_device(ip)
|
287
|
+
return if saved_devices.include? ip
|
288
|
+
|
289
|
+
@saved_devices << ip
|
290
|
+
|
291
|
+
File.write('.saved_devices', @saved_devices.join("\n"))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'rubyserial'
|
3
|
+
require 'json'
|
4
|
+
require 'io/console'
|
5
|
+
require 'faye/websocket'
|
6
|
+
require 'eventmachine'
|
7
|
+
require 'byebug'
|
8
|
+
|
9
|
+
module Oschii
|
10
|
+
DeviceUnavailable = Class.new(StandardError)
|
11
|
+
NoConnection = Class.new(StandardError)
|
12
|
+
|
13
|
+
class Device
|
14
|
+
def initialize
|
15
|
+
@log_lines = []
|
16
|
+
refresh
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :log_lines
|
20
|
+
|
21
|
+
def restart
|
22
|
+
raise NoConnection
|
23
|
+
end
|
24
|
+
|
25
|
+
# Connection
|
26
|
+
|
27
|
+
def refresh
|
28
|
+
@device_details = nil
|
29
|
+
@status = nil
|
30
|
+
@settings = nil
|
31
|
+
@config = nil
|
32
|
+
poke!
|
33
|
+
end
|
34
|
+
|
35
|
+
def ip
|
36
|
+
raise NoConnection
|
37
|
+
end
|
38
|
+
|
39
|
+
def poke
|
40
|
+
raise NoConnection
|
41
|
+
end
|
42
|
+
|
43
|
+
def poke!
|
44
|
+
remaining_attempts = 3
|
45
|
+
while remaining_attempts > 0
|
46
|
+
begin
|
47
|
+
if poke
|
48
|
+
# device_details
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
return false
|
52
|
+
|
53
|
+
rescue RubySerial::Error => e
|
54
|
+
raise DeviceUnavailable, case e.message
|
55
|
+
when 'ENOENT'
|
56
|
+
'(no device)'
|
57
|
+
when 'EBUSY'
|
58
|
+
'(port in use)'
|
59
|
+
else
|
60
|
+
e.message
|
61
|
+
end
|
62
|
+
rescue RestClient::Exception => e
|
63
|
+
raise DeviceUnavailable, e.message
|
64
|
+
end
|
65
|
+
remaining_attempts -= 1
|
66
|
+
end
|
67
|
+
raise DeviceUnavailable, '(no response)'
|
68
|
+
end
|
69
|
+
|
70
|
+
# Device details
|
71
|
+
|
72
|
+
def raw_device_details
|
73
|
+
raise NoConnection
|
74
|
+
end
|
75
|
+
|
76
|
+
def device_details
|
77
|
+
@device_details ||= JSON.parse(raw_device_details)
|
78
|
+
end
|
79
|
+
|
80
|
+
def version
|
81
|
+
device_details['version']
|
82
|
+
end
|
83
|
+
|
84
|
+
def name
|
85
|
+
device_details['name']
|
86
|
+
end
|
87
|
+
|
88
|
+
def name=(new_name)
|
89
|
+
self.settings = ({ name: new_name })
|
90
|
+
refresh
|
91
|
+
end
|
92
|
+
|
93
|
+
def description
|
94
|
+
device_details['description']
|
95
|
+
end
|
96
|
+
|
97
|
+
def config_name
|
98
|
+
device_details['configName']
|
99
|
+
end
|
100
|
+
|
101
|
+
def config_description
|
102
|
+
device_details['configDescription']
|
103
|
+
end
|
104
|
+
|
105
|
+
# Status
|
106
|
+
|
107
|
+
def raw_status
|
108
|
+
raise NoConnection
|
109
|
+
end
|
110
|
+
|
111
|
+
def status
|
112
|
+
@status ||= JSON.parse(raw_status)
|
113
|
+
end
|
114
|
+
|
115
|
+
def uptime
|
116
|
+
status['uptime']
|
117
|
+
end
|
118
|
+
|
119
|
+
# Settings
|
120
|
+
|
121
|
+
def raw_settings
|
122
|
+
raise NoConnection
|
123
|
+
end
|
124
|
+
|
125
|
+
def settings=(new_settings)
|
126
|
+
raise NoConnection
|
127
|
+
end
|
128
|
+
|
129
|
+
def settings
|
130
|
+
@settings ||= JSON.parse(raw_settings)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Configuration
|
134
|
+
|
135
|
+
def raw_config
|
136
|
+
raise NoConnection
|
137
|
+
end
|
138
|
+
|
139
|
+
def raw_config=(json)
|
140
|
+
raise NoConnection
|
141
|
+
end
|
142
|
+
|
143
|
+
def config=(hash)
|
144
|
+
self.raw_config = hash.to_json
|
145
|
+
end
|
146
|
+
|
147
|
+
def config
|
148
|
+
@config ||= Oschii::Config.parse(raw_config)
|
149
|
+
end
|
150
|
+
|
151
|
+
def clear!
|
152
|
+
self.config = {}
|
153
|
+
end
|
154
|
+
|
155
|
+
def update!
|
156
|
+
self.config = config.compacted
|
157
|
+
end
|
158
|
+
|
159
|
+
def upload_config(filename = nil, silent: false)
|
160
|
+
if filename.nil?
|
161
|
+
filenames = Dir.glob("configs/#{name}_*.json")
|
162
|
+
if filenames.empty?
|
163
|
+
puts 'No previous config' unless silent
|
164
|
+
return
|
165
|
+
end
|
166
|
+
filename = filenames.sort.last
|
167
|
+
|
168
|
+
unless silent
|
169
|
+
display_name = filename.split('/')[-1]
|
170
|
+
.split('_')[-1]
|
171
|
+
.split('+')[0]
|
172
|
+
.gsub('T', ' ')
|
173
|
+
.gsub('.json', '')
|
174
|
+
puts "Latest: #{display_name}"
|
175
|
+
begin
|
176
|
+
prompt '>> [ENTER] to upload, [ESC] to cancel <<'
|
177
|
+
rescue CancelSerialQuery
|
178
|
+
return
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
self.config = JSON.parse File.read(filename)
|
184
|
+
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
def save_config(filename = nil, silent: false)
|
189
|
+
filename ||= "configs/#{name}_#{Time.now.iso8601}.json"
|
190
|
+
File.write filename, JSON.pretty_generate(config)
|
191
|
+
puts "Saved #{filename.split('/')[-1]}" unless silent
|
192
|
+
end
|
193
|
+
|
194
|
+
# Logging
|
195
|
+
|
196
|
+
def log
|
197
|
+
raise NoConnection
|
198
|
+
end
|
199
|
+
|
200
|
+
def clear_log
|
201
|
+
raise NoConnection
|
202
|
+
end
|
203
|
+
|
204
|
+
def tail(filter: nil)
|
205
|
+
while true
|
206
|
+
while (line = log_lines.shift)
|
207
|
+
if filter.nil? || line.match?(filter)
|
208
|
+
puts line
|
209
|
+
end
|
210
|
+
end
|
211
|
+
sleep 0.05
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def logger(params = {})
|
216
|
+
current_logger = settings['logger']
|
217
|
+
|
218
|
+
all = params[:all]
|
219
|
+
|
220
|
+
sensors = all.nil? ? params[:sensors] : all
|
221
|
+
drivers = all.nil? ? params[:drivers] : all
|
222
|
+
monitors = all.nil? ? params[:monitors] : all
|
223
|
+
listeners = all.nil? ? params[:listeners] : all
|
224
|
+
states = all.nil? ? params[:states] : all
|
225
|
+
timers = all.nil? ? params[:timers] : all
|
226
|
+
subs = all.nil? ? params[:subs] : all
|
227
|
+
pixels = all.nil? ? params[:pixels] : all
|
228
|
+
network_in = all.nil? ? params[:network_in] : all
|
229
|
+
network_out = all.nil? ? params[:network_out] : all
|
230
|
+
timestamp = params[:timestamp]
|
231
|
+
to_file = params[:to_file]
|
232
|
+
|
233
|
+
current_logger['sensors'] = sensors unless sensors.nil?
|
234
|
+
current_logger['drivers'] = drivers unless drivers.nil?
|
235
|
+
current_logger['monitors'] = monitors unless monitors.nil?
|
236
|
+
current_logger['listeners'] = listeners unless listeners.nil?
|
237
|
+
current_logger['states'] = states unless states.nil?
|
238
|
+
current_logger['timers'] = timers unless timers.nil?
|
239
|
+
current_logger['subs'] = subs unless subs.nil?
|
240
|
+
current_logger['pixels'] = pixels unless pixels.nil?
|
241
|
+
current_logger['networkIn'] = network_in unless network_in.nil?
|
242
|
+
current_logger['networkOut'] = network_out unless network_out.nil?
|
243
|
+
current_logger['timestamp'] = timestamp unless timestamp.nil?
|
244
|
+
current_logger['logToFile'] = to_file unless to_file.nil?
|
245
|
+
update_settings({ 'logger' => current_logger })
|
246
|
+
current_logger = settings['logger']
|
247
|
+
puts JSON.pretty_generate current_logger
|
248
|
+
end
|
249
|
+
|
250
|
+
def inspect
|
251
|
+
"<#{self.class.name}[#{name}] (v#{version})>"
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def prompt(text, obscure: false)
|
257
|
+
print "#{text}: "
|
258
|
+
input = ''
|
259
|
+
char = ''
|
260
|
+
until !char.empty? && char.ord == 13
|
261
|
+
char = STDIN.getch
|
262
|
+
if char.ord == 127
|
263
|
+
# BACKSPACE
|
264
|
+
input = input[0..-2]
|
265
|
+
print "\r#{text}: #{' ' * input.size} "
|
266
|
+
print "\r#{text}: #{obscure ? '*' * input.size : input}"
|
267
|
+
elsif char.ord == 27
|
268
|
+
# ESC
|
269
|
+
raise CancelSerialQuery
|
270
|
+
elsif char.ord == 13
|
271
|
+
# ENTER
|
272
|
+
else
|
273
|
+
input += char
|
274
|
+
if obscure
|
275
|
+
print '*'
|
276
|
+
else
|
277
|
+
print char
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
puts
|
282
|
+
input
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Oschii
|
2
|
+
class HttpDevice < Device
|
3
|
+
def initialize(ip)
|
4
|
+
@ip_address = ip
|
5
|
+
@osc_clients = {}
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :ip_address, :osc_clients, :log_socket
|
10
|
+
|
11
|
+
def restart
|
12
|
+
RestClient.post("http://#{ip}/restart", {})&.body
|
13
|
+
end
|
14
|
+
|
15
|
+
# Connection
|
16
|
+
|
17
|
+
def refresh
|
18
|
+
stop_web_socket
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def ip
|
23
|
+
ip_address
|
24
|
+
end
|
25
|
+
|
26
|
+
def poke
|
27
|
+
device_details
|
28
|
+
begin
|
29
|
+
start_web_socket
|
30
|
+
rescue
|
31
|
+
# ignored
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Raw getters
|
37
|
+
|
38
|
+
def raw_device_details
|
39
|
+
RestClient.get("http://#{ip}/device")&.body || ''
|
40
|
+
end
|
41
|
+
|
42
|
+
def raw_status
|
43
|
+
RestClient.get("http://#{ip}/status")&.body || ''
|
44
|
+
end
|
45
|
+
|
46
|
+
def raw_settings
|
47
|
+
RestClient.get("http://#{ip}/settings.json")&.body || ''
|
48
|
+
end
|
49
|
+
|
50
|
+
def raw_config
|
51
|
+
RestClient.get("http://#{ip}/config.json")&.body || ''
|
52
|
+
end
|
53
|
+
|
54
|
+
# Updaters
|
55
|
+
|
56
|
+
def raw_config=(json)
|
57
|
+
RestClient.post(
|
58
|
+
"http://#{ip}/config",
|
59
|
+
json,
|
60
|
+
content_type: 'application/json'
|
61
|
+
)
|
62
|
+
# refresh
|
63
|
+
end
|
64
|
+
|
65
|
+
def settings=(new_settings)
|
66
|
+
RestClient.post(
|
67
|
+
"http://#{ip}/settings",
|
68
|
+
new_settings.to_json,
|
69
|
+
content_type: 'application/json'
|
70
|
+
)
|
71
|
+
refresh
|
72
|
+
end
|
73
|
+
|
74
|
+
# Logging
|
75
|
+
|
76
|
+
def start_web_socket
|
77
|
+
EM.run {
|
78
|
+
@log_socket = Faye::WebSocket::Client.new("ws://#{ip}/logger_ws")
|
79
|
+
|
80
|
+
log_socket.on :open do |event|
|
81
|
+
log_socket.send('WebSocket connected')
|
82
|
+
end
|
83
|
+
|
84
|
+
log_socket.on :message do |event|
|
85
|
+
event.data.split("\n").each do |line|
|
86
|
+
log_lines << line
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
log_socket.on :close do |event|
|
91
|
+
log_socket.send("WebSocket DISCONNECTED: #{event.code} #{event.reason}")
|
92
|
+
@log_socket = nil
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def stop_web_socket
|
98
|
+
log_socket&.close
|
99
|
+
end
|
100
|
+
|
101
|
+
# Network Commands
|
102
|
+
|
103
|
+
def fire(address, *values)
|
104
|
+
send_osc address, *values
|
105
|
+
end
|
106
|
+
|
107
|
+
def send_osc(address, *values, port: 3333)
|
108
|
+
address = "/#{address}" unless address[0] == '/'
|
109
|
+
osc_client(port).send(OSC::Message.new(address, *values))
|
110
|
+
end
|
111
|
+
|
112
|
+
def osc_client(port)
|
113
|
+
osc_clients[port] ||= OSC::Client.new(ip, port)
|
114
|
+
end
|
115
|
+
|
116
|
+
def get(path, value = 1)
|
117
|
+
send_http path, value, method: :get
|
118
|
+
end
|
119
|
+
|
120
|
+
def post(path, value = 1)
|
121
|
+
send_http path, value, method: :post
|
122
|
+
end
|
123
|
+
|
124
|
+
def send_http(path, value = 1, method: :post)
|
125
|
+
path = "/#{path}" unless %w(/ :).include?(path[0])
|
126
|
+
method = method.to_s.downcase.to_sym
|
127
|
+
url = "http://#{ip}#{path}"
|
128
|
+
if method == :get
|
129
|
+
args = [url]
|
130
|
+
else
|
131
|
+
args = [url, value.to_s]
|
132
|
+
end
|
133
|
+
puts RestClient.send(method, *args)
|
134
|
+
rescue RestClient::BadRequest => e
|
135
|
+
puts "Oschii didn't like that: #{e.http_body}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def log
|
139
|
+
RestClient.get("http://#{ip}/log")&.body
|
140
|
+
end
|
141
|
+
|
142
|
+
def clear_log
|
143
|
+
RestClient.delete("http://#{ip}/log")&.body
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s
|
147
|
+
inspect
|
148
|
+
end
|
149
|
+
|
150
|
+
def inspect
|
151
|
+
"<#{self.class.name}[#{name}] #{ip_address} (v#{version})>"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Oschii
|
2
|
+
class OscMonitor
|
3
|
+
COL_WIDTH = 40
|
4
|
+
|
5
|
+
def initialize(cloud)
|
6
|
+
@cloud = cloud
|
7
|
+
@values = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :cloud, :values
|
11
|
+
|
12
|
+
def add(name, max: 100)
|
13
|
+
unless values[name.to_sym]
|
14
|
+
values[name.to_sym] = {value: nil, max: max}
|
15
|
+
cloud.capture_osc name do |value|
|
16
|
+
values[name.to_sym][:value] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
values[name.to_sym][:max] = max
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
puts
|
25
|
+
values.each do |k, v|
|
26
|
+
print " #{k}".ljust (COL_WIDTH+4)
|
27
|
+
end
|
28
|
+
puts
|
29
|
+
while true
|
30
|
+
render
|
31
|
+
sleep 0.01
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def render
|
36
|
+
print "\r"
|
37
|
+
values.each do |k, v|
|
38
|
+
print sensor_bar(v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def sensor_bar(value)
|
43
|
+
result = '['
|
44
|
+
norm = [value[:value].to_f / value[:max], 1.0].min * COL_WIDTH
|
45
|
+
COL_WIDTH.times do |i|
|
46
|
+
if i < norm
|
47
|
+
result += '■'
|
48
|
+
else
|
49
|
+
result += ':'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result += '] '
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Oschii
|
2
|
+
class SerialDevice < Device
|
3
|
+
BAUD_RATE = 115200
|
4
|
+
|
5
|
+
CancelSerialQuery = Class.new(StandardError)
|
6
|
+
|
7
|
+
def initialize(serial)
|
8
|
+
@serial = serial
|
9
|
+
@capturing_serial = false
|
10
|
+
super()
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :serial
|
14
|
+
attr_accessor :capturing_serial, :serial_line_buffer
|
15
|
+
|
16
|
+
def poke
|
17
|
+
start_serial_capture
|
18
|
+
serial_query('poke', timeout: 2) == 'Tickles!'
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
return @version if @version
|
23
|
+
|
24
|
+
attempts = 3
|
25
|
+
while attempts > 0
|
26
|
+
@version = serial_query 'version'
|
27
|
+
if @version.empty?
|
28
|
+
attempts -= 1
|
29
|
+
else
|
30
|
+
attempts = 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ip
|
36
|
+
serial_query 'ip'
|
37
|
+
end
|
38
|
+
|
39
|
+
def restart
|
40
|
+
serial_query 'restart'
|
41
|
+
end
|
42
|
+
|
43
|
+
def name
|
44
|
+
return @name if @name
|
45
|
+
|
46
|
+
attempts = 3
|
47
|
+
while attempts > 0
|
48
|
+
@name = serial_query 'name'
|
49
|
+
if @name.empty?
|
50
|
+
attempts -= 1
|
51
|
+
else
|
52
|
+
attempts = 0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def name=(new_name)
|
58
|
+
puts 'Querying serial port....'
|
59
|
+
if serial_query('name=') == '>> Enter new name <<'
|
60
|
+
puts serial_query new_name
|
61
|
+
end
|
62
|
+
refresh
|
63
|
+
end
|
64
|
+
|
65
|
+
def config=(new_config)
|
66
|
+
puts 'Querying serial port....'
|
67
|
+
if serial_query('config=') == '>> Enter new configuration <<'
|
68
|
+
puts 'Uploading configuration....'
|
69
|
+
puts serial_query new_config.to_json
|
70
|
+
end
|
71
|
+
refresh
|
72
|
+
end
|
73
|
+
|
74
|
+
def raw_config
|
75
|
+
serial_query 'config', timeout: 3
|
76
|
+
end
|
77
|
+
|
78
|
+
def status
|
79
|
+
JSON.parse(serial_query('status'))
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_settings(new_settings = nil, file: nil)
|
83
|
+
unless file.nil?
|
84
|
+
puts 'Reading file....'
|
85
|
+
new_settings = JSON.parse(File.read(file))
|
86
|
+
end
|
87
|
+
puts 'Querying serial port....'
|
88
|
+
if serial_query('settings=') == '>> Enter new settings <<'
|
89
|
+
puts 'Uploading settings....'
|
90
|
+
puts serial_query new_settings.to_json
|
91
|
+
refresh
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def raw_settings
|
96
|
+
serial_query 'settings'
|
97
|
+
end
|
98
|
+
|
99
|
+
def serial_query(query, timeout: 3)
|
100
|
+
# puts "#> #{query}"
|
101
|
+
purge_serial
|
102
|
+
serial_port.write (query.empty? ? "\n" : query)
|
103
|
+
sleep timeout
|
104
|
+
result = log_lines.join
|
105
|
+
log_lines.clear
|
106
|
+
# puts " #{result}"
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
def purge_serial
|
111
|
+
log_lines.clear
|
112
|
+
self.serial_line_buffer = ''
|
113
|
+
end
|
114
|
+
|
115
|
+
def start_serial_capture
|
116
|
+
return if capturing_serial
|
117
|
+
|
118
|
+
Thread.new do
|
119
|
+
self.capturing_serial = true
|
120
|
+
self.serial_line_buffer = ''
|
121
|
+
log_lines.clear
|
122
|
+
while capturing_serial
|
123
|
+
input = serial_port.read 100
|
124
|
+
input.each_char do |c|
|
125
|
+
if c == "\n"
|
126
|
+
log_lines << serial_line_buffer.gsub("\r", '')
|
127
|
+
self.serial_line_buffer = ''
|
128
|
+
else
|
129
|
+
serial_line_buffer << c
|
130
|
+
end
|
131
|
+
end
|
132
|
+
sleep 0.01
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def stop_serial_capture
|
138
|
+
self.capturing_serial = false
|
139
|
+
end
|
140
|
+
|
141
|
+
def start_wifi(ssid = nil, password = nil)
|
142
|
+
begin
|
143
|
+
if ssid.nil?
|
144
|
+
ssid = prompt 'Enter SSID'
|
145
|
+
end
|
146
|
+
|
147
|
+
if password.nil?
|
148
|
+
password = prompt 'Enter password', obscure: true
|
149
|
+
end
|
150
|
+
|
151
|
+
puts 'Querying serial port...'
|
152
|
+
if serial_query('start wifi') == '>> Enter new Wifi SSID <<'
|
153
|
+
puts 'Sending SSID...'
|
154
|
+
if serial_query(ssid) == '>> Enter new Wifi Password <<'
|
155
|
+
puts 'Sending password...'
|
156
|
+
puts serial_query(password)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
rescue CancelSerialQuery
|
161
|
+
puts 'Cancelled'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def stop_wifi
|
166
|
+
serial_query 'stop wifi'
|
167
|
+
end
|
168
|
+
|
169
|
+
def start_ethernet
|
170
|
+
serial_query 'start ethernet'
|
171
|
+
end
|
172
|
+
|
173
|
+
def stop_ethernet
|
174
|
+
serial_query 'stop ethernet'
|
175
|
+
end
|
176
|
+
|
177
|
+
def serial_port
|
178
|
+
@serial_port ||= Serial.new serial, BAUD_RATE
|
179
|
+
end
|
180
|
+
|
181
|
+
def log
|
182
|
+
serial_query 'print log'
|
183
|
+
end
|
184
|
+
|
185
|
+
def clear_log
|
186
|
+
serial_query 'clear log'
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_s
|
190
|
+
inspect
|
191
|
+
end
|
192
|
+
|
193
|
+
def inspect
|
194
|
+
"<#{self.class.name}[#{name}] #{serial} (v#{version})>"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Oschii
|
2
|
+
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
3
|
+
def initialize(server, cloud)
|
4
|
+
super
|
5
|
+
@cloud = cloud
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :cloud
|
9
|
+
|
10
|
+
def add_headers(res)
|
11
|
+
res.header['Access-Control-Allow-Origin'] = '*'
|
12
|
+
res.header['Access-Control-Allow-Methods'] = 'GET'
|
13
|
+
res.header['Vary'] = 'Access-Control-Request-Headers'
|
14
|
+
res.header['Access-Control-Allow-Headers'] = 'Content-Type, Accept'
|
15
|
+
res.header['Access-Control-Request-Method'] = '*'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class GetDevices < Servlet
|
20
|
+
def do_GET(req, res)
|
21
|
+
res.status = 200
|
22
|
+
res.content_type = 'application/json'
|
23
|
+
res.body = cloud.devices.sort_by { |k, _v| k }.map do |name, device|
|
24
|
+
{
|
25
|
+
name: device.name,
|
26
|
+
ip: device.ip,
|
27
|
+
details: device.device_details
|
28
|
+
}
|
29
|
+
end.to_json
|
30
|
+
add_headers res
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class RefreshCloud < Servlet
|
35
|
+
def do_POST(req, res)
|
36
|
+
Thread.new do
|
37
|
+
cloud.populate
|
38
|
+
end
|
39
|
+
res.status = 200
|
40
|
+
res.content_type = 'text/plain'
|
41
|
+
res.body = 'Refreshing Cloud'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/oschii.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'oschii/version'
|
2
|
+
|
3
|
+
require_relative 'oschii/device'
|
4
|
+
require_relative 'oschii/http_device'
|
5
|
+
require_relative 'oschii/serial_device'
|
6
|
+
require_relative 'oschii/cloud'
|
7
|
+
require_relative 'oschii/osc_monitor'
|
8
|
+
|
9
|
+
module Oschii
|
10
|
+
LOGO = "
|
11
|
+
╔═╗┌─┐┌─┐┬ ┬┬┬
|
12
|
+
║ ║└─┐│ ├─┤││
|
13
|
+
╚═╝└─┘└─┘┴ ┴┴┴
|
14
|
+
-- R U B Y ---
|
15
|
+
#{VERSION.center(14)}
|
16
|
+
|
17
|
+
".freeze
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
puts LOGO
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def cloud(silent: false)
|
26
|
+
@cloud ||= Cloud.new(silent: silent)
|
27
|
+
end
|
28
|
+
|
29
|
+
def populate
|
30
|
+
cloud.populate
|
31
|
+
end
|
32
|
+
|
33
|
+
def serve_forever
|
34
|
+
populate
|
35
|
+
puts 'Press ENTER to quit....'
|
36
|
+
gets
|
37
|
+
end
|
38
|
+
|
39
|
+
def serial
|
40
|
+
@serial ||= cloud.find_serial
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(m, *args, &block)
|
44
|
+
return if @cloud.nil?
|
45
|
+
|
46
|
+
node = cloud.get(m)
|
47
|
+
|
48
|
+
return node unless node.nil?
|
49
|
+
|
50
|
+
raise NameError
|
51
|
+
end
|
52
|
+
|
53
|
+
def send_osc(ip, address, *values, port: 3333)
|
54
|
+
OSC::Client.new(ip, port).send(OSC::Message.new(address, *values.to_a))
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oschii_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Barri Mason
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faye-websocket
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.11.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.11.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyserial
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: osc-ruby
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webrick
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.8.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 7.1.2
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 7.1.2
|
97
|
+
description: Interfaces with a network of Oschiis
|
98
|
+
email: loki@amarantha.net
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- Gemfile
|
104
|
+
- Gemfile.lock
|
105
|
+
- lib/oschii.rb
|
106
|
+
- lib/oschii/cloud.rb
|
107
|
+
- lib/oschii/device.rb
|
108
|
+
- lib/oschii/http_device.rb
|
109
|
+
- lib/oschii/osc_monitor.rb
|
110
|
+
- lib/oschii/serial_device.rb
|
111
|
+
- lib/oschii/servlets.rb
|
112
|
+
- lib/oschii/session.rb
|
113
|
+
- lib/oschii/version.rb
|
114
|
+
homepage: https://rubygems.org/gems/oschii_ruby
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 2.7.7
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubygems_version: 3.3.26
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: Gem for managing Oschiis
|
137
|
+
test_files: []
|