PDA

View Full Version : Python - pyinsim - A Python InSim Module


DarkTimes
17th April 2008, 13:48
Important!

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 here (http://pyinsim.codeplex.com/releases/view/56516)!

the_angry_angel
17th April 2008, 14:05
Very nice work DarkTimes - lots of whitespace lovers with abnormal tab key fingers will be very happy :D 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"?

JamesF1
17th April 2008, 14:13
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.

DarkTimes
17th April 2008, 14:43
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.
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.

JamesF1
17th April 2008, 14:46
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 :)

DarkTimes
17th April 2008, 14:48
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, 2And 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.

DarkTimes
17th April 2008, 20:43
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? :p

DarkTimes
17th April 2008, 20:58
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()

DarkTimes
18th April 2008, 16:56
Edit: Removed cause it was about an obsolete version.

DarkTimes
19th April 2008, 17:31
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.

JamesF1
19th April 2008, 17:37
Good work :) When I have some spare time, I'll definitely be testing out Pyinsim :)

DarkTimes
19th April 2008, 20:07
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()

JamesF1
20th April 2008, 01:04
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.

DarkTimes
20th April 2008, 01:11
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.

:)

JamesF1
20th April 2008, 01:13
Ahh, right-o :) Seems a bit of an odd thing to do with it... but oh well.

DarkTimes
20th April 2008, 23:38
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.

Dygear
21st April 2008, 11:01
To bad we can't give it a soul.
Sure we can, import soul;
Oh, right on python!

http://imgs.xkcd.com/comics/new_pet.png

DarkTimes
21st April 2008, 11:13
:D

I <3 xkcd. :)

mcgas001
21st April 2008, 11:20
http://imgs.xkcd.com/comics/python.png

Now that is funny! :D

Nadeo4441
21st April 2008, 15:22
Thanks for that!
*Starts learning python*

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

DarkTimes
21st April 2008, 17:16
//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. :)

Nadeo4441
21st April 2008, 17:41
I cant get it working , :shrug:
Im getting this in lfs : "InSim guest closed : "

DarkTimes
21st April 2008, 17:49
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.

Nadeo4441
21st April 2008, 17:56
I have tested your unchanged code from post #21 ... i get that error too , and before the application close, it shows "Connection failed" .

DarkTimes
21st April 2008, 18:03
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.

DarkTimes
21st April 2008, 18:13
I have uploaded the binary distributable for 0.1.3 to the first post. If you are a Windows user can just download that and run the exe, which will install it automatically.

Nadeo4441
21st April 2008, 18:46
i have installed 0.1.3, and im getting same error..

DarkTimes
21st April 2008, 18:56
There could be an issue with the Pyinsim installation, which can happen if you're upgrading from 0.1.2 (but won't happen for future upgrades as the bug has been fixed in 0.1.3).

Go to X:\Path\To\Python\Lib\site-packages and delete the following files:

Pyinsim.py
Pyinsim.pyc
Pyinsim.pyo

There should also be a folder called Pyinsim, but don't delete that, as that's where 0.1.3 has been installed. Now the script should work...

Nadeo4441
21st April 2008, 19:08
i removed Pyinsim folder, and that 3 files . After that i have installed 0.1.3 . I got this :
ImportError: No module named Pyinsim

After that i copied the files from Lib\site-packages\Pyinsim
to Lib\site-packages\ ... and it worked
Thanks for your help anyway :)

DarkTimes
21st April 2008, 19:18
I said not to remove the Pyinsim folder, but I could have been more clear, sorry.

As I said this was a bug in 0.1.2 which has been fixed now, so it won't happen again for future upgrades.

Nadeo4441
21st April 2008, 19:58
Oh, sorry, im blind :shhh:

Nadeo4441
23rd April 2008, 16:27
Sorry for doublepost, im testing this, and im stucked in that sittuation :

def Msg(mso):
msgt = mso["Msg"]
if msgt == "eTS.NadeoSvK : ^L!test":
SendMessage("/msg " + msgt)

if i type !test, it starts posting msgt ,and dont stop

// i know , its normal, but how to fix it?

filur
23rd April 2008, 17:00
how to fix it?

struct IS_MSO // MSg Out - system messages and user messages
...
byte UCID; // connection's unique id (0 = host)
if mso["UCID"] != 0 and msgt == "eTS.NadeoSvK : ^L!test":

DarkTimes
23rd April 2008, 17:03
if mso["UCID"] != 0 and msgt == "eTS.NadeoSvK : ^L!test":

There's no && in Python. :)

Nadeo4441
23rd April 2008, 17:57
thanks guys :)

//Edit: DarkTimes: What about an OutSim&OutGauge support?

DarkTimes
23rd April 2008, 23:20
I have uploaded 0.1.4 to the first post, which contains a few tweaks and additions.

Changes:

- You now define the default values for packets with traditional-style parameters, instead of passing a dictionary. Sadly this is a breaking change (the first one!), but the code looks much nicer like this. isi = Packet(ISP_ISI, ReqI=1, Admin="FishDance", IName="Pyinsim")

- The prefix char in IS_ISI is now an actual char instead of a byte, so you don't need to cast the prefix char into a byte yourself. Yes, I know, but it wasn't as simple to add as it sounds. isi["Prefix"] = "!" # This works now!

- Multiple packets obejcts can now be sent with a single SendP() call. The packets will be sent one after the other, in the order they are specified. For example: insim.sendP(isi, tiny, mst)

- An interesting little idea I had was to use XML to create packets, so I've added a new function called packetXml which will return a packet object created from XML data. You can either parse XML from a file or from a string. Here is an example:

The XML file, called Packets.xml. You define a Packet element and give it a Name attribute, which is important as you will use that to reference the Packet later on. The rest of the info follows the typical InSim packet stuff. Anything you omit from a packet will be filled in with appropriate default data before it's sent (which is the same for everything in Pyinsim really).

<?xml version="1.0" encoding="utf-8"?>
<Pyinsim>
<Packet Name="Init">
<ReqI>1</ReqI>
<UDPPort>0</UDPPort>
<Admin>Pass</Admin>
<IName>Pyinsim</IName>
<Prefix>$</Prefix>
</Packet>
<Packet Name="HelloMsg">
<Msg>Hello, world!</Msg>
</Packet>
</Pyinsim>

You call PacketXml() and pass in the type of packet you want to create, the Name attribute for the packet in the XML file, and also the path to the XML file. PacketXml then parses the data and returns a regular Packet object populated from the XML.


xmlFile = "Packets.xml"

isi = packetXml(ISP_ISI, "Init", xmlFile)
mst = packetXml(ISP_MST, "HelloMsg", xmlFile)

insim.sendP(isi, mst)


Anyway...

DarkTimes
23rd April 2008, 23:59
//Edit: DarkTimes: What about an OutSim&OutGauge support?
Yes, I'm going to add that soon. I'm just trying get the InSim part right first. :)

Nadeo4441
24th April 2008, 14:29
if im a windows user , how to install it ? (0.1.4)

JamesF1
24th April 2008, 14:30
Run the installer?

Nadeo4441
24th April 2008, 14:39
doesnt work...

JamesF1
24th April 2008, 14:47
If you don't describe the problem, we can't help you. If you have Python installed, there should be no problems at all.

Nadeo4441
24th April 2008, 14:49
i have python installed , and i have Pyinsim 0.1.3. I click on the setup.py , it shows something but closes ... and it doesnt work..

DarkTimes
24th April 2008, 19:38
Edit: I have now uploaded a new fixed installer to the first post.

Nadeo4441
24th April 2008, 20:15
oh , i got it now, thanks

DarkTimes
26th April 2008, 16:49
Due to a request I have created an example of how you might go about using commands like "!help" and "!admin".

from pyinsim import *
insim = InSim()

def messageReceived(insim, mso):
command = parseCommand(mso, '!')
if command[0] == "help":
print 'Help command'
elif command[0] == "admin":
print 'Admin command'
elif command[0] == "report":
print 'Report', command[1]

insim.Bind(ISP_MSO, MessageReceived)

try:
insim.connect("127.0.0.1", 29999)
except Exception, (msg):
print "Error:", msg
else:
insim.send(ISP_ISI, Admin="Pass", IName="Pyinsim")
insim.run()
finally:
insim.close()

tmehlinger
6th June 2008, 22:36
I've been playing LFS for a couple months and wanted to get started in some InSim programming. Thanks very much for this Python module, it made my day. :)

DarkTimes
7th June 2008, 17:22
Thanks! Glad you find it useful. :)

Tanuva
30th September 2008, 21:07
I'm fiddling round to get the button example from the thread's first page to work, but my button still only appears in singleplayer mode.
I changed the UCID to the actual player's one - didn't really help. Hints?

InSim.SendP(self, Packet(ISP_BTN, ReqI=1, ClickID=0, UCID=ucid,
BStyle=ISB_DARK, W=40, H=10, T=30, L=5, Text=speed))

DarkTimes
1st October 2008, 00:18
It's been a while since I did any work with InSim, but I think it's because of the ISF_LOCAL flag on this line:

insim.SendP(Packet(ISP_ISI, Admin="Pass", IName="Pyinsim", Flags=ISF_LOCAL))

This flag, I believe, tells InSim to only display buttons in single-player mode, or if you are a guest on a server (not the host). Change the line to:

insim.SendP(Packet(ISP_ISI, Admin="Pass", IName="Pyinsim"))

and it should work in multiplayer.

As I say, been a while, but this should work. I reckon this is the fundamental root of the issue.

Tanuva
1st October 2008, 11:04
Yep, the ISF_LOCAL flag was the problem. Thanks! :)

Silox
1st October 2008, 13:13
B-E-A-U-tiful!

/me is desperate to learn Python as soon my Ranking System is finished...

ken830
3rd October 2008, 23:28
Just wanted to say thanks to DarkTimes for all of his hard work and for making this available to all of us. I certainly appreciate it! Thanks!

DarkTimes
7th October 2008, 16:58
Just a short note for an oddity I noticed ('pulled my hair out over') earlier. For some reason calling InSim.Close() doesn't actually seem to close the internal socket, when running in TCP mode. You need to send the packet to close InSim before you try to close the socket, or else nothing seems to happen (and nothing happening is the worst thing when you're trying to figure out a bug).


# Send tiny close to InSim.
tinyClose = Packet(ISP_TINY, SubT=TINY_CLOSE)
insim.SendP(tinyClose)

# Now you can close the socket.
insim.Close()


This may well be a Berkeley sockets issue, and not a Pyinsim or Python problem, but it's different behaviour from that in the .NET Berkeley sockets implementation that I'm used to. I'm not sure what's going on... but it's likely I've run out of brains...

I may roll a workaround into the next release, if I can get around to releasing it. :)

Edit: This is fixed in Pyinsim 0.1.5 and above.

Tanuva
10th October 2008, 17:20
Hey!

First I have to praise Darktimes for his great work on PyInsim!

My actual problem:
I have a button:
InSim.SendP(self, Packet(ISP_BTN, ReqI=1, ClickID=cid, UCID=ucid,
BStyle=ISB_DARK | ISB_RIGHT,
W=cw, H=ch, T=cy, L=cx, Text=carbtntext))
[ReqI is the same for all my buttons since I dont know what it does...]

Now I added ISP_BTC to the list of packet handlers, but the handler doesnt get triggered once I click the button (in "show connections" mode to have a cursor).
What am I doing wrong? :)

DarkTimes
10th October 2008, 17:32
I have a button:
InSim.sendP(self, Packet(ISP_BTN, ReqI=1, ClickID=cid, UCID=ucid,
BStyle=ISB_DARK | ISB_RIGHT,
W=cw, H=ch, T=cy, L=cx, Text=carbtntext))Now I added ISP_BTC to the list of packet handlers, but the handler doesnt get triggered once I click the button (in "show connections" mode to have a cursor).
What am I doing wrong? :)
It looks like you need to add the flag ISB_CLICK to the BStyles byte.
InSim.sendP(self, Packet(ISP_BTN, ReqI=1, ClickID=cid, UCID=ucid,
BStyle=ISB_DARK | ISB_RIGHT | ISB_CLICK,
W=cw, H=ch, T=cy, L=cx, Text=carbtntext))Now when the button is clicked, InSim will send back an IS_BTC packet which will be picked up by the packet handler.

Tanuva
10th October 2008, 18:03
Of course! I knew I read something about that in InSim.txt, but I couldn't refind it. Thank you! :D

DarkTimes
13th October 2008, 12:57
Uploaded version 0.1.5 to the first post. It contains a few fixes and changes, and please note that it breaks comptibility with previous versions.

Changes:


Fix: Fixed 'bad file description' error on attempted reconnects.
Fix: Fixed bug with InSim not closing when calling InSim.Close() in TCP mode.
Fix: Changed InSim.Run() documentation to make it clear it's designed to only be used when running Pyinsim from a console application.

Change: Added ISP_CPP Camera Position Packet, which means all InSim packets are now complete.
Change: Renamed InSim.RegisterPackets() and InSim.UnregisterPackets() to InSim.Bind() and InSim.Unbind() respectively. This is a breaking change.
Change: Added LFSTime class, for handling time operations.
Change: Increased internal buffer size default to 1024
Change: Added InSim.ConnectionLost() event, which is called when the InSim connection is lost.


Note: Pyinsim does not currently yet support Vectors as a data-type, so a small work-around is used. For instance, instead of the Pos attribute of the CPP packet being a vector, it is in fact split into three seperate attributes, which are named Pos1, Pos2 and Pos3, each of them containing an int.

Also note that instead of distributing four separate downloads, I have combined them all into one single package. This file contains the windows installer, the source distribution, license, readme and HTML documentation, as well as the template file.

Crady
20th October 2008, 17:15
DarkTimes!


Thank you!!!!!!


Ok... I took me some few time today and tried to understand python and your lib a bit...

Well I think I already failed at the start :(

Ok... As I told you in the PM I sent you I am also intersted in hanling a rolling start and... tataaaaaaaa... you already coded something like that!

But the Problem I have is that any speeding of 1mph causes a spec.

So I tried to change your code a bit. I wanted that a personal rcm appears to the driver who speeded (I dunno how to code buttons yet), then the timer should run, rcm should be cleared by rcc and then if the car still is faster than the limit a spec should follow...

This is my try:

elif ToKph(car["Speed"]) > SPEED_LIMIT:
# Player broke the speed-limit, spec them.
SendMessage("/rcm " + GET_SLOWER)
SendMessage("/rcm_ply " + players[car["PLID"]][0]["PName"])
BlockingTimer(MSG_TIMEOUT)
SendMessage("/rcc_ply " + players[car["PLID"]][0]["PName"])
players[car["Speed"]] = 0
if ToKph(car["Speed"]) > SPEED_LIMIT:
SendMessage("/spec " + players[car["PLID"]][0]["PName"])
SendMessage(SPEEDING_MSG)

But I realized 2 big problems with it:
1.) rcm_ply does not work with Nickname and changing "PName" to "UName" gives me an error...
2.) I become sent to spec anyway even if I slow down under the limit...

I seems to me that the 1st speed I got the message for speeding for is stored somehow and at the "if" it still is over the limit...

So I tryed to set the Speed to 0 manually which seems not to work too...

An other problem I noticed is that there seems to be an endles loop after speeding... because I am not able to join the race later - I always become to spec again as soon as I join...

Any clue?

If I (or we) manage to get this to work my next step will be to create a command like "!rollingstarton" and "!rollingstartoff" to be able to choose wether there will be a rolling start or not. The over-next step should be using a config file and then creating a exe...

DarkTimes
21st October 2008, 10:20
1.) rcm_ply does not work with Nickname and changing "PName" to "UName" gives me an error...

Using the nickname should work. Not sure why it doesn't. It works fine for me.


2.) I become sent to spec anyway even if I slow down under the limit...

The example I posted is very simple, if you go across the speed limit at all then you are just spectated immediately. If you wanted to adapt it to give a warning to slow down, you would need to setup some sort of timer that would start when the player began speeding, then after X amount of seconds had passed it would check the speed again, and if you were still over the limit you would get spectated.

Edit: The example program is badly written, I've uploaded a better version now.

DarkTimes
21st October 2008, 14:27
I've completely rewritten the original RollingStarts program and I've updated the original post. The new version is written in a much clearer way and I tested it a few times to check it works without bugs. It may still have a couple, I don't know. It still works the same way as the previous version mind you.

http://www.lfsforum.net/showthread.php?p=774956#post774956

In other news: Update version 0.1.6 in the first post.

Changes


Updated PyinsimTemplate.py to add support for taking over cars and for players being renamed. Also added connection lost event handler.
Added ToUnicode(str, default) function which converts a LFS string to unicode, and handles converting all the character encodings and escape chars. Note this is still being tested, so might be a little flaky.
Added keepAlive boolean param to InSim(connType, keepAlive) contrsuctor, which allows you to set if InSim responds to keep alive packet automatically.
Added CheckPort(ipStr) and CheckIP(portStr) functions, to make it easy to validate IP addresses and port number strings.
Added ParseCommand(mso, prefix, sansPrefix), which parses a command from an MSO packet and returns a tuple.
Added weather and wind enums.
Added flags enum to check ISP_FLG attributes.
Added car info flags

Crady
22nd October 2008, 07:14
Thank you, DarkTime, great one!

I will test your rewritten Code this afternoon and will report! But yet I got some worries in mind: What will happen if the racer at Pole spinns out although he obtained speedlimit? Do all other drivers go to spec then because they "overtake" ?? Well although it is just a "Demo Code" I would like to stay and improve on it... - I would say that spec could be changed with a drive through penalty and the formation lap Funktions itself should be switchable to on/off via a command which only can be applied by an Amdin...

Secondary I have a suggestion to python / Pyinsim itself:

What do you think about developing a good insim application together in this forum to 1st create a usefull app, 2nd to show the possibilities of Python and 3rd help ppl learning Python (espeically Pyinsim)...

DarkTimes
22nd October 2008, 14:28
Thank you, DarkTime, great one!

I will test your rewritten Code this afternoon and will report! But yet I got some worries in mind: What will happen if the racer at Pole spinns out although he obtained speedlimit? Do all other drivers go to spec then because they "overtake" ?? Well although it is just a "Demo Code" I would like to stay and improve on it... - I would say that spec could be changed with a drive through penalty and the formation lap Funktions itself should be switchable to on/off via a command which only can be applied by an Amdin...

Yes, in its current form it's very harsh on mistakes, as if you pass another driver at all it specs you. You could easily mod it to give a penalty instead.

def PenalisePlayer(pName):
SendMessage('/p_dt %s' % (pName))Which would give a drive-through instead, which an admin could clear if they decided it was unfair.

What do you think about developing a good insim application together in this forum to 1st create a usefull app, 2nd to show the possibilities of Python and 3rd help ppl learning Python (espeically Pyinsim)...Sorry, but I'm quite busy with some other projects at the moment, or I should be busy with them, if I wasn't such a slacker. I will help people using Pyinsim all I can, but I can't work on any outside projects specifically at the moment.

Crady
22nd October 2008, 15:36
Sorry, but I'm quite busy with some other projects at the moment, or I should be busy with them, if I wasn't such a slacker. I will help people using Pyinsim all I can, but I can't work on any outside projects specifically at the moment.

I really can imagine that you are very busy - as I am too...

But I now decided to change and spare my projects and believe me, I bought a Video2Brain DVD for Python beginners and a very big book "Python the complete manual"... I guess after watching the video and reading the 1st 100 pages in this book I will be able to understand it more clearly!

My big problem is the LFS insim... I never spent time in that and to be honestly I do not understand anything in the manual which can be found in the doc folder... But thats why you made this great lib... Now I only need to know what I can get from LFS and how to use it :)

My far away aim is create a webapplication which makes it via user accounts possible to manage the lapper config more easy than scripting a config file... I mean adding/removing/changing autoactions, adding/removing user rights, editing the swear filter, editing the ban list, synchronizing the ban list for several servers of a team... etc...

Well hard dreams.... but lets see, what the future brings :)

Crady
25th October 2008, 17:41
Hi...

After reading a bit in the Pyton book I bought I must say that I seem to be able to do some simple scripts...

But I want to create some insim apps... And this still is my problem:

To configure an amount of Servers I want to create an app wihich asks me "Which Admin Commad?" for Security it also should ask for the Admin Pass.

Then it should connect to to all IPs / Ports entered in a List, execute the commad, end exit insim...

But my problem is how to close the insim after the command and how to open another after this?

Or is it possible to connect to more than one Server at once?

This is my try with the use of your template:

#
# Copyright 2008 Alex McBride.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Lesser General Public License (LGPL) as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import Pyinsim
import sys


