Jump to content

kOS Rover Control!


MAFman

Recommended Posts

Recently, I got hooked on the kOS mod, which implements coding in KSP. I'm currently developing methods of rover control, both manual and automatic. I'd appreciate any and all ideas / suggestions / whatever!

Link to comment
Share on other sites

Im on my phone right now so forgive the small response..

Id love a trailing car alerter system for a land train that activates an engine functioning as an alarm speaker for when the last wagon starts to track badly and bounce around..

Thus not having to switch to external view to check the train status

Also the same script triggering the rotary beacons at 8 ms and the siren at any speed below 5 ms..similar to a railway bell for when in KSC depot

And if we can add a 25 ms auto speed governer that shuts off wheel power at anything above that.. Id be very happy :)

 

2BcMnB0.jpg

Oh and a welcome message on bootup of the script that mentions the trains tag.. Eg LTV-102 etc

 

Too much? :D

 

Link to comment
Share on other sites

@Overland The last three would be pretty easy. The first one. . . I wonder if the Double-C Seismic Accelerometer reads differently on different parts of the train. That might be very doable... I'm going to play with it tonight, but I don't have KOS installed, and there's no internet at home.

 

I've tried to control tail-whip with various "cabooses", but no luck yet.

Link to comment
Share on other sites

Thanks :) I actually dont get tail whip half as much as I used to with steering drawbars im using now..the problem is with the wheel friction once it catches after say being accidently airborne from a drop in terrain

I did have a crude version using wheelsound mods skid sound and a tiny wheel used as a curb feeler.. Setting off an audible beep.. Sadly it was either too sencitive giving false readings or not enough and by the time it triggered..the wagon was throwing parts and exploding

I appreciate the effort too :) the forced move to rover based overland trains has been a bit of a blessing..

 

Would raster prop monitor provide a read out??

Link to comment
Share on other sites

I'm working on a rover control library that implements a generic PID library.

The thing I'm kind of stuck on is automatic navigation, either dynamic (paying attention to bearing to target and terrain slope), or manual (like Seth Persigehl's rover script).

Link to comment
Share on other sites

8 hours ago, Overland said:

...Would raster prop monitor provide a read out??

RPM was my first thought when you mentioned "having to switch to external view to check the train".  Two things with RPM would help - first and most simply, the hull camera so you could have in-cab CCTV back along the length of your vehicle.  Another mod, which is almost a must for me, is Vessel Viewer.  With that integrated with RPM you'll get a dynamic schematic view of the whole vehicle.

Edited by Pecan
Link to comment
Share on other sites

1 minute ago, Pecan said:

RPM was my first thought when you mentioned "having to switch to external view to check the train".  Two things with RPM would help - first and most simply, the hull camera so you could have in-cab CCTV back along the length of your vehicle.  Another mod, which is almost a must for me, is Vessel Viewer.  With that integrated with RPM you'll get a dynamic schematic view of the whole vehicle.

RasterPropMonitor does provide a readout if you have VesselView installed. It gives you a realtime schematic view of your ship.

Link to comment
Share on other sites

I just got done copying Seth Persigehl's rover.ks script to try to adapt it, but I'm stuck...How do I properly implement a PID for both wheel steering and wheel throttle, to maintain constant speed and heading?

 

Also, can someone explain how to use boot scripts to do things like displaying info with HUDTEXT()?

Link to comment
Share on other sites

11 hours ago, MAFman said:

Also, can someone explain how to use boot scripts to do things like displaying info with HUDTEXT()?

A boot script is just regular code. You should be able to call hudtext as normal.

Link to comment
Share on other sites

I've been having trouble with my roverDriver script again...My rover keeps driving in a left-handed circle probably 20m in radius... I got it to return no errors this time though! :D

Here's my code...

// Auto Nav
// Takes a LATLNG() of a goal, and iteratively navigates to it.
PARAMETER goal.

