-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathLimelight.java
255 lines (208 loc) · 8.54 KB
/
Limelight.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package com.team254.frc2019.subsystems;
import com.team254.frc2019.Constants;
import com.team254.frc2019.RobotState;
import com.team254.lib.geometry.Pose2d;
import com.team254.lib.geometry.Rotation2d;
import com.team254.lib.geometry.Translation2d;
import com.team254.lib.util.Util;
import com.team254.lib.vision.TargetInfo;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Subsystem for interacting with the Limelight 2
*/
public class Limelight extends Subsystem {
public final static int kDefaultPipeline = 0;
public final static int kSortTopPipeline = 1;
public static class LimelightConstants {
public String kName = "";
public String kTableName = "";
public double kHeight = 0.0;
public Pose2d kTurretToLens = Pose2d.identity();
public Rotation2d kHorizontalPlaneToLens = Rotation2d.identity();
}
private NetworkTable mNetworkTable;
public Limelight(LimelightConstants constants) {
mConstants = constants;
mNetworkTable = NetworkTableInstance.getDefault().getTable(constants.kTableName);
}
public static class PeriodicIO {
// INPUTS
public double latency;
public int givenLedMode;
public int givenPipeline;
public double xOffset;
public double yOffset;
public double area;
// OUTPUTS
public int ledMode = 1; // 0 - use pipeline mode, 1 - off, 2 - blink, 3 - on
public int camMode = 0; // 0 - vision processing, 1 - driver camera
public int pipeline = 0; // 0 - 9
public int stream = 2; // sets stream layout if another webcam is attached
public int snapshot = 0; // 0 - stop snapshots, 1 - 2 Hz
}
private LimelightConstants mConstants = null;
private PeriodicIO mPeriodicIO = new PeriodicIO();
private boolean mOutputsHaveChanged = true;
private double[] mZeroArray = new double[]{0, 0, 0, 0, 0, 0, 0, 0};
private List<TargetInfo> mTargets = new ArrayList<>();
private boolean mSeesTarget = false;
public Pose2d getTurretToLens() {
return mConstants.kTurretToLens;
}
public double getLensHeight() {
return mConstants.kHeight;
}
public Rotation2d getHorizontalPlaneToLens() {
return mConstants.kHorizontalPlaneToLens;
}
@Override
public synchronized void readPeriodicInputs() {
mPeriodicIO.latency = mNetworkTable.getEntry("tl").getDouble(0) / 1000.0 + Constants.kImageCaptureLatency;
mPeriodicIO.givenLedMode = (int) mNetworkTable.getEntry("ledMode").getDouble(1.0);
mPeriodicIO.givenPipeline = (int) mNetworkTable.getEntry("pipeline").getDouble(0);
mPeriodicIO.xOffset = mNetworkTable.getEntry("tx").getDouble(0.0);
mPeriodicIO.yOffset = mNetworkTable.getEntry("ty").getDouble(0.0);
mPeriodicIO.area = mNetworkTable.getEntry("ta").getDouble(0.0);
mSeesTarget = mNetworkTable.getEntry("tv").getDouble(0) == 1.0;
}
@Override
public synchronized void writePeriodicOutputs() {
if (mPeriodicIO.givenLedMode != mPeriodicIO.ledMode ||
mPeriodicIO.givenPipeline != mPeriodicIO.pipeline) {
System.out.println("Table has changed from expected, retrigger!!");
mOutputsHaveChanged = true;
}
if (mOutputsHaveChanged) {
mNetworkTable.getEntry("ledMode").setNumber(mPeriodicIO.ledMode);
mNetworkTable.getEntry("camMode").setNumber(mPeriodicIO.camMode);
mNetworkTable.getEntry("pipeline").setNumber(mPeriodicIO.pipeline);
mNetworkTable.getEntry("stream").setNumber(mPeriodicIO.stream);
mNetworkTable.getEntry("snapshot").setNumber(mPeriodicIO.snapshot);
mOutputsHaveChanged = false;
}
}
@Override
public void stop() {}
@Override
public boolean checkSystem() {
return true;
}
@Override
public synchronized void outputTelemetry() {
SmartDashboard.putBoolean(mConstants.kName + ": Has Target", mSeesTarget);
SmartDashboard.putNumber(mConstants.kName + ": Pipeline Latency (ms)", mPeriodicIO.latency);
}
public enum LedMode {
PIPELINE, OFF, BLINK, ON
}
public synchronized void setLed(LedMode mode) {
if (mode.ordinal() != mPeriodicIO.ledMode) {
mPeriodicIO.ledMode = mode.ordinal();
mOutputsHaveChanged = true;
}
}
public synchronized void setPipeline(int mode) {
if (mode != mPeriodicIO.pipeline) {
RobotState.getInstance().resetVision();
mPeriodicIO.pipeline = mode;
System.out.println(mPeriodicIO.pipeline + ", " + mode);
mOutputsHaveChanged = true;
}
}
public synchronized void triggerOutputs() {
mOutputsHaveChanged = true;
}
public synchronized int getPipeline() {
return mPeriodicIO.pipeline;
}
public synchronized boolean seesTarget() {
return mSeesTarget;
}
/**
* @return two targets that make up one hatch/port or null if less than two targets are found
*/
public synchronized List<TargetInfo> getTarget() {
List<TargetInfo> targets = getRawTargetInfos();
if (seesTarget() && targets != null) {
return targets;
}
return null;
}
private synchronized List<TargetInfo> getRawTargetInfos() {
List<double[]> corners = getTopCorners();
if (corners == null) {
return null;
}
double slope = 1.0;
if (Math.abs(corners.get(1)[0] - corners.get(0)[0]) > Util.kEpsilon) {
slope = (corners.get(1)[1] - corners.get(0)[1]) /
(corners.get(1)[0] - corners.get(0)[0]);
}
mTargets.clear();
for (int i = 0; i < 2; ++i) {
// Average of y and z;
double y_pixels = corners.get(i)[0];
double z_pixels = corners.get(i)[1];
// Redefine to robot frame of reference.
double nY = -((y_pixels - 160.0) / 160.0);
double nZ = -((z_pixels - 120.0) / 120.0);
double y = Constants.kVPW / 2 * nY;
double z = Constants.kVPH / 2 * nZ;
TargetInfo target = new TargetInfo(y, z);
target.setSkew(slope);
mTargets.add(target);
}
return mTargets;
}
/**
* Returns raw top-left and top-right corners
*
* @return list of corners: index 0 - top left, index 1 - top right
*/
private List<double[]> getTopCorners() {
double[] xCorners = mNetworkTable.getEntry("tcornx").getDoubleArray(mZeroArray);
double[] yCorners = mNetworkTable.getEntry("tcorny").getDoubleArray(mZeroArray);
mSeesTarget = mNetworkTable.getEntry("tv").getDouble(0) == 1.0;
// something went wrong
if (!mSeesTarget ||
Arrays.equals(xCorners, mZeroArray) || Arrays.equals(yCorners, mZeroArray)
|| xCorners.length != 8 || yCorners.length != 8) {
return null;
}
return extractTopCornersFromBoundingBoxes(xCorners, yCorners);
}
private static final Comparator<Translation2d> xSort = Comparator.comparingDouble(Translation2d::x);
private static final Comparator<Translation2d> ySort = Comparator.comparingDouble(Translation2d::y);
/**
* Returns raw top-left and top-right corners
*
* @return list of corners: index 0 - top left, index 1 - top right
*/
public static List<double[]> extractTopCornersFromBoundingBoxes(double[] xCorners, double[] yCorners) {
List<Translation2d> corners = new ArrayList<>();
for (int i = 0; i < xCorners.length; i++) {
corners.add(new Translation2d(xCorners[i], yCorners[i]));
}
corners.sort(xSort);
List<Translation2d> left = corners.subList(0, 4);
List<Translation2d> right = corners.subList(4, 8);
left.sort(ySort);
right.sort(ySort);
List<Translation2d> leftTop = left.subList(0, 2);
List<Translation2d> rightTop = right.subList(0, 2);
leftTop.sort(xSort);
rightTop.sort(xSort);
Translation2d leftCorner = leftTop.get(0);
Translation2d rightCorner = rightTop.get(1);
return List.of(new double[]{leftCorner.x(), leftCorner.y()}, new double[]{rightCorner.x(), rightCorner.y()});
}
public double getLatency() {
return mPeriodicIO.latency;
}
}