private int readFromOffset(int offset, byte[] rawData) { totalPacketCount++; if (totalPacketCount == estimateRateStartPacket) { ticksAtRateEstimateStart = DateTime.Now.Ticks; } else if (totalPacketCount == estimateRateEndPacket && ticksAtRateEstimateStart > 0) { rateEstimate = (float)(TimeSpan.TicksPerSecond * (estimateRateEndPacket - estimateRateStartPacket)) / (float)(DateTime.Now.Ticks - ticksAtRateEstimateStart); } // the first 2 bytes are the version - discard it for now int frameTypeAndSequence = rawData[offset + 2]; int frameType = frameTypeAndSequence & 3; int sequence = frameTypeAndSequence >> 2; int frameLength = 0; if (frameType == 0) { telemPacketCount++; frameLength = sTelemetryData_PacketSize; Boolean sequenceCheckOK = isNextInSequence(sequence); if (strictPacketOrdering && !sequenceCheckOK) { discardedTelemCount++; } else { handle = GCHandle.Alloc(rawData.Skip(offset).Take(frameLength).ToArray(), GCHandleType.Pinned); sTelemetryData telem = (sTelemetryData)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(sTelemetryData)); if (sequenceCheckOK || !telemIsOutOfSequence(telem)) { //buttonsState = ConvertByteToBoolArray(telem.sDPad); lastSequenceNumberForTelemPacket = sequence; workingGameState = StructHelper.MergeWithExistingState(workingGameState, telem); // newSpotterData = workingGameState.hasNewPositionData; handle.Free(); } } } else if (frameType == 1) { frameLength = sParticipantInfoStrings_PacketSize; handle = GCHandle.Alloc(rawData.Skip(offset).Take(frameLength).ToArray(), GCHandleType.Pinned); sParticipantInfoStrings strings = (sParticipantInfoStrings)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(sParticipantInfoStrings)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, strings); handle.Free(); } else if (frameType == 2) { frameLength = sParticipantInfoStringsAdditional_PacketSize; handle = GCHandle.Alloc(rawData.Skip(offset).Take(frameLength).ToArray(), GCHandleType.Pinned); sParticipantInfoStringsAdditional additional = (sParticipantInfoStringsAdditional)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(sParticipantInfoStringsAdditional)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, additional); handle.Free(); } return(frameLength + offset); }
private Boolean telemIsOutOfSequence(sTelemetryData telem) { if (telem.sViewedParticipantIndex >= 0 && telem.sParticipantInfo.Length > telem.sViewedParticipantIndex) { int lapsCompletedInTelem = telem.sParticipantInfo[telem.sViewedParticipantIndex].sLapsCompleted; float lapTimeInTelem = telem.sCurrentTime; if (lapTimeInTelem > 0 && lastValidTelemCurrentLapTime > 0) { // if the number of completed laps has decreased, or our laptime has decreased without starting // a new lap then we need to discard the packet. The lapsCompleted is unreliable, this may end badly if (lastValidTelemLapsCompleted > lapsCompletedInTelem || (lapTimeInTelem < lastValidTelemCurrentLapTime && lastValidTelemLapsCompleted == lapsCompletedInTelem)) { discardedTelemCount++; return(true); } } lastValidTelemCurrentLapTime = lapTimeInTelem; lastValidTelemLapsCompleted = lapsCompletedInTelem; acceptedOutOfSequenceTelemCount++; } return(false); }
public static pCarsAPIStruct MergeWithExistingState(pCarsAPIStruct existingState, sTelemetryData udpTelemetryData) { existingState.hasNewPositionData = false; existingState.mGameState = (uint)udpTelemetryData.sGameSessionState & 7; existingState.mSessionState = (uint)udpTelemetryData.sGameSessionState >> 4; existingState.mRaceState = (uint)udpTelemetryData.sRaceStateFlags & 7; // Participant Info existingState.mViewedParticipantIndex = udpTelemetryData.sViewedParticipantIndex; existingState.mNumParticipants = udpTelemetryData.sNumParticipants; // Unfiltered Input existingState.mUnfilteredThrottle = (float)udpTelemetryData.sUnfilteredThrottle / 255f; existingState.mUnfilteredBrake = (float)udpTelemetryData.sUnfilteredBrake / 255f; existingState.mUnfilteredSteering = (float)udpTelemetryData.sUnfilteredSteering / 127f; existingState.mUnfilteredClutch = (float)udpTelemetryData.sUnfilteredClutch / 255f; existingState.mLapsInEvent = udpTelemetryData.sLapsInEvent; existingState.mTrackLength = udpTelemetryData.sTrackLength; // Timing & Scoring existingState.mLapInvalidated = (udpTelemetryData.sRaceStateFlags >> 3 & 1) == 1; existingState.mSessionFastestLapTime = udpTelemetryData.sBestLapTime; existingState.mLastLapTime = udpTelemetryData.sLastLapTime; existingState.mCurrentTime = udpTelemetryData.sCurrentTime; existingState.mSplitTimeAhead = udpTelemetryData.sSplitTimeAhead; existingState.mSplitTimeBehind = udpTelemetryData.sSplitTimeBehind; existingState.mSplitTime = udpTelemetryData.sSplitTime; existingState.mEventTimeRemaining = udpTelemetryData.sEventTimeRemaining; existingState.mPersonalFastestLapTime = udpTelemetryData.sPersonalFastestLapTime; existingState.mWorldFastestLapTime = udpTelemetryData.sWorldFastestLapTime; existingState.mCurrentSector1Time = udpTelemetryData.sCurrentSector1Time; existingState.mCurrentSector2Time = udpTelemetryData.sCurrentSector2Time; existingState.mCurrentSector3Time = udpTelemetryData.sCurrentSector3Time; existingState.mSessionFastestSector1Time = udpTelemetryData.sFastestSector1Time; existingState.mSessionFastestSector2Time = udpTelemetryData.sFastestSector2Time; existingState.mSessionFastestSector3Time = udpTelemetryData.sFastestSector3Time; existingState.mPersonalFastestSector1Time = udpTelemetryData.sPersonalFastestSector1Time; existingState.mPersonalFastestSector2Time = udpTelemetryData.sPersonalFastestSector2Time; existingState.mPersonalFastestSector3Time = udpTelemetryData.sPersonalFastestSector3Time; existingState.mWorldFastestSector1Time = udpTelemetryData.sWorldFastestSector1Time; existingState.mWorldFastestSector2Time = udpTelemetryData.sWorldFastestSector2Time; existingState.mWorldFastestSector3Time = udpTelemetryData.sWorldFastestSector3Time; // Flags existingState.mHighestFlagColour = (uint)udpTelemetryData.sHighestFlag & 7; existingState.mHighestFlagReason = (uint)udpTelemetryData.sHighestFlag >> 3 & 3; // Pit Info existingState.mPitMode = (uint)udpTelemetryData.sPitModeSchedule & 7; existingState.mPitSchedule = (uint)udpTelemetryData.sPitModeSchedule >> 3 & 3; // Car State existingState.mCarFlags = udpTelemetryData.sCarFlags; existingState.mOilTempCelsius = udpTelemetryData.sOilTempCelsius; existingState.mOilPressureKPa = udpTelemetryData.sOilPressureKPa; existingState.mWaterTempCelsius = udpTelemetryData.sWaterTempCelsius; existingState.mWaterPressureKPa = udpTelemetryData.sWaterPressureKpa; existingState.mFuelPressureKPa = udpTelemetryData.sFuelPressureKpa; existingState.mFuelLevel = udpTelemetryData.sFuelLevel; existingState.mFuelCapacity = udpTelemetryData.sFuelCapacity; existingState.mSpeed = udpTelemetryData.sSpeed; existingState.mRPM = udpTelemetryData.sRpm; existingState.mMaxRPM = udpTelemetryData.sMaxRpm; existingState.mBrake = (float)udpTelemetryData.sBrake / 255f; existingState.mThrottle = (float)udpTelemetryData.sThrottle / 255f; existingState.mClutch = (float)udpTelemetryData.sClutch / 255f; existingState.mSteering = (float)udpTelemetryData.sSteering / 127f; existingState.mGear = udpTelemetryData.sGearNumGears & 15; existingState.mNumGears = udpTelemetryData.sGearNumGears >> 4; existingState.mOdometerKM = udpTelemetryData.sOdometerKM; existingState.mAntiLockActive = (udpTelemetryData.sRaceStateFlags >> 4 & 1) == 1; existingState.mBoostActive = (udpTelemetryData.sRaceStateFlags >> 5 & 1) == 1; existingState.mBoostAmount = udpTelemetryData.sBoostAmount; // Motion & Device Related existingState.mOrientation = udpTelemetryData.sOrientation; existingState.mLocalVelocity = udpTelemetryData.sLocalVelocity; existingState.mWorldVelocity = udpTelemetryData.sWorldVelocity; existingState.mAngularVelocity = udpTelemetryData.sAngularVelocity; existingState.mLocalAcceleration = udpTelemetryData.sLocalAcceleration; existingState.mWorldAcceleration = udpTelemetryData.sWorldAcceleration; existingState.mExtentsCentre = udpTelemetryData.sExtentsCentre; existingState.mTyreFlags = toUIntArray(udpTelemetryData.sTyreFlags); existingState.mTerrain = toUIntArray(udpTelemetryData.sTerrain); existingState.mTyreY = udpTelemetryData.sTyreY; existingState.mTyreRPS = udpTelemetryData.sTyreRPS; existingState.mTyreSlipSpeed = udpTelemetryData.sTyreSlipSpeed; existingState.mTyreTemp = toFloatArray(udpTelemetryData.sTyreTemp, 255); existingState.mTyreGrip = toFloatArray(udpTelemetryData.sTyreGrip, 255); existingState.mTyreHeightAboveGround = udpTelemetryData.sTyreHeightAboveGround; existingState.mTyreLateralStiffness = udpTelemetryData.sTyreLateralStiffness; existingState.mTyreWear = toFloatArray(udpTelemetryData.sTyreWear, 255); existingState.mBrakeDamage = toFloatArray(udpTelemetryData.sBrakeDamage, 255); existingState.mSuspensionDamage = toFloatArray(udpTelemetryData.sSuspensionDamage, 255); existingState.mBrakeTempCelsius = toFloatArray(udpTelemetryData.sBrakeTempCelsius, 1); existingState.mTyreTreadTemp = toFloatArray(udpTelemetryData.sTyreTreadTemp, 1); existingState.mTyreLayerTemp = toFloatArray(udpTelemetryData.sTyreLayerTemp, 1); existingState.mTyreCarcassTemp = toFloatArray(udpTelemetryData.sTyreCarcassTemp, 1); existingState.mTyreRimTemp = toFloatArray(udpTelemetryData.sTyreRimTemp, 1); existingState.mTyreInternalAirTemp = toFloatArray(udpTelemetryData.sTyreInternalAirTemp, 1); existingState.mWheelLocalPosition = udpTelemetryData.sWheelLocalPositionY; existingState.mRideHeight = udpTelemetryData.sRideHeight; existingState.mSuspensionTravel = udpTelemetryData.sSuspensionTravel; existingState.mSuspensionVelocity = udpTelemetryData.sSuspensionVelocity; existingState.mAirPressure = toFloatArray(udpTelemetryData.sAirPressure, 1); existingState.mEngineSpeed = udpTelemetryData.sEngineSpeed; existingState.mEngineTorque = udpTelemetryData.sEngineTorque; existingState.mEnforcedPitStopLap = (uint)udpTelemetryData.sEnforcedPitStopLap; // Car Damage existingState.mCrashState = udpTelemetryData.sCrashState; existingState.mAeroDamage = (float)udpTelemetryData.sAeroDamage / 255f; existingState.mEngineDamage = (float)udpTelemetryData.sEngineDamage / 255f; // Weather existingState.mAmbientTemperature = udpTelemetryData.sAmbientTemperature; existingState.mTrackTemperature = udpTelemetryData.sTrackTemperature; existingState.mRainDensity = (float)udpTelemetryData.sRainDensity / 255f; existingState.mWindSpeed = udpTelemetryData.sWindSpeed * 2; existingState.mWindDirectionX = (float)udpTelemetryData.sWindDirectionX / 127f; existingState.mWindDirectionY = (float)udpTelemetryData.sWindDirectionY / 127f; //existingState.mCloudBrightness = udpTelemetryData.sCloudBrightness / 255; if (existingState.mParticipantData == null) { existingState.mParticipantData = new pCarsAPIParticipantStruct[udpTelemetryData.sParticipantInfo.Count()]; } for (int i = 0; i < udpTelemetryData.sParticipantInfo.Count(); i++) { sParticipantInfo newPartInfo = udpTelemetryData.sParticipantInfo[i]; Boolean isActive = (newPartInfo.sRacePosition >> 7) == 1; pCarsAPIParticipantStruct existingPartInfo = existingState.mParticipantData[i]; existingPartInfo.mIsActive = isActive; if (isActive) { existingPartInfo.mCurrentLap = newPartInfo.sCurrentLap; existingPartInfo.mCurrentLapDistance = newPartInfo.sCurrentLapDistance; existingPartInfo.mLapsCompleted = (uint)newPartInfo.sLapsCompleted & 127; // TODO: there's a 'lapInvalidated' flag here but nowhere to put it in the existing struct Boolean lapInvalidated = (newPartInfo.sLapsCompleted >> 7) == 1; existingPartInfo.mRacePosition = (uint)newPartInfo.sRacePosition & 127; existingPartInfo.mCurrentSector = (uint)newPartInfo.sSector & 7; // and now the bit magic for the extra position precision... float[] newWorldPositions = toFloatArray(newPartInfo.sWorldPosition, 1); float xAdjustment = ((float)((uint)newPartInfo.sSector >> 3 & 3)) / 4f; float zAdjustment = ((float)((uint)newPartInfo.sSector >> 5 & 3)) / 4f; newWorldPositions[0] = newWorldPositions[0] + xAdjustment; newWorldPositions[2] = newWorldPositions[2] + zAdjustment; if (!existingState.hasNewPositionData && i != udpTelemetryData.sViewedParticipantIndex && (existingPartInfo.mWorldPosition == null || (newWorldPositions[0] != existingPartInfo.mWorldPosition[0] || newWorldPositions[2] != existingPartInfo.mWorldPosition[2]))) { existingState.hasNewPositionData = true; } existingPartInfo.mWorldPosition = newWorldPositions; // TODO: LastSectorTime is now in the UDP data, but there's no slot for this in the participants struct // existingPartInfo.mLastSectorTime = newPartInfo.sLastSectorTime; } else { existingPartInfo.mWorldPosition = new float[] { 0, 0, 0 }; } existingState.mParticipantData[i] = existingPartInfo; } // TODO: buttons return(existingState); }