# Init globals
insim = Pyinsim.InSim(Pyinsim.INSIM_TCP)
connections = {}
players = {}
Server = ['192.168.2.104', '127.0.0.1']
Port = [29999, 29998]
# Helper functions.
def SendMessage(msg):
"""Send message to LFS."""
if len(msg) > 64:
insim.SendP(Pyinsim.Packet(Pyinsim.ISP_MSX, Msg=msg))
else:
insim.SendP(Pyinsim.Packet(Pyinsim.ISP_MST, Msg=msg))


def SendMessageConn(msg, ucid=0, plid=0):
"""Send message to a specific connection or player."""
insim.SendP(Pyinsim.Packet(Pyinsim.ISP_MTC, Msg=msg, UCID=ucid, PLID=plid))


#def RequestPlayersConns():
# """Request all players and connections to be sent."""
# insim.SendP(Pyinsim.Packet(Pyinsim.ISP_TINY, ReqI=1, SubT=TINY_NCN))
# insim.SendP(Pyinsim.Packet(Pyinsim.ISP_TINY, ReqI=1, SubT=TINY_NPL))


def GetConnection(ucid):
"""Get connection from UCID."""
return connections[ucid]


def GetPlayer(plid):
"""Get player from PLID."""
return players[plid]


def GetPlayerFromUcid(ucid):
"""Get player from UCID."""
for player in players.itervalues():
if player['UCID'] == ucid:
return player
return None


# TODO: Add more helper functions.


# Packet received events
def VersionCheck(ver):
"""Check the version."""
if ver['InSimVer'] != Pyinsim.INSIM_VERSION:
print 'Invalid InSim version detected.'
sys.exit(0)


def ConnectionJoined(ncn):
"""Add connection to connections dictionary."""
connections[ncn['UCID']] = ncn


def ConnectionLeft(cnl):
"""Delete connection from connections dictionary."""
del connections[cnl['UCID']]


def ConnectionRenamed(cpr):
"""Rename player in connections and players lists."""
connection = GetConnection(cpr['UCID'])
connection['PName'] = cpr['PName']
player = GetPlayerFromUcid(cpr['UCID'])
player['PName'] = cpr['PName']
player['Plate'] = cpr['Plate']


def PlayerJoined(npl):
"""Add player to players dictionary."""
players[npl['PLID']] = npl


def PlayerLeft(pll):
"""Delete player from players dictionary."""
del players[pll['PLID']]


def TookOverCar(toc):
"""Change UCID for player."""
player = GetPlayer(toc['PLID'])
player['UCID'] = toc['NewUCID']


# TODO: Add more packet event handlers.


# Bind events.
insim.Bind({Pyinsim.ISP_VER: VersionCheck,
Pyinsim.ISP_NCN: ConnectionJoined,
Pyinsim.ISP_CNL: ConnectionLeft,
Pyinsim.ISP_NPL: PlayerJoined,
Pyinsim.ISP_PLL: PlayerLeft,
Pyinsim.ISP_CPR: ConnectionRenamed,
Pyinsim.ISP_TOC: TookOverCar})


# Connection lost.
def ConnectionLost():
print 'InSim connection lost.'
sys.exit(0)

insim.ConnectionLost(ConnectionLost)

# Ask for Input
Command = str(raw_input('Command?: '))
Adminpass = str(raw_input('Admin Pass?: '))


# Connect to InSim.

try:
insim.Connect(Server[0], Port[0])
except Pyinsim.socket.error, (ex):
print 'Connection to InSim failed: %s' % (ex.args[1])
sys.exit(0)
else:
# Initailise InSim and request players/connections.
insim.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3Pyinsim', ReqI=1))

# send message
SendMessage(Command)

# Keep program thread alive.
insim.Run()
finally:
insim.Close

DarkTimes
26th October 2008, 01:40
It's nice you are learning some programming. It's an addictive hobby. :)

Yes, connecting to multiple servers is possible, although it does add a few complications. The important thing to note however is that each instance of InSim() can only deal with a single server connection, so you would need to create a new InSim() object for each server you wanted to connect to. You could store them in a Python list or tuple.

This is a rough example of how might do this (it's totally untested). Note: I made the Server and Port lists a dictionary called 'hosts', and created a new list called 'sockets', just as it seemed a little neater. I loop through the list of hosts, create a new InSim connection for each server, and add each connected server to our sockets list.


# Store hosts and ports as a dictionary (dict).
hosts = {'192.168.2.104': 29999, '127.0.0.1': 29998}

# Create a list to store each InSim connection.
sockets = []

# Loop over the hosts dictionary.
for host, port in hosts.iteritems():

# Create new InSim object.
insim = Pyinsim.InSim()

# Connect to this server.
insim.Connect(host, port)

# Add connected InSim object to our sockets list.
sockets.append(insim)Then if you wanted to access a certain server, you could do something like this.


if sockets[0].Connected == True:
print 'Socket 0 connected' # First server in sockets list connected.

if sockets[1].Connected == True:
print 'Socket 1 connected' # Second server in sockets list connected.Or you could loop through each server:
for socket in sockets:
if socket.Connected == True:
print 'Socket connected!'

I hope that make some sense to you. :)

Maybe if you explain what it is you are trying to do I can provide a more detailed example, as there a quite a few ways you could go about doing this.

Crady
26th October 2008, 08:40
Hmm... wrong OS booted now... I can take a closer look on it this afternoon - after the DTM finale :)

Well exactly it should be a way in which an Admin is able to change a setting to every of his Servers once without connecting to all of them...

e.g. You have 5 server and want to change all of them to have 30 minutes quali. Then you have to connect to all 5 and have to type /qual=30 ... Or you have a guy you donīt want on your Servers, you need to visit all of your Servers and type /ban xyz xdays ... etc...

In this case I just want to create small app doing this at once... If I get this to work I would like to create a GUI... I am not sure if I should create a windows GUI or buttons in LFS...

Crady
26th October 2008, 16:27
Ok... I tryed it and got some Problems...

I in only can send the a Message if I am loggend in as Admin and if ther is the command : insim.Run() or in ,y example socket.Run()

But this command stops the program from connecting to the next server... Here is my try so far:

# Ask for Input
Command = str(raw_input('Command?: '))
Adminpass = str(raw_input('Admin Pass?: '))

# Loop over the hosts dictionary.
for host, port in hosts.iteritems():

# Create new InSim object.
insim = Pyinsim.InSim()

# Connect to this server.
insim.Connect(host, port)

# Add connected InSim object to our sockets list.
sockets.append(insim)


# Loop over the sockets
for socket in sockets:
if socket.Connected == True:
print 'connected'
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3XXX', ReqI=1))
SendMessage(Command)

socket.Run()

DarkTimes
26th October 2008, 17:02
Whenever you call InSim.Run() it effectively pauses your program at that point, and no code that comes after it will be executed until the InSim connection closes. That means you need to put it right at the end of your program, something like this.
Command = str(raw_input('Command?: '))
Adminpass = str(raw_input('Admin Pass?: '))

for host, port in hosts.iteritems():
insim = Pyinsim.InSim()
insim.Connect(host, port)
sockets.append(insim)

for socket in sockets:
if socket.Connected == True:
print 'connected'
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3XXX', ReqI=1))
SendMessage(Command)

# TODO: Write rest of program.

# END OF PROGRAM
for socket in sockets:
socket.Run()

Crady
26th October 2008, 18:07
Yes, thats it!

Now it works... Thank you!

Crady
26th October 2008, 21:45
ok, ok... it only seems to work on my testing place at home with 2 Servers...

Now I found another prolem:

The "real" servers are hosted at 500Servers. I am talking about 4 Server where always 2 Server have the same IP - just different ports...

If I now start the Program it only connects to 2 Server (to one for each IP) instead of all 4..

my hosts are configured like that:

hosts = {'192.168.2.104': 29999, '192.168.2.104': 2998, '127.0.0.1':2997, '127.0.0.1': 29996}

Any Idea? Perhaps I create a 2 Dictionaries? and then loop over 2 Dictionaries?

Crady
27th October 2008, 11:38
Ok... I solved this problem:

The ports are unique. So I changed the order in the dictionary to {port: host} and it works...

Thank you!

But now there encounters an other question:

By using this technique: Is it possible to use your RollingStart and FlagMessages program - or even other progrmas to work on more than one Server at time? I mean if I let the insim.connect loop over 4 or more server Do I have a rolling start option or a displayed blue/yellow flag message at all Server or do you think the program will become messed?

If I will work I would recommend that you use this option to your next programs. So teams and leagues are able to use them by only starting one app instead of one for each server..

Crady
31st October 2008, 15:54
DarkTimes,

Have been some busy these days... But now I have time to go further :)

Ok, as I told you everything works as mentioned... But now I again have one Question:

Is it possible to do the "SendMessage(Command)" to a special time or lets say every 10 Minutes?

In this way I could make this program sending some "news" every 10 Minutes as message or a "Time to sleep" warning a 00:00h every day...

like this:

for socket in sockets:
if socket.Connected == True:
print 'connected'
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3XXX', ReqI=1))
SendMessage('/msg ' + News) #every 10 Minutes
SendMessage('/mag Time to sleep now') #at 00:00h every day

# TODO: Write rest of program.

# END OF PROGRAM
for socket in sockets:
socket.Run()

DarkTimes
31st October 2008, 18:37
Yes, it's possible to do this. The best way would be to use Python's built-in threading.timer class, which allows you to set up a timer which calls a function or method after a certain number of seconds have elapsed.


INTERVAL = 600 # Number of seconds in 10 minutes.

def timer_event():
# Timer event fired, send message.
SendMessage('/msg {0}' % (News))

# Start timer again.
timer = threading.Timer(INTERVAL, timer_event)
timer.start()

# Start timer.
timer = threading.Timer(INTERVAL, timer_event)
timer.start()

That's a rough example of how you might do it, you can look at the RollingStarts example I posted to see a timer in use, it uses a timer to control how long RCM messages appear on the screen.

If you wanted a message to be sent at a certain time each day, you could setup a timer that elapsed every second or so, and checked to see what the current time is. Then you could send a message


if current_time == message_send_time:
SendMessage(msg)

I'd need to look up the documentation for Python to see what classes or functions you would need for this, but you should be able to find them in Pythons time and threading.timer modules, if you look them up in the standard library yourself.

HTH :)

Crady
2nd November 2008, 12:16
Hi, DarkTims!

Again thanks:)

Well the timer in this way seems to go in an endless loop and crash the whole program when executed in IDLE... So I changed it pit and it now closes all InSim connecrions one after another after 10 Seconds...

So I am able to use Windows Scedule Service to open the program when I need it...

Because of this I only took a short view on the current_time thing... But I saw that I can get the current system time by including Year, Date, Seconds and Miliseconds as a list... So I first have to strip out only the thime etc.... Well this is done by the Windows Scedule Service too :)

tmehlinger
7th November 2008, 05:00
Wow, I kind of stopped watching this thread, glad everyone is still interested in this.

I have a question about buttons... I've had great success creating individual buttons and making them do all sorts of stuff, but I've hit a brick wall.

Is there any way to make buttons overlap? I want to do something like the menus in CTRA or Alrio where there are a bunch of buttons sitting within a "panel", but I can't seem to make it work. I figured the way to do it was make a big button with no text in it and overlay it with more buttons, but then the underlying button just doesn't display.

Any ideas?

** Edit **
Please ignore, I was using the same ClickID for the buttons meant to make this work... problem solved. Going to keep this here in case other idiots like myself stumble on the same problem. :)

Crady
13th November 2008, 15:59
Hi, Dark Times - or others!

Again I have a little problem to be solved:

I can connect to every host specified in a dictionary and then I can send a message to the host.

But what I would like to do is to send a message to every host in my dictionary every lets say 5 minutes...

I tryed doing it with a timer but it only works for the last connected server - but the message is not sent to all connected servers, it is sent e.g. twice to the last server if I have 2 Server in mx Dictionary:

def timer_message():
for socket in sockets:
sendmessage('/msg Welcome'
timer = threading.Timer(300, timer_message)
timer.start()


an other way I tried was:

a=0

for socket in sockets:
if socket.Connected == True:
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3app', ReqI=1))

while a == 0:
SendMessage('/unban ' + unbans)
time.sleep(300)
a = 0
but here it only connects to the last server - which is sounding quite logical because the program has an endless loop...

Crady
13th November 2008, 21:43
OMG... there is the next idea I need your help for:

Is it possible to place a small button file which opens a list of all connected racer as buttons?

But only connected racers in a special config file should be able to see this button..

Then I can click on a Racer Name and a code will be executed with the racers licence name?

I would like to add the licence names of racers I clicked on to a text file to handle lapper options with them...

DarkTimes
14th November 2008, 14:57
a=0

for socket in sockets:
if socket.Connected == True:
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin=Adminpass,
IName='^3app', ReqI=1))

while a == 0:
SendMessage('/unban ' + unbans)
time.sleep(300)
a = 0

This is an infinite loop, it just keeps looping over the same code again and again, and the program never progresses past this point.

Based on the earlier code posted, I knocked up a quick example of how you could do this:

import Pyinsim
import threading

hosts = {'127.0.0.1': 29999}
sockets = []
timer = None

def SendMessage(socket, msg):
"""Send message to specific socket."""
socket.SendP(Pyinsim.Packet(Pyinsim.ISP_MST, Msg=msg))

def TimerElapsed():
"""Function called when the timer finishes."""
for socket in sockets:
SendMessage(socket, "Hello")
# Start the timer again.
StartTimer()

def StartTimer():
"""Starts the timer."""
timer = threading.Timer(300, TimerElapsed)
timer.start()

for host, port in hosts.iteritems():
# Loop through each host, connect and initailise it, then add it
# to sockets list.
insim = Pyinsim.InSim()
insim.Connect(host, port)
insim.SendP(Pyinsim.Packet(Pyinsim.ISP_ISI, Admin='pass', IName='^3XXX', ReqI=1))
sockets.append(insim)

# Start the timer.
StartTimer()

# END OF PROGRAM
for socket in sockets:
socket.Run()Hope that helps some.

Crady
14th November 2008, 16:47
Dark Times,

again and again... Thank you :)

works like charme :)

Next aim will be the button things... Do you have a usefull manual for creating buttons - I mean either I will need a mall button which opens a bigger button to type text in (which could be used later) or I will need a small button to display a clickable drivers list which gives me the licence name into a variable...

Crady
18th November 2008, 00:48
Dark Times,

Does pyinsim0.1.6 also run with python2.6 or do I have to sty at 2.5.2?

DarkTimes
18th November 2008, 01:58
edit: irrelevant.

DarkTimes
13th December 2008, 12:34
edit: irrelevant.

DarkTimes
13th December 2008, 19:01
edit: irrelevant.

Drift King CZ
20th January 2009, 22:44
I downloaded Python 2.5.4 and Pyinsim 0.1.9, but the installation doesn't work on Windows Vista. Windows just report that it's unable to run the file. What should I do?

DarkTimes
21st January 2009, 17:57
I don't know why it doesn't work on Vista. You can just unzip the pyinsim-0.1.9.zip and copy the file pyinsim.py into the same directory as your program's files.

Drift King CZ
22nd January 2009, 07:20
I tried to do it manually, but I probably moved it to bad directory or I don't know. I moved it to Python25\Lib\site-packages\ and when I ran insim program written in python, it immediately shutted down as usually without pyinsim library which means it didn't work.

So I copied Python25 folder from my computer running on Windows XP and it worked.

Crady
22nd January 2009, 10:05
Under Vista you have to execute the installer as Administrator. Otherwise it will not work...

DarkTimes
22nd January 2009, 18:48
On a random note, I was playing around with NetBeans 6.5 (http://www.netbeans.org/index.html) today, which has newly integrated support for Python (http://www.netbeans.org/features/python/index.html), and it seems to work great! What struck me most was some very comprehensive auto-completion (although still not as good as WingIDE), and especially an excellent inline debugger. Definietly a nice free alternative to the costly WingIDE, the bloated Eclipse and the endearingly homemade Stani's Python Editor. Definietly one to watch, as this is just the first version.

Crady
22nd January 2009, 20:21
Thank you! I am using eclipse...

paXton
31st January 2009, 13:11
Hey DarkTimes!

Your Pyinsim is a very nice work! Thanks for that. :thumb:

After my first tries with python some years (?) ago, when marcoz released his RaceManager, i stopped due to some issues.

Now i started again with python and insim, and it seems much nicer with pyinsim and the new insim than before.

After struggling the last days with some differences between the .py-sources in the forum and the pyinsim-template (with/without Player-class/player-npl-list), i think I'm now back in business to do my last insim-stat ideas with python.

I found, that GetTimeStr() from pyinsim.py doesn't work as expected.
Here's my noobish workaround (not fully tested).


def GetTimeStr(self):
"""Get a string representing the time (h:mm:ss.ttt).

Return:
A string representing the time.

"""
return '%d:%.2d:%.2d.%.3d' % (self.GetHours(), int(self.GetMinutes() % 60),
self.GetSeconds(), self.GetMilliseconds())


Cu later... (thread subscribed) :)
paXton


BTW: Sorry for my bad English.

DarkTimes
31st January 2009, 13:39
OK - thanks. There is a small mistake in the way the time is formatted. The actual maths that figures out the hours/min/sec/ms is correct, it's just a mistake in the formatting.

If you change the method to look like this, it should work fine:

def GetTimeStr(self):
"""Get a string representing the time (h:mm:ss.tt).

Return:
A string representing the time.

"""
return '%d:%.2d:%.2d.%.3d' % (self.GetHours(), self.GetMinutes(),
self.GetSeconds(), self.GetMilliseconds())
I've fixed the bug in my version. I don't know when I will have time to test/release the next version, as it has a lot of changes.

Thanks

paXton
31st January 2009, 17:40
If you change the method to look like this, it should work fine:


sorry, it doesn't. the minutes contain always the full hours.
for example: 1h 20min 12.334sec -> 1:80:12.334

