Last active
May 13, 2020 03:46
-
-
Save bradland/0e8504ed5d32fa64744f3585d0fa6d2f to your computer and use it in GitHub Desktop.
Beginnings of a rudimentary CLI telemetry utility for Assetto Corsa written in Ruby.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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