/* * Copyright 2014-2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.supercanvas.components.pinned; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.util.Collection; import ccre.cluck.Cluck; import ccre.cluck.tcp.CluckTCPClient; import ccre.cluck.tcp.TracingCluckTCPClient; import ccre.net.Network; import ccre.net.TrafficCounting; import ccre.supercanvas.Rendering; import ccre.supercanvas.SuperCanvasComponent; import ccre.supercanvas.SuperCanvasPanel; /** * A component that displays the current results from the * CountingNetworkProvider and allows control of the remote IP address. * * @author skeggsc */ public class CluckNetworkingComponent extends SuperCanvasComponent { private static final long serialVersionUID = 8969267415884377303L; /** * The remote address that represents that no connection is wanted. */ public static final String DO_NOT_CONNECT = ":"; private static final String[] optionNames = new String[] { "roboRIO (default)", "Local (default)", "roboRIO (2015)", "roboRIO (USB)", "roboRIO (non-FMS)", "roboRIO (alternate 1)", "roboRIO (alternate 2)", "Local (alternate 1)", "Local (alternate 2)", "Don't Connect" }; private static final String[] optionAddrs = new String[] { "roboRIO-$T$E$A$M-FRC.local:5800", "127.0.0.1:1540", "roboRIO-$T$E$A$M.local:5800", "172.22.11.2:1540", "roboRIO-$T$E$A$M-FRC.local:1540", "roboRIO-$T$E$A$M-FRC.local:5805", "roboRIO-$T$E$A$M-FRC.local:1735", "127.0.0.1:80", "127.0.0.1:443", DO_NOT_CONNECT }; private final StringBuilder address = new StringBuilder(optionAddrs[0]); private boolean expanded = false; private transient CluckTCPClient client; /** * Create a new CluckNetworkingComponent. */ public CluckNetworkingComponent() { } /** * Create a new CluckNetworkingComponent with a specified remote address. * * @param address the default address. */ public CluckNetworkingComponent(String address) { this.address.setLength(0); this.address.append(address); } private int firstMenuEntry = 0, menuEntryDelta = 1; @Override public void render(Graphics2D g, int screenWidth, int screenHeight, FontMetrics fontMetrics, int mouseX, int mouseY) { if (expanded) { String raddr = address.toString(); if (System.currentTimeMillis() % 1000 < 500 && getPanel().editing == address) { raddr += "|"; } String[] lines = new String[3 + optionNames.length]; // Three header lines: lines[0] = "Type or select from list"; lines[1] = raddr; lines[2] = getStatusMessage(); System.arraycopy(optionNames, 0, lines, 3, optionNames.length); menuEntryDelta = fontMetrics.getHeight(); int height = menuEntryDelta * lines.length + 10; Rendering.drawBody(Color.GRAY, g, screenWidth - 100, height / 2, 200, height); for (int i = 0; i < lines.length; i++) { if (i >= 3 && mouseX >= screenWidth - 200 && mouseY >= menuEntryDelta * i && mouseY < menuEntryDelta * (i + 1)) { g.setColor(Color.CYAN); } else if (i >= 3 && optionAddrs[i - 3].equals(address.toString())) { g.setColor(Color.GREEN); } else { g.setColor(Color.WHITE); } g.drawString(lines[i], screenWidth - 195, fontMetrics.getAscent() + fontMetrics.getHeight() * i); } firstMenuEntry = menuEntryDelta * 3; // after the three header lines } else { if (getPanel().editmode) { g.setColor(contains(mouseX, mouseY) ? Color.CYAN : Color.WHITE); } else { g.setColor(contains(mouseX, mouseY) ? Color.GREEN : Color.BLACK); } StringBuilder sb = new StringBuilder(getStatusMessage()); String summ = client == null ? null : client.getErrorSummary(); if (summ != null) { sb.append(" (").append(summ).append(')'); } sb.append(" ~").append(TrafficCounting.getRateBytesPerSecond() / 128).append("kbs/s"); String countReport = sb.toString(); g.drawString(countReport, screenWidth - fontMetrics.stringWidth(countReport), fontMetrics.getAscent()); } } private synchronized String getStatusMessage() { if (client == null) { return "not ready"; } else if (client.isReconnecting()) { if (client.isEstablished()) { return "establishing..."; } else { return "connecting to " + client.getRemote() + "..."; } } else if (client.isEstablished()) { return "active"; } else { float pause_remain = (int) ((client.getReconnectDeadline() - System.currentTimeMillis()) / 100f) / 10f; if (pause_remain <= 0) { return "about to reconnect"; } else { return "pausing for " + pause_remain + "s"; } } } @Override protected void onChangePanel(SuperCanvasPanel panel) { updateConnection(); } /** * Should the Cluck connection log its traffic data? */ public static boolean useLoggingConnection = false; private synchronized void updateConnection() { String remote = calculateRemote(); if (getPanel() == null || remote == null) { if (client != null) { client.terminate(); client = null; } } else if (client == null) { client = useLoggingConnection ? new TracingCluckTCPClient(remote, Cluck.getNode(), "robot", null) : new CluckTCPClient(remote, Cluck.getNode(), "robot", null); client.setReconnectDelay(1000); client.setLogDuringNormalOperation(false); client.start(); } else { if (!client.getRemote().equals(remote)) { client.setRemote(remote); } } } @Override public void onPressedEnter() { updateConnection(); } private String calculateRemote() { if (address.toString().equals(DO_NOT_CONNECT)) { // don't connect return null; } char T = '?', E = '?', A = '?', M = '?'; Collection<String> addresses = Network.listIPv4Addresses(); for (String addr : addresses) { String[] spt = addr.split("[.]"); if (spt.length == 4 && spt[0].equals("10")) { try { int prefix = Integer.parseInt(spt[1]); int suffix = Integer.parseInt(spt[2]); if (prefix >= 0 && prefix < 100 && suffix > 0 && suffix < 100) { T = (char) ((prefix / 10) + '0'); E = (char) ((prefix % 10) + '0'); A = (char) ((suffix / 10) + '0'); M = (char) ((suffix % 10) + '0'); break; } } catch (NumberFormatException e) { // Do nothing. } } } if (address.toString().contains("$T") && T == '?') { return null; } return address.toString().replace("$T", Character.toString(T)).replace("$E", Character.toString(E)).replace("$A", Character.toString(A)).replace("$M", Character.toString(M)); } @Override public boolean contains(int x, int y) { if (expanded) { return x >= getPanel().getWidth() - 200 && y <= (firstMenuEntry + menuEntryDelta * optionAddrs.length); } else { return x >= getPanel().getWidth() - 100 && y <= 18; } } @Override public boolean onInteract(int x, int y) { return onSelect(x, y); } @Override public boolean onSelect(int x, int y) { if (y >= firstMenuEntry && expanded) { int menuId = (y - firstMenuEntry) / menuEntryDelta; if (menuId >= 0 && menuId < optionAddrs.length) { address.setLength(0); address.append(optionAddrs[menuId]); updateConnection(); return true; } } expanded = !expanded; if (expanded) { getPanel().editing = address; } else if (getPanel().editing == address) { getPanel().editing = null; } getPanel().raise(this); return true; } }