look at my code (http://www.lfsforum.net/showthread.php?p=1059639#post1059639).

Ciao... paXton

DarkTimes
31st January 2009, 18:22
Ah, you didn't mention what the bug was in your bug report. I looked at the code, saw a bug with the formatting, and presumed I'd fixed the one you were talking about. :p

Anyway, no biggie. I've added your fix to my version. Thanks for the help. :)

Here's some code:

def GetMinutes(self):
"""Get the time in minutes.

Returns:
The minutes component of the time.

"""
return int(math.floor(self.__ms / 1000 / 60) % 60)


def GetTimeStr(self):
"""Get a string representing the time (h:mm:ss.tt).

Return:
A string representing the time.

"""
return '%d.%.2d:%.2d.%.3d' % (self.GetHours(), self.GetMinutes(),
self.GetSeconds(), self.GetMilliseconds())

DarkTimes
31st January 2009, 22:43
I am uploading a BETA of Pyinsim 0.2, which contains many changes to the library. It still requires some testing, so I am not designating it the official release just yet. When this comes out of beta, I will make this Pyinsim 1.0. You can download it at the bottom of this post. The existing pyinsim examples in this thread have not been updated, so please look at templates\pyinsim_template.py to see some code examples for 0.2/1.0RC.

Changes since 0.1.9:

Members Renamed:

In order to better comply with the Python style-guidelines, a great many methods, functions and variables have been renamed. The following rules have been applied:


Module, function and method names begin with lower-case character.
Class names are capitalised.
Members preceded by an underscore '_' indicate they are private to the module.
Members preceeded by double-underscore '__' indicate they are private to a class.
Members suffixed by an underscore '_' are named this way to prevent clashes with existing members (EG type_ or str_).
ALL-CAPS indicates the member is a constant.

Members Renamed Redux

In addition to this, several functions and methods have been generally renamed.


ToUnicode renamed strToUnicode
ToMps renamed speedToMps
ToKph renamed speedToKph
ToMph renamed speedToMph
LFSTime.GetHours renamed to LFSTime.hours
LFSTime.GetMinutes renamed to LFSTime.minutes
LFSTime.GetSeconds renamed to LFSTime.seconds
LFSTime.GetMilliseconds renamed to LFSTime.milliseconds
LFSTime.GetTotalMilliseconds renamed to LFSTime.totalMilliseconds
LFSTime.GetTimeStr renamed to LFSTime.timeStr.
InSim.ConnectionLost renamed to InSim.bindConnectionLost.

Further Changes to LFSTime class.

The method LFSTime.GetLapTimeStr has been removed, and now LFSTime.timeStr serves for both lap time and time strings. The signature for this method is:

str LFSTime.timeStr(bool hours=False)Set hours=True to force use of the hours component of the time, which will otherwise be omitted unless hours > 0.

Changes to InSim.send()

The method InSim.send(data) has been renamed to InSim.sendB(data), which accomplishes the same thing. The send method is now a shorthand for sending packets, without having to create a seperate packet object first. For instance, what was previously:

insim.sendP(pyinsim.Packet(pyinsim.ISP_MST, Msg='Hello, world!'))Has now been simplified to:

insim.send(pyinsim.ISP_MST, Msg='Hello, world!')This is now the recommended way of sending packets, however previous usage of sendP still works as it did before.

Changes to InSim.bind()

InSim.Bind has been changed, it now no longer accepts a dictionary of event handlers, but a separate bind call must now be made for each one. For example, whereas before you did this:

insim.bind({ISP_VER: versionCheck, ISP_MSO: messageReceived})You must now do:

insim.bind(ISP_VER, versionCheck)
insim.bind(ISP_MSO, messageReceived)
General Changes


Fixed bug in checkIP() function.
Fixed bug in LFSTime.timeStr() method.
Fixed bug in LFSTime.minutes() method.
Fixed bug in ISP_SMALL packet.
Removed keepAlive parameter from InSim constructor.
Added ISP_ALL packet type, which allows you to specify a single callback for all packet events.
Added bool InSim.isBound(type_=ISP_NONE) method, which returns True if an event has already been bound.
Added insim reference to connection lost callback.
Added bindThreadError(callback) method, which allows you to bind a callback to catch an error from the internal receive thread.

Pyinsim Template

The template has completely been rewritten, in order to comply with the many updates to the pyinsim module in this release. It is much better now.

Documentation

The module documentation has been moved to the epydoc documentation generator, which provides much more comprehensive documentation than before. The whole documentation has also been revised, tweaked and brought up to date. The new documentation is a large improvement. You can access it by going to docs\index.html.

Future

I was also wondering whether people would appreciate some examples of how to build a GUI application with Pyinsim?

Upload removed, see next post...

DarkTimes
1st February 2009, 13:42
Uploaded version 0.2.1 BETA.

Changes since 0.2


Fixed bug with threadError event.
Some light optimisation of the internal receive thread.
Renamed the 'templates' folder to 'examples'.
More improvements to documentation.
pyinsim_template: General tweaks and refactoring.
pyinsim_template: Added threadError event handler and some useful debugging code.


Note: beta version removed, see first post for info.

DarkTimes
5th February 2009, 19:16
Uploaded pyinsim 1.0 release to first post. It's no longer in beta. :)

Changes since 0.2.1 beta:


Added pack() method to _CarTrackPacket() class.
Added several new examples to /examples documentation folder
Lots of small internal tweaks.

DarkTimes
11th February 2009, 18:01
Uploaded pyinsim 1.0.1 to the first post.

Changes since 1.0:


Lots of small internal changes, rewrote _Buffer class to further simplify InSim.__receiveThread, also changed the way in which InSim.run() blocks the calling thread, which should save a few CPU cycles.
InSim.connect() now checks that the socket is not already connected before attempting to connect.
strToUnicode() no longer removes colour codes from the string, set the new parameter cols=False to return to the previous functionality.

Nadeo4441
11th February 2009, 18:12
Thanks for the updates! At least i wont be bored tonight, my apps are still running at 0.1.9 version...

DarkTimes
19th February 2009, 14:20
Uploaded pyinsim 1.1 to the first post.

Changes since 1.0.1:


Added OutSim and OutGauge support.
You can now create multiple event-handlers for a single packet event.
You can now also bind multiple event-handlers to connectionLost and threadError events.
Added InSim.raisePacketEvent(packet) method, which is useful for testing and debugging.
Added optional name parameter to InSim constructor and InSim.getName() method, which is not used by pyinsim, but useful if you need to tell multiple connections apart.
Fixed syntax error bug in PSE_* flags.


OutSim and OutGauge

There are two new classes, named OutSim and OutGauge. They both work the same way (they inherit from the same base class internally) and have been designed to work as similarly to InSim as possible.

Here's a typically trivial example of using OutGauge, which checks the OG_SHIFTLIGHT flag, and prints out a message whenever the shift-light comes on. In order to enable OutGauge you must update your LFS cfg.txt file.

OutGauge Mode 0 :0-off 1-driving 2-driving+replay
OutGauge Delay 1 :minimum delay between packets (100ths of a sec)
OutGauge IP 0.0.0.0 :IP address to send the UDP packet
OutGauge Port 0 :IP port
OutGauge ID 0 :if not zero, adds an identifier to the packet

Here is the example.

import pyinsim

def outgaugePacket(outgauge, packet):
if packet['Flags'] & pyinsim.OG_SHIFTLIGHT:
print 'Shift-light on!'

def timeout(outgauge, timeout):
print 'OutGauge timed out in %d seconds' % timeout

outgauge = pyinsim.OutGauge(timeout=20.0)
outgauge.bind(outgaugePacket)
outgauge.bindTimeout(timeout)

try:
outgauge.connect('localhost', 30000)
outgauge.run()
except pyinsim.socket.error, err:
print 'OutGauge Error:', err

The timeout is new and important, as OutGauge uses UDP and UDP is stateless. It specifies the amount of seconds to wait for a response from LFS before timing out. If no response is received within the specified timespan, then a timeout event is raised and the connection is closed. Setting the timespan to zero will wait indefinitely. The default is 30.0 seconds.

There are a lot of changes and tweaks to the code, a lot of it has been completely rewritten, and a lot has been added. Goes without saying, it may have one or two bugs, but it all seems to work well in testing. I plan to work on some more stuff in the future, like receiving MCI and NLP packets on a separate UDP connection (which is technically possible at the moment, but looks messy), and I would also like to add some more advanced examples, such as using wxPython to create gauges for an OutGauge program and so on. But anyway, I hope it all works OK as it is for the moment.

As always, any comments, suggestions etc.. are welcome!

DarkTimes
2nd March 2009, 15:30
I was just looking through the recent demos for wxPython 2.8.9.2 (http://www.wxpython.org/index.php) and saw this new one. It's like they were reading my mind. :)

This will make creating OutSim and OutGauge apps a lot easier.

morpha
2nd March 2009, 16:10
Uhh I love that :D

As for pyinsim itself, I'm currently experimenting with Struct objects to use the compiled format string and classes for the packets instead of UserDicts.

_PACKET_FORMAT = {
ISP_ISI : struct.Struct('4B2HBcH16s16s'),
ISP_VER : struct.Struct('4B8s6sH'),
ISP_TINY : struct.Struct('4B'),
ISP_SMALL : struct.Struct('4BI'),
ISP_STA : struct.Struct('4BfH10B6s2B'),
ISP_SCH : struct.Struct('8B'),
ISP_SFP : struct.Struct('4BH2B'),
ISP_SCC : struct.Struct('8B'),
ISP_CPP : struct.Struct('4B3i3H2Bf2H'),
ISP_ISM : struct.Struct('8B32s'),
ISP_MSO : struct.Struct('8B128s'),
ISP_III : struct.Struct('8B64s'),
ISP_MST : struct.Struct('4B64s'),
ISP_MTC : struct.Struct('8B64s'),
ISP_MOD : struct.Struct('4B4i'),
ISP_VTN : struct.Struct('8B'),
ISP_RST : struct.Struct('8B6s2B6H'),
ISP_NCN : struct.Struct('4B24s24s4B'),
ISP_CNL : struct.Struct('8B'),
ISP_CPR : struct.Struct('4B24s8s'),
ISP_NPL : struct.Struct('6BH24s8s4s16s8Bi4B'),
ISP_PLP : struct.Struct('4B'),
ISP_PLL : struct.Struct('4B'),
ISP_LAP : struct.Struct('4B2I2H4B'),
ISP_SPX : struct.Struct('4B2I4B'),
ISP_PIT : struct.Struct('4B2H8B2I'),
ISP_PSF : struct.Struct('4B2I'),
ISP_PLA : struct.Struct('8B'),
ISP_CCH : struct.Struct('8B'),
ISP_PEN : struct.Struct('8B'),
ISP_TOC : struct.Struct('8B'),
ISP_FLG : struct.Struct('8B'),
ISP_PFL : struct.Struct('4B2H'),
ISP_FIN : struct.Struct('4B2I4B2H'),
ISP_RES : struct.Struct('4B24s24s8s4s2I4B2H2BH'),
ISP_REO : struct.Struct('36B'),
ISP_NLP : struct.Struct('4B'),
ISP_MCI : struct.Struct('4B'),
ISP_MSX : struct.Struct('BBBB96s'),
ISP_MSL : struct.Struct('BBBB128s'),
ISP_CRS : struct.Struct('4B'),
ISP_BFN : struct.Struct('8B'),
ISP_AXI : struct.Struct('6BH32s'),
ISP_AXO : struct.Struct('4B'),
ISP_BTN : struct.Struct('12B240s'),
ISP_BTC : struct.Struct('8B'),
ISP_BTT : struct.Struct('8B96s'),
ISP_RIP : struct.Struct('8B2H64s'),
ISP_SSH : struct.Struct('8B32s'),
NODELAP : struct.Struct('2H2B'),
COMPCAR : struct.Struct('2H4B3i3Hh'),
OUTSIM : struct.Struct('I12f4i'),
OUTGAUGE : struct.Struct('I4sH2B12f16s16si'),
}

Classes look like thisclass IS_TINY:
def __init__(self, ReqI = 0, SubT = None):
self._Size = 4
self._Type = ISP_TINY
self.ReqI = ReqI
self.SubT = SubT
def pack(self):
return _PACKET_FORMAT[self._Type].pack(self._Size, self._Type, self.ReqI, self.SubT)

and the __raisePacketEvent would simply do something like(...)
packet = _PACKET_DEFINITIONS[packetType](*_PACKET_FORMAT[packetType].unpack(data)[2:])
(...)
Still working on the class definitions, don't know how it'll work out but I'll definitely give it a try. UserDicts are a good solution but they do feel kind of "hackish" to me :tilt:

Dygear
3rd March 2009, 06:48
As for pyinsim itself, I'm currently experimenting with Struct objects to use the compiled format string and classes for the packets instead of UserDicts.

Awesome style mate, love it! Kinda looks like a php pack / unpack function to me!

DarkTimes
3rd March 2009, 19:07
Uhh I love that :D

As for pyinsim itself, I'm currently experimenting with Struct objects to use the compiled format string and classes for the packets instead of UserDicts.
(...)

Still working on the class definitions, don't know how it'll work out but I'll definitely give it a try. UserDicts are a good solution but they do feel kind of "hackish" to me :tilt:
Well, keep me updated on your progress. While I made the decisions I made for pyinsim for good reasons, I am planning to start working on pyinsim 2.0 at some point, and I'm open to any and all suggestion and improvement ideas. While I'm still not sure having a separate class for each packet is ideal, I do think a lot of improvements (read simplification :p) could be made to the current implementation.

At the moment I'm working on a few examples of making OutGauge and OutSim apps using the speedmeter module, which I'll upload once I get them finished. I've basically got them working, but they've exposed a couple of annoying issues with pyinsim, so I'll need to release another version of pyinsim before I can finish them.

morpha
10th March 2009, 09:22
By now I'm thinking your solution was in fact the better one, I had originally intended to use a base class providing a pack function like this:def pack(self):
return _PACKET_FORMAT[self.Type].pack(*self.__dict__.values())

but unfortunately the dict is not in the order the variables have been assigned in (meaning it's effectively packing in random - or at least not the intended - order) and I don't see an efficient way to fix that.

Anyway, here're some new and some optimized functions:_eatNullChars = lambda str_: str_[:str_.find('\0')]
Because not all strings are really NULL-terminated, such as the numberplate. Not sure about this one though, find might not be optimal.

def speedToMs(speed):
"""Convert speed to meters per second.

Args:
speed - The speed to convert.

Returns:
Metres per second.

"""
return speed / 327.68


def speedToKph(speed):
"""Convert speed to kilometers per hour.

Args:
speed - The speed to convert.

Returns:
Kilometers per hour.

"""
return speed / 91.02

def speedToMph(speed):
"""Convert speed to miles per hour.

Args:
speed - The speed to convert.

Returns:
Miles per hour.
"""
return speed / 146.486067

def directionToDegrees(direction):
"""Convert direction to degrees.

Args:
direction - The direction to convert.

Returns:
Degrees ranging from 0 to 180°.
"""
return direction / 182.04

def lengthToMetres(length):
"""Convert length to meters.

Args:
length - The length in LFS world coordinate units.

Returns:
The length in meters.
"""
return length / 65536.0
Direct speed conversion, added direction conversion and renamed distToMeters (metre is a unit of length so this seems more appropriate).

I'm still working on the classes and I'll post my version as soon as it's done but as I said, I do think your solution is the more efficient one.

DarkTimes
10th March 2009, 13:06
By now I'm thinking your solution was in fact the better one, I had originally intended to use a base class providing a pack function like this:def pack(self):
return _PACKET_FORMAT[self.Type].pack(*self.__dict__.values())
but unfortunately the dict is not in the order the variables have been assigned in (meaning it's effectively packing in random - or at least not the intended - order) and I don't see an efficient way to fix that.
Yeah, I had that problem too, it's to do with the way hashtables are stored in memory. I had to turn the dict back into a list by looping through the keys in the packet definition, and then use that to pack the struct.


Anyway, here're some new and some optimized functions:_eatNullChars = lambda str_: str_[:str_.find('\0')]Because not all strings are really NULL-terminated, such as the numberplate. Not sure about this one though, find might not be optimal.Well at the moment I just do
_eatNullChars = lambda str_: str_.rstrip('\000')which seems pretty optimal to me really. Stuff like the numberplate is unpacked so seldom though I'm not sure a change here would really make much difference to be honest.

Direct speed conversion, added direction conversion and renamed distToMeters (metre is a unit of length so this seems more appropriate).

I'm still working on the classes and I'll post my version as soon as it's done but as I said, I do think your solution is the more efficient one.Thanks for these. :)

morpha
10th March 2009, 17:40
Well at the moment I just do
_eatNullChars = lambda str_: str_.rstrip('\000')which seems pretty optimal to me really. Stuff like the numberplate is unpacked so seldom though I'm not sure a change here would really make much difference to be honest.
Your rstrip() may not iterate over as many chars as my find() does, but it returns a copy instead of a simple slice - eliminating the performance gain - and it's not as reliable.

My numberplate reads "42D" ingame, it's correctly unpacked using my implementation of _eatNullChars but yours returns unexpected additional characters including the terminating NUL.
Coming from LFS, the raw string looked like this
rawstr = '42D\000!!6&'now because it's not NUL-padded, the characters following the terminating character are not guaranteed to be NUL, causing your function to effectively do nothing with this particular type of string.

>>> def stwtch():
tst = time.time()
tstr = 'abcdefghijklmnopqrstuvwxyz\000'
for i in xrange(0, 10000000, 1):
tstr.rstrip('\000')
return time.time() - tst

>>> print stwtch()
8.02099990845
>>> def stwtch():
tst = time.time()
tstr = 'abcdefghijklmnopqrstuvwxyz\000'
for i in xrange(0, 10000000, 1):
tstr.find('\000')
return time.time() - tst

>>> print stwtch()
7.92100000381

I win :razz:

DarkTimes
10th March 2009, 22:51
OK - well I didn't realise it was a bug - of course I'll fix that. In terms of performance, a few milliseconds here or there isn't an issue with an InSim program really, as the actual amount of data being processed is quite small. If you just write all the InSim data to a binary file and then parse the packets out of that instead of from a socket, then pyinsim will read a whole 40 lap race in about a second, and I'd wager the majority of that is just basic file IO. The goal of pyinsim was always to be as easy to use for the developer as possible (hence the use of Python to begin with :)), so my advice is to make it's as easy to use and fun to write as possible, and leave performance concerns to those silly C programmers. :)

morpha
12th March 2009, 00:37
My version finally reached a state in which a comparison was possible.
I expected it to consume a little more RAM than your version but less CPU cycles due to the precompiled formatting strings.
However, after a 2 lap race on AS5 (2 AI drivers) with ISF_MCI @ 50ms interval, your version had allocated a total of 6220K, my version with roughly 50 additional classes surprisingly allocated just 6036K. I guess the UserDict module adds a little unecessary - but in this case completely insignificant - overhead.

As for CPU usage, no accurately measureable difference, 0 to 2% for both versions in this particular test (receive all packets and just print "recv pack")

So far the only difference between our versions seems to be a few bytes of RAM and "packet.data" instead of "packet['data']".

By the way the _eatNullChars I posted is still not perfect, it's eating the last char of a string that doesn't contain any NULL chars (like a plate with 8 characters) because find() returns -1 instead of None if the string does not contain the needle. I don't think this is the best solution but it's the best I could come up with:_eatNullChars = lambda str_: str_[:(str_.find('\0') if str_.find('\0') != -1 else None)]

DarkTimes
2nd April 2009, 09:38
Uploaded 1.1.2 to the first post (http://www.lfsforum.net/showthread.php?t=41890).

Changes since 1.1.1


Added insimConnect() function, which provides a shorthand for creating and initialising an insim connection.
Renamed distToMeters() to lengthToMeters().
Added directionToDegrees(), radiansToDegrees() and radiansToRpm() functions.
Added checkAdmin() function which checks admin pass length.
Several small optimisations.
Fixed: bug with removing terminating line characters from C-style strings.
Fixed: checkIP now returns True if the IP string is 'localhost'.
Fixed: thread error bug when attempting to reconnect after the connection has been closed.


Thanks for morpha for suggestions and improvement ideas.

morpha
13th April 2009, 20:03
I decided to release my version despite it's dodgy state, still needs testing and OutSim and OutGauge are currently not working. I also removed the xml packet stuff but it wouldn't be hard to port if someone actually uses it :thumb:

It's based on 1.1.2 so the newly added conversion functions are included, in optimized form (2.0 * math.pi replaced with 6.28318531 etc., there's no point in doing these calculations over and over again).
Also changed the spelling of some function names to proper, british English :razz:

DarkTimes
14th April 2009, 21:50
Cool thanks. :)

DarkTimes
15th April 2009, 09:39
Uploaded pyinsim 1.1.3 to the first post.

Changed since 1.1.2

Added timeStr(time, hours=False), which is a shorthand for creating a formatted time string.
Added mpsToMph(mps) and mpsToKph(mps) functions, which converts meters per second to MPH and KPH.
Added distance(a=(0, 0, 0), b=(0, 0, 0)) method, which returns the distance between two points.
Fixed bug in WEA_* weather enum
Fixed bug with Tyres in IS_PIT packet

DarkTimes
30th April 2009, 17:43
Awesome style mate, love it! Kinda looks like a php pack / unpack function to me!
Haha, I'm two months late, but yes, I just re-read this. I wanted to say that pyinsim uses this extensively internally, in fact as far as I'm aware the whole struct.unpack() method is the only suck-free way to unpack binary formatted strings in Python. But anyway, well, I think we can all agree that dynamically typed languages are FTW! Pity those poor souls with their old-fashioned static languages and the hideous amounts of boilerplate they require. :D

*I love Python. I really, really love it. :)

DarkTimes
3rd May 2009, 13:16
I wrote a small example of creating an OutGauge app with wxPython (http://wxpython.org/). You need version 2.8.9.2 of wxPython or better for this to work. It's just a basic KPH gauge, but it demonstrates the principle.

import wx
import wx.lib.agw.speedmeter as sm
import math
import os
import pyinsim


HOST = 'localhost'
PORT = 30000
TIMEOUT = 10 # Seconds.


class OutGaugeApp(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)

# Setup layout
self.CreateStatusBar()
sizer = wx.BoxSizer()
self.SetSizer(sizer)

# Create gauges.
self.createSpeedometer()
sizer.Add(self.speedometer, 1, wx.EXPAND)
self.closing = False

# Start OutGauge.
self.og = pyinsim.OutGauge(TIMEOUT)
self.og.bindTimeout(self.onTimeout)
self.og.bindError(self.onError)
self.og.bind(self.onOutGauge)

try:
self.og.connect(HOST, PORT)
except pyinsim.socket.error, err:
self.SetStatusText('OutGauge Error: %s' % err)

# Bind window events.
self.Bind(wx.EVT_CLOSE, self.onClose)

#Show window.
self.Center()
self.Show()

def onClose(self, evt):
self.closing = True
self.og.close()
self.Destroy()

def createSpeedometer(self):
extrastyle = sm.SM_DRAW_HAND | sm.SM_DRAW_SECTORS | sm.SM_DRAW_MIDDLE_TEXT | sm.SM_DRAW_SECONDARY_TICKS
self.speedometer = sm.SpeedMeter(self, extrastyle=extrastyle)
self.speedometer.SetAngleRange(-math.pi / 6, 7 * math.pi / 6)
intervals = range(0, 201, 20)
self.speedometer.SetIntervals(intervals)
colours = [wx.BLACK] * 10
self.speedometer.SetIntervalColours(colours)
ticks = [str(interval) for interval in intervals]
self.speedometer.SetTicks(ticks)
self.speedometer.SetTicksColour(wx.WHITE)
self.speedometer.SetNumberOfSecondaryTicks(5)
self.speedometer.SetTicksFont(wx.Font(7, wx.SWISS, wx.NORMAL, wx.NORMAL))
self.speedometer.SetMiddleText("Km/h")
self.speedometer.SetMiddleTextColour(wx.WHITE)
self.speedometer.SetMiddleTextFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD))
self.speedometer.SetHandColour(wx.Colour(255, 50, 0))
self.speedometer.DrawExternalArc(False)

