001/************************* PROJECT RON *************************/ 002/* Copyright (c) 2026 StuyPulse Robotics. All rights reserved. */ 003/* Use of this source code is governed by an MIT-style license */ 004/* that can be found in the repository LICENSE file. */ 005/***************************************************************/ 006package com.stuypulse.robot.util.vision; 007 008// LimelightHelpers v1.14 (REQUIRES LLOS 2026.0 OR LATER) 009import com.fasterxml.jackson.annotation.JsonFormat; 010import com.fasterxml.jackson.annotation.JsonFormat.Shape; 011import com.fasterxml.jackson.annotation.JsonProperty; 012import com.fasterxml.jackson.core.JsonProcessingException; 013import com.fasterxml.jackson.databind.DeserializationFeature; 014import com.fasterxml.jackson.databind.ObjectMapper; 015import edu.wpi.first.math.geometry.Pose2d; 016import edu.wpi.first.math.geometry.Pose3d; 017import edu.wpi.first.math.geometry.Rotation2d; 018import edu.wpi.first.math.geometry.Rotation3d; 019import edu.wpi.first.math.geometry.Translation2d; 020import edu.wpi.first.math.geometry.Translation3d; 021import edu.wpi.first.math.util.Units; 022import edu.wpi.first.net.PortForwarder; 023import edu.wpi.first.networktables.DoubleArrayEntry; 024import edu.wpi.first.networktables.NetworkTable; 025import edu.wpi.first.networktables.NetworkTableEntry; 026import edu.wpi.first.networktables.NetworkTableInstance; 027import edu.wpi.first.networktables.TimestampedDoubleArray; 028import java.util.Arrays; 029import java.util.Map; 030import java.util.concurrent.ConcurrentHashMap; 031 032/** 033 * LimelightHelpers provides static methods and classes for interfacing with 034 * Limelight vision 035 * cameras in FRC. This library supports all Limelight features including 036 * AprilTag tracking, Neural 037 * Networks, and standard color/retroreflective tracking. 038 */ 039public class LimelightHelpers { 040 041 private static final Map<String, DoubleArrayEntry> doubleArrayEntries = new ConcurrentHashMap<>(); 042 043 /** 044 * Represents a Color/Retroreflective Target Result extracted from JSON Output 045 */ 046 public static class LimelightTarget_Retro { 047 048 @JsonProperty("t6c_ts") 049 private double[] cameraPose_TargetSpace; 050 051 @JsonProperty("t6r_fs") 052 private double[] robotPose_FieldSpace; 053 054 @JsonProperty("t6r_ts") 055 private double[] robotPose_TargetSpace; 056 057 @JsonProperty("t6t_cs") 058 private double[] targetPose_CameraSpace; 059 060 @JsonProperty("t6t_rs") 061 private double[] targetPose_RobotSpace; 062 063 public Pose3d getCameraPose_TargetSpace() { 064 return toPose3D(cameraPose_TargetSpace); 065 } 066 067 public Pose3d getRobotPose_FieldSpace() { 068 return toPose3D(robotPose_FieldSpace); 069 } 070 071 public Pose3d getRobotPose_TargetSpace() { 072 return toPose3D(robotPose_TargetSpace); 073 } 074 075 public Pose3d getTargetPose_CameraSpace() { 076 return toPose3D(targetPose_CameraSpace); 077 } 078 079 public Pose3d getTargetPose_RobotSpace() { 080 return toPose3D(targetPose_RobotSpace); 081 } 082 083 public Pose2d getCameraPose_TargetSpace2D() { 084 return toPose2D(cameraPose_TargetSpace); 085 } 086 087 public Pose2d getRobotPose_FieldSpace2D() { 088 return toPose2D(robotPose_FieldSpace); 089 } 090 091 public Pose2d getRobotPose_TargetSpace2D() { 092 return toPose2D(robotPose_TargetSpace); 093 } 094 095 public Pose2d getTargetPose_CameraSpace2D() { 096 return toPose2D(targetPose_CameraSpace); 097 } 098 099 public Pose2d getTargetPose_RobotSpace2D() { 100 return toPose2D(targetPose_RobotSpace); 101 } 102 103 @JsonProperty("ta") 104 public double ta; 105 106 @JsonProperty("tx") 107 public double tx; 108 109 @JsonProperty("ty") 110 public double ty; 111 112 @JsonProperty("txp") 113 public double tx_pixels; 114 115 @JsonProperty("typ") 116 public double ty_pixels; 117 118 @JsonProperty("tx_nocross") 119 public double tx_nocrosshair; 120 121 @JsonProperty("ty_nocross") 122 public double ty_nocrosshair; 123 124 @JsonProperty("ts") 125 public double ts; 126 127 public LimelightTarget_Retro() { 128 cameraPose_TargetSpace = new double[6]; 129 robotPose_FieldSpace = new double[6]; 130 robotPose_TargetSpace = new double[6]; 131 targetPose_CameraSpace = new double[6]; 132 targetPose_RobotSpace = new double[6]; 133 } 134 } 135 136 /** Represents an AprilTag/Fiducial Target Result extracted from JSON Output */ 137 public static class LimelightTarget_Fiducial { 138 139 @JsonProperty("fID") 140 public double fiducialID; 141 142 @JsonProperty("fam") 143 public String fiducialFamily; 144 145 @JsonProperty("t6c_ts") 146 private double[] cameraPose_TargetSpace; 147 148 @JsonProperty("t6r_fs") 149 private double[] robotPose_FieldSpace; 150 151 @JsonProperty("t6r_ts") 152 private double[] robotPose_TargetSpace; 153 154 @JsonProperty("t6t_cs") 155 private double[] targetPose_CameraSpace; 156 157 @JsonProperty("t6t_rs") 158 private double[] targetPose_RobotSpace; 159 160 public Pose3d getCameraPose_TargetSpace() { 161 return toPose3D(cameraPose_TargetSpace); 162 } 163 164 public Pose3d getRobotPose_FieldSpace() { 165 return toPose3D(robotPose_FieldSpace); 166 } 167 168 public Pose3d getRobotPose_TargetSpace() { 169 return toPose3D(robotPose_TargetSpace); 170 } 171 172 public Pose3d getTargetPose_CameraSpace() { 173 return toPose3D(targetPose_CameraSpace); 174 } 175 176 public Pose3d getTargetPose_RobotSpace() { 177 return toPose3D(targetPose_RobotSpace); 178 } 179 180 public Pose2d getCameraPose_TargetSpace2D() { 181 return toPose2D(cameraPose_TargetSpace); 182 } 183 184 public Pose2d getRobotPose_FieldSpace2D() { 185 return toPose2D(robotPose_FieldSpace); 186 } 187 188 public Pose2d getRobotPose_TargetSpace2D() { 189 return toPose2D(robotPose_TargetSpace); 190 } 191 192 public Pose2d getTargetPose_CameraSpace2D() { 193 return toPose2D(targetPose_CameraSpace); 194 } 195 196 public Pose2d getTargetPose_RobotSpace2D() { 197 return toPose2D(targetPose_RobotSpace); 198 } 199 200 @JsonProperty("ta") 201 public double ta; 202 203 @JsonProperty("tx") 204 public double tx; 205 206 @JsonProperty("ty") 207 public double ty; 208 209 @JsonProperty("txp") 210 public double tx_pixels; 211 212 @JsonProperty("typ") 213 public double ty_pixels; 214 215 @JsonProperty("tx_nocross") 216 public double tx_nocrosshair; 217 218 @JsonProperty("ty_nocross") 219 public double ty_nocrosshair; 220 221 @JsonProperty("ts") 222 public double ts; 223 224 public LimelightTarget_Fiducial() { 225 cameraPose_TargetSpace = new double[6]; 226 robotPose_FieldSpace = new double[6]; 227 robotPose_TargetSpace = new double[6]; 228 targetPose_CameraSpace = new double[6]; 229 targetPose_RobotSpace = new double[6]; 230 } 231 } 232 233 /** Represents a Barcode Target Result extracted from JSON Output */ 234 public static class LimelightTarget_Barcode { 235 236 /** Barcode family type (e.g. "QR", "DataMatrix", etc.) */ 237 @JsonProperty("fam") 238 public String family; 239 240 /** Gets the decoded data content of the barcode */ 241 @JsonProperty("data") 242 public String data; 243 244 @JsonProperty("txp") 245 public double tx_pixels; 246 247 @JsonProperty("typ") 248 public double ty_pixels; 249 250 @JsonProperty("tx") 251 public double tx; 252 253 @JsonProperty("ty") 254 public double ty; 255 256 @JsonProperty("tx_nocross") 257 public double tx_nocrosshair; 258 259 @JsonProperty("ty_nocross") 260 public double ty_nocrosshair; 261 262 @JsonProperty("ta") 263 public double ta; 264 265 @JsonProperty("pts") 266 public double[][] corners; 267 268 public LimelightTarget_Barcode() { 269 } 270 271 public String getFamily() { 272 return family; 273 } 274 } 275 276 /** Represents a Neural Classifier Pipeline Result extracted from JSON Output */ 277 public static class LimelightTarget_Classifier { 278 279 @JsonProperty("class") 280 public String className; 281 282 @JsonProperty("classID") 283 public double classID; 284 285 @JsonProperty("conf") 286 public double confidence; 287 288 @JsonProperty("zone") 289 public double zone; 290 291 @JsonProperty("tx") 292 public double tx; 293 294 @JsonProperty("txp") 295 public double tx_pixels; 296 297 @JsonProperty("ty") 298 public double ty; 299 300 @JsonProperty("typ") 301 public double ty_pixels; 302 303 public LimelightTarget_Classifier() { 304 } 305 } 306 307 /** Represents a Neural Detector Pipeline Result extracted from JSON Output */ 308 public static class LimelightTarget_Detector { 309 310 @JsonProperty("class") 311 public String className; 312 313 @JsonProperty("classID") 314 public double classID; 315 316 @JsonProperty("conf") 317 public double confidence; 318 319 @JsonProperty("ta") 320 public double ta; 321 322 @JsonProperty("tx") 323 public double tx; 324 325 @JsonProperty("ty") 326 public double ty; 327 328 @JsonProperty("txp") 329 public double tx_pixels; 330 331 @JsonProperty("typ") 332 public double ty_pixels; 333 334 @JsonProperty("tx_nocross") 335 public double tx_nocrosshair; 336 337 @JsonProperty("ty_nocross") 338 public double ty_nocrosshair; 339 340 public LimelightTarget_Detector() { 341 } 342 } 343 344 /** Represents hardware statistics from the Limelight. */ 345 public static class HardwareReport { 346 347 @JsonProperty("cid") 348 public String cameraId; 349 350 @JsonProperty("cpu") 351 public double cpuUsage; 352 353 @JsonProperty("dfree") 354 public double diskFree; 355 356 @JsonProperty("dtot") 357 public double diskTotal; 358 359 @JsonProperty("ram") 360 public double ramUsage; 361 362 @JsonProperty("temp") 363 public double temperature; 364 365 public HardwareReport() { 366 } 367 } 368 369 /** Represents IMU data from the JSON results. */ 370 public static class IMUResults { 371 372 @JsonProperty("data") 373 public double[] data; 374 375 @JsonProperty("quat") 376 public double[] quaternion; 377 378 @JsonProperty("yaw") 379 public double yaw; 380 381 // Parsed from data array 382 public double robotYaw; 383 384 public double roll; 385 386 public double pitch; 387 388 public double rawYaw; 389 390 public double gyroZ; 391 392 public double gyroX; 393 394 public double gyroY; 395 396 public double accelZ; 397 398 public double accelX; 399 400 public double accelY; 401 402 public IMUResults() { 403 data = new double[0]; 404 quaternion = new double[4]; 405 } 406 407 public void parseDataArray() { 408 if (data != null && data.length >= 10) { 409 robotYaw = data[0]; 410 roll = data[1]; 411 pitch = data[2]; 412 rawYaw = data[3]; 413 gyroZ = data[4]; 414 gyroX = data[5]; 415 gyroY = data[6]; 416 accelZ = data[7]; 417 accelX = data[8]; 418 accelY = data[9]; 419 } 420 } 421 } 422 423 /** Represents capture rewind buffer statistics. */ 424 public static class RewindStats { 425 426 @JsonProperty("bufferUsage") 427 public double bufferUsage; 428 429 @JsonProperty("enabled") 430 public int enabled; 431 432 @JsonProperty("flushing") 433 public int flushing; 434 435 @JsonProperty("frameCount") 436 public int frameCount; 437 438 @JsonProperty("latpen") 439 public int latencyPenalty; 440 441 @JsonProperty("storedSeconds") 442 public double storedSeconds; 443 444 public RewindStats() { 445 } 446 } 447 448 /** Limelight Results object, parsed from a Limelight's JSON results output. */ 449 public static class LimelightResults { 450 451 public String error; 452 453 @JsonProperty("pID") 454 public double pipelineID; 455 456 @JsonProperty("tl") 457 public double latency_pipeline; 458 459 @JsonProperty("cl") 460 public double latency_capture; 461 462 public double latency_jsonParse; 463 464 @JsonProperty("ts") 465 public double timestamp_LIMELIGHT_publish; 466 467 @JsonProperty("ts_rio") 468 public double timestamp_RIOFPGA_capture; 469 470 @JsonProperty("ts_nt") 471 public long timestamp_nt; 472 473 @JsonProperty("ts_sys") 474 public long timestamp_sys; 475 476 @JsonProperty("ts_us") 477 public long timestamp_us; 478 479 @JsonProperty("v") 480 @JsonFormat(shape = Shape.NUMBER) 481 public boolean valid; 482 483 @JsonProperty("pTYPE") 484 public String pipelineType; 485 486 @JsonProperty("tx") 487 public double tx; 488 489 @JsonProperty("ty") 490 public double ty; 491 492 @JsonProperty("txnc") 493 public double tx_nocrosshair; 494 495 @JsonProperty("tync") 496 public double ty_nocrosshair; 497 498 @JsonProperty("ta") 499 public double ta; 500 501 @JsonProperty("botpose") 502 public double[] botpose; 503 504 @JsonProperty("botpose_wpired") 505 public double[] botpose_wpired; 506 507 @JsonProperty("botpose_wpiblue") 508 public double[] botpose_wpiblue; 509 510 @JsonProperty("botpose_tagcount") 511 public double botpose_tagcount; 512 513 @JsonProperty("botpose_span") 514 public double botpose_span; 515 516 @JsonProperty("botpose_avgdist") 517 public double botpose_avgdist; 518 519 @JsonProperty("botpose_avgarea") 520 public double botpose_avgarea; 521 522 @JsonProperty("botpose_orb") 523 public double[] botpose_orb; 524 525 @JsonProperty("botpose_orb_wpiblue") 526 public double[] botpose_orb_wpiblue; 527 528 @JsonProperty("botpose_orb_wpired") 529 public double[] botpose_orb_wpired; 530 531 @JsonProperty("t6c_rs") 532 public double[] camerapose_robotspace; 533 534 @JsonProperty("hw") 535 public HardwareReport hardware; 536 537 @JsonProperty("imu") 538 public IMUResults imuResults; 539 540 @JsonProperty("rewind") 541 public RewindStats rewindStats; 542 543 @JsonProperty("PythonOut") 544 public double[] pythonOutput; 545 546 public Pose3d getBotPose3d() { 547 return toPose3D(botpose); 548 } 549 550 public Pose3d getBotPose3d_wpiRed() { 551 return toPose3D(botpose_wpired); 552 } 553 554 public Pose3d getBotPose3d_wpiBlue() { 555 return toPose3D(botpose_wpiblue); 556 } 557 558 public Pose2d getBotPose2d() { 559 return toPose2D(botpose); 560 } 561 562 public Pose2d getBotPose2d_wpiRed() { 563 return toPose2D(botpose_wpired); 564 } 565 566 public Pose2d getBotPose2d_wpiBlue() { 567 return toPose2D(botpose_wpiblue); 568 } 569 570 @JsonProperty("Retro") 571 public LimelightTarget_Retro[] targets_Retro; 572 573 @JsonProperty("Fiducial") 574 public LimelightTarget_Fiducial[] targets_Fiducials; 575 576 @JsonProperty("Classifier") 577 public LimelightTarget_Classifier[] targets_Classifier; 578 579 @JsonProperty("Detector") 580 public LimelightTarget_Detector[] targets_Detector; 581 582 @JsonProperty("Barcode") 583 public LimelightTarget_Barcode[] targets_Barcode; 584 585 public LimelightResults() { 586 botpose = new double[6]; 587 botpose_wpired = new double[6]; 588 botpose_wpiblue = new double[6]; 589 botpose_orb = new double[6]; 590 botpose_orb_wpiblue = new double[6]; 591 botpose_orb_wpired = new double[6]; 592 camerapose_robotspace = new double[6]; 593 targets_Retro = new LimelightTarget_Retro[0]; 594 targets_Fiducials = new LimelightTarget_Fiducial[0]; 595 targets_Classifier = new LimelightTarget_Classifier[0]; 596 targets_Detector = new LimelightTarget_Detector[0]; 597 targets_Barcode = new LimelightTarget_Barcode[0]; 598 pythonOutput = new double[0]; 599 pipelineType = ""; 600 } 601 } 602 603 /** 604 * Represents a Limelight Raw Fiducial result from Limelight's NetworkTables 605 * output. 606 */ 607 public static class RawFiducial { 608 609 public int id = 0; 610 611 public double txnc = 0; 612 613 public double tync = 0; 614 615 public double ta = 0; 616 617 public double distToCamera = 0; 618 619 public double distToRobot = 0; 620 621 public double ambiguity = 0; 622 623 public RawFiducial( 624 int id, 625 double txnc, 626 double tync, 627 double ta, 628 double distToCamera, 629 double distToRobot, 630 double ambiguity) { 631 this.id = id; 632 this.txnc = txnc; 633 this.tync = tync; 634 this.ta = ta; 635 this.distToCamera = distToCamera; 636 this.distToRobot = distToRobot; 637 this.ambiguity = ambiguity; 638 } 639 640 @Override 641 public boolean equals(Object obj) { 642 if (this == obj) 643 return true; 644 if (obj == null || getClass() != obj.getClass()) 645 return false; 646 RawFiducial other = (RawFiducial) obj; 647 return id == other.id 648 && Double.compare(txnc, other.txnc) == 0 649 && Double.compare(tync, other.tync) == 0 650 && Double.compare(ta, other.ta) == 0 651 && Double.compare(distToCamera, other.distToCamera) == 0 652 && Double.compare(distToRobot, other.distToRobot) == 0 653 && Double.compare(ambiguity, other.ambiguity) == 0; 654 } 655 } 656 657 /** 658 * Represents a Limelight Raw Target/Contour result from Limelight's 659 * NetworkTables output. 660 */ 661 public static class RawTarget { 662 663 public double txnc = 0; 664 665 public double tync = 0; 666 667 public double ta = 0; 668 669 public RawTarget(double txnc, double tync, double ta) { 670 this.txnc = txnc; 671 this.tync = tync; 672 this.ta = ta; 673 } 674 675 @Override 676 public boolean equals(Object obj) { 677 if (this == obj) 678 return true; 679 if (obj == null || getClass() != obj.getClass()) 680 return false; 681 RawTarget other = (RawTarget) obj; 682 return Double.compare(txnc, other.txnc) == 0 683 && Double.compare(tync, other.tync) == 0 684 && Double.compare(ta, other.ta) == 0; 685 } 686 } 687 688 /** 689 * Represents a Limelight Raw Neural Detector result from Limelight's 690 * NetworkTables output. 691 */ 692 public static class RawDetection { 693 694 public int classId = 0; 695 696 public double txnc = 0; 697 698 public double tync = 0; 699 700 public double ta = 0; 701 702 public double corner0_X = 0; 703 704 public double corner0_Y = 0; 705 706 public double corner1_X = 0; 707 708 public double corner1_Y = 0; 709 710 public double corner2_X = 0; 711 712 public double corner2_Y = 0; 713 714 public double corner3_X = 0; 715 716 public double corner3_Y = 0; 717 718 public RawDetection( 719 int classId, 720 double txnc, 721 double tync, 722 double ta, 723 double corner0_X, 724 double corner0_Y, 725 double corner1_X, 726 double corner1_Y, 727 double corner2_X, 728 double corner2_Y, 729 double corner3_X, 730 double corner3_Y) { 731 this.classId = classId; 732 this.txnc = txnc; 733 this.tync = tync; 734 this.ta = ta; 735 this.corner0_X = corner0_X; 736 this.corner0_Y = corner0_Y; 737 this.corner1_X = corner1_X; 738 this.corner1_Y = corner1_Y; 739 this.corner2_X = corner2_X; 740 this.corner2_Y = corner2_Y; 741 this.corner3_X = corner3_X; 742 this.corner3_Y = corner3_Y; 743 } 744 } 745 746 /** Represents a 3D Pose Estimate. */ 747 public static class PoseEstimate { 748 749 public Pose2d pose; 750 751 public double timestampSeconds; 752 753 public double latency; 754 755 public int tagCount; 756 757 public double tagSpan; 758 759 public double avgTagDist; 760 761 public double avgTagArea; 762 763 public RawFiducial[] rawFiducials; 764 765 public boolean isMegaTag2; 766 767 /** Instantiates a PoseEstimate object with default values */ 768 public PoseEstimate() { 769 this.pose = new Pose2d(); 770 this.timestampSeconds = 0; 771 this.latency = 0; 772 this.tagCount = 0; 773 this.tagSpan = 0; 774 this.avgTagDist = 0; 775 this.avgTagArea = 0; 776 this.rawFiducials = new RawFiducial[] {}; 777 this.isMegaTag2 = false; 778 } 779 780 public PoseEstimate( 781 Pose2d pose, 782 double timestampSeconds, 783 double latency, 784 int tagCount, 785 double tagSpan, 786 double avgTagDist, 787 double avgTagArea, 788 RawFiducial[] rawFiducials, 789 boolean isMegaTag2) { 790 this.pose = pose; 791 this.timestampSeconds = timestampSeconds; 792 this.latency = latency; 793 this.tagCount = tagCount; 794 this.tagSpan = tagSpan; 795 this.avgTagDist = avgTagDist; 796 this.avgTagArea = avgTagArea; 797 this.rawFiducials = rawFiducials; 798 this.isMegaTag2 = isMegaTag2; 799 } 800 801 @Override 802 public boolean equals(Object obj) { 803 if (this == obj) 804 return true; 805 if (obj == null || getClass() != obj.getClass()) 806 return false; 807 PoseEstimate that = (PoseEstimate) obj; 808 // We don't compare the timestampSeconds as it isn't relevant for equality and 809 // makes 810 // unit testing harder 811 return Double.compare(that.latency, latency) == 0 812 && tagCount == that.tagCount 813 && Double.compare(that.tagSpan, tagSpan) == 0 814 && Double.compare(that.avgTagDist, avgTagDist) == 0 815 && Double.compare(that.avgTagArea, avgTagArea) == 0 816 && pose.equals(that.pose) 817 && Arrays.equals(rawFiducials, that.rawFiducials); 818 } 819 } 820 821 /** Encapsulates the state of an internal Limelight IMU. */ 822 public static class IMUData { 823 824 public double robotYaw = 0.0; 825 826 public double Roll = 0.0; 827 828 public double Pitch = 0.0; 829 830 public double Yaw = 0.0; 831 832 public double gyroX = 0.0; 833 834 public double gyroY = 0.0; 835 836 public double gyroZ = 0.0; 837 838 public double accelX = 0.0; 839 840 public double accelY = 0.0; 841 842 public double accelZ = 0.0; 843 844 public IMUData() { 845 } 846 847 public IMUData(double[] imuData) { 848 if (imuData != null && imuData.length >= 10) { 849 this.robotYaw = imuData[0]; 850 this.Roll = imuData[1]; 851 this.Pitch = imuData[2]; 852 this.Yaw = imuData[3]; 853 this.gyroX = imuData[4]; 854 this.gyroY = imuData[5]; 855 this.gyroZ = imuData[6]; 856 this.accelX = imuData[7]; 857 this.accelY = imuData[8]; 858 this.accelZ = imuData[9]; 859 } 860 } 861 } 862 863 private static ObjectMapper mapper; 864 865 /** Print JSON Parse time to the console in milliseconds */ 866 static boolean profileJSON = false; 867 868 static final String sanitizeName(String name) { 869 if ("".equals(name) || name == null) { 870 return "limelight"; 871 } 872 return name; 873 } 874 875 /** 876 * Takes a 6-length array of pose data and converts it to a Pose3d object. Array 877 * format: [x, y, z, 878 * roll, pitch, yaw] where angles are in degrees. 879 * 880 * @param inData Array containing pose data [x, y, z, roll, pitch, yaw] 881 * @return Pose3d object representing the pose, or empty Pose3d if invalid data 882 */ 883 public static Pose3d toPose3D(double[] inData) { 884 if (inData.length < 6) { 885 // System.err.println("Bad LL 3D Pose Data!"); 886 return new Pose3d(); 887 } 888 return new Pose3d( 889 new Translation3d(inData[0], inData[1], inData[2]), 890 new Rotation3d( 891 Units.degreesToRadians(inData[3]), 892 Units.degreesToRadians(inData[4]), 893 Units.degreesToRadians(inData[5]))); 894 } 895 896 /** 897 * Takes a 6-length array of pose data and converts it to a Pose2d object. Uses 898 * only x, y, and yaw 899 * components, ignoring z, roll, and pitch. Array format: [x, y, z, roll, pitch, 900 * yaw] where angles 901 * are in degrees. 902 * 903 * @param inData Array containing pose data [x, y, z, roll, pitch, yaw] 904 * @return Pose2d object representing the pose, or empty Pose2d if invalid data 905 */ 906 public static Pose2d toPose2D(double[] inData) { 907 if (inData.length < 6) { 908 // System.err.println("Bad LL 2D Pose Data!"); 909 return new Pose2d(); 910 } 911 Translation2d tran2d = new Translation2d(inData[0], inData[1]); 912 Rotation2d r2d = new Rotation2d(Units.degreesToRadians(inData[5])); 913 return new Pose2d(tran2d, r2d); 914 } 915 916 /** 917 * Converts a Pose3d object to an array of doubles in the format [x, y, z, roll, 918 * pitch, yaw]. 919 * Translation components are in meters, rotation components are in degrees. 920 * 921 * @param pose The Pose3d object to convert 922 * @return A 6-element array containing [x, y, z, roll, pitch, yaw] 923 */ 924 public static double[] pose3dToArray(Pose3d pose) { 925 double[] result = new double[6]; 926 result[0] = pose.getTranslation().getX(); 927 result[1] = pose.getTranslation().getY(); 928 result[2] = pose.getTranslation().getZ(); 929 result[3] = Units.radiansToDegrees(pose.getRotation().getX()); 930 result[4] = Units.radiansToDegrees(pose.getRotation().getY()); 931 result[5] = Units.radiansToDegrees(pose.getRotation().getZ()); 932 return result; 933 } 934 935 /** 936 * Converts a Pose2d object to an array of doubles in the format [x, y, z, roll, 937 * pitch, yaw]. 938 * Translation components are in meters, rotation components are in degrees. 939 * Note: z, roll, and 940 * pitch will be 0 since Pose2d only contains x, y, and yaw. 941 * 942 * @param pose The Pose2d object to convert 943 * @return A 6-element array containing [x, y, 0, 0, 0, yaw] 944 */ 945 public static double[] pose2dToArray(Pose2d pose) { 946 double[] result = new double[6]; 947 result[0] = pose.getTranslation().getX(); 948 result[1] = pose.getTranslation().getY(); 949 result[2] = 0; 950 result[3] = Units.radiansToDegrees(0); 951 result[4] = Units.radiansToDegrees(0); 952 result[5] = Units.radiansToDegrees(pose.getRotation().getRadians()); 953 return result; 954 } 955 956 private static double extractArrayEntry(double[] inData, int position) { 957 if (inData.length < position + 1) { 958 return 0; 959 } 960 return inData[position]; 961 } 962 963 private static PoseEstimate getBotPoseEstimate( 964 String limelightName, String entryName, boolean isMegaTag2) { 965 DoubleArrayEntry poseEntry = LimelightHelpers.getLimelightDoubleArrayEntry(limelightName, entryName); 966 TimestampedDoubleArray tsValue = poseEntry.getAtomic(); 967 double[] poseArray = tsValue.value; 968 long timestamp = tsValue.timestamp; 969 if (poseArray.length == 0) { 970 // Handle the case where no data is available 971 return new PoseEstimate(); 972 } 973 var pose = toPose2D(poseArray); 974 double latency = extractArrayEntry(poseArray, 6); 975 int tagCount = (int) extractArrayEntry(poseArray, 7); 976 double tagSpan = extractArrayEntry(poseArray, 8); 977 double tagDist = extractArrayEntry(poseArray, 9); 978 double tagArea = extractArrayEntry(poseArray, 10); 979 // Convert server timestamp from microseconds to seconds and adjust for latency 980 double adjustedTimestamp = (timestamp / 1000000.0) - (latency / 1000.0); 981 int valsPerFiducial = 7; 982 int expectedTotalVals = 11 + valsPerFiducial * tagCount; 983 RawFiducial[] rawFiducials; 984 if (poseArray.length != expectedTotalVals) { 985 // Array size mismatch - return empty array instead of null-filled array 986 rawFiducials = new RawFiducial[0]; 987 } else { 988 rawFiducials = new RawFiducial[tagCount]; 989 for (int i = 0; i < tagCount; i++) { 990 int baseIndex = 11 + (i * valsPerFiducial); 991 int id = (int) poseArray[baseIndex]; 992 double txnc = poseArray[baseIndex + 1]; 993 double tync = poseArray[baseIndex + 2]; 994 double ta = poseArray[baseIndex + 3]; 995 double distToCamera = poseArray[baseIndex + 4]; 996 double distToRobot = poseArray[baseIndex + 5]; 997 double ambiguity = poseArray[baseIndex + 6]; 998 rawFiducials[i] = new RawFiducial(id, txnc, tync, ta, distToCamera, distToRobot, ambiguity); 999 } 1000 } 1001 return new PoseEstimate( 1002 pose, 1003 adjustedTimestamp, 1004 latency, 1005 tagCount, 1006 tagSpan, 1007 tagDist, 1008 tagArea, 1009 rawFiducials, 1010 isMegaTag2); 1011 } 1012 1013 /** 1014 * Gets the latest raw fiducial/AprilTag detection results from NetworkTables. 1015 * 1016 * @param limelightName Name/identifier of the Limelight 1017 * @return Array of RawFiducial objects containing detection details 1018 */ 1019 public static RawFiducial[] getRawFiducials(String limelightName) { 1020 var entry = LimelightHelpers.getLimelightNTTableEntry(limelightName, "rawfiducials"); 1021 var rawFiducialArray = entry.getDoubleArray(new double[0]); 1022 int valsPerEntry = 7; 1023 if (rawFiducialArray.length % valsPerEntry != 0) { 1024 return new RawFiducial[0]; 1025 } 1026 int numFiducials = rawFiducialArray.length / valsPerEntry; 1027 RawFiducial[] rawFiducials = new RawFiducial[numFiducials]; 1028 for (int i = 0; i < numFiducials; i++) { 1029 int baseIndex = i * valsPerEntry; 1030 int id = (int) extractArrayEntry(rawFiducialArray, baseIndex); 1031 double txnc = extractArrayEntry(rawFiducialArray, baseIndex + 1); 1032 double tync = extractArrayEntry(rawFiducialArray, baseIndex + 2); 1033 double ta = extractArrayEntry(rawFiducialArray, baseIndex + 3); 1034 double distToCamera = extractArrayEntry(rawFiducialArray, baseIndex + 4); 1035 double distToRobot = extractArrayEntry(rawFiducialArray, baseIndex + 5); 1036 double ambiguity = extractArrayEntry(rawFiducialArray, baseIndex + 6); 1037 rawFiducials[i] = new RawFiducial(id, txnc, tync, ta, distToCamera, distToRobot, ambiguity); 1038 } 1039 return rawFiducials; 1040 } 1041 1042 /** 1043 * Gets the latest raw neural detector results from NetworkTables 1044 * 1045 * @param limelightName Name/identifier of the Limelight 1046 * @return Array of RawDetection objects containing detection details 1047 */ 1048 public static RawDetection[] getRawDetections(String limelightName) { 1049 var entry = LimelightHelpers.getLimelightNTTableEntry(limelightName, "rawdetections"); 1050 var rawDetectionArray = entry.getDoubleArray(new double[0]); 1051 int valsPerEntry = 12; 1052 if (rawDetectionArray.length % valsPerEntry != 0) { 1053 return new RawDetection[0]; 1054 } 1055 int numDetections = rawDetectionArray.length / valsPerEntry; 1056 RawDetection[] rawDetections = new RawDetection[numDetections]; 1057 for (int i = 0; i < numDetections; i++) { 1058 // Starting index for this detection's data 1059 int baseIndex = i * valsPerEntry; 1060 int classId = (int) extractArrayEntry(rawDetectionArray, baseIndex); 1061 double txnc = extractArrayEntry(rawDetectionArray, baseIndex + 1); 1062 double tync = extractArrayEntry(rawDetectionArray, baseIndex + 2); 1063 double ta = extractArrayEntry(rawDetectionArray, baseIndex + 3); 1064 double corner0_X = extractArrayEntry(rawDetectionArray, baseIndex + 4); 1065 double corner0_Y = extractArrayEntry(rawDetectionArray, baseIndex + 5); 1066 double corner1_X = extractArrayEntry(rawDetectionArray, baseIndex + 6); 1067 double corner1_Y = extractArrayEntry(rawDetectionArray, baseIndex + 7); 1068 double corner2_X = extractArrayEntry(rawDetectionArray, baseIndex + 8); 1069 double corner2_Y = extractArrayEntry(rawDetectionArray, baseIndex + 9); 1070 double corner3_X = extractArrayEntry(rawDetectionArray, baseIndex + 10); 1071 double corner3_Y = extractArrayEntry(rawDetectionArray, baseIndex + 11); 1072 rawDetections[i] = new RawDetection( 1073 classId, txnc, tync, ta, corner0_X, corner0_Y, corner1_X, corner1_Y, corner2_X, 1074 corner2_Y, corner3_X, corner3_Y); 1075 } 1076 return rawDetections; 1077 } 1078 1079 /** 1080 * Gets the raw target contours from NetworkTables. Returns ungrouped contours 1081 * in normalized 1082 * screen space (-1 to 1). 1083 * 1084 * @param limelightName Name/identifier of the Limelight 1085 * @return Array of RawTarget objects containing up to 3 contours 1086 */ 1087 public static RawTarget[] getRawTargets(String limelightName) { 1088 var entry = LimelightHelpers.getLimelightNTTableEntry(limelightName, "rawtargets"); 1089 var rawTargetArray = entry.getDoubleArray(new double[0]); 1090 int valsPerEntry = 3; 1091 if (rawTargetArray.length % valsPerEntry != 0) { 1092 return new RawTarget[0]; 1093 } 1094 int numTargets = rawTargetArray.length / valsPerEntry; 1095 RawTarget[] rawTargets = new RawTarget[numTargets]; 1096 for (int i = 0; i < numTargets; i++) { 1097 int baseIndex = i * valsPerEntry; 1098 double txnc = extractArrayEntry(rawTargetArray, baseIndex); 1099 double tync = extractArrayEntry(rawTargetArray, baseIndex + 1); 1100 double ta = extractArrayEntry(rawTargetArray, baseIndex + 2); 1101 rawTargets[i] = new RawTarget(txnc, tync, ta); 1102 } 1103 return rawTargets; 1104 } 1105 1106 /** 1107 * Gets the corner coordinates of detected targets from NetworkTables. Requires 1108 * "send contours" to 1109 * be enabled in the Limelight Output tab. 1110 * 1111 * @param limelightName Name/identifier of the Limelight 1112 * @return Array of doubles containing corner coordinates [x0, y0, x1, y1, ...] 1113 */ 1114 public static double[] getCornerCoordinates(String limelightName) { 1115 return getLimelightNTDoubleArray(limelightName, "tcornxy"); 1116 } 1117 1118 /** 1119 * Prints detailed information about a PoseEstimate to standard output. Includes 1120 * timestamp, 1121 * latency, tag count, tag span, average tag distance, average tag area, and 1122 * detailed information 1123 * about each detected fiducial. 1124 * 1125 * @param pose The PoseEstimate object to print. If null, prints "No 1126 * PoseEstimate available." 1127 */ 1128 public static void printPoseEstimate(PoseEstimate pose) { 1129 if (pose == null) { 1130 System.out.println("No PoseEstimate available."); 1131 return; 1132 } 1133 System.out.printf("Pose Estimate Information:%n"); 1134 System.out.printf("Timestamp (Seconds): %.3f%n", pose.timestampSeconds); 1135 System.out.printf("Latency: %.3f ms%n", pose.latency); 1136 System.out.printf("Tag Count: %d%n", pose.tagCount); 1137 System.out.printf("Tag Span: %.2f meters%n", pose.tagSpan); 1138 System.out.printf("Average Tag Distance: %.2f meters%n", pose.avgTagDist); 1139 System.out.printf("Average Tag Area: %.2f%% of image%n", pose.avgTagArea); 1140 System.out.printf("Is MegaTag2: %b%n", pose.isMegaTag2); 1141 System.out.println(); 1142 if (pose.rawFiducials == null || pose.rawFiducials.length == 0) { 1143 System.out.println("No RawFiducials data available."); 1144 return; 1145 } 1146 System.out.println("Raw Fiducials Details:"); 1147 for (int i = 0; i < pose.rawFiducials.length; i++) { 1148 RawFiducial fiducial = pose.rawFiducials[i]; 1149 System.out.printf(" Fiducial #%d:%n", i + 1); 1150 System.out.printf(" ID: %d%n", fiducial.id); 1151 System.out.printf(" TXNC: %.2f%n", fiducial.txnc); 1152 System.out.printf(" TYNC: %.2f%n", fiducial.tync); 1153 System.out.printf(" TA: %.2f%n", fiducial.ta); 1154 System.out.printf(" Distance to Camera: %.2f meters%n", fiducial.distToCamera); 1155 System.out.printf(" Distance to Robot: %.2f meters%n", fiducial.distToRobot); 1156 System.out.printf(" Ambiguity: %.2f%n", fiducial.ambiguity); 1157 System.out.println(); 1158 } 1159 } 1160 1161 public static Boolean validPoseEstimate(PoseEstimate pose) { 1162 return pose != null && pose.rawFiducials != null && pose.rawFiducials.length != 0; 1163 } 1164 1165 public static NetworkTable getLimelightNTTable(String tableName) { 1166 return NetworkTableInstance.getDefault().getTable(sanitizeName(tableName)); 1167 } 1168 1169 public static void Flush() { 1170 NetworkTableInstance.getDefault().flush(); 1171 } 1172 1173 public static NetworkTableEntry getLimelightNTTableEntry(String tableName, String entryName) { 1174 return getLimelightNTTable(tableName).getEntry(entryName); 1175 } 1176 1177 public static DoubleArrayEntry getLimelightDoubleArrayEntry(String tableName, String entryName) { 1178 String key = tableName + "/" + entryName; 1179 return doubleArrayEntries.computeIfAbsent( 1180 key, 1181 k -> { 1182 NetworkTable table = getLimelightNTTable(tableName); 1183 return table.getDoubleArrayTopic(entryName).getEntry(new double[0]); 1184 }); 1185 } 1186 1187 public static double getLimelightNTDouble(String tableName, String entryName) { 1188 return getLimelightNTTableEntry(tableName, entryName).getDouble(0.0); 1189 } 1190 1191 public static void setLimelightNTDouble(String tableName, String entryName, double val) { 1192 getLimelightNTTableEntry(tableName, entryName).setDouble(val); 1193 } 1194 1195 public static void setLimelightNTDoubleArray(String tableName, String entryName, double[] val) { 1196 getLimelightNTTableEntry(tableName, entryName).setDoubleArray(val); 1197 } 1198 1199 public static double[] getLimelightNTDoubleArray(String tableName, String entryName) { 1200 return getLimelightNTTableEntry(tableName, entryName).getDoubleArray(new double[0]); 1201 } 1202 1203 public static String getLimelightNTString(String tableName, String entryName) { 1204 return getLimelightNTTableEntry(tableName, entryName).getString(""); 1205 } 1206 1207 public static String[] getLimelightNTStringArray(String tableName, String entryName) { 1208 return getLimelightNTTableEntry(tableName, entryName).getStringArray(new String[0]); 1209 } 1210 1211 // /// 1212 /** 1213 * Does the Limelight have a valid target? 1214 * 1215 * @param limelightName Name of the Limelight camera ("" for default) 1216 * @return True if a valid target is present, false otherwise 1217 */ 1218 public static boolean getTV(String limelightName) { 1219 return 1.0 == getLimelightNTDouble(limelightName, "tv"); 1220 } 1221 1222 /** 1223 * Gets the horizontal offset from the crosshair to the target in degrees. 1224 * 1225 * @param limelightName Name of the Limelight camera ("" for default) 1226 * @return Horizontal offset angle in degrees 1227 */ 1228 public static double getTX(String limelightName) { 1229 return getLimelightNTDouble(limelightName, "tx"); 1230 } 1231 1232 /** 1233 * Gets the vertical offset from the crosshair to the target in degrees. 1234 * 1235 * @param limelightName Name of the Limelight camera ("" for default) 1236 * @return Vertical offset angle in degrees 1237 */ 1238 public static double getTY(String limelightName) { 1239 return getLimelightNTDouble(limelightName, "ty"); 1240 } 1241 1242 /** 1243 * Gets the horizontal offset from the principal pixel/point to the target in 1244 * degrees. This is the 1245 * most accurate 2d metric if you are using a calibrated camera and you don't 1246 * need adjustable 1247 * crosshair functionality. 1248 * 1249 * @param limelightName Name of the Limelight camera ("" for default) 1250 * @return Horizontal offset angle in degrees 1251 */ 1252 public static double getTXNC(String limelightName) { 1253 return getLimelightNTDouble(limelightName, "txnc"); 1254 } 1255 1256 /** 1257 * Gets the vertical offset from the principal pixel/point to the target in 1258 * degrees. This is the 1259 * most accurate 2d metric if you are using a calibrated camera and you don't 1260 * need adjustable 1261 * crosshair functionality. 1262 * 1263 * @param limelightName Name of the Limelight camera ("" for default) 1264 * @return Vertical offset angle in degrees 1265 */ 1266 public static double getTYNC(String limelightName) { 1267 return getLimelightNTDouble(limelightName, "tync"); 1268 } 1269 1270 /** 1271 * Gets the target area as a percentage of the image (0-100%). 1272 * 1273 * @param limelightName Name of the Limelight camera ("" for default) 1274 * @return Target area percentage (0-100) 1275 */ 1276 public static double getTA(String limelightName) { 1277 return getLimelightNTDouble(limelightName, "ta"); 1278 } 1279 1280 /** 1281 * T2D is an array that contains several targeting metrcis 1282 * 1283 * @param limelightName Name of the Limelight camera 1284 * @return Array containing [targetValid, targetCount, targetLatency, 1285 * captureLatency, tx, ty, 1286 * txnc, tync, ta, tid, targetClassIndexDetector, 1287 * targetClassIndexClassifier, 1288 * targetLongSidePixels, targetShortSidePixels, 1289 * targetHorizontalExtentPixels, 1290 * targetVerticalExtentPixels, targetSkewDegrees] 1291 */ 1292 public static double[] getT2DArray(String limelightName) { 1293 return getLimelightNTDoubleArray(limelightName, "t2d"); 1294 } 1295 1296 /** 1297 * Gets the number of targets currently detected. 1298 * 1299 * @param limelightName Name of the Limelight camera 1300 * @return Number of detected targets 1301 */ 1302 public static int getTargetCount(String limelightName) { 1303 double[] t2d = getT2DArray(limelightName); 1304 if (t2d.length == 17) { 1305 return (int) t2d[1]; 1306 } 1307 return 0; 1308 } 1309 1310 /** 1311 * Gets the classifier class index from the currently running neural classifier 1312 * pipeline 1313 * 1314 * @param limelightName Name of the Limelight camera 1315 * @return Class index from classifier pipeline 1316 */ 1317 public static int getClassifierClassIndex(String limelightName) { 1318 double[] t2d = getT2DArray(limelightName); 1319 if (t2d.length == 17) { 1320 return (int) t2d[11]; 1321 } 1322 return 0; 1323 } 1324 1325 /** 1326 * Gets the detector class index from the primary result of the currently 1327 * running neural detector 1328 * pipeline. 1329 * 1330 * @param limelightName Name of the Limelight camera 1331 * @return Class index from detector pipeline 1332 */ 1333 public static int getDetectorClassIndex(String limelightName) { 1334 double[] t2d = getT2DArray(limelightName); 1335 if (t2d.length == 17) { 1336 return (int) t2d[10]; 1337 } 1338 return 0; 1339 } 1340 1341 /** 1342 * Gets the current neural classifier result class name. 1343 * 1344 * @param limelightName Name of the Limelight camera 1345 * @return Class name string from classifier pipeline 1346 */ 1347 public static String getClassifierClass(String limelightName) { 1348 return getLimelightNTString(limelightName, "tcclass"); 1349 } 1350 1351 /** 1352 * Gets the primary neural detector result class name. 1353 * 1354 * @param limelightName Name of the Limelight camera 1355 * @return Class name string from detector pipeline 1356 */ 1357 public static String getDetectorClass(String limelightName) { 1358 return getLimelightNTString(limelightName, "tdclass"); 1359 } 1360 1361 /** 1362 * Gets the pipeline's processing latency contribution. 1363 * 1364 * @param limelightName Name of the Limelight camera 1365 * @return Pipeline latency in milliseconds 1366 */ 1367 public static double getLatency_Pipeline(String limelightName) { 1368 return getLimelightNTDouble(limelightName, "tl"); 1369 } 1370 1371 /** 1372 * Gets the capture latency. 1373 * 1374 * @param limelightName Name of the Limelight camera 1375 * @return Capture latency in milliseconds 1376 */ 1377 public static double getLatency_Capture(String limelightName) { 1378 return getLimelightNTDouble(limelightName, "cl"); 1379 } 1380 1381 /** 1382 * Gets the active pipeline index. 1383 * 1384 * @param limelightName Name of the Limelight camera 1385 * @return Current pipeline index (0-9) 1386 */ 1387 public static double getCurrentPipelineIndex(String limelightName) { 1388 return getLimelightNTDouble(limelightName, "getpipe"); 1389 } 1390 1391 /** 1392 * Gets the current pipeline type. 1393 * 1394 * @param limelightName Name of the Limelight camera 1395 * @return Pipeline type string (e.g. "retro", "apriltag", etc) 1396 */ 1397 public static String getCurrentPipelineType(String limelightName) { 1398 return getLimelightNTString(limelightName, "getpipetype"); 1399 } 1400 1401 /** 1402 * Gets the full JSON results dump. 1403 * 1404 * @param limelightName Name of the Limelight camera 1405 * @return JSON string containing all current results 1406 */ 1407 public static String getJSONDump(String limelightName) { 1408 return getLimelightNTString(limelightName, "json"); 1409 } 1410 1411 /** 1412 * Switch to getBotPose 1413 * 1414 * @param limelightName 1415 * @return 1416 */ 1417 @Deprecated 1418 public static double[] getBotpose(String limelightName) { 1419 return getLimelightNTDoubleArray(limelightName, "botpose"); 1420 } 1421 1422 /** 1423 * Switch to getBotPose_wpiRed 1424 * 1425 * @param limelightName 1426 * @return 1427 */ 1428 @Deprecated 1429 public static double[] getBotpose_wpiRed(String limelightName) { 1430 return getLimelightNTDoubleArray(limelightName, "botpose_wpired"); 1431 } 1432 1433 /** 1434 * Switch to getBotPose_wpiBlue 1435 * 1436 * @param limelightName 1437 * @return 1438 */ 1439 @Deprecated 1440 public static double[] getBotpose_wpiBlue(String limelightName) { 1441 return getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); 1442 } 1443 1444 public static double[] getBotPose(String limelightName) { 1445 return getLimelightNTDoubleArray(limelightName, "botpose"); 1446 } 1447 1448 public static double[] getBotPose_wpiRed(String limelightName) { 1449 return getLimelightNTDoubleArray(limelightName, "botpose_wpired"); 1450 } 1451 1452 public static double[] getBotPose_wpiBlue(String limelightName) { 1453 return getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); 1454 } 1455 1456 public static double[] getBotPose_TargetSpace(String limelightName) { 1457 return getLimelightNTDoubleArray(limelightName, "botpose_targetspace"); 1458 } 1459 1460 public static double[] getCameraPose_TargetSpace(String limelightName) { 1461 return getLimelightNTDoubleArray(limelightName, "camerapose_targetspace"); 1462 } 1463 1464 public static double[] getTargetPose_CameraSpace(String limelightName) { 1465 return getLimelightNTDoubleArray(limelightName, "targetpose_cameraspace"); 1466 } 1467 1468 public static double[] getTargetPose_RobotSpace(String limelightName) { 1469 return getLimelightNTDoubleArray(limelightName, "targetpose_robotspace"); 1470 } 1471 1472 /** 1473 * Gets the average color under the crosshair region as a 3-element array. 1474 * 1475 * @param limelightName Name of the Limelight camera 1476 * @return Array containing [Blue, Green, Red] color values (BGR order) 1477 */ 1478 public static double[] getTargetColor(String limelightName) { 1479 return getLimelightNTDoubleArray(limelightName, "tc"); 1480 } 1481 1482 public static double getFiducialID(String limelightName) { 1483 return getLimelightNTDouble(limelightName, "tid"); 1484 } 1485 1486 /** 1487 * Gets the Limelight heartbeat value. Increments once per frame, allowing you 1488 * to detect if the 1489 * Limelight is connected and alive. 1490 * 1491 * @param limelightName Name of the Limelight camera 1492 * @return Heartbeat value that increments each frame 1493 */ 1494 public static double getHeartbeat(String limelightName) { 1495 return getLimelightNTDouble(limelightName, "hb"); 1496 } 1497 1498 public static String getNeuralClassID(String limelightName) { 1499 return getLimelightNTString(limelightName, "tclass"); 1500 } 1501 1502 public static String[] getRawBarcodeData(String limelightName) { 1503 return getLimelightNTStringArray(limelightName, "rawbarcodes"); 1504 } 1505 1506 // /// 1507 // /// 1508 public static Pose3d getBotPose3d(String limelightName) { 1509 double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose"); 1510 return toPose3D(poseArray); 1511 } 1512 1513 /** 1514 * (Not Recommended) Gets the robot's 3D pose in the WPILib Red Alliance 1515 * Coordinate System. 1516 * 1517 * @param limelightName Name/identifier of the Limelight 1518 * @return Pose3d object representing the robot's position and orientation in 1519 * Red Alliance field 1520 * space 1521 */ 1522 public static Pose3d getBotPose3d_wpiRed(String limelightName) { 1523 double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_wpired"); 1524 return toPose3D(poseArray); 1525 } 1526 1527 /** 1528 * (Recommended) Gets the robot's 3D pose in the WPILib Blue Alliance Coordinate 1529 * System. 1530 * 1531 * @param limelightName Name/identifier of the Limelight 1532 * @return Pose3d object representing the robot's position and orientation in 1533 * Blue Alliance field 1534 * space 1535 */ 1536 public static Pose3d getBotPose3d_wpiBlue(String limelightName) { 1537 double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); 1538 return toPose3D(poseArray); 1539 } 1540 1541 /** 1542 * Gets the robot's 3D pose with respect to the currently tracked target's 1543 * coordinate system. 1544 * 1545 * @param limelightName Name/identifier of the Limelight 1546 * @return Pose3d object representing the robot's position and orientation 1547 * relative to the target 1548 */ 1549 public static Pose3d getBotPose3d_TargetSpace(String limelightName) { 1550 double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_targetspace"); 1551 return toPose3D(poseArray); 1552 } 1553 1554 /** 1555 * Gets the camera's 3D pose with respect to the currently tracked target's 1556 * coordinate system. 1557 * 1558 * @param limelightName Name/identifier of the Limelight 1559 * @return Pose3d object representing the camera's position and orientation 1560 * relative to the target 1561 */ 1562 public static Pose3d getCameraPose3d_TargetSpace(String limelightName) { 1563 double[] poseArray = getLimelightNTDoubleArray(limelightName, "camerapose_targetspace"); 1564 return toPose3D(poseArray); 1565 } 1566 1567 /** 1568 * Gets the target's 3D pose with respect to the camera's coordinate system. 1569 * 1570 * @param limelightName Name/identifier of the Limelight 1571 * @return Pose3d object representing the target's position and orientation 1572 * relative to the camera 1573 */ 1574 public static Pose3d getTargetPose3d_CameraSpace(String limelightName) { 1575 double[] poseArray = getLimelightNTDoubleArray(limelightName, "targetpose_cameraspace"); 1576 return toPose3D(poseArray); 1577 } 1578 1579 /** 1580 * Gets the target's 3D pose with respect to the robot's coordinate system. 1581 * 1582 * @param limelightName Name/identifier of the Limelight 1583 * @return Pose3d object representing the target's position and orientation 1584 * relative to the robot 1585 */ 1586 public static Pose3d getTargetPose3d_RobotSpace(String limelightName) { 1587 double[] poseArray = getLimelightNTDoubleArray(limelightName, "targetpose_robotspace"); 1588 return toPose3D(poseArray); 1589 } 1590 1591 /** 1592 * Gets the camera's 3D pose with respect to the robot's coordinate system. 1593 * 1594 * @param limelightName Name/identifier of the Limelight 1595 * @return Pose3d object representing the camera's position and orientation 1596 * relative to the robot 1597 */ 1598 public static Pose3d getCameraPose3d_RobotSpace(String limelightName) { 1599 double[] poseArray = getLimelightNTDoubleArray(limelightName, "camerapose_robotspace"); 1600 return toPose3D(poseArray); 1601 } 1602 1603 /** 1604 * Gets the Pose2d for easy use with Odometry vision pose estimator 1605 * (addVisionMeasurement) 1606 * 1607 * @param limelightName 1608 * @return 1609 */ 1610 public static Pose2d getBotPose2d_wpiBlue(String limelightName) { 1611 double[] result = getBotPose_wpiBlue(limelightName); 1612 return toPose2D(result); 1613 } 1614 1615 /** 1616 * Gets the MegaTag1 Pose2d and timestamp for use with WPILib pose estimator 1617 * (addVisionMeasurement) in the WPILib Blue alliance coordinate system. 1618 * 1619 * @param limelightName 1620 * @return 1621 */ 1622 public static PoseEstimate getBotPoseEstimate_wpiBlue(String limelightName) { 1623 return getBotPoseEstimate(limelightName, "botpose_wpiblue", false); 1624 } 1625 1626 /** 1627 * Gets the MegaTag2 Pose2d and timestamp for use with WPILib pose estimator 1628 * (addVisionMeasurement) in the WPILib Blue alliance coordinate system. Make 1629 * sure you are calling 1630 * setRobotOrientation() before calling this method. 1631 * 1632 * @param limelightName 1633 * @return 1634 */ 1635 public static PoseEstimate getBotPoseEstimate_wpiBlue_MegaTag2(String limelightName) { 1636 return getBotPoseEstimate(limelightName, "botpose_orb_wpiblue", true); 1637 } 1638 1639 /** 1640 * Gets the Pose2d for easy use with Odometry vision pose estimator 1641 * (addVisionMeasurement) 1642 * 1643 * @param limelightName 1644 * @return 1645 */ 1646 public static Pose2d getBotPose2d_wpiRed(String limelightName) { 1647 double[] result = getBotPose_wpiRed(limelightName); 1648 return toPose2D(result); 1649 } 1650 1651 /** 1652 * Gets the Pose2d and timestamp for use with WPILib pose estimator 1653 * (addVisionMeasurement) when 1654 * you are on the RED alliance 1655 * 1656 * @param limelightName 1657 * @return 1658 */ 1659 public static PoseEstimate getBotPoseEstimate_wpiRed(String limelightName) { 1660 return getBotPoseEstimate(limelightName, "botpose_wpired", false); 1661 } 1662 1663 /** 1664 * Gets the Pose2d and timestamp for use with WPILib pose estimator 1665 * (addVisionMeasurement) when 1666 * you are on the RED alliance 1667 * 1668 * @param limelightName 1669 * @return 1670 */ 1671 public static PoseEstimate getBotPoseEstimate_wpiRed_MegaTag2(String limelightName) { 1672 return getBotPoseEstimate(limelightName, "botpose_orb_wpired", true); 1673 } 1674 1675 /** 1676 * Gets the Pose2d for easy use with Odometry vision pose estimator 1677 * (addVisionMeasurement) 1678 * 1679 * @param limelightName 1680 * @return 1681 */ 1682 public static Pose2d getBotPose2d(String limelightName) { 1683 double[] result = getBotPose(limelightName); 1684 return toPose2D(result); 1685 } 1686 1687 /** 1688 * Gets the current IMU data from NetworkTables. IMU data is formatted as 1689 * [robotYaw, Roll, Pitch, 1690 * Yaw, gyroX, gyroY, gyroZ, accelX, accelY, accelZ]. Returns all zeros if data 1691 * is invalid or 1692 * unavailable. 1693 * 1694 * @param limelightName Name/identifier of the Limelight 1695 * @return IMUData object containing all current IMU data 1696 */ 1697 public static IMUData getIMUData(String limelightName) { 1698 double[] imuData = getLimelightNTDoubleArray(limelightName, "imu"); 1699 if (imuData == null || imuData.length < 10) { 1700 // Returns object with all zeros 1701 return new IMUData(); 1702 } 1703 return new IMUData(imuData); 1704 } 1705 1706 // /// 1707 // /// 1708 public static void setPipelineIndex(String limelightName, int pipelineIndex) { 1709 setLimelightNTDouble(limelightName, "pipeline", pipelineIndex); 1710 } 1711 1712 public static void setPriorityTagID(String limelightName, int ID) { 1713 setLimelightNTDouble(limelightName, "priorityid", ID); 1714 } 1715 1716 /** 1717 * Sets LED mode to be controlled by the current pipeline. 1718 * 1719 * @param limelightName Name of the Limelight camera 1720 */ 1721 public static void setLEDMode_PipelineControl(String limelightName) { 1722 setLimelightNTDouble(limelightName, "ledMode", 0); 1723 } 1724 1725 public static void setLEDMode_ForceOff(String limelightName) { 1726 setLimelightNTDouble(limelightName, "ledMode", 1); 1727 } 1728 1729 public static void setLEDMode_ForceBlink(String limelightName) { 1730 setLimelightNTDouble(limelightName, "ledMode", 2); 1731 } 1732 1733 public static void setLEDMode_ForceOn(String limelightName) { 1734 setLimelightNTDouble(limelightName, "ledMode", 3); 1735 } 1736 1737 /** 1738 * Enables standard side-by-side stream mode. 1739 * 1740 * @param limelightName Name of the Limelight camera 1741 */ 1742 public static void setStreamMode_Standard(String limelightName) { 1743 setLimelightNTDouble(limelightName, "stream", 0); 1744 } 1745 1746 /** 1747 * Enables Picture-in-Picture mode with secondary stream in the corner. 1748 * 1749 * @param limelightName Name of the Limelight camera 1750 */ 1751 public static void setStreamMode_PiPMain(String limelightName) { 1752 setLimelightNTDouble(limelightName, "stream", 1); 1753 } 1754 1755 /** 1756 * Enables Picture-in-Picture mode with primary stream in the corner. 1757 * 1758 * @param limelightName Name of the Limelight camera 1759 */ 1760 public static void setStreamMode_PiPSecondary(String limelightName) { 1761 setLimelightNTDouble(limelightName, "stream", 2); 1762 } 1763 1764 /** 1765 * Sets the crop window for the camera. The crop window in the UI must be 1766 * completely open. 1767 * 1768 * @param limelightName Name of the Limelight camera 1769 * @param cropXMin Minimum X value (-1 to 1) 1770 * @param cropXMax Maximum X value (-1 to 1) 1771 * @param cropYMin Minimum Y value (-1 to 1) 1772 * @param cropYMax Maximum Y value (-1 to 1) 1773 */ 1774 public static void setCropWindow( 1775 String limelightName, double cropXMin, double cropXMax, double cropYMin, double cropYMax) { 1776 double[] entries = new double[4]; 1777 entries[0] = cropXMin; 1778 entries[1] = cropXMax; 1779 entries[2] = cropYMin; 1780 entries[3] = cropYMax; 1781 setLimelightNTDoubleArray(limelightName, "crop", entries); 1782 } 1783 1784 /** 1785 * Sets the keystone modification for the crop window. 1786 * 1787 * @param limelightName Name of the Limelight camera 1788 * @param horizontal Horizontal keystone value (-0.95 to 0.95) 1789 * @param vertical Vertical keystone value (-0.95 to 0.95) 1790 */ 1791 public static void setKeystone(String limelightName, double horizontal, double vertical) { 1792 double[] entries = new double[2]; 1793 entries[0] = horizontal; 1794 entries[1] = vertical; 1795 setLimelightNTDoubleArray(limelightName, "keystone_set", entries); 1796 } 1797 1798 /** Sets 3D offset point for easy 3D targeting. */ 1799 public static void setFiducial3DOffset( 1800 String limelightName, double offsetX, double offsetY, double offsetZ) { 1801 double[] entries = new double[3]; 1802 entries[0] = offsetX; 1803 entries[1] = offsetY; 1804 entries[2] = offsetZ; 1805 setLimelightNTDoubleArray(limelightName, "fiducial_offset_set", entries); 1806 } 1807 1808 /** 1809 * Sets robot orientation values used by MegaTag2 localization algorithm. 1810 * 1811 * @param limelightName Name/identifier of the Limelight 1812 * @param yaw Robot yaw in degrees. 0 = robot facing red alliance wall 1813 * in FRC 1814 * @param yawRate (Unnecessary) Angular velocity of robot yaw in degrees 1815 * per second 1816 * @param pitch (Unnecessary) Robot pitch in degrees 1817 * @param pitchRate (Unnecessary) Angular velocity of robot pitch in degrees 1818 * per second 1819 * @param roll (Unnecessary) Robot roll in degrees 1820 * @param rollRate (Unnecessary) Angular velocity of robot roll in degrees 1821 * per second 1822 */ 1823 public static void SetRobotOrientation( 1824 String limelightName, 1825 double yaw, 1826 double yawRate, 1827 double pitch, 1828 double pitchRate, 1829 double roll, 1830 double rollRate) { 1831 SetRobotOrientation_INTERNAL( 1832 limelightName, yaw, yawRate, pitch, pitchRate, roll, rollRate, true); 1833 } 1834 1835 public static void SetRobotOrientation_NoFlush( 1836 String limelightName, 1837 double yaw, 1838 double yawRate, 1839 double pitch, 1840 double pitchRate, 1841 double roll, 1842 double rollRate) { 1843 SetRobotOrientation_INTERNAL( 1844 limelightName, yaw, yawRate, pitch, pitchRate, roll, rollRate, false); 1845 } 1846 1847 private static void SetRobotOrientation_INTERNAL( 1848 String limelightName, 1849 double yaw, 1850 double yawRate, 1851 double pitch, 1852 double pitchRate, 1853 double roll, 1854 double rollRate, 1855 boolean flush) { 1856 double[] entries = new double[6]; 1857 entries[0] = yaw; 1858 entries[1] = yawRate; 1859 entries[2] = pitch; 1860 entries[3] = pitchRate; 1861 entries[4] = roll; 1862 entries[5] = rollRate; 1863 setLimelightNTDoubleArray(limelightName, "robot_orientation_set", entries); 1864 if (flush) { 1865 Flush(); 1866 } 1867 } 1868 1869 /** 1870 * Configures the IMU mode for MegaTag2 Localization 1871 * 1872 * @param limelightName Name/identifier of the Limelight 1873 * @param mode IMU mode. 1874 */ 1875 public static void SetIMUMode(String limelightName, int mode) { 1876 setLimelightNTDouble(limelightName, "imumode_set", mode); 1877 } 1878 1879 /** 1880 * Configures the complementary filter alpha value for IMU Assist Modes (Modes 3 1881 * and 4) 1882 * 1883 * @param limelightName Name/identifier of the Limelight 1884 * @param alpha Defaults to .001. Higher values will cause the internal 1885 * IMU to converge onto the 1886 * assist source more rapidly. 1887 */ 1888 public static void SetIMUAssistAlpha(String limelightName, double alpha) { 1889 setLimelightNTDouble(limelightName, "imuassistalpha_set", alpha); 1890 } 1891 1892 /** 1893 * Configures the throttle value. Set to 100-200 while disabled to reduce 1894 * thermal 1895 * output/temperature. 1896 * 1897 * @param limelightName Name/identifier of the Limelight 1898 * @param throttle Defaults to 0. Your Limelgiht will process one frame 1899 * after skipping throttle 1900 * frames. 1901 */ 1902 public static void SetThrottle(String limelightName, int throttle) { 1903 setLimelightNTDouble(limelightName, "throttle_set", throttle); 1904 } 1905 1906 /** 1907 * Overrides the valid AprilTag IDs that will be used for localization. Tags not 1908 * in this list will 1909 * be ignored for robot pose estimation. 1910 * 1911 * @param limelightName Name/identifier of the Limelight 1912 * @param validIDs Array of valid AprilTag IDs to track 1913 */ 1914 public static void SetFiducialIDFiltersOverride(String limelightName, int[] validIDs) { 1915 double[] validIDsDouble = new double[validIDs.length]; 1916 for (int i = 0; i < validIDs.length; i++) { 1917 validIDsDouble[i] = validIDs[i]; 1918 } 1919 setLimelightNTDoubleArray(limelightName, "fiducial_id_filters_set", validIDsDouble); 1920 } 1921 1922 /** 1923 * Sets the downscaling factor for AprilTag detection. Increasing downscale can 1924 * improve 1925 * performance at the cost of potentially reduced detection range. 1926 * 1927 * @param limelightName Name/identifier of the Limelight 1928 * @param downscale Downscale factor. Valid values: 1.0 (no downscale), 1.5, 1929 * 2.0, 3.0, 4.0. Set to 1930 * 0 for pipeline control. 1931 */ 1932 public static void SetFiducialDownscalingOverride(String limelightName, float downscale) { 1933 // pipeline 1934 int d = 0; 1935 if (downscale == 1.0) { 1936 d = 1; 1937 } 1938 if (downscale == 1.5) { 1939 d = 2; 1940 } 1941 if (downscale == 2) { 1942 d = 3; 1943 } 1944 if (downscale == 3) { 1945 d = 4; 1946 } 1947 if (downscale == 4) { 1948 d = 5; 1949 } 1950 setLimelightNTDouble(limelightName, "fiducial_downscale_set", d); 1951 } 1952 1953 /** 1954 * Sets the camera pose relative to the robot. 1955 * 1956 * @param limelightName Name of the Limelight camera 1957 * @param forward Forward offset in meters 1958 * @param side Side offset in meters 1959 * @param up Up offset in meters 1960 * @param roll Roll angle in degrees 1961 * @param pitch Pitch angle in degrees 1962 * @param yaw Yaw angle in degrees 1963 */ 1964 public static void setCameraPose_RobotSpace( 1965 String limelightName, 1966 double forward, 1967 double side, 1968 double up, 1969 double roll, 1970 double pitch, 1971 double yaw) { 1972 double[] entries = new double[6]; 1973 entries[0] = forward; 1974 entries[1] = side; 1975 entries[2] = up; 1976 entries[3] = roll; 1977 entries[4] = pitch; 1978 entries[5] = yaw; 1979 setLimelightNTDoubleArray(limelightName, "camerapose_robotspace_set", entries); 1980 } 1981 1982 // /// 1983 // /// 1984 public static void setPythonScriptData(String limelightName, double[] outgoingPythonData) { 1985 setLimelightNTDoubleArray(limelightName, "llrobot", outgoingPythonData); 1986 } 1987 1988 public static double[] getPythonScriptData(String limelightName) { 1989 return getLimelightNTDoubleArray(limelightName, "llpython"); 1990 } 1991 1992 // /// 1993 // /// 1994 /** 1995 * Triggers a snapshot capture via NetworkTables by incrementing the snapshot 1996 * counter. 1997 * Rate-limited to once per 10 frames on the Limelight. 1998 * 1999 * @param limelightName Name of the Limelight camera 2000 */ 2001 public static void triggerSnapshot(String limelightName) { 2002 double current = getLimelightNTDouble(limelightName, "snapshot"); 2003 setLimelightNTDouble(limelightName, "snapshot", current + 1); 2004 } 2005 2006 /** 2007 * Enables or pauses the rewind buffer recording. 2008 * 2009 * @param limelightName Name of the Limelight camera 2010 * @param enabled True to enable recording, false to pause 2011 */ 2012 public static void setRewindEnabled(String limelightName, boolean enabled) { 2013 setLimelightNTDouble(limelightName, "rewind_enable_set", enabled ? 1 : 0); 2014 } 2015 2016 /** 2017 * Triggers a rewind capture with the specified duration. Maximum duration is 2018 * 165 seconds. 2019 * Rate-limited on the Limelight. 2020 * 2021 * @param limelightName Name of the Limelight camera 2022 * @param durationSeconds Duration of rewind capture in seconds (max 165) 2023 */ 2024 public static void triggerRewindCapture(String limelightName, double durationSeconds) { 2025 double[] currentArray = getLimelightNTDoubleArray(limelightName, "capture_rewind"); 2026 double counter = (currentArray.length > 0) ? currentArray[0] : 0; 2027 double[] entries = new double[2]; 2028 entries[0] = counter + 1; 2029 entries[1] = Math.min(durationSeconds, 165); 2030 setLimelightNTDoubleArray(limelightName, "capture_rewind", entries); 2031 } 2032 2033 /** 2034 * Gets the latest JSON results output and returns a LimelightResults object. 2035 * 2036 * @param limelightName Name of the Limelight camera 2037 * @return LimelightResults object containing all current target data 2038 */ 2039 public static LimelightResults getLatestResults(String limelightName) { 2040 long start = System.nanoTime(); 2041 LimelightHelpers.LimelightResults results = new LimelightHelpers.LimelightResults(); 2042 if (mapper == null) { 2043 mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 2044 } 2045 try { 2046 String jsonString = getJSONDump(limelightName); 2047 if (jsonString == null || jsonString.isEmpty() || jsonString.isBlank()) { 2048 results.error = "lljson error: empty json"; 2049 } else { 2050 results = mapper.readValue(jsonString, LimelightResults.class); 2051 if (results.imuResults != null) { 2052 results.imuResults.parseDataArray(); 2053 } 2054 } 2055 } catch (JsonProcessingException e) { 2056 results.error = "lljson error: " + e.getMessage(); 2057 } 2058 long end = System.nanoTime(); 2059 double millis = (end - start) * .000001; 2060 results.latency_jsonParse = millis; 2061 if (profileJSON) { 2062 System.out.printf("lljson: %.2f\r\n", millis); 2063 } 2064 return results; 2065 } 2066 2067 /** 2068 * Sets up port forwarding for a Limelight 3A/3G connected via USB. This allows 2069 * access to the 2070 * Limelight web interface and video stream when connected to the robot over 2071 * USB. 2072 * 2073 * <p> 2074 * For usbIndex 0: ports 5800-5809 forward to 172.29.0.1 For usbIndex 1: ports 2075 * 5810-5819 2076 * forward to 172.29.1.1 etc. 2077 * 2078 * <p> 2079 * Call this method once during robot initialization. To access the interface of 2080 * the camera 2081 * with usbIndex0, you would go to roboRIO-(teamnum)-FRC.local:5801. Port 5811 2082 * for usb index 1 2083 * 2084 * @param usbIndex The USB index of the Limelight (0, 1, 2, etc.) 2085 */ 2086 public static void setupPortForwardingUSB(int usbIndex) { 2087 String ip = "172.29." + usbIndex + ".1"; 2088 int basePort = 5800 + (usbIndex * 10); 2089 for (int i = 0; i < 10; i++) { 2090 PortForwarder.add(basePort + i, ip, 5800 + i); 2091 } 2092 } 2093}