Skip to content

Commit

Permalink
- updated to PyYAML 5.3
Browse files Browse the repository at this point in the history
- exclude converting to datetime on parsing level with custom yaml loader
- added DiskSubHeader for IBT
- fixed a bug where parsing end position for session info was not set correctly
- vars.txt updated
  • Loading branch information
kutu committed Apr 18, 2020
1 parent 33a10d9 commit 8f1e3bc
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 31 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -7,3 +7,7 @@ MANIFEST
*.bin
*.txt
test_*.py
*.ibt
tutorial_*.py
setup.cfg
*.bat
71 changes: 43 additions & 28 deletions irsdk.py
Expand Up @@ -11,11 +11,11 @@
from yaml.reader import Reader as YamlReader

try:
from yaml.cyaml import CLoader as YamlLoader
from yaml.cyaml import CSafeLoader as YamlSafeLoader
except ImportError:
from yaml import Loader as YamlLoader
from yaml import SafeLoader as YamlSafeLoader

VERSION = '1.2.4'
VERSION = '1.2.5'

SIM_STATUS_URL = 'http://127.0.0.1:32034/get_sim_status?object=simStatus'

Expand Down Expand Up @@ -289,6 +289,13 @@ class VarHeader(IRSDKStruct):
desc = IRSDKStruct.property_value_str(48, '64s')
unit = IRSDKStruct.property_value_str(112, '32s')

class DiskSubHeader(IRSDKStruct):
session_start_date = IRSDKStruct.property_value(0, 'Q')
session_start_time = IRSDKStruct.property_value(8, 'd')
session_end_time = IRSDKStruct.property_value(16, 'd')
session_lap_count = IRSDKStruct.property_value(24, 'i')
session_record_count = IRSDKStruct.property_value(28, 'i')

class IRSDK:
def __init__(self, parse_yaml_async=False):
self.parse_yaml_async = parse_yaml_async
Expand Down Expand Up @@ -507,19 +514,21 @@ def _get_session_info(self, key):
self._parse_yaml(key, session_data)
return session_data['data']

def _parse_yaml(self, key, session_data):
session_info_update = self.last_session_info_update

def _get_session_info_binary(self, key):
start = self._header.session_info_offset
end = self._header.session_info_len

end = start + self._header.session_info_len
# search section by key
self._shared_mem.seek(0)
start = self._shared_mem.find(('\n%s:\n' % key).encode(YAML_CODE_PAGE), start, end)
match_end = re.compile(rb'\n\w').search(self._shared_mem, start + 1, end)
if match_end:
end = match_end.start()
data_binary = self._shared_mem[start:end]
match_start = re.compile(('\n%s:\n' % key).encode(YAML_CODE_PAGE)).search(self._shared_mem, start, end)
if not match_start:
return None
match_end = re.compile(b'\n\n').search(self._shared_mem, match_start.start() + 1, end)
if not match_end:
return None
return self._shared_mem[match_start.start() + 1 : match_end.start()]

def _parse_yaml(self, key, session_data):
session_info_update = self.last_session_info_update
data_binary = self._get_session_info_binary(key)

# section not found
if not data_binary:
Expand All @@ -540,9 +549,7 @@ def _parse_yaml(self, key, session_data):
def name_replace(m):
return m.group(1) + '"%s"' % re.sub(r'(["\\])', r'\\\1', m.group(2))
yaml_src = re.sub(r'((?:UserName|TeamName|AbbrevName|Initials): )(.*)', name_replace, yaml_src)
if key == 'WeekendInfo':
yaml_src = re.sub(r'(Date: )(.*)', r'\1"\2"', yaml_src)
result = yaml.load(yaml_src, Loader=YamlLoader)
result = yaml.load(yaml_src, Loader=CustomYamlSafeLoader)
# check if result is available, and yaml data is not updated while we were parsing it in async mode
if result and (not self.parse_yaml_async or self.last_session_info_update == session_info_update):
session_data['data'] = result[key]
Expand Down Expand Up @@ -577,19 +584,18 @@ def _pad_car_num(self, num):

class IBT:
def __init__(self):
self.buffers_length = 0

self._ibt_file = None
self._shared_mem = None
self._header = None
self._disk_header = None

self.__var_headers = None
self.__var_headers_dict = None
self.__var_headers_names = None
self.__session_info_dict = None

def __getitem__(self, key):
return self.get(self.buffers_length - 1, key)
return self.get(self._disk_header.session_record_count - 1, key)