def onTimeout(self, og, timeout):
if hasattr(self, 'closing') and not self.closing:
og.close()
self.SetStatusText('OutGauge: LFS timed out')

def onError(self, og, err):
og.close()
self.SetStatusText('OutGauge Error: %s' % err)

def onOutGauge(self, og, packet):
# Update the control.
kph = pyinsim.mpsToKph(packet['Speed'])
self.speedometer.SetSpeedValue(kph)


if __name__ == '__main__':
app = wx.App(redirect=True)
style = wx.DEFAULT_DIALOG_STYLE | wx.MINIMIZE_BOX
OutGaugeApp(None, wx.ID_ANY, 'OutGauge', size=(400, 400), style=style)
app.MainLoop()

If you don't like copying and pasting you can download the source below.

DarkTimes
18th June 2009, 16:40
Uploaded 1.1.4 to the first post.

Changes since 1.1.3:

Removed LFSTime class. This has now been replaced by two new functions time(ms) and timeStr(ms).
Added metersToMiles(meters) and metersToKilometers(meters) functions.
Added Packet.insim() and _CarTrackPacket.insim(), which will return a reference to the InSim object which created the packet.
Renamed InSim.getName() to InSim.name().
Added several new examples which can be found in the /examples folder.


The new time(ms) function replaces the LFSTime class. It accepts the time in milliseconds as a parameter and returns a tuple containing the hours, minutes, seconds and thousandths of the time. For example:

h, m, s, t = pyinsim.time(milliseconds)

To convert the time into a formated string, use the timeStr(ms) function.

lapTimeStr = pyinsim.timeStr(milliseconds) # Returns 1:24.567 etc..

xergio
18th June 2009, 18:07
Hi.

First of all, very good job! :D I've abandoned PHPLFS and I adopted Pyinsim 4ever! :P

Well, I downloaded the last version 1.1.4, and when i've tested (for example) the buttons example, I got this traceback:

Traceback (most recent call last):
File "insim_buttons.py", line 40, in <module>
insim.send(pyinsim.ISP_ISI, Admin='Pass', IName='^3pyinsim', Flags=pyinsim.ISF_LOCAL)
File "/home/salvarez/_/_p/salvarez/pylfs/pyinsim_1.1.4/examples/pyinsim.py", line 1478, in send
self.sendB(Packet(packetType, **values).pack())
File "/home/salvarez/_/_p/salvarez/pylfs/pyinsim_1.1.4/examples/pyinsim.py", line 1748, in pack
return struct.pack(self.__packStr, [values.append(self[p[0]]) for p in _PACKET_DEFS[self['Type']]])
UnboundLocalError: local variable 'values' referenced before assignment

I've switched this commented lines:

def pack(self):
"""Pack the current packet values into a binary string, which can then
be sent to InSim.

Returns:
A binary formatted string.

"""
return struct.pack(self.__packStr, [values.append(self[p[0]]) for p in _PACKET_DEFS[self['Type']]])

# values = []
# for packetDef in _PACKET_DEFS[self['Type']]:
# values.append(self[packetDef[0]])
# return struct.pack(self.__packStr, *values)

And all work now.

It's correct?

ty!

DarkTimes
18th June 2009, 18:16
Yes, you're quite right, the comments should have been switched. In fact I did switch them before I built the distribution, but maybe I forgot to save it.

I've uploaded a new 1.1.4.1 with this error corrected.

Thanks for pointing this out!

Tim NL
19th June 2009, 17:22
Hi,
I downloaded last night version 1.1.4.1 of Pyinsim and installed Python 2.6.
Everything works fine and very nice to program :thumb:.

I found a small bug in the lap times example script.
When i drive a lap the output to the console is ss:mm.xx incase of mm:ss.xx

example:
Lap: [NLR]Tim - FOX - 10:01.030 -- but it was a laptime of 1:10.030
Lap: [NLR]Tim - FOX - 4:01.910 -- but it was a laptime of 1:04.910

DarkTimes
20th June 2009, 10:26
It seems that was the day for lots of stupid little errors sorry, I've uploaded a fixed version to the first post.

DarkTimes
20th June 2009, 14:47
I'm working on a new version I think I'm going to designate pyinsim 1.5 (pyinsim 2.0 is going to be the one that supports Python 3.0). Basically I've finally given in and decided to do away with the whole packets are dictionaries thing, and now each packet has its own individual class. One very cool thing is that pyinsim already contains all the data for each packet, just in a different format, so I wrote some scripts that converted the old packet format into the new one, which means that after a couple of hours work I already have every packet converted, and can also be 95% confident that the packet definitions are bug free, as I am 95% certain that the existing definitions are bug free.

The new packet format allows me to do new things that weren't feasible before, such as proper support for arrays (so things like Tyres and vectors are now proper tuples), and also things like inline converting of times to string format and strings into unicode, so they no longer require separate function calls. That means that what was previously something like...

lapTime = pyinsim.timeStr(lap['LTime'])
playerName = pyinsim.strToUnicode(npl['PName'])

Could now be simplified to...

lapTime = lap.LTimeStr()
playerName = npl.PNameU() # U for Unicode

That being said the actual API is still in development, so how it actually works may change. One of the design philosophies of pyinsim is that it should hide as little from the developer as possible (nothing magic happens behind the scenes), so I need to be careful with any new additions like this that they are purely to make the API simpler and less annoying to use, and not just to placate lazy programmers (like me). I want programmers using pyinsim to have to think about InSim and program to it, and not just to some shiny API that does everything for you. :p

There are also several new things I want to add such as custom packet definitions, so instead of using pyinsim's IS_NPL packet, you will be able to create your own custom New PLayer packet which pyinsim will treat as if it were its own. I also plan to have some sort of underlying object persistence system, which is hard to explain, but will work like the ReqI does currently and return custom py objects to you in response to requests.

Well, as I say this is all in development, so it may all change, but so far it's all working out as intended. The most time consuming thing will be the documentation, as I want pyinsim to have complete __doc__ and HTML documentation for every packet and every possible value. Sadly it's hard to write a script that does that for you.:)

Sorry for the wall of text. :)

xergio
20th June 2009, 21:19
It sounds very nice :D We'll stay tuned!

DarkTimes
29th June 2009, 16:01
I have uploaded a beta of pyinsim 1.5 to the first post. The module has undergone a great many changes and is not compatible with previous versions.

Changes Summery


Packets are no longer dictionaries and each packet now has its own unique class.
InSim now supports TCP and UDP.
New class OutReceiver() which handles UDP packets sent by LFS on a seperate port, for OutSim, OutGauge and InSim (MCI/NLP etc..).
New event system and events.
Support for custom packets.
Lots of small changes and tweaks.


Packets

Packets are no longer dictionaries, so you no longer access packet attributes using a dictionary key, but instead use the normal object syntax.

def versionCheck(insim, ver):
# It's a proper object! :)
if ver.InSimVer == pyinsim.INSIM_VERSION:
print 'InSim connected!'
else:
print 'Invalid InSim version'

InSim UDP

You can now connect to InSim in UDP mode, as well as with TCP. You can set the connection mode by passing either INSIM_UDP or INSIM_TCP into the InSim() constructor. The timeout specifies the number of seconds to wait before timing out in UDP mode. When a timeout occurs a EVT_TIMEOUT event is raised (see events below).

insim = pyinsim.InSim(pyinsim.INSIM_UDP, timeout=30.0)

OutReceiver

The old OutSim() and OutGauge() objects have been removed are are now replaced with the new OutReceiver(), which handles all packets arriving on a separate UDP port, including OutSim, OutGauge and MCI or NLP. Here is an example of OutGauge using the new system.

import pyinsim

def outgaugePacket(out, og):
# Check if the shift-light is on.
if og.DashLights & pyinsim.DL_SHIFT and og.ShowLights == 0:
print 'Shift-light on!'

# Create a new OutReceiver() for OutGauge with a timeout of 30 seconds.
out = pyinsim.OutReceiver(pyinsim.OUT_OUTGAUGE, 30.0)

# Bind the OutGauge packet event.
out.bind(pyinsim.ISP_OUTGAUGE, outgaugePacket)

# Start the receiver.
out.start('localhost', 30000)

# Block the thread until the connection times out.
out.run()

Events

The old bindThreadError() and bindConnectionLost() methods have been removed and replaced with a new event system. You bind pyinsim events now in the same method that you bind packet events.

import pyinsim

# EVT_CLOSED event handler.
def insimClosed(insim, reason):
if reason == pyinsim.CLOSE_REQUEST:
print 'InSim closed by a call to InSim.close()'
elif reason == pyinsim.CLOSE_LFS:
print 'InSim closed as LFS closed'

insim = pyinsim.InSim()

# Bind a pyinsim event.
insim.bind(pyinsim.EVT_CLOSE, insimClosed)

# Etc..

Currently the available events are as follows:


EVT_CLOSE - InSim connection has closed (contains close reason)
EVT_ERROR - An error has been raised on an internal thread (contains error message)
EVT_TIMEOUT - LFS has timed out (contains timeout seconds)


Download

Please see the first post (http://www.lfsforum.net/showthread.php?p=772627#post772627) for download information.

DarkTimes
29th June 2009, 18:06
I finally got around to reading the documentation for my documentation generator, and realised that I've been doing it all wrong. So I've uploaded an improved version with much better documentation, and also some fixes for silly errors I made in the examples.

Edit: fixed some ****ing stupid bugs as well.

DarkTimes
1st July 2009, 15:40
Uploaded 1.5.3, which has some small fixes. The only interesting thing is that I added back in the OutSim and OutGauge classes, but they are now just wrappers for the OutReceiver class. It's mainly a cosmetic thing, the functionality is exactly the same.

the_angry_angel
1st July 2009, 17:06
I must say DarkTimes, your work with pyinsim is inspiring and solely you are making me warm to the idea of developing in python. The simplicity of your API is fantastic :)

DarkTimes
9th July 2009, 16:39
I discovered a bug in IS_REO in 1.5 beta. I have fixed it, but it haven't had time to make a full binary release, plus it's the only small change. If you plan to use IS_REO then you should update pyinsim.py and replace the current IS_REO with the following class.

class IS_REO:
def __init__(self, ReqI=0, NumP=0, PLID=[]):
self.Size = 36
self.Type = ISP_REO
self.ReqI = ReqI
self.NumP = NumP
self.PLID = PLID

def pack(self):
return struct.pack('BBBB32B', self.Size, self.Type, self.ReqI, self.NumP, *self.PLID)

def unpack(self, data):
self.Size, self.Type, self.ReqI, self.NumP = struct.unpack('BBBB', data[:4])
self.PLID = struct.unpack('32B', data[4:])

This fix will be in the next release.

morpha
12th July 2009, 13:20
I discovered a bug in IS_REO in 1.5 beta...
Couple of things:
Why allow ReqI to be set in the packet's initialisation when the only way for ReqI to be something other than 0 is to receive a response to a TINY_REO? (of which unpack() will take care) Why allow NumP to be set in the initialisation? It should be len(PLID) - PLID.count(0), setting it manually results in what I would call undefined behaviour.

I'd also recommend using the more efficient pre-compiled structs and ridding instruction packets of unpack() and info packets of pack(). My most recent definitions are attached (board doesn't allow .py extension).

DarkTimes
12th July 2009, 13:39
The packet classes were generated programatically by parsing InSim.txt, at some point I will need to manually revise each one, but I haven't had time to do so. Tweaking each packet is a lot of work and I have lots and lots of other things I could be doing. I will switch to precompiled structs at some point too I guess. It is a beta, it's not finished. :)

tmehlinger
26th October 2009, 21:06
Found a few bugs in 1.5.3 beta. Note the bold text:


class IS_NLP:

...

self.Size, self.Type, self.ReqI, self.NumP = struct.unpack('BBBB', data[:4])
offset = 4
while offset < self.Size:
nl = NodeLap()
nl.unpack(data[offset:offset + 4])
self.NodeLaps.append(nl)
offset += 4


A NodeLap struct is 6 bytes long... change the 4s to 6s and it's fixed. :)


class IS_RST:

...

def __init__(self, ReqI=0, RaceLaps=0, QualMins=0, NumP=0, Track='', Weather=0, Wind=0, Flags=0, NumNodes=0, Finish=0, Split1=0, Split2=0, Split3=0):

...

self.Zero = Zero



class IS_MSX:

...

def __init__(self, ReqI=0, Msg=''):

...

self.Zero = Zero



class IS_AXI:

...

def __init__(self, ReqI=0, AXStart=0, NumCP=0, NumO=0, LName=''):

...

self.Zero = Zero


Zero is never defined so these assignments all throw exceptions.

Also, you might consider wrapping the code in onPacketReceived with a try/except block and printing the stack trace on exceptions. I don't know if it's something weird I'm doing and not realizing it, but my Pyinsim apps always fail silently when my Pyinsim-related code raises an exception. I figure it's either me being dumb or the threading module suppressing output to stderr.

Example of what I did:


import traceback

...

def onPacketReceived(self, data):
"""Virtual method called when a packet is received. Handles the keep
alive pulse and raises packet events.

@type data: string
@param data: Packet data as a binary formatted string.

"""
try:
packetType = _packetType(data)

# Keep alive.
if packetType == ISP_TINY:
if _tinyType(data) == TINY_NONE:
self.sendB(data)

# Raise packet event.
if packetType in self.__callbacks:
for callback, _customPacket in self.__callbacks[packetType]:
if _customPacket:
packet = _customPacket()
else:
packet = _PACKET_DEFS[packetType]()
packet.unpack(data)
callback(self, packet)
except:
traceback.print_exc()

DarkTimes
27th October 2009, 10:50
OK - thanks for the bug reports on the beta, I'll fix those.

The packet receive code runs on a separate thread, so any exceptions which occur there happen on a different call-stack from your main program thread, which is why they appear to fail silently. To detect errors which happen on seperate threads in pyinsim you need to bind an event to EVT_ERROR.

def error(insim, err):
print 'Error:', err

insim.bind(pyinsim.EVT_ERROR, error)

tmehlinger
27th October 2009, 11:10
The packet receive code runs on a separate thread, so any exceptions which occur there happen on a different call-stack from your main program thread, which is why they appear to fail silently. To detect errors which happen on seperate threads in pyinsim you need to bind an event to EVT_ERROR.

def error(insim, err):
print 'Error:', err

insim.Bind(pyinsim.EVT_ERROR, error)

A ha! Thanks very much... I figured it was something like that but wasn't sure how to take care of it. :thumb:

tmehlinger
27th October 2009, 12:34
Found one more, also in IS_NLP. The bold code is my fix.


def unpack(self, data):
"""Unpack the packet data from a binary formatted string.

@type data: string
@param data: The packet data as a binary formatted string.

"""
self.Size, self.Type, self.ReqI, self.NumP = struct.unpack('BBBB', data[:4])
lim = self.Size
if self.NumP % 2 == 1:
lim -= 2
offset = 4
while offset < lim:
nl = NodeLap()
nl.unpack(data[offset:offset + 6])
self.NodeLaps.append(nl)
offset += 6


NodeLap packets are six bytes long, but InSim packets always have size divisible by 4. For an odd number of NodeLap structs, this means the packet is two bytes longer than the end of the last NodeLap struct. The last two empty bytes of the packet were getting passed to a new NodeLap for unpacking, which threw an exception.

For quick reference, here's the section from the InSim docs about IS_NLP:


struct IS_NLP // Node and Lap Packet - variable size
{
byte Size; // 4 + NumP * 6 (PLUS 2 if needed to make it a multiple of 4)
byte Type; // ISP_NLP
byte ReqI; // 0 unless this is a reply to an TINY_NLP request
byte NumP; // number of players in race

NodeLap Info[32]; // node and lap of each player, 1 to 32 of these (NumP)
};

tmehlinger
27th October 2009, 15:15
I'd also recommend using the more efficient pre-compiled structs and ridding instruction packets of unpack() and info packets of pack(). My most recent definitions are attached (board doesn't allow .py extension).

In addition to this, if you want to squeeze out even more performance (well... mostly just lower memory footprint), consider using __slots__. Note that you'd need to update your packet classes to new-style class definitions, but that would be an easy search-and-replace.

http://docs.python.org/reference/datamodel.html#slots

I think I'm done spamming the thread now. :D

DarkTimes
5th November 2009, 11:48
Uploaded 1.5.5 beta.

Changes


Fixed bugs with IS_NLP, IS_RST, IS_AXI and IS_MSX noted above
Updated OutGauge to support LFS Z25 (including example and documentation)

DarkTimes
5th November 2009, 11:57
Please note: I have decided to depreciate the old version of pyinsim in favor of the beta, as the beta is clearly much improved now, and so have removed the old download. Much of the example code in this thread is from the old version of the module and so is no longer supported. Please refer to the 'examples' folder within the distribution for up-to-date sample code. At some point I will try to update the code here when I can, but truthfully the examples included should be enough to get anyone up and running.

I have decided to leave the 'beta' moniker attatched to the module for the time being, as I am confident there may still be one or two small bugs lying around.

DarkTimes
6th November 2009, 09:42
I've uploaded a new version of the distribution, as I made a silly mistake with the OutGauge example code and misunderstood a little how the new flags work.

SumBeam
8th November 2009, 14:41
You have a little fault in your example_2.py file... In line 32:

insim.bind(pyinsim.EVT_CL0SED, insimClosed)


EVT_CL0SED (there is a 'null' as O) isn't found in pyinsim.py. I had to change it to EVT_CLOSE (with a real 'O'), to get it working :)

morpha
16th November 2009, 18:16
Some of the packet definitions, while technically correct, can lead to unwanted (but to be expected) behaviour. Certain packets require the last byte of a string to be 0, the current packet definitions don't comply with this requirement in that they don't enforce it.

The affected packets are IS_MST IS_MSX IS_MSL IS_MTC IS_RIP IS_SSHMy suggestion is to reduce the string length by one and add a pad byte at the end of the struct format. IS_MST's pack() would then look like thisdef pack(self):
"""Pack the packet values into a binary formatted string.

@rtype: string
@return: A binary formatted string.

"""
return struct.pack('4B63sx', self.Size, self.Type, self.ReqI, self.Zero, self.Msg)
Without this, LFS will drop the packet and throw a server side error message.

learjet45
17th November 2009, 04:29
Pardon my ignornace, but I have a question.

I'm not a major serious coder or anything. However, I have been looking through and I'm not sure if I missed something, misunderstood something, or if it doesn't exist. Is there any way to get the license account username (ex I use the name !Learjet45 on servers, but my s2 license is learjet45. Is there a way to get the username (the learjet45 in my example) with pyinsim?

I have some previous experience with Python with the Sandbox mod for Battlefield 2 so that's why I chose pyinsim to experiment with. I understand some basics of python, but again, I am no serious major coder.
This is actually for a little code I want to experiment with that will take someone's username and various other bits of info (not really sure what yet :P ) and forward it to a PHP file that places the info in to a MySQL database.
My friend has given me the code he uses for the Sandbox mod to do a similar action, but I doubt that it is fully compatible with Pyinsim straight from Sandbox. So I am challenging myself to make it compatible with pyinsim.

morpha
17th November 2009, 04:52
Yes, but pyinsim itself is not a tracker like LFSLapper or AIRIO, which means while it is able to request and receive the information you seek, it does not do so on its own nor does it store it anywhere if you request it.

Perhaps you could post the code you have so we can help you port it to pyinsim?

