Skip to content

Instantly share code, notes, and snippets.

@bradland
Last active May 13, 2020
Embed
What would you like to do?
Beginnings of a rudimentary CLI telemetry utility for Assetto Corsa written in Ruby.
#!/usr/bin/env ruby
$: << './lib'
require 'socket'
require 'bindata'
require 'json'
class UTF8String < BinData::String
def snapshot
super.force_encoding('UTF-8')
end
end
class BinData::String
def snapshot
super.force_encoding('UTF-16LE')
end
end
class Handshaker < BinData::Record
int32le :identifier
int32le :version
int32le :operation_id
end
class HandshakerResponse < BinData::Record
string :car_name, read_length: 100
string :driver_name, read_length: 100
int32le :identifier
int32le :version
string :track_name, read_length: 100
string :track_config, read_length: 100
end
class RTCarInfo < BinData::Record
string :identifier, read_length: 4, trim_padding: true
int32le :rt_size
float_le :speed_kmh
float_le :speed_mph
float_le :speed_ms
int8 :is_abs_enabled
int8 :is_abs_in_action
int8 :is_tc_in_action
int8 :is_tc_enabled
int8 :is_in_pit
int8 :is_engine_limiter_on
float_le :acc_g_vertical
float_le :acc_g_horizontal
float_le :acc_g_frontal
int32le :lap_time
int32le :last_lap
int32le :best_lap
int32le :lap_count
float_le :gas
float_le :brake
float_le :clutch
float_le :engine_rpm
float_le :steer
int32le :gear
float_le :cg_height
float_le :wheel_angular_speed
float_le :slip_angle
float_le :slip_angle_contact_patch
float_le :slip_ratio
float_le :tyre_slip
float_le :nd_slip
float_le :load
float_le :dy
float_le :mz
float_le :tyre_dirty_level
float_le :camber_rad
float_le :tyre_radius
float_le :tyre_loaded_radius
float_le :suspension_height
float_le :car_position_normalized
float_le :car_slope
float_le :car_coordinates
end
class RTLap < BinData::Record
int32le :car_identifier_number
int32le :lap
string :driver_name, read_length: 100
string :car_name, read_length: 100
int32le :time
end
class Parser
def detect(record)
case record[:bytesize]
when 408
handle_handshake_response(record)
when 328
handle_rtcarinfo(record)
else
nil
end
end
def handle_handshake_response(record)
HandshakerResponse.read(record[:snap])
end
def handle_rtcarinfo(record)
RTCarInfo.read(record[:snap])
end
end
class ACTelemetry
AC_PORT = 9996
AC_HANDSHAKE = Handshaker.new(identifier: 1, version: 1, operation_id: 0).to_binary_s
AC_UPDATE = Handshaker.new(identifier: 1, version: 1, operation_id: 1).to_binary_s
AC_DISMISS = Handshaker.new(identifier: 1, version: 1, operation_id: 3).to_binary_s
CLIENT_PORT = 9999
CLIENT_IP_ADDR = '0.0.0.0'
def initialize(args)
@ac_ip_addr = args.pop
@udp = UDPSocket.new
@udp.bind(CLIENT_IP_ADDR, CLIENT_PORT)
@parser = Parser.new
end
def run!
listen
loop do
timestamp = -> { Time.now.to_s }
$stderr.puts "Command? [(h)andshake,(u)pdate,(d)ismiss,(t)test,(q)uit]"
cmd = gets.chomp
case cmd
when "h"
request :handshake
when "u"
request :update
when "d"
request :dismiss
when "t"
msg = "Test: #{timestamp.call}"
$stderr.puts "Sending test message to #{@ac_ip_addr}:#{AC_PORT}"
request msg
when "q"
$stderr.puts "Exiting..."
exit 0
end
end
end
def request(type)
case type
when :handshake
$stderr.puts "Sending handshake..."
send(AC_HANDSHAKE)
when :update
$stderr.puts "Sending update..."
send(AC_UPDATE)
when :dismiss
$stderr.puts "Sending dismiss..."
send(AC_DISMISS)
else
send(type)
end
end
def listen
Thread.new do
reader do |record|
$stdout.print "#{record.inspect}\n" if @debug
$stdout.print "#{@parser.detect(record).inspect}\n"
$stdout.flush
end
end
end
def reader
loop do
data, addrinfo = @udp.recvfrom(4096)
host, port = addrinfo[3], addrinfo[1]
record = {
host: host,
port: port,
bytesize: data.bytesize,
snap: data
}
yield record
end
end
def send(msg)
@udp.send(msg,0,@ac_ip_addr,AC_PORT)
end
end
begin
if $0 == __FILE__
ACTelemetry.new(ARGV).run!
end
rescue Interrupt
# Ctrl^C
exit 130
rescue Errno::EPIPE
# STDOUT was closed
exit 74 # EX_IOERR
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment