Module: BitFields

Extended by:
InstanceMethods
Defined in:
lib/bit_fields.rb

Overview

BitFields provides a simple way to extract a values from bit fields, especially if they don’t correspond to standard sizes (such as char, int, long, etc., see String#unpack for further informations).

For example the Primary Header of the Telemetry Frame in the ESA PSS Standard has this specification:

            |                          TRANSFER FRAME PRIMARY HEADER                              |
 ___________|_____________________________________________________________________________________|
|SYNC MARKER| FRAME IDENTIFICATION | MASTER  | VIRTUAL |           FRAME DATA FIELD STATUS        |
|           |----------------------| CHANNEL | CHANNEL |------------------------------------------|
|           |Ver. |S/C.|Virt.|Op.Co| FRAME   | FRAME   |2nd head.|Sync|Pkt ord|Seg.  |First Header|
|           | no. | ID | Chn.| Flag| COUNT   | COUNT   |  flag   |flag|flag   |len.ID|  Pointer   |
|           |_____|____|_____|_____|         |         |_________|____|_______|______|____________|
|           |  2  | 10 |  3  |  1  |         |         |    1    |  1 |   1   |   2  |     11     |
|-----------|----------------------|---------|---------|------------------------------------------|
|    32     |          16          |    8    |    8    |                   16                     |

Will become:

class PrimaryHeader
  include BitFields
  field :frame_identification, 'n' do
    bit_field :version,                  2
    bit_field :spacecraft_id,           10
    bit_field :virtual_channel,          3
    bit_field :op_control_field_flag,    2
  end

  field :master_channel_frame_count
  field :virtual_channel_frame_count

  field :frame_data_field_status, 'n' do
    bit_field :secondary_header_flag,    1
    bit_field :sync_flag,                1
    bit_field :packet_order_flag,        1
    bit_field :segment_length_id,        2
    bit_field :first_header_pointer,    11
  end
end

And can be used like:

packed_ph = [0b10100111_11111111, 11, 23, 0b10100111_11111111].pack('nCCn') # => "\247\377\v\027\247\377"

ph = PrimaryHeader.new packed_ph

ph.virtual_channel_frame_count    # => 23
ph.secondary_header_flag          # => 0b1
ph.sync_flag                      # => 0b0
ph.first_header_pointer           # => 0b111_11111111

ph[:first_header_pointer]         # => 0b111_11111111

Defined Under Namespace

Modules: InstanceMethods

Instance Attribute Summary collapse

Attributes included from InstanceMethods

#attributes, #raw, #unpacked

Instance Method Summary collapse

Methods included from InstanceMethods

[], initialize, method_missing, pack_bit_fields, to_s

Instance Attribute Details

#bit_fieldsObject (readonly)

Collects the bit_fields definitions for later parsing



69
70
71
# File 'lib/bit_fields.rb', line 69

def bit_fields
  @bit_fields
end

#fieldsObject (readonly)

Collects the fields definitions for later parsing



66
67
68
# File 'lib/bit_fields.rb', line 66

def fields
  @fields
end

#unpack_recipeObject (readonly)

Collects the full String#unpack directive used to parse the raw value.



72
73
74
# File 'lib/bit_fields.rb', line 72

def unpack_recipe
  @unpack_recipe
end

Instance Method Details

#bit_field(name, width) ⇒ Object

Defines a bit field to be extracted from a field

name

the name of the bit field (that will be used to access it)

width

the number of bits from which this value should be extracted

TODO: add options to enable method aliases TODO: add a skip bits syntax



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/bit_fields.rb', line 126

def bit_field name, width
  raise "'bit_field' can be used only inside a 'field' block." if @_current_bit_fields.nil?
  
  # Register the bit field definition
  @_current_bit_fields << [name, width, bit_mask(width)]
  
  # Define the attribute accessor
  bits_attr_accessor(name)
  
  if  width == 1 or name.to_s =~ /_flag$/
    # Define a question mark method if the size is 1 bit
    class_eval "def #{name}?; self.attributes[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
    
    # returns nil if no substitution happens...
    if flag_method_name = name.to_s.gsub!(/_flag$/, '?')
      # Define another question mark method if ends with "_flag"
      class_eval "alias #{flag_method_name} #{name}?\n", __FILE__, __LINE__
    end
  end
  
end

#bit_mask(size) ⇒ Object



148
149
150
# File 'lib/bit_fields.rb', line 148

def bit_mask size
  2 ** size - 1
end

#bits_attr_accessor(name) ⇒ Object

Defines accessors for the attributes hash



75
76
77
78
# File 'lib/bit_fields.rb', line 75

def bits_attr_accessor name
  class_eval "def #{name};       self.attributes[#{name.inspect}];     end;", __FILE__, __LINE__
  class_eval "def #{name}=(val); self.attributes[#{name.inspect}]=val; end;", __FILE__, __LINE__
end

#field(name, unpack_recipe = 'C', &bit_fields_definitions_block) ⇒ Object

Defines a field to be extracted with String#unpack from the raw value

name

the name of the field (that will be used to access it)

unpack_recipe

the String#unpack directive corresponding to this field (optional, defaults to char: “C”)

bit_fields_definitions_block

the block in which bit_fields can be defined (optional)

Also defines the attribute reader method

TODO: add a skip bits syntax



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
# File 'lib/bit_fields.rb', line 91

def field name, unpack_recipe = 'C', &bit_fields_definitions_block
  include InstanceMethods # when used we include instance methods
  
  # Setup class "instance" vars
  @fields        ||= []
  @bit_fields    ||= {}
  @unpack_recipe ||= ""
  
  # Register the field definition
  @unpack_recipe << unpack_recipe
  @fields        << name
  
  # Define the attribute accessor
  bits_attr_accessor(name)
  
  # There's a bit-structure too?
  if block_given?
    @_current_bit_fields = []
    
    bit_fields_definitions_block.call
    
    @bit_fields[name] = @_current_bit_fields
    @_current_bit_fields = nil
  end
end