Class: CycleAnalystLogger::CycleAnalyst

Inherits:
Object
  • Object
show all
Defined in:
lib/cycle_analyst_logger/cycle_analyst.rb

Constant Summary collapse

CA_DICT =

Hash definition that describes the names, values and units of the Cycle Analyst log data

{
  0 => {address: 0, name: "Amp Hours", units: "Ah", scale: 1},
  1 => { address: 1, name: "Volts", units: "V", scale: 1 },
  2 => { address: 2, name: "Current", units: "A", scale: 1},
  3 => { address: 3, name: "Speed", units: "Mph", scale: 1},
  4 => { address: 4, name: "Distance", units: "Miles", scale: 1},
  5 => { address: 5, name: "Motor Temp", units: "DegC", scale: 1},
  6 => { address: 6, name: "Human Cadence", units: "RPM", scale: 1},
  7 => { address: 7, name: "Human Power", units: "W", scale: 1},
  8 => { address: 8, name: "Human Torque", units: "Nm", scale: 1},
  9 => { address: 9, name: "Throttle In", units: "V", scale: 1},
  10 => { address: 10, name: "Throttle Out", units: "V", scale: 1},
  11 => { address: 11, name:  "AuxA", units: "", scale: 1},
  12 => { address: 11, name:  "AuxD", units: "", scale: 1},
  13 => { address: 12, name: "Limit Flags", units: "bit flags", scale: 1}
}
CA_STD_HEADER =

CA_STD_HEADER

%w(Ah V A S D Deg RPM HW Nm ThI ThO AuxA AuxD Flgs)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ CycleAnalyst

CycleAnalyst New



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 62

def initialize(opts)
  @baudrate = opts[:baud_ca]
  @tty = opts[:tty_ca]
  @dict = CA_DICT
  @serial_io = SerialPort.new @tty, @baudrate, 8, 1
  @enable_phaserunner = opts[:enable_phaserunner]

  if @enable_phaserunner
    @phaserunner = Phaserunner::Modbus.new(
      tty: opts[:tty_pr], baudrate: opts[:baud_pr]
    )
  end

  @enable_gps = opts[:enable_gps]

  if @enable_gps
    @gps_data = {}
    @gps = Gps.new(@gps_data, {tty: opts[:tty_gps], baudrate: opts[:baud_gps]})
  end
end

Instance Attribute Details

#baudrateObject (readonly)

Cycle Analyst serial port Baudrate



14
15
16
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 14

def baudrate
  @baudrate
end

#dictObject (readonly)

Hash that describes the names, values and units of the Cycle Analyst log data



20
21
22
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 20

def dict
  @dict
end

#enable_gpsObject (readonly)

If the gps should be read



32
33
34
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 32

def enable_gps
  @enable_gps
end

#enable_phaserunnerObject (readonly)

If the phaserunner should be read



26
27
28
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 26

def enable_phaserunner
  @enable_phaserunner
end

#gpsObject (readonly)

Handle of the Gps object



35
36
37
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 35

def gps
  @gps
end

#gps_dataObject (readonly)

Shared data for gps data



38
39
40
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 38

def gps_data
  @gps_data
end

#phaserunnerObject (readonly)

Handle of the Phaserunner::Modbus object



29
30
31
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 29

def phaserunner
  @phaserunner
end

#serial_ioObject (readonly)

Handle from the SerialPort object



23
24
25
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 23

def serial_io
  @serial_io
end

#ttyObject (readonly)

Cycle Analyst serial port name



17
18
19
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 17

def tty
  @tty
end

Class Method Details

.log_to_ca_file(log_filename) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 166

def self.log_to_ca_file(log_filename)
  output_filename = log_filename.sub(
    /cycle_analyst\.(\d\d\d\d\-\d\d\-\d\d_\d\d\-\d\d\-\d\d).csv/,
    'CALog_\1.txt'
  )
  out_fd = File.open(output_filename, 'w')

  File.readlines(log_filename).each.with_index do |log_line, idx|
    log_record = log_line.strip.split(',')
    if idx == 0
      out_fd.puts CA_STD_HEADER.join("\t")
      next
    end

    log_end = CA_STD_HEADER.length
    log_segment = log_record[1..log_end]
    out_fd.puts log_segment.join("\t") if validate_log_record(log_record)
  end
end

.valid_timestamp(input) ⇒ Object



148
149
150
151
152
153
154
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 148

def self.valid_timestamp(input)
  begin
    Time.parse(input)
  rescue ArgumentError
    nil
  end
end

.validate_log_record(log_record) ⇒ Object

Very basic test that the record is valid



157
158
159
160
161
162
163
164
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 157

def self.validate_log_record(log_record)
  return false unless valid_timestamp log_record[0]
  log_record.each.with_index do |element, idx|
    next if [0,14,29,38].any? { |i| idx == i } #Skip Timestamps and CA Limit value
    return false unless element.is_number? || element.empty?
  end
  true
end

Instance Method Details

#get_logs(loop_count, quiet, disable_nmea_out) ⇒ Object

Get line from Cycle Analyst serial port, optionally also the Phaserunner and send to stdout and file

Parameters:

  • output_fd (File)

    File Descriptor of the output file to write to. Don’t write to file if nil

  • loop_count (Integer, Symbol)

    Number of lines to output, or forever if :forever

  • quite (Boolean)

    Don’t output to stdout if true



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
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 109

def get_logs(loop_count, quiet, disable_nmea_out)
  timestamp = Time.now.strftime('%Y-%m-%d_%H-%M-%S')
  filename = "cycle_analyst-v#{VERSION}-#{timestamp}.csv"
  output_fd = File.open(filename, 'w')

  if enable_gps
    unless disable_nmea_out
      nmea_filename = "nmea.#{timestamp}.txt"
      nmea_fd = File.open(nmea_filename, 'w')
    else
      nmea_fd = nil
    end
    gps_thread = Thread.new { gps.run(nmea_fd, disable_nmea_out) }
  end

  line_number = 0
  hdr = %Q(Timestamp,#{log_header})

  puts hdr if not quiet
  output_fd.puts hdr if output_fd

  serial_io.each_line.with_index do |line, idx|
    output = (
      [Time.now.utc.round(10).iso8601(6)] +
      tsv2array(line)
    )

    output += phaserunner.bulk_log_data if enable_phaserunner
    output += gps.log_data if enable_gps

    output_line = output.flatten.join(',')

    puts output_line unless quiet
    output_fd.puts output_line if output_fd

    break if idx >= loop_count
  end
end

#log_headerString

Forms the proper header line

Returns:

  • (String)

    of a printable CSV header line



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 85

def log_header
  hdr = dict.map do |(address, node)|
    "#{node[:name]} (#{node[:units]})"
  end
  if enable_phaserunner
    hdr += phaserunner.bulk_log_header.map { |name| "PR #{name}" }
  end

  if enable_gps
    hdr += gps.log_header
  end

  hdr.join(',')
end

#tsv2array(line) ⇒ Object

Converts a TSV string into an array



101
102
103
# File 'lib/cycle_analyst_logger/cycle_analyst.rb', line 101

def tsv2array(line)
  line.strip.split("\t")
end