The online racing simulator
Python - pyinsim - A Python InSim Module [OLD]
Important!

OBSOLETE DO NOT DOWNLOAD

Development of pyinsim has moved to a new thread.

http://www.lfsforum.net/showthread.php?t=70545

This thread has been left for archive purposes only. Please note: this version of pyinsim is no longer supported and most of the code in this thread will not work with this or later versions! The only reason to download this is if you need to work with legacy code. Otherwise you should download the latest stable release, which you can download [url=http://pyinsim.codeplex.com/releases/[/url]!
Attached files
pyinsim_1.5.5_beta.zip - 379.6 KB - 1144 views
Very nice work DarkTimes - lots of whitespace lovers with abnormal tab key fingers will be very happy The API seems straight forward, and complements that provided by LFS_External nicely.

Any objections if I move this into the libraries subforum, or are you not convinced that this is "finished"?
You're not going to believe this.

During the past week or so, I've been learning Python - and had started a Python InSim Module... Weird.

If you're interested in some help, I could contribute, but not so much over the next month or so.
Quote from the_angry_angel :Any objections if I move this into the libraries subforum, or are you not convinced that this is "finished"?

I considered putting it in there, but really it's not finished yet, so I don't think it's ready for that.
Quote from JamesF1 : You're not going to believe this.

During the past week or so, I've been learning Python - and had started a Python InSim Module... Weird.

If you're interested in some help, I could contribute, but not so much over the next month or so.

Hehe, awesome.

I would be very grateful for any input on how the actual library has been put together, how the packets are assembled etc.. I spent a lot of time going back and forwards between different designs, but I think the method I ended up with does work quite well. There's a fair amount of spaghetti code inside the packet class that needs sorting.

At the moment it sort of stands both as 'half-finished' and as a 'proof-of-concept'. It needs a lot of work to clean everything up.

I have some development tools I can post too, such as a set of functions which will parse InSim.txt to create the various packet definition lists etc..

Anyway... noob that I am I somehow managed to mess up the tab spacing in the version I posted (really smart with Python, of all languages lol), so I've uploaded a new version with the correct tab spacing.
Quote from DarkTimes :I would be very grateful for any input on how the actual library has been put together, how the packets are assembled etc.. I spent a lot of time going back and forwards between different designs, but I think the method I ended up with does work quite well. There's a fair amount of spaghetti code inside the packet class that needs sorting.

Looks good from here, I had a quick scan down the source, and I can't say I'd have done it much differently. Your enumerations may benefit from being done on one line for each section, say:
ENUM_1, ENUM_2, ENUM_3 = 0, 1, 2

And you should probably document your def's like this:
def bla():
"""This is a document comment, with three double quotes"""

Just a couple of thoughts, I'll see if I get some time to look over it more fully later on
Quote from JamesF1 :Looks good from here, I had a quick scan down the source, and I can't say I'd have done it much differently. Your enumerations may benefit from being done on one line for each section, say:
ENUM_1, ENUM_2, ENUM_3 = 0, 1, 2

And you should probably document your def's like this:
def bla():
"""This is a document comment, with three double quotes"""

Just a couple of thoughts, I'll see if I get some time to look over it more fully later on

Yes, I need to do the docstrings. I've always been crap at documentation, and at the moment whole classes can be rewritten still (I re-wrote the InSim class earlier for instance), so it seems silly to document stuff that I'm not sure I'm going to keep.

The enums and the packet defs could be on one line, but I find it easier to read the way I've done it. Also I wrote a script that parses the data out of InSim.txt so I didn't have to write each enum and packet myself, and that was the easiest way to output the data.
I've updated it to finish the NLP packets. I renamed _MciPacket to _CarTrackPacket, as it now handles both MCI and NLP. It's not very neat, the whole MCI/NLP packet feels like a (big) bit of a hack really, but I wanted to add NLP as it was the only major thing missing for InSim, apart from one camera packet I've not spent time working out yet. Aside from that all packets, enums and flags should be done.

Tomorrow I will spend some time refactoring and cleaning up the packet code, as it is just very messy at the moment. Do they have an obfuscated Python contest?
InSim button example.

import pyinsim

# Global variables.
BUTTON_ID = 1

# Button click event
def buttonClick(insim, btc):
if btc['ClickID'] == BUTTON_ID:
print 'I was clicked!'

insim = pyinsim.InSim()
# Register the event with InSim.
insim.bind(pyinsim.ISP_BTC, buttonClick)

try:
# Connect to LFS.
insim.connect('127.0.0.1', 29999)

# Initialise InSim.
insim.send(pyinsim.ISP_ISI, Admin='pass', IName='pyinsim', Flags=pyinsim.ISF_LOCAL)

# Create a button packet, fill in the values.
btn = pyinsim.Packet(pyinsim.ISP_BTN)
btn['ReqI'] = 1
btn['UCID'] = 0
btn['ClickID'] = BUTTON_ID
btn['BStyle'] = pyinsim.ISB_DARK | pyinsim.ISB_CLICK
btn['T'] = 35
btn['L'] = 5
btn['W'] = 20
btn['H'] = 8
btn['Text'] = 'Click me!'
insim.sendP(btn)

insim.run()
except pyinsim.socket.error, err:
print 'InSim Error:', err[1]
finally:
insim.close()

Edit: Removed cause it was about an obsolete version.
I've been testing a few things and I can confirm that Pyinsim runs unmodified on IronPython for the .NET Framework. I've been messing around with getting it running on the Silverlight 2 beta with IronPython, and while it fundamentally works, the socket restrictions on the beta have just made it too hard to do anything useful without a herculean effort (you need to use the web server as a proxy and bounce the packets off it. It's not very fun). From what I've read I'm confident it will work with Silverlight 2 final, albeit with some serious modifications to the socket code, so hopefully I should be able to report more progress on Pyinsim for Sliverlight in the future.
Good work When I have some spare time, I'll definitely be testing out Pyinsim
Rolling Starts

Here is an example program which enforces rolling starts on a server. You can set the number of formation laps before the race, and if during that time a player breaks the speed-limit, or overtakes another car, they will be spectated. It also lets you configure the messages when are sent and other stuff like that.

"""Full example program. Enforces rolling starts on a host. If a player speeds
or over-takes another car, then are spectated and sent a message."""

import pyinsim
import time
import threading
import sys


# InSim Config.
HOST = '127.0.0.1'
PORT = 29999
ADMIN = 'pass' # Your admin password here.

# Constants.
MCI_INTERVAL = 100
FORMATION_LAPS = 1
SPEED_LIMIT = 80 # Kph
START_MSG = "^1Rolling start - Keep below %d Kph and don't overtake!" % SPEED_LIMIT
GREEN_MSG = '^2Green flag! GO GO GO!'
SPEEDING_MSG = '%s ^3spectated for speeding!'
OVERTAKE_MSG = '%s ^3spectated for overtaking!'
MSG_TIMEOUT = 10 # How many seconds messages stay on the screen.

# Globals.
insim = pyinsim.InSim()
players = {}
onFormationLap = False
rccTimer = None


# Player class.
class Player:
"""Small class to handle player."""
def __init__(self, npl):
self.pName = npl['PName'] # Player name.
self.plid = npl['PLID']
self.position = 0 # Position in race.


# Helper functions.
def sendMessage(msg):
"""Send message to LFS."""
insim.send(pyinsim.ISP_MST, Msg=msg)


def sendRcmAll(msg, timeout):
"""Send RCM message to all players."""
sendMessage('/rcm %s' % msg)
sendMessage('/rcm_all')
rccTimer = threading.Timer(timeout, clearRcmAll)
rccTimer.start()


def clearRcmAll():
"""Clear RCM message."""
sendMessage('/rcc_all')


def spectatePlayer(pName):
"""Spectate a player."""
sendMessage('/spec %s' % pName)


def resetPositions():
"""Reset all player positions to zero."""
for player in players.values():
player.position = 0


def getPlayer(plid):
"""Get player from players dict."""
return players[plid]


def playerExists(plid):
"""Get if player exists in players dict."""
return plid in players


def requestPlayers(insim):
"""Request for all players to be sent."""
insim.send(pyinsim.ISP_TINY, SubT=pyinsim.TINY_NPL, ReqI=1)


# Events.
def playerJoined(insim, npl):
"""Create new player object and add to players dict."""
players[npl['PLID']] = Player(npl)


def playerLeft(insim, pll):
""" Remove player object from players dict."""
del players[pll['PLID']]
# When a player leaves, any drivers behind them will be specced for
# over-taking, so we need to reset all positions.
resetPositions()


def raceStarted(insim, rst):
"""Race started. Request all players to be sent and begin formation
lap."""
global onFormationLap

requestPlayers(insim)
if rst['RaceLaps'] > 0:
sendRcmAll(START_MSG, MSG_TIMEOUT)
onFormationLap = True
else:
# A qual or practice session, no formation lap.
onFormationLap = False


def carUpdate(insim, mci):
"""Car position update."""
global onFormationLap

for car in mci['CompCars']:
if playerExists(car['PLID']) and onFormationLap:
player = getPlayer(car['PLID'])

if car['Lap'] > FORMATION_LAPS:
# Formation lap has ended.
onFormationLap = False
sendRcmAll(GREEN_MSG, MSG_TIMEOUT)
elif pyinsim.speedToKph(car['Speed']) > SPEED_LIMIT:
# Player is speeding.
spectatePlayer(player.pName)
sendMessage(SPEEDING_MSG % player.pName)
elif player.position == 0:
# Player has no position, set it.
player.position = car['Position']
elif car['Position'] < player.position:
# Player has over-taken another car.
spectatePlayer(player.pName)
sendMessage(OVERTAKE_MSG % player.pName)


def connectionLost(insim):
"""Event called when connection to InSim is lost."""
print 'InSim Error: The connection to InSim lost'

def threadError(insim, err):
"""Event called when there is an error on the internal receive thread."""
print 'InSim Error:', err

# Bind events.
insim.bind(pyinsim.ISP_NPL, playerJoined)
insim.bind(pyinsim.ISP_PLL, playerLeft)
insim.bind(pyinsim.ISP_RST, raceStarted)
insim.bind(pyinsim.ISP_MCI, carUpdate)
insim.bindConnectionLost(connectionLost)
insim.bindThreadError(threadError)

if __name__ == '__main__':
try:
insim.connect(HOST, PORT)
insim.send(pyinsim.ISP_ISI, Admin=ADMIN, IName='RollingStarts',
Flags=pyinsim.ISF_MCI, Interval=MCI_INTERVAL)
insim.run()
except pyinsim.socket.error, err:
print 'InSim Error: %s' % err[1]
finally:
insim.close()

Pardon my ignorance - I'm not totally clued up on current InSim version details, etc. but where's the IS_REN packet in your source? I couldn't find it.
There is no IS_REN packet. I believe it's now a sub-type of IS_TINY, called TINY_REN.


def tinyReceived(insim, tiny):
if tiny['SubT'] == pyinsim.TINY_REN:
print 'Race Ending!'

insim.Bind(pyinsim.ISP_TINY, TinyReceived)

Catch a TINY and check the SubT.

Ahh, right-o Seems a bit of an odd thing to do with it... but oh well.
Hello,

I have uploaded a new source-distribution to the first post, of version 0.1.3. There are a couple of quite subtle but important changes.

# The documentation has been cleaned up a lot!

# The Packet class has been completely rewritten and the code is much, much simpler. Packets now inherit directly from UserDict, meaning they now have all the same methods and properties as Python dictionaries. This means you can now iterate over packets like you can a normal dictionary.

In addition to this, the Packet class now contains a new constructor parameter called values, which allows you to specify a default dictionary for the packet. Basically that means you can create packets like this:


isi = Packet(ISP_ISI, ReqI=1, Admin="Spam", IName="pyinsim")

# A new method named SendP() has been added to the InSim class, which allows you to send a packet without having to call its Pack() method first.


insim.sendP(isi)

# Instead of the old...

insim.sendB(isi.pack())

Anyway, you can download the source-dist in the first post. These features were added to support a new Pyinsim module which I'm planning to release soon, which will make writing InSim apps easier than it currently is, but I felt these changes were useful enough on their own to warrant a separate release.
[offtopic]
To bad we can't give it a soul.
Sure we can, import soul;
Oh, right on python!


[/offtopic]


I <3 xkcd.


Now that is funny!
Thanks for that!
*Starts learning python*

//edit: could you write a example, what will refresh speed data in a button?
Quote from Nadeo4441 ://edit: could you write a example, what will refresh speed data in a button?

This is a really basic example, it will only work in a single-player game and if you're the only car, but it demonstrates the principle.

"""Example 3: Update a button in the local LFS client with your current speed
in KPH."""

import pyinsim

# Constant for the speed button ClickID
ID_SPEED_BUTTON = 1

# Helper for placing speed button on the game-screen.
def sendSpeedButton(insim, speed=0):
insim.send(pyinsim.ISP_BTN, ReqI=1, UCID=0, ClickID=ID_SPEED_BUTTON,
BStyle=pyinsim.ISB_DARK, T=175, L=5, W=25, H=8,
Text='%.3d Kph' % speed)

# Event called whenever a car is updated.
def carUpdate(insim, mci):
# Loop through each car.
for car in mci['CompCars']:
# Use pyinsim's built-in function to convert speed to KPH.
speed = pyinsim.speedToKph(car['Speed'])
# Update speed button.
sendSpeedButton(insim, speed)

insim = pyinsim.InSim()

# Bind car update event-handler.
insim.bind(pyinsim.ISP_MCI, carUpdate)

# Connect to LFS and initailise InSim.
try:
insim.connect('127.0.0.1', 29999)

# We set the ISF_MCI flag to tell LFS to send IS_MCI car position updates, and also the
# Interval of how many milliseconds to wait between updates.
insim.send(pyinsim.ISP_ISI, Admin='pass', IName='pyinsim',
Flags=pyinsim.ISF_LOCAL | pyinsim.ISF_MCI, Interval=100)

insim.run()
except pyinsim.socket.error, err:
print 'InSim Error:', err[1]
finally:
insim.close()

Hope that helps.
I cant get it working ,
Im getting this in lfs : "InSim guest closed : "
I get that error too if the program closes right away, closing the network connection, before the ISI packet has been received by LFS. You need to force the program to stay running so that Pyinsim can keep the network connection open. That's what this code is suppose to do:

insim.run()

So first of all make sure you have that code somewhere before the end of your app. If you do, them I might need more info to solve the problem.
I have tested your unchanged code from post #21 ... i get that error too , and before the application close, it shows "Connection failed" .
Please make sure you have 0.1.3 from the first post, which I uploaded last night. The example I posted is for that version. If I try it with the old 0.1.2 then I get exactly the same error you post. Please make sure you've updated.

Edit: The actual error is that SendP doesn't exist, as that wasn't added until 0.1.3. Because I'm just catching all exceptions, it's catching that one too, and printing the connection failed message. If you remove the Except exception catching block you'll see the real error message.

Python - pyinsim - A Python InSim Module
(243 posts, started )
FGED GREDG RDFGDR GSFDG