Initialisation depot

This commit is contained in:
Serge NOEL
2026-02-10 11:05:54 +01:00
commit 549c9f388e
55 changed files with 13984 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER
// COPYRIGHT (c) 2013-2015 Gregg E. Berman
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
//
//////////////////////////////////////////////////////////////////////////
//
// DCC++ CONTROLLER is a Java program written using the 64-bit Processing Library
// and Processing IDE (version 3.01).
//
// DCC++ CONTROLLER provides users with a fully customizeable graphical
// front end for the total control of model trains and model train layouts
// via its companion program, DCC++ BASE STATION.
//
// DCC++ BASE STATION allows a standard Arduino Uno with an Arduino Motor Shield
// to be used as a fully-functioning digital command and control (DCC) base station
// for controlling model train layouts that conform to current National Model
// Railroad Association (NMRA) DCC standards.
//
// DCC++ CONTROLLER communicates with DCC++ BASE STATION using simple text commands sent
// via a standard USB Serial Cord at speeds of up to 115200 Baud. A Bluetooth Wireless
// Connection may be used in place of a USB Serial Cord without any software modification.
//
// This version of DCC++ CONTROLLER supports:
//
// * Multi-Cab / Multi-Throttle configurations using 128-step speed control
// * 2-byte and 4-byte cab addresses
// * Customizable cab function buttons F0-F12
// * User-created multi-layout track plan
// * Customizeable turnouts and crossovers with controls integrated into track plan
// * Customizeable routes with configurable buttons
// * Customizeable routes with route buttons integrated into track plan
// * Master Power Button
// * Customizable key-controls
// * Real-time current monitor
// * Optional track-integrated sensors
// * Optional user-created Auto Pilot routines (when used with track-integrated sensors)
// * Manual activation/de-activation of accessory functions using 512 addresses, each with 4 sub-addresses
// * Programming on the Main Operations Track
// - write configuration variable bytes
// - set/clear specific configuration variable bits
// * Programming on the Programming Track
// - write configuration variable bytes
// - read configuration variable bytes
//
// With the exception of a standard 15V power supply for the Arduino Uno that can
// be purchased in any electronics store, no additional hardware is required.
//
// Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or
// commercial hardware, software, interfaces, specifications, or methods related
// to the control of model trains using NMRA DCC standards. Both programs are wholly
// original, developed by the author, and are not derived from any known commercial,
// free, or open-source model railroad control packages by any other parties.
//
// However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and
// embedded libraries associated with Arduino and Processing. Tremendous thanks to those
// responsible for these terrific open-source initiatives that enable programs like
// DCC++ to be developed and distributed in the same fashion.
//
// REFERENCES:
//
// NMRA DCC Standards: http://www.nmra.org/standards/DCC/standards_rps/DCCStds.html
// Arduino: http://www.arduino.cc/
// Processing: http://processing.org/
// GNU General Public License: http://opensource.org/licenses/GPL-3.0
//
//////////////////////////////////////////////////////////////////////////
import processing.serial.*;
import processing.net.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.*;
final String CONTROLLER_VERSION = "3.0";
final int BASE_BAUD = 115200;
final int SCREEN_WIDTH = 1366;
final int SCREEN_HEIGHT = 768;
final String STATUS_FILE = "dccStatus.xml";
//////////////////////////////////////////////////////////////////////////
void settings(){
size(SCREEN_WIDTH,SCREEN_HEIGHT);
}
//////////////////////////////////////////////////////////////////////////
void setup(){
Initialize();
}
//////////////////////////////////////////////////////////////////////////
void draw(){
background(backgroundColor);
for(DccComponent dcc : dccComponents)
dcc.display();
if(frameCount==1) // if this is the first frame, just display components and return (otherwise user stare at a blank screen while serial is opening
return;
if(frameCount==2) // is this is the second frame, open the serial port --- screen will have already been displayed in prior frame
aPort.open(arduinoPortXML.getContent());
for(int i=buttonQueue2.size()-1;i>=0;i--){
buttonQueue2.get(i).init();
buttonQueue2.remove(i);
}
for(int i=buttonQueue.size()-1;i>=0;i--){
buttonQueue2.add(buttonQueue.get(i));;
buttonQueue.remove(i);
}
if(!mousePressed){
cursorType=ARROW;
previousComponent=selectedComponent;
selectedComponent=null;
int nComponents = dccComponents.size();
for(int i=nComponents-1;i>=0;i--)
dccComponents.get(i).check();
cursor(cursorType);
}
int m=millis();
if(m-lastTime>250 && aPort!=null && currentMeter.isOn){
lastTime=m;
aPort.write("<c>");
}
msgBoxClock.setMessage(nf(hour(),2)+":"+nf(minute(),2)+":"+nf(second(),2));
if(saveXMLFlag){
try{
saveXML(dccStatusXML,STATUS_FILE);
saveXMLFlag=false;
} catch(Exception e){
println("Couldn't save. Will retry");
}
}
autoPilot.safetyCheck();
} // draw
//////////////////////////////////////////////////////////////////////////
abstract class DccComponent{
Window window=null;
int xPos, yPos;
String componentName="NAME NOT DEFINED";
abstract void display();
void check(){};
void pressed(){};
void rightClick(){};
void shiftPressed(){};
void released(){};
void drag(){};
void init(){};
protected int xWindow(){
if(window==null)
return 0;
return window.xPos;
}
protected int yWindow(){
if(window==null)
return 0;
return window.yPos;
}
}
//////////////////////////////////////////////////////////////////////////
interface CallBack{
void execute(int n, String c);
}
//////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,491 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Core Components
//
// PowerButton - send power on/off command to the DCC++ Base Station
//
// CurrentMeter - monitors main track current draw from the DCC++ Base Station
// - displays scrolling bar chart of current measured
//
// HelpButton - toggles Help Window
//
// QuitButton - quits DCC++ Controller
// - connection to DCC++ Base Station terminated
// - NOTE: track power remains on and trains will continue to operate
// since DCC+ Base Station operates independently!
//
// AccessoryButton - sends a DCC ACCESSORY COMMAND to the DCC++ Base Station
// to either activate or de-activate an accessory depending on
// whether the button is labeled "ON" or "OFF"
// - two pre-specified input boxes are used: one for the user
// to input the desired accessory address, and one for
// accessory number (sub-address)
// - the default configuration of DCC++ Controller defines an
// Accessory Window that includes these two input boxes as well
// as ON and OFF buttons.
//
// CleaningCarButton - sends a DCC THROTTLE COMMAND to the DCC++ Base Station that operates
// a mobile decoder with a pre-specified cab number
// - this decoder drives a motor that spins a cleaning pad in a
// track-cleaning car
// - clicking the button toggles the throttle between either 0 or 126 (max speed)
// - the default configuration of DCC++ Controller defines an
// Extras Window that includes this button
//
// LEDColorButton - provide for interactive control of an LED-RGB Light Strip
//////////////////////////////////////////////////////////////////////////
// DCC Component: PowerButton
//////////////////////////////////////////////////////////////////////////
class PowerButton extends RectButton{
PowerButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText);
}
PowerButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL);
} // PowerButton
//////////////////////////////////////////////////////////////////////////
void turnOn(){
aPort.write("<1>");
}
//////////////////////////////////////////////////////////////////////////
void shiftPressed(){
aPort.write("<Z 1 0>");
exit();
}
//////////////////////////////////////////////////////////////////////////
void turnOff(){
aPort.write("<0>");
}
} // PowerButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: CurrentMeter
//////////////////////////////////////////////////////////////////////////
class CurrentMeter extends DccComponent{
int nSamples, kHeight;
int maxCurrent;
int[] samples;
int sampleIndex;
int nGridLines;
boolean isOn;
CurrentMeter(int xPos, int yPos, int nSamples, int kHeight, int maxCurrent, int nGridLines){
this.xPos=xPos;
this.yPos=yPos;
this.nSamples=nSamples;
this.kHeight=kHeight;
this.maxCurrent=maxCurrent;
this.nGridLines=nGridLines;
this.isOn=true;
samples=new int[nSamples];
sampleIndex=nSamples-1;
dccComponents.add(this);
} // CurrentMeter
//////////////////////////////////////////////////////////////////////////
void display(){
int i;
rectMode(CORNER);
noFill();
strokeWeight(1);
textFont(buttonFont,8);
textAlign(LEFT,CENTER);
stroke(200);
rect(xPos,yPos,nSamples+1,kHeight+2);
if(isOn)
stroke(50,200,100);
else
stroke(200,100,100);
for(i=0;i<nSamples;i++){
line(xPos+1+i,yPos+kHeight+1,xPos+1+i,yPos+kHeight+1-samples[(sampleIndex+i)%nSamples]*kHeight/maxCurrent);
}
stroke(200);
for(i=1;i<nGridLines;i++){
line(xPos+1,yPos+kHeight+1-kHeight*i/nGridLines,xPos+1+nSamples,yPos+kHeight+1-kHeight*i/nGridLines);
}
fill(255);
for(i=0;i<=nGridLines;i++){
text(nf(i*2000/nGridLines,0)+" mA",xPos+10+nSamples,yPos+kHeight+1-kHeight*i/nGridLines);
}
} // display
//////////////////////////////////////////////////////////////////////////
void addSample(int s){
samples[sampleIndex]=s;
sampleIndex=(sampleIndex+1)%nSamples;
}
} // CurrentMeter Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: AccessoryButton
//////////////////////////////////////////////////////////////////////////
class AccessoryButton extends EllipseButton{
InputBox accAddInput, accSubAddInput;
AccessoryButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox accAddInput, InputBox accSubAddInput){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, accAddInput, accSubAddInput);
}
AccessoryButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox accAddInput, InputBox accSubAddInput){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.accAddInput=accAddInput;
this.accSubAddInput=accSubAddInput;
} // AccessoryButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
int accAddress=accAddInput.getIntValue();
int accSubAddress=accSubAddInput.getIntValue();
if(accAddress>511)
msgBoxMain.setMessage("Error - Accessory Address must be in range 0-511",color(255,30,30));
else if(accSubAddress>3)
msgBoxMain.setMessage("Error - Accessory Sub Address must be in range 0-3",color(255,30,30));
else
aPort.write("<a"+accAddress+" "+accSubAddress+" "+(bText.equals("ON")?1:0)+">");
}
} // AccessoryButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: Quit Button
//////////////////////////////////////////////////////////////////////////
class QuitButton extends RectButton{
QuitButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText);
}
QuitButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.NORMAL);
} // PowerButton
//////////////////////////////////////////////////////////////////////////
void turnOn(){
super.turnOn();
exit();
}
} // QuitButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: Help Button
//////////////////////////////////////////////////////////////////////////
class HelpButton extends EllipseButton{
HelpButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText);
}
HelpButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
} // PowerButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
helpWindow.toggle();
}
} // HelpButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: CleaningCar Button
//////////////////////////////////////////////////////////////////////////
class CleaningCarButton extends RectButton{
int cab;
int reg;
CleaningCarButton(int cab, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
this(null, cab, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText);
}
CleaningCarButton(Window window, int cab, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL);
reg=cabButtons.size()+1;
this.cab=cab;
} // PowerButton
//////////////////////////////////////////////////////////////////////////
void turnOn(){
super.turnOn();
aPort.write("<t"+reg+" "+cab+" 126 1>");
}
//////////////////////////////////////////////////////////////////////////
void turnOff(){
super.turnOff();
aPort.write("<t"+reg+" "+cab+" 0 1>");
}
//////////////////////////////////////////////////////////////////////////
void shiftPressed(){
autoPilot.clean();
}
} // CleaningCarButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: LED Color Button
//////////////////////////////////////////////////////////////////////////
class LEDColorButton extends DccComponent{
int bWidth, bHeight;
float hue;
float sat;
float val;
LEDColorButton(Window window, int xPos, int yPos, int bWidth, int bHeight, float hue, float sat, float val){
this.xPos=xPos;
this.yPos=yPos;
this.bWidth=bWidth;
this.bHeight=bHeight;
this.hue=hue;
this.sat=sat;
this.val=val;
this.window=window;
window.windowComponents.add(this);
}
//////////////////////////////////////////////////////////////////////////
void display(){
rectMode(CENTER);
colorMode(HSB,1.0,1.0,1.0);
fill(hue,sat,val);
rect(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight);
colorMode(RGB,255);
}
//////////////////////////////////////////////////////////////////////////
void update(int s){
color c;
colorMode(HSB,1.0,1.0,1.0);
c=color(hue,sat,val);
colorMode(RGB,255);
aPort.write("<G RGB "+int(red(c))+" "+int(green(c))+" "+int(blue(c))+" "+s+">");
ledHueMsg.setMessage("Hue: "+int(hue*360),color(200,200,200));
ledSatMsg.setMessage("Sat: "+int(sat*100),color(200,200,200));
ledValMsg.setMessage("Val: "+int(val*100),color(200,200,200));
ledRedMsg.setMessage("Red: "+int(red(c)),color(200,200,200));
ledGreenMsg.setMessage("Green: "+int(green(c)),color(200,200,200));
ledBlueMsg.setMessage("Blue: "+int(blue(c)),color(200,200,200));
}
} // LEDColorButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: LED Value Selector
//////////////////////////////////////////////////////////////////////////
class LEDValSelector extends DccComponent{
int bWidth, bHeight;
LEDColorButton cButton;
PImage valBox;
LEDValSelector(Window window, int xPos, int yPos, int bWidth, int bHeight, LEDColorButton cButton){
this.xPos=xPos;
this.yPos=yPos;
this.bWidth=bWidth;
this.bHeight=bHeight;
this.cButton=cButton;
valBox = createImage(bWidth+1,bHeight+1,RGB);
this.window=window;
window.windowComponents.add(this);
colorMode(HSB,1.0,1.0,1.0);
valBox.loadPixels();
for(int y=0;y<valBox.height;y++){
for(int x=0;x<valBox.width;x++){
valBox.pixels[x+y*valBox.width]=color(0,0,float(x)/float(bWidth)); // since x will be maximum at width of box, normalize by bWidth which is one less than box width to ensure max brightness is 1.0
}
}
valBox.updatePixels();
colorMode(RGB,255);
}
//////////////////////////////////////////////////////////////////////////
void display(){
imageMode(CORNER);
colorMode(HSB,1.0,1.0,1.0);
tint(cButton.hue,cButton.sat,1.0);
image(valBox,xPos+xWindow(),yPos+yWindow());
noTint();
fill(0.0,0.0,1.0);
noStroke();
pushMatrix();
translate(xPos+xWindow()+cButton.val*float(bWidth),yPos+yWindow()-2);
triangle(0,0,-5,-10,5,-10);
translate(0,bHeight+4);
triangle(0,0,-5,10,5,10);
rectMode(CORNER);
rect(-5,10,10,10);
fill(0,0,0);
triangle(0,15,-5,20,5,20);
popMatrix();
colorMode(RGB,255);
}
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && mouseX>=xPos+xWindow()+cButton.val*float(bWidth)-5 && mouseX<=xPos+xWindow()+cButton.val*float(bWidth)+5 && mouseY>=yPos+yWindow()+bHeight+2 && mouseY<=yPos+yWindow()+bHeight+22){
cursorType=HAND;
selectedComponent=this;
}
}
//////////////////////////////////////////////////////////////////////////
void drag(){
cButton.val=constrain(float(mouseX-xPos-xWindow())/bWidth,0.0,1.0);
cButton.update(0);
}
//////////////////////////////////////////////////////////////////////////
void released(){
cButton.update(1);
}
} // LEDValSelector Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: LED Color Selector
//////////////////////////////////////////////////////////////////////////
class LEDColorSelector extends DccComponent{
PImage colorWheel;
int radius;
LEDColorButton cButton;
LEDColorSelector(Window window, int xPos, int yPos, int radius, LEDColorButton cButton){
float d, h;
this.xPos=xPos;
this.yPos=yPos;
this.radius=radius;
this.cButton=cButton;
colorWheel=createImage(radius*2+1,radius*2+1,RGB);
this.window=window;
window.windowComponents.add(this);
colorWheel.loadPixels();
colorMode(HSB,1.0,1.0,1.0);
for(int i=0, y=radius;y>=-radius;y--){
for(int x=-radius;x<=radius;x++){
d=sqrt(x*x+y*y);
if(d<0.5){
colorWheel.pixels[i]=color(0.0,0.0,1.0); // center of wheel always has zero saturation (hue does not matter)
} else
if(d>radius){
colorWheel.pixels[i]=color(0.0,0.0,0.0); // outside of wheel is always fully black (hue and saturation does not matter)
} else {
h=acos(float(x)/d); // find angle in radians
if(y<0) // adjust angle to reflect lower half of wheel
h=TWO_PI-h;
colorWheel.pixels[i]=color(h/TWO_PI,d/float(radius),1.0); // hue is based on angle normalized to 1.0, saturation is based on distance to center normalized to 1.0, brightness is always 1.0
}
i++;
} // x-loop
} // y-loop
colorMode(RGB,255);
colorWheel.updatePixels();
}
//////////////////////////////////////////////////////////////////////////
void display(){
imageMode(CENTER);
colorMode(HSB,1.0,1.0,1.0);
image(colorWheel,xPos+xWindow(),yPos+yWindow());
colorMode(RGB,255);
}
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && ((pow(mouseX-xPos-xWindow(),2)+pow(mouseY-yPos-yWindow(),2))<=pow(radius,2))){
cursorType=CROSS;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
drag();
}
//////////////////////////////////////////////////////////////////////////
void drag(){
float d,h;
color selectedColor;
d=sqrt(pow(mouseX-xPos-xWindow(),2)+pow(mouseY-yPos-yWindow(),2));
if(d<0.5){
h=0.0;
} else {
h=acos(float(mouseX-xPos-xWindow())/d);
if(mouseY>(yPos+yWindow()))
h=TWO_PI-h;
cButton.hue=h/TWO_PI;
cButton.sat=constrain(d/float(radius),0.0,1.0);
}
cButton.update(0);
}
//////////////////////////////////////////////////////////////////////////
void released(){
cButton.update(1);
}
} // LEDColorSelector Class
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,75 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Constants
//////////////////////////////////////////////////////////////////////////
enum ButtonType{
NORMAL,
ONESHOT,
HOLD,
REVERSE,
T_COMMAND,
TI_COMMAND,
Z_COMMAND
}
enum InputType{
BIN ("[01]"),
DEC ("[0-9]"),
HEX ("[A-Fa-f0-9]");
final String regexp;
InputType(String regexp){
this.regexp=regexp;
}
}
enum CabFunction{
F_LIGHT,
R_LIGHT,
D_LIGHT,
BELL,
HORN,
S_HORN
}
enum ThrottleSpeed{
FULL,
SLOW,
STOP,
REVERSE,
REVERSE_SLOW;
static ThrottleSpeed index(String findName){
for(ThrottleSpeed p : ThrottleSpeed.values()){
if(p.name().equals(findName))
return(p);
}
return(null);
}
}
enum AutoProgram{
NONE ("NONE"),
ALL_CABS_RUN ("ALL CABS RUN"),
ALL_CABS_PARK ("ALL CABS PARK"),
SINGLE_CAB_PARK ("SINGLE CAB PARK"),
AUTO_CLEAN ("AUTO CLEAN"),
SINGLE_CAB_RUN ("SINGLE CAB RUN");
String name;
AutoProgram(String name){
this.name=name;
}
static AutoProgram index(String findName){
for(AutoProgram p : AutoProgram.values()){
if(p.name.equals(findName))
return(p);
}
return(null);
}
boolean equals(AutoProgram p){
return(this==p);
}
}

