Class: MotorControlBoard
- Inherits:
-
Object
- Object
- MotorControlBoard
- Defined in:
- lib/motorcontrolboard.rb,
lib/motorcontrolboard/mcb_data.rb,
lib/motorcontrolboard/mcb_connection.rb
Overview
This class implements an interface to communicate with a Motor Control Board (irawiki.disco.unimib.it/irawiki/index.php/INFIND2011/12_Motor_control_board)
It uses an internal representation of the commands that can be sent to the board and also the representation of the memory locations where is possible to write/read along with the position, the type and the actual value.
A row in the internal representation, which corresponds to a memory location in the physical board, is composed by some fields:
-
mask_name: a symbol to target the field. Is derived from the mask defined in the serial header file of the firmware. The symbol is obtained by removing the initial MASK_ and making the name lowercase
-
position: the position of the memory location, starting from zero
-
type: the tipe of the memory location. It can be one among FLSC accoring to the ARRAY::pack documentation
-
value: the value of the memory location
-
valid: a valid bit. If set, the row will be involved in the next read/write command
Instance Attribute Summary collapse
-
#baud_rate ⇒ Object
Baud rate of the connection.
-
#data ⇒ Object
readonly
Internal representation of the board memory, as array of hashes.
-
#port ⇒ Object
String describing the port where the board is connected.
-
#vidpid ⇒ Object
Can be obtained through a command like ‘lsusb’.
Instance Method Summary collapse
-
#addData(newData) ⇒ Object
Add data to internal representation.
-
#cmd_coast ⇒ Object
Send the command coast.
-
#cmd_get ⇒ Object
Send the command get.
-
#cmd_gocalib ⇒ Object
Send the command gocalib.
-
#cmd_goinit ⇒ Object
Send the command goinit.
-
#cmd_gorunning ⇒ Object
Send the command gorunning.
-
#cmd_reset ⇒ Object
Send the command reset.
-
#cmd_set ⇒ Object
Send the command set.
-
#cmd_start ⇒ Object
Send the command start.
-
#cmd_uncoast ⇒ Object
Send the command uncoast.
-
#cmd_whois ⇒ Object
Send the command whois and return the result.
-
#connect ⇒ Object
Connect to serial port specified by @port member.
-
#dataMask ⇒ Object
Return the mask of the valid bits of the internal representation.
-
#dataResetValid ⇒ Object
Set valid bit to 0 for each row.
-
#dataSetValid ⇒ Object
Set valid bit to 1 for each row.
-
#dataValues ⇒ Object
Return the values according to valid bit.
-
#disconnect ⇒ Object
Disconnect from serial port.
-
#findData(needle) ⇒ Object
Return the index of the matching row diven some search params in hash format.
-
#findPort ⇒ Object
Find the port to which the board is connected, looking for the specified @vidpid.
-
#getByMask(mask) ⇒ Object
Read data according to the given mask.
-
#getByNames(*names) ⇒ Object
Read data according to the given names.
-
#getByValid ⇒ Object
Get data according to the valid bit.
-
#getMaxPos ⇒ Object
Return the maximum value of the position field among all data.
-
#getSingleData(pos) ⇒ Object
Read data at the given position.
-
#initData(initFile) ⇒ Object
Init the internal representation loading the given file.
-
#initialize(args = {}) ⇒ MotorControlBoard
constructor
Accept as argument an hash specifying #port, #vidpid, and or #baud_rate #data has to be initialized with the apposite function #initData.
-
#lenByType(type) ⇒ Object
Return the byte length of a type used by pack/unpack.
-
#loadData(dataToLoad) ⇒ Object
Load data from a yaml representation.
-
#loadDataFromFile(path) ⇒ Object
Load a yaml file and saves to state with #addData.
-
#maskByPos(pos) ⇒ Object
Return the mask conresponding to the given position.
-
#maskFromNames(*names) ⇒ Object
Return a mask according to the names passed as parameters.
-
#maskToPos(mask) ⇒ Object
Return an array of poses given a mask.
-
#maskToValid(mask) ⇒ Object
Set the valid bit according to a given mask.
-
#positionFromName(name) ⇒ Object
Return the position given the name.
-
#saveData ⇒ Object
Return internal data in yaml format.
-
#saveDataToFile(path) ⇒ Object
- Dump internal data to yaml format and save to file Params:
path
-
path where to save the yaml data.
- Dump internal data to yaml format and save to file Params:
-
#sendC(char) ⇒ Object
Send a single char.
-
#sendCommand(command) ⇒ Object
commands.
-
#sendS(string) ⇒ Object
Send a string of char.
-
#setByName(name, val) ⇒ Object
Send single data by name.
-
#setByPos(pos, val) ⇒ Object
yet to be implemented..
-
#setByValid ⇒ Object
Set data according to valid bit.
-
#setPort(num) ⇒ Object
Shortcut to set a ‘/dev/ttyUSBx’ port.
-
#sortData ⇒ Object
Sort data by position.
- #startByte ⇒ Object
- #validDataLength ⇒ Object
Constructor Details
#initialize(args = {}) ⇒ MotorControlBoard
Accept as argument an hash specifying #port, #vidpid, and or #baud_rate #data has to be initialized with the apposite function #initData
26 27 28 29 30 |
# File 'lib/motorcontrolboard.rb', line 26 def initialize(args = {}) @port = args['port'] || '/dev/ttyUSB0' @baud_rate = args['baud_rate'] || 57600 @vidpid = args['vidpid'] end |
Instance Attribute Details
#baud_rate ⇒ Object
Baud rate of the connection. In the actual firmware version it should be 57600
13 14 15 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 13 def baud_rate @baud_rate end |
#data ⇒ Object (readonly)
Internal representation of the board memory, as array of hashes
7 8 9 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 7 def data @data end |
#port ⇒ Object
String describing the port where the board is connected. Usually is something like ‘/dev/ttyUSB0’. Can be auto-discovered, setting the #vidpid variable if the vid:pid of the board is known
11 12 13 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 11 def port @port end |
#vidpid ⇒ Object
Can be obtained through a command like ‘lsusb’.
Setting this variable allow to discover the port where the board is conencted automatically
8 9 10 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 8 def vidpid @vidpid end |
Instance Method Details
#addData(newData) ⇒ Object
Add data to internal representation
Params:
newData
-
data to add in array of hashes format
If the valid field is present, only rows with valid==1 will be taken into account. The matching row, selected with the #findData method, is updated with the new value and the valid bit is set
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 68 def addData(newData) if newData[0]['valid'] != nil newData = newData.select{|newData| newData['valid']==1} end newData.each do |newValidDatum| if (index = findData(newValidDatum)) @data[index]['value'] = newValidDatum['value'] @data[index]['valid'] = 1 else puts 'Unable to find entry matching mask_name and position' puts newValidDatum end end sortData() end |
#cmd_coast ⇒ Object
Send the command coast
143 144 145 |
# File 'lib/motorcontrolboard.rb', line 143 def cmd_coast self.sendCommand('j') end |
#cmd_get ⇒ Object
Send the command get
123 124 125 |
# File 'lib/motorcontrolboard.rb', line 123 def cmd_get self.sendCommand('G') end |
#cmd_gocalib ⇒ Object
Send the command gocalib
135 136 137 |
# File 'lib/motorcontrolboard.rb', line 135 def cmd_gocalib self.sendCommand('b') end |
#cmd_goinit ⇒ Object
Send the command goinit
139 140 141 |
# File 'lib/motorcontrolboard.rb', line 139 def cmd_goinit self.sendCommand('i') end |
#cmd_gorunning ⇒ Object
Send the command gorunning
131 132 133 |
# File 'lib/motorcontrolboard.rb', line 131 def cmd_gorunning self.sendCommand('a') end |
#cmd_reset ⇒ Object
Send the command reset
151 152 153 |
# File 'lib/motorcontrolboard.rb', line 151 def cmd_reset self.sendCommand('r') end |
#cmd_set ⇒ Object
Send the command set
119 120 121 |
# File 'lib/motorcontrolboard.rb', line 119 def cmd_set self.sendCommand('S') end |
#cmd_start ⇒ Object
Send the command start
127 128 129 |
# File 'lib/motorcontrolboard.rb', line 127 def cmd_start self.sendCommand('x') end |
#cmd_uncoast ⇒ Object
Send the command uncoast
147 148 149 |
# File 'lib/motorcontrolboard.rb', line 147 def cmd_uncoast self.sendCommand('u') end |
#cmd_whois ⇒ Object
Send the command whois and return the result
155 156 157 158 159 160 161 162 |
# File 'lib/motorcontrolboard.rb', line 155 def cmd_whois self.sendCommand('w') who = "" while (char = @sp.getc) != "\u0000" who << char end who end |
#connect ⇒ Object
Connect to serial port specified by @port member
If @vidpid is set, it checks if that device is connected to a serial port and that port is chosen, regardless of the @port value. If block given it closes the connection at the end of the block
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 29 def connect if findPort() puts "Automatically selected port #{@port}" end data_bits = 8 stop_bits = 1 parity = SerialPort::NONE begin @sp = SerialPort.new(@port, @baud_rate, data_bits, stop_bits, parity) @open = true rescue puts 'ERROR: Unable to find serial port ' + @port @open = false end if block_given? yield self.disconnect p "port closed" end @open end |
#dataMask ⇒ Object
Return the mask of the valid bits of the internal representation
The data get sorted but the position is not taken in account so for example if a position is missing a zero will not be added. The user is responsible to provide consistent data
37 38 39 40 |
# File 'lib/motorcontrolboard.rb', line 37 def dataMask sortData() '0b' + @data.inject(''){|mem, ob| ob['valid'].to_s + mem } end |
#dataResetValid ⇒ Object
Set valid bit to 0 for each row
96 97 98 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 96 def dataResetValid @data.each {|row| row['valid']=0} end |
#dataSetValid ⇒ Object
Set valid bit to 1 for each row
101 102 103 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 101 def dataSetValid @data.each {|row| row['valid']=1} end |
#dataValues ⇒ Object
Return the values according to valid bit
The data get sorted but the position is not taken in account Values are also packed according to their type
46 47 48 49 |
# File 'lib/motorcontrolboard.rb', line 46 def dataValues sortData() @data.select{|data| data['valid']==1}.inject("") {|acc, val| acc << [val['value']].pack(val['type'])} end |
#disconnect ⇒ Object
Disconnect from serial port
54 55 56 57 58 59 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 54 def disconnect if @open @open = false @sp.close end end |
#findData(needle) ⇒ Object
Return the index of the matching row diven some search params in hash format
Params:
needle
-
the hash to search. Search is performed by mask_name, position and type.
If the result contains one row only, its index is returned, otherwise nil is returned
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 50 def findData(needle) result = @data result = result.select { |a| a['mask_name']==needle['mask_name']} if needle['mask_name'] != nil result = result.select { |a| a['position']==needle['position']} if needle['position'] != nil result = result.select { |a| a['type']==needle['type']} if needle['type'] != nil if result.length == 1 @data.index result.first else nil end end |
#findPort ⇒ Object
Find the port to which the board is connected, looking for the specified @vidpid
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 62 def findPort begin if @vidpid busNumber = Integer(`lsusb|grep #{@vidpid}`[4..6]) port = `ls /sys/bus/usb-serial/devices/ -ltrah |grep usb#{busNumber}`.chop @port = '/dev/'+port.gsub(/ttyUSB[0-9]+/).first else false end rescue false end end |
#getByMask(mask) ⇒ Object
Read data according to the given mask
204 205 206 207 208 |
# File 'lib/motorcontrolboard.rb', line 204 def getByMask(mask) dataResetValid maskToValid(mask) getByValid end |
#getByNames(*names) ⇒ Object
Read data according to the given names
199 200 201 |
# File 'lib/motorcontrolboard.rb', line 199 def getByNames(*names) getByMask(maskFromNames(*names)) end |
#getByValid ⇒ Object
Get data according to the valid bit
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/motorcontrolboard.rb', line 211 def getByValid() mask = dataMask() if (Integer(mask)!=0) @data = @data.sort_by { |row| row['position'] } len = validDataLength cmd_get() sleep 0.1 self.sendS([Integer(mask)].pack('L')) result = [] readBytes = [] begin Timeout::timeout(1) do len.times {readBytes << @sp.getbyte} readBytes.reverse! @data.select{|data| data['valid']==1}.each do |row| #do we need to revert this?? data="" lenByType(row['type']).times do data << readBytes.pop end value = data.unpack(row['type']).first row['value'] = value result << {'mask_name'=>row['mask_name'], 'value' => value} end end rescue puts 'Timeout to read with mask ' + mask puts 'Read ' + readBytes.length.to_s + '/' + len.to_s + ' bytes' puts 'READ:' + readBytes.to_s end return result end end |
#getMaxPos ⇒ Object
Return the maximum value of the position field among all data
111 112 113 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 111 def getMaxPos @data.max_by {|row| row['position']}['position'] end |
#getSingleData(pos) ⇒ Object
Read data at the given position
194 195 196 |
# File 'lib/motorcontrolboard.rb', line 194 def getSingleData(pos) getByMask(maskByPos(pos)) end |
#initData(initFile) ⇒ Object
Init the internal representation loading the given file.
Params:
initFile
-
the path to a yaml file containing the data structure to be used.
The file should contain at least mask_name, position and type. Also if value and valid are present they will be set to 0. Other fields are permitted although they will not be used: this functionality is not tested.
Data will be sorted by position
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 19 def initData(initFile) @data = [] initData = YAML.load_file(initFile) initData.each do |datum| datum['value']=0 datum['valid']=0 end @data = initData sortData() end |
#lenByType(type) ⇒ Object
Return the byte length of a type used by pack/unpack
81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/motorcontrolboard.rb', line 81 def lenByType(type) case type when 'L', 'F' 4 when 'S' 2 when 'C' 1 else 0 end end |
#loadData(dataToLoad) ⇒ Object
Load data from a yaml representation
#addData is called so that everything is stored in the internal state
86 87 88 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 86 def loadData(dataToLoad) addData(YAML.load(dataToLoad)) end |
#loadDataFromFile(path) ⇒ Object
Load a yaml file and saves to state with #addData
91 92 93 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 91 def loadDataFromFile(path) addData(YAML.load_file(path)) end |
#maskByPos(pos) ⇒ Object
Return the mask conresponding to the given position
76 77 78 |
# File 'lib/motorcontrolboard.rb', line 76 def maskByPos(pos) return "0b"+"1"+"0"*pos end |
#maskFromNames(*names) ⇒ Object
Return a mask according to the names passed as parameters
The length of the mask will be the max pos + 1 so if there are missing/duplicated positions the mask will be inconsistent
62 63 64 65 66 67 68 69 |
# File 'lib/motorcontrolboard.rb', line 62 def maskFromNames(*names) mask = '0'*(getMaxPos()+1) names.each do |name| mask[positionFromName(name)]='1' end mask='0b'+mask.reverse end |
#maskToPos(mask) ⇒ Object
Return an array of poses given a mask
95 96 97 98 99 100 101 102 103 |
# File 'lib/motorcontrolboard.rb', line 95 def maskToPos(mask) pos = [] mask[2..-1].reverse.each_char.with_index do |e, i| if e=='1' pos << i end end pos end |
#maskToValid(mask) ⇒ Object
Set the valid bit according to a given mask
52 53 54 55 56 |
# File 'lib/motorcontrolboard.rb', line 52 def maskToValid(mask) maskToPos(mask).each do |pos| (@data.select{|row| row['position']==pos}.first)['valid']=1 end end |
#positionFromName(name) ⇒ Object
Return the position given the name
71 72 73 |
# File 'lib/motorcontrolboard.rb', line 71 def positionFromName(name) (@data.select{|newData| newData['mask_name']==name}.first)['position'] end |
#saveData ⇒ Object
Return internal data in yaml format
31 32 33 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 31 def saveData YAML.dump @data end |
#saveDataToFile(path) ⇒ Object
Dump internal data to yaml format and save to file Params:
path
-
path where to save the yaml data
38 39 40 41 42 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 38 def saveDataToFile(path) File.open(path, 'w') do |f| YAML.dump(@data, f) end end |
#sendC(char) ⇒ Object
Send a single char
77 78 79 80 81 82 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 77 def sendC(char) if (!@open) connect() end @sp.putc char.chr end |
#sendCommand(command) ⇒ Object
commands
106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/motorcontrolboard.rb', line 106 def sendCommand(command) startByte() sendC(command) if @echo puts 'sent: ' + command puts 'waiting for return' rec = @sp.getc puts 'received: ' + rec puts 'does they match? ' + (command==rec).to_s end end |
#sendS(string) ⇒ Object
Send a string of char
85 86 87 88 89 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 85 def sendS(string) string.each_char do |char| sendC(char) end end |
#setByName(name, val) ⇒ Object
Send single data by name
Params:
name
-
The symbol matching the name of the mask
val
-
The value to assign
181 182 183 184 185 186 |
# File 'lib/motorcontrolboard.rb', line 181 def setByName(name, val) dataResetValid maskToValid(maskFromNames(name)) (@data.select {|row| row['mask_name']==name}.first)['value']=val setByValid() end |
#setByPos(pos, val) ⇒ Object
yet to be implemented.. is this useful?
189 190 |
# File 'lib/motorcontrolboard.rb', line 189 def setByPos(pos, val) end |
#setByValid ⇒ Object
Set data according to valid bit. The value saved in the internal representation will be set
166 167 168 169 170 171 172 173 174 |
# File 'lib/motorcontrolboard.rb', line 166 def setByValid() mask = dataMask() values = dataValues() cmd_set() sleep 0.1 self.sendS([Integer(mask)].pack('L')) sleep 0.1 self.sendS(values) end |
#setPort(num) ⇒ Object
Shortcut to set a ‘/dev/ttyUSBx’ port. It also connects to that port
Params:
num
-
number of the ttyUSB port to be set
20 21 22 23 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 20 def setPort(num) @port = '/dev/ttyUSB' + num.to_s connect end |
#sortData ⇒ Object
Sort data by position
106 107 108 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 106 def sortData @data = @data.sort_by { |row| row['position'] } end |
#startByte ⇒ Object
91 92 93 94 |
# File 'lib/motorcontrolboard/mcb_connection.rb', line 91 def startByte() sendC(0x55) sleep 0.1 end |
#validDataLength ⇒ Object
115 116 117 |
# File 'lib/motorcontrolboard/mcb_data.rb', line 115 def validDataLength @data.select{|row| row['valid']==1}.inject(0){|sum, row| sum+lenByType(row['type'])} end |