BTW there is a MySQL module for python 2.6, MySQLdb (http://sourceforge.net/projects/mysql-python/) (windows distros (http://www.codegood.com/)).

DarkTimes
17th November 2009, 18:39
Yes, feel free to post the code and we can help you port it. In terms of InSim, the username (license name) is sent in the IS_NCN packet when a player joins the host.

This code for example prints out the license name of each player who joins a host.

import pyinsim

def connectionJoined(insim, ncn):
print 'Username', ncn.UName, 'joined host'

insim = pyinsim.insimConnect('localhost', 29999, IName='^3pyinsim')
insim.bind(pyinsim.ISP_NCN, connectionJoined)
insim.run()

learjet45
18th November 2009, 01:17
Yes, but pyinsim itself is not a tracker like LFSLapper or AIRIO, which means while it is able to request and receive the information you seek, it does not do so on its own nor does it store it anywhere if you request it.

Perhaps you could post the code you have so we can help you port it to pyinsim?

BTW there is a MySQL module for python 2.6, MySQLdb (http://sourceforge.net/projects/mysql-python/) (windows distros (http://www.codegood.com/)).

Thanks, and I'll see what I can do on my own first. :) Plus I need permission first to be able to post the code.

Yes, feel free to post the code and we can help you port it. In terms of InSim, the username (license name) is sent in the IS_NCN packet when a player joins the host.

This code for example prints out the license name of each player who joins a host.

import pyinsim

def connectionJoined(insim, ncn):
print 'Username', ncn.UName, 'joined host'

insim = pyinsim.insimConnect('localhost', 29999, IName='^3pyinsim')
insim.bind(pyinsim.ISP_NCN, connectionJoined)
insim.run()

Thanks for the sample code! That will help me a lot! And again, I'll wait and see what I can do on my own first before I post the code. :)

learjet45
18th November 2009, 04:12
Well from what I have of my basic test code (connects to the MySQL database, searches for my username, and sends back info if it is found, next part varies depending on if it is found), and so far so good. I got rid of all my errors with the code and it will run just fine. But for some reason I have to use 192.168.1.104 for my IP for insim instead of localhost, I get the error that insim couldnt connect if I use localhost.
Anyways, I don't know what's up, but as soon as I join my server, insim shuts off basically.
It happens with the sample codes too. As soon as the event happens once (ex a racer speeds over 80 km/h in example 4 i believe), insim closes the connection and doesn't run anymore. How do I keep insim alive so it doesn't shut off?

morpha
18th November 2009, 04:40
import the modules traceback and time and wrap the entire code from below the imports down to the last line intry:

<code>

except:
traceback.print_exc()
time.sleep(10)That should give you enough time to read the exception. It'd still be easier for us to help if you could post the code ;)

learjet45
18th November 2009, 05:28
I still gotta hear back from my friend who gave me the code.
In the mean time, I'll try it. Thanks!

morpha
18th November 2009, 06:26
Btw Alex, an easy way to compile your __all__ dict is using dir() and filtering all names starting with an underscore, provided that's a consistent pattern you used for private variables, which I believe you did.

My modified pyinsim, which I shall release soon-ish, currently counts 395 public names. It's using precompiled structs and regexes, __slots__ as suggested by tmehlinger and sorted callbacks (sometimes a callback needs to be first or last to be called).
I'm working on the UDP part now, particularly mixed mode where NLP/MCI is sent via UDP and the rest via TCP. If you or anyone else wants to have a peek, I could upload the current version, but as it is now, UDP is not working at all so I'd rather wait till that's fixed.

DarkTimes
18th November 2009, 14:22
OK cool - I'll look forward to it. :)

I am currently working on a new version that uses pre-compiled structs, __slots__, and supports Python 3.0. I'm also rewriting the API a little to simplify it further and add more features. I don't have a lot of time for it at the moment, which is why it's taking a while.

sorted callbacks (sometimes a callback needs to be first or last to be called)
Callbacks are currently called in the order they are bound, if you want a callback called last just bind it last.

morpha
18th November 2009, 14:50
and supports Python 3.0.
Interesting, I'd like to get into Python 3 but it'll take some getting used to and I'll have to make sure all modules I need are available... MySQLdb most certainly is not :sadbanana

I'm also rewriting the API a little to simplify it further and add more features. I don't have a lot of time for it at the moment, which is why it's taking a while.Perhaps I should wait for your version then :razz:

Callbacks are currently called in the order they are bound, if you want a callback called last just bind it last.

Well it's not that easy once your application grows, especially if it unbinds and rebinds alot. My solution is simple (although it might not look like it :razz:) and lightweight:
def bind(self, evtType, callback, priority = None):
"""Bind a packet callback event-handler.

@type evtType: enum
@param evtType: Type of evt to bind.
@type callback: function
@param callback: Function to call when the event is raised.
@type priority: number
@param priority: CBPRIO_FIRST ensures the callback is called first upon receiving a packet of
evtType, CBPRIO_LAST ensures it is called last. There can only be one FIRST and LAST per evtType and
it's always the most recent priority entry.

"""
# No callbacks for that type, priority irrelevant.
if not evtType in self.__callbacks:
self.__callbacks[evtType] = []
self.__callbacks[evtType].append((callback, priority))
# Priority FIRST, this callback wants to be called first upon receiving a packet of evtType.
elif priority == CBPRIO_FIRST:
self.__callbacks[evtType][0] = (self.__callbacks[evtType][0][0], None)
self.__callbacks[evtType].insert(0,(callback, priority))
# Priority LAST, this callback wants to be called after all others.
elif priority == CBPRIO_LAST:
self.__callbacks[evtType][-1] = (self.__callbacks[evtType][-1][0], None)
self.__callbacks[evtType].append((callback, priority))
# If a LAST priority is set on the cb stack, pop it back to the end of the stack.
elif self.__callbacks[evtType][-1][1] == CBPRIO_LAST:
self.__callbacks[evtType].extend([(callback, priority), self.__callbacks[evtType].pop()])
else:
self.__callbacks[evtType].append((callback, priority))Basically it will make sure that a handler bound with CBPRIO_FIRST will always be the first to be called, unless a second handler requests CBPRIO_FIRST, in which case it should probably throw an exception, but I left that out since I can't think of a situation where the solution would be anything other than ignoring it. Same applies to CBPRIO_LAST being the last to be called, obviously.

Edit: After reading the most significant changes (http://docs.python.org/dev/3.0/whatsnew/3.0.html) I'm inclined to give it a try, even though, as expected, there is no MySQLdb for it, which is one of the most important modules for my app.

DarkTimes
18th November 2009, 18:09
It took me ages to get around to trying Python 3.0, but it's not that big a change on the surface really. A lot of things make more sense and most of the significant changes can be learnt in half an hour. The biggest change though, which affects pyinsim the most, is that all strings are now unicode and there is a new byte data-type. As pyinsim deals with strings of data, all that has to be rewritten to use the new type. Also as all strings are now unicode, pyinsim needs to be able to convert LFS strings into unicode and then back again, when packing and unpacking packets. Dealing with LFS strings is a horrible nightmare. But aside from that it's coming along OK.

learjet45
19th November 2009, 01:42
Alright. I need some help now.
LFS closes the connection to insim as soon as the I connect. PM me for the file, I don't want it out in the open and neither does my friend who wrote the original code this is based on.

DarkTimes
19th November 2009, 02:45
Does a message come up within LFS before it closes? It could be that you are not sending the ISI initialisation packet, alternatively it could be that the game admin password you've supplied is incorrect.

Another cause could be that you've neglected to include a call to InSim.run() in your program, which prevents your program from exiting while the connection is still active. If you look at the example code I posted you can see run() being used.

import pyinsim

def connectionJoined(insim, ncn):
print 'Username', ncn.UName, 'joined host'

insim = pyinsim.insimConnect('localhost', 29999, IName='^3pyinsim')
insim.bind(pyinsim.ISP_NCN, connectionJoined)
insim.run()

A Python program will exit as soon as it reaches the end of the main program thread, regardless of any background threads which may be running (like the ones in pyinsim listening for packets). When this happens Python will release all the resources used by your program, causing the socket connection with LFS to be closed. Calling run() prevents this by telling your program to wait until all the background threads have completed before continuing.

tmehlinger
19th November 2009, 04:35
Also make sure you're binding EVT_ERROR.


def insim_error(insim, error):
print 'InSim error: ', error

insim.bind(pyinsim.EVT_ERROR, insim_error)


If you are running insim.run() as DarkTimes mentioned above, it could very well be that your code is bombing out somewhere and failing silently because the error happens on a child thread (it won't get caught on the main thread where you're doing all your work). This has happened to me. :tilt:

learjet45
20th November 2009, 02:09
Does a message come up within LFS before it closes? It could be that you are not sending the ISI initialisation packet, alternatively it could be that the game admin password you've supplied is incorrect.

Another cause could be that you've neglected to include a call to InSim.run() in your program, which prevents your program from exiting while the connection is still active. If you look at the example code I posted you can see run() being used.

cut

A Python program will exit as soon as it reaches the end of the main program thread, regardless of any background threads which may be running (like the ones in pyinsim listening for packets). When this happens Python will release all the resources used by your program, causing the socket connection with LFS to be closed. Calling run() prevents this by telling your program to wait until all the background threads have completed before continuing.
I've got the initialization packet, the run(), and the correct admin password

The end of my code:
try:
insim.connect('192.168.1.104', 29999)
insim.send(pyinsim.ISP_ISI, Admin='Not For You to Know', IName='^3pyinsim')
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NPL)
insim.run()
except pyinsim.socket.error, err:
print 'InSim Error:', err[1]


And right before that:
insim = pyinsim.InSim()
insim.bind(pyinsim.ISP_NCN, connectionJoined)

Also make sure you're binding EVT_ERROR.


def insim_error(insim, error):
print 'InSim error: ', error

insim.bind(pyinsim.EVT_ERROR, insim_error)


If you are running insim.run() as DarkTimes mentioned above, it could very well be that your code is bombing out somewhere and failing silently because the error happens on a child thread (it won't get caught on the main thread where you're doing all your work). This has happened to me. :tilt:
Alright. I'll try it and see if it helps.

I also had a few return true/return false from the original code from my friend, and I removed those as morpha recommended. Code still works fine. But I still get the InSim Guest Closed: pyinsim in live for speed dedi server as soon as I connect. I literally watched the server via teamviewer as I connected, and as soon as I connect right after the Learjet45 connected (learjet45) line, the insim closes the connection to pyinsim or whatever.
I'll try that EVT_ERROR bind and see if that helps.

Edit: That did the trick! Thanks tme! Here is what I get:
InSim error: connectionJoined() takes exactly 4 arguments (2 given)
From python IDLE GUI.
If someone wants the code to look at, again feel free to PM me. I don't want it floating around for anyone to get hold of. My friend and I have tried to keeps our codes as private as possible, especially when it is developed specifically for our servers (in which case, the original code he gave me was made specifically for our clan's server).

morpha
20th November 2009, 02:18
I also had a few return true/return false from the original code from my friend, and I removed those as morpha recommended.

Just so you understand, they were not syntactically incorrect, just pointless ;)

Edit: Like I told you via PM, connectionJoined() cannot take more than 2 parameters ;)
Edit again: To avoid confusion, that's ignoring the instance reference (self).

learjet45
20th November 2009, 02:50
Well alrighty then.
New idea time. I'll see how this new idea goes and see what I can do.

tmehlinger
20th November 2009, 15:00
Warning, I'm planting the seed for a free software argument... :)

Since it appears that you're new to Python/InSim programming, you might as well just post your code. I doubt you or your friend are doing anything really revolutionary with InSim that warrants keeping the code secret. Furthermore, if/when you're making mistakes, it's extremely likely that we've made the same mistakes ourselves and we'll be able to advise you better.

By the same token, it's a nice gesture to share your code since you're using someone else's freely given code in the same application. :)

learjet45
20th November 2009, 22:56
Warning, I'm planting the seed for a free software argument... :)

Since it appears that you're new to Python/InSim programming, you might as well just post your code. I doubt you or your friend are doing anything really revolutionary with InSim that warrants keeping the code secret. Furthermore, if/when you're making mistakes, it's extremely likely that we've made the same mistakes ourselves and we'll be able to advise you better.

By the same token, it's a nice gesture to share your code since you're using someone else's freely given code in the same application. :)

I'm not totally new to Python, and neither is my friend.
he just doesn't like his codes floating around in the open without his consent.
I have no problem sending you the code. If you PM me I'll send you a link. I'm just not used to Insim programming. I have some basic experience with Python. I'm no major coder or anything, but it's not like I have never written a single line of code in my life.

PhilS13
20th November 2009, 23:04
Trying to make this work again http://www.lfsforum.net/showthread.php?t=49607. It seemed to work on the earlier versions of pyinsim.

When using python 2.6 and pyinsim 1.5.5 I get multiple errors.

Looks like I was able to fix some minor syntax errors but I'm stuck on the .Packet arguments(line 222). Would be nice if you guys could have a look a it. I'm an absolute beginner at this...

DarkTimes
21st November 2009, 19:28
Yeah, that's from a really, really old version of pyinsim. I will have a go at porting it to pyinsim 1.5 tomorrow. Shouldn't take too long.

DarkTimes
9th January 2010, 02:46
Not that many people care probably, but I'm going to release a new version of this soon, maybe tomorrow. The new features are:


Vastly improved performance (pre-compilation, slots, general optimisations...)
Improved error reporting (no hooking up separate error events)
Simplified API (easier ways to do stuff)
Seamless support for UDP (no dealing with seperate API calls, timeouts etc..)
Even more helper functions (sending messages, commands, packets etc..)
A free cookie...

And coming soon we'll have...


.NET 4.0 support (DLR)

I've been working on this for a little while now, but it's really started to come together in the last few days. I'm starting to feel very pleased with it, especially how it supports UDP, which has caused some big changes to the API. Much of the code has been rewritten, and is now much improved. Turns out it's easier to write code when you're sober... who knew? :p

morpha
9th January 2010, 09:05
Looking good, especially the cookie part :razz:
I've been busy myself and, among other things, discovered that Shift-JIS is not the correct codec for Japanese, proven by the fact that it fails to de- and encode high mappings (CJK characters). Instead, cp932, which is Microsoft's Shift-JIS extension, should be used.

I've written a separate module called strmanip for string manipulation, it includes a slightly modified strToUnicode (renamed to toUnicode, since it's part of a string related module anyway), it's counterpart fromUnicode, stripColours and escapeString. I want to add more, I just don't know what at this point. Open for ideas, perhaps some applications require some common string manipulation that I just can't see. Perhaps a function to build car strings from lists?

So far it works fine, there's just one problem with fromUnicode; CJK characters are unified in Unicode, making it impossible to correctly identify a language if only CJK characters are present. For performance reasons, fromUnicode only performs a single character look-behind. The pleasant side-effect of this is that it usually generates short strings, using the same encoding for as much of the string as possible. For example, all character sets include the 128 ASCII characters, so it's not necessary to switch encoding for any of those.

What I also want to do is a language module, like LFSLapper and Airio provide.
I'll post some of my ideas regarding that, as well as strmanip, later when I've got something to show :)

On a side note: Anyone tested python 2.7 yet? I'm looking forward to it I must say :thumbsup:

Dygear
9th January 2010, 09:17
Yeah, I'm really up for the cookie thing as well.
(See, we do care)

tmehlinger
10th January 2010, 04:19
Seamless support for UDP (no dealing with seperate API calls, timeouts etc..)



Any chance you can make OutGauge over InSim work? :)

I recently made my own modifications to pyinsim to support the InSim relay. I still need to work out a few bugs, but if you're interested, I'll send you my changes.

DarkTimes
10th January 2010, 14:02
OutGauge over InSim works at the moment (correct me if I'm wrong), but currently you need to create a separate OutGauge object to receive the packets. But yes, the new version will handle packets received on a separate UDP connection without any extra work. In fact you don't need to do anything except bind events for the packets you want to receive. There is still a separate class for standalone OutGauge/OutSim programs, which is a design consideration as opposed to a technical one.

To be honest I've never used InSimRelay, I'd need to look into how to support it. You can send me your code if you want and I'll consider what I'll need to do to add it.

DarkTimes
10th January 2010, 14:09
Instead, cp932, which is Microsoft's Shift-JIS extension, should be used.
OK - noted, thanks.

I would be very interested in a separate string manipulation module. At the moment the strToUnicode stuff was added simply so I could display LFS strings in wxPython apps with something resembling the correct encoding. In an ideal world pyinsim would convert strings to and from unicode, and in Python 3.0 as all strings are unicode, it could do this by default for each string without any input from the user. I've really not spent the time I'd need to on this functionality though, so it would make my life 100 times easier if it already existed. :p

morpha
12th January 2010, 03:21
Yet another "it's entirely insignificant but who gives a damn :razz:" mini-tweak, pyinsim.time(ms): Drop the floor, casting to int will always floor anyway. minutes can be converted with one division less:m = int((ms / 60000) % 60)
Attached you will find a stripped down version of strmanip as it is now, it's quite large because it contains character encoding look-up tables. Some of those come with python in the encodings module, but unfortunately not all, so I decided it's best to just include them. Unfortunately it's mostly undocumented, if you need any specific info, just ask and I'll try to explain :thumb:

Regarding your ideal world: Whether to convert to and from Unicode automatically really depends on the application's purpose. If, for example, the strings remain "in LFS", i.e. are only stored by the by the application to be displayed later, possibly unmodified, converting them would be a waste of resources. With that in mind, I would advise you against having pyinsim convert everything automatically, because the application may not need to display a string outside of LFS at all.

On another note: As I've mentioned before, CJK characters are not guaranteed to be identified correctly because they are shared among several languages. This means that you should never convert values you wish to compare to values from LFS without converting those as well. Ideally, you wouldn't convert them at all as converting them to Unicode could potentially produce collisions, if for example two player names contain the same character at the same position, but from different encodings.

Yet another note: In my ideal LFS world, we'd simply have UTF-8 or UTF-16 :razz:

Dygear
12th January 2010, 04:38
m = int((ms / 60000) % 60)[/list]

So it was you that thought me that! Got any tips for returning a string with hours, minutes and seconds?

h = <your code here>;
m = int((ms / 60000) % 60);
s = <your code here>;

printf("You've been playing for %d hours, %d minutes and %d seconds", h, m, s);

I came up with this, but it would be used to convert seconds.

h = int((time / 3600) % 60);
m = int((((time - (h * 3600)) / 60) % 60);
s = int((time - ((h * 3600) + (m * 60))));

morpha
12th January 2010, 05:25
You could use the same function and simply multiply your input integer in seconds by 1000, making it milliseconds, or you could simply shift the formulas "one up": (assuming PHP)
$h = $time / 3600;
$m = $time / 60 % 60;
$s = $time % 60;

Casting is only required if the results are not used in a formatted string.
€#0: Actually the same applies to python, meaning explicit casting is unnecessary in timeStr, format does this automatically if a float is supplied for a %d.

€#1: Here's what I'd consider the perfect timeStr() and time():
def time(ms):
"""Convert milliseconds into hours, minutes, seconds and thousanths.

@type ms: number
@param ms: The time in milliseconds

@rtype: tuple
@return: A tuple containing the (hours, mins, secs, thousandths).

"""
h = ms / 3600000
m = ms / 60000 % 60
s = ms / 1000 % 60
t = ms % 1000
return (h, m, s, t)

def timeStr(ms, hour_fmt = False):
"""Convert milliseconds into a formatted time string (EG h:m:s.t).

@type ms: number
@param ms: The time in milliseconds.
@type hour_fmt: boolean/string
@param hours: Either boolean True to enforce inclusion of hours, even if 0, or a custom format string. Defaults to False for neither.


@rtype: string
@return: The formatted time string (E.G. hh:mm:ss.ttt)

"""
h, m, s, t = time(ms)
if isinstance(hour_fmt, str):
return hour_fmt % {'h':h, 'm':m, 's':s, 't':t}
if hour_fmt:
return '%d:%02d:%02d.%03d' % (h, m, s, t)
else:
return '%d:%02d.%03d' % (m, s, t)


€#2: This allows for very flexible formatting, like:
>>> timeStr(float(5234567), 'You have been playing for %(h)0.1f hours.')
'You have been playing for 1.5 hours.'
Casting to float because the result is always of the same type the input is.

or Dygear's example:
>>> timeStr(5234567, 'You\'ve been playing for %(h)d hours, %(m)d minutes and %(s)d seconds')
"You've been playing for 1 hours, 27 minutes and 14 seconds"

tmehlinger
12th January 2010, 22:53
All right, here we go. I've attached my full, modified pyinsim module based on 1.5.5. I have also attached just the diff. Note the .txt extension because the board won't permit .diff or .py.

Here's some sample code that:


connects to the relay
grabs the host list
selects a random host with active connections that doesn't require a spectator pass
dumps the connected UCIDs



import pyinsim_relay as pyinsim
import random
import time

class RelayTest(object):
def __init__(self):
self.hosts = []

insim = pyinsim.InSim(use_relay=True)
insim.connect()

insim.bind(pyinsim.IRP_HOS, self.handle_hostlist)
insim.bind(pyinsim.IRP_ERR, self.handle_relay_error)
insim.bind(pyinsim.ISP_NCN, self.handle_new_conn)
insim.bind(pyinsim.EVT_ERROR, self.handle_insim_error)

insim.send(pyinsim.IRP_HLR)
time.sleep(5)

host = pyinsim.stripColours(random.choice(self.hosts))
print 'Selecting host: ', host
insim.send(pyinsim.IRP_SEL, HName=host)
time.sleep(2)

insim.send(pyinsim.ISP_TINY, SubT=pyinsim.TINY_NCN, ReqI=1)

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

def handle_hostlist(self, insim, hos):
for host in hos.Hosts:
if host.NumConns > 1 and not host.Flags & 1:
self.hosts.append(host.HName)

def handle_relay_error(self, insim, err):
print 'InSim Relay error:', err.ErrNo

def handle_new_conn(self, insim, ncn):
print 'New connection, UCID:', ncn.UCID

def handle_insim_error(self, insim, err):
print 'InSim error:', err

if __name__ == '__main__':
RelayTest()


Note that once you've successfully selected a host, you receive packets as though you'd connected directly to the host instead of using the relay. No extra work involved. :)

DarkTimes
13th January 2010, 20:11
Yet another "it's entirely insignificant but who gives a damn :razz:" mini-tweak, pyinsim.time(ms): Drop the floor, casting to int will always floor anyway. minutes can be converted with one division less:m = int((ms / 60000) % 60)
Attached you will find a stripped down version of strmanip as it is now, it's quite large because it contains character encoding look-up tables. Some of those come with python in the encodings module, but unfortunately not all, so I decided it's best to just include them. Unfortunately it's mostly undocumented, if you need any specific info, just ask and I'll try to explain :thumb:

Regarding your ideal world: Whether to convert to and from Unicode automatically really depends on the application's purpose. If, for example, the strings remain "in LFS", i.e. are only stored by the by the application to be displayed later, possibly unmodified, converting them would be a waste of resources. With that in mind, I would advise you against having pyinsim convert everything automatically, because the application may not need to display a string outside of LFS at all.

On another note: As I've mentioned before, CJK characters are not guaranteed to be identified correctly because they are shared among several languages. This means that you should never convert values you wish to compare to values from LFS without converting those as well. Ideally, you wouldn't convert them at all as converting them to Unicode could potentially produce collisions, if for example two player names contain the same character at the same position, but from different encodings.

Yet another note: In my ideal LFS world, we'd simply have UTF-8 or UTF-16 :razz:
Yes, I would love for LFS to support in UTF-8, but sadly that's unlikely to happen. I see what you mean about decoding strings that don't get used, probably in the long run I will leave the unicode encoding/decoding stuff as separate functions. I can think of several ways where I could have the string only decoded when it's actually accessed, but this would probably add an unnecessary layer of complexity, which is something I always try to avoid when designing libraries. In my experience it's a mistake to try to hide something from the user, as eventually the fact you're hiding it will just confuse things (the law of leaky abstractions (http://www.joelonsoftware.com/articles/LeakyAbstractions.html)).

Thanks for providing this code though, it's interesting. I plan to add a strFromUnicode function to the next release, if that's OK with you. Obviously I will give you credit for having written it. :p

DarkTimes
13th January 2010, 20:16
All right, here we go. I've attached my full, modified pyinsim module based on 1.5.5.
Awesome thanks, I'll pick it apart and see what's what. :)