View File

@@ -0,0 +1,528 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Classes for Cab Throttle and Cab Function Controls
//
// Throttle - creates a sliding throttle to set the speed and direction
// of one or more locomotive cabs
// - cabs are selected by clicking any of the cab buttons
// that have been associated with the throttle
// - multiple throttles, each with a distinct set of cab buttons,
// is allowed. It is also possible to define one throttle per
// cab, in which case a visible cab button would not be needed
// since there is nothing to select
// - moving the slider up or down sends a DCC THROTTLE COMMAND to
// the DCC++ Base Station with the cab addres and register number
// specified by the selected can button
// - throttle commands assume mobile decoders are configured for 128-step speed control
// with speeds ranging from a minimum of 0 to a maximum of 126.
// - the throttle command sent to the DCC++ Base Station also codes whether motion
// is forward or backward
// - negative throttle numbers are NOT used to indicate reverse motion
// - a negative throttle number is used to instruct the DCC++ Base Station
// to initiate an immediate emergency stop of the specified cab.
// - this is in contrast to setting the throttle to 0, in which case the
// cab will stop according to any deceleration parameters (which may allow the locomotive
// to coast before stopping)
// - throttle slider can also be controlled with arrows as follows:
//
// * UP ARROW = increase speed by one unit in the forward direction
// (which decreases speed if already moving in the reverse direction)
// * DOWN ARROW = increase speed by one unit in the reverse direction
// (which decreases speed if already moving in the forward direction)
// * LEFT ARROW = set speed to zero (locomotive will coast to stop if configured with deceleration)
// * RIGHT ARROW = emergency stop (locomotive will stop immediately, ignoring any deceleration parameters)
//
// - Note: throttle slider and arrow buttons will not permit single action that causes locomotive
// to stop and then reverse. This allows users to move slider or press arrow keys to slow
// locomotive to zero without worrying about overshooting and reversing direction. Once slider is
// at zero, reclick to start sliding in reverse direction.
//
// CabButton - defines a button to activate a given cab address for a given throttle
// - in addition to the cab address (which can be short or long), the button
// contains:
//
// * informaiton on which register number the DCC++ Base Station
// should use for throttle commands to this cab
// * a data structure indicating which cab functions (lights, horns, etc.)
// are defined for this cab
//
// FunctionButton - sends a CAB FUNCTION COMMMAND to the DCC++ Base Station to
// activate or de-activate any cab function F0-F12
// - function buttons are always associated with a particular throttle, but
// are dynamically configured according to the cab selected
// to be active for that throttle
// - configuration information for each function button is stored in
// a data structure contained within each cab button
// - configuration data includes the name of each button and whether the function
// should:
//
// * be toggled from on to off, or off to on, with each mouse click (e.g. a headlight)
// * be activated upon pressing the mouse button but de-active when the mouse
// button is released (e.g. a horn)
// * be turned on and then immediately off with a single mouse click (e.g. coupler sounds)
//////////////////////////////////////////////////////////////////////////
// DCC Component: Throttle
//////////////////////////////////////////////////////////////////////////
class Throttle extends DccComponent{
final int KMAXPOS=126;
final int KMINPOS=-126;
int kWidth=50;
int kHeight=15;
int sPos,sSpeed;
int kMaxTemp, kMinTemp;
float tScale;
CabButton cabButton=null;
Throttle(int xPos, int yPos, float tScale){
this.xPos=xPos;
this.yPos=yPos;
this.tScale=tScale;
dccComponents.add(this);
} // Throttle
//////////////////////////////////////////////////////////////////////////
void display(){
int i;
rectMode(CENTER);
ellipseMode(CENTER);
strokeWeight(1);
noStroke();
fill(255);
rect(xPos,yPos,kWidth/2.0,(KMAXPOS-KMINPOS)*tScale);
fill(0);
rect(xPos,yPos,kWidth/4.0,(KMAXPOS-KMINPOS)*tScale);
stroke(0);
for(i=0;i<KMAXPOS*tScale;i+=10*tScale)
line(xPos-kWidth/4.0,yPos-i,xPos+kWidth/4.0,yPos-i);
for(i=0;i>KMINPOS*tScale;i-=10*tScale)
line(xPos-kWidth/4.0,yPos-i,xPos+kWidth/4.0,yPos-i);
if(cabButton==null)
return;
noStroke();
for(i=kWidth;i>0;i--){
fill(230-(i*2),230-(i*2),255-(i*3));
ellipse(xPos,yPos-cabButton.speed*tScale,i,i/2);
}
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(cabButton==null)
return;
if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-(yPos-cabButton.speed*tScale))*(mouseY-(yPos-cabButton.speed*tScale))/(kWidth*kWidth/16.0)<=1){
cursorType=HAND;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
sPos=mouseY;
sSpeed=cabButton.speed;
if(sSpeed>0){
kMaxTemp=KMAXPOS;
kMinTemp=0;
}
else if(sSpeed<0){
kMaxTemp=0;
kMinTemp=KMINPOS;
}
else{
kMaxTemp=KMAXPOS;
kMinTemp=KMINPOS;
}
noCursor();
} // pressed
//////////////////////////////////////////////////////////////////////////
void drag(){
int tPos;
tPos=constrain(int((sPos-mouseY)/tScale)+sSpeed,kMinTemp,kMaxTemp);
if(tPos>0)
kMinTemp=0;
else if(tPos<0)
kMaxTemp=0;
cabButton.setThrottle(tPos);
} // drag
//////////////////////////////////////////////////////////////////////////
void keyControl(int m){
int tPos;
if(m==0){ // emergency stop
tPos=0;
cabButton.throttleSpeed=ThrottleSpeed.STOP;
} else {
tPos=constrain(sSpeed+=m,kMinTemp,kMaxTemp);
}
if(tPos>0)
kMinTemp=0;
else if(tPos<0)
kMaxTemp=0;
cabButton.setThrottle(tPos);
} // keyControl
} // Throttle Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: CabButton
//////////////////////////////////////////////////////////////////////////
class CabButton extends RectButton{
int reg, cab;
int speed=0;
String name;
RouteButton sidingRoute;
int sidingSensor=0;
int parkingSensor=0;
XML speedXML, cabDefaultXML;
XML throttleDefaultsXML;
ThrottleSpeed throttleSpeed=ThrottleSpeed.STOP;
Window fbWindow;
ArrayList<Window> windowList = new ArrayList<Window>();
int[] fStatus = new int[29];
HashMap<CabFunction,FunctionButton> functionsHM = new HashMap<CabFunction,FunctionButton>();
Throttle throttle;
PImage cabImage;
String cabFile;
Window editWindow;
InputBox cabNumInput;
CabButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, int cab, Throttle throttle){
super(null, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, str(cab), ButtonType.NORMAL);
this.cab=cab;
this.throttle=throttle;
cabButtons.add(this);
reg=cabButtons.size();
cabFile=("cab-"+cab+".jpg");
cabImage=loadImage(cabFile);
name="Cab"+cab;
cabsHM.put(name,this);
colorMode(HSB,255);
editWindow = new Window(xPos-(bWidth/2),yPos-(bHeight/2),bWidth,bHeight,color(baseHue,255,255),color(baseHue,255,125));
cabNumInput = new InputBox(this);
speedXML=autoPilotXML.getChild(name);
if(speedXML==null){
speedXML=autoPilotXML.addChild(name);
speedXML.setContent(ThrottleSpeed.STOP.name());
}
cabDefaultXML=cabDefaultsXML.getChild(name);
if(cabDefaultXML==null){
cabDefaultXML=cabDefaultsXML.addChild(name);
}
} // CabButton
//////////////////////////////////////////////////////////////////////////
void display(){
super.display();
imageMode(CENTER);
fill(30);
rect(xPos+bWidth/2+30,yPos,42,20);
stroke(backgroundColor);
line(xPos+bWidth/2+23,yPos-10,xPos+bWidth/2+23,yPos+10);
line(xPos+bWidth/2+37,yPos-10,xPos+bWidth/2+37,yPos+10);
textFont(throttleFont,22);
if(speed>0)
fill(0,255,0);
else if(speed<0)
fill(255,0,0);
else
fill(255,255,0);
text(nf(abs(speed),3),xPos+bWidth/2+30,yPos);
} // display
//////////////////////////////////////////////////////////////////////////
void functionButtonWindow(int xPos, int yPos, int kWidth, int kHeight, color backgroundColor, color outlineColor){
if(windowList.size()==1) // there is already one defined window and another is requested -- add a NextFunctionsButton to the original window
new NextFunctionsButton(fbWindow, this, kWidth/2, kHeight+5, 40, 15, 60, 8, "More...");
fbWindow=new Window(xPos,yPos,kWidth,kHeight,backgroundColor,outlineColor);
windowList.add(fbWindow);
if(windowList.size()>1) // there are at least two defined windows -- add a NextFunctionsButton to this window
new NextFunctionsButton(fbWindow, this, kWidth/2, kHeight+5, 40, 15, 60, 8, "More...");
}
//////////////////////////////////////////////////////////////////////////
void setFunction(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, int fNum, String name, ButtonType buttonType, CabFunction ... cFunc){
new FunctionButton(fbWindow,xPos,yPos,bWidth,bHeight,baseHue,fontSize,this,fNum,name,buttonType,cFunc);
}
//////////////////////////////////////////////////////////////////////////
void activateFunction(CabFunction cFunc, boolean s){
if(functionsHM.containsKey(cFunc))
functionsHM.get(cFunc).activateFunction(s);
}
//////////////////////////////////////////////////////////////////////////
void turnOn(){
if(throttle.cabButton!=null){
throttle.cabButton.fbWindow.close();
throttle.cabButton.turnOff();
}
super.turnOn();
fbWindow.show();
throttle.cabButton=this;
opCabInput.setIntValue(cab);
}
//////////////////////////////////////////////////////////////////////////
void turnOff(){
super.turnOff();
fbWindow.close();
throttle.cabButton=null;
}
//////////////////////////////////////////////////////////////////////////
void shiftPressed(){
autoPilot.parkCab(this);
}
//////////////////////////////////////////////////////////////////////////
void rightClick(){
editWindow.open();
}
//////////////////////////////////////////////////////////////////////////
void setThrottle(int tPos){
aPort.write("<t"+reg+" "+cab+" "+abs(tPos)+" "+int(tPos>0)+">");
if(throttleSpeed!=ThrottleSpeed.STOP)
throttleDefaultsXML.setInt(throttleSpeed.name(),tPos);
}
//////////////////////////////////////////////////////////////////////////
void setThrottle(ThrottleSpeed throttleSpeed){
this.throttleSpeed=throttleSpeed;
setThrottle(throttleDefaultsXML.getInt(throttleSpeed.name()));
speedXML.setContent(throttleSpeed.name());
activateFunction(CabFunction.F_LIGHT,true);
activateFunction(CabFunction.R_LIGHT,true);
activateFunction(CabFunction.D_LIGHT,true);
}
//////////////////////////////////////////////////////////////////////////
void setThrottleDefaults(int fullSpeed, int slowSpeed, int reverseSpeed, int reverseSlowSpeed){
throttleDefaultsXML=cabDefaultXML.getChild("throttleDefaults");
if(throttleDefaultsXML==null){
throttleDefaultsXML=cabDefaultXML.addChild("throttleDefaults");
throttleDefaultsXML.setInt(ThrottleSpeed.FULL.name(),fullSpeed);
throttleDefaultsXML.setInt(ThrottleSpeed.SLOW.name(),slowSpeed);
throttleDefaultsXML.setInt(ThrottleSpeed.REVERSE.name(),reverseSpeed);
throttleDefaultsXML.setInt(ThrottleSpeed.REVERSE_SLOW.name(),reverseSlowSpeed);
throttleDefaultsXML.setInt(ThrottleSpeed.STOP.name(),0);
}
}
//////////////////////////////////////////////////////////////////////////
void setSidingDefaults(RouteButton sidingRoute, int parkingSensor, int sidingSensor){
this.sidingRoute=sidingRoute;
this.parkingSensor=parkingSensor;
this.sidingSensor=sidingSensor;
}
//////////////////////////////////////////////////////////////////////////
void stopThrottle(){
aPort.write("<t"+reg+" "+cab+" -1 0>");
throttleSpeed=ThrottleSpeed.STOP;
}
//////////////////////////////////////////////////////////////////////////
String toString(){
return(name);
}
} // CabButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: FunctionButton
//////////////////////////////////////////////////////////////////////////
class FunctionButton extends RectButton{
int fNum;
CabButton cabButton;
String name;
int oneShotCount;
int fPolarity;
FunctionButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, CabButton cabButton, int fNum, String name, ButtonType buttonType, CabFunction[] cFunc){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, name, buttonType);
this.fNum=abs(fNum)%29; // ensures fNum is always between 0 and 28, inclusive
this.cabButton=cabButton;
this.name=name;
for(int i=0;i<cFunc.length;i++)
cabButton.functionsHM.put(cFunc[i],this);
if(buttonType==ButtonType.REVERSE)
this.fPolarity=1;
} // FunctionButton
//////////////////////////////////////////////////////////////////////////
void display(){
if(buttonType==ButtonType.ONESHOT && oneShotCount>0){
oneShotCount--;
isOn=true;
}
else
isOn=(cabButton.fStatus[fNum]!=fPolarity);
super.display();
} // display
//////////////////////////////////////////////////////////////////////////
void turnOn(){
activateFunction(true);
}
//////////////////////////////////////////////////////////////////////////
void turnOff(){
activateFunction(false);
}
//////////////////////////////////////////////////////////////////////////
void released(){
if(buttonType==ButtonType.HOLD)
turnOff();
}
//////////////////////////////////////////////////////////////////////////
void activateFunction(boolean s){
int f=0;
int e=0;
if(s){
cabButton.fStatus[fNum]=1-fPolarity;
if(buttonType==ButtonType.ONESHOT){
fPolarity=1-fPolarity;
oneShotCount=1;
}
} else{
cabButton.fStatus[fNum]=fPolarity;
}
if(fNum<5){ // functions F0-F4 are single byte instructions of form 1-0-0-F0-F4-F3-F2-F1
f=(1<<7)
+(cabButton.fStatus[0]<<4)
+(cabButton.fStatus[4]<<3)
+(cabButton.fStatus[3]<<2)
+(cabButton.fStatus[2]<<1)
+cabButton.fStatus[1];
} else if(fNum<9){ // functions F5-F8 are single byte instructions of form 1-0-1-1-F8-F7-F6-F5
f=(1<<7)
+(1<<5)
+(1<<4)
+(cabButton.fStatus[8]<<3)
+(cabButton.fStatus[7]<<2)
+(cabButton.fStatus[6]<<1)
+cabButton.fStatus[5];
} else if(fNum<13){ // functions F9-F12 are single byte instructions of form 1-0-1-0-F12-F11-F10-F9
f=(1<<7)
+(1<<5)
+(cabButton.fStatus[12]<<3)
+(cabButton.fStatus[11]<<2)
+(cabButton.fStatus[10]<<1)
+cabButton.fStatus[9];
} else if(fNum<21){ // functions F13-F20 are two-byte instructions of form 0xDE followed by F20-F19-F18-F17-F16-F15-F14-F13
f=222; // 0xDE
e=(cabButton.fStatus[20]<<7)
+(cabButton.fStatus[19]<<6)
+(cabButton.fStatus[18]<<5)
+(cabButton.fStatus[17]<<4)
+(cabButton.fStatus[16]<<3)
+(cabButton.fStatus[15]<<2)
+(cabButton.fStatus[14]<<1)
+cabButton.fStatus[13];
} else if(fNum<29){ // functions F21-F28 are two-byte instructions of form 0xDF followed by F28-F27-F26-F25-F24-F23-F22-F21
f=223; // 0xDF
e=(cabButton.fStatus[28]<<7)
+(cabButton.fStatus[27]<<6)
+(cabButton.fStatus[26]<<5)
+(cabButton.fStatus[25]<<4)
+(cabButton.fStatus[24]<<3)
+(cabButton.fStatus[23]<<2)
+(cabButton.fStatus[22]<<1)
+cabButton.fStatus[21];
}
if(fNum<13)
aPort.write("<f"+cabButton.cab+" "+f+">");
else
aPort.write("<f"+cabButton.cab+" "+f+" "+e+">");
} // activateFunction
} // FunctionButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: NextFunctionsButton
//////////////////////////////////////////////////////////////////////////
class NextFunctionsButton extends RectButton{
CabButton cButton;
NextFunctionsButton(Window window, CabButton cButton, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.ONESHOT);
this.cButton=cButton;
} // PowerButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
cButton.fbWindow.close();
cButton.fbWindow=throttleA.cabButton.windowList.get((throttleA.cabButton.windowList.indexOf(throttleA.cabButton.fbWindow)+1)%throttleA.cabButton.windowList.size());
cButton.fbWindow.open();
}
} // NextFunctionsButton Class

