tinderfridge 0.14.0 → 0.16.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 +4 -4
- data/lib/tinderfridge/device/configuration.rb +16 -0
- data/lib/tinderfridge/device.rb +13 -16
- data/lib/tinderfridge/device_collection.rb +6 -2
- data/lib/tinderfridge/device_info.txt +1 -0
- data/lib/tinderfridge/devices/brick_master/brick_master.rb +49 -0
- data/lib/tinderfridge/devices/bricklet_co2_v2/bricklet_co2_v2.rb +8 -0
- data/lib/tinderfridge/devices/bricklet_industrial_dual_ac_in/bricklet_industrial_dual_ac_in.json +11 -0
- data/lib/tinderfridge/devices/bricklet_particulate_matter/bricklet_particulate_matter.rb +38 -0
- data/lib/tinderfridge/devices/bricklet_segment_display_4x7_v2/bricklet_segment_display_4x7_v2.rb +29 -17
- data/lib/tinderfridge/ip_connection.rb +21 -2
- data/lib/tinderfridge/shared/logger.rb +34 -19
- data/lib/tinderfridge/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8269237b83fcfd5a052cdcc9cb9f702bbca2f8e093a918dca330418679cf0496
|
4
|
+
data.tar.gz: ff1e59646d0b04205e1f5151d6710c2f0834e2822dab3e842fd627c5a1878140
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb2db92d210948ce317538ef9826ec34e2171061f91f01ea4aa6964edd5379f8274e624cdfa5078618d7ebff4029f8c776c404324587c3344fb08ce9d58d9449
|
7
|
+
data.tar.gz: 2944c853d896630f99b2d305a8b76782fb67158199bfa108ca3ff7432e2c5381710bfdecca993d398bd60b26a0bf1eaefb81d8ef7afc1ba49c8574d255d37915
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tinkerforge
|
2
|
+
class Device
|
3
|
+
|
4
|
+
# Returns configuration data for the device (a mutable Hash).
|
5
|
+
def config
|
6
|
+
@config ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
# Sets configuration data (a Hash) for the device.
|
10
|
+
def config=(configuration)
|
11
|
+
raise(ArgumentError, 'Invalid configuration') unless configuration.class == Hash
|
12
|
+
@config = configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/tinderfridge/device.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'tinkerforge/ip_connection'
|
2
2
|
require 'tinderfridge/shared/logger'
|
3
|
+
require 'tinderfridge/device/configuration'
|
3
4
|
|
4
5
|
module Tinkerforge
|
5
6
|
|
@@ -76,7 +77,7 @@ module Tinkerforge
|
|
76
77
|
def initialize(uid, ipcon, device_identifier, device_display_name)
|
77
78
|
original_initialize(uid, ipcon, device_identifier, device_display_name)
|
78
79
|
if respond_to? 'get_identity'
|
79
|
-
logger_debug "Created
|
80
|
+
logger_debug "Created #{self.class}"
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
@@ -97,10 +98,17 @@ module Tinkerforge
|
|
97
98
|
|
98
99
|
# Returns the device's properties.
|
99
100
|
def properties
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
|
102
|
+
# BrickDaemon inherits from Device, but has no #get_identity.
|
103
|
+
return {} unless respond_to? 'get_identity'
|
104
|
+
|
105
|
+
identity = get_identity
|
106
|
+
|
107
|
+
@properties ||= [
|
108
|
+
[ 'device_identifier' , device_identifier ],
|
109
|
+
[ 'device_display_name', device_display_name ],
|
110
|
+
[ 'hardware_version' , identity[3].join('.') ],
|
111
|
+
].compact.to_h.merge load_properties
|
104
112
|
end
|
105
113
|
|
106
114
|
alias props properties
|
@@ -134,17 +142,6 @@ module Tinkerforge
|
|
134
142
|
].compact.to_h
|
135
143
|
end
|
136
144
|
|
137
|
-
# Returns configuration data for the device (a mutable Hash).
|
138
|
-
def config
|
139
|
-
@config ||= {}
|
140
|
-
end
|
141
|
-
|
142
|
-
# Sets configuration data (a Hash) for the device.
|
143
|
-
def config=(configuration)
|
144
|
-
raise(ArgumentError, 'Invalid configuration') unless configuration.class == Hash
|
145
|
-
@config = configuration
|
146
|
-
end
|
147
|
-
|
148
145
|
# Opens the online documentation for the device (Mac OS only).
|
149
146
|
#
|
150
147
|
# When the URL for the documentation is not known, does nothing.
|
@@ -2,6 +2,8 @@ module Tinkerforge
|
|
2
2
|
|
3
3
|
class DeviceCollection < Hash
|
4
4
|
|
5
|
+
alias devices values
|
6
|
+
|
5
7
|
# Returns the temperatures as measured inside the microcontrollers of devices in the collection.
|
6
8
|
#
|
7
9
|
# Nil for devices that do not support the get_chip_temperature method.
|
@@ -125,9 +127,11 @@ module Tinkerforge
|
|
125
127
|
smap('ipcon').values.compact.uniq
|
126
128
|
end
|
127
129
|
|
128
|
-
# Disconnects IP Connections used by devices in the collection.
|
130
|
+
# Disconnects IP Connections used by devices in the collection, and removes all devices from the collection.
|
129
131
|
def disconnect
|
130
|
-
ipcons.map { |i| [i, (i.get_connection_state == 0 ? nil : i.disconnect) ] }.to_h
|
132
|
+
result = ipcons.map { |i| [i, (i.get_connection_state == 0 ? nil : i.disconnect) ] }.to_h
|
133
|
+
clear
|
134
|
+
result
|
131
135
|
end
|
132
136
|
|
133
137
|
# Returns an array of devices in the collection matching the selector.
|
@@ -145,3 +145,4 @@
|
|
145
145
|
2165 DC Bricklet 2.0 Tinkerforge::BrickletDCV2 bricklet_dc_v2
|
146
146
|
2166 Silent Stepper Bricklet 2.0 Tinkerforge::BrickletSilentStepperV2 bricklet_silent_stepper_v2
|
147
147
|
2171 GPS Bricklet 3.0 Tinkerforge::BrickletGPSV3 bricklet_gps_v3
|
148
|
+
2174 Industrial Dual AC In Bricklet Tinkerforge::BrickletIndustrialDualACIn bricklet_industrial_dual_ac_in
|
@@ -6,6 +6,55 @@ module Tinkerforge
|
|
6
6
|
CHIP_TEMPERATURE_UNIT = 0.1
|
7
7
|
# REVIEW: This should ideally be part of base Tinkerforge.
|
8
8
|
|
9
|
+
# Returns the device's state.
|
10
|
+
def state
|
11
|
+
super.merge [
|
12
|
+
safe_send_state('connection_type' , 'get_connection_type' ), # FW 2.4.0
|
13
|
+
safe_send_state('status_led_enabled', 'is_status_led_enabled'), # FW 2.3.2
|
14
|
+
safe_send_state('chibi_present' , 'is_chibi_present' ),
|
15
|
+
safe_send_state('rs485_present' , 'is_rs485_present' ),
|
16
|
+
safe_send_state('ethernet_present' , 'is_ethernet_present' ), # FW 2.1.0
|
17
|
+
safe_send_state('wifi_present' , 'is_wifi_present' ),
|
18
|
+
safe_send_state('wifi2_present' , 'is_wifi2_present' ), # FW 2.4.0
|
19
|
+
].compact.to_h
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the state of the WIFI Extension 2.0.
|
23
|
+
def wifi2_state
|
24
|
+
if is_wifi2_present
|
25
|
+
[
|
26
|
+
safe_send_state('mesh_configuration' , 'get_wifi2_mesh_configuration' ), # FW 2.4.2 / 2.1.0
|
27
|
+
safe_send_state('mesh_router_ssid' , 'get_wifi2_mesh_router_ssid' ), # FW 2.4.2 / 2.1.0
|
28
|
+
safe_send_state('mesh_common_status' , 'get_wifi2_mesh_common_status' ), # FW 2.4.2 / 2.1.0
|
29
|
+
safe_send_state('mesh_client_status' , 'get_wifi2_mesh_client_status' ), # FW 2.4.2 / 2.1.0
|
30
|
+
safe_send_state('mesh_ap_status' , 'get_wifi2_mesh_ap_status' ), # FW 2.4.2 / 2.1.0
|
31
|
+
safe_send_state('configuration' , 'get_wifi2_configuration' ), # FW 2.4.0
|
32
|
+
safe_send_state('status' , 'get_wifi2_status' ), # FW 2.4.0
|
33
|
+
safe_send_state('client_configuration', 'get_wifi2_client_configuration'), # FW 2.4.0
|
34
|
+
safe_send_state('client_hostname' , 'get_wifi2_client_hostname' ), # FW 2.4.0
|
35
|
+
safe_send_state('ap_configuration' , 'get_wifi2_ap_configuration' ), # FW 2.4.0
|
36
|
+
|
37
|
+
if r = safe_send_state('firmware_version', 'get_wifi2_firmware_version' ) # FW 2.4.0
|
38
|
+
[ r[0], r[1].join('.') ]
|
39
|
+
end
|
40
|
+
].compact.to_h
|
41
|
+
end
|
42
|
+
rescue
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Safely call a method for inclusion in state hash.
|
49
|
+
# When method blows up, state can ignore it.
|
50
|
+
# REVIEW: possible candidate for Device class
|
51
|
+
def safe_send_state(key, methot)
|
52
|
+
[key, send(methot)]
|
53
|
+
rescue => e
|
54
|
+
logger_warn "Device '#{uid_string}': '#{methot}' failed. #{e}"
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
9
58
|
end
|
10
59
|
|
11
60
|
end
|
@@ -16,6 +16,14 @@ module Tinkerforge
|
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
+
def _print_4
|
20
|
+
if (co2 = get_co2_concentration) > 9999
|
21
|
+
' ol '
|
22
|
+
else
|
23
|
+
'%4d' % co2
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
def _view_21x8
|
20
28
|
"CO2V2 #{uid_string.rjust 8}\n\n\n" +
|
21
29
|
('%d PPM' % get_co2_concentration).center(21)
|
data/lib/tinderfridge/devices/bricklet_industrial_dual_ac_in/bricklet_industrial_dual_ac_in.json
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"dimensions": [
|
3
|
+
40,
|
4
|
+
40,
|
5
|
+
16
|
6
|
+
],
|
7
|
+
"weight": 29,
|
8
|
+
"documentation_en_url": "https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Industrial_Dual_AC_In.html",
|
9
|
+
"versions_identifier": "bricklets:industrial_dual_ac_in",
|
10
|
+
"released": "2023-05-12"
|
11
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Tinkerforge
|
2
|
+
|
3
|
+
class BrickletParticulateMatter
|
4
|
+
|
5
|
+
# Enables the fan and the laser diode.
|
6
|
+
def enable
|
7
|
+
set_enable true
|
8
|
+
end
|
9
|
+
|
10
|
+
# Disables the fan and the laser diode.
|
11
|
+
def disable
|
12
|
+
set_enable false
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the device's state.
|
16
|
+
def state
|
17
|
+
super.merge(
|
18
|
+
'enabled' => get_enable,
|
19
|
+
'pm_concentration' => get_pm_concentration,
|
20
|
+
'pm_count' => get_pm_count,
|
21
|
+
'sensor_info' => get_sensor_info,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def _view_21x8
|
28
|
+
"PM #{uid_string.rjust 8}\n\n" +
|
29
|
+
if get_enable
|
30
|
+
" PM1 %4d\n PM2.5 %4d\n PM10 %4d" % get_pm_concentration
|
31
|
+
else
|
32
|
+
"\n disabled"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/tinderfridge/devices/bricklet_segment_display_4x7_v2/bricklet_segment_display_4x7_v2.rb
CHANGED
@@ -35,30 +35,39 @@ module Tinkerforge
|
|
35
35
|
self.segments = segments.gsub(/[ _]/, '').ljust(35, '0').chars.map { |s| s == '1' }
|
36
36
|
end
|
37
37
|
|
38
|
+
# Turns all 35 segments on.
|
39
|
+
def all_on
|
40
|
+
self.segments = [true]*35
|
41
|
+
end
|
42
|
+
|
38
43
|
# Returns the device's state.
|
39
44
|
def state
|
40
45
|
super.merge( 'brightness' => get_brightness )
|
41
46
|
end
|
42
47
|
|
43
48
|
# Displays a string.
|
44
|
-
def print(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def print(text_or_object='')
|
50
|
+
if text_or_object.respond_to?( :_print_4, true)
|
51
|
+
print ( text_or_object.send(:_print_4) rescue '' )
|
52
|
+
else
|
53
|
+
out = ''
|
54
|
+
colon = false
|
55
|
+
|
56
|
+
text_or_object.to_s.chars.each do |c|
|
57
|
+
if glyphs.key? c
|
58
|
+
out << glyphs[c] << '0'
|
59
|
+
elsif c == '.' and out[-1] == '0'
|
60
|
+
out[-1] = '1'
|
61
|
+
elsif c == ':' and out.size == 16
|
62
|
+
colon = true
|
63
|
+
else
|
64
|
+
raise "Can not display character '#{c}'"
|
65
|
+
end
|
57
66
|
end
|
58
|
-
end
|
59
67
|
|
60
|
-
|
61
|
-
|
68
|
+
self.segments_string = out[0,32].ljust(32,'0') + ( colon ? '110' : '000' )
|
69
|
+
nil
|
70
|
+
end
|
62
71
|
end
|
63
72
|
|
64
73
|
# Returns the definition of glyphs for Unicode chracters.
|
@@ -78,6 +87,7 @@ module Tinkerforge
|
|
78
87
|
|
79
88
|
'A' => '1110111',
|
80
89
|
'b' => '0011111',
|
90
|
+
'B' => '1111111',
|
81
91
|
'c' => '0001101',
|
82
92
|
'C' => '1001110',
|
83
93
|
'd' => '0111101',
|
@@ -91,6 +101,7 @@ module Tinkerforge
|
|
91
101
|
'J' => '0111000',
|
92
102
|
'L' => '0001110',
|
93
103
|
'l' => '0001100',
|
104
|
+
'N' => '1110110',
|
94
105
|
'n' => '0010101',
|
95
106
|
'O' => '1111110',
|
96
107
|
'o' => '0011101',
|
@@ -103,7 +114,8 @@ module Tinkerforge
|
|
103
114
|
'u' => '0011100',
|
104
115
|
'V' => '0111110',
|
105
116
|
'v' => '0011100',
|
106
|
-
'
|
117
|
+
'Y' => '0111011',
|
118
|
+
'y' => '0111011',
|
107
119
|
|
108
120
|
'ö' => '1011101',
|
109
121
|
'ü' => '1011100',
|
@@ -16,6 +16,25 @@ module Tinkerforge
|
|
16
16
|
# Returns the network socket used by the IP Connection.
|
17
17
|
attr_reader :socket
|
18
18
|
|
19
|
+
alias original_connect connect
|
20
|
+
|
21
|
+
# Creates a TCP/IP connection to the given host and port. Logs events if event logging is enabled.
|
22
|
+
def connect(host, port)
|
23
|
+
logger_debug "Connecting to %s:%s" % [host, port]
|
24
|
+
ts = Time.now.to_f
|
25
|
+
original_connect(host, port)
|
26
|
+
logger_debug "Connected to %s:%s (%5.3fs)" % [host, port, Time.now.to_f - ts]
|
27
|
+
end
|
28
|
+
|
29
|
+
alias original_disconnect disconnect
|
30
|
+
|
31
|
+
# Disconnects the TCP/IP connection. Logs events if event logging is enabled.
|
32
|
+
def disconnect
|
33
|
+
logger_debug "Disconnecting from #{host}:#{port}"
|
34
|
+
original_disconnect
|
35
|
+
logger_debug "Disconnected from #{host}:#{port}"
|
36
|
+
end
|
37
|
+
|
19
38
|
# Returns a programmer-friendly representation of the object.
|
20
39
|
def inspect
|
21
40
|
"#{self.class} (%s:%s)" % (host ? [host, port] : ['-', '-'] )
|
@@ -130,14 +149,14 @@ module Tinkerforge
|
|
130
149
|
require "tinkerforge/#{dev_info[2][1]}"
|
131
150
|
Tinkerforge.const_get(dev_info[2][0]).new enum_data[0], self
|
132
151
|
else
|
133
|
-
logger_warn "
|
152
|
+
logger_warn "[ #{enum_data[0]} ] Unknown Device Identifier: #{enum_data[5]}"
|
134
153
|
nil
|
135
154
|
end
|
136
155
|
end
|
137
156
|
|
138
157
|
def logger_log_enum(enum_data)
|
139
158
|
logger_debug(
|
140
|
-
"
|
159
|
+
"[ #{enum_data[0]} ] Device " +
|
141
160
|
['available', 'connected', 'disconnected'][enum_data[6]] +
|
142
161
|
( enum_data[6] == 2 ? '' : " (Device Identifier: #{enum_data[5]})" )
|
143
162
|
)
|
@@ -14,33 +14,48 @@ module Tinkerforge
|
|
14
14
|
Tinkerforge.logger
|
15
15
|
end
|
16
16
|
|
17
|
-
def logger_debug(
|
18
|
-
|
19
|
-
logger.debug(msg)
|
20
|
-
end
|
17
|
+
def logger_debug(message='', &block)
|
18
|
+
logger_log(0, message, &block)
|
21
19
|
end
|
22
20
|
|
23
|
-
def logger_info(
|
24
|
-
|
25
|
-
logger.info(msg)
|
26
|
-
end
|
21
|
+
def logger_info(message='', &block)
|
22
|
+
logger_log(1, message, &block)
|
27
23
|
end
|
28
24
|
|
29
|
-
def logger_warn(
|
30
|
-
|
31
|
-
logger.warn(msg)
|
32
|
-
end
|
25
|
+
def logger_warn(message='', &block)
|
26
|
+
logger_log(2, message, &block)
|
33
27
|
end
|
34
28
|
|
35
|
-
def logger_error(
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
def logger_error(message='', &block)
|
30
|
+
logger_log(3, message, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger_fatal(message='', &block)
|
34
|
+
logger_log(4, message, &block)
|
39
35
|
end
|
40
36
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
37
|
+
def logger_log(level, message='', &block)
|
38
|
+
return unless logger
|
39
|
+
|
40
|
+
if message.empty? and block_given?
|
41
|
+
message = yield
|
42
|
+
end
|
43
|
+
|
44
|
+
if respond_to? 'uid_string'
|
45
|
+
message = "[ #{uid_string} ] #{message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
case level
|
49
|
+
when 0
|
50
|
+
logger.debug(message)
|
51
|
+
when 1
|
52
|
+
logger.info(message)
|
53
|
+
when 2
|
54
|
+
logger.warn(message)
|
55
|
+
when 3
|
56
|
+
logger.error(message)
|
57
|
+
when 4
|
58
|
+
logger.fatal(message)
|
44
59
|
end
|
45
60
|
end
|
46
61
|
|
data/lib/tinderfridge/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tinderfridge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lllist.eu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tinkerforge
|
@@ -38,6 +38,7 @@ extra_rdoc_files: []
|
|
38
38
|
files:
|
39
39
|
- lib/tinderfridge.rb
|
40
40
|
- lib/tinderfridge/device.rb
|
41
|
+
- lib/tinderfridge/device/configuration.rb
|
41
42
|
- lib/tinderfridge/device_collection.rb
|
42
43
|
- lib/tinderfridge/device_info.rb
|
43
44
|
- lib/tinderfridge/device_info.txt
|
@@ -87,6 +88,7 @@ files:
|
|
87
88
|
- lib/tinderfridge/devices/bricklet_industrial_digital_in_4_v2/bricklet_industrial_digital_in_4_v2.json
|
88
89
|
- lib/tinderfridge/devices/bricklet_industrial_digital_out_4_v2/bricklet_industrial_digital_out_4_v2.json
|
89
90
|
- lib/tinderfridge/devices/bricklet_industrial_dual_0_20ma_v2/bricklet_industrial_dual_0_20ma_v2.json
|
91
|
+
- lib/tinderfridge/devices/bricklet_industrial_dual_ac_in/bricklet_industrial_dual_ac_in.json
|
90
92
|
- lib/tinderfridge/devices/bricklet_industrial_dual_ac_relay/bricklet_industrial_dual_ac_relay.json
|
91
93
|
- lib/tinderfridge/devices/bricklet_industrial_dual_analog_in_v2/bricklet_industrial_dual_analog_in_v2.json
|
92
94
|
- lib/tinderfridge/devices/bricklet_industrial_dual_relay/bricklet_industrial_dual_relay.json
|
@@ -120,6 +122,7 @@ files:
|
|
120
122
|
- lib/tinderfridge/devices/bricklet_outdoor_weather/bricklet_outdoor_weather.json
|
121
123
|
- lib/tinderfridge/devices/bricklet_outdoor_weather/bricklet_outdoor_weather.rb
|
122
124
|
- lib/tinderfridge/devices/bricklet_particulate_matter/bricklet_particulate_matter.json
|
125
|
+
- lib/tinderfridge/devices/bricklet_particulate_matter/bricklet_particulate_matter.rb
|
123
126
|
- lib/tinderfridge/devices/bricklet_performance_dc/bricklet_performance_dc.json
|
124
127
|
- lib/tinderfridge/devices/bricklet_piezo_speaker_v2/bricklet_piezo_speaker_v2.json
|
125
128
|
- lib/tinderfridge/devices/bricklet_piezo_speaker_v2/bricklet_piezo_speaker_v2.rb
|