Microsoft Lifecam HD 3000

We are having problems connecting our Microsoft LifeCam 3000 to the SFX SmartDashboard using C++. The camera pops up on the roborio web interface and identifies as “cam0,” but we are unable to get it to stream back to either the regular or sfx dashboard using this code. We are currently running the simple vision example. We can confirm the camera works as we ran it on a laptop successfully. Please help us solve this problem as soon as possible.

Have you added the include for WPILib to the sample vision? Does it compile and go onto the Rio easily

Different topic but same product…

How have people removed the base the camera came with?

See the “Disassembling the camera” section at this link:

Yes we’ve done that but the code works fine and the camera is connected to the internet interface. The main problem is that the camera isn’t showing up on our SmartDashboard.

Has anyone gotten SFX to work with the USB camera? I have the same problem - camera actually works with the smartdashboard jar and the default dashboard but I’d prefer to use the SFX dashboard. I’d love a link to a patch I could make myself if one exists.

I wrote a USB cam sfx plugin last year. I’ll find it for you once I’m out of school :slight_smile:

Thanks - we start our second regional tomorrow and am hoping to be able to substitute the sfx dashboard for the older smartdashboard

https://github.com/ligerbots/Stronghold2016Robot/blob/master/Dashboard/plugins/CameraServerPlugin.jar

Here’s the source


package org.ligerbots.sfx;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

import javax.imageio.ImageIO;

import dashfx.lib.controls.Category;
import dashfx.lib.controls.Control;
import dashfx.lib.controls.Designable;
import dashfx.lib.data.DataCoreProvider;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

/**
 * @author Erik
 */
@Designable(value = "CameraServer", image = "/camera.png", description = "Reads images from CameraServer")
@Category("General")
public class CameraServerPlugin extends GridPane implements Control, Runnable {
	ImageView ui;
	Pane uil;

	StringProperty robotAddr = new SimpleStringProperty("10.28.77.2");
	IntegerProperty fps = new SimpleIntegerProperty(30);
	IntegerProperty mode = new SimpleIntegerProperty(0);
	BooleanProperty showSaveBtn = new SimpleBooleanProperty(true);

	Thread imageGrabber = null;
	volatile StringProperty connStatus = new SimpleStringProperty(
			"Connecting...");
	volatile boolean destroyed = false;
	volatile Socket socket = null;

	final Object saveLock = new Object();
	volatile boolean saveImage = false;

	@Designable(description = "Whether to show the save image button or not", value = "Show Save Button")
	public BooleanProperty showSaveBtnProperty() {
		return showSaveBtn;
	}

	public boolean getShowSaveBtn() {
		return showSaveBtn.get();
	}

	public void setShowSaveBtn(boolean value) {
		showSaveBtn.set(value);
	}

	@Designable(description = "The IP or domain name of the robot", value = "Robot Address")
	public StringProperty addrProperty() {
		return robotAddr;
	}

	public String getAddr() {
		return robotAddr.get();
	}

	public void setAddr(String addr) {
		robotAddr.set(addr);
	}

	@Designable(value = "FPS", description = "The fps for video to send at")
	public IntegerProperty fpsProperty() {
		return fps;
	}

	public int getFps() {
		return fps.get();
	}

	public void setFps(int newFps) {
		fps.set(newFps);
	}

	@Designable(value = "Resolution", description = "0 for 480p, 1 for 240p, 2 for 120p")
	public IntegerProperty resProperty() {
		return mode;
	}

	public int getRes() {
		return mode.get();
	}

	public void setRes(int res) {
		mode.set(res);
	}