FUNCTION POPULATE
{
	SET pos TO LATLNG(SHIP:GEOPOSITION:LAT, SHIP:GEOPOSITION:LNG).
	SET a TO 3600/(2*CONSTANT:PI*BODY:RADIUS). // 10m in any cardinal direction
	SET b TO SIN(45)*a.
	
	SET oA TO LATLNG(pos:LAT + a, pos:LNG). // North
	SET oB TO LATLNG(pos:LAT + B, pos:LNG + b). // Northeast
	SET oC TO LATLNG(pos:LAT, pos:LNG + a). // East
	SET oD TO LATLNG(pos:LAT - b, pos:LNG + b). // Southeast
	SET oE TO LATLNG(pos:LAT - a, pos:LNG). // South
	SET oF TO LATLNG(pos:LAT - b, pos:LNG - b). // Southwest
	SET oG TO LATLNG(pos:LAT, pos:LNG - a). //West
	SET oH TO LATLNG(pos:LAT + b, pos:LNG - b). // Northwest
	
	SET options TO LIST(oA, oB, oC, oD, oE, oF, oG, oH).
	RETURN options.
}
FUNCTION SLOPE_IS_OK
{
	PARAMETER point.
	SET location TO LATLNG(point:LAT, point:LNG).
	SET slope TO ABS(ARCTAN2((location:TERRAINHEIGHT - SHIP:ALTITUDE), 10)).
	IF slope < 11.25
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION ANGLE_IS_OK
{
	PARAMETER point, goal.
	SET angle TO ABS(goal:BEARING - point:BEARING).
	IF angle < 45
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION DECIDE_NEXT
{
	PARAMETER options, goal.
	FOR o IN options
	{
		IF SLOPE_IS_OK(o) AND ANGLE_IS_OK(goal, o)
		{
			SET nextPoint TO LATLNG(o:LAT, o:LNG).
			RETURN nextPoint.
			BREAK.
		}
		ELSE
		{
			// Just keep swimming...
		}
	}
}
FUNCTION DRIVE
{
	PARAMETER waypt.
	SET spd TO 10. SET hdg TO waypt:HEADING.
	SET loopT TO 0.01. SET loopEndT TO TIME:SECONDS.
	SET pWT TO 0. SET iWT TO 0. SET dWT TO 0. SET lastPWT TO 0.
	SET wtVal TO 0.
	SET NORTHPOLE TO LATLNG(90,0).
	SET runmode TO 0.
	IF waypt:DISTANCE < 5
	{
		BRAKES ON. SET runmode TO -1.
	}
	UNTIL runmode = -1
	{
		IF NORTHPOLE:BEARING <= 0
		{
			set cHdg TO ABS(NORTHPOLE:BEARING).
		}
		ELSE
		{
			SET cHdg TO (180 - NORTHPOLE:BEARING) + 180.
		}
		IF runmode = 0
		{
			SET pWT TO spd - GROUNDSPEED.
			SET iWT TO MIN(1, MAX(-1, iWT + (pWT * loopT))).
			SET dWT TO ((pWT - lastPWT) / (loopEndT - loopT)).
			SET wtVal TO MIN(1, MAX(-1, pWT + iWT + dWT)).
			
			SET SHIP:CONTROL:WHEELTHROTTLE TO wtVal.
			SET SHIP:CONTROL:WHEELSTEER TO hdg.
			SET loopT TO TIME:SECONDS - loopEndT.
			SET loopEndT TO TIME:SECONDS.
		}
	}
}
UNTIL goal:DISTANCE < 5
{
	POPULATE().
	DECIDE_NEXT(options, goal).
	DRIVE(nextPoint).
	BRAKES ON. WAIT 5. BRAKES OFF.
}
BRAKES ON.

 

Link to comment
Share on other sites

Ok, fixed the driving-in-circles thing. Now it just drives backward and doesn't control the steering or throttle at all! I think the decision-making algorithm works, though...

// Auto Nav
// Takes a LATLNG() of a goal, and iteratively navigates to it.
PARAMETER goal.
GEAR OFF. LOCK THROTTLE TO 0. SAS OFF. RCS OFF. BRAKES ON.
FUNCTION POPULATE
{
	SET pos TO LATLNG(SHIP:GEOPOSITION:LAT, SHIP:GEOPOSITION:LNG).
	SET a TO (360 * 1000)/(2*CONSTANT:PI*BODY:RADIUS). // 10m in any cardinal direction
	SET b TO SIN(45)*a.
	
	SET oA TO LATLNG((pos:LAT + a), (pos:LNG)). // North
	SET oB TO LATLNG((pos:LAT + b), (pos:LNG + b)). // Northeast
	SET oC TO LATLNG((pos:LAT), (pos:LNG + a)). // East
	SET oD TO LATLNG((pos:LAT - b), (pos:LNG + b)). // Southeast
	SET oE TO LATLNG((pos:LAT - a), (pos:LNG)). // South
	SET oF TO LATLNG((pos:LAT - b), (pos:LNG - b)). // Southwest
	SET oG TO LATLNG((pos:LAT), (pos:LNG - a)). //West
	SET oH TO LATLNG((pos:LAT + b), (pos:LNG - b)). // Northwest
	
	SET options TO LIST(oA, oB, oC, oD, oE, oF, oG, oH).
	RETURN options.
}
FUNCTION SLOPE_IS_OK
{
	PARAMETER point.
	SET location TO LATLNG(point:LAT, point:LNG).
	SET slope TO ABS(ARCTAN2((location:TERRAINHEIGHT - SHIP:ALTITUDE), 10)).
	IF slope < 11.25
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION ANGLE_IS_OK
{
	PARAMETER point, goal.
	SET angle TO ABS(goal:BEARING - point:BEARING).
	IF angle < 45
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION DECIDE_NEXT
{
	PARAMETER options, goal.
	FOR o IN options
	{
		IF SLOPE_IS_OK(o) AND ANGLE_IS_OK(goal, o)
		{
			SET nextPoint TO LATLNG(o:LAT, o:LNG).
			RETURN nextPoint.
			BREAK.
		}
		ELSE
		{
			// Just keep swimming...
		}
	}
}
FUNCTION DRIVE
{
	PARAMETER waypt.
	
	SET spd TO 5.
	SET loopT TO 0.01. SET loopEndT TO TIME:SECONDS.
	SET eWT TO 0. SET iWT TO 0. SET wtVAL TO 0.
	SET NORTHPOLE TO LATLNG(90,0).
	SET eSteer TO 0.
	LOCK turnLimit TO MIN(1, 1.5/GROUNDSPEED).
	
	UNTIL waypt:DISTANCE < 2.5
	{
		IF NORTHPOLE:BEARING <= 0
		{
			SET cHead TO ABS(NORTHPOLE:BEARING).
		}
		
		ELSE
		{
			SET cHead TO (180 - NORTHPOLE:BEARING) + 180.
		}
		
		SET wtVAL TO MIN(1, MAX(-1, (-eWT + iWT))).
		SET eSteer TO (hdg - cHead).
		
		IF eSteer > 180
			{
				//Make sure the headings make sense
				SET eSteer TO eSteer - 360.
			}
		ELSE IF eSteer < -180
		{
			SET eSteer TO eSteer + 360.
		}
		
		IF spd - GROUNDSPEED > spd + spd * 0.1 // We're too fast!
		{
			LOCK THROTTLE TO -1.
		}
		ELSE IF GROUNDSPEED < spd - spd * 0.1
		{
			LOCK THROTTLE TO 1.
		}
		
		SET desiredSteering TO -eSteer / 10.
		set kturn to min(1, max( -1, desiredSteering)).
			
		
		SET SHIP:CONTROL:WHEELSTEER TO kTurn.
		
		PRINT("Distance to waypoint: " + ROUND(waypt:DISTANCE, 2)) AT (2, 21).
		PRINT("Wheel Throttle: " + wtVAL) AT (2,22).
		
		SET loopT TO TIME:SECONDS - loopEndT.
		SET loopEndT TO TIME:SECONDS.
	}
}
UNTIL goal:DISTANCE < 50
{
	IF SHIP:SENSORS:LIGHT < 0.75
	{
		LIGHTS ON.
	}
	ELSE
	{
		LIGHTS OFF.
	}
	CLEARSCREEN.
	POPULATE().
	PRINT("=====Autonavigation v1.0=====") AT (0,2).
	PRINT("Populating options...") AT (2,5).
	
	WAIT 1.
	
	PRINT("Options: " + ROUND(oA:LAT, 5) + ", " + ROUND(oA:LNG, 2)) AT (2,10).
	PRINT(ROUND(oB:LAT, 5) + ", " + ROUND(oB:LNG, 2)) AT (11,11).
	PRINT(ROUND(oC:LAT, 5) + ", " + ROUND(oC:LNG, 2)) AT (11,12).
	PRINT(ROUND(oD:LAT, 5) + ", " + ROUND(oD:LNG, 2)) AT (11,13).
	PRINT(ROUND(oE:LAT, 5) + ", " + ROUND(oE:LNG, 2)) AT (11,14).
	PRINT(ROUND(oF:LAT, 5) + ", " + ROUND(oF:LNG, 2)) AT (11,15).
	PRINT(ROUND(oG:LAT, 5) + ", " + ROUND(oG:LNG, 2)) AT (11,16).
	PRINT(ROUND(oH:LAT, 5) + ", " + ROUND(oH:LNG, 2)) AT (11,17).
	
	WAIT 1.
	DECIDE_NEXT(options, goal).
	PRINT("Driving to " + "(" + ROUND(nextPoint:LAT,5) + ", " + ROUND(nextPoint:LNG, 5) + ")") AT (2,20).
	BRAKES OFF.
	IF nextPoint:BEARING > 20
	{
		LOCK WHEELSTEER TO nextPoint. LOCK WHEELTHROTTLE TO -0.5.
		WAIT UNTIL nextPoint:BEARING < 30.
	}
	DRIVE(nextPoint).
	BRAKES ON. WAIT 5. BRAKES OFF.
}
BRAKES ON.

 

Link to comment
Share on other sites

UPDATE: Found the bug!

// Auto Nav
// Takes a LATLNG() of a goal, and iteratively navigates to it.
PARAMETER goal.

FUNCTION POPULATE
{
	SET pos TO LATLNG(SHIP:GEOPOSITION:LAT, SHIP:GEOPOSITION:LNG).
	SET a TO 360000/(2*CONSTANT:PI*BODY:RADIUS). // 10m in any cardinal direction
	SET b TO SIN(45)*a.
	
	SET oA TO LATLNG(pos:LAT + a, pos:LNG). // North
	SET oB TO LATLNG(pos:LAT + B, pos:LNG + b). // Northeast
	SET oC TO LATLNG(pos:LAT, pos:LNG + a). // East
	SET oD TO LATLNG(pos:LAT - b, pos:LNG + b). // Southeast
	SET oE TO LATLNG(pos:LAT - a, pos:LNG). // South
	SET oF TO LATLNG(pos:LAT - b, pos:LNG - b). // Southwest
	SET oG TO LATLNG(pos:LAT, pos:LNG - a). //West
	SET oH TO LATLNG(pos:LAT + b, pos:LNG - b). // Northwest
	
	SET options TO LIST(oA, oB, oC, oD, oE, oF, oG, oH).
	RETURN options.
}
FUNCTION SLOPE_IS_OK
{
	PARAMETER point.
	SET location TO LATLNG(point:LAT, point:LNG).
	SET slope TO ABS(ARCTAN2((location:TERRAINHEIGHT - SHIP:ALTITUDE), 10)).
	IF slope < 11.25
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION ANGLE_IS_OK
{
	PARAMETER point, goal.
	SET angle TO ABS(goal:BEARING - point:BEARING).
	IF angle < 45
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}
FUNCTION DECIDE_NEXT
{
	PARAMETER options, goal.
	FOR o IN options
	{
		IF SLOPE_IS_OK(o) AND ANGLE_IS_OK(goal, o)
		{
			SET nextPoint TO LATLNG(o:LAT, o:LNG).
			RETURN nextPoint.
			BREAK.
		}
		ELSE
		{
			// Just keep swimming...
		}
	}
}
FUNCTION DRIVE
{
	PARAMETER waypt.
	CLEARSCREEN.
	SET spd TO 10. SET hdg TO waypt:HEADING.
	SET loopT TO 0.01. SET loopEndT TO TIME:SECONDS.
	SET pWT TO 0. SET iWT TO 0. SET dWT TO 0. SET lastPWT TO 0.
	SET eSteer TO 0. SET dSteer TO 0. SET lastESteer TO 0.
	SET wtVal TO 0.
	SET NORTHPOLE TO LATLNG(90,0).
	SET runmode TO 0.
	IF waypt:DISTANCE < 5
	{
		BRAKES ON. SET runmode TO -1.
	}
	UNTIL runmode = -1
	{
		IF NORTHPOLE:BEARING <= 0
		{
			set cHdg TO ABS(NORTHPOLE:BEARING).
		}
		ELSE
		{
			SET cHdg TO (180 - NORTHPOLE:BEARING) + 180.
		}
		
		IF runmode = 0
		{
			SET pWT TO spd - GROUNDSPEED.
			SET iWT TO MIN(1, MAX(-1, iWT + (pWT * loopT))).
			SET dWT TO ((pWT - lastPWT) / (loopEndT - loopT)).
			SET wtVal TO MIN(1, MAX(-1, pWT + iWT + dWT)).
			
			SET eSteer TO (hdg - cHdg) / 180.
			SET dSteer TO ((eSteer - lastESteer)/(loopEndT - loopT)).
			SET steerVal TO -eSteer + dSteer.
			
			SET SHIP:CONTROL:WHEELTHROTTLE TO wtVal.
			SET SHIP:CONTROL:WHEELSTEER TO steerVal.
			
			SET lastPWT TO pWT.
			SET lastESteer TO eSteer.
			SET loopT TO TIME:SECONDS - loopEndT.
			SET loopEndT TO TIME:SECONDS.
		}
		PRINT("pWT: " + pWT) AT (2, 5).
		PRINT("iWT: " + iWT) AT (2, 6).
		PRINT("dWT: " + dWT) AT (2,7).
		PRINT("eSteer: " + eSteer) AT (2, 9).
		PRINT("steerVal: " + steerVal) AT (2,10).
	}
}
UNTIL goal:DISTANCE < 5
{
	POPULATE().
	DECIDE_NEXT(options, goal).
	DRIVE(nextPoint).
	BRAKES ON. WAIT 5. BRAKES OFF.
}
BRAKES ON.

 

Link to comment
Share on other sites

Gah! I don't know what I did, but now it crashes KSP... time to start over...

 

Can someone help me develop this?

 

I need a kOS program that takes a LATLNG and iteratively navigates to it.

Steps of the iteration:

Spoiler

1: List possible options 100m away.

2: Iterate through the list and pick the first option that meets the following criteria:

2a. Slope is less than 15°.

2b. Angle between goal and option is less than 45°.

3. If the second condition cannot be met, meet only the first condition.

4. Drive at 5 m/s toward the waypoint, stopping upon arrival (distance < 5m).

5. Upon reaching the goal, halt and exit.

 

Link to comment
Share on other sites

  • 2 weeks later...

I have something that looks promising, but it refuses to run... Can someone tell me why?

PARAMETER hdg. // 0 -> 359; 0 = north, 90 = east, etc.

FUNCTION NOTIFY
{
	PARAMETER message.
	HUDTEXT("kOS: " + message, 5, 2, 50, GREEN, FALSE).
}

FUNCTION SET_GOAL
{
	PARAMETER theta.
	SET pos TO SHIP:GEOPOSITION.
	SET h TO pos:TERRAINHEIGHT.
	SET r TO BODY:RADIUS.
	
	SET d TO (2 * CONSTANT:PI * BODY:RADIUS) * ARCSIN(SQRT((R+h)^2-R^2)/((R+h)/360)). // Radio horizon equation
	SET delta TO d * 0.95. // 5% safety margin
	
	SET goal TO LATLNG	(	pos:LAT + (delta * SIN(theta)),
							pos:LNG + (delta * COS(theta))
						).
	RETURN goal.
}

FUNCTION ARRIVED
{
	PARAMETER location, setpoint.
	
	SET here TO LATLNG(location:LAT, location:LNG).
	SET there TO LATLNG(setpoint:LAT, setpoint:LNG).
	
	SET dist TO BODY:RADIUS * ARCCOS((SIN(here:LNG) * SIN(there:LNG)) + (COS(here:LNG) * COS(there:LNG) * COS(ABS(here:LAT - there:LAT)))).
	
	IF dist < 10
	{
		SET arrived TO TRUE.
	}
	ELSE
	{
		SET arrived TO FALSE.
	}
	RETURN arrived.
}

FUNCTION POPULATE
{
	SET loc TO LATLNG(SHIP:GEOPOSITION).
	SET off_90 TO 36000/(2*CONSTANT:PI*BODY:RADIUS).
	SET off_675 TO off_90 * SIN(67.5).
	SET off_45 TO off_90 * SIN(45).
	SET off_225 TO off_90 * SIN(22.5).
	
	SET a TO LATLNG(loc:LAT + off_90, loc:LNG).				// N
	SET b TO LATLNG(loc:LAT + off_675, loc:LNG + off_225). 	// NNE
	SET c TO LATLNG(loc:LAT + off_45, loc:LNG + off_45). 	// NE
	SET d TO LATLNG(loc:LAT + off_225, loc:LNG + off_675).	// ENE
	SET e TO LATLNG(loc:LAT, LOC:LNG + off_90).				// E
	SET f TO LATLNG(loc:LAT - off_225, loc:LNG + off_675).	// ESE
	SET g TO LATLNG(loc:LAT - off_45, loc:LNG + off_45).	// SE
	SET h TO LATLNG(loc:LAT - off_675, loc:LNG + off_225).	// SSE
	SET i TO LATLNG(loc:LAT - off_90, loc:LNG).				// S
	SET j TO LATLNG(loc:LAT - off_675, loc:LNG - off_225).	// SSW
	SET k TO LATLNG(loc:LAT - off_45, loc:LNG - off_45).	// SW
	SET l TO LATLNG(loc:LAT - off_225, loc:LNG - off_675).	// WSW
	SET m TO LATLNG(loc:LAT, loc:LNG - off_90).				// W
	SET n TO LATLNG(loc:LAT + off_225, loc:LNG - off_675).	// WNW
	SET o TO LATLNG(loc:LAT + off_45, loc:LNG - off_45).	// NW
	SET p TO LATLNG(loc:LAT + off_675, loc:LNG - off_225).	// NNW
	
	SET options TO LIST(a, b, c, d,
						e, f, g, h,
						i, j, k, l,
						m, n, o, p).
	RETURN options.
}

FUNCTION SLOPE_IS_SAFE
{
	PARAMETER point.
	SET slope TO ABS(ARCTAN2(point:TERRAINHEIGHT - SHIP:ALTITUDE, 100)).
	IF slope < 15
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}

FUNCTION ANGLE_IS_REASONABLE
{
	PARAMETER point, goal.
	SET angle TO ABS(goal:BEARING - point:BEARING).
	IF angle < 60 
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}

FUNCTION DECIDE
{
	PARAMETER options, goal.
	FOR o IN options
	{
		IF SLOPE_IS_SAFE(o)
		{
			IF ANGLE_IS_REASONABLE(o, goal)
			{
				SET nextpoint TO LATLNG(o:LAT, o:LNG).
				RETURN nextpoint.
			}
		}
	}
}

FUNCTION DRIVE_TO
{
	PARAMETER point.
	
	LOCK turnlimit TO MIN(1, 1.5 / GROUNDSPEED).	// Scale the 
													// turning radius
													// based on current speed
	SET loopTime TO 0.01.
	SET loopEndTime TO TIME:SECONDS.
	SET eWheelThrottle TO 0. // Error between target speed and actual speed
	SET iWheelThrottle TO 0. // Accumulated speed error
	SET wtVAL TO 0. //Wheel Throttle Value
	SET eSteer TO 0. // Steering error
	SET kTurn TO 0. //Wheel turn value.
	SET targetspeed TO 0. //Cruise control starting speed
	SET targetHeading TO 90. //Used for autopilot steering
	SET NORTHPOLE TO LATLNG( 90, 0). //Reference heading
	
	CLEARSCREEN. PRINT("Activate action group 10 to begin driving.").
	ON AG10
	{
		SET runmode TO 0.
	}
	UNTIL runmode = -1
	{
		IF SHIP:SENSORS:LIGHT < 0.75
		{
			LIGHTS ON.
		}
		ELSE
		{
			LIGHTS OFF.
		}
		IF runmode = 0
		{
			//Update the compass:
			// I want the heading to match the navball 
			// and be out of 360' instead of +/-180'
			// I do this by judging the heading relative
			// to a latlng set to the north pole
			IF NORTHPOLE:BEARING <= 0
			{
				SET cHeading TO ABS(NORTHPOLE:BEARING).
			}
			ELSE
			{
				SET cHeading TO (180 - NORTHPOLE:BEARING) + 180.
			}
			
			IF targetHeading > 360
			{
				SET targetHeading TO targetHeading - 360.
			}
			ELSE IF targetHeading < 0
			{
				SET targetHeading TO targetHeading + 360.
			}
		
			BRAKES OFF.
			SET eWT TO targetSpeed - GROUNDSPEED.
			SET iWT TO MIN(1, MAX(-1, iWheelThrottle + (eWheelThrottle * loopTime))).
			SET wtVAL TO MIN(1, MAX(-1, eWT + iWT)).
			
			SET eSteer TO targetHeading - cHeading.
			IF eSteer > 180
			{
				SET eSteer TO eSteer - 360.
			}
			SET desiredSteering TO -eSteer / 10.
			SET kTurn TO MIN(1, MAX(-1, desiredSteering)) * turnLimit.
		}
		SET SHIP:CONTROL:WHEELTHROTTLE TO wtVAL.
		SET SHIP:CONTROL:WHEELSTEER TO kTurn.
		SET loopTime TO TIME:SECONDS - loopEndTime.
		SET loopEndTime TO TIME:SECONDS.
		
		IF ARRIVED(SHIP:GEOPOSITION, point:GEOPOSITION)
		{
			SET runmode TO -1.
		}
	}
	LOCK WHEELSTEER TO 0. LOCK WHEELTHROTTLE TO -1.
	WAIT UNTIL GROUNDSPEED < 1.
	UNLOCK ALL. BRAKES ON. NOTIFY("Arrived at waypoint. Setting next.").
}

SET_GOAL(hdg).
UNTIL ARRIVED(SHIP:GEOPOSITION, goal)
{
	POPULATE().
	DECIDE(options, goal).
	DRIVE_TO(nextPoint).
}
LOCK WHEELSTEER TO 0. LOCK WHEELTHROTTLE TO -1.
WAIT UNTIL GROUNDSPEED < 1.
UNLOCK ALL. BRAKES ON. NOTIFY("Arrived at goal! Shutting down.").
FOR ants IN SHIP:PARTSNAMED("rtLongAntenna2")
{
	ants:GETMODULE("ModuleAnimateGeneric"):DOEVENT("Extend").
}
LIGHTS ON. SHUTDOWN.

 

Link to comment
Share on other sites

  • 2 weeks later...

 

There's a bug in kOS for one thing; geopositions can't be passed as parameters, so this:

FUNCTION SLOPE_IS_SAFE
{
	PARAMETER point.
	SET slope TO ABS(ARCTAN2(point:TERRAINHEIGHT - SHIP:ALTITUDE, 100)).
	IF slope < 15
	{
		RETURN TRUE.
	}
	ELSE
	{
		RETURN FALSE.
	}
}

 

results in an error. There appears to be something wrong with arctan2() as well, so that isn't going to do what you think it will even if it did parse.

Link to comment
Share on other sites

2 hours ago, surge said:

geopositions can't be passed as parameters


Are you sure?  I've been doing exactly that in my own scripts and it has worked.
 

Quote

 

results in an error. There appears to be something wrong with arctan2() as well, so that isn't going to do what you think it will even if it did parse.


 

This isn't very descriptive.  If it's a bug, can you describe its behaviour more clearly (what's the error you see?  What is arctan2 doing that is different than what you'd think?), since it's not a bug we've seen, and we can't fix what we can't see happening.

Link to comment
Share on other sites

Nevermind. I "pasted" (manually typed, hah!) bits of that code into kOS and it was sh.itting itself on the geoposition parameters. I've successfully done it multiple times since, so chalk that one up to exhaustion from playing too much KSP, I guess.

As for the arctan2 thing, that was, sadly several different web searches returning the same incorrect equation for something not relevant here. As long as you look out for radians/degree thing, it seems to also be ok.

I need sleep.

 

As for the topic, in the meantime, I made this (maybe OP can use it):

parameter thdg.
//
// V1a0-k completely AUTONOMOUS rover guidance.
// It'll just run around trying to find new biomes.
// Who knows where it'll end up?!
//
set debug to false.
set sciencethreshold to 0.1. // if science taken < this, reset.
set cruisespeed to 20. // standard cruise speed.
set maxslope to 30. // max allowed terrain difference.
set brakethreshold to 2. // speed err to apply brakes
set powermin to 0.4.
set powerhyst to 0.2.

// TODO:

// if speed is low, turn on motors in order: front, back, middle.

// if power is low, turn off motors in order: middle, back, front.
// if all motors off, brakes,
// wait until recharge.
// reset guidance.

// 1st level guidance: pick random direction,
// drive at speed.
// until new biome {
// if "Atmospheric Fluid Spectro-Variometer"s are full
// brake. wait for recovery.
// else pick random direction from -90..90
// }
// so it tries to traverse as many as possible.

// hazardous terrain: if unable to go forward, reverse 100m. reset
// 1st level guidance.


set ABORT to false.
on ABORT {
    BRAKES on.
    print "Aborting.".
    set SHIP:CONTROL:NEUTRALIZE to true.
}

function printv {
    // prints a vector with rounding so it fits on a line
    parameter v, line.
    
    print "V(" +round(v:x,4) +", " +round(v:y, 4)
        +", " +round(v:z, 4) +")   " at (0,line).
}

//
// step 1: detect new biome and get science.
//

local biomescanners to SHIP:PARTSNAMED("SurfaceScanner").
local biomemodule to biomescanners[0]:GETMODULE("ModuleGPS").
lock BIOME to biomemodule:GETFIELD("biome").
if not (defined knownbiomes)
    // Add previously known biomes here before boot.
    set knownbiomes to LIST().
function isnewbiome {
    local haveb to "Error".
    for tb in knownbiomes {
        if tb = BIOME {
            set haveb to false.
            break.
        }
    }
    if haveb <> false return biome.
    return false.
}

set lastbiome to "Null".
function isnewbiome2 {
    if BIOME <> lastbiome {
        set lastbiome to BIOME.
        return true.
    }
}

function checksensors {
    // checks the sensors are ok and have room for more
    local sl to SHIP:PARTSNAMED("sensorAtmosphere").
    for ex in sl {
        local m to ex:GETMODULE("ModuleScienceExperiment").
        if not m:hasdata return true.
    }
    return false.
}

function takereading {
    local sl to SHIP:PARTSNAMED("sensorAtmosphere").
    local aquired to false.
    for ex in sl {
        local m to ex:GETMODULE("ModuleScienceExperiment").
        if not m:hasdata {
            m:DEPLOY().
            wait until m:HASDATA.
            knownbiomes:add(biome).
            set sc to m:DATA[0].
            print sc:TITLE.
            set aquired to true.
            if (sc:SCIENCEVALUE < sciencethreshold) {
                print "    Science value " +
                    round(sc:SCIENCEVALUE, 1)
                    +" too low, resetting.".
                m:RESET().
            }
            break.
        }
    }
    return aquired.
}

//
// step 2: drive in a given direction.
//
set odometer to 0.
set north to UP *R(90,-90,0).
set northpole to  latlng(90, 0).
lock SHIPHDG to mod(360 -northpole:bearing, 360).
lock SHIPSPEED to SHIP:VELOCITY:SURFACE:MAG.

set east to UP *R(0, -90, 0).
function strack {
    local r1 to vcrs(up:vector,
        SHIP:VELOCITY:SURFACE):normalized.
    local h to vang(r1, east:vector).
    if (r1:y) > 0.0 set h to 360 -h.
    return h.
}

function hdgerr {
    parameter t, h.
    return mod((t-h)+540, 360)-180.
}


set bl to " ".
set space to " ".
set i to 0.
until i < 48 {
    set bl to bl +space.
    set i to i+1.
}
clearscreen.
print "+-----------------------------------------------+".
print "| ALL AUTONOMOUS ROVER GUIDANCE SYSTEM (AARGS!) |".
print "|                                               |".
print "|  Target:               Heading:               |".
print "|  Steer:                Error:                 |".
print "|  Speed:                Error:                 |".
print "|  Odometer:                                    |".
print "|                                               |".
print "|                        Biome:                 |".
print "|                                               |".
print "+-----------------------------------------------+".
print "Log:                                             ".
function printscreen {
    print "Log:                                             " at (0,11).
    print "Biome: " +biome +"  "at (25,8).
    print "Target: " +round(thdg, 1) +"o    " at (3, 3).
    print "Heading: " +round(SHIPHDG,1) +"o     " at (25, 3).
    print "Steer: " +round(-SHIP:CONTROL:WHEELSTEER,2)
        +"    " at (3, 4).
    print "Error: " +round(hdgerr(thdg),1) +"o     " at (25, 4).
    print "Speed: " +round(SHIPSPEED, 2) +"m/s  " at (3, 5).
//    print "Error: " +round(0, 2) +"m/s  " at (25, 5).
    print "Odometer: " +round(odometer/1000,1) +"km     "
        at (3, 6).
    print "Throttle: " +round(SHIP:CONTROL:WHEELTHROTTLE,2)
        +"    " at (25, 6).
}


set odopos to SHIP:GEOPOSITION.
function doodometer {
    local odoseg to odopos:DISTANCE.
    if odoseg > 10 {
        set odometer to odometer +odopos:DISTANCE.
        set odopos to SHIP:GEOPOSITION.
    }
}

function getterrainat {
    parameter dist, bear.
    // gets geoposition from a point away from ship.
    local slat to SHIP:GEOPOSITION:LAT.
    local slon to SHIP:GEOPOSITION:LNG.
    // not sure what the *100 is for...
    local dr to dist*100/BODY:RADIUS.
    local tlat to arcsin(sin(slat) *cos(dr)
        +cos(slat) *sin(dr) *cos(bear)).
    local tlon to slon +arctan2(sin(bear)
        *sin(dr) *cos(slat),
        cos(dr) -sin(slat) *sin(tlat)).
    set vd to vecdraw(LATLNG(tlat,tlon):position, up:vector *5,
        red, "getterrainat()", 1, debug).
    return LATLNG(tlat,tlon).
}

function lookahead {
// test various points ahead for GEOPOSITION:TERRAINHEIGHT.
// check for ridiculous slope, and water (<0)
    local th to SHIPHDG -10. // test headings
    until th > (SHIPHDG +10) {
        local h to getterrainat(50, th):TERRAINHEIGHT.
        print "Lookahead: " +round(h -SHIP:ALTITUDE)
            +"m   " at (3,8).
        if (h < 0.5) {
            print "Water.".
            return th.
        }
        if (h < SHIP:ALTITUDE-maxslope
        or h > SHIP:ALTITUDE +maxslope) {
            print "Bad terrain.".
            return th.
        }
        set th to th +10.
    }
    return false.
}
function isblocked {
    if lookahead() return true.
    // check if any wheels are blocked.
    local wl to SHIP:PARTSNAMED("roverWheel1").
    for w in wl {
        if w:GETMODULE("ModuleWheelBase")
        :GETFIELD("wheel blocked") = "Yes"
            return true.
    }
// FIXME: Also try to check if we are not moving forward,
// and should be.
    return false.
}


function parkbrake {
    set SHIP:CONTROL:NEUTRALIZE to true.
    BRAKES on.
    if not ABORT wait until SHIP:VELOCITY:SURFACE:MAG < 1.
}

if not (defined obstacles) set obstacles to QUEUE().
function pushobstacle {
    parameter p.
    obstacles:push(p).
    print "Obstacle at "
        +round(p:LAT, 2) +", " +round(p:LNG,2).
    if (obstacles:LENGTH > 10) {
        set op to obstacles:pop.
        print "Forgot obstacle "
            +round(op:LAT, 2) +", " +round(op:LNG,2).

    }
}

set steerpid to PIDLOOP(0.008, 0.0005, 0.01, -1, 1). // fast speed
set steerpid:setpoint to 0.
set accelpid to PIDLOOP(0.05, 0.005, 0.01, 0, 1). // TODO
set reversepid to PIDLOOP(0.1, 0.005, 0.02, -1, 0). //TODO
set avoidspeed to 5.
function avoid {
    parameter dist. // distance to back up.
    // log current point
    local badpoint to SHIP:GEOPOSITION.
    pushobstacle(badpoint).

    print "Avoiding...".
    parkbrake().

    // try to reverse dist.
    BRAKES off.
    set SHIP:CONTROL:WHEELSTEER to RANDOM()/2 -0.5.
    until ABORT or (badpoint:DISTANCE > dist) {
        // check behind us!
        if (getterrainat(5, SHIPHDG -180):TERRAINHEIGHT < 0)
            return false.

        if (badpoint:DISTANCE > 10)
            set SHIP:CONTROL:WHEELSTEER to 0.
        set SHIP:CONTROL:WHEELTHROTTLE to
            reversepid:UPDATE(TIME:SECONDS*10,
            avoidspeed -SHIPSPEED).
        printscreen().
        wait 0.1.
    }
    parkbrake().
    return true.
}

function power {
    set rl to stage:resources.
    for r in rl {
        if r:name = "ElectricCharge" {
            return r.
        }
    }
    return nil.
}

function waitforpower {
    print "Power loss.".
    parkbrake().
    LIGHTS off.
    wait until ABORT
    or power():amount >= power():capacity*powermin.
    print "Power back.".
    LIGHTS on.
    BRAKES off.
}

function cruise {
    parameter step.
    set SHIP:CONTROL:WHEELSTEER to
        steerpid:UPDATE(TIME:SECONDS/step, hdgerr(thdg)).
    set dS to SHIPSPEED -cruisespeed.
    if dS > brakethreshold {
        set SHIP:CONTROL:WHEELTHROTTLE to
            accelpid:UPDATE(TIME:SECONDS/step, dS).
        BRAKES on. wait 0.1.
    } else {
        BRAKES off.
        if power():amount <  power():capacity *powerhyst
            waitforpower().
        set SHIP:CONTROL:WHEELTHROTTLE to
            accelpid:UPDATE(TIME:SECONDS/step,
            dS).
    }
    doodometer().
}

if defined debug {
    on AG10 {
        set thdg to thdg +5.
        preserve.
    }
    on AG9 {
        set thdg to thdg -5.
        preserve.
    }
}


function dosegment {
    parameter length. // how far to run in this segment.
    set startodo to odometer.
    LIGHTS on.
    BRAKES off. wait 0.1.
    set resolution to 0.1.
    until ABORT or (odometer -startodo > length) {
        if (isblocked()) {
            if avoid(100) break.
            else set ABORT to true.
        }

        
        cruise(resolution).

        if not checksensors() {
            print "Sensors damaged or full.".
            set ABORT to true.
            break.
        }
        set newbiome to isnewbiome2().
        if newbiome {
            if not takereading() {
                print "Sensors full.".
                break.
            }
        }

        printscreen().
        wait resolution.
    }
}


function isobstacle {
    parameter hdg.
    // checks if the heading is in obstacles
    
    set oerr to 15.
    for o in obstacles {
        if o:bearing > (hdg -oerr)
        and o:bearing < (hdg +oerr)
            return true.
    }
    return false.
}

until ABORT {
    print "New segment, heading " +round(thdg) +"o.".
    dosegment(10000).
    parkbrake().

    set thdg to thdg -45 +RANDOM() *90.
    until not isobstacle(thdg) set thdg to RANDOM() *360.
    wait 5.
}
LIGHTS off.
parkbrake().

// step 3: drive in a given direction and take readings.

// step 4: drive in a given direction for a specified distance
// and take readings.

// step 5: drive in random direction for random distance and take
// readings.
// dont forget about the obstacles queue!

unlock BIOME.
unlock SHIPHDG.
BRAKES on. wait 0.1.

Edited by surge
Link to comment
Share on other sites

Sadly that is fiddly as hell, and there is no real answer. I have since changed the steerpid and am still fiddling with it:

set steerpid to PIDLOOP(0.008, 0.0004, 0.02, -1, 1). // fast speed

But the kOS docs are broken and wrong. The wiki page they used to point to is also useless in practice. The best way to tune them I've seen comes from super fancy R/C servo manuals:

1) set all values to 0.

2) slowly raise P until positive input is corrected by negative output and vice versa.

3) slowly raise D until you get a stable oscillation. Then halve it.

4) slowly raise I until it doesn't overcorrect. Then halve it too.

The problem is firstly doing that takes phenominal amounts of time, and secondly PID controllers only really work for a specific range of inputs. If youre trying to steer a jet powered landspeed record Thrust-SSC replica, you'll need completely different values than for a cute little rover put-putting around at 10km/hr.

I don't like it either, but thems the facts. I have in the past used variations of sin/cos() functions that essentially do the same thing, but don't account for long term errors. If you're desperate, learn some maths and try that. It's probably quicker and easier than CENSORED with pid controllers in the long run.

 

Edited by surge
censorship can go eat a dick
Link to comment
Share on other sites

  • 4 weeks later...
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...