diff --git a/SourceCode/GPS/Classes/CAngleSensor.cs b/SourceCode/GPS/Classes/CAngleSensor.cs new file mode 100644 index 0000000..bd0037a --- /dev/null +++ b/SourceCode/GPS/Classes/CAngleSensor.cs @@ -0,0 +1,189 @@ +//Please, if you use this, share the improvements + +using System; +using System.Globalization; + +namespace OpenGrade +{ + /// + /// Handles angle sensor input and filtering for excavator arm and stick + /// Compensates for movement of arm and stick affecting GPS antenna reading + /// + public class CAngleSensor + { + // Angle sensor data + public double armAngle; // Angle of main boom arm (degrees) + public double stickAngle; // Angle of bucket stick (degrees) + + // Filtered angle values using moving average + private double[] armAngleBuffer; + private double[] stickAngleBuffer; + private int bufferIndex = 0; + private const int FILTER_BUFFER_SIZE = 10; + + // Raw sensor values + public double armAngleRaw; + public double stickAngleRaw; + + // Calibration offsets + public double armAngleOffset = 0.0; + public double stickAngleOffset = 0.0; + + // Sensor connection status + public bool isConnected = false; + public string lastError = ""; + + // Update timestamp + public DateTime lastUpdate; + + public CAngleSensor() + { + armAngleBuffer = new double[FILTER_BUFFER_SIZE]; + stickAngleBuffer = new double[FILTER_BUFFER_SIZE]; + + // Initialize buffers + for (int i = 0; i < FILTER_BUFFER_SIZE; i++) + { + armAngleBuffer[i] = 0.0; + stickAngleBuffer[i] = 0.0; + } + + lastUpdate = DateTime.Now; + } + + /// + /// Parse angle sensor data from serial input + /// Expected format: $ANGLE,armAngle,stickAngle*checksum + /// Example: $ANGLE,45.2,32.5*2F + /// + public bool ParseAngleData(string sentence) + { + try + { + if (string.IsNullOrEmpty(sentence) || !sentence.StartsWith("$ANGLE")) + return false; + + // Remove checksum + int checksumIndex = sentence.IndexOf('*'); + if (checksumIndex > 0) + sentence = sentence.Substring(0, checksumIndex); + + // Split by comma + string[] parts = sentence.Split(','); + if (parts.Length < 3) + { + lastError = "Invalid angle sensor format"; + return false; + } + + // Parse arm angle + if (!double.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out armAngleRaw)) + { + lastError = "Failed to parse arm angle"; + return false; + } + + // Parse stick angle + if (!double.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out stickAngleRaw)) + { + lastError = "Failed to parse stick angle"; + return false; + } + + // Apply calibration offsets + armAngle = armAngleRaw - armAngleOffset; + stickAngle = stickAngleRaw - stickAngleOffset; + + // Apply filtering + UpdateFilteredValues(); + + lastUpdate = DateTime.Now; + isConnected = true; + lastError = ""; + + return true; + } + catch (Exception e) + { + lastError = "Parse error: " + e.Message; + return false; + } + } + + /// + /// Update filtered angle values using moving average + /// + private void UpdateFilteredValues() + { + armAngleBuffer[bufferIndex] = armAngle; + stickAngleBuffer[bufferIndex] = stickAngle; + + bufferIndex++; + if (bufferIndex >= FILTER_BUFFER_SIZE) + bufferIndex = 0; + + // Calculate moving averages + double armSum = 0; + double stickSum = 0; + + for (int i = 0; i < FILTER_BUFFER_SIZE; i++) + { + armSum += armAngleBuffer[i]; + stickSum += stickAngleBuffer[i]; + } + + armAngle = armSum / FILTER_BUFFER_SIZE; + stickAngle = stickSum / FILTER_BUFFER_SIZE; + } + + /// + /// Calibrate angle sensors (call when arm/stick in known position) + /// + public void CalibrateAngles(double armKnownAngle, double stickKnownAngle) + { + armAngleOffset = armAngleRaw - armKnownAngle; + stickAngleOffset = stickAngleRaw - stickKnownAngle; + } + + /// + /// Normalize angles to 0-360 range + /// + public double NormalizeAngle(double angle) + { + angle = angle % 360.0; + if (angle < 0) + angle += 360.0; + return angle; + } + + /// + /// Convert degrees to radians + /// + public double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } + + /// + /// Convert radians to degrees + /// + public double RadiansToDegrees(double radians) + { + return radians * 180.0 / Math.PI; + } + + /// + /// Check connection timeout (30 seconds) + /// + public bool IsConnectionAlive() + { + TimeSpan elapsed = DateTime.Now - lastUpdate; + if (elapsed.TotalSeconds > 30) + { + isConnected = false; + return false; + } + return true; + } + } +} diff --git a/SourceCode/GPS/Classes/CArmKinematics.cs b/SourceCode/GPS/Classes/CArmKinematics.cs new file mode 100644 index 0000000..871e123 --- /dev/null +++ b/SourceCode/GPS/Classes/CArmKinematics.cs @@ -0,0 +1,179 @@ +//Please, if you use this, share the improvements + +using System; + +namespace OpenGrade +{ + /// + /// Calculates excavator arm kinematics to determine antenna position + /// relative to the excavator base, accounting for arm and stick angles + /// + public class CArmKinematics + { + // Excavator arm dimensions (in meters) - adjust for your specific machine + public double armLength = 5.0; // Main boom length + public double stickLength = 3.0; // Bucket stick length + + // Antenna offset from stick endpoint (in meters) + public double antennaOffsetX = 0.2; // Forward offset + public double antennaOffsetY = 0.1; // Vertical offset + public double antennaOffsetZ = 0.0; // Side offset + + // Base reference point (where arm connects to excavator body) + public double baseHeight = 2.0; // Height of arm pivot from ground + public double baseOffsetX = 0.5; // Forward offset from center + public double baseOffsetY = 0.0; // Side offset from center + + // Calculated antenna position relative to base + public vec3 antennaPosition; + + // Previous position for velocity calculation + private vec3 previousPosition; + private DateTime lastCalculation; + + public CArmKinematics() + { + antennaPosition = new vec3(0, 0, 0); + previousPosition = new vec3(0, 0, 0); + lastCalculation = DateTime.Now; + } + + /// + /// Calculate antenna position based on arm and stick angles + /// angles in degrees + /// returns vec3 with easting (X), northing (Y), altitude (Z) offsets from base + /// + public vec3 CalculateAntennaPosition(double armAngleDeg, double stickAngleDeg) + { + // Convert to radians + double armAngleRad = armAngleDeg * Math.PI / 180.0; + double stickAngleRad = stickAngleDeg * Math.PI / 180.0; + + // Calculate arm endpoint position (2D) + // Assuming 0 degrees = horizontal to the right, positive = upward + double armEndX = armLength * Math.Cos(armAngleRad); + double armEndY = armLength * Math.Sin(armAngleRad); + + // Calculate stick endpoint relative to arm endpoint + // Stick angle is relative to ground (not relative to arm) + double stickEndX = armEndX + stickLength * Math.Cos(stickAngleRad); + double stickEndY = armEndY + stickLength * Math.Sin(stickAngleRad); + + // Add antenna offset from stick endpoint + double antennaX = stickEndX + antennaOffsetX; + double antennaY = stickEndY + antennaOffsetY; + + // Add base offsets + double totalX = baseOffsetX + antennaX; + double totalY = baseHeight + antennaY; + double totalZ = baseOffsetY + antennaOffsetZ; + + // Store previous position + previousPosition = antennaPosition; + + // Update current position + antennaPosition = new vec3(totalX, totalY, totalZ); + lastCalculation = DateTime.Now; + + return antennaPosition; + } + + /// + /// Calculate antenna position with stick angle relative to arm + /// (alternative calculation method) + /// + public vec3 CalculateAntennaPositionRelative(double armAngleDeg, double stickAngleRelativeDeg) + { + double armAngleRad = armAngleDeg * Math.PI / 180.0; + double stickRelativeRad = stickAngleRelativeDeg * Math.PI / 180.0; + + // Arm endpoint + double armEndX = armLength * Math.Cos(armAngleRad); + double armEndY = armLength * Math.Sin(armAngleRad); + + // Stick angle relative to arm + double stickAbsoluteAngle = armAngleDeg + stickAngleRelativeDeg; + double stickAbsoluteRad = stickAbsoluteAngle * Math.PI / 180.0; + + // Stick endpoint + double stickEndX = armEndX + stickLength * Math.Cos(stickAbsoluteRad); + double stickEndY = armEndY + stickLength * Math.Sin(stickAbsoluteRad); + + // Add antenna offset + double antennaX = stickEndX + antennaOffsetX; + double antennaY = stickEndY + antennaOffsetY; + + previousPosition = antennaPosition; + antennaPosition = new vec3(baseOffsetX + antennaX, baseHeight + antennaY, baseOffsetY + antennaOffsetZ); + lastCalculation = DateTime.Now; + + return antennaPosition; + } + + /// + /// Get antenna velocity (change in position per second) + /// + public vec3 GetAntennaVelocity() + { + TimeSpan timeDelta = DateTime.Now - lastCalculation; + if (timeDelta.TotalSeconds < 0.001) // Avoid division by zero + return new vec3(0, 0, 0); + + double timeFactor = timeDelta.TotalSeconds; + double velX = (antennaPosition.easting - previousPosition.easting) / timeFactor; + double velY = (antennaPosition.northing - previousPosition.northing) / timeFactor; + double velZ = (antennaPosition.heading - previousPosition.heading) / timeFactor; + + return new vec3(velX, velY, velZ); + } + + /// + /// Get antenna displacement from previous calculation + /// + public double GetAntennaDisplacement() + { + double dx = antennaPosition.easting - previousPosition.easting; + double dy = antennaPosition.northing - previousPosition.northing; + double dz = antennaPosition.heading - previousPosition.heading; + + return Math.Sqrt(dx * dx + dy * dy + dz * dz); + } + + /// + /// Set arm dimensions for specific excavator model + /// + public void SetArmDimensions(double arm, double stick, double armBase, double stickBase) + { + armLength = arm; + stickLength = stick; + baseHeight = armBase; + baseOffsetX = stickBase; + } + + /// + /// Set antenna offset from bucket/stick endpoint + /// + public void SetAntennaOffset(double offsetX, double offsetY, double offsetZ) + { + antennaOffsetX = offsetX; + antennaOffsetY = offsetY; + antennaOffsetZ = offsetZ; + } + + /// + /// Validate arm angles are within physical limits + /// + public bool ValidateAngles(double armAngleDeg, double stickAngleDeg) + { + // Arm angle typically: -10 to 90 degrees + if (armAngleDeg < -20 || armAngleDeg > 100) + return false; + + // Stick angle typically: -90 to 30 degrees + if (stickAngleDeg < -120 || stickAngleDeg > 50) + return false; + + return true; + } + } +} diff --git a/SourceCode/GPS/Classes/CExcavatorVehicle.cs b/SourceCode/GPS/Classes/CExcavatorVehicle.cs new file mode 100644 index 0000000..8c1d0a6 --- /dev/null +++ b/SourceCode/GPS/Classes/CExcavatorVehicle.cs @@ -0,0 +1,233 @@ +//Please, if you use this, share the improvements + +using System; +using SharpGL; + +namespace OpenGrade +{ + /// + /// Excavator-specific vehicle class + /// Extends CVehicle functionality with angle sensor compensation + /// Calculates GPS antenna position based on arm and stick angles + /// + public class CExcavatorVehicle : CVehicle + { + private readonly OpenGL gl; + private readonly FormGPS mf; + + // Angle sensor and kinematics + public CAngleSensor angleSensor; + public CArmKinematics armKinematics; + + // Compensation settings + public bool enableAngleCompensation = true; + public bool enableFilteredAngles = true; + + // Compensated antenna position + private double compensatedEasting = 0; + private double compensatedNorthing = 0; + private double compensatedAltitude = 0; + + public CExcavatorVehicle(OpenGL _gl, FormGPS _f) : base(_gl, _f) + { + gl = _gl; + mf = _f; + + // Initialize angle sensor and kinematics + angleSensor = new CAngleSensor(); + armKinematics = new CArmKinematics(); + + // Load excavator-specific settings if available + LoadExcavatorSettings(); + } + + /// + /// Load excavator-specific settings from properties + /// + private void LoadExcavatorSettings() + { + try + { + // Load arm dimensions + double armLen = Properties.Vehicle.Default.setExcavator_armLength; + double stickLen = Properties.Vehicle.Default.setExcavator_stickLength; + double armBase = Properties.Vehicle.Default.setExcavator_baseHeight; + double stickBase = Properties.Vehicle.Default.setExcavator_baseOffsetX; + + armKinematics.SetArmDimensions(armLen, stickLen, armBase, stickBase); + + // Load antenna offset + double offsetX = Properties.Vehicle.Default.setExcavator_antennaOffsetX; + double offsetY = Properties.Vehicle.Default.setExcavator_antennaOffsetY; + double offsetZ = Properties.Vehicle.Default.setExcavator_antennaOffsetZ; + + armKinematics.SetAntennaOffset(offsetX, offsetY, offsetZ); + } + catch + { + // Use defaults if properties not found + } + } + + /// + /// Update antenna position based on angle sensor readings + /// Call this when new angle sensor data is received + /// + public void UpdateAntennaPositionFromAngles() + { + if (!enableAngleCompensation || !angleSensor.isConnected) + return; + + // Calculate antenna position based on arm and stick angles + vec3 offset = armKinematics.CalculateAntennaPosition( + angleSensor.armAngle, + angleSensor.stickAngle); + + // Apply compensation to GPS position + // This adjusts the antenna reference point based on arm movement + compensatedEasting = offset.easting; + compensatedNorthing = offset.northing; + compensatedAltitude = offset.heading; // Using heading field for Z offset + } + + /// + /// Get the compensated GPS position + /// Applies antenna offset compensation based on arm/stick angles + /// + public vec3 GetCompensatedGPSPosition(vec3 baseGPSPosition) + { + if (!enableAngleCompensation || !angleSensor.isConnected) + return baseGPSPosition; + + // Apply compensation to base GPS position + return new vec3( + baseGPSPosition.easting + compensatedEasting, + baseGPSPosition.northing + compensatedNorthing, + baseGPSPosition.heading + compensatedAltitude); + } + + /// + /// Parse angle sensor data from serial input + /// + public bool ParseAngleSensorData(string sentence) + { + if (angleSensor.ParseAngleData(sentence)) + { + UpdateAntennaPositionFromAngles(); + return true; + } + return false; + } + + /// + /// Get antenna displacement from arm/stick movement + /// + public double GetAntennaDisplacement() + { + return armKinematics.GetAntennaDisplacement(); + } + + /// + /// Check if angle sensor connection is active + /// + public bool IsAngleSensorActive() + { + return angleSensor.IsConnectionAlive(); + } + + /// + /// Calibrate angle sensors to known position + /// + public void CalibrateAngleSensors(double armAngle, double stickAngle) + { + angleSensor.CalibrateAngles(armAngle, stickAngle); + } + + /// + /// Get current arm and stick angles + /// + public void GetCurrentAngles(out double armAngle, out double stickAngle) + { + armAngle = angleSensor.armAngle; + stickAngle = angleSensor.stickAngle; + } + + /// + /// Draw excavator arm based on current angles + /// + public void DrawExcavatorArm() + { + if (!angleSensor.isConnected) + return; + + gl.PushMatrix(); + + // Translate to arm base + gl.Translate( + armKinematics.baseOffsetX, + armKinematics.baseHeight, + armKinematics.baseOffsetY); + + // Draw main arm boom + double armAngleRad = angleSensor.armAngle * Math.PI / 180.0; + gl.Color(0.7f, 0.7f, 0.7f); + gl.LineWidth(8.0f); + gl.Begin(OpenGL.GL_LINES); + gl.Vertex(0, 0, 0); + gl.Vertex( + (float)(armKinematics.armLength * Math.Cos(armAngleRad)), + (float)(armKinematics.armLength * Math.Sin(armAngleRad)), + 0); + gl.End(); + + // Translate to arm endpoint + gl.Translate( + (float)(armKinematics.armLength * Math.Cos(armAngleRad)), + (float)(armKinematics.armLength * Math.Sin(armAngleRad)), + 0); + + // Draw bucket stick + double stickAngleRad = angleSensor.stickAngle * Math.PI / 180.0; + gl.Color(0.5f, 0.5f, 0.5f); + gl.LineWidth(6.0f); + gl.Begin(OpenGL.GL_LINES); + gl.Vertex(0, 0, 0); + gl.Vertex( + (float)(armKinematics.stickLength * Math.Cos(stickAngleRad)), + (float)(armKinematics.stickLength * Math.Sin(stickAngleRad)), + 0); + gl.End(); + + // Draw antenna position marker + gl.Translate( + (float)(armKinematics.stickLength * Math.Cos(stickAngleRad) + armKinematics.antennaOffsetX), + (float)(armKinematics.stickLength * Math.Sin(stickAngleRad) + armKinematics.antennaOffsetY), + (float)armKinematics.antennaOffsetZ); + + gl.Color(1.0f, 0.0f, 0.0f); + gl.PointSize(6.0f); + gl.Begin(OpenGL.GL_POINTS); + gl.Vertex(0, 0, 0); + gl.End(); + + gl.PopMatrix(); + gl.LineWidth(1); + } + + /// + /// Get status information for display + /// + public string GetStatusInfo() + { + if (!angleSensor.isConnected) + return "Angle Sensor: DISCONNECTED"; + + return string.Format("Arm: {0:F1}° Stick: {1:F1}° Antenna Offset: ({2:F3}, {3:F3}, {4:F3})", + angleSensor.armAngle, + angleSensor.stickAngle, + compensatedEasting, + compensatedNorthing, + compensatedAltitude); + } + } +}