morpha
14th January 2010, 02:56
Thanks for providing this code though, it's interesting. I plan to add a strFromUnicode function to the next release, if that's OK with you. Obviously I will give you credit for having written it. :p

Sure :)

I'm also looking into Cython and SIP, both of which should allow for significant performance improvements if used correctly. Though for now, functionality is more important to me than performance, so don't expect any results anytime soon :razz:

DarkTimes
19th January 2010, 13:00
In addition to this, if you want to squeeze out even more performance (well... mostly just lower memory footprint), consider using __slots__. Note that you'd need to update your packet classes to new-style class definitions, but that would be an easy search-and-replace.

http://docs.python.org/reference/datamodel.html#slots

I think I'm done spamming the thread now. :D
In retrospect converting the packets to use __slots__ isn't a great idea. The actual memory footprint you save is minimal and unless you are creating millions of objects doesn't really impact on performance. Add to this that it limits what you can do with an object, that you cannot extend the packets dynamically, I have decided not to use them. I may leave __slots__ on MCI, NLP, OutGauge and OutSim packets, as you can have many of those instantiated every second, so it might have some use there.

DarkTimes
20th January 2010, 16:45
PYINSIM 1.6.4 BETA

I've uploaded pyinsim 1.6 BETA below. The new version of the library has been largely rewritten and contains many changes and additions, especially to the core InSim class. The idea behind this release is to clean up a lot of the old guff left over from earlier versions, so I can focus on pyinsim 2.0 and Python 3.0 support. That being said it is still the best version of pyinsim evar! :D

I'll try to give a rough overview of what's new in pyinsim 1.6!

Documentation

The documentation for the module has been rewritten and brought up date, with lots more information and better organisation, which should make using the documentation much easier. As before you can access the full API reference by opening the file 'documentation.html' included in the package download.

Examples

The examples included in the package have been rewritten and brought up to date as well, and several more new ones have been added! Again you can access the examples by browsing the 'examples' folder once you have unzipped the package.

Helper Functions

All of the old helper functions have been tweaked, tested and brought up-to-date, and in addition several (hopefully) useful ones have been added.


strFromUnicode() - Convert a unicode string back into a LFS encoded string (thanks to morpha :))
collision() - Determine if two rectangles are colliding
timeToMs() - Convert time list [h, m, s, t] back into milliseconds
newEvt() - Create an event identifier for creating and raising your own custom events
version() - Determine if the correct version of pyinsim is installed

Exceptions

A lot of work has been done on exceptions and pyinsim will no-longer raise general socket.errors, but instead has a set of new unique exception classes.


InSimError - General InSim related errors
InSimVersion - Incorrect version of InSim detected (more below)
OutError - General OutSim and OutGauge related errors
OutTimeout - An OutSim or OutGauge connection has timed out

In addition you no longer need to bind an event to EVT_ERROR to catch exceptions on the internal packet receive thread (although you can if you still want). Errors which occur on internal threads are now printed to std::err like all exceptions, which makes debugging pyinsim programs much easier and less confusing!

Performance

The overall performance of the module has been improved, through the use of pre-compiled structs and other code changes. As discussed in posts above __slots__ are used for MCI, NLP, OutGauge and OutSim packets, in order to reduce the memory overhead of creating these packets when using a small receive interval. For other packets the __slots__ are available but have actually been commented out, so you can reactivate them by doing a find and replace on 'pyinsim.py' and changing '#__slots__' to '__slots__'. It's high-tech engineering, I know. :p

API

As mentioned above most of the API changes reside in the InSim class, which has been largely rewritten. The most important change is that the InSim.connect() method has been removed and replaced with the new InSim.init().

The new Insim.init() method connects to LFS, sends the ISI packet, checks the InSim version, and starts a separate UDP connection when necessary. This means that the InSim connection process has been simplified to a single function call.

import pyinsim

insim = pyinsim.InSim()

try:
insim.init('localhost', 29999, IName='^3pyinsim') # Optional ISI args

except pyinsim.InSimVersion:
print 'Invalid InSim version detected'
except pyinsim.InSimError as err:
print 'InSim Error:', err

This code-snippet also demonstrates how to use the new exception classes, both to catch a InSimVersion exception, and a general InSimError.

The second major change is that InSim.run() has been removed, as the new socket code makes it obsolete. In pyinsim 1.6 your program should remain active as long as LFS is connected (hopefully :p)! Some more testing may be needed on this functionality, but it certainly works with Python 2.6.4.

In additon several old methods have been changed into instance variables, mainly InSim.isConnected(), InSim.getName() and InSim.setName(). This simply means that whereas before you did:

if insim.isConnected():
pass
You would now do:

if insim.connected:
pass

Lastly several new methods have been added that add some extra functionality to the class:


InSim.sendm() - Shorthand for sending MST and MSX packets
InSim.timer() - Execute a function callback after a timeout has elapsed
InSim.event() - Raise a custom event


It is recommended that you checkout the full API reference to see all of the additions and changes, as it's hard to sum everything up in a few paragraphs. Obviously it goes without saying that these changes make pyinsim 1.6 incompatible with code written for previous versions, but hopefully these adjustments mean that I will not have to change the API for future release and can work on keeping future versions backwards compatible. That being said, the beta API could still change subtly before 1.6 goes final.

Download

So there we have what's new in pyinsim 1.6 BETA. You do need to have Python 2.6 in order for it to run, which you can download from the official Python site (http://python.org/). For installation instructions checkout the readme.txt file included in the download.

Note: As it is a beta it may still have one or two bugs, and some things may change before the final release, so you have been warned. Aside from that I'd be happy hear any comments or criticism you have regarding the module. :)

tmehlinger
20th January 2010, 16:53
Very cool, going to play with it tonight.

Any chance you pulled the InSim relay stuff in, or will I have to modify it? :)

DarkTimes
20th January 2010, 16:56
Very cool, going to play with it tonight.

Any chance you pulled the InSim relay stuff in, or will I have to modify it? :)

You know what I forgot about InSim relay. Oh dear, I wonder what's wrong with my brain sometimes.... eh, maybe I shouldn't go there :p. I will add support for it before 1.6 goes final, as it will be nice to get it in.

tmehlinger
20th January 2010, 19:41
You know what... I forgot about InSim relay. Oh dear... I wonder what's wrong with my brain sometimes.... eh, maybe I shouldn't go there :p. I will add support for it before 1.6 goes final, as it will be nice to get it in.

Don't lose any sleep over it, I was just curious. :)

DarkTimes
21st January 2010, 17:36
I noticed a couple of bugs in your relay code. Firstly you are missing the IRP_ARQ and IRP_ARP packets, also your IR_HOS and IR_ERR packets are assigned the wrong type in their constructors. Anyway, I've added InSim relay support to pyinsim 1.6 BETA, so it shouldn't matter too much anyway. :)

DarkTimes
21st January 2010, 17:37
I've uploaded pyinsim 1.6.1 BETA to the beta post (http://www.lfsforum.net/showthread.php?p=1359033#post1359033). Changes in this release:


Rewritten socket code to use byte arrays, which should yield some good performance improvements and also help with the planned port to Python 3.0
Added InSim Relay support, based on code supplied by tmehlinger
Changed OutGauge and OutSim classes to inherit from InSim.

For information here is the tmehlinger's InSim Relay example ported to pyinsim 1.6.1

"""Example 13: InSim Relay

This example demonstrates how to use InSim Relay. We connect to the relay,
request a list of hosts, select a random one, and then print out its
connection list.

"""

import pyinsim
import random

# List of hosts.
hosts = []

# Check if host is populated and does not require a spectator pass, then
# add to hosts list.
def hostList(isr, hos):
global hosts
for host in hos.Hosts:
if host.NumConns > 1 and not host.Flags & pyinsim.HOS_SPECPASS:
hosts.append(host.HName)

# Print out the error message.
def relayError(isr, err):
print 'InSim Relay error:', pyinsim.irerr(err.ErrNo)

# Print out the name and UCID of the connection.
def newConn(isr, ncn):
print 'New connection, UName %s (%d)' % (ncn.UName, ncn.UCID)

# Request for all connections to be sent.
def requestConns(isr):
isr.send(pyinsim.ISP_TINY, SubT=pyinsim.TINY_NCN, ReqI=1)

# Select a random host from the hosts list.
def selectHost(isr):
host = pyinsim.stripColours(random.choice(hosts))
print 'Selecting host:', host
isr.send(pyinsim.IRP_SEL, HName=host)

# Request connections after 2 seconds.
isr.timer(requestConns, 2)

# Create new InSimRelay object and bind events.
isr = pyinsim.InSimRelay()
isr.bind(pyinsim.IRP_HOS, hostList)
isr.bind(pyinsim.IRP_ERR, relayError)
isr.bind(pyinsim.ISP_NCN, newConn)

try:
# Initialise InSimRelay.
isr.init()

# Request host list.
isr.send(pyinsim.IRP_HLR)

# Select host after 5 seconds.
isr.timer(selectHost, 5)

except pyinsim.InSimError as err:
print 'Error:', err

I'd be really keen to hear your input on the new InSimRelay API. :)

morpha
21st January 2010, 18:38
Haven't had time yet to take a proper look, but here's a tiny improvement suggestion:
def stripColours(lfsStr):
"""Strip colour codes (^1, ^3 etc..) from a string.

@type lfsStr: string
@param lfsStr: The string to strip.
@rtype: string
@return: The string sans colour codes.

"""
return _COLOUR_REGEX.sub('', lfsStr)
Since _COLOUR_REGEX is a compiled regex and using its sub method is about 30% faster :thumbsup:

€: Also I just noticed that my __slots__ implementation or rather usage is wrong, and so is yours! __slots__ only works with classes extending object.
>>> class SlotsTest:
__slots__=['a','b']


>>> st = SlotsTest()
>>> st.c = 1
>>>
>>> class SlotsTest(object):
__slots__=['a','b']


>>> st = SlotsTest()
>>> st.c = 1

Traceback (most recent call last):
File "<pyshell#63>", line 1, in <module>
st.c = 1
AttributeError: 'SlotsTest' object has no attribute 'c'
>>>

DarkTimes
21st January 2010, 19:55
OK - thanks, I've changed the regexes and fixed the __slots__ issue on the packets which use them.

I have uploaded 1.6.2 BETA to the beta post (http://www.lfsforum.net/showthread.php?p=1359033#post1359033), which fixes a bunch of issues with 1.6.1, which is basically that I managed to break OutGauge and OutSim. It should all be working now, hopefully. :p

tmehlinger
23rd January 2010, 03:46
I noticed a couple of bugs in your relay code. Firstly you are missing the IRP_ARQ and IRP_ARP packets, also your IR_HOS and IR_ERR packets are assigned the wrong type in their constructors. Anyway, I've added InSim relay support to pyinsim 1.6 BETA, so it shouldn't matter too much anyway. :)

I probably posted an older version of my file, sorry about that. I had run into the incorrect types thing (it manifested itself in things not working at all :)) and fixed it myself. Whoops. As for the missing packets, good catch... I totally missed them.

This is great stuff, keep up the good work! :thumb:

DarkTimes
25th January 2010, 19:37
Uploaded 1.6.3 to the beta post (http://www.lfsforum.net/showthread.php?p=1359033#post1359033).

Changes since 1.6.2 BETA

Fixed bug with NodeLap unpacking
lookup() function now works more reliably
InSim.sendm() now supports sending MTC messages (see docs)
Improvements to parseCommand()
Added new cruise server example to 'examples' folder

DarkTimes
26th January 2010, 14:40
I wanted a more complete example program for the documentation, so I wrote a (very) small cruise server. You can drive around and earn cash, which you can then spend on buying and selling cars. It has an on-screen button that shows your current cash and total distance travelled, and several various chat commands like !buy, !sell, !cars, !prices and !help. As I say it's just a simple example, but I do have to admit I had fun writing it. :p Now I need to write a CTRA-style example for balance. :) The full example can be found in the 'examples' folder in the beta download (http://www.lfsforum.net/showthread.php?p=1359033#post1359033).

It requires a sub-directory called 'data\cruise' to store the user data, which is included the examples folder in the download, but you will need to create yourself if you just copy this code.

"""Example 15: Cruise

A simple cruise server. As you drive you earn cash, which can be spent on buying
and selling cars. User data is stored as pickled Python objects in the
data\cruise folder, and is retrieved and stored when players join and leave the
host. There are several chat commands such as !prices, !buy, !sell, !cars and
!help, plus an onscreen display which shows the players current cash and their
total kilometers driven.

"""

# Make sure correct version of pyinsim is imported.
VERSION = '1.6.4'
try:
import pyinsim
if not pyinsim.version(VERSION):
raise ImportError
except ImportError:
print 'You must install pyinsim %s or better' % VERSION
import sys
sys.exit(1)

# Dependencies.
import cPickle
import os

# Constants.
HOST = 'localhost'
PORT = 29999
PREFIX = '!'
UDPPORT = 30000
INTERVAL = 500 # Milliseconds
PROG_NAME = '^7pyCruise'
CASH_MULTIPLIER = 2 # Dollars per second
STARTING_CARS = ['UF1',]
STARTING_CASH = 1000 # Dollars
HEARTBEAT_INTERVAL = 1 # Seconds
USER_DIR = 'data\\cruise'
HOST_ID = 0
SPEED_DEADZONE = 10
CAR_PRICES = {'XFG': 4500, 'XRG': 6000, 'FBM': 150000, 'XRT': 14000, 'RB4': 12000,
'FXO': 12000, 'LX4': 15000, 'LX6': 25000, 'MRT': 30000,'UF1': 3000,
'RAC': 30000, 'FZ5': 38000, 'XFR': 50000, 'UFR': 45000, 'FOX': 150000,
'FO8': 165000, 'BF1': 350000, 'FXR': 120000, 'XRR': 120000, 'FZR': 130000}

# Global variables.
connections = {}
players = {}
insim = pyinsim.InSim()

# Class to store user info.
class UserVars:
def __init__(self):
self.last_pos = ()
self.dist = 0
self.cash = STARTING_CASH
self.cars = STARTING_CARS
self.on_track = False

# Draw on-screen display.
def draw_osd(ncn):
insim.send(pyinsim.ISP_BTN, ReqI=1, UCID=ncn.UCID, ClickID=1,
BStyle=pyinsim.ISB_DARK, T=4, L=85, W=30, H=6,
Text='Cash: $%d | Distance: %.2f Km' % (ncn.vars.cash, ncn.vars.dist))

# Called every second to update cash and OSD.
def heartbeat(insim):
for ncn in connections.values():
if ncn.UCID != HOST_ID:
if ncn.vars.on_track:
ncn.vars.cash += CASH_MULTIPLIER
draw_osd(ncn)
insim.timer(heartbeat, HEARTBEAT_INTERVAL)

# Load user info from file.
def load_user_vars(uname):
path = os.path.join(USER_DIR, uname)
if os.path.exists(path):
try:
with open(path, 'r') as f:
return cPickle.load(f)
except IOError as err:
print 'Load Error:', err
return UserVars() # Default

# Save user info to file.
def save_user_vars(uname, vars):
path = os.path.join(USER_DIR, uname)
try:
with open(path, 'w') as f:
cPickle.dump(vars, f)
except IOError as err:
print 'Save Error:', err

# Request all connections and players to be sent.
def req_conns():
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NCN)
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NPL)

# New connection joined host
def new_conn(insim, ncn):
if ncn.UCID != HOST_ID:
ncn.vars = load_user_vars(ncn.UName)
connections[ncn.UCID] = ncn

# Connection left host.
def conn_left(insim, cnl):
if cnl.UCID != HOST_ID:
ncn = connections[cnl.UCID]
save_user_vars(ncn.UName, ncn.vars)
del connections[cnl.UCID]

# Player tries to join track.
def new_ply(insim, npl):
players[npl.PLID] = npl
ncn = connections[npl.UCID]
if npl.CName in ncn.vars.cars:
ncn.vars.on_track = True
else:
insim.sendm('/spec %s' % ncn.UName)
insim.sendm('^3| ^7You do not own the %s' % npl.CName, ncn.UCID)

# Player leaves track.
def ply_left(insim, pll):
npl = players[pll.PLID]
ncn = connections[npl.UCID]
ncn.vars.on_track = False
del players[pll.PLID]

# Print out car prices.
def cmd_prices(ncn, args):
insim.sendm('^3| ^7Car Prices:', ncn.UCID)
for car, price in CAR_PRICES.iteritems():
insim.sendm('^3| ^7%s: $%d' % (car, price), ncn.UCID)

# Buy a new car.
def cmd_buy(ncn, args):
if args:
for arg in args:
car = arg.upper()
if car in ncn.vars.cars:
insim.sendm('^3| ^7You already own the %s' % car, ncn.UCID)
elif car not in CAR_PRICES:
insim.sendm('^3| ^7The %s does not exist' % car, ncn.UCID)
elif CAR_PRICES[car] > ncn.vars.cash:
insim.sendm('^3| ^7You do not have enough cash for the %s' % car, ncn.UCID)
else:
ncn.vars.cash -= CAR_PRICES[car]
ncn.vars.cars.append(car)
insim.sendm('^3| %s ^7bought the %s' % (ncn.PName, car))
else:
insim.sendm('^3| ^7No car selected', ncn.UCID)

# Sell an owned car.
def cmd_sell(ncn, args):
if args:
for arg in args:
car = arg.upper()
if car not in CAR_PRICES:
insim.sendm('^3| ^7The %s does not exist' % car, ncn.UCID)
elif car not in ncn.vars.cars:
insim.sendm('^3| ^7You do not own the %s' % car, ncn.UCID)
else:
ncn.vars.cash += CAR_PRICES[car]
ncn.vars.cars.remove(car)
insim.sendm('^3| %s ^7sold the %s' % (ncn.PName, car))
else:
insim.sendm('^3| ^7No car selected', ncn.UCID)

# Print list of cars owned.
def cmd_cars(ncn, args):
insim.sendm('^3| ^7Currently Owned Cars:', ncn.UCID)
for car in ncn.vars.cars:
insim.sendm('^3| ^7%s: $%d' %(car, CAR_PRICES[car]), ncn.UCID)

