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
//////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,58 @@
Whats DCC++
------------
DCC++ is an open-source hardware and software system for the operation of DCC-equipped model railroads.
The system consists of two parts, the DCC++ Base Station and the DCC++ Controller.
The DCC++ Base Station consists of an Arduino micro controller fitted with an Arduino Motor Shield that can be connected directly to the tracks of a model railroad.
The DCC++ Controller provides operators with a customizable GUI to control their model railroad. It is written in Java using the Processing graphics library and IDE and communicates with the DCC++ Base Station via a standard serial connection over a USB cable or wireless over BlueTooth.
Whats in this Repository
-------------------------
This repository, Controller, contains a complete DCC++ Graphical User Interface sketch, written in Java, and designed for use within the Processing IDE environment (www.processing.org). All sketch files are in the folder named DCCpp_Controller.
To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Controller.pde within the DCCpp_Controller folder using your Processing IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Processing IDE relies on the structure and name of the folder to properly display and run the code.
Though this code base is relatively mature and has been tested with the latest version of Processing (3.0.1), it is not as well-commented or documented as the DCC++ Base Station code for the Arduino.
Before using this code, you may wish to visit my DCC++ YouTube channel (see link on main DCC++ GitHub screen) and watch the demo videos showing all the features and functions of this interface.
Use and Customization
---------------------
DCC++ Controller can be used with or withouth a connection to a DCC++ Base Station, though obviously without a Base Station you won't be able to control a model railroad. However, you would still be able to test out the interface, modify the layout, create turnouts, add and delete cabs, throttles, etc.
All main operating functions are found on the main screen. Hitting 'h' toggles a help window on and off that contains a list of all other windows that can be opened with similar single-key toggling. You can also toggle the help window via the question mark in the upper right corner of the screen.
To connect the DCC++ Controller to a DCC++ Arduino Base Station, first connect the Base Station to your PC or Mac via its USB cable. Then open and run the DCC++ Controller within the Processing Environment. Hitting 's' will bring up a serial connection window. Hit the SCAN button and the interface will identify all available serial ports. Use the arrow keys to select which port contains your DCC++ Arduino Base Station and then hit the CONNECT button. After 5 or so seconds, a message should appear at the top of the screen indicating connectivity. If not, please re-check your serial connection and make sure you don't have the Arduino IDE Serial Monitor (or any other serial monitor) opened and connected to the Base Station. This will block the Controller from connecting to the Arduino since only one serial connection to the Arduino can be opened at a time.
If you do not have an Arduino Base Station, or just want to test out the Controller, you can select "Emulator" from the serial connection window. This will allow the Controller to operate most functions as if it were connected to a Base Station.
Note that most of the functions on the Controller rely on feedback from the Base Station in order to operate. This is why the imbedded "Emulator" functionality is needed -- to provide emulated feedback to the Controller.
If you sucessfully connect the Controller to the Base Station, the first thing you may want to test is the "Power" button. This should turn on and off power to the tracks. If the Power button lights up when you press it, this means the Controller is properly communicating with the Base Station since the Power button won't light until it receives a confirm from the Base Station.
I have pre-programmed 7 cabs and all of their functions into a single throttle. You should be able to select any cab button and control the throttle. However, unless your cab numbers happen to match one of the 7 I have included, you will not be able to operate any of your trains. Almost all of the code you will need to customize for your own layout can be found in the "controllerConfig" tab of the Processing IDE. Definitions of the throttle, the cabs, and the cab buttons can be found starting at line 283. The first cab you'll see defined is #2004 in the following line:
cab2004 = new CabButton(tAx-125,tAy-150,50,30,150,15,2004,throttleA);
It's okay to leave the name of the variable as cab2004 -- it could be called anything. The actual cab number is provided in the second-to-last parameter. Change this from 2004 to match the cab number for one of your locomotives. Then restart the program (you don't have to restart Processing itself, just the Controller program). Controller should have remembered your serial settings from before so you wont have to go through the serial scan and connect every time, unless you want to make a change.
Hit the Power button and verifify that it lights up. Then hit the cab button that now should show the cab number you just modified. Give the throttle a try. If all is well, your train should now be moving.
Starting at around line 365 in “configController” you'll find all the code that creates the track layout. You should be able to modify these to match your own. The code under the "dTracks" tab should provide some info on the parameters.
Starting at around line 507 you'll find the code for the turnouts. The routines supporting these functions can be found in the "dRoutes" tab. Note that each turnout has a uniquely defined ID number. These numbers must match the ID number of turnouts you defined in the Arduino DCC++ Base Station sketch. If not, the turnout will not respond on the interface when you click it to switch direction (and obviously will not respond on your layout). You can define a turnout in the Base Station sketch even if it is not really connected to an accessory decoder, if you'd like to simply test the Controller functionality.
This is a rather complex code base and it's definitely not as clean and tight as the Base Station sketch, but I hope you'll be able to get the gist of things by changing individual parameters and observing the net effect.
Ideally, if others start to utilize this Controller, it would probably make sense to move the customization of the cabs and layout into an XML or JSON parameters file. A good project for the future...
Enjoy!