View File

@@ -0,0 +1,139 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Class for Route Button
//
// RouteButton - creates a button to activate one or more Track Buttons
// that in turn set one or more TURNOUTS or CROSSOVERS to either an
// open or closed position representing a specific track route
// - tracks may also be added to a route button so that they are highlighted
// on the screen when the route button is first selected
// - track highlights will be color-coded to indicate whether each
// turnout or crossover that in in the route is already set properly,
// or needs to be toggled if that route is activiated
//
// - two types of route buttons are supported:
//
// * large stand-alone button with a text label indicated the name of the route
// * small button placed on a track where the route is obvious and does
// not require a name (such as at the end of a siding)
//
//////////////////////////////////////////////////////////////////////////
class RouteButton extends DccComponent{
int xPos, yPos;
int kWidth, kHeight;
String label="";
boolean routeOn=false;
ArrayList<TrackButton> aTrackButtons = new ArrayList<TrackButton>();
ArrayList<TrackButton> bTrackButtons = new ArrayList<TrackButton>();
ArrayList<Track> rTracks = new ArrayList<Track>();
RouteButton(Track refTrack, int kWidth, int kHeight){
this.xPos=int((refTrack.x[0]+refTrack.x[1])/2.0*refTrack.layout.sFactor+refTrack.layout.xCorner);
this.yPos=int((refTrack.y[0]+refTrack.y[1])/2.0*refTrack.layout.sFactor+refTrack.layout.yCorner);
this.kWidth=kWidth;
this.kHeight=kHeight;
dccComponents.add(this);
}
RouteButton(int xPos, int yPos, int kWidth, int kHeight, String label){
this.xPos=xPos;
this.yPos=yPos;
this.kWidth=kWidth;
this.kHeight=kHeight;
this.label=label;
dccComponents.add(this);
} // RouteButton
//////////////////////////////////////////////////////////////////////////
void addTrackButton(TrackButton trackButton, int tPos){
if(tPos==0){ // specifies that this track button should be set to A when route selected
aTrackButtons.add(trackButton);
trackButton.aRouteButtons.add(this);
} else if (tPos==1) { // specifies that this track button should be set to B when route selected
bTrackButtons.add(trackButton);
trackButton.bRouteButtons.add(this);
}
}
//////////////////////////////////////////////////////////////////////////
void addTrack(Track track){
rTracks.add(track);
}
//////////////////////////////////////////////////////////////////////////
void display(){
if(label.equals("")){
ellipseMode(CENTER);
if(routeOn)
fill(color(0,255,0));
else
fill(color(0,150,0));
noStroke();
ellipse(xPos,yPos,kWidth/2,kHeight/2);
} else{
ellipseMode(CENTER);
if(routeOn)
fill(color(0,200,200));
else
fill(color(0,100,100));
noStroke();
ellipse(xPos,yPos,kWidth,kHeight);
textFont(buttonFont,12);
textAlign(CENTER,CENTER);
fill(color(0));
text(label,xPos,yPos);
}
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){
cursorType=HAND;
selectedComponent=this;
for(Track track : rTracks){
track.hStatus=1;
}
}
else if(previousComponent==this){
for(Track track : rTracks){
track.hStatus=0;
}
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
for(TrackButton trackButton : aTrackButtons){
if(trackButton.rEnabled)
trackButton.pressed(0);
}
for(TrackButton trackButton : bTrackButtons){
if(trackButton.rEnabled)
trackButton.pressed(1);
}
routeOn=true;
} // pressed
//////////////////////////////////////////////////////////////////////////
void shiftPressed(){
for(TrackButton trackButton : aTrackButtons){
if(trackButton.rEnabled)
trackButton.pressed(1);
}
for(TrackButton trackButton : bTrackButtons){
if(trackButton.rEnabled)
trackButton.pressed(0);
}
routeOn=false;
} // shiftPressed
} // RouteButton Class

View File