	public CameraServerPlugin() {
		uil = new Pane();
		uil.setStyle("-fx-border-color: black;");
		ui = new ImageView();
		uil.getChildren().add(this.ui);
		uil.setPrefHeight(120.0D);
		uil.setPrefWidth(160.0D);
		uil.setManaged(false);
		ui.fitHeightProperty().bind(this.uil.heightProperty());
		ui.fitWidthProperty().bind(this.uil.widthProperty());
		ui.setPreserveRatio(false);

		Image img = new Image(
				CameraServerPlugin.class.getResourceAsStream("/default.png"));
		ui.setImage(img);

		setAlignment(Pos.TOP_LEFT);
		add(uil, 0, 0, 3, 1);

		final Label statusLabel = new Label("Connecting...");
		statusLabel.setBackground(new Background(new BackgroundFill(Color.WHITE,
				CornerRadii.EMPTY, Insets.EMPTY)));
		statusLabel.setCache(true);
		add(statusLabel, 0, 1, 3, 1);

		connStatus.addListener(new ChangeListener<String>() {
			@Override
			public void changed(ObservableValue<? extends String> observable,
					String oldValue, String newValue) {
				final String val = newValue;
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						statusLabel.setText(val);
					}
				});
			}
		});

		final Button saveImageButton = new Button("Save Image");
		add(saveImageButton, 0, 2, 3, 1);
		saveImageButton.setOnAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent event) {
				synchronized (saveLock) {
					saveImage = true;
				}
			}
		});

		robotAddr.addListener(new ChangeListener<String>() {
			@Override
			public void changed(ObservableValue<? extends String> observable,
					String oldValue, String newValue) {
				if (socket != null) {
					try {
						socket.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});

		fps.addListener(new ChangeListener<Number>() {
			@Override
			public void changed(ObservableValue<? extends Number> observable,
					Number oldValue, Number newValue) {
				if (socket != null) {
					try {
						socket.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});

		mode.addListener(new ChangeListener<Number>() {
			@Override
			public void changed(ObservableValue<? extends Number> observable,
					Number oldValue, Number newValue) {
				if (newValue.intValue() == 1) {
					uil.setPrefWidth(320);
					uil.setPrefHeight(240);
				} else if (newValue.intValue() == 2) {
					uil.setPrefWidth(160);
					uil.setPrefHeight(120);
				} else {
					uil.setPrefWidth(640);
					uil.setPrefHeight(480);
				}
				if (socket != null) {
					try {
						socket.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});

		showSaveBtn.addListener(new ChangeListener<Boolean>() {
			@Override
			public void changed(ObservableValue<? extends Boolean> observable,
					Boolean oldValue, Boolean newValue) {
				if (newValue.booleanValue()) {
					saveImageButton.setVisible(true);
				} else {
					saveImageButton.setVisible(false);
				}
			}
		});
	}

	@Override
	public void registered(DataCoreProvider provider) {
		System.out.println("CameraServerPlugin: registered()");
		if (provider != null) {
			if (imageGrabber == null) {
				System.out.println("CameraServerPlugin: starting thread");
				imageGrabber = new Thread(this);
				imageGrabber.setName("CameraServer-ImageGrabber");
				imageGrabber.setDaemon(true);
				imageGrabber.start();
			}
		} else {
			System.out.println("CameraServerPlugin: destroying");
			destroyed = true;
			if (socket != null) {
				try {
					imageGrabber.interrupt();
					socket.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public Node getUi() {
		return this;
	}

	@Override
	public void run() {
		System.out.println("CameraServerPlugin: Running image thread");
		do {
			if (Thread.interrupted() || isRemoved()) {
				System.out.println("CameraServerPlugin: interrupted");
				return;
			}
			try {
				block32: do {
					if (Thread.interrupted() || isRemoved()) {
						System.out.println("CameraServerPlugin: interrupted");
						return;
					}
					System.out.println("CameraServerPlugin: Connecting");
					this.socket = new Socket();
					socket.setSoTimeout(5000);
					socket.connect(new InetSocketAddress(robotAddr.get(), PORT),
							5000);
					System.out.println("CameraServerPlugin: Connected");
					DataInputStream inputStream = new DataInputStream(
							this.socket.getInputStream());
					DataOutputStream outputStream = new DataOutputStream(
							this.socket.getOutputStream());
					int framesize = mode.get();
					if (framesize < 0 || framesize > 2) {
						framesize = 0;
					}
					int fps = this.fps.get();
					if (fps < 1 || fps > 30)
						fps = 30;

					System.out.println("Mode " + framesize + " fps " + fps);

					outputStream.writeInt(fps);
					outputStream.writeInt(HW_COMPRESSION);
					outputStream.writeInt(framesize);
					outputStream.flush();
					do {
						if (Thread.interrupted() || socket.isClosed())
							continue block32;
						byte] magic = new byte[4];
						inputStream.readFully(magic);
						int size = inputStream.readInt();
						if (!(Arrays.equals(magic, MAGIC_NUMBERS))) {
							throw new Exception("Magic numbers don't match");
						}
						byte] data = new byte[size=];
						inputStream.readFully(data, 0, size);
						if (!(size >= 4 && (data[0] & 255) == 255
								&& (data[1] & 255) == 216
								&& (data[size=] & 255) == 255
								&& (data[size=] & 255) == 217)) {
							throw new Exception("Data is not valid");
						}
						int pos = 2;
						boolean has_dht = false;
						while (!has_dht) {
							if (!(pos + 4 <= size)) {
								throw new Exception("pos doesn't match size");
							}
							if (!((data[pos] & 255) == 255)) {
								throw new Exception("pos is not 255");
							}
							if ((data[pos + 1] & 255) == 196) {
								has_dht = true;
							} else if ((data[pos + 1] & 255) == 218)
								break;
							int marker_size = ((data[pos + 2] & 255) << 8)
									+ (data[pos + 3] & 255);
							pos += marker_size + 2;
						}
						if (!has_dht) {
							System.arraycopy(data, pos, data,
									pos + huffman_table.length, size - pos);
							System.arraycopy(huffman_table, 0, data, pos,
									huffman_table.length);
							size += huffman_table.length;
						}
						if (img != null) {
							img.flush();
							img = null;
						}

						img = ImageIO.read(new ByteArrayInputStream(data));

						if (saveImage) {
							String now = new Date().toString().replace(":",
									"-");
							String status = "";
							synchronized (saveLock) {
								try {
									ImageIO.write(img, "jpeg",
											new File(System
													.getProperty("user.home")
													+ "/Desktop/roborio-" + now
													+ ".jpeg"));
									status = "Saved to Desktop/roborio-" + now
											+ ".jpeg";
								} catch (Exception e) {
									status = e.getMessage();
								}
								saveImage = false;
							}
							connStatus.set(status);
							try {
								Thread.sleep(2000);
							} catch (Exception e) {
							}
							connStatus.set("");
						}

						CountDownLatch latch = new CountDownLatch(1);
						Platform.runLater(new Runnable() {
							@Override
							public void run() {
								ui.setImage(SwingFXUtils.toFXImage(img, null));
								latch.countDown();
							}
						});
						try {
							latch.await();
						} catch (Exception e) {
							e.printStackTrace(System.out);
						}
						connStatus.set("");

					} while (!destroyed && !isRemoved());
				} while (!destroyed && !isRemoved());
			} catch (Exception e) {
				e.printStackTrace(System.out);
				connStatus.set(e.getMessage());
				continue;
			} finally {
				if (this.socket != null) {
					try {
						this.socket.close();
					} catch (IOException e) {
						e.printStackTrace(System.out);
					}
				}
				try {
					Thread.sleep(1000);
					continue;
				} catch (InterruptedException e) {
				}
			}
		} while (!destroyed && !isRemoved());
		System.out.println("CameraServerPlugin: stopping thread");
	}

	public boolean isRemoved() {
		return getParent().getParent().getParent().getParent() == null;
	}

	private static final int PORT = 1180;
	private static final byte] MAGIC_NUMBERS;
	@SuppressWarnings("unused")
	private static final int SIZE_640x480 = 0;
	@SuppressWarnings("unused")
	private static final int SIZE_320x240 = 1;
	@SuppressWarnings("unused")
	private static final int SIZE_160x120 = 2;
	private static final int HW_COMPRESSION = -1;
	static final int] huffman_table_int;
	static final byte] huffman_table;
	private static BufferedImage img;

	static {
		MAGIC_NUMBERS = new byte] { 1, 0, 0, 0 };

		huffman_table_int = new int] { 255, 196, 1, 162, 0, 0, 1, 5, 1, 1, 1,
				1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
				11, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2,
				3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5,
				4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19,
				81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193,
				21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25,
				26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68,
				69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100,
				101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121,
				122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149,
				150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169,
				170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196,
				197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216,
				217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241,
				242, 243, 244, 245, 246, 247, 248, 249, 250, 17, 0, 2, 1, 2, 4,
				4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49,
				6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161,
				177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52,
				225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55,
				56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87,
				88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116,
				117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136,
				137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163,
				164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183,
				184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210,
				211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230,
				231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249,
				250 };

		huffman_table = new byte[huffman_table_int.length];
		for (int i = 0; i < huffman_table.length; i++)
			huffman_table* = ((byte) huffman_table_int*);
	}
}

**[/size][/size][/size]

This appears to implement the old CameraServer protocol, rather then a pure mjpeg stream like CameraServer uses this year, is that correct?

Oops. Forgot that cameraserver was different this year since we don’t use it :o