# Print usage info.
def cmd_help(ncn, args):
insim.sendm('^3| ^7Usage Info:', ncn.UCID)
insim.sendm('^3| !prices ^7- View car prices', ncn.UCID)
insim.sendm('^3| !buy ^7- Buy a new car', ncn.UCID)
insim.sendm('^3| !sell ^7- Sell an owned car', ncn.UCID)
insim.sendm('^3| !cars ^7- See what cars you currently own', ncn.UCID)

CMD_LOOKUP = {'prices': cmd_prices, 'buy': cmd_buy, 'sell': cmd_sell,
'cars': cmd_cars, 'help': cmd_help,}

# Handle command message from LFS.
def message_out(insim, mso):
cmd = pyinsim.parseCmd(mso)
if cmd:
ncn = connections[mso.UCID]
cmd0 = cmd[0].lower()
if cmd0 in CMD_LOOKUP:
CMD_LOOKUP[cmd0](ncn, cmd[1])
else:
insim.sendm('^3| ^7Unknown command', ncn.UCID)

# Calculate distance travelled.
def add_distance(ncn, car):
curr_pos = (car.X, car.Y, car.Z)
if ncn.vars.last_pos:
dist = pyinsim.distance(ncn.vars.last_pos, curr_pos)
ncn.vars.dist += pyinsim.metres(dist) / 1000 # Km
ncn.vars.last_pos = curr_pos

# Player MCI updates.
def car_info(insim, mci):
for car in mci.CompCars:
if car.Speed < SPEED_DEADZONE: continue
npl = players[car.PLID]
ncn = connections[npl.UCID]
add_distance(ncn, car)

# Remind to set /cruise flag.
def race_start(insim, rst):
if not rst.Flags & pyinsim.HOSTF_CRUISE:
insim.sendm('^3| ^1WARNING: NOT IN CRUISE MODE!')
insim.sendm('^3| ^1WARNING: NOT IN CRUISE MODE!')

# Save user vars if connection is lost.
def closed(insim, reason):
for ncn in connections.values():
if ncn.UCID != HOST_ID:
save_user_vars(ncn.UName, ncn)

if __name__ == '__main__':
# Bind event-handlers.
insim.bind(pyinsim.ISP_NCN, new_conn)
insim.bind(pyinsim.ISP_CNL, conn_left)
insim.bind(pyinsim.ISP_NPL, new_ply)
insim.bind(pyinsim.ISP_PLL, ply_left)
insim.bind(pyinsim.ISP_MSO, message_out)
insim.bind(pyinsim.ISP_MCI, car_info)
insim.bind(pyinsim.ISP_RST, race_start)
insim.bind(pyinsim.EVT_CLOSED, closed)

try:
# Initialise InSim.
insim.init(HOST, PORT, IName=PROG_NAME, Prefix=PREFIX, UDPPort=UDPPORT,
Flags=pyinsim.ISF_MCI, Interval=INTERVAL)

# Request players/connections.
req_conns()

# Start heartbeat timer.
insim.timer(heartbeat, HEARTBEAT_INTERVAL)
except pyinsim.Error as err:
print 'InSim Error:', err

DarkTimes
30th January 2010, 20:20
I'm currently in the process of switching the documentation over to the Sphinx (http://sphinx.pocoo.org/) documentation generator and, now I've finally managed to get it working (grrr!), the HTML it produces is really quite beautiful. The flexibility of Sphinx also means I'm now able to move a lot of the old "inline" documentation out of the module itself, meaning the docstrings are now much snappier and to the point, keeping the big long explanations in the accompanying HTML files. The only problem is that the output is so pretty it's encouraged me to write more documentation, which I'm in the slow process of doing! Oh well... Also I may need to find a web site to host it this time, so more people can see it. :)

jasonmatthews
30th January 2010, 21:15
I'm currently in the process of switching the documentation over to the Sphinx (http://sphinx.pocoo.org/) documentation generator and, now I've finally managed to get it working (grrr!), the HTML it produces is really quite beautiful. The flexibility of Sphinx also means I'm now able to move a lot of the old "inline" documentation out of the module itself, meaning the docstrings are now much snappier and to the point, keeping the big long explanations in the accompanying HTML files. The only problem is that the output is so pretty it's encouraged me to write more documentation, which I'm in the slow process of doing! Oh well... Also I may need to find a web site to host it this time, so more people can see it. :)How about ours?

DarkTimes
30th January 2010, 21:29
Thanks Turkey, but I've taken up an offer of hosting the distribution from morpha. It makes sense since he's responsible for having written parts of it. Thanks your generous offer anyway. :)

DarkTimes
1st February 2010, 19:51
Uploaded 1.6.4 beta to the beta post (http://www.lfsforum.net/showthread.php?p=1359033#post1359033).

Changes


Fixed bug with OSD in cruise.py example
Fixed bug with version() not correctly detecting higher pyinsim versions
Lots of small optimisations to the packet receive code and to unpacking individual packets
Changed ISP_ALL to EVT_ALL
Changed ISP_OUTSIM and ISP_OUTGAUGE to OUT_OUTSIM and OUT_OUTGAUGE
Renamed many helper functions, as part of the great API cleanup
Renamed InSimError to Error and InSimVersion to VersionError
Cleaned up all packets, removing unessesary methods and parameters
Improvements to InSim.sendb(), so it works more reliably

Note: This beta release does not include the documentation, as it's currently being reworked.

Note: All the packets have been tweaked, so there may be some attribute errors.

learjet45
7th March 2010, 05:35
I've picked up my cruise script project again thanks to DarkTimes's base script with 1.6.4 beta, and I'm loving it.
I've been doing some changes and additions, and so far so good. Except one thing.
I've tried making an !give code to send other users money. It's not very advanced and I know it will let you send money to yourself. but what I need to get done first is it actually giving money. Here is what I have:

#Change cash level
def cmd_give(ncn, args):
if args:
for arg in args:
ncn = arg.split(':')[-0]
amt = arg.split(':')[-1]
#insim.sendm('^3| ^7You are trying to send %s $%s' % (user, amt))
ncn.vars.cash += amt
insim.sendm('cash changed')

The commented out line was for testing. I was trying to get it to say you are sending whatever user a certain amount. However that failed. It would print out 2 messages, saying "You are trying to send user $user" and "You are trying to send 1 $1".
Even though I figured I would have issues, I tried to keep going. Basically, I got as far as that. Any ideas? Here is what python shell tells me:

>>> Traceback (most recent call last):
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2570, in __recvThread
[self.__recv(sock) for sock in socks]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2591, in __recv
[self.__onPacketData(pdata) for pdata in self.__buffer]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2608, in __onPacketData
if isp: self.event(ptype, packet)
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2475, in event
[callback(self, *args) for callback in self.__callbacks[evt]]
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 267, in message_out
CMD_LOOKUP[cmd0](ncn, cmd[1])
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 231, in cmd_give
ncn.vars.cash += amt
AttributeError: 'str' object has no attribute 'vars'

DarkTimes
7th March 2010, 14:08
The commands are passed by pyinsim already split, so if you typed the command '!give DarkTimes 500' in LFS, the args which are passed into cmd_give() should be a list like this:

['DarkTimes', '500']

The !give part of the command was process earlier in the script.

I wrote and tested this and it seems to work. I had to add an extra function to find a user by their UName, because it cleaned up some of the flow logic in the function.

# Get a conn from their UName
def ncn_from_uname(uname):
ncn = filter(lambda n: n.UName == uname, connections.values())
if ncn:
return ncn[0]
return None

def cmd_give(ncn, args):
# Check args are correct length.
if len(args) != 2:
insim.sendm('^3| Invalid give command', ncn.UCID)
return

# Get the uname and ncn for the user we're giving this to.
other_uname = args[0]
other_ncn = ncn_from_uname(other_uname)
if not other_ncn:
insim.sendm('^3| The user %s ^3does not exist' % other_uname, ncn.UCID)
return

# Check the amount set is a number.
if not args[1].isdigit():
insim.sendm('^3| The amount %s is not a number' % args[1], ncn.UCID)
return

# Award the amount and confirm.
amount = int(args[1])
other_ncn.vars.cash += amount
insim.sendm('^3| You have been given $%d by %s' % (amount, ncn.PName), other_ncn.UCID)
insim.sendm('^3| You have given %s^3 the amount of $%d' % (other_ncn.PName, amount), ncn.UCID)

I hope that helps.

learjet45
7th March 2010, 15:57
Thanks DarkTimes!
Edit:
Can I get some help with an !location or !position code

I have tried to make a code to print out the users coordinates (doesnt have to be a certain user, just the user who types it), but can't seem to get it.

Thanks!

DarkTimes
7th March 2010, 20:30
We already store the users last XYZ coords in the user_vars, as list called last_pos, so basically all you would need to do is something like this:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.meters(last_pos[0])
y = pyinsim.meters(last_pos[1])
z = pyinsim.meters(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)

Note: that code is untested.

learjet45
7th March 2010, 23:40
Tried it, doesn't seem to work.

>>> Traceback (most recent call last):
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2570, in __recvThread
[self.__recv(sock) for sock in socks]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2591, in __recv
[self.__onPacketData(pdata) for pdata in self.__buffer]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2608, in __onPacketData
if isp: self.event(ptype, packet)
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2475, in event
[callback(self, *args) for callback in self.__callbacks[evt]]
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 407, in message_out
CMD_LOOKUP[cmd0](ncn, cmd[1])
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 174, in cmd_location
x = pyinsim.meters(last_pos[0])
AttributeError: 'module' object has no attribute 'meters'

DarkTimes
8th March 2010, 14:24
I misspelled the word 'metres'. :)

Try:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.metres(last_pos[0])
y = pyinsim.metres(last_pos[1])
z = pyinsim.metres(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)

learjet45
8th March 2010, 23:49
I misspelled the word 'metres'. :)

Try:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.metres(last_pos[0])
y = pyinsim.metres(last_pos[1])
z = pyinsim.metres(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)
Will try, thanks! Totally missed that myself :P

DarkTimes
5th June 2010, 21:35
I've not been working on this for several months, as I had to cut back on my day-to-day coding due to carpel-tunnel syndrome. However I'm fully recovered and am able to return to spending many hours a day coding (albeit with a much improved posture and an ergonomic keyboard). This means I hope to finish up on this beta and finally get it out there. Sorry for the six month delay. :)

Dygear
5th June 2010, 23:14
I had to cut back on my day-to-day coding due to carpel-tunnel syndrome.

I know how that feels! I'm only 21 too, so this sucks massively.

DarkTimes
6th June 2010, 03:39
I know how that feels! I'm only 21 too, so this sucks massively.
Hah, I wish I was 21.

Actully I don't, being 21 was rubbish.

morpha
8th July 2010, 20:13
Just a quickie: I found out that the charsets I used in strmanip are incorrect, so here's a fixed (but untested!) version.

Dygear
8th July 2010, 23:53
I wanted a more complete example program for the documentation, so I wrote a (very) small cruise server. [...] As I say it's just a simple example, but I do have to admit I had fun writing it.

Your a sick man, DarkTimes! So, where is the CTRA system?

mstram
5th August 2010, 03:28
Hi D.T,

First, thanks for writing this, and making it available ! :)

Except for the #@@#%#@^^ stupid indenting, I much prefer the interactive nature of Python rather than the overhead of a compiler.

I downloaded ver 1.55 that was in the first post of this thread, before I found this post (you really could start another thread ;)).

I had 1.55 working on my XP box, and was playing with the example scripts.

I was trying to output the speed and car.Node for a player, and it was sorta working.

I modified the example.4 script (kick player off if speed > 80 kph).

At Blackwood, the first node (starting gate was 299 .. it then went up to 310(?) .. and started over from 0 a few hundred feet from the first hairpin.

When I restarted the race, sometimes the script was working ... other times the node was increasing without the car even moving !

(Is that a bug that was fixed with 1.6.4 ?)

The line of code I added was just :

insim.send(pyinsim.ISP_MST, Msg='(NODE):%d' % car.Node)

Anyway, I downloaded 1.64, ran the installer and now when I try to run any of the example scripts, I'm getting :

InSim Error: Could not init InSim


Mike
================================

My python path =

C:\Python26\Lib;C:\Python26\DLLs;C:\Python26\Lib\l ib-tk;E:\Python26\Lib;E:\Pytho
n26\DLLs;E:\Python26\Lib\lib-tk;E:\Python26\Lib\site-packages

(E:\python26 is where pyinsim loaded itself, and e:\python26 is in my path)

Directory of E:\Python26\Lib\site-packages

08/04/2010 10:24 PM <DIR> ..
08/04/2010 10:24 PM <DIR> pyinsim
08/04/2010 10:24 PM <DIR> .
02/01/2010 03:44 PM 292 pyinsim-1.6.4-py2.6.egg-info
1 File(s) 292 bytes

Directory of E:\Python26\Lib\site-packages\pyinsim

08/04/2010 10:24 PM <DIR> ..
08/04/2010 10:24 PM <DIR> .
02/01/2010 03:44 PM 85,035 pyinsim.py
08/04/2010 10:24 PM 98,051 pyinsim.pyc
08/04/2010 10:24 PM 98,051 pyinsim.pyo
02/01/2010 03:39 PM 341,757 _strmanip.py
08/04/2010 10:24 PM 339,955 _strmanip.pyc
08/04/2010 10:24 PM 339,955 _strmanip.pyo
02/01/2010 03:39 PM 940 __init__.py
08/04/2010 10:24 PM 362 __init__.pyc
08/04/2010 10:24 PM 362 __init__.pyo
9 File(s) 1,304,468 bytes

Total Files Listed:
10 File(s) 1,304,760 bytes
5 Dir(s) 120,520,704 bytes free



PYINSIM 1.6.4 BETA

I've uploaded pyinsim 1.6 BETA below. The new version of the library has been largely rewritten and contains many changes and additions, especially to the core InSim class. The idea behind this release is to clean up a lot of the old guff left over from earlier versions, so I can focus on pyinsim 2.0 and Python 3.0 support. That being said it is still the best version of pyinsim evar! :D

PoVo
5th August 2010, 10:34
Great Cruise example DarkTimes, a nice way for beginners to learn Python that way :thumb:

DarkTimes
7th August 2010, 21:26
@mstram

Heya, I'm not sure what the problem is. Are you using the examples from the old version with the beta? If so they won't work as the API was changed. There are new examples included with the beta that should be OK. If you're not then I would need to see the code you're trying to run to figure out what the problem is.

mstram
7th August 2010, 21:41
Dt,

I'm trying to run any of the examples in the 1.6.4..beta\examples folder.

I'm running python ver 2.6.5

Just get :
InSim Error: Could not init InSim

I haven't installed any third party debuggers / IDE's.

Is there a "native" python "trace" or debug instruction ? .. or any other info I can provide?

DarkTimes
7th August 2010, 21:58
You can delete the try/except block in an example, which should give you a stacktrace, but I don't think you will get much more info if it's just a InSim Init message (this is the error that's thrown when no other message is applicable).

You could try this, the simplest pyinsim program:

import pyinsim

insim = pyinsim.InSim()
insim.init('localhost', 29999)

I'm running the examples now and they work fine for me.

Edit: I just installed Python 2.6 to make sure and it's still working fine.

EditEdit: Some Python IDEs do allow debugging, but it depends on which one you are using.

mstram
7th August 2010, 23:27
I have it working now.

It appears to have been an issue with the firewall and port 29000 not being open, the error was in the socket initialization.

Also, I can't tell if the code was supposed to have the "raise Error('Could not init InSim')" line indented under the 'except' or not (it wasn't indented).


except socket.error as err:
raise Error(err[1])
raise Error('Could not init InSim')


or


except socket.error as err:
raise Error(err[1])
raise Error('Could not init InSim')





Also, how do you terminate an example program?

Ctr-BRK, CTR-C have no effect, I've been going to the Windows task manager and killing python

It would preferable to add a keypress check routine or something similar for a more elegant exit :)

DarkTimes
8th August 2010, 00:53
No, it shouldn't be indented. It's not in my version. Not sure why that is, maybe your IDE interpreting the spaces wrongly.

I was just thinking of that keypress issue today while I was checking the examples. I might need to think about that. I'm not 100% sure that should be a part of pyinsim itself though, as it's kinda of outside its realm. It's not difficult to add it yourself, but it's a bit too late on a Saturday night for me to code up an example now, but I'll think about it more when I'm sober tomorrow.

mstram
8th August 2010, 01:38
Tried running Cruise.py
--------------

Traceback (most recent call last):

File "C:\Python26\Lib\site-packages\pyinsim\pyinsim.py", line 2570, in __recvT
hread
[self.__recv(sock) for sock in socks]

File "C:\Python26\Lib\site-packages\pyinsim\pyinsim.py", line 2591, in __recv
[self.__onPacketData(pdata) for pdata in self.__buffer]

File "C:\Python26\Lib\site-packages\pyinsim\pyinsim.py", line 2608, in __onPac
ketData

if isp: self.event(ptype, packet)

File "C:\Python26\Lib\site-packages\pyinsim\pyinsim.py", line 2475, in event
[callback(self, *args) for callback in self.__callbacks[evt]]

File "cruise.py", line 118, in new_ply
if npl.CName in ncn.vars.cars:

AttributeError: 'IS_NCN' object has no attribute 'vars'

DarkTimes
8th August 2010, 09:32
The cruise.py example only works if you're connected to a dedi-server, I think that might be the problem. I guess I should write that somewhere in it.

DarkTimes
8th August 2010, 09:53
If you want to use a key to exit the program you can simply do this.

import pyinsim

insim = pyinsim.InSim()
insim.init()

raw_input('Press <ENTER> to exit')

insim.close()

In the current beta this actually causes an exception to be thrown on closing (still closes though :)), but I have fixed that in my development version.

mstram
8th August 2010, 17:36
The cruise.py example only works if you're connected to a dedi-server, I think that might be the problem. I guess I should write that somewhere in it.

Hmm ...... now I'm confused.

Do you mean I have to connect to a server using the multiplayer option dialog? ....

And then run the "local" server?

So that LFS would then be connected to two servers?