@@ -0,0 +1,815 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Classes for Sensors and AutoPilot Control
//
// AutoPilot Button - automaticaly operates three cabs in a pattern
// by which each cab travels out to the sky bridge,
// reverses course, and comes back into the inner
// reversing loop after passing through the crossover
// - status is saved between session
// - clicking this button RESUMES a previous session
// or stops the current session
// - resumption implies all cabs, turnouts, and sensors are in
// the exact same position as before the session was halted
// - shift-clicking this button STARTS a new session
// - starting a new seesion assume all cabs are in their start position
// but sensors and turnouts will be automatically reset
//
// TrackSensor - defines a track sensor that triggers when the first car of a train passes, and
// then again when the last car of that same train passes.
// - creates a track sensor button on the track layout where ther sensor is located
// - a given track sensor is defined to be "on" once an initial trigger is received from passage
// of first the car of a train, and defined to be "off" once a second trigger is received from
// passage of last car of that same train
// - if the on/off status of a track sensor button seems out of sync with the actual train,
// user can manually toggle the sensor "on" or "off" by clicking the appropriate sensor button
//
//////////////////////////////////////////////////////////////////////////
class AutoPilotButton extends RectButton{
int[] cabs={8601,6021,1506,622,1202,54}; // list of all cabs to be included in autoPilot - order does not matter since it will be randomized
ArrayList<CabButton> cabList = new ArrayList<CabButton>();
int phase=0;
int tCount=0;
int crossOver=0;
AutoProgram program=AutoProgram.NONE;
XML cabListXML, phaseXML, tCountXML, crossOverXML, programXML;
int safetyTimer;
AutoPilotButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText);
}
AutoPilotButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL);
phaseXML=autoPilotXML.getChild("Phase");
if(phaseXML==null){
phaseXML=autoPilotXML.addChild("Phase");
phaseXML.setContent(str(phase));
} else{
phase=int(phaseXML.getContent());
}
tCountXML=autoPilotXML.getChild("TCount");
if(tCountXML==null){
tCountXML=autoPilotXML.addChild("TCount");
tCountXML.setContent(str(tCount));
} else{
tCount=int(tCountXML.getContent());
}
crossOverXML=autoPilotXML.getChild("CrossOver");
if(crossOverXML==null){
crossOverXML=autoPilotXML.addChild("CrossOver");
crossOverXML.setContent(str(crossOver));
} else{
crossOver=int(crossOverXML.getContent());
}
programXML=autoPilotXML.getChild("Program");
if(programXML==null){
programXML=autoPilotXML.addChild("Program");
programXML.setContent(program.name);
} else{
program=AutoProgram.index(programXML.getContent());
}
cabListXML=autoPilotXML.getChild("CabList");
if(cabListXML==null){
cabListXML=autoPilotXML.addChild("CabList");
cabListXML.setContent(join(nf(cabs,0)," "));
}
for(int i: int(split(trim(cabListXML.getContent())," ")))
cabList.add(cabsHM.get("Cab"+i));
updateDiagBox();
} // AutoButton
//////////////////////////////////////////////////////////////////////////
void display(){
super.display();
textAlign(CENTER,CENTER);
textFont(messageFont,12);
fill(color(255));
text(program.name,xPos+xWindow(),yPos+yWindow()+bHeight/2+10);
}
//////////////////////////////////////////////////////////////////////////
void pressed(){
if(isOn){
turnOff();
return;
}
if(program==AutoProgram.NONE){
msgBoxMain.setMessage("Can't resume Auto Pilot until program specified!",color(50,50,200));
return;
}
for(CabButton cb : cabList) // set throttles of all cabs specified in current program to prior values
cb.setThrottle(ThrottleSpeed.index(cb.speedXML.getContent()));
if(program.equals(AutoProgram.AUTO_CLEAN))
cleaningCab.turnOn();
msgBoxMain.setMessage("Auto Pilot Resuming",color(50,50,200));
safetyTimer=millis();
turnOn();
} // pressed
//////////////////////////////////////////////////////////////////////////
void init(){
phase=0;
tCount=0;
crossOver=0;
for(TrackSensor ts : sensorsHM.values())
ts.reset();
cabList.clear();
for(int i:cabs)
cabList.add(cabsHM.get("Cab"+i));
for(int i=0;i<3;i++) // randomize list
cabList.add(0,cabList.remove(int(random(i,cabList.size()))));
updateCabList();
for(CabButton cb : cabList) // halt all cabs specified in full autopilot program
cb.setThrottle(ThrottleSpeed.STOP);
rButtonReset.pressed();
cabList.get(0).sidingRoute.pressed(); // set siding turnouts so they are aligned for first cab
rButtonSpiral.pressed();
rButton12.pressed();
cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start first cab!
msgBoxMain.setMessage("Auto Pilot Engaged",color(50,50,200));
updateDiagBox();
} // init()
//////////////////////////////////////////////////////////////////////////
void clean(){
if(isOn){
msgBoxMain.setMessage("Auto Pilot already engaged!",color(50,50,200));
return;
}
cleaningCab.turnOn(); // turn on cleaning car
cabList.clear();
cabList.add(cabsHM.get("Cab"+2004)); // assumes cab 2004 is pulling cleaning car
updateCabList();
phase=100;
phaseXML.setContent(str(phase));
tButton10.pressed(0);
for(TrackSensor ts : sensorsHM.values())
ts.reset();
cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start throttle for cab
msgBoxMain.setMessage("Auto Clean Engaged",color(50,50,200));
setProgram(AutoProgram.AUTO_CLEAN);
safetyTimer=millis();
turnOn();
} // clean
//////////////////////////////////////////////////////////////////////////
void parkCab(CabButton selectedCab){
if(selectedCab.parkingSensor==0){
msgBoxMain.setMessage("Auto Park not available for Cab "+selectedCab.cab,color(50,50,200));
return;
}
if(isOn){
msgBoxMain.setMessage("Auto Pilot already engaged!",color(50,50,200));
return;
}
cabList.clear();
cabList.add(selectedCab);
updateCabList();
phase=42;
phaseXML.setContent(str(phase));
cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start throttle for cab selected --- do not modify throttles for any other cabs
msgBoxMain.setMessage("Auto Park Engaged for Cab "+selectedCab.cab,color(50,50,200));
setProgram(AutoProgram.SINGLE_CAB_PARK);
safetyTimer=millis();
turnOn();
} // parkCab
//////////////////////////////////////////////////////////////////////////
void shiftPressed(){
if(!isOn){
setProgram(AutoProgram.ALL_CABS_RUN);
safetyTimer=millis();
turnOn();
msgBoxMain.setMessage("Starting Auto Pilot...",color(50,50,200));
buttonQueue.add(this);
} else
if(program==AutoProgram.ALL_CABS_RUN){
msgBoxMain.setMessage("Switching to Auto Park",color(50,50,200));
setProgram(AutoProgram.ALL_CABS_PARK);
} else{
msgBoxMain.setMessage("Auto Park or other program already engaged!",color(50,50,200));
}
} // shiftPressed
//////////////////////////////////////////////////////////////////////////
void turnOff(){
super.turnOff();
msgBoxMain.setMessage("Auto Pilot Disengaged",color(50,50,200));
for(CabButton cb : cabList) // halt (but without updating XML) all cabs specified in current program only
cb.stopThrottle();
if(program.equals(AutoProgram.AUTO_CLEAN))
cleaningCab.turnOff();
if(program.equals(AutoProgram.SINGLE_CAB_RUN)){
aPort.write("<u>");
setProgram(AutoProgram.NONE);
}
}
//////////////////////////////////////////////////////////////////////////
void updateCabList(){
cabListXML.setContent("");
for(CabButton cb : cabList)
cabListXML.setContent(cabListXML.getContent()+cb.cab+" ");
cabListXML.setContent(trim(cabListXML.getContent()));
}
//////////////////////////////////////////////////////////////////////////
void process(int s, boolean isActive){
int lastPhase;
if(!isOn || program.equals(AutoProgram.SINGLE_CAB_RUN))
return;
if(!isActive)
s=-s;
lastPhase=phase;
switch(s){
case 1:
if(phase==3){
rButtonBridge.pressed();
phase=4;
} else
if(phase==4){
phase=5;
} else
if(phase==5){
phase=6;
} else
if(phase==10){
crossOver++;
if(crossOver==2){
cabList.get(0).stopThrottle();
// cabList.get(0).activateFunction(CabFunction.HORN,true);
// cabList.get(0).activateFunction(CabFunction.HORN,false);
} else{
cabList.get(0).activateFunction(CabFunction.S_HORN,true);
}
} else
if(phase==11){
phase=12;
} else
if(phase==13){
phase=14;
} else
if(phase==40){
tButton20.pressed(0);
phase=41;
}
break;
case -1:
if(phase==2){
tCount++;
} else
if(phase==120){
phase=42;
tButton20.pressed(1);
tButton10.pressed(0);
}
break;
case 2:
if(phase==0){
tButton40.routeDisabled();
cabList.get(1).sidingRoute.pressed();
tButton40.routeEnabled();
cabList.get(1).setThrottle(ThrottleSpeed.FULL);
phase=1;
// } else
// if(phase==10 || phase==11){
// cabList.get(2).setFunction(CabFunction.HORN,false);
} else
if(phase==30){
tButton50.pressed(1);
phase=31;
}
break;
case -2:
if(phase==2){
tCount++;
if(tCount==1)
cabList.get(1).setThrottle(ThrottleSpeed.STOP);
} else
if(phase==9){
tButton30.pressed(1);
tCount++;
} else
if(phase==10){
if(crossOver>0){
crossOver--;
}
if(crossOver==1){
cabList.get(0).setThrottle(ThrottleSpeed.FULL);
// cabList.get(0).activateFunction(CabFunction.S_HORN,true);
}
}
break;
case 3:
if(phase==7){
tButton30.pressed(0);
phase=8;
} else
if(phase==10){
crossOver++;
if(crossOver==2){
cabList.get(2).stopThrottle();
// cabList.get(2).activateFunction(CabFunction.HORN,true);
// cabList.get(2).activateFunction(CabFunction.HORN,false);
} else{
cabList.get(2).activateFunction(CabFunction.S_HORN,true);
}
}
break;
case -3:
if(phase==110){
phase++;
tButton30.pressed(1);
} else
if(phase==111||phase==112){
phase++;
} else
if(phase==113){
phase++;
tButton30.pressed(0);
tButton40.pressed(1);
tButton1.pressed(0);
}
break;
case 4:
if(phase==1){
tButton40.routeDisabled();
cabList.get(2).sidingRoute.pressed();
tButton40.routeEnabled();
cabList.get(2).setThrottle(ThrottleSpeed.FULL);
phase=2;
} else
if(phase==8){
tButton40.pressed(0);
phase=9;
// } else
// if(phase==10){
// cabList.get(0).setFunction(CabFunction.HORN,false);
} else
if(phase==12){
tButton4.pressed(1); // set reversing loop
tButton7.pressed(0);
phase=20; // start "parking" phase, then resume pattern
} else
if((phase==21 || phase==31 || phase==42) && cabList.get(0).parkingSensor==4){
cabList.get(0).setThrottle(ThrottleSpeed.SLOW);
phase++;
} else
if(phase==41){
tButton50.pressed(0);
tButton4.pressed(1); // set reversing loop
tButton7.pressed(0);
phase=42;
}
break;
case -4:
if(phase==10){
if(crossOver>0){
crossOver--;
}
if(crossOver==1){
cabList.get(2).setThrottle(ThrottleSpeed.FULL);
// cabList.get(2).activateFunction(CabFunction.S_HORN,true);
}
cabList.get(1).setThrottle(ThrottleSpeed.FULL); // just in case cab-1 was stopped on bridge
tButton40.pressed(1);
tButton1.pressed(0);
tButton20.pressed(1);
crossOver=0;
phase=11;
} else
if((phase==22 || phase==32 || phase==43) && cabList.get(0).parkingSensor==4){
phase++;
cabList.get(0).setThrottle(ThrottleSpeed.STOP);
cabList.get(0).sidingRoute.shiftPressed();
delay(500);
cabList.get(0).sidingRoute.pressed();
sensorsHM.get(cabList.get(0).sidingSensor).pressed(false);
cabList.get(0).setThrottle(ThrottleSpeed.REVERSE);
}
break;
case 5:
if(phase==6){
cabList.get(0).setThrottle(ThrottleSpeed.SLOW);
} else
if(phase==14){
cabList.get(1).setThrottle(ThrottleSpeed.SLOW);
} else
if(phase==42 && cabList.get(0).parkingSensor==5){
cabList.get(0).setThrottle(ThrottleSpeed.SLOW);
phase++;
}
break;
case -5:
if(phase==6){
cabList.get(0).setThrottle(ThrottleSpeed.STOP);
phase=7;
} else
if(phase==14){
cabList.get(1).setThrottle(ThrottleSpeed.STOP);
cabList.add(cabList.remove(0)); // move cab-0 to end of list
updateCabList();
phase=7; // start next cycle
} else
if(phase==43 && cabList.get(0).parkingSensor==5){
phase++;
cabList.get(0).setThrottle(ThrottleSpeed.STOP);
cabList.get(0).sidingRoute.shiftPressed();
delay(500);
cabList.get(0).sidingRoute.pressed();
sensorsHM.get(cabList.get(0).sidingSensor).pressed(false);
delay(100);
cabList.get(0).setThrottle(ThrottleSpeed.REVERSE);
} else
if(phase==100){
phase++;
tButton10.pressed(1);
tButton30.pressed(1);
tButton50.pressed(1);
tButton4.pressed(0);
tButton20.pressed(0);
} else
if(phase==101||phase==102){
phase++;
} else
if(phase==103){
phase++;
tButton20.pressed(1);
tButton50.pressed(0);
} else
if(phase==104||phase==105){
phase++;
} else
if(phase==106){
phase++;
tButton10.pressed(0);
} else
if(phase==107||phase==108){
phase++;
} else
if(phase==109){
phase++;
tButton20.pressed(0);
tButton30.pressed(0);
}
break;
case 6:
if(phase==10){
cabList.get(1).stopThrottle(); // wait on bridge until cab-0 clears sensor 4
}
break;
case -6:
if(phase==9){
tCount++;
tButton8.pressed();
}
break;
case 7:
case 8:
case 9:
case 10:
case 12:
case 13:
case 14:
if(phase==23 || phase==33 || phase==44){
phase++;
cabList.get(0).setThrottle(ThrottleSpeed.REVERSE_SLOW);
}
break;
case -7:
case -8:
case -9:
case -10:
case -12:
case -13:
case -14:
if(phase==24 || phase==34 || phase==45){
cabList.get(0).setThrottle(ThrottleSpeed.STOP);
sensorsHM.get(cabList.get(0).parkingSensor).pressed(false);
tButton40.pressed(1);
if(program==AutoProgram.SINGLE_CAB_PARK||program==AutoProgram.AUTO_CLEAN){
phase=51; // phase must have previously been 45
turnOff();
} else
if(program==AutoProgram.ALL_CABS_PARK){
cabList.add(0,cabList.remove(2)); // move cab-2 to beginning of list, making it cab-0
updateCabList();
phase+=6; // start parking routine at either phase=30, or if second cab just parked then phase=40, or if third cab finished parking phase=51
if(phase==51){
turnOff();
}
} else{
cabList.add(3,cabList.remove(int(random(3,cabList.size())))); // pick random cab to be next to leave siding
updateCabList();
tButton40.routeDisabled();
cabList.get(3).sidingRoute.pressed();
tButton40.routeEnabled();
cabList.get(3).setThrottle(ThrottleSpeed.FULL);
phase=25;
}
}
break;
case 11:
if(phase==20){
phase=21;
} else
if((phase==21 || phase==31 || phase==42) && cabList.get(0).parkingSensor==11){
cabList.get(0).setThrottle(ThrottleSpeed.SLOW);
phase++;
} else
if(phase==25){
phase=26;
} else
if(phase==26){
phase=13;
}
break;
case -11:
if((phase==22 || phase==32 || phase==43) && cabList.get(0).parkingSensor==11){
phase++;
cabList.get(0).setThrottle(ThrottleSpeed.STOP);
cabList.get(0).sidingRoute.shiftPressed();
delay(500);
cabList.get(0).sidingRoute.pressed();
sensorsHM.get(cabList.get(0).sidingSensor).pressed(false);
delay(100);
cabList.get(0).setThrottle(ThrottleSpeed.REVERSE);
} else
if(phase==114||phase==115){
phase++;
} else
if(phase==116){
phase++;
tButton4.pressed(1);
tButton7.pressed(0);
tButton5.pressed(0);
tButton20.pressed(0);
tButton8.pressed(0);
} else
if(phase==117){
phase++;
tButton40.pressed(0);
} else
if(phase==118||phase==119){
phase++;
}
break;
} // switch t
if(phase==2 && tCount==2){
cabList.get(1).setThrottle(ThrottleSpeed.FULL); // just in case cab-1 was previously stopped to wait for cab-0 to catch up
rButton10.pressed();
rButton11.pressed();
tCount=0;
phase=3;
} else
if(phase==9 && tCount==2){
cabList.get(0).setThrottle(ThrottleSpeed.FULL);
tButton20.pressed(0);
tButton4.pressed(0);
tCount=0;
crossOver=0;
phase=10;
}
phaseXML.setContent(str(phase));
tCountXML.setContent(str(tCount));
crossOverXML.setContent(str(crossOver));
updateDiagBox();
if(phase!=lastPhase) // there was an update of the phase
safetyTimer=millis(); // reset timer
} // process
//////////////////////////////////////////////////////////////////////////
void setProgram(AutoProgram p){
program=p;
programXML.setContent(program.name);
updateDiagBox();
saveXMLFlag=true;
}
//////////////////////////////////////////////////////////////////////////
void updateDiagBox(){
String s="";
for(XML xml: autoPilotXML.getChildren()){
if(!xml.getName().equals("#text"))
s=s+(String.format("%10s",xml.getName())+" = "+xml.getContent()+"\n");
}
msgAutoState.setMessage(s);
} // updateDiagBox
//////////////////////////////////////////////////////////////////////////
void safetyCheck(){
int countDown;
if(!isOn || program.equals(AutoProgram.SINGLE_CAB_RUN))
return;
countDown=120-int((millis()-safetyTimer)/1000);
msgAutoTimer.setMessage("Timer = "+countDown);
if(countDown<=0){
powerButton.turnOff();
turnOff();
}
} // safetyCheck
} // AutoPilot Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: TrackSensor
//////////////////////////////////////////////////////////////////////////
class TrackSensor extends Track{
boolean isActive=false;
boolean sensorDefault;
int xPos, yPos;
int mTime;
int kWidth, kHeight;
String sensorName;
int sensorNum;
XML sensorButtonXML;
MessageBox msgBoxSensor;
TrackSensor(Track refTrack, int trackPoint, float tLength, int kWidth, int kHeight, int sensorNum, boolean sensorDefault){
super(refTrack,trackPoint,tLength);
this.kWidth=kWidth;
this.kHeight=kHeight;
this.xPos=int(x[1]*layout.sFactor+layout.xCorner);
this.yPos=int(y[1]*layout.sFactor+layout.yCorner);
this.sensorNum=sensorNum;
sensorName="Sensor"+sensorNum;
componentName=sensorName;
this.sensorDefault=sensorDefault;
sensorButtonXML=sensorButtonsXML.getChild(sensorName);
if(sensorButtonXML==null){
sensorButtonXML=sensorButtonsXML.addChild(sensorName);
sensorButtonXML.setContent(str(isActive));
} else{
isActive=boolean(sensorButtonXML.getContent());
}
sensorsHM.put(sensorNum,this);
msgBoxSensor=new MessageBox(sensorWindow,0,sensorNum*22+22,-1,0,color(175),18,"S-"+nf(sensorNum,2)+":",color(50,50,250));
}
TrackSensor(Track refTrack, int trackPoint, float curveRadius, float curveAngleDeg, int kWidth, int kHeight, int sensorNum, boolean sensorDefault){
super(refTrack,trackPoint,curveRadius,curveAngleDeg);
this.kWidth=kWidth;
this.kHeight=kHeight;
this.xPos=int(x[1]*layout.sFactor+layout.xCorner);
this.yPos=int(y[1]*layout.sFactor+layout.yCorner);
this.sensorNum=sensorNum;
this.sensorDefault=sensorDefault;
sensorName="Sensor"+sensorNum;
componentName=sensorName;
sensorButtonXML=sensorButtonsXML.getChild(sensorName);
if(sensorButtonXML==null){
sensorButtonXML=sensorButtonsXML.addChild(sensorName);
sensorButtonXML.setContent(str(isActive));
} else{
isActive=boolean(sensorButtonXML.getContent());
}
sensorsHM.put(sensorNum,this);
msgBoxSensor=new MessageBox(sensorWindow,0,sensorNum*22+22,-1,0,color(175),18,"S-"+nf(sensorNum,2)+":",color(50,50,250));
}
//////////////////////////////////////////////////////////////////////////
void display(){
ellipseMode(CENTER);
strokeWeight(1);
stroke(color(255,255,0));
noFill();
if(isActive)
fill(color(50,50,200));
ellipse(xPos,yPos,kWidth/2,kHeight/2);
} // display()
//////////////////////////////////////////////////////////////////////////
void pressed(){
pressed(!isActive);
}
//////////////////////////////////////////////////////////////////////////
void pressed(boolean isActive){
this.isActive=isActive;
autoPilot.process(sensorNum,isActive);
sensorButtonXML.setContent(str(isActive));
saveXMLFlag=true;
if(isActive){
msgBoxSensor.setMessage("S-"+nf(sensorNum,2)+": "+nf(hour(),2)+":"+nf(minute(),2)+":"+nf(second(),2)+" - "+nf((millis()-mTime)/1000.0,0,1)+" sec");
mTime=millis();
}
} // pressed
//////////////////////////////////////////////////////////////////////////
void reset(){
pressed(sensorDefault);
} // reset
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){
cursorType=HAND;
selectedComponent=this;
}
} // check
} // TrackSensor Class

