├── lib ├── serial_can_bus.rb └── serial_can_bus │ ├── iso-tp.rb │ ├── response.rb │ ├── request.rb │ └── serial_can_bus.rb ├── CHANGELOG ├── LICENSE ├── serial_can_bus.gemspec └── README.md /lib/serial_can_bus.rb: -------------------------------------------------------------------------------- 1 | require 'bit-struct' 2 | require 'serialport' 3 | 4 | require 'serial_can_bus/serial_can_bus' 5 | require 'serial_can_bus/request' 6 | require 'serial_can_bus/response' 7 | require 'serial_can_bus/iso-tp' 8 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 0.4 (20160505) 2 | 3 | * add ISO-TP (ISO-15765-2) support 4 | * improve API documentation 5 | * change while_receiving returned data from Fixnum to String 6 | * add string support in frame data transmission 7 | 8 | == 0.3 (20160415) 9 | 10 | * adjust BtrSetup bitrate table 11 | 12 | == 0.2 (20131007) 13 | 14 | * better abstraction favouring instance methods 15 | 16 | == 0.1 (20130724) 17 | 18 | * first public release 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Andrea Barisani 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /serial_can_bus.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'serial_can_bus' 3 | s.version = '0.4' 4 | s.date = '2016-05-05' 5 | s.summary = 'A simple implementation of the LAWICEL ASCII protocol for serial 6 | CAN bus adapters (slcan).' 7 | 8 | s.description = 'A simple implementation of the LAWICEL ASCII protocol for 9 | LAWICEL serial CAN bus adapters, tested with CANUSB though it should work with minimal 10 | or null effort on CAN232 adapters (both available at http://www.can232.com). 11 | 12 | This class of devices is covered by the slcan driver in the Linux kernel, this 13 | Ruby implementation however does not require such driver and allows full 14 | interaction with the adapters as long as the serial port is available.' 15 | 16 | s.authors = ["Andrea Barisani"] 17 | s.email = 'andrea@inversepath.com' 18 | s.files = ['lib/serial_can_bus.rb', 'lib/serial_can_bus/serial_can_bus.rb', 19 | 'lib/serial_can_bus/request.rb', 'lib/serial_can_bus/response.rb', 20 | 'lib/serial_can_bus/iso-tp.rb'] 21 | s.extra_rdoc_files = ['CHANGELOG', 'LICENSE', 'README.md'] 22 | s.homepage = 'https://github.com/f-secure-foundry/SerialCanBus' 23 | s.requirements = ['bit-struct', 'serialport'] 24 | s.licenses = ['ISC'] 25 | end 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SerialCanBus 2 | ============ 3 | 4 | Copyright (c) Andrea Barisani 5 | 6 | A Ruby library for LAWICEL serial CAN bus adapters 7 | 8 | Introduction 9 | ============ 10 | 11 | A simple implementation of the LAWICEL ASCII protocol for LAWICEL serial CAN 12 | bus adapters, tested with CANUSB though it should work with minimal or null 13 | effort on CAN232 adapters (both available at http://www.can232.com). 14 | 15 | This class of devices is covered by the slcan driver in the Linux kernel, this 16 | Ruby implementation however does not require such driver and allows full 17 | interaction with the adapters as long as the serial port is available. 18 | 19 | Examples 20 | ======== 21 | 22 | initialization: 23 | 24 | ``` 25 | require 'serial_can_bus' 26 | slcan = SerialCanBus.new('/dev/ttyUSB0', 19200, 125000) 27 | ``` 28 | 29 | transmission of standard CAN frame with identifier 0x7ff and 2 bytes of data: 30 | 31 | ``` 32 | slcan.transmit_frame(:standard, 0x7ff, 2, 0xbeef) 33 | ``` 34 | 35 | sniff first 20 frames: 36 | 37 | ``` 38 | slcan.while_receiving(20) do |kind, identifier, length, data| 39 | puts "kind #{kind} identifier #{identifier.to_s(16)} data #{data.unpack('H*')}" 40 | end 41 | ``` 42 | 43 | get adapter status: 44 | 45 | ``` 46 | puts slcan.issue_command(:status_flag).dump 47 | ``` 48 | 49 | Requirements 50 | ============ 51 | 52 | bit-struct, serialport 53 | 54 | Resources 55 | ========= 56 | 57 | The SerialCanBus repository is https://github.com/f-secure-foundry/SerialCanBus 58 | 59 | Automatically generated documentation can be found at 60 | http://rubydoc.info/github/f-secure-foundry/SerialCanBus/master 61 | or using the Ruby Index (ri) tool (e.g. `ri SerialCanBus.transmit_frame`) 62 | 63 | Please report support and feature requests to 64 | 65 | License 66 | ======= 67 | 68 | Copyright (c) Andrea Barisani 69 | 70 | Permission to use, copy, modify, and distribute this software for any 71 | purpose with or without fee is hereby granted, provided that the above 72 | copyright notice and this permission notice appear in all copies. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 75 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 76 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 77 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 78 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 79 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 80 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 81 | -------------------------------------------------------------------------------- /lib/serial_can_bus/iso-tp.rb: -------------------------------------------------------------------------------- 1 | # Helpers for ISO-TP (ISO-15765-2) data packet creation/parsing. 2 | # 3 | # - Examples 4 | # 5 | # build multiple segments from single payload: 6 | # segments = SerialCanBus::ISOTP.split(payload) 7 | # 8 | # segments.each do |data| 9 | # response = slcan.transmit_frame(:standard, 0x7ff, data.size, data) 10 | # end 11 | 12 | class SerialCanBus::ISOTP 13 | 14 | # Single frame 15 | 16 | class Single < BitStruct 17 | unsigned :header, 4, :default => 0 18 | 19 | ## 20 | # :attr_accessor: dlength 21 | # data length (Fixnum, 4 bits) 22 | unsigned :dlength, 4, 'data length' 23 | 24 | ## 25 | # :attr_accessor: data 26 | # frame data (binary) 27 | rest :data 28 | 29 | def errors 30 | errors = [] 31 | 32 | unless (0..7).include?(dlength) 33 | errors << "invalid length (#{dlength} != 0-7)" 34 | end 35 | 36 | if data.size > 7 37 | errors << "excessive data length (#{data.size} > 7)" 38 | end 39 | 40 | errors 41 | end 42 | end 43 | 44 | # First frame of multi-frame packet 45 | 46 | class First < BitStruct 47 | unsigned :header, 4, :default => 1 48 | 49 | ## 50 | # :attr_accessor: dlength 51 | # total data length Fixnum, 12 bits) 52 | unsigned :dlength, 12, 'total data length' 53 | 54 | ## 55 | # :attr_accessor: data 56 | # frame data (binary) 57 | rest :data 58 | 59 | def errors 60 | errors = [] 61 | 62 | unless (8..0xfff).include?(dlength) 63 | errors << "invalid length (#{dlength} != 8-4095)" 64 | end 65 | 66 | if data.size > 6 67 | errors << "excessive data length (#{data.size} > 6)" 68 | end 69 | 70 | errors 71 | end 72 | end 73 | 74 | # Subsequent data of a multi-frame packet 75 | 76 | class Consecutive < BitStruct 77 | unsigned :header, 4, :default => 2 78 | 79 | ## 80 | # :attr_accessor: index 81 | # consecutive packet index (Fixnum, 4 bits) 82 | unsigned :dindex, 4, 'index' 83 | 84 | ## 85 | # :attr_accessor: data 86 | # frame data (binary) 87 | rest :data 88 | 89 | def errors 90 | errors = [] 91 | 92 | if dindex > 15 93 | errors << "invalid index (#{dindex} > 15)" 94 | end 95 | 96 | if data.size > 7 97 | errors << "excessive data length (#{data.size} > 7)" 98 | end 99 | 100 | errors 101 | end 102 | end 103 | 104 | # Flow control packet 105 | 106 | class Flow < BitStruct 107 | unsigned :header, 4, :default => 3 108 | 109 | ## 110 | # :attr_accessor: fc 111 | # flow control flag (Fixnum, 4 bits) 112 | unsigned :fc, 4, 'FC flag' 113 | 114 | ## 115 | # :attr_accessor: block_size 116 | # block size (Fixnum, 1 byte) 117 | unsigned :block_size, 8, 'block size' 118 | 119 | ## 120 | # :attr_accessor: st 121 | # separation time (Fixnum, 1 byte) 122 | unsigned :st, 8, 'ST' 123 | 124 | def errors 125 | errors = [] 126 | 127 | if fc > 2 128 | errors << "invalid FC flag (#{dindex} > 2)" 129 | end 130 | 131 | errors 132 | end 133 | end 134 | 135 | # Split data in one ore more ISO-TP segments. 136 | # 137 | # arguments: 138 | # count frame count (optional) 139 | # 140 | # return values: 141 | 142 | def self.split(data) 143 | packets = [] 144 | dlength = data.size 145 | 146 | if dlength <= 7 147 | packets << Single.new(:dlength => dlength, :data => data) 148 | elsif dlength <= 0xfff 149 | index = 1 150 | packets << First.new(:dlength => dlength, :data => data[0..5]) 151 | 152 | data[6..-1].scan(/.{1,7}/).each do |segment| 153 | packets << Consecutive.new(:dindex => index % 16, :data => segment) 154 | index += 1 155 | end 156 | else 157 | raise "invalid length (#{dlength} != 0-4095)" 158 | end 159 | 160 | packets 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/serial_can_bus/response.rb: -------------------------------------------------------------------------------- 1 | # CANUSB ASCII command response definitions 2 | # (http://www.canusb.com/docs/canusb_manual.pdf) 3 | # 4 | # All objects are BitStruct ones, you can use describe() class method for 5 | # fields documentation (e.g. puts SerialCanBus::Response::Transmit.describe). 6 | # 7 | # available responses: 8 | # SerialCanBus::Response::AcceptanceCode 9 | # SerialCanBus::Response::AcceptanceMask 10 | # SerialCanBus::Response::BtrSetup 11 | # SerialCanBus::Response::CloseChannel 12 | # SerialCanBus::Response::GetSerial 13 | # SerialCanBus::Response::GetVersion 14 | # SerialCanBus::Response::OpenChannel 15 | # SerialCanBus::Response::StandardSetup 16 | # SerialCanBus::Response::StatusFlag 17 | # SerialCanBus::Response::Transmit 18 | 19 | class SerialCanBus::Response 20 | class Simple < BitStruct #:nodoc: 21 | unsigned :return_code, 1*8, 'return code' 22 | end 23 | 24 | class GetSerial < BitStruct 25 | string :cmd, 1*8, 'command' 26 | 27 | ## 28 | # :attr_accessor: serial 29 | # serial number (String, 4 bytes) 30 | string :serial, 4*8, 'serial number' 31 | end 32 | 33 | class GetVersion < BitStruct 34 | string :cmd, 1*8, 'command' 35 | 36 | ## 37 | # :attr_accessor: hwv 38 | # hardware revision (String, 2 bytes) 39 | string :hwv, 2*8, 'hardware revision' 40 | 41 | ## 42 | # :attr_accessor: swv 43 | # software revision (String, 2 bytes) 44 | string :swv, 2*8, 'software revision' 45 | end 46 | 47 | # Provides decoding for the ASCII status_flag returned by the respective 48 | # Request. 49 | 50 | class StatusFlag < BitStruct 51 | string :cmd, 1*8, 'command' 52 | 53 | ## 54 | # :attr_accessor: status_flag 55 | # status flag (String, 2 bytes) 56 | string :status_flag, 2*8 57 | 58 | # CAN receive FIFO queue full (TrueClass or FalseClass) 59 | def rx_queue_full 60 | !((status_flag.to_i(16) >> 7) & 1).zero? 61 | end 62 | 63 | # CAN transmit FIFO queue full (TrueClass or FalseClass) 64 | def tx_queue_full 65 | !((status_flag.to_i(16) >> 6) & 1).zero? 66 | end 67 | 68 | # error warning (TrueClass or FalseClass) 69 | def error_warning 70 | !((status_flag.to_i(16) >> 5) & 1).zero? 71 | end 72 | 73 | # data overrun (TrueClass or FalseClass) 74 | def data_overrun 75 | !((status_flag.to_i(16) >> 4) & 1).zero? 76 | end 77 | 78 | # error passive (TrueClass or FalseClass) 79 | def error_passive 80 | !((status_flag.to_i(16) >> 2) & 1).zero? 81 | end 82 | 83 | # arbitration lost (TrueClass or FalseClass) 84 | def arbitration_lost 85 | !((status_flag.to_i(16) >> 1) & 1).zero? 86 | end 87 | 88 | # bus error (TrueClass or FalseClass) 89 | def bus_error 90 | !((status_flag.to_i(16) >> 0) & 1).zero? 91 | end 92 | 93 | # dump a Hash with all available information 94 | def dump 95 | { :status_flag => status_flag, 96 | :rx_queue_full => rx_queue_full, 97 | :tx_queue_full => tx_queue_full, 98 | :error_warning => error_warning, 99 | :data_overrun => data_overrun, 100 | :error_passive => error_passive, 101 | :arbitration_lost => arbitration_lost, 102 | :bus_error => bus_error 103 | } 104 | end 105 | end 106 | 107 | class AcceptanceCode < Simple 108 | ## 109 | # :attr_accessor: return_code 110 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 111 | end 112 | 113 | class AcceptanceMask < Simple 114 | ## 115 | # :attr_accessor: return_code 116 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 117 | end 118 | 119 | class BtrSetup < Simple 120 | ## 121 | # :attr_accessor: return_code 122 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 123 | end 124 | 125 | class CloseChannel < Simple 126 | ## 127 | # :attr_accessor: return_code 128 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 129 | end 130 | 131 | class OpenChannel < Simple 132 | ## 133 | # :attr_accessor: return_code 134 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 135 | end 136 | 137 | class StandardSetup < Simple 138 | ## 139 | # :attr_accessor: return_code 140 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 141 | end 142 | 143 | class Transmit < Simple 144 | ## 145 | # :attr_accessor: return_code 146 | # return code (Fixnum, 1 byte), see SerialCanBus RETURN_CODE constant 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /lib/serial_can_bus/request.rb: -------------------------------------------------------------------------------- 1 | # CANUSB ASCII command request definitions 2 | # (http://www.canusb.com/docs/canusb_manual.pdf) 3 | # 4 | # All objects are BitStruct ones, you can use describe() class method for 5 | # fields documentation (e.g. puts SerialCanBus::Request::Transmit.describe). 6 | # 7 | # available requests: 8 | # SerialCanBus::Request::AcceptanceCode 9 | # SerialCanBus::Request::AcceptanceMask 10 | # SerialCanBus::Request::BtrSetup 11 | # SerialCanBus::Request::CloseChannel 12 | # SerialCanBus::Request::GetSerial 13 | # SerialCanBus::Request::GetVersion 14 | # SerialCanBus::Request::OpenChannel 15 | # SerialCanBus::Request::StandardSetup 16 | # SerialCanBus::Request::StatusFlag 17 | # SerialCanBus::Request::Transmit 18 | 19 | class SerialCanBus::Request 20 | class Simple < BitStruct #:nodoc: 21 | string :cmd, 1*8, 'command' 22 | end 23 | 24 | # Get adapter serial number. 25 | class GetSerial < Simple 26 | initial_value.cmd = 'N' 27 | end 28 | 29 | # Get adapter version number. 30 | class GetVersion < Simple 31 | initial_value.cmd = 'V' 32 | end 33 | 34 | # Open CAN bus channel. 35 | class OpenChannel < Simple 36 | initial_value.cmd = 'O' 37 | end 38 | 39 | # Close CAN bus channel. 40 | class CloseChannel < Simple 41 | initial_value.cmd = 'C' 42 | end 43 | 44 | # Request Status Flag. 45 | class StatusFlag < Simple 46 | initial_value.cmd = 'F' 47 | end 48 | 49 | # Transmit frame. 50 | class Transmit < BitStruct 51 | ## 52 | # :attr_accessor: data 53 | # frame data (binary) 54 | rest :frame 55 | end 56 | 57 | # Setup predefined CAN bus speeds. 58 | 59 | class StandardSetup < BitStruct 60 | # Standard values for predefined CAN bit-rates. 61 | 62 | BITRATE = { 1000000 => '8', 63 | 800000 => '7', 64 | 500000 => '6', 65 | 250000 => '5', 66 | 125000 => '4', 67 | 100000 => '3', 68 | 50000 => '2', 69 | 20000 => '1', 70 | 10000 => '0' } 71 | 72 | string :cmd, 1*8, 'command', :default => 'S' 73 | 74 | ## 75 | # :attr_accessor: value 76 | # bitrate (String, 1 bytes), see BITRATE constant and bitrate=() for 77 | # automatic conversion from Fixnum 78 | string :value, 1*8, 'command', :default => BITRATE[125000] 79 | 80 | def bitrate=(bitrate) 81 | unless BITRATE[bitrate] 82 | raise 'unsupported bitrate' 83 | end 84 | 85 | self.value = BITRATE[bitrate] 86 | end 87 | end 88 | 89 | # Setup arbitrary CAN bus speeds. 90 | 91 | class BtrSetup < BitStruct 92 | # The following formula shows how the SJA1000 clock speed, Baud Rate 93 | # Prescaler and Time Segment registers are used to calculate the bitrate. 94 | # 95 | # bitrate = 16000000 / (2 * (BRP.x + 1) * (3 + TSEG1.x + TSEG2.x)) 96 | # 97 | # The BITRATE hash includes valid BTR0/BTR1 values, for the SJA1000 of the 98 | # CANUSB adapter, corresponding to each bitrate. 99 | 100 | BITRATE = { 1000000 => 0x4014, 101 | 800000 => 0x4016, 102 | 500000 => 0x401c, 103 | 250000 => 0x411c, 104 | 125000 => 0x431c, 105 | 100000 => 0x441c, 106 | 50000 => 0x491c, 107 | 20000 => 0x581c, 108 | 10000 => 0x711c } 109 | 110 | string :cmd, 1*8, 'command', :default => 's' 111 | 112 | ## 113 | # :attr_accessor: value 114 | # bitrate (String, 4 bytes), see BITRATE constant and bitrate=() for 115 | # automatic conversion from Fixnum 116 | string :value, 4*8, 'command', :default => BITRATE[125000] 117 | 118 | def bitrate=(bitrate) 119 | unless BITRATE[bitrate] 120 | raise 'unsupported bitrate' 121 | end 122 | 123 | self.value = BITRATE[bitrate].to_s(16).rjust(4, '0').upcase 124 | end 125 | end 126 | 127 | # Configure Acceptance Mask (AMn register of SJA1000). 128 | 129 | class AcceptanceMask < BitStruct 130 | string :cmd, 1*8, 'command', :default => 'm' 131 | 132 | ## 133 | # :attr_accessor: value 134 | # acceptance mask (String, 8 bytes), receive all frames by default, see 135 | # mask=() for automatic conversion from Bignum 136 | string :value, 8*8, 'command', :default => 'FFFFFFFF' 137 | 138 | def mask=(mask) 139 | self.value = mask.to_s(16).rjust(8, '0').upcase 140 | end 141 | end 142 | 143 | # Configure Acceptance Code (ACn register of SJA1000). 144 | 145 | class AcceptanceCode < BitStruct 146 | string :cmd, 1*8, 'command', :default => 'M' 147 | 148 | ## 149 | # :attr_accessor: value 150 | # acceptance code (String, 8 bytes), receive all frames by default, see 151 | # code=() for automatic conversion from Bignum 152 | string :value, 8*8, 'command', :default => '00000000' 153 | 154 | def code=(code) 155 | self.value = code.to_s(16).rjust(8, '0').upcase 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/serial_can_bus/serial_can_bus.rb: -------------------------------------------------------------------------------- 1 | require 'bit-struct' 2 | require 'serialport' 3 | 4 | # A simple implementation of the LAWICEL ASCII protocol for LAWICEL serial CAN 5 | # bus adapters, tested with CANUSB though it should work with minimal or null 6 | # effort on CAN232 adapters (both available at http://www.can232.com). 7 | # 8 | # This class of devices is covered by the slcan driver in the Linux kernel, 9 | # this Ruby implementation however does not require such driver and allows full 10 | # interaction with the adapters as long as the serial port is available. 11 | # 12 | # - Examples 13 | # 14 | # initialization: 15 | # slcan = SerialCanBus.new('/dev/ttyUSB0', 19200, 125000) 16 | # 17 | # transmission of standard CAN frame with identifier 0x7ff and 2 bytes of data: 18 | # slcan.transmit_frame(:standard, 0x7ff, 2, 0xbeef) 19 | # 20 | # sniff first 20 frames: 21 | # slcan.while_receiving(20) do |kind, identifier, length, data| 22 | # puts "identifier #{identifier.to_s(16)} data #{data.unpack('H*')}" 23 | # end 24 | # 25 | # get adapter status: 26 | # puts slcan.issue_command(:status_flag).dump 27 | # 28 | # Andrea Barisani | github.com/inversepath/SerialCanBus 29 | 30 | class SerialCanBus 31 | 32 | # SerialPort object created during initialization. 33 | attr_accessor :serial 34 | 35 | # The RETURN CODE hash matches the LAWICEL ASCII protocol values returned by 36 | # transmit_frame() and certain Response types. 37 | 38 | RETURN_CODE = { 0x07 => :error, 39 | 0x5a => :ok, # Z 40 | 0x7a => :ok # z 41 | } 42 | 43 | # Standard CAN frame with 11-bit identifier. 44 | 45 | class StandardFrame < BitStruct 46 | string :kind, 1*8, 'command - frame type', :default => 't' 47 | 48 | ## 49 | # :attr_accessor: identifier 50 | # 11-bit identifier (String, 3 bytes) 51 | string :identifier, 3*8, '11-bit identifier' 52 | 53 | ## 54 | # :attr_accessor: dlength 55 | # data length (String, 1 byte) 56 | string :dlength, 1*8, 'data_length' 57 | 58 | ## 59 | # :attr_accessor: data 60 | # frame data (binary) 61 | rest :data 62 | 63 | def errors 64 | errors = [] 65 | 66 | unless (0..8).include?(dlength.to_i) 67 | errors << "invalid length (#{dlength} != 0-8)" 68 | end 69 | 70 | if data.size > 8 * 2 71 | errors << "excessive data length (#{data.size / 2} > 8)" 72 | end 73 | 74 | errors 75 | end 76 | end 77 | 78 | # Extended CAN frame with 29-bit identifier. 79 | 80 | class ExtendedFrame < BitStruct 81 | string :kind, 1*8, 'command - frame type', :default => 'T' 82 | 83 | ## 84 | # :attr_accessor: identifier 85 | # 29-bit identifier (String, 8 bytes) 86 | string :identifier, 8*8, '29-bit identifier' 87 | 88 | ## 89 | # :attr_accessor: dlength 90 | # data length (String, 1 byte) 91 | string :dlength, 1*8, 'data_length' 92 | 93 | ## 94 | # :attr_accessor: data 95 | # frame data (binary) 96 | rest :data 97 | 98 | def errors 99 | errors = [] 100 | 101 | unless (0..8).include?(dlength.to_i) 102 | errors << "invalid length (#{dlength} != 0-8)" 103 | end 104 | 105 | if data.size > 8 * 2 106 | errors << "excessive data length (#{data.size / 2} > 8)" 107 | end 108 | 109 | errors 110 | end 111 | end 112 | 113 | # Initialize the serial adapter. Returns a SerialPort object. 114 | # 115 | # The initialization automatically opens the CAN bus channel, it can be 116 | # manually closed with issue_command(). It is recommended to close the 117 | # channel as soon as possible when not receiving frames to avoid filling the 118 | # adapter buffer, which prevents successful request transmission. 119 | # 120 | # example: 121 | # slcan = SerialCanBus.new('/dev/ttyUSB0', 19200, 125000, 0x0, 0xffffffff) 122 | # 123 | # arguments: 124 | # device character device for the adapter serial port (String) 125 | # speed serial port baud rate (Fixnum) 126 | # bitrate desired can bus bitrate (Fixnum) 127 | # mask CAN acceptance mask register (Bignum) 128 | # code CAN acceptance code register (Bignum) 129 | 130 | def initialize(device = '/dev/ttyUSB0', speed = 19200, bitrate = 125000, mask = 0xffffffff, code = 0x0) 131 | responses = [] 132 | @serial = SerialPort.new(device, speed) 133 | 134 | issue_command(:close_channel) 135 | 136 | responses << issue_command(:standard_setup, { :bitrate => bitrate }) 137 | responses << issue_command(:acceptance_mask, { :mask => mask }) 138 | responses << issue_command(:acceptance_code, { :code => code }) 139 | responses << issue_command(:open_channel) 140 | 141 | responses.each do |response| 142 | if !response or response.return_code != 13 # CR 143 | raise "initialization failed (#{response.class} returned (#{response.return_code}), please reset adapter" 144 | end 145 | end 146 | 147 | @serial 148 | end 149 | 150 | # Issue a command to the serial CAN adapter. Returns a SerialCanBus::Response 151 | # object for the specific subclass of the issued command. 152 | # 153 | # example: 154 | # response = slcan.issue_command(:btr_setup, { :bitrate => 125000 }) 155 | # or alternatively: 156 | # response = slcan.issue_command(SerialCanBus::Request::BtrSetup.new(:bitrate => 125000)) 157 | # 158 | # arguments: 159 | # command Symbol for the command name or request instance 160 | # options command attributes (when command is a Symbol) 161 | # 162 | # return value: 163 | # response SerialCanBus::Response subclass 164 | # 165 | # available command symbols and request classes: 166 | # :acceptance_code (SerialCanBus::Request::AcceptanceCode) 167 | # :acceptance_mask (SerialCanBus::Request::AcceptanceMask) 168 | # :btr_setup (SerialCanBus::Request::BtrSetup) 169 | # :close_channel (SerialCanBus::Request::CloseChannel) 170 | # :get_serial (SerialCanBus::Request::GetSerial) 171 | # :get_version (SerialCanBus::Request::GetVersion) 172 | # :open_channel (SerialCanBus::Request::OpenChannel) 173 | # :standard_setup (SerialCanBus::Request::StandardSetup) 174 | # :status_flag (SerialCanBus::Request::StatusFlag) 175 | # :transmit (SerialCanBus::Request::Transmit) 176 | 177 | def issue_command(command, options = {}) 178 | begin 179 | case command 180 | when Symbol 181 | command = command.to_s.split('_').map { |s| s.capitalize }.join 182 | request = Request.const_get(command).new(options) 183 | response = Response.const_get(command) 184 | when Object 185 | request = command 186 | response = Response.const_get(request.class.to_s.split('::').last) 187 | end 188 | rescue Exception => e 189 | raise "invalid command: #{command}, #{e.message}" 190 | end 191 | 192 | @serial.write(request) 193 | @serial.write("\r") 194 | 195 | response.new(@serial.read(response.new.size)) 196 | end 197 | 198 | # Transmit a CAN frame. Returns a SerialCanBus::Response::Transmit object. It 199 | # is a wrapper to SerialCanBus::Request::Transmit which can be used directly 200 | # with issue_command() and the respective frame kind class. 201 | # 202 | # When transmitting frames it is desirable to initialize the adapter with 203 | # acceptance mask and code that prevent packet reception (inverting 204 | # defaults), this helps performance and cuts noise on the serial channel. 205 | # 206 | # 207 | # example: 208 | # response = slcan.transmit_frame(:standard, 0x7ff, 2, 0xbeef) 209 | # 210 | # arguments: 211 | # kind Symbol for the frame type (:standard or :extended) 212 | # length frame length (0-8) 213 | # identifier CAN identifier (11-bit for standard, 29-bit for extended) 214 | # data frame data (e.g. 0x61616161, "aaaa") 215 | # 216 | # return value: 217 | # response SerialCanBus::Response::Transmit 218 | 219 | def transmit_frame(kind = :standard, identifier = 0, length = 0, frame_data = 0) 220 | case frame_data 221 | when String 222 | data = frame_data.bytes.map { |b| b.to_s(16).rjust(2, '0') }.join.rjust(length * 2, '0') 223 | else 224 | data = frame_data.to_s(16).rjust(length * 2, '0') 225 | end 226 | 227 | case kind 228 | when :standard 229 | identifier = (identifier & 0x7ff).to_s(16).rjust(3, '0') 230 | frame = StandardFrame.new(:identifier => identifier, :data => data, :dlength => length.to_s) 231 | when :extended 232 | identifier = (identifier & 0x1fffffff).to_s(16).rjust(8, '0') 233 | frame = ExtendedFrame.new(:identifier => identifier, :data => data, :dlength => length.to_s) 234 | else 235 | raise 'invalid frame kind' 236 | end 237 | 238 | if frame.errors.any? 239 | raise "invalid frame: #{frame.errors.join("\n")}" 240 | else 241 | response = issue_command(:transmit, { :frame => frame }) 242 | end 243 | 244 | @serial.read(1) if RETURN_CODE[response.return_code] == :ok 245 | 246 | response 247 | end 248 | 249 | # Wrapper that yields received frames to the invoking block. Returns frame 250 | # elements. 251 | # 252 | # The wrapper does not close the CAN bus channel on exit, this can be 253 | # performed manually with issue_command(). It is recommended to close the 254 | # channel as soon as possible when not receiving frames to avoid filling the 255 | # adapter buffer, which prevents successful request transmission. 256 | # 257 | # example (inspect data of first 100 received frames): 258 | # slcan.while_receiving(100) do |kind, identifier, length, data| 259 | # puts "identifier #{identifier.to_s(16)} data #{data.unpack('H*')}" 260 | # end 261 | # 262 | # arguments: 263 | # count frame count (optional) 264 | # 265 | # return values: 266 | # kind Symbol for the frame type (:standard or :extended) 267 | # identifier CAN identifier (Fixnum) 268 | # length frame length (Fixnum) 269 | # data frame data (String) 270 | 271 | def while_receiving(count = nil, &block) 272 | n = 0 273 | 274 | loop do 275 | kind = @serial.read(1) 276 | 277 | case kind 278 | when /t/ 279 | kind = :standard 280 | identifier = @serial.read(3).to_i(16) 281 | length = @serial.read(1).to_i(16) 282 | when /T/ 283 | kind = :extended 284 | identifier = @serial.read(8).to_i(16) 285 | length = @serial.read(1).to_i(16) 286 | when /\r/, "\x7f" 287 | next 288 | else 289 | raise "spurious frame kind value: #{kind.inspect}" 290 | end 291 | 292 | next unless length 293 | 294 | data = @serial.read(length * 2) 295 | data = data.scan(/../).map { |x| x.to_i(16).chr }.join() 296 | 297 | yield [kind, identifier, length, data] 298 | 299 | if @serial.read(1) != "\r" 300 | raise 'invalid data (expected CR) aborting' 301 | end 302 | 303 | n = n.next 304 | 305 | break if count and n >= count 306 | end 307 | end 308 | end 309 | --------------------------------------------------------------------------------