@property
def file_name(self):
Expand All @@ -611,7 +617,7 @@ def open(self, ibt_file):
self._ibt_file = open(ibt_file, 'rb')
self._shared_mem = mmap.mmap(self._ibt_file.fileno(), 0, access=mmap.ACCESS_READ)
self._header = Header(self._shared_mem)
self.buffers_length = int((self._shared_mem.size() - self._header.var_buf[0].buf_offset) / self._header.buf_len)
self._disk_header = DiskSubHeader(self._shared_mem, 112)

def close(self):
if self._shared_mem:
Expand All @@ -620,11 +626,10 @@ def close(self):
if self._ibt_file:
self._ibt_file.close()

self.buffers_length = 0

self._ibt_file = None
self._shared_mem = None
self._header = None
self._disk_header = None

self.__var_headers = None
self.__var_headers_dict = None
Expand All @@ -634,14 +639,14 @@ def close(self):
def get(self, index, key):
if not self._header:
return None
if 0 > index >= self.buffers_length:
if 0 > index >= self._disk_header.session_record_count:
return None
if key in self._var_headers_dict:
var_header = self._var_headers_dict[key]
fmt = VAR_TYPE_MAP[var_header.type] * var_header.count
var_offset = var_header.offset + self._header.var_buf[0].buf_offset + index * self._header.buf_len
res = struct.unpack_from(fmt, self._shared_mem, var_offset)
return res[0] if var_header.count == 1 else list(res)
return list(res) if var_header.count > 1 else res[0]
return None

def get_all(self, key):
Expand All @@ -652,11 +657,11 @@ def get_all(self, key):
fmt = VAR_TYPE_MAP[var_header.type] * var_header.count
var_offset = var_header.offset + self._header.var_buf[0].buf_offset
buf_len = self._header.buf_len
sigle_or_array = var_header.count == 1
is_array = var_header.count > 1
results = []
for i in range(self.buffers_length):
for i in range(self._disk_header.session_record_count):
res = struct.unpack_from(fmt, self._shared_mem, var_offset + i * buf_len)
results.append(res[0] if sigle_or_array else list(res))
results.append(list(res) if is_array else res[0])
return results
return None

Expand All @@ -681,6 +686,16 @@ def _var_headers_dict(self):
self.__var_headers_dict[var_header.name] = var_header
return self.__var_headers_dict

# https://stackoverflow.com/a/37958106/1034242
class CustomYamlSafeLoader(YamlSafeLoader):
@classmethod
def remove_implicit_resolver(cls, tag_to_remove):
if not 'yaml_implicit_resolvers' in cls.__dict__:
cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
for first_letter, mappings in cls.yaml_implicit_resolvers.items():
cls.yaml_implicit_resolvers[first_letter] = [(tag, regexp) for tag, regexp in mappings if tag != tag_to_remove]
CustomYamlSafeLoader.remove_implicit_resolver('tag:yaml.org,2002:timestamp')

def main():
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='version', version='Python iRacing SDK %s' % VERSION, help='show version and exit')
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Expand Up @@ -12,19 +12,19 @@
url='https://github.com/kutu/pyirsdk',
py_modules=['irsdk'],
license='MIT',
platforms=['win32', 'win64'],
platforms=['win64'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.7',
'Topic :: Utilities',
],
entry_points={
'console_scripts': ['irsdk = irsdk:main'],
},
install_requires=[
'PyYAML >= 3.11',
'PyYAML >= 5.3',
],
)
5 changes: 5 additions & 0 deletions vars.txt
Expand Up @@ -7,14 +7,19 @@ CamCameraNumber Active camera number,
CamCameraState State of camera system, irsdk_CameraState
CamCarIdx Active camera's focus car index,
CamGroupNumber Active camera group number,
CarIdxBestLapNum Cars best lap number,
CarIdxBestLapTime Cars best lap time, s
CarIdxClassPosition Cars class position in race by car index,
CarIdxEstTime Estimated time to reach current location on track, s
CarIdxF2Time Race time behind leader or fastest lap time otherwise, s
CarIdxGear -1=reverse 0=neutral 1..n=current gear by car index,
CarIdxLap Laps started by car index,
CarIdxLapCompleted Laps completed by car index,
CarIdxLapDistPct Percentage distance around lap by car index, %
CarIdxLastLapTime Cars last lap time, s
CarIdxOnPitRoad On pit road between the cones by car index,
CarIdxP2P_Count Push2Pass count of usage (or remaining in Race),
CarIdxP2P_Status Push2Pass active or not,
CarIdxPosition Cars position in race by car index,
CarIdxRPM Engine rpm by car index, revs/min
CarIdxSteer Steering wheel angle by car index, rad
Expand Down

0 comments on commit 8f1e3bc

Please sign in to comment.