View File

@@ -0,0 +1,264 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Classes for Layouts and Tracks
//
// Layout - defines a scaled region on the screen into which tracks
// will be place using scaled coordinates
//
// Track - defines a curved or straight piece of track.
// - placement on layout can be in absolute scaled coordinates
// or linked to one end of a previously-defined track.
// - tracks can be linked even across separate layouts
// - define multiple overlapping tracks to create any type
// of turnout, crossover, or other complex track
//////////////////////////////////////////////////////////////////////////
class Layout{
int xCorner, yCorner;
float sFactor;
Layout(int xCorner, int yCorner, int frameWidth, float layoutWidth, float layoutHeight){
this.xCorner=xCorner;
this.yCorner=yCorner;
sFactor=float(frameWidth)/layoutWidth; // frameWidth in pixels, layoutWidth in mm, inches, cm, etc.
} // Layout
Layout(Layout layout){
this.xCorner=layout.xCorner;
this.yCorner=layout.yCorner;
this.sFactor=layout.sFactor;
} // Layout
void copy(Layout layout){
this.xCorner=layout.xCorner;
this.yCorner=layout.yCorner;
this.sFactor=layout.sFactor;
} // copy
boolean equals(Layout layout){
return((this.xCorner==layout.xCorner)&&(this.yCorner==layout.yCorner)&&(this.sFactor==layout.sFactor));
} // equals
} // Layout Class
//////////////////////////////////////////////////////////////////////////
class Track extends DccComponent{
float[] x = new float[2];
float[] y = new float[2];
float[] a = new float[2];
color tColor;
float xR, yR;
float r;
float aStart, aEnd;
int tStatus=1; // specfies current track status (0=off/not visible, 1=on/visible)
int hStatus=0; // specifies if current track is highlighted (1) or normal (0)
Layout layout;
Track(Layout layout, float x, float y, float tLength, float angleDeg){
this.x[0]=x;
this.y[0]=y;
this.a[1]=angleDeg/360.0*TWO_PI;
this.a[0]=this.a[1]+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.x[1]=this.x[0]+cos(this.a[1])*tLength;
this.y[1]=this.y[0]-sin(this.a[1])*tLength;
this.layout=layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - straight, absolute
//////////////////////////////////////////////////////////////////////////
Track(Track track, int trackPoint, float tLength, Layout layout){
this.x[0]=track.x[trackPoint%2];
this.y[0]=track.y[trackPoint%2];
this.a[1]=track.a[trackPoint%2];
this.a[0]=this.a[1]+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.x[1]=this.x[0]+cos(this.a[1])*tLength;
this.y[1]=this.y[0]-sin(this.a[1])*tLength;
this.layout=layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - straight, relative, Layout specified
//////////////////////////////////////////////////////////////////////////
Track(Track track, int trackPoint, float tLength){
this.x[0]=track.x[trackPoint%2];
this.y[0]=track.y[trackPoint%2];
this.a[1]=track.a[trackPoint%2];
this.a[0]=this.a[1]+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.x[1]=this.x[0]+cos(this.a[1])*tLength;
this.y[1]=this.y[0]-sin(this.a[1])*tLength;
this.layout=track.layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - straight, relative, no Layout specified
//////////////////////////////////////////////////////////////////////////
Track(Layout layout, float x, float y, float curveRadius, float curveAngleDeg, float angleDeg){
float thetaR, thetaA;
int d;
thetaR=curveAngleDeg/360.0*TWO_PI;
thetaA=angleDeg/360.0*TWO_PI;
d=(thetaR>0)?1:-1;
this.x[0]=x;
this.y[0]=y;
this.a[0]=thetaA+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.a[1]=thetaA+thetaR;
if(this.a[1]>=TWO_PI)
this.a[1]-=TWO_PI;
if(this.a[1]<0)
this.a[1]+=TWO_PI;
this.r=curveRadius;
this.xR=this.x[0]-d*this.r*sin(thetaA);
this.yR=this.y[0]-d*this.r*cos(thetaA);
this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR);
this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR);
if(d==1){
this.aEnd=PI/2-thetaA;
this.aStart=this.aEnd-thetaR;
}else{
this.aStart=1.5*PI-thetaA;
this.aEnd=this.aStart-thetaR;
}
this.layout=layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - curved, absolute
//////////////////////////////////////////////////////////////////////////
Track(Track track, int trackPoint, float curveRadius, float curveAngleDeg, Layout layout){
float thetaR, thetaA;
int d;
thetaR=curveAngleDeg/360.0*TWO_PI;
thetaA=track.a[trackPoint%2];
d=(thetaR>0)?1:-1;
this.x[0]=track.x[trackPoint%2];
this.y[0]=track.y[trackPoint%2];
this.a[0]=thetaA+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.a[1]=thetaA+thetaR;
if(this.a[1]>=TWO_PI)
this.a[1]-=TWO_PI;
if(this.a[1]<0)
this.a[1]+=TWO_PI;
this.r=curveRadius;
this.xR=this.x[0]-d*this.r*sin(thetaA);
this.yR=this.y[0]-d*this.r*cos(thetaA);
this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR);
this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR);
if(d==1){
this.aEnd=PI/2-thetaA;
this.aStart=this.aEnd-thetaR;
}else{
this.aStart=1.5*PI-thetaA;
this.aEnd=this.aStart-thetaR;
}
this.layout=layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - curved, relative, Layout specified
//////////////////////////////////////////////////////////////////////////
Track(Track track, int trackPoint, float curveRadius, float curveAngleDeg){
float thetaR, thetaA;
int d;
thetaR=curveAngleDeg/360.0*TWO_PI;
thetaA=track.a[trackPoint%2];
d=(thetaR>0)?1:-1;
this.x[0]=track.x[trackPoint%2];
this.y[0]=track.y[trackPoint%2];
this.a[0]=thetaA+PI;
if(this.a[0]>=TWO_PI)
this.a[0]-=TWO_PI;
this.a[1]=thetaA+thetaR;
if(this.a[1]>=TWO_PI)
this.a[1]-=TWO_PI;
if(this.a[1]<0)
this.a[1]+=TWO_PI;
this.r=curveRadius;
this.xR=this.x[0]-d*this.r*sin(thetaA);
this.yR=this.y[0]-d*this.r*cos(thetaA);
this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR);
this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR);
if(d==1){
this.aEnd=PI/2-thetaA;
this.aStart=this.aEnd-thetaR;
}else{
this.aStart=1.5*PI-thetaA;
this.aEnd=this.aStart-thetaR;
}
this.layout=track.layout;
this.tColor=color(255,255,0);
dccComponents.add(this);
} // Track - curved, relative, no Layout specified
//////////////////////////////////////////////////////////////////////////
void display(){
if(tStatus==1){ // track is visible
if(hStatus==1) // track is highlighted
stroke(color(0,255,0));
else
stroke(color(255,255,0));
} else{ // track is not visible
if(hStatus==1) // track is highlighted
stroke(color(255,0,0));
else
stroke(color(80,80,0));
}
strokeWeight(3);
ellipseMode(RADIUS);
noFill();
if(r==0){
line(x[0]*layout.sFactor+layout.xCorner,y[0]*layout.sFactor+layout.yCorner,x[1]*layout.sFactor+layout.xCorner,y[1]*layout.sFactor+layout.yCorner);
}
else{
arc(xR*layout.sFactor+layout.xCorner,yR*layout.sFactor+layout.yCorner,r*layout.sFactor,r*layout.sFactor,aStart,aEnd);
}
} // display()
} // Track Class
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,153 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Class for Track Button
//
// TrackButton - creates a TURNOUT or CROSSOVER by grouping two sets of
// of pre-specified tracks
// - one set of tracks defines the state of the turnout
// or crossover in the "open" position
// - the other set of tracks defines the state of the turnout
// or crossover in the "closed" position
// - a clickable but otherwise invisible button (the Track Button)
// located near the center of the turnout or crossover
// toggles between the closed and open positions
//
//
// - when toggled, TrackButton will:
//
// * reset the colors of each set of tracks to
// indicate whether the turnour or crossover
// is "open" or "closed"
//
// * reset the color of any route buttons that use this
// track button
//
// * send a DCC ACCESSORY COMMAND to the DCC++ Base Station
// using the Accessory Address and Accessory Number
// specified for this Track Button
//
// In accordance with NMRA DCC Standards, accessory decoders
// are controlled using 12 bits messages. The first 11 form
// a main address (9 bits) and a sub address (2 bits). Depending
// on the specifics of a particular manufacturers decoder, these
// 11 bits can be interpreted as a single address (0-2047) or
// as a main address (0-511) with 4 sub addresses (0-3). Some decoders
// may respond to any address matching the first 9 bits; others may
// also consider the two sub address bits. In any case, Track Button
// can be used to send the correct combination of 11 bits to sucessfully
// communicate with the decoder.
//
// The 12th bit is generally considered to be the data bit that is used
// to toggle the accessory either on or off. In the case of a decoder
// driving a turnout or crossover, this data bit is used to toggle between
// the open and closed positions.
//
//////////////////////////////////////////////////////////////////////////
class TrackButton extends DccComponent{
int xPos, yPos;
int kWidth, kHeight;
int buttonStatus=0;
int id;
boolean rEnabled=true;
ArrayList<Track> aTracks = new ArrayList<Track>();
ArrayList<Track> bTracks = new ArrayList<Track>();
ArrayList<RouteButton> aRouteButtons = new ArrayList<RouteButton>();
ArrayList<RouteButton> bRouteButtons = new ArrayList<RouteButton>();
TrackButton(int kWidth, int kHeight, int id){
this.kWidth=kWidth;
this.kHeight=kHeight;
this.id=id;
this.componentName="T"+id;
trackButtonsHM.put(id,this);
dccComponents.add(this);
} // FunctionButton
//////////////////////////////////////////////////////////////////////////
void addTrack(Track track, int tPos){
int n=aTracks.size()+bTracks.size();
this.xPos=int((this.xPos*n+(track.x[0]+track.x[1])/2.0*track.layout.sFactor+track.layout.xCorner)/(n+1.0));
this.yPos=int((this.yPos*n+(track.y[0]+track.y[1])/2.0*track.layout.sFactor+track.layout.yCorner)/(n+1.0));
if(tPos==0){ // specifies that this track should be considered part of aTracks
track.tStatus=1-buttonStatus;
aTracks.add(track);
} else if (tPos==1) { // specifies that this track should be considered part of bTracks
track.tStatus=buttonStatus;
bTracks.add(track);
}
}
//////////////////////////////////////////////////////////////////////////
void display(){
if(buttonStatus==0){
for(Track track : bTracks)
track.display();
for(Track track : aTracks)
track.display();
} else {
for(Track track : aTracks)
track.display();
for(Track track : bTracks)
track.display();
}
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){
cursorType=HAND;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void routeEnabled(){
rEnabled=true;
}
//////////////////////////////////////////////////////////////////////////
void routeDisabled(){
rEnabled=false;
}
//////////////////////////////////////////////////////////////////////////
void pressed(){
pressed(1-buttonStatus);
}
//////////////////////////////////////////////////////////////////////////
void pressed(int buttonStatus){
aPort.write("<T"+id+" "+buttonStatus+">");
delay(50);
}
//////////////////////////////////////////////////////////////////////////
void update(int buttonStatus){
this.buttonStatus=buttonStatus;
for(Track track : aTracks)
track.tStatus=1-buttonStatus;
for(Track track : bTracks)
track.tStatus=buttonStatus;
if(buttonStatus==0){
for(RouteButton routeButton : bRouteButtons)
routeButton.routeOn=false;
} else {
for(RouteButton routeButton : aRouteButtons)
routeButton.routeOn=false;
}
} // update
} // TrackButton Class

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<dccStatus>
<serialPort>Emulator</serialPort>
<sensorButtons>
<Sensor1>false</Sensor1>
<Sensor2>false</Sensor2>
<Sensor3>true</Sensor3>
<Sensor4>false</Sensor4>
<Sensor5>false</Sensor5>
<Sensor6>false</Sensor6>
<Sensor7>true</Sensor7>
<Sensor8>true</Sensor8>
<Sensor9>false</Sensor9>
<Sensor10>false</Sensor10>
<Sensor11>true</Sensor11>
<Sensor12>false</Sensor12>
<Sensor13>true</Sensor13>
<Sensor14>false</Sensor14>
</sensorButtons>
<autoPilot>
<Cab2004>FULL</Cab2004>
<Cab622>FULL</Cab622>
<Cab8601>STOP</Cab8601>
<Cab6021>STOP</Cab6021>
<Cab54>STOP</Cab54>
<Cab1202>FULL</Cab1202>
<Cab1506>STOP</Cab1506>
<Phase>42</Phase>
<TCount>0</TCount>
<CrossOver>0</CrossOver>
<Program>SINGLE CAB PARK</Program>
<CabList>622</CabList>
<Cab2904>STOP</Cab2904>
</autoPilot>
<cabDefaults>
<Cab2004>
<throttleDefaults FULL="100" REVERSE="-50" REVERSE_SLOW="-45" SLOW="50" STOP="0"/>
</Cab2004>
<Cab622>
<throttleDefaults FULL="50" REVERSE="-20" REVERSE_SLOW="-16" SLOW="30" STOP="0"/>
</Cab622>
<Cab8601>
<throttleDefaults FULL="77" REVERSE="-34" REVERSE_SLOW="-30" SLOW="46" STOP="0"/>
</Cab8601>
<Cab6021>
<throttleDefaults FULL="55" REVERSE="-25" REVERSE_SLOW="-15" SLOW="25" STOP="0"/>
</Cab6021>
<Cab54>
<throttleDefaults FULL="59" REVERSE="-17" REVERSE_SLOW="-10" SLOW="14" STOP="0"/>
</Cab54>
<Cab1202>
<throttleDefaults FULL="32" REVERSE="-18" REVERSE_SLOW="-15" SLOW="25" STOP="0"/>
</Cab1202>
<Cab1506>
<throttleDefaults FULL="80" REVERSE="-30" REVERSE_SLOW="-27" SLOW="42" STOP="0"/>
</Cab1506>
<Cab2904>
<throttleDefaults FULL="100" REVERSE="-50" REVERSE_SLOW="-45" SLOW="50" STOP="0"/>
</Cab2904>
</cabDefaults>
<arduinoPort>/dev/tty.usbmodem1431</arduinoPort>
<serverList>192.168.1.169</serverList>
</dccStatus>

View File

@@ -0,0 +1,336 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Event Handlers
//
// Top-level processing of mouse, keyboard, and serial events.
// Most of the real functionality is contained in other methods,
// functions, and classes called by these handlers
//
//////////////////////////////////////////////////////////////////////////
void mouseDragged(){
if(selectedComponent!=null)
selectedComponent.drag();
}
//////////////////////////////////////////////////////////////////////////
void mousePressed(){
if(activeInputBox!=null){
for(InputBox inputBox : activeInputBox.linkedBoxes)
inputBox.setIntValue(activeInputBox.getIntValue());
}
activeInputBox=null;
if(selectedComponent!=null){
if (keyPressed == true && key == CODED){
if(keyCode == SHIFT){
selectedComponent.shiftPressed();
} else if(keyCode == CONTROL){
msgBoxMain.setMessage("Component Name: "+selectedComponent.componentName,color(30,30,150));
}
}
else if(mouseButton==LEFT){
selectedComponent.pressed();
} else {
selectedComponent.rightClick();
}
}
}
//////////////////////////////////////////////////////////////////////////
void mouseReleased(){
if(selectedComponent!=null)
selectedComponent.released();
}
//////////////////////////////////////////////////////////////////////////
void keyPressed(){
keyCommand(key, keyCode);
}
//////////////////////////////////////////////////////////////////////////
void keyReleased(){
keyCommandReleased(key, keyCode);
}
//////////////////////////////////////////////////////////////////////////
void serialEvent(Serial p){
receivedString(p.readString());
}
//////////////////////////////////////////////////////////////////////////
void clientEvent(Client c){
String s;
s=c.readStringUntil('>');
if(s!=null)
receivedString(s);
}
//////////////////////////////////////////////////////////////////////////
void receivedString(String s){
if(s.charAt(0)!='<')
return;
String c=s.substring(2,s.length()-1);
switch(s.charAt(1)){
case 'i':
baseID=c;
msgBoxMain.setMessage("Found "+baseID,color(0,150,0));
break;
case '*':
msgBoxDiagIn.setMessage(c,color(30,30,150));
break;
case 'r':
String[] cs=splitTokens(c,"|");
callBacks.get(int(cs[0])).execute(int(cs[1]),cs[2]);
break;
case 'T':
int[] n=int(splitTokens(c));
if(n[0]>cabButtons.size())
break;
CabButton t=cabButtons.get(n[0]-1);
if(n[2]==1)
t.speed=n[1];
else
t.speed=-n[1];
break;
case 'Q':
if(sensorsHM.get(int(c))!=null){
sensorsHM.get(int(c)).pressed();
}
break;
case 'Y':
int[] h1=int(splitTokens(c));
if(remoteButtonsHM.get(h1[0])!=null){
if(h1[1]==1)
remoteButtonsHM.get(h1[0]).turnOn();
else
remoteButtonsHM.get(h1[0]).turnOff();
}
break;
case 'H':
int[] h=int(splitTokens(c));
if(trackButtonsHM.get(h[0])!=null){
trackButtonsHM.get(h[0]).update(h[1]);
} else if(remoteButtonsHM.get(h[0])!=null){
if(h[1]==((remoteButtonsHM.get(h[0]).buttonType==ButtonType.T_COMMAND)?1:0))
remoteButtonsHM.get(h[0]).turnOn();
else
remoteButtonsHM.get(h[0]).turnOff();
}
break;
case 'L':
int[] z=int(splitTokens(c));
color tempColor;
tempColor=color(z[0],z[1],z[2]);
colorMode(HSB,1.0,1.0,1.0);
ledColorButton.hue=hue(tempColor);
ledColorButton.sat=saturation(tempColor);
ledColorButton.val=brightness(tempColor);
ledColorButton.update(0);
colorMode(RGB,255);
break;
case 'U':
autoPilot.cabList.clear();
autoPilot.setProgram(AutoProgram.SINGLE_CAB_RUN);
autoPilot.turnOn();
break;
case 'p':
if(c.equals("1")){
powerButton.isOn=true;
msgBoxMain.setMessage("Track Power On",color(30,30,150));
} else if(c.equals("0")){
powerButton.isOn=false;
msgBoxMain.setMessage("Track Power Off",color(30,30,150));
} else if(c.equals("2")){
msgBoxMain.setMessage("MAIN Track Current Overload - Power Off",color(200,30,30));
powerButton.isOn=false;
} else if(c.equals("3")){
msgBoxMain.setMessage("PROG Track Current Overload - Power Off",color(200,30,30));
powerButton.isOn=false;
}
break;
case 'a':
currentMeter.addSample(int(c));
break;
}
} // receivedString
//////////////////////////////////////////////////////////////////////////
void keyCommand(char k, int kC){
if(activeInputBox!=null){
activeInputBox.keyStroke(k, kC);
return;
}
if(k==CODED){
switch(kC){
case UP:
if(throttleA.cabButton!=null){
if(!keyHold)
throttleA.pressed();
throttleA.keyControl(1);
}
break;
case DOWN:
if(throttleA.cabButton!=null){
if(!keyHold)
throttleA.pressed();
throttleA.keyControl(-1);
}
break;
case LEFT:
if(throttleA.cabButton!=null){
throttleA.keyControl(0);
}
break;
case RIGHT:
if(throttleA.cabButton!=null){
throttleA.cabButton.stopThrottle();
}
break;
}
} // key is coded
else{
switch(k){
case 'P':
powerButton.turnOn();
break;
case 'F':
aPort.write("<3>");
break;
case 'f':
aPort.write("<2>");
break;
case ' ':
powerButton.turnOff();
break;
case 'a':
accWindow.toggle();
break;
case 'c':
currentMeter.isOn=!currentMeter.isOn;
break;
case 'e':
extrasWindow.toggle();
break;
case 'x':
autoWindow.toggle();
break;
case 'S':
sensorWindow.toggle();
break;
case 'l':
ledWindow.toggle();
break;
case 's':
portWindow.toggle();
break;
case 'h':
helpWindow.toggle();
break;
case 'q':
imageWindow.toggle();
break;
case 'd':
diagWindow.toggle();
break;
case 'i':
if(layoutBridge.equals(layout2))
layoutBridge.copy(layout);
else
layoutBridge.copy(layout2);
break;
case 'p':
progWindow.toggle();
break;
case 'o':
opWindow.toggle();
break;
case 'n':
if(throttleA.cabButton!=null){
throttleA.cabButton.fbWindow.close();
throttleA.cabButton.fbWindow=throttleA.cabButton.windowList.get((throttleA.cabButton.windowList.indexOf(throttleA.cabButton.fbWindow)+1)%throttleA.cabButton.windowList.size());
throttleA.cabButton.fbWindow.open();
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
cabButtons.get(int(k)-int('1')).pressed();
break;
}
} // key not coded
keyHold=true;
} // keyCommand
//////////////////////////////////////////////////////////////////////////
void keyCommandReleased(char k, int kC){
keyHold=false;
if(k==CODED){
switch(kC){
}
} // key is coded
else{
switch(k){
}
} // key not coded
} // keyCommandReleased
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,168 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Generic Ellipse and Rectangle Buttons
//
// EllipseButton - base class for creating simple buttons
// - operating buttons that extend EllipseButton should
// over-ride these methods with functionality specific
// to that button
//
// RectButton - variant of EllipseButton that define a rectanglular button
//
//////////////////////////////////////////////////////////////////////////
class EllipseButton extends DccComponent{
int bWidth, bHeight;
int baseHue;
color textColor;
int fontSize;
String bText;
ButtonType buttonType;
int remoteCode;
boolean isOn=false;
EllipseButton(){
this(width/2,height/2,80,50,100,color(0),16,"Button",ButtonType.NORMAL);
}
EllipseButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){
this(null, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType);
}
EllipseButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){
this.xPos=xPos;
this.yPos=yPos;
this.bWidth=bWidth;
this.bHeight=bHeight;
this.bText=bText;
this.fontSize=fontSize;
this.baseHue=baseHue;
this.textColor=textColor;
this.window=window;
this.buttonType=buttonType;
if(window==null)
dccComponents.add(this);
else
window.windowComponents.add(this);
} // EllipseButton
//////////////////////////////////////////////////////////////////////////
void display(){
colorMode(HSB,255);
ellipseMode(CENTER);
noStroke();
fill(color(baseHue,255,isOn?255:125));
ellipse(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight);
fill(textColor);
textFont(buttonFont,fontSize);
textAlign(CENTER,CENTER);
text(bText,xPos+xWindow(),yPos+yWindow());
if(buttonType==ButtonType.ONESHOT && isOn)
turnOff();
colorMode(RGB,255);
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX-xPos-xWindow())*(mouseX-xPos-xWindow())/(bWidth*bWidth/4.0)+(mouseY-yPos-yWindow())*(mouseY-yPos-yWindow())/(bHeight*bHeight/4.0)<=1){
cursorType=HAND;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void turnOn(){
isOn=true;
}
//////////////////////////////////////////////////////////////////////////
void turnOff(){
isOn=false;
}
//////////////////////////////////////////////////////////////////////////
void pressed(){
if(buttonType==ButtonType.T_COMMAND){
aPort.write("<T"+remoteCode+" "+(isOn?"0>":"1>"));
return;
}
if(buttonType==ButtonType.TI_COMMAND){
aPort.write("<T"+remoteCode+" "+(isOn?"1>":"0>"));
return;
}
if(buttonType==ButtonType.Z_COMMAND){
aPort.write("<Z"+remoteCode+" "+(isOn?"0>":"1>"));
return;
}
if(isOn)
turnOff();
else
turnOn();
}
//////////////////////////////////////////////////////////////////////////
void released(){
if(buttonType==ButtonType.HOLD)
turnOff();
}
} // EllipseButton Class
//////////////////////////////////////////////////////////////////////////
class RectButton extends EllipseButton{
RectButton(){
super(width/2,height/2,80,50,100,color(0),16,"Button",ButtonType.NORMAL);
}
RectButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){
super(null, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType);
}
RectButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){
super(window, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType);
}
RectButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType, int remoteCode){
super(window, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType);
this.remoteCode=remoteCode;
remoteButtonsHM.put(remoteCode,this);
} // RectangleButton
//////////////////////////////////////////////////////////////////////////
void display(){
colorMode(HSB,255);
rectMode(CENTER);
noStroke();
fill(color(baseHue,255,isOn?255:125));
rect(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight);
fill(textColor);
textFont(buttonFont,fontSize);
textAlign(CENTER,CENTER);
text(bText,xPos+xWindow(),yPos+yWindow());
if(buttonType==ButtonType.ONESHOT && isOn)
turnOff();
colorMode(RGB,255);
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX>xPos+xWindow()-bWidth/2)&&(mouseX<xPos+xWindow()+bWidth/2)&&(mouseY>yPos+yWindow()-bHeight/2)&&(mouseY<yPos+yWindow()+bHeight/2)){
cursorType=HAND;
selectedComponent=this;
}
} // check
} // RectButton Class

