argus 0.0.0 → 0.3.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +25 -1
  4. data/Rakefile +4 -0
  5. data/lib/argus.rb +5 -0
  6. data/lib/argus/ardrone_control_modes.rb +14 -0
  7. data/lib/argus/at_commander.rb +104 -0
  8. data/lib/argus/cad_type.rb +22 -0
  9. data/lib/argus/cfields.rb +80 -0
  10. data/lib/argus/controller.rb +165 -0
  11. data/lib/argus/drone.rb +62 -0
  12. data/lib/argus/float_encoding.rb +13 -0
  13. data/lib/argus/led_animation.rb +48 -0
  14. data/lib/argus/nav_data.rb +78 -0
  15. data/lib/argus/nav_monitor.rb +83 -0
  16. data/lib/argus/nav_option.rb +35 -0
  17. data/lib/argus/nav_option_checksum.rb +20 -0
  18. data/lib/argus/nav_option_demo.rb +79 -0
  19. data/lib/argus/nav_option_unknown.rb +9 -0
  20. data/lib/argus/nav_option_vision_detect.rb +83 -0
  21. data/lib/argus/nav_streamer.rb +111 -0
  22. data/lib/argus/nav_tag.rb +35 -0
  23. data/lib/argus/null_nav_monitor.rb +13 -0
  24. data/lib/argus/pave_parser.rb +39 -0
  25. data/lib/argus/tcp_video_streamer.rb +20 -0
  26. data/lib/argus/time_queue.rb +62 -0
  27. data/lib/argus/udp_sender.rb +13 -0
  28. data/lib/argus/version.rb +10 -0
  29. data/spec/argus/at_commander_spec.rb +82 -0
  30. data/spec/argus/controller_spec.rb +149 -0
  31. data/spec/argus/float_encoding_spec.rb +12 -0
  32. data/spec/argus/led_animation_spec.rb +21 -0
  33. data/spec/argus/nav_data_spec.rb +147 -0
  34. data/spec/argus/nav_option_checksum_spec.rb +21 -0
  35. data/spec/argus/nav_option_demo_spec.rb +56 -0
  36. data/spec/argus/nav_option_spec.rb +32 -0
  37. data/spec/argus/nav_option_unknown_spec.rb +19 -0
  38. data/spec/argus/nav_option_vision_detect_spec.rb +65 -0
  39. data/spec/argus/nav_streamer_spec.rb +111 -0
  40. data/spec/argus/null_nav_monitor_spec.rb +17 -0
  41. data/spec/argus/time_queue_spec.rb +61 -0
  42. data/spec/argus/udp_sender_spec.rb +33 -0
  43. data/spec/spec_helper.rb +13 -0
  44. metadata +51 -12
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+
5
+ describe FloatEncoding do
6
+ Given(:f) { 5.0 }
7
+ Given(:encoded_f) { 1084227584 }
8
+ Then { FloatEncoding.encode_float(f) == encoded_f }
9
+ Then { FloatEncoding.decode_float(encoded_f) == f }
10
+ end
11
+
12
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+
5
+ describe LedAnimation do
6
+ describe ".lookup_name" do
7
+ Then { LedAnimation.lookup_name(3) == :blink_orange }
8
+ end
9
+
10
+ describe ".lookup_value" do
11
+ Then { LedAnimation.lookup_value(:blink_orange) == 3 }
12
+ Then { LedAnimation.lookup_value("blink_orange") == 3 }
13
+ Then { LedAnimation.lookup_value(3) == 3 }
14
+ Then { LedAnimation.lookup_value("3") == 3 }
15
+
16
+ Then { LedAnimation.lookup_value(:unknown) == nil }
17
+ Then { LedAnimation.lookup_value("UNKNOWN") == nil }
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+ describe NavData do
5
+ Given(:state_bits) { 0xcf8a0c94 }
6
+ Given(:seq_num) { 173064 }
7
+ Given(:vision_flag) { 0 }
8
+ Given(:raw_header) { Bytes.make_header(state_bits, seq_num, vision_flag) }
9
+ Given(:raw_nav_bytes) { Bytes.make_nav_data(raw_header) }
10
+
11
+ When(:nav_data) { NavData.new(raw_nav_bytes) }
12
+
13
+ Then { nav_data.sequence_number == 173064 }
14
+ Then { nav_data.vision_flag == 0 }
15
+
16
+ describe "sequence number" do
17
+ Given(:seq_num) { 1234 }
18
+ Then { nav_data.sequence_number == 1234 }
19
+ end
20
+
21
+ describe "vision flag" do
22
+ Given(:vision_flag) { 1 }
23
+ Then { nav_data.vision_flag == 1 }
24
+ end
25
+
26
+ describe "state queries" do
27
+
28
+ def bit(n)
29
+ 0x00000001 << n
30
+ end
31
+
32
+ # Matcher for handling the mask testing for the state mask.
33
+ #
34
+ # Usage:
35
+ # bit(5).should be_the_mask_for(:method [, off_value, on_value])
36
+ #
37
+ # If off_value and on_value are not given, then false/true will
38
+ # be assumed.
39
+ #
40
+ matcher :be_the_mask_for do |method, off_result=false, on_result=true|
41
+ match do |mask|
42
+ @msg = "OK"
43
+ try_alternative(method, 0, off_result) &&
44
+ try_alternative(method, mask, on_result)
45
+ end
46
+
47
+ failure_message_for_should do |mask|
48
+ @msg
49
+ end
50
+
51
+ # Try one of the alternatives. Sending method to the navdata
52
+ # constructed with mask should return teh expected value.
53
+ def try_alternative(method, mask, expected)
54
+ raw = Bytes.make_nav_data(Bytes.make_header(mask, 1, 0))
55
+ nav_data = NavData.new(raw)
56
+ result = nav_data.send(method)
57
+ return true if result == expected
58
+ @msg = "expected mask of 0x#{'%08x' % mask} with #{method}\n" +
59
+ "to return: #{expected.inspect}\n" +
60
+ "got: #{result.inspect}"
61
+ false
62
+ end
63
+ end
64
+
65
+ Then { bit(0).should be_the_mask_for(:flying?) }
66
+ Then { bit(1).should be_the_mask_for(:video_enabled?) }
67
+ Then { bit(2).should be_the_mask_for(:vision_enabled?) }
68
+ Then { bit(3).should be_the_mask_for(:control_algorithm, :euler, :angular_speed) }
69
+
70
+ Then { bit(4).should be_the_mask_for(:altitude_control_algorithm_active?) }
71
+ Then { bit(5).should be_the_mask_for(:start_button_pressed?) }
72
+ Then { bit(6).should be_the_mask_for(:control_command_ack?) }
73
+ Then { bit(7).should be_the_mask_for(:camera_ready?) }
74
+
75
+ Then { bit(8).should be_the_mask_for(:travelling_enabled?) }
76
+ Then { bit(9).should be_the_mask_for(:usb_ready?) }
77
+ Then { bit(10).should be_the_mask_for(:mode, :all, :demo) }
78
+ Then { bit(11).should be_the_mask_for(:bootstrap?) }
79
+
80
+ Then { bit(12).should be_the_mask_for(:moter_problem?) }
81
+ Then { bit(13).should be_the_mask_for(:communication_lost?) }
82
+ Then { bit(14).should be_the_mask_for(:software_fault?) }
83
+ Then { bit(15).should be_the_mask_for(:low_battery?) }
84
+
85
+ Then { bit(16).should be_the_mask_for(:emergency_landing_requested?) }
86
+ Then { bit(17).should be_the_mask_for(:timer_elapsed?) }
87
+ Then { bit(18).should be_the_mask_for(:magnometer_needs_calibration?) }
88
+ Then { bit(19).should be_the_mask_for(:angles_out_of_range?) }
89
+
90
+ Then { bit(20).should be_the_mask_for(:too_much_wind?) }
91
+ Then { bit(21).should be_the_mask_for(:ultrasonic_sensor_deaf?) }
92
+ Then { bit(22).should be_the_mask_for(:cutout_detected?) }
93
+ Then { bit(23).should be_the_mask_for(:pic_version_number_ok?) }
94
+
95
+ Then { bit(24).should be_the_mask_for(:at_codec_thread_on?) }
96
+ Then { bit(25).should be_the_mask_for(:navdata_thread_on?) }
97
+ Then { bit(26).should be_the_mask_for(:video_thread_on?) }
98
+ Then { bit(27).should be_the_mask_for(:acquisition_thread_on?) }
99
+
100
+ Then { bit(28).should be_the_mask_for(:control_watchdog_delayed?) }
101
+ Then { bit(29).should be_the_mask_for(:adc_watchdog_delayed?) }
102
+ Then { bit(30).should be_the_mask_for(:com_watchdog_problem?) }
103
+ Then { bit(31).should be_the_mask_for(:emergency_landing?) }
104
+ end
105
+
106
+ describe "state mask" do
107
+ context "all zeros" do
108
+ Given(:state_bits) { 0 }
109
+ Then { nav_data.state_mask == state_bits }
110
+ end
111
+
112
+ context "non-zero" do
113
+ Given(:state_bits) { 12345 }
114
+ Then { nav_data.state_mask == state_bits }
115
+ end
116
+ end
117
+
118
+ describe "#options" do
119
+ Then { nav_data.options.size == 1 }
120
+ Then { nav_data.options.first.is_a?(NavOptionChecksum) }
121
+ end
122
+ end
123
+
124
+ describe "multiple options" do
125
+ context "with good data" do
126
+ Given(:raw_header) { Bytes.make_header(0, 0, 0) }
127
+ Given(:raw_nav_bytes) {
128
+ Bytes.make_nav_data(raw_header, Bytes.make_demo_data)
129
+ }
130
+ When(:nav_data) { NavData.new(raw_nav_bytes) }
131
+ Then { nav_data.options.size == 2 }
132
+ Then { nav_data.options[0].is_a?(NavOptionDemo) }
133
+ Then { nav_data.options[1].is_a?(NavOptionChecksum) }
134
+ end
135
+
136
+ context "with short data" do
137
+ Given(:raw_nav_bytes) {
138
+ Bytes.make_header(0, 0, 0).pack("C*") +
139
+ [4321, 100, 0].pack("vvV")
140
+ }
141
+ When(:nav_data) { NavData.new(raw_nav_bytes) }
142
+ Then { nav_data.options.size == 1 }
143
+ Then { nav_data.options[0].is_a?(NavOptionUnknown) }
144
+ end
145
+ end
146
+
147
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+
5
+ describe NavOptionChecksum do
6
+ Given(:raw_data) { [NavTag::CHECKSUM, 8, 0x12345678].pack("vvV") }
7
+ Given(:csum) { NavOptionChecksum.new(raw_data) }
8
+
9
+ describe ".tag" do
10
+ Then { NavOptionChecksum.tag == NavTag::CHECKSUM }
11
+ end
12
+
13
+ describe "data fields" do
14
+ Then { csum.tag == NavOptionChecksum.tag }
15
+ Then { csum.size == raw_data.size }
16
+ Then { csum.chks == 0x12345678 }
17
+ Then { csum.checksum == 0x12345678 }
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ module Argus
5
+ describe NavOptionDemo do
6
+ def f(float)
7
+ FloatEncoding.encode_float(float)
8
+ end
9
+
10
+ # NOTE: This is a Base 64 encoded NavData packet recorded directly from the drone.
11
+ Given(:base64_data) {
12
+ "iHdmVdAEgA/5iwAAAAAAAAAAlAAAAAIAPAAAAADwREUAgCNFQFEiyAAAAABk" +
13
+ "AAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
14
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAADWP3i/6kNxPniCg72IpXO+64R4" +
15
+ "v+kAAD2hLGG9u7U6PatYfz8AAAAAAAAAAAAAdcMQAEgBAAAAAAAAAAAAAAAA" +
16
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
17
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
18
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
19
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
20
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
21
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
22
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8I" +
23
+ "ANQdAAA="
24
+ }
25
+ Given(:raw_data) { Base64.decode64(base64_data) }
26
+ Given(:nav_data) { NavData.new(raw_data) }
27
+
28
+ When(:demo) { nav_data.options.first }
29
+
30
+ Then { ! demo.nil? }
31
+ Then { demo.tag == Argus::NavTag::DEMO }
32
+ Then { demo.size == 148 }
33
+
34
+ Then { demo.ctrl_state == 0x20000 }
35
+ Then { demo.control_state_name == :default }
36
+ Then { demo.vbat_flying_percentage == 60 }
37
+ Then { demo.battery_level == 60 }
38
+ Then { demo.theta == 3151.0 }
39
+ Then { demo.phi == 2616.0 }
40
+ Then { demo.psi == -166213.0 }
41
+ Then { demo.pitch == 3.151 }
42
+ Then { demo.roll == 2.616 }
43
+ Then { demo.yaw == -166.213 }
44
+ Then { demo.altitude == 0 }
45
+ Then { demo.vx == about(0).delta(0.00000001) }
46
+ Then { demo.vy == about(0).delta(0.00000001) }
47
+ Then { demo.vz == about(0).delta(0.00000001) }
48
+ Then { demo.detection_camera_rot }
49
+ Then { demo.detection_camera_trans }
50
+ Then { demo.detection_tag_index == 0 }
51
+ Then { demo.detection_camera_type == 13 }
52
+ Then { demo.drone_camera_rot }
53
+ Then { demo.drone_camera_trans }
54
+ end
55
+
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+ describe NavOption do
5
+ let(:raw_checksum) { [ NavTag::CHECKSUM, 8, 0x5555aaaa].pack("vvV") }
6
+ let(:raw_demo) { [ NavTag::DEMO, 148 ].pack("vv") + "\0" * 148 }
7
+ let(:raw_vision_detect) { [ NavTag::VISION_DETECT, 328 ].pack("vv") + "\0" * 328 }
8
+ let(:raw_unknown_tag) { [ 12345, 8 ].pack("vv") + " " }
9
+
10
+ When(:result) { NavOption.parse(raw_data) }
11
+
12
+ describe "parsing checksum options" do
13
+ Given(:raw_data) { raw_checksum }
14
+ Then { result.is_a?(NavOptionChecksum) }
15
+ end
16
+
17
+ describe "parsing demo option" do
18
+ Given(:raw_data) { raw_demo }
19
+ Then { result.is_a?(NavOptionDemo) }
20
+ end
21
+
22
+ describe "parsing vision detect option" do
23
+ Given(:raw_data) { raw_vision_detect }
24
+ Then { result.is_a?(NavOptionVisionDetect) }
25
+ end
26
+
27
+ describe "parsing bad tag" do
28
+ Given(:raw_data) { raw_unknown_tag }
29
+ Then { result.is_a?(NavOptionUnknown) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+
5
+ describe NavOptionUnknown do
6
+ Given(:raw_data) { [0x5231, 8, 0x12345678].pack("vvV") }
7
+ Given(:opt) { NavOptionUnknown.new(raw_data) }
8
+
9
+ describe ".tag" do
10
+ Then { NavOptionUnknown.tag == 0xfffe }
11
+ end
12
+
13
+ describe "data fields" do
14
+ Then { opt.tag == 0x5231 }
15
+ Then { opt.size == raw_data.size }
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+
5
+ describe NavOptionVisionDetect do
6
+ def f(float)
7
+ FloatEncoding.encode_float(float)
8
+ end
9
+
10
+ Given(:raw_data) {
11
+ [
12
+ NavTag::VISION_DETECT, 328,
13
+ 2,
14
+ 1, 2, 3, 4,
15
+ 10, 11, 12, 13,
16
+ 20, 21, 22, 23,
17
+ 30, 31, 32, 33,
18
+ 40, 41, 42, 43,
19
+ 50, 51, 52, 53,
20
+ f(60.0), f(61.0), f(62.0), f(63.0),
21
+ 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,
22
+ 0,0,0, 0,0,0, 0,0,0, 0,0,0,
23
+ 70, 71, 72, 73
24
+ ].flatten.pack("vv V V4 V4 V4 V4 V4 V4 V4 V36 V12 V4")
25
+ }
26
+
27
+ When(:detect) { NavOptionVisionDetect.new(raw_data) }
28
+
29
+ Then { NavOptionVisionDetect.tag == NavTag::VISION_DETECT }
30
+ Then { detect.tag == Argus::NavTag::VISION_DETECT }
31
+ Then { detect.size == 328 }
32
+
33
+ describe "C fields" do
34
+ Then { detect.nb_detected == 2 }
35
+ Then { detect.type == [1, 2, 3, 4] }
36
+ Then { detect.type_name == [:vertical, :vision, :none, :cocarde] }
37
+ Then { detect.xc == [10, 11, 12, 13] }
38
+ Then { detect.yc == [20, 21, 22, 23] }
39
+ Then { detect.width == [30, 31, 32, 33] }
40
+ Then { detect.height == [40, 41, 42, 43] }
41
+ Then { detect.dist == [50, 51, 52, 53] }
42
+ Then { detect.distance == [50, 51, 52, 53] }
43
+ Then { detect.orientation_angle == [60.0, 61.0, 62.0, 63.0] }
44
+ Then { detect.rotation.size == 9 * 4 }
45
+ Then { detect.translation.size == 3 * 4 }
46
+ Then { detect.camera_source == [70, 71, 72, 73] }
47
+ end
48
+
49
+ describe "detection info" do
50
+ Then { detect.detections.size == 2 }
51
+
52
+ let(:d0) { detect.detections[0] }
53
+ Then { d0.type == 1 }
54
+ Then { d0.type_name == :vertical }
55
+ Then { d0.x == 10 }
56
+ Then { d0.y == 20 }
57
+ Then { d0.width == 30 }
58
+ Then { d0.height == 40 }
59
+ Then { d0.orientation_angle == 60.0 }
60
+ Then { d0.camera_source == 70 }
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ module Argus
4
+ describe NavStreamer do
5
+ Given(:remote_host) { '192.168.1.1' }
6
+ Given(:port) { 5554 }
7
+ Given(:socket) { flexmock("Socket", send: nil).should_ignore_missing }
8
+ Given(:streamer) { NavStreamer.new(remote_host: remote_host, UDPSocket: flexmock(new: socket)) }
9
+
10
+ context "after starting" do
11
+ Given { streamer.start }
12
+
13
+ context "when receiving good data" do
14
+ Given(:bytes) { Bytes.make_nav_data(Bytes.make_header(0x1234, 0, 0)) }
15
+ Given { socket.should_receive(recvfrom: bytes) }
16
+ When(:nav_data) { streamer.receive_data }
17
+ Then { nav_data.state_mask == 0x1234}
18
+ end
19
+
20
+ context "when receiving bad data" do
21
+ Given(:bytes) { [0x89, 0x77, 0x66, 0x55, 0x34, 0x12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].pack("C*") }
22
+ Given { socket.should_receive(:recvfrom => bytes) }
23
+ When(:nav_data) { streamer.receive_data }
24
+ Then { nav_data.nil? }
25
+ end
26
+ end
27
+
28
+ describe "State Transitions" do
29
+ Given {
30
+ flexmock(streamer, reconnect: nil, request_nav_data: nil)
31
+ }
32
+
33
+ def goto_wait
34
+ streamer.start
35
+ end
36
+
37
+ def goto_run
38
+ streamer.start
39
+ streamer.received_data
40
+ end
41
+
42
+ context "when init & start" do
43
+ Given { streamer.start }
44
+ Then { streamer.should have_received(:reconnect).once }
45
+ Then { streamer.state == :wait }
46
+ end
47
+
48
+ context "when init & short_timeout" do
49
+ Given { streamer.short_timeout }
50
+ Then { streamer.should have_received(:reconnect).never }
51
+ Then { streamer.should have_received(:request_nav_data).never }
52
+ Then { streamer.state == :init }
53
+ end
54
+
55
+ context "when init & long_timeout" do
56
+ Given { streamer.long_timeout }
57
+ Then { streamer.should have_received(:reconnect).never }
58
+ Then { streamer.should have_received(:request_nav_data).never }
59
+ Then { streamer.state == :init }
60
+ end
61
+
62
+ context "when wait & received_data" do
63
+ Given { goto_wait }
64
+ Given { streamer.received_data }
65
+ Then { streamer.should have_received(:reconnect).once }
66
+ Then { streamer.should have_received(:request_nav_data).never }
67
+ Then { streamer.state == :run }
68
+ end
69
+
70
+ context "when wait & short_timeout" do
71
+ Given { goto_wait }
72
+ Given { streamer.short_timeout }
73
+ Then { streamer.should have_received(:reconnect).once }
74
+ Then { streamer.should have_received(:request_nav_data).once }
75
+ Then { streamer.state == :wait }
76
+ end
77
+
78
+ context "when wait & long_timeout" do
79
+ Given { goto_wait }
80
+ Given { streamer.long_timeout }
81
+ Then { streamer.should have_received(:reconnect).twice }
82
+ Then { streamer.should have_received(:request_nav_data).never }
83
+ Then { streamer.state == :wait }
84
+ end
85
+
86
+ context "when run & received_data" do
87
+ Given { goto_run }
88
+ Given { streamer.received_data }
89
+ Then { streamer.should have_received(:reconnect).once }
90
+ Then { streamer.should have_received(:request_nav_data).never }
91
+ Then { streamer.state == :run }
92
+ end
93
+
94
+ context "when run & short_timeout" do
95
+ Given { goto_run }
96
+ Given { streamer.short_timeout }
97
+ Then { streamer.should have_received(:reconnect).once }
98
+ Then { streamer.should have_received(:request_nav_data).never }
99
+ Then { streamer.state == :run }
100
+ end
101
+
102
+ context "when run & long_timeout" do
103
+ Given { goto_run }
104
+ Given { streamer.long_timeout }
105
+ Then { streamer.should have_received(:reconnect).twice }
106
+ Then { streamer.should have_received(:request_nav_data).never }
107
+ Then { streamer.state == :wait }
108
+ end
109
+ end
110
+ end
111
+ end