"""Example 15: Cruise

A simple cruise server. As you drive you earn cash, which can be spent on buying and selling cars. User data is stored as pickled Python objects in the
data\cruise folder,

DarkTimes
8th August 2010, 18:27
The cruise.py script needs to be connected to a dedi-server (http://www.lfs.net/?page=addons) in order to work. It won't work if you just connect it to a local LFS client. You can download the dedi and run it on your local computer, connect cruise.py to it, then start LFS and connect to the dedi using the Local Network option in Join Specific Host.

It may be possible to run the cruise script on a local copy of LFS, if you open it up and change the constant

HOST_ID = 0

to some other value (not between 0 and 255), such as

HOST_ID = -1

#or

HOST_ID = 256

But I've not tested this and most likely some things will break horribly.

mstram
8th August 2010, 22:38
Hi D.t.,

Ok, I'll give that a try.

I had tried running that server before, the instructions are not very clear about where to setup the config info. I tried editing the setup.cfg file, but when I start the server it's ignoring / over writing the settings.

It sure doesn't have as nice an interface for settings as the multiplayer screen in the LFS client.

I'll revisit it , and I saw there was a network "sticky", I'll re-read it, and see what the issue is with it

Dygear
8th August 2010, 23:15
You could also use this to raw send commands to the console?

DarkTimes
8th August 2010, 23:18
I'm not sure what you mean, Dygear.

DarkTimes
8th August 2010, 23:27
Hi D.t.,

Ok, I'll give that a try.

I had tried running that server before, the instructions are not very clear about where to setup the config info. I tried editing the setup.cfg file, but when I start the server it's ignoring / over writing the settings.

It sure doesn't have as nice an interface for settings as the multiplayer screen in the LFS client.

I'll revisit it , and I saw there was a network "sticky", I'll re-read it, and see what the issue is with it
Yeah it can be a bit tricky the first time, after that it's easy though. Here is a quick guide :)


Download the dedi-server
Open the file setup.cfg with your favourite code editor
Locate the line that says '//insim=29999' and change it to say '/insim=29999'
Find the line '/usemaster=yes' and change it to '/usemaster=no' (optional but good for devlopment)
Start the dedi-server by double-clicking the exe
Run the cruise.py script from either the command line or from your Python IDE
Confirm in the dedi-server window that 'pyCruise' has connected
Start LFS, go to Multiplayer, select Join Specific Host, then choose the Local Network tab
Set the Host IP address to '127.0.0.1' and the Host port to 63392
Click Go in LFS and confirm you have connected to the host


That should work for you, if it does not then I will need more information.

Edit: LFS will overwrite your config if you edit it while the program is running. You need to stop LFS, edit the config, then restart it.

mstram
8th August 2010, 23:43
Hi Dt.,

Thanks for the all the info !

It's almost working :D

The dedicate server says "A new guest is connecting", then the LFS client pops up a message saying "Did not receive driver info", and the ded-serv then says "New Guest failed to connect"

Mike

DarkTimes
8th August 2010, 23:48
I don't know the solution to that problem, you might need to ask in the hosts forum.

Dygear
10th August 2010, 02:24
If you want to use a key to exit the program you can simply do this.

import pyinsim

insim = pyinsim.InSim()
insim.init()

raw_input('Press <ENTER> to exit')

insim.close()

In the current beta this actually causes an exception to be thrown on closing (still closes though :)), but I have fixed that in my development version.

You could also use this to raw send commands to the console?

I'm not sure what you mean, Dygear.

Yeah, sorry that was really out of context, as when I posted that message I had no idea there was another page to this thread.

Anyway, You could use RAW_INPUT to send raw command async to the lfs console after you type in the command, and then when you hit return it would send a packet that would run that command from the host side. It would be like RCON (Remote Console in Counter-Strike terms.)

DarkTimes
10th August 2010, 09:24
Ah right, I think I get you. Yeah, you could use it to send commands from the console to LFS.

import pyinsim

print 'Enter command and press return to send, or press CTRL+Z to exit'

insim = pyinsim.InSim()

try:
insim.init('localhost', 29999)

while True:
cmd = raw_input('Enter Command: ')
insim.sendm(cmd) # Send command!

except EOFError:
insim.close()
except pyinsim.Error as err:
print 'InSim Error: %s' % err

DarkTimes
9th September 2010, 23:39
Inspired by PRISM I decided to see if I could use pyinsim to create a plugin hosting system. I've been hacking away at it for a few days and it seems to be working quite well, although it's just a prototype. I ended up having to rewrite a bunch of stuff in pyinsim, especially the socket code, so that it nicely supported multiple hosts and also properly integrated with the plugin system.

Here is a brief list of features:

Modular plugin system
Simple per-plugin data persistence
Inter-plugin messaging system
Support for multiple hosts
Host state management (players, connections etc..)

Creating a plugin

You create a new plugin by creating a new module in the plugins directory. Inside this module you add you plugin classes, which must inherit from the plugin.Plugin base class. Here is an example of the simplest plugin you could create, that simply sends a welcome message to each host that connects.

# hello.py

import plugin

class Hello(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)

def connected(self, host):
host.send(plugin.ISP_MST, Msg='Hello, %s' % host.name)

The Plugin base class contains a few methods you can override to catch specific events.


initialized - The plugin system is initialized, the plugin_map and data members are populated.
uninitialized - The plugin system is shutting down and the plugin data is about to be saved to disk.
connected - A new host has connected and added to the host_map.
disconnected - A host has been lost and removed from the host_map.

The Plugin also contains a bunch of other methods and properties.


plugin_map - A dictionary of currently loaded plugins keyed by name
host_map - A dictionary of currently connected hosts keyed by name
data - A user object you can set that will be available the next time the plugin starts (you can store anything that's serializable, it's a dict by default)
bind(), unbind() and isbound() - Packet event binding methods
subscribe(), unsubscribe() and notify() - Messaging system for communicating between plugins
find_plugin() - Find another plugin by name

You can bind packet events inside a plugin, just like you could with normal pyinsim. Here is a simple example that spectates any driver that goes above 80 Kph.

# speeding.py

import plugin

SPEED_LIMIT = 80 # Kph

class Speeding(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)
self.bind(plugin.ISP_MCI, self.car_update)

def car_update(self, host, mci):
for car in mci.Info:
if plugin.kph(car.Speed) > SPEED_LIMIT:
npl = host.players[car.PLID]
host.sendm('/spec %s' % npl.PName)
host.sendm('%s ^7spectated for speeding!' % npl.PName)
host.sendm('Do not go above %d Kph!' % SPEED_LIMIT, ucid=npl.UCID)

The host argument represents the host that the packet came from. It has a few methods and properties you can call.


conns - The current connection list dictionary
players - The current player list dictionary
state - The current host state (last STA received)
name - The host name (as defined in config.py)
hostaddr - A tuple containing the (ip, port) of the host
send() - Send a packet to the host
sendm() - Send a message to the host (MTC, MST or MSX)

Configuring and starting the app

The configuration can be found in config.py file, which is basically just a python file where you can set various global variables. In here you can configure which hosts you want to connect to, and also which plugin modules get loaded. It's pretty self-explanatory when you see it.

Once you have created some plugins and configured the hosts, you can start the app by double-clicking the 'pyinsim.bat' file in the directory, or with the command 'python app.py'.

As I say it's just a prototype that I hacked together in a couple of days. It'll probably have a few bugs in it, and I'm not 100% happy with the plugin API, so that might change if I continue to work on it, but all in all it seems to be working quite nicely.

Edit: Note you cannot use a ReqI of 255 in your plugins, that's reserved by the system. I may change it to some other number. Not sure.

Dygear
11th September 2010, 06:15
host.send(plugin.ISP_MST, Msg='Hello, %s' % host.name)

I wish PHP had this elegance. Great work as always DarkTimes. :)

DarkTimes
11th September 2010, 15:03
Yeah, Python is a very elegant language. That's one of the reasons I like it so much. Plus, it's just awesome. :)

Anyway, trying to think up ideas for plugins, I wrote a little swear-filter. It checks each message for a swear-word and kicks the player after giving three warning. You might need to make the actual swear list more realistic. :p

# !/usr/bin/env python
# swear_filter.py

import plugin
import re

# List of swearwords, in lower-case.
SWEAR_WORDS = ['boobs', 'bum', 'fart', 'knockers', 'poo']
MAX_WARNINGS = 3 # 0 for no warning.
SPLIT_REGEX = re.compile('\W+')

def get_swearwords(msg):
"""Return a list of swearwords in the message."""
words = SPLIT_REGEX.split(msg.lower())
return filter(lambda w: w in SWEAR_WORDS, words)

def contains_swearword(msg):
"""Return true if a message contains a swearword."""
words = SPLIT_REGEX.split(msg.lower())
for word in words:
if word in SWEAR_WORDS:
return True
return False

class SwearFilter(plugin.Plugin):
"""Plugin to handle a swear-filter."""
def __init__(self):
plugin.Plugin.__init__(self)
self.warnings = {} # Store warnings in a dict with the uname as the key.
self.bind(plugin.ISP_MSO, self.message_out)
self.bind(plugin.ISP_CNL, self.connection_left)

def message_out(self, host, mso):
"""Handle MSO message packet."""
if mso.UserType == plugin.MSO_USER:
msg = mso.Msg[mso.TextStart:]
if contains_swearword(msg):
ncn = host.conns[mso.UCID]
self.update_warnings(ncn)
self.check_warnings(host, ncn)

def connection_left(self, host, cnl):
"""Handle CNL packet, delete any warnings which exist for the user."""
ncn = host.conns(cnl.UCID)
if ncn.UName in self.warnings:
del self.warnings[ncn.UName]

def update_warnings(self, ncn):
"""Increment a user's warnings."""
if ncn.UName in self.warnings:
self.warnings[ncn.UName] += 1
else:
self.warnings[ncn.UName] = 1 # Add new warning to dict.

def check_warnings(self, host, ncn):
"""Check if the user has exceeded their warning quota, otherwise send warning messge."""
if self.warnings[ncn.UName] >= MAX_WARNINGS:
host.sendm('^7| %s ^3kicked for swearing' % ncn.PName)
host.sendm('/kick %s' % ncn.UName)
else:
warnings = self.warnings[ncn.UName]
host.sendm('^7| ^3Swear warning (%d of %d)' % (warnings, MAX_WARNINGS), ncn.UCID)

Drop it into a module called 'swear_filter.py' and add a reference to 'plugins.swear_filter' to the PLUGINS config variable.

DarkTimes
11th September 2010, 21:25
OK, only one download in the past three days, I guess using a scripting language to control hosts isn't a popular idea. :)

Dygear
12th September 2010, 03:49
OK, only one download in the past three days, I guess using a scripting language to control hosts isn't a popular idea. :)

I'm going to tend to disagree, but I guess we shall see.

DarkTimes
12th September 2010, 08:27
Yeah I should have said "using Python as a". :)

JustForFunRacing
14th September 2010, 14:58
Hmm.... would it possible to code something like that with Python/pyinsim:

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

DarkTimes
14th September 2010, 16:46
Yeah, it's easy to log lap and split times, in fact I remember writing this exact program for someone a couple of years ago. The only issue is that LFS does not provide any way to figure out what setup someone is using, as that's considered cheating by the game. I can write a simple plugin that logs lap and splits for you though. Just tell me the format that the CSV file should use.

DarkTimes
14th September 2010, 18:20
Hmm.... would it possible to code something like that with Python/pyinsim:

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

Here is a rough plugin I've written for pyinsim3000 (http://www.lfsforum.net/showthread.php?p=1483056#post1483056) that logs lap and split times to a text file. You need to drop it into the plugins directory and add 'plugins.lap_csv' to the PLUGINS variable in the config.py file.

It creates a new CSV file for each session (practice, qualifying, race etc..) and stores the CSV in the format:

username, nickname, track, car, lap_num, sp1, sp2, sp3, time

Obviously I've written it quickly, so it might have one or two bugs. You can stop and start the logging with the commands !start and !stop.

# lap_csv.py

import plugin
import datetime
import os

CURRENT_DIR = os.path.dirname(__file__)

# uname, pname, track, car, lap_num, sp1, sp2, sp3, time

class LapInfo(object):
def __init__(self):
self.splits = [0,0,0]
self.last_split = 0

class LapCSV(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)
self.bind(plugin.ISP_RST, self.race_start)
self.bind(plugin.ISP_TINY, self.tiny)
self.bind(plugin.ISP_SPX, self.split)
self.bind(plugin.ISP_LAP, self.lap)
self.bind(plugin.ISP_MSO, self.message_out)

def connected(self, host):
host.send(plugin.ISP_TINY, ReqI=255, SubT=plugin.TINY_RST)

def disconnected(self, host):
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False

def create_name(self, track):
now = datetime.datetime.now()
return '%s_%s.csv' % (track, now.strftime('%d_%m_%Y_%H_%M_%S'))

def race_start(self, host, rst):
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False
name = self.create_name(rst.Track)
path = os.path.join(CURRENT_DIR, name)
host.file = open(path, 'w')
host.logging = True

def tiny(self, host, tiny):
if tiny.SubT == plugin.TINY_REN:
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False

def split(self, host, spx):
npl = host.players[spx.PLID]
if not hasattr(npl, 'lap_info'):
npl.lap_info = LapInfo()
npl.lap_info.splits[spx.Split-1] = (spx.STime - npl.lap_info.last_split)
npl.lap_info.last_split = spx.STime

def lap(self, host, lap):
if hasattr(host, 'logging') and host.logging:
npl = host.players[lap.PLID]
if not hasattr(npl, 'lap_info'):
return
ncn = host.conns[npl.UCID]
sp1 = npl.lap_info.splits[0]
sp2 = npl.lap_info.splits[1]
sp3 = lap.LTime - npl.lap_info.last_split
host.file.write('%s,%s,%s,%s,%d,%s,%s,%s,%s\n' % (ncn.UName,
ncn.PName,
host.state.Track,
npl.CName,
lap.LapsDone,
plugin.timestr(sp1),
plugin.timestr(sp2),
plugin.timestr(sp3),
plugin.timestr(lap.LTime),))
npl.lap_info = LapInfo()

def message_out(self, host, mso):
if mso.UserType == plugin.MSO_PREFIX:
args = mso.Msg[mso.TextStart:].split()
if args:
cmd = args[0].lower()
if cmd == '!start':
if hasattr(host, 'logging'):
host.logging = True
host.sendm('^3| ^7Started logging', mso.UCID)
elif cmd == '!stop':
if hasattr(host, 'logging'):
host.logging = False
host.sendm('^3| ^7Stopped logging', mso.UCID)

Dygear
14th September 2010, 18:27
Color me impressed.

codehound
14th September 2010, 21:15
OK, only one download in the past three days, I guess using a scripting language to control hosts isn't a popular idea. :)

Well, my problem is that I didn't(and still don't) understand what it is or what I could do with it. :confused:

But I certainly do like pyinsim and thank you very much for the library and the excellent examples (which is the only way an idiot like me can make a program). I used pyinsim to write a program that averages N number of past laps and keeps updating the average after each lap (moving average). It displays the result to the LFS chat screen for each driver. I wanted to duplicate the Time Trail mode from iRacing. It also writes the splits, laptimes, and averages to the Python shell window. Then I just copy and paste the results to a file after my session is over. Not very elegant but it works.

So thanks again and your hard work is appreciated even though we users don't always say so.

JustForFunRacing
15th September 2010, 09:31
Great one, Dark Times!

I got it working, but there is one problem and two suggestion:

Problem: After ending the logging with the stop command and then closing your app, it takes quite a long time until the file is written completely (around a minute or so). I guess there is no "close file" command and Windows closes the file itself after a while.

Suggestion 1: Is it possible to only log my own times, instead of all connected racers?

Suggestion 2: Would it be possible to create an exe file of your app once it reaches final status?

DarkTimes
15th September 2010, 10:35
If you want it as an .exe then it might just be easier to write it using .NET. You can make exe files from Python programs, but it's a pain the arse and usually not worth the hassel.

In terms of the other things, sure.

JustForFunRacing
15th September 2010, 10:48
Hmm... well I tried to create an exe - without success. So .NET really seems to be the better choice.

I also saw, that you also have created Spark.... Hmm... Would take some days to learn how to code in .NET - but itīs worth it I guess...

Any clue how to solve the long waiting until the CSV is useable (it still stays a 0kb file for a long time)?

DarkTimes
15th September 2010, 16:42
Well, my problem is that I didn't(and still don't) understand what it is or what I could do with it. :confused:
I've been thinking about how to answer this question for a couple of days, and I guess it does need an explanation.

Basically pyinsim 3000 is not an InSim library, it is a standalone InSim program that you extend by writing plugins.

The program itself is very basic. It manages multiple InSim hosts, keeps track of connection and players lists, plus a bunch of other things that almost every InSim program needs. It does not implement any functionality itself, instead you add functionality to it by adding plugins. Plugins are small, modular peices of code that can be easily 'plugged-in' to the main system by dropping them into the 'plugins' folder. You can write your own plugins, or use plugins that have been written by other people. This allows you to build a host system for your server just by choosing which plugins you want to add.

For instance, if you were building a lapper type server, there may be several plugins you could use. One for tracking lap times, another for storing laps in a database, another for awarding points to drivers, another for dealing with commands etc.. This way you can pick and choose which parts of the system you want to use, and can easily turn features on and off by adding and removing plugins. Ideally, each plugin would only do one small thing. You can think of them as small independent blocks you can combine in different ways to build exactly the program that you want. For instance if you didn't want your lapper server to award points to drivers, you could just turn that plugin off. Or if you wanted it to store driver points in a different way, you could switch that plugin out for a different one, or replace it with your own.

pyinsim 3k provides the infrastructure to support this. Ideally it should be usable by people without any programming skill, all they would need to know is how to add plugins to the system and configure them. Of course having the knowledge to write and edit Python code would be a benefit.

I need to write a proper post explaining how the plugins work, but I hoped people would figure that out by looking at the examples. Still, a more detailed explanation of how they work and why they work the way they do is required. Also it is just a rough, unfinished prototype. That said it is stable and most of the code is pretty good (which is why I released it, I was surprised by how much it didn't suck).

Dygear
15th September 2010, 17:13
I was surprised by how much it didn't suck.

I've been saying plugins are the way to go for the past 5 years, I'm so glad people are starting to take notice that programming for InSim is pretty hard when your new. Providing a good plugin interface is important for the prorogation of the game as a whole and will be a key feature to any fully functional InSim system and a key to increase the polarity of LFS as a whole.

We can do much better then this, we can do so much better then this, but it requires C/C++/(Source)PAWN and a hooks into the LFS core. Maybe in 5 years (S/P LFS S3) I'll write C++ InSimMod with some of Scawen's help in and SourcePAWN (http://wiki.alliedmods.net/Introduction_to_SourcePawn) or regular PAWN (http://www.compuphase.com/pawn/pawn.htm) for the plugins. Of course I'd need to teach myself much more about C++ then I already know, but I needed to know alot more about PHP before I could make PRISM and that's where the LFSWorldSDK came in handy.

DarkTimes
15th September 2010, 17:33
I meant I was surprised that the code I had written didn't suck, I've been sold on the plugin idea for long time. I struggled quite a lot with the way the program was structured and it took me a long time for figure out the best route to go down. The thing I'm happiest about in pyinsim 3K is that it is not a multi-threaded program - it does everything on a single-thread. I plan to merge the new socket code back into the main pyinsim branch, as being able to create single-threaded pyinsim programs is a very important change.

That said I'm still trying to figure out whether to keep developing this. It am happy with it, but with PRISM getting so much support on the forum it seems unnecessary to have two plugin systems that are so similar. I do however have a rough prototype of a .NET plugin system that uses JavaScript as the language that I'm pretty excited about (it's more awesome than it sounds :p), but it would be a colossal amount of work to finish.

Dygear
16th September 2010, 05:35
That said I'm still trying to figure out whether to keep developing this. It am happy with it, but with PRISM getting so much support on the forum it seems unnecessary to have two plugin systems that are so similar. I do however have a rough prototype of a .NET plugin system that uses JavaScript as the language that I'm pretty excited about (it's more awesome than it sounds :p), but it would be a colossal amount of work to finish.

I think there is room for all three. :)

codehound
16th September 2010, 15:23
I've been thinking about how to answer this question for a couple of days, and I guess it does need an explanation.

Basically pyinsim 3000 is not an InSim library, it is a standalone InSim program that you extend by writing plugins.


Thank you for your reply. Re-reading the post a third time it finally makes sense. Two things through me off. One, I was not clear that it was not part of pyinsim. And second, I missed the 'P' in "Plugin" and was reading it as though Plugin was plugin, referring to methods the user had to provide. :doh: Therefore, I was not seeing what advantages there were over just using pyinsim.

DarkTimes
19th September 2010, 08:32
I was trying to create a project for this on Google Code, but it won't let me as there is already a InSim library on SourceForge called pyinsim, which is empty and looks discarded! Does anyone know anything about this? I checked the dates and it was register just after I posted the first version of the library. This is really annoying!

http://sourceforge.net/projects/pyinsim/

Edit: Pff now I've got to wait for this other guy to "approve" my bloody project!

DarkTimes
19th September 2010, 11:47
OK, sod it. I've decided not to use Google Code but to use CodePlex instead, as really that's what I'm familiar with anyway. You can visit the new CodePlex site for pyinsim here:

http://pyinsim.codeplex.com/

I've done an initial commit for the pyinsim 2.0 source code, which you should be able to access. I'm almost ready to release pyinsim 2.0, but a couple of things still need to be worked on, such as the OutGauge and OutSim support which is currently very buggy. That said, InSim and InSim relay are currently fully supported and seem to be working well.

I decided to use Mercurial for this project, as I've been messing around with it for a couple of days and it seems really good. I have to say getting the CodePlex site setup literally took less than five minutes. And to commit the source code all it took was...

hg clone https://hg01.codeplex.com/pyinsim pyinsim
cd pyinsim
hg add
hg commit
hg push

which was awesome. Anyway, I'll provide more info about pyinsim 2.0 and why I've made a project site for it later.

codehound
26th September 2010, 19:30
I am trying to write a program that will detect when the connection to LFS insim dies and will restart my client program. I use the insim.close() command but I still get the following message when I try to init (still using 1.6 version of pyinsim, by the way):

InSim Error: A connect request was made on an already connected socket

def openConnection(insim, client):
# Initialise InSim.
try:
insim.init(client.clientAddr, client.port, Prefix='%', Admin=client.admin,IName=client.appName)
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_ISM)
onceConnected = insim.connected
except pyinsim.Error as err:
print 'InSim Error:', err

# Connect to LFS insim for first time.
onceConnected = False
openConnection(insim, thisClient)
while True:
try:
sleep(STATUS_CHECK_INTERVAL)
if not insim.connected:
if not onceConnected:
# This program started before LFS insim available, try again.
openConnection(insim, thisClient)
else:
# Connection to LFS insim was lost. Stop sockects and restart on next cycle.
insim.close()
onceConnected = False
except KeyboardInterrupt:
print 'User stopped program.'
exitProgram() # Close log files and save data. Then exit.

DarkTimes
26th September 2010, 19:40
Edit: Sorry, I didn't realise this was the old thread. I don't provide support for this version of pyinsim anymore. If you really want to use it, you're on your own (the code is in site-packages if you want to read it). I suggest moving to the new and shiny version of the library.

Edit: I wrote a rough example of doing this with pyinsim 2.0.

http://www.lfsforum.net/showthread.php?p=1490842#post1490842