View File

@@ -0,0 +1,254 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Generic Input/Output Text Boxes
//
// MessageBox - defines an output box for displaying text of specified
// - size, color, and background
//
// InputBox - defines a box with text that can by input via the keyboard
// - box size and allowable characters can be constrained
// - text that is input is stored for later reference by other
// classes and methods
// - multiple boxes can be lniked so that they form a "tab" group
// - clicking on any object outside the input box ends the input mode
// - hitting "return" also ends the input mode
//
//////////////////////////////////////////////////////////////////////////
class MessageBox extends DccComponent{
int kWidth, kHeight;
color boxColor;
color msgColor;
int fontSize;
String msgText;
MessageBox(int xPos, int yPos, int kWidth, int kHeight, color boxColor, int fontSize){
this(null, xPos, yPos, kWidth, kHeight, boxColor, fontSize);
} // MessageBox
MessageBox(int xPos, int yPos, int kWidth, int kHeight, color boxColor, int fontSize, String msgText, color msgColor){
this(null, xPos, yPos, kWidth, kHeight, boxColor, fontSize);
setMessage(msgText, msgColor);
} // MessageBox
MessageBox(Window window, int xPos, int yPos, int kWidth, int kHeight, color boxColor, int fontSize, String msgText, color msgColor){
this(window, xPos, yPos, kWidth, kHeight, boxColor, fontSize);
setMessage(msgText, msgColor);
} // MessageBox
MessageBox(Window window, int xPos, int yPos, int kWidth, int kHeight, color boxColor, int fontSize){
this.xPos=xPos;
this.yPos=yPos;
this.kWidth=kWidth;
this.kHeight=kHeight;
this.msgText="";
this.msgColor=color(0,0,255);
this.fontSize=fontSize;
this.boxColor=boxColor;
this.window=window;
if(window==null)
dccComponents.add(this);
else
window.windowComponents.add(this);
} // MessageBox
//////////////////////////////////////////////////////////////////////////
void display(){
noStroke();
rectMode(CENTER);
fill(boxColor);
rect(xPos+xWindow()-(kWidth<0?kWidth/2:0),yPos+yWindow(),abs(kWidth),kHeight);
textFont(messageFont,fontSize);
textAlign(kWidth<0?LEFT:CENTER,CENTER);
fill(msgColor);
text(msgText,xPos+xWindow(),yPos+yWindow());
} // display
//////////////////////////////////////////////////////////////////////////
void setMessage(String msgText, color msgColor){
this.msgText=msgText;
this.msgColor=msgColor;
}
//////////////////////////////////////////////////////////////////////////
void setMessage(String msgText){
this.msgText=msgText;
}
} // MessageBox Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: InputBox
//////////////////////////////////////////////////////////////////////////
class InputBox extends DccComponent{
int kWidth, kHeight;
int fontSize;
color boxColor;
color msgColor;
String inputText="";
int maxChars;
Pattern regexp;
InputType inputType;
InputBox nextBox=null;
CabButton cb=null;
ArrayList<InputBox> linkedBoxes = new ArrayList<InputBox>();
InputBox(CabButton cb){
this(cb.editWindow,4,cb.bHeight/2,cb.fontSize,color(255,0,255),color(0,0,0),4,InputType.DEC);
this.cb=cb;
setIntValue(cb.cab);
}
InputBox(int xPos, int yPos, int fontSize, color boxColor, color msgColor, int maxChars, InputType inputType){
this(null, xPos, yPos, fontSize, boxColor, msgColor, maxChars, inputType);
}
InputBox(Window window, int xPos, int yPos, int fontSize, color boxColor, color msgColor, int maxChars, InputType inputType){
this.xPos=xPos;
this.yPos=yPos;
this.fontSize=fontSize;
this.msgColor=msgColor;
this.boxColor=boxColor;
this.window=window;
this.maxChars=maxChars;
textFont(messageFont,fontSize);
String s="0";
for(int i=0;i<maxChars;i++,s+="0");
this.kWidth=int(textWidth(s));
this.kHeight=fontSize+4;
this.inputType=inputType;
linkedBoxes.add(this);
regexp=regexp.compile(inputType.regexp);
if(window==null)
dccComponents.add(this);
else
window.windowComponents.add(this);
} // InputBox
//////////////////////////////////////////////////////////////////////////
void display(){
String textCursor;
noStroke();
rectMode(CENTER);
if(activeInputBox==this)
fill(255);
else
fill(boxColor);
rect(xPos+xWindow()+kWidth/2,yPos+yWindow(),kWidth,kHeight);
textFont(messageFont,fontSize);
textAlign(LEFT,CENTER);
fill(msgColor);
if(activeInputBox!=this && inputText.length()==0)
textCursor="?";
else if(activeInputBox==this && (millis()/500)%2==1)
textCursor="|";
else
textCursor="";
text(inputText+textCursor,xPos+xWindow(),yPos+yWindow());
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX>xPos+xWindow())&&(mouseX<xPos+xWindow()+kWidth)&&(mouseY>yPos+yWindow()-kHeight/2)&&(mouseY<yPos+yWindow()+kHeight/2)){
if(activeInputBox!=this)
cursorType=TEXT;
selectedComponent=this;
}
}
//////////////////////////////////////////////////////////////////////////
void pressed(){
activeInputBox=this;
}
//////////////////////////////////////////////////////////////////////////
void setNextBox(InputBox nextBox){
this.nextBox=nextBox;
}
//////////////////////////////////////////////////////////////////////////
void linkBox(InputBox inputBox){
linkedBoxes=inputBox.linkedBoxes;
linkedBoxes.add(this);
}
//////////////////////////////////////////////////////////////////////////
int getIntValue(){
if(inputText.length()==0)
return 0;
if(inputType==InputType.DEC)
return int(inputText);
if(inputType==InputType.BIN)
return unbinary(inputText);
if(inputType==InputType.HEX)
return unhex(inputText);
return 0;
}
//////////////////////////////////////////////////////////////////////////
void setIntValue(int v){
if(inputType==InputType.DEC)
inputText=str(v);
else if(inputType==InputType.BIN)
inputText=binary(v,8);
else if(inputType==InputType.HEX)
inputText=hex(v,2);
else
inputText="";
}
//////////////////////////////////////////////////////////////////////////
void resetValue(){
inputText="";
}
//////////////////////////////////////////////////////////////////////////
void keyStroke(char k, int kC){
if(kC!=CODED){
if(regexp.matcher(str(k)).find() && inputText.length()<maxChars){
inputText+=k;
} else if(k==BACKSPACE && inputText.length()>0){
inputText=inputText.substring(0,inputText.length()-1);
} else if(k==ENTER || k==RETURN){
activeInputBox=null;
for( InputBox inputBox : linkedBoxes)
inputBox.setIntValue(getIntValue());
if(cb!=null){
cb.cab=getIntValue();
cb.bText=str(cb.cab);
cb.cabFile=("cab-"+cb.cab+".jpg");
cb.cabImage=loadImage(cb.cabFile);
cb.name="Cab"+cb.cab;
cabsHM.put(cb.name,cb);
cb.editWindow.close();
}
} else if(k==TAB){
if(nextBox!=null)
nextBox.pressed();
else
activeInputBox=null;
for( InputBox inputBox : linkedBoxes)
inputBox.setIntValue(getIntValue());
if(cb!=null){
setIntValue(cb.cab);
cb.editWindow.close();
}
}
} // kc!=CODED
} // keyStroke
} // InputBox Class
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,291 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Generic Windows
//
// Window - creates a window box of a specified size, color, and
// initial position into which other components can be placed
// such as buttons, message boxes, and text boxes
//
// DragBar - creates a drag bar on window to allow it to be dragged
// across screen
//
// CloseButton - creates close button on window that closes window box
// - windows are normally opened by other buttons or key commands
// defined elsewhere
//
// ImageWindow - extends Window to create a window bx into which
// a single cab image tied to a specified throttle will be displayed
//
// JPGWindow - extends Window to create a generic window box for diplaying a single jpg image
//
//////////////////////////////////////////////////////////////////////////
class Window extends DccComponent{
int xPos, yPos;
int kWidth, kHeight;
color backgroundColor;
color outlineColor;
ArrayList<DccComponent> windowComponents = new ArrayList<DccComponent>();
Window(int xPos, int yPos, int kWidth, int kHeight, color backgroundColor, color outlineColor){
this.xPos=xPos;
this.yPos=yPos;
this.kWidth=kWidth;
this.kHeight=kHeight;
this.backgroundColor=backgroundColor;
this.outlineColor=outlineColor;
} // Window
//////////////////////////////////////////////////////////////////////////
void display(){
rectMode(CORNER);
fill(backgroundColor);
strokeWeight(3);
stroke(outlineColor);
rect(xPos,yPos,kWidth,kHeight);
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX>xPos)&&(mouseX<xPos+kWidth)&&(mouseY>yPos)&&(mouseY<yPos+kHeight)){
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
close();
open();
}
//////////////////////////////////////////////////////////////////////////
void toggle(){
if(dccComponents.contains(this))
close();
else
open();
} // toggle
//////////////////////////////////////////////////////////////////////////
void open(){
if(dccComponents.contains(this))
return;
dccComponents.add(this); /// adds window and components to end of dccComponents --- will display last on top
for(DccComponent windowComponent : windowComponents)
dccComponents.add(windowComponent);
}
//////////////////////////////////////////////////////////////////////////
void show(){
if(dccComponents.contains(this))
return;
for(DccComponent windowComponent : windowComponents)
dccComponents.add(0,windowComponent);
dccComponents.add(0,this); // adds window and components to start of dccComponents --- will display first on bottom
}
//////////////////////////////////////////////////////////////////////////
void close(){
if(!dccComponents.contains(this))
return;
for(DccComponent windowComponent : windowComponents)
dccComponents.remove(windowComponent);
dccComponents.remove(this);
}
} // Window Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: DragBar
//////////////////////////////////////////////////////////////////////////
class DragBar extends DccComponent{
int xPos, yPos;
int kWidth, kHeight;
color backgroundColor;
Window window;
int xDrag, yDrag;
DragBar(Window window, int xPos, int yPos, int kWidth, int kHeight, color backgroundColor){
this.window=window;
this.xPos=xPos;
this.yPos=yPos;
this.kWidth=kWidth;
this.kHeight=kHeight;
this.backgroundColor=backgroundColor;
window.windowComponents.add(this);
} // Window
//////////////////////////////////////////////////////////////////////////
void display(){
rectMode(CORNER);
fill(backgroundColor);
noStroke();
rect(xPos+window.xPos,yPos+window.yPos,kWidth,kHeight);
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX>xPos+window.xPos)&&(mouseX<xPos+window.xPos+kWidth)&&(mouseY>yPos+window.yPos)&&(mouseY<yPos+window.yPos+kHeight)){
cursorType=MOVE;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
window.close();
window.open();
xDrag=mouseX-window.xPos;
yDrag=mouseY-window.yPos;
cursor(ARROW);
}
//////////////////////////////////////////////////////////////////////////
void drag(){
window.xPos=mouseX-xDrag;
window.yPos=mouseY-yDrag;
}
} // DragBar Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: CloseButton
//////////////////////////////////////////////////////////////////////////
class CloseButton extends DccComponent{
int xPos, yPos;
int kWidth, kHeight;
color backgroundColor;
color lineColor;
Window window;
CloseButton(Window window, int xPos, int yPos, int kWidth, int kHeight, color backgroundColor, color lineColor){
this.window=window;
this.xPos=xPos;
this.yPos=yPos;
this.kWidth=kWidth;
this.kHeight=kHeight;
this.backgroundColor=backgroundColor;
this.lineColor=lineColor;
window.windowComponents.add(this);
} // Window
//////////////////////////////////////////////////////////////////////////
void display(){
rectMode(CORNER);
fill(backgroundColor);
stroke(lineColor);
strokeWeight(1);
rect(xPos+window.xPos,yPos+window.yPos,kWidth,kHeight);
line(xPos+window.xPos,yPos+window.yPos,xPos+window.xPos+kWidth,yPos+window.yPos+kHeight);
line(xPos+window.xPos,yPos+window.yPos+kHeight,xPos+window.xPos+kWidth,yPos+window.yPos);
} // display
//////////////////////////////////////////////////////////////////////////
void check(){
if(selectedComponent==null && (mouseX>xPos+window.xPos)&&(mouseX<xPos+window.xPos+kWidth)&&(mouseY>yPos+window.yPos)&&(mouseY<yPos+window.yPos+kHeight)){
cursorType=HAND;
selectedComponent=this;
}
} // check
//////////////////////////////////////////////////////////////////////////
void pressed(){
window.close();
}
//////////////////////////////////////////////////////////////////////////
// void drag(){
// }
} // CloseButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: ImageWindow
//////////////////////////////////////////////////////////////////////////
class ImageWindow extends Window{
PImage img;
Throttle throttle;
int w,h;
ImageWindow(Throttle throttle, int w, int h, int xPos, int yPos, color outlineColor){
super(xPos, yPos, w, h, color(255), outlineColor);
new DragBar(this,0,0,w,10,outlineColor);
new CloseButton(this,w-12,0,10,10,outlineColor,color(255,255,255));
this.throttle=throttle;
this.w=w;
this.h=h;
} // Window
//////////////////////////////////////////////////////////////////////////
void display(){
super.display();
if(throttle.cabButton==null){
textAlign(CENTER,CENTER);
fill(color(200,0,0));
textFont(messageFont,20);
text("PLEASE SELECT CAB TO DISPLAY IMAGE",xPos+w/2,yPos+h/2);
} else if(throttle.cabButton.cabImage==null){
textAlign(CENTER,CENTER);
fill(color(200,0,0));
textFont(messageFont,20);
text("NO IMAGE FILE FOUND FOR THIS CAB",xPos+w/2,yPos+h/2);
} else{
imageMode(CORNER);
image(throttle.cabButton.cabImage,xPos,yPos,w,h);
}
} // display
} // ImageWindow Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: JPGWindow
//////////////////////////////////////////////////////////////////////////
class JPGWindow extends Window{
PImage img;
int w,h;
JPGWindow(String JPGFile, int w, int h, int xPos, int yPos, color outlineColor){
super(xPos, yPos, w, h, color(255), outlineColor);
new DragBar(this,0,0,w,10,outlineColor);
new CloseButton(this,w-12,0,10,10,outlineColor,color(255,255,255));
img=loadImage(JPGFile);
this.w=w;
this.h=h;
} // Window
//////////////////////////////////////////////////////////////////////////
void display(){
super.display();
imageMode(CORNER);
image(img,xPos,yPos,w,h);
} // display
} // JPGWindow Class
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,439 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Programming Components
//
// All classes and methods related to programming mobile decoder
// configuration variables (CVs)
////
// ProgWriteReadButton - sends a DCC PROGRAMMING COMMAND to the DCC++ Base Station
// that either reads from, or writes to, a user-specified CV in a mobile
// decoder on the Programming Track
// - Note: cab numbers (mobile decoder addresses) are not used on the Programming
// Track. Whatever locomotive is on the track will be programmed!
// - users specify the CV to be written or read via an input box
// - in the case of writing to a CV, three linked input boxes allow the user to
// specify the byte in either HEX, DECIMAL, or BINARY formats
// - in the case of reading from a CV, these three linked input boxes are updated
// to display the results of the read in HEX, DECIMAL, and BINARY formats
// - in the case of writing to a CV, the DCC++ Base Station automatically performs an
// subsequent read to verify the byte was properly written
//
// ProgAddReadButton - sends a series of DCC PROGRAMMING COMMANDS to the DCC++ Base Station
// that reads the following CVs from a mobile decoder on the Programming Track:
//
// * CV #1 - contains the short (single byte) cab address
// * CV #17 - contains the high byte of a long (two byte) cab address
// * CV #18 - contains the low byte of a long (two byte) cab address
// * CV #29 - bit 5 indicates whether mobile decoder is using long or short cab address
//
// - CV #17 and CV #18 are combined into a single cab address
// - three input boxes display the results of the short address, the long address,
// - and whether of not the mobile decoder is using the long or short cab address
//
// ProgShortAddWriteButton - sends a DCC PROGRAMMING COMMAND to the DCC++ Base Station
// that writes the short cab address specified in the first input box described above
// to a mobile decoder on the Programming Track
//
// ProgLongAddWriteButton - sends a DCC PROGRAMMING COMMAND to the DCC++ Base Station
// that writes the long cab address specified in the second input box described above
// to a mobile decoder on the Programming Track
//
// ProgLongShortButton - sends a DCC PROGRAMMING COMMAND to the DCC++ Base Station
// that indicates whether the mobile decoder on the Programming Track should use its
// short cab address or long cab address
//
// The default configuration of DCC++ Controller defines a Programming Window that includes all of the above components
//
//
// OpWriteButton - sends a DCC PROGRAMMING COMMAND to the DCC++ Base Station that writes a user-specified byte
// or sets/clears a user-specified bit in a user-specified CV of a mobile decoder with a
// user-specified cab address on the Main Operations Track
// - uses one input box for specifiying the cab address and one for the CV
// - when writing a full byte, uses 3 linked boxes for specifying the value in
// either HEX, DECIMAL, or BINARY format
// - when setting/clearing a bit, uses on input box to specify the bit number
// - the default configuration of DCC++ Controller defines an Operation Programming Window that
// includes all of these components
//////////////////////////////////////////////////////////////////////////
// DCC Component: ProgWriteReadButton
//////////////////////////////////////////////////////////////////////////
class ProgWriteReadButton extends EllipseButton implements CallBack{
InputBox progCVInput, progValueInput;
ProgWriteReadButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox progCVInput, InputBox progValueInput){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, progCVInput, progValueInput);
}
ProgWriteReadButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox progCVInput, InputBox progValueInput){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.progCVInput=progCVInput;
this.progValueInput=progValueInput;
callBacks.add(this);
} // ProgrWriteReadButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
int cv=progCVInput.getIntValue();
int val=progValueInput.getIntValue();
if(cv<1 || cv>1024){
msgBoxMain.setMessage("Error - CV must be in range 1-1024",color(255,30,30));
} else if(bText=="WRITE"){
aPort.write("<W"+cv+" "+val+" "+callBacks.indexOf(this)+" 1>");
} else if(bText=="READ"){
aPort.write("<R"+cv+" "+callBacks.indexOf(this)+" 0>");
}
} // pressed
//////////////////////////////////////////////////////////////////////////
void execute(int n, String c){
String[] cs = splitTokens(c);
int cv=int(cs[0]);
int val=int(cs[1]);
progCVInput.setIntValue(cv);
if(val<0){
msgBoxMain.setMessage(n==0?"Error - Read Failed":"Error - Write Failed",color(255,30,30));
progHEXInput.resetValue();
progBINInput.resetValue();
progDECInput.resetValue();
} else{
msgBoxMain.setMessage(n==0?"Read Succeeded":"Write Succeeded",color(30,150,30));
progHEXInput.setIntValue(val);
progBINInput.setIntValue(val);
progDECInput.setIntValue(val);
}
} // execute
} // progWriteReadButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: ProgAddReadButton
//////////////////////////////////////////////////////////////////////////
class ProgAddReadButton extends EllipseButton implements CallBack{
InputBox shortAddInput, longAddInput;
MessageBox activeAddBox;
int longAdd;
ProgAddReadButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox shortAddInput, InputBox longAddInput, MessageBox activeAddBox){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, shortAddInput, longAddInput, activeAddBox);
}
ProgAddReadButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox shortAddInput, InputBox longAddInput, MessageBox activeAddBox){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.shortAddInput=shortAddInput;
this.longAddInput=longAddInput;
this.activeAddBox=activeAddBox;
callBacks.add(this);
} // ProgAddReadButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
aPort.write("<R1 "+callBacks.indexOf(this)+" 0>");
} // pressed
//////////////////////////////////////////////////////////////////////////
void execute(int n, String c){
String[] cs = splitTokens(c);
int cv=int(cs[0]);
int val=int(cs[1]);
switch(cv){
case 1:
if(val<0){
msgBoxMain.setMessage("Error - Reading Short Address Failed",color(255,30,30));
shortAddInput.resetValue();
} else{
shortAddInput.setIntValue(val);
aPort.write("<R17 "+callBacks.indexOf(this)+" 0>");
}
break;
case 17:
if(val<0){
msgBoxMain.setMessage("Error - Reading First Byte of Long Address Failed",color(255,30,30));
longAddInput.resetValue();
} else{
longAdd=(val&0x3F)*256;
aPort.write("<R18 "+callBacks.indexOf(this)+" 0>");
}
break;
case 18:
if(val<0){
msgBoxMain.setMessage("Error - Reading Second Byte of Long Address Failed",color(255,30,30));
longAddInput.resetValue();
} else{
longAdd+=val;
longAddInput.setIntValue(longAdd);
aPort.write("<R29 "+callBacks.indexOf(this)+" 0>");
}
break;
case 29:
if(val<0){
msgBoxMain.setMessage("Error - Reading Second Byte of Long Address Failed",color(255,30,30));
activeAddBox.setMessage("?",color(200,50,50));
} else{
if((val&0x20)==0)
activeAddBox.setMessage("SHORT",color(200,50,50));
else
activeAddBox.setMessage("LONG",color(200,50,50));
msgBoxMain.setMessage("Reading Short and Long Addresses Succeeded",color(30,150,30));
}
break;
}
} // execute
} // ProgAddReadButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: ProgShortAddWriteButton
//////////////////////////////////////////////////////////////////////////
class ProgShortAddWriteButton extends EllipseButton implements CallBack{
InputBox addInput;
ProgShortAddWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, addInput);
}
ProgShortAddWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.addInput=addInput;
callBacks.add(this);
} // ProgAddReadButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
int val=addInput.getIntValue();
if(val<1 || val>127){
msgBoxMain.setMessage("Error - Short Address must be in range 1-127",color(255,30,30));
} else {
aPort.write("<W1"+" "+val+" "+callBacks.indexOf(this)+" 0>");
}
} // pressed
//////////////////////////////////////////////////////////////////////////
void execute(int n, String c){
String[] cs = splitTokens(c);
int cv=int(cs[0]);
int val=int(cs[1]);
if(val<0){
msgBoxMain.setMessage("Error - Write Short Address Failed",color(255,30,30));
addInput.resetValue();
} else{
msgBoxMain.setMessage("Write Short Address Succeeded",color(30,150,30));
addInput.setIntValue(val);
}
} // execute
} // ProgShortAddWriteButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: ProgLongAddWriteButton
//////////////////////////////////////////////////////////////////////////
class ProgLongAddWriteButton extends EllipseButton implements CallBack{
InputBox addInput;
int longAddIn, longAddOut;
ProgLongAddWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, addInput);
}
ProgLongAddWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.addInput=addInput;
callBacks.add(this);
} // ProgAddReadButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
longAddIn=addInput.getIntValue();
if(longAddIn<0 || longAddIn>10239){
msgBoxMain.setMessage("Error - Long Address must be in range 0-10239",color(255,30,30));
} else {
aPort.write("<W17"+" "+(longAddIn/256+192)+" "+callBacks.indexOf(this)+" 0>");
}
} // pressed
//////////////////////////////////////////////////////////////////////////
void execute(int n, String c){
String[] cs = splitTokens(c);
int cv=int(cs[0]);
int val=int(cs[1]);
switch(cv){
case 17:
if(val<0){
msgBoxMain.setMessage("Error - Writing First Byte of Long Address Failed",color(255,30,30));
addInput.resetValue();
} else{
longAddOut=(val&0x3F)*256;
aPort.write("<W18"+" "+(longAddIn%256)+" "+callBacks.indexOf(this)+" 0>");
}
break;
case 18:
if(val<0){
msgBoxMain.setMessage("Error - Writing Second Byte of Long Address Failed",color(255,30,30));
addInput.resetValue();
} else{
msgBoxMain.setMessage("Write Long Address Succeeded",color(30,150,30));
longAddOut+=val;
addInput.setIntValue(longAddOut);
}
break;
}
} // execute
} // ProgLongAddWriteButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: ProgLongShortButton
//////////////////////////////////////////////////////////////////////////
class ProgLongShortButton extends EllipseButton implements CallBack{
MessageBox activeAddBox;
ProgLongShortButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, MessageBox activeAddBox){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, activeAddBox);
}
ProgLongShortButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, MessageBox activeAddBox){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.activeAddBox=activeAddBox;
callBacks.add(this);
} // ProgrWriteReadButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
if(bText=="Long"){
aPort.write("<B 29 5 1 "+callBacks.indexOf(this)+" 1>");
} else if(bText=="Short"){
aPort.write("<B 29 5 0 "+callBacks.indexOf(this)+" 0>");
}
} // pressed
//////////////////////////////////////////////////////////////////////////
void execute(int n, String c){
String[] cs = splitTokens(c);
int val=int(cs[2]);
switch(val){
case -1:
msgBoxMain.setMessage(n==1?"Error - Activating Long Address Failed":"Error - Activating Short Address Failed",color(255,30,30));
activeAddBox.setMessage("?",color(200,50,50));
break;
case 0:
msgBoxMain.setMessage("Activating Short Address Succeeded",color(30,150,30));
activeAddBox.setMessage("SHORT",color(200,50,50));
break;
case 1:
msgBoxMain.setMessage("Activating Long Address Succeeded",color(30,150,30));
activeAddBox.setMessage("LONG",color(200,50,50));
break;
}
} // execute
} // ProgLongShortButton Class
//////////////////////////////////////////////////////////////////////////
// DCC Component: OpWriteButton
//////////////////////////////////////////////////////////////////////////
class OpWriteButton extends EllipseButton{
InputBox opCVInput, opValueInput;
OpWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox opCVInput, InputBox opValueInput){
this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, opCVInput, opValueInput);
}
OpWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox opCVInput, InputBox opValueInput){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
this.opCVInput=opCVInput;
this.opValueInput=opValueInput;
} // OpWriteButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
super.pressed();
int cab=opCabInput.getIntValue();
int cv=opCVInput.getIntValue();
int val=opValueInput.getIntValue();
if(cab<1 || cab>10239){
msgBoxMain.setMessage("Error - Cab must be in range 1-10239",color(255,30,30));
return;
}
if(cv<1 || cv>1024){
msgBoxMain.setMessage("Error - CV must be in range 1-1024",color(255,30,30));
return;
}
if(bText=="WRITE"){
aPort.write("<w"+cab+" "+cv+" "+val+" >");
return;
}
if(val>7){
msgBoxMain.setMessage("Error - Bit must be in range 0-7",color(255,30,30));
return;
}
if(bText=="SET"){
aPort.write("<b"+cab+" "+cv+" "+val+" 1>");
} else if(bText=="CLEAR"){
aPort.write("<b"+cab+" "+cv+" "+val+" 0>");
}
} // pressed
} // OpWriteButton Class

