/
brick.rb
135 lines (124 loc) · 4.55 KB
/
brick.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
require "lego_nxt/types"
require "lego_nxt/constants"
require "lego_nxt/errors"
module LegoNXT
# A mid-level API for talking to a LEGO NXT brick.
class Brick
# The connection object.
#
# @return [LegoNXT::UsbConnection, LegoNXT::SerialConnection]
attr_reader :connection
# @param [LegoNXT::UsbConnection, LegoNXT::SerialConnection] connection The connection object.
def initialize connection
@connection = connection
end
# Plays a tone.
#
# @param [Integer] frequency Range: 200-1400Hz
# @param [Integer] duration in milliseconds (1/1000 of a second)
# @return [nil]
def play_tone frequency, duration
raise ArgumentError.new("Frequency must be between 200-1400Hz; got #{frequency}") if frequency < 200 || frequency > 1400
transmit(
DirectOps::NO_RESPONSE,
DirectOps::PLAYTONE,
word(frequency),
word(duration)
)
end
# The battery level.
#
# @return [Integer] The voltage in millivolts.
def battery_level
transceive(
DirectOps::REQUIRE_RESPONSE,
DirectOps::GETBATTERYLEVEL,
).unpack('S<')[0]
end
# Resets the tracking for the motor position.
#
# @param [Symbol] port The port the motor is attached to. Should be `:a`, `:b`, or `:c`
# @param [Boolean] set_relative Sets the position tracking to relative if true, otherwise absolute if false.
# @return [nil]
def reset_motor_position port, set_relative
transmit(
DirectOps::NO_RESPONSE,
DirectOps::RESETMOTORPOSITION,
normalize_motor_port(port),
normalize_boolean(set_relative)
)
end
# Runs the motor
#
# @param [Symbol] port The port the motor is attached to. Should be `:a`, `:b`, `:c`, `:all`
# @param [Integer] power A number between -100 through 100 inclusive. Defaults to 100.
# @return [nil]
def run_motor port, power=100
raise ArgumentError.new("Power must be -100 through 100") if power < -100 || power > 100
transmit(
LegoNXT::DirectOps::NO_RESPONSE,
LegoNXT::DirectOps::SETOUTPUTSTATE,
normalize_motor_port(port, accept_all=true),
sbyte(power), # power set point
byte(1), # mode
byte(0), # regulation mode
sbyte(0), # turn ratio
byte(0x20), # run state
long(0), # tacho limit
)
end
# Stops the motor
#
# @param [Symbol] port The port the motor is attached to. Should be `:a`, `:b`, `:c`, `:all`
# @return [nil]
def stop_motor port
run_motor port, 0
end
# A wrapper around the transmit function for the connection.
#
# @param [LegoNXT::Type] bits A list of bytes.
# @return [nil]
def transmit *bits
bitstring = bits.map(&:byte_string).join("")
connection.transmit bitstring
end
# A wrapper around the transceive function for the connection.
#
# @param [LegoNXT::Type] bits A list of bytes.
# @return [String] Bytes as returned by the connection object.
def transceive *bits
bitstring = bits.map(&:byte_string).join("")
retval = connection.transceive bitstring
# Check that it's a response bit.
raise LegoNXT::BadResponseError unless retval[0] == "\x02"
# Check that it's for this command.
raise LegoNXT::BadResponseError unless retval[1] == bitstring[1]
# Check that there is no error.
# TODO: This should raise a specific error based on the status bit.
raise LegoNXT::StatusError unless retval[2] == "\x00"
return retval[3..-1]
end
# Converts a port symbol into the appropriate byte().
#
# @param [Symbol] port It should be `:a`, `:b`, `:c`, or `:all` (which correspond to the markings on the brick)
# @param [Boolean] accept_all If true, then `:all` will be allowed, otherwise it's an error.
# @return [UnsignedByte] The corresponding byte for the port.
def normalize_motor_port port, accept_all=false
@portmap ||= { a: byte(0),
b: byte(1),
c: byte(2),
all: byte(0xff),
}
raise ArgumentError.new("You cannot specify :all for this port") if port == :all and !accept_all
raise ArgumentError.new("Motor ports must be #{@portmap.keys.inspect}: got #{port.inspect}") unless @portmap.include? port
@portmap[port]
end
# Converts a boolean value into `byte(0)` or `byte(1)`
#
# @param [Object] bool The value to convert.
# @return [UnsignedByte] Returns 0 or 1
def normalize_boolean bool
bool ? byte(1) : byte(0)
end
end
end