View File

@@ -0,0 +1,247 @@
//////////////////////////////////////////////////////////////////////////
// DCC++ CONTROLLER: Serial Components
//
// All classes and methods related to serial communication to and from
// the DCC++ Base Station
//
// PortScanButton - function depends on button label as follows:
//
// "SCAN" - create a list all serial ports on the computer
// ">" - scroll forward through the list
// "<" - scroll backwards through the list
// "CONNECT" - attempt to connect to a DCC++ Base Station
//
// - the default configuration of DCC++ Controller defines a
// Serial Window that includes all of these components
//
// ArduinoPort - defines a generic port connection to the DCC++ Base Station
// - extends Processing's normal Serial class by adding an
// Ethernet or WiFi Client connection at port 2560 as well as
// a "simulation" function so that DCC++ Controller can be run
// in "emulator" mode without actually establishing a connection
// to the DCC++ Base Station
// - ideal for developing, testing, and demonstrating DCC++ Controller
// without an Arduino
// - also adds functionality that echos to a pre-specified text box all
// text that is written to the DCC++ Base Station
// - the default configuration of DCC++ Controller defines a
// Diagnostic Window that includes this text box and is useful for
// observing the exact commands DCC++ Controller sends to the
// DCC++ Base Station
//
//////////////////////////////////////////////////////////////////////////
// DCC Component: PortScanButton
//////////////////////////////////////////////////////////////////////////
class PortScanButton extends RectButton{
boolean isComplete;
PortScanButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){
super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT);
} // AccessoryButton
//////////////////////////////////////////////////////////////////////////
void pressed(){
isComplete=false;
super.pressed();
}
//////////////////////////////////////////////////////////////////////////
void scan(){
String[] emulator = {"Emulator"};
String[] serverList=splitTokens(serverListXML.getContent());
aPort.portList=concat(emulator,Serial.list());
aPort.portList=concat(aPort.portList,serverList);
aPort.displayedPort=0;
portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200));
portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50));
} // scan
//////////////////////////////////////////////////////////////////////////
void turnOff(){
String[] emulator = {"Emulator"};
if(isComplete==false){
isComplete=true;
return;
}
super.turnOff();
if(bText=="SCAN"){
scan();
return;
} // SCAN
if(bText==">" && aPort.portList!=null && aPort.portList.length>0){
aPort.displayedPort=(aPort.displayedPort+1)%aPort.portList.length;
portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200));
portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50));
return;
} // >
if(bText=="<" && aPort.portList!=null && aPort.portList.length>0){
if(--aPort.displayedPort<0)
aPort.displayedPort=aPort.portList.length-1;
portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200));
portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50));
return;
} // <
if(bText=="CONNECT" && aPort.portList!=null && aPort.portList.length>0){
arduinoPortXML.setContent(aPort.portList[aPort.displayedPort]);
portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200));
saveXML(dccStatusXML,STATUS_FILE);
baseID=null;
aPort.open(arduinoPortXML.getContent());
return;
} // <
} // pressed
} // PortScanButton Class
//////////////////////////////////////////////////////////////////////////
// ArduinoPort
//////////////////////////////////////////////////////////////////////////
class ArduinoPort{
Serial port;
Client client;
String[] portList;
int displayedPort;
boolean emulate;
String portName;
int baud;
ArduinoPort(){
emulate=false;
port=null;
client=null;
}
//////////////////////////////////////////////////////////////////////////
void write(String text){
msgBoxDiagOut.setMessage(text,color(30,30,150));
if(emulate)
simulate(text);
else if(port!=null)
port.write(text);
else if(client!=null)
client.write(text);
} // write
//////////////////////////////////////////////////////////////////////////
void simulate(String text){
String c = text.substring(2,text.length()-1);
switch(text.charAt(1)){
case 'c':
if(powerButton.isOn)
receivedString("<a150>");
else
receivedString("<a10>");
break;
case '0':
receivedString("<p0>");
break;
case '1':
receivedString("<p1>");
break;
case 't':
String[] s = splitTokens(c);
if(int(s[2])==-1)
s[2]="0";
receivedString("<T"+s[0]+" "+s[2]+" "+s[3]+">");
break;
case 'T':
String[] s1 = splitTokens(c);
receivedString("<H"+s1[0]+" "+s1[1]+">");
break;
case 'z':
String[] s2 = splitTokens(c);
receivedString("<Z"+s2[0]+" "+s2[1]+">");
break;
} //switch
} // simulate
//////////////////////////////////////////////////////////////////////////
void open(String portName){
int t;
this.portName=portName;
emulate=false;
if(port!=null)
port.stop();
if(client!=null)
client.stop();
int[] n=int(splitTokens(portName,"."));
if(n.length==4 && n[0]>0 && n[0]<=255 && n[1]>=0 && n[1]<=255 && n[2]>=0 && n[2]<=255 && n[3]>=0 && n[3]<=255){
client=new Client(Applet,portName,2560);
if(client.ip()==null){
msgBoxMain.setMessage("Can't connect to Server: "+portName,color(200,50,0));
client=null;
return;
} else if(client!=null){
msgBoxMain.setMessage("Waiting for Base Station at Server: "+client.ip(),color(200,50,0));
client.write("<s>");
return;
}
}
if(portName.equals("Emulator")){
emulate=true;
msgBoxMain.setMessage("Using Emulator to Simulate Arduino",color(50,50,200));
return;
}
try{
port=new Serial(Applet,portName,BASE_BAUD);
port.bufferUntil('>');
} catch(Exception e){
msgBoxMain.setMessage("Serial Port Busy: "+portName,color(200,50,0));
port=null;
return;
}
if(port.port==null){
msgBoxMain.setMessage("Can't find Serial Port: "+portName,color(200,50,0));
port=null;
return;
}
msgBoxMain.setMessage("Waiting for Base Station at Serial Port: "+portName,color(200,50,0));
t=millis();
while(millis()-t<3000);
port.write("<s>");
} // open()
} // Class ArduinoPort
//////////////////////////////////////////////////////////////////////////