Initialisation depot
106
DCC-Centrale/Firmware/.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
|
||||
- [ ] Verify that the copilot-instructions.md file in the .github directory is created.
|
||||
|
||||
- [ ] Clarify Project Requirements
|
||||
<!-- Ask for project type, language, and frameworks if not specified. Skip if already provided. -->
|
||||
|
||||
- [ ] Scaffold the Project
|
||||
<!--
|
||||
Ensure that the previous step has been marked as completed.
|
||||
Call project setup tool with projectType parameter.
|
||||
Run scaffolding command to create project files and folders.
|
||||
Use '.' as the working directory.
|
||||
If no appropriate projectType is available, search documentation using available tools.
|
||||
Otherwise, create the project structure manually using available file creation tools.
|
||||
-->
|
||||
|
||||
- [ ] Customize the Project
|
||||
<!--
|
||||
Verify that all previous steps have been completed successfully and you have marked the step as completed.
|
||||
Develop a plan to modify codebase according to user requirements.
|
||||
Apply modifications using appropriate tools and user-provided references.
|
||||
Skip this step for "Hello World" projects.
|
||||
-->
|
||||
|
||||
- [ ] Install Required Extensions
|
||||
<!-- ONLY install extensions provided mentioned in the get_project_setup_info. Skip this step otherwise and mark as completed. -->
|
||||
|
||||
- [ ] Compile the Project
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Install any missing dependencies.
|
||||
Run diagnostics and resolve any issues.
|
||||
Check for markdown files in project folder for relevant instructions on how to do this.
|
||||
-->
|
||||
|
||||
- [ ] Create and Run Task
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Check https://code.visualstudio.com/docs/debugtest/tasks to determine if the project needs a task. If so, use the create_and_run_task to create and launch a task based on package.json, README.md, and project structure.
|
||||
Skip this step otherwise.
|
||||
-->
|
||||
|
||||
- [ ] Launch the Project
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Prompt user for debug mode, launch only if confirmed.
|
||||
-->
|
||||
|
||||
- [ ] Ensure Documentation is Complete
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Verify that README.md and the copilot-instructions.md file in the .github directory exists and contains current project information.
|
||||
Clean up the copilot-instructions.md file in the .github directory by removing all HTML comments.
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Execution Guidelines
|
||||
PROGRESS TRACKING:
|
||||
- If any tools are available to manage the above todo list, use it to track progress through this checklist.
|
||||
- After completing each step, mark it complete and add a summary.
|
||||
- Read current todo list status before starting each new step.
|
||||
|
||||
COMMUNICATION RULES:
|
||||
- Avoid verbose explanations or printing full command outputs.
|
||||
- If a step is skipped, state that briefly (e.g. "No extensions needed").
|
||||
- Do not explain project structure unless asked.
|
||||
- Keep explanations concise and focused.
|
||||
|
||||
DEVELOPMENT RULES:
|
||||
- Use '.' as the working directory unless user specifies otherwise.
|
||||
- Avoid adding media or external links unless explicitly requested.
|
||||
- Use placeholders only with a note that they should be replaced.
|
||||
- Use VS Code API tool only for VS Code extension projects.
|
||||
- Once the project is created, it is already opened in Visual Studio Code—do not suggest commands to open this project in Visual Studio again.
|
||||
- If the project setup information has additional rules, follow them strictly.
|
||||
|
||||
FOLDER CREATION RULES:
|
||||
- Always use the current directory as the project root.
|
||||
- If you are running any terminal commands, use the '.' argument to ensure that the current working directory is used ALWAYS.
|
||||
- Do not create a new folder unless the user explicitly requests it besides a .vscode folder for a tasks.json file.
|
||||
- If any of the scaffolding commands mention that the folder name is not correct, let the user know to create a new folder with the correct name and then reopen it again in vscode.
|
||||
|
||||
EXTENSION INSTALLATION RULES:
|
||||
- Only install extension specified by the get_project_setup_info tool. DO NOT INSTALL any other extensions.
|
||||
|
||||
PROJECT CONTENT RULES:
|
||||
- If the user has not specified project details, assume they want a "Hello World" project as a starting point.
|
||||
- Avoid adding links of any type (URLs, files, folders, etc.) or integrations that are not explicitly required.
|
||||
- Avoid generating images, videos, or any other media files unless explicitly requested.
|
||||
- If you need to use any media assets as placeholders, let the user know that these are placeholders and should be replaced with the actual assets later.
|
||||
- Ensure all generated components serve a clear purpose within the user's requested workflow.
|
||||
- If a feature is assumed but not confirmed, prompt the user for clarification before including it.
|
||||
- If you are working on a VS Code extension, use the VS Code API tool with a query to find relevant VS Code API references and samples related to that query.
|
||||
|
||||
TASK COMPLETION RULES:
|
||||
- Your task is complete when:
|
||||
- Project is successfully scaffolded and compiled without errors
|
||||
- copilot-instructions.md file in the .github directory exists in the project
|
||||
- README.md file exists and is up to date
|
||||
- User is provided with clear instructions to debug/launch the project
|
||||
|
||||
Before starting a new task in the above plan, update progress in the plan.
|
||||
-->
|
||||
- Work through each checklist item systematically.
|
||||
- Keep communication concise and focused.
|
||||
- Follow development best practices.
|
||||
BIN
DCC-Centrale/Firmware/BaseStation/DCC++ Arduino Sketch.pdf
Normal file
239
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/**********************************************************************
|
||||
|
||||
Accessories.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION can keep track of the direction of any turnout that is controlled
|
||||
by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories
|
||||
connected in this fashion, can always be operated using the DCC BASE STATION Accessory command:
|
||||
|
||||
<a ADDRESS SUBADDRESS ACTIVATE>
|
||||
|
||||
However, this general command simply sends the appropriate DCC instruction packet to the main tracks
|
||||
to operate connected accessories. It does not store or retain any information regarding the current
|
||||
status of that accessory.
|
||||
|
||||
To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically
|
||||
invoke the required <a> command as needed, first define/edit/delete such turnouts using the following
|
||||
variations of the "T" command:
|
||||
|
||||
<T ID ADDRESS SUBADDRESS>: creates a new turnout ID, with specified ADDRESS and SUBADDRESS
|
||||
if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<T ID>: deletes definition of turnout ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<T>: lists all defined turnouts
|
||||
returns: <H ID ADDRESS SUBADDRESS THROW> for each defined turnout or <X> if no turnouts defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
ADDRESS: the primary address of the decoder controlling this turnout (0-511)
|
||||
SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3)
|
||||
|
||||
Once all turnouts have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the turnout definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
To "throw" turnouts that have been defined use:
|
||||
|
||||
<T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position
|
||||
returns: <H ID THROW>, or <X> if turnout ID does not exist
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
THROW: 0 (unthrown) or 1 (thrown)
|
||||
|
||||
When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so
|
||||
that it is retained even without power. A list of the current directions of each Turnout in the form <H ID THROW> is generated
|
||||
by this sketch whenever the <s> status command is invoked. This provides an efficient way of initializing
|
||||
the directions of any Turnouts being monitored or controlled by a separate interface or GUI program.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Accessories.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::activate(int s){
|
||||
char c[20];
|
||||
data.tStatus=(s>0); // if s>0 set turnout=ON, else if zero or negative set turnout=OFF
|
||||
sprintf(c,"a %d %d %d",data.address,data.subAddress,data.tStatus);
|
||||
SerialCommand::parse(c);
|
||||
if(num>0)
|
||||
EEPROM.put(num,data.tStatus);
|
||||
INTERFACE.print("<H");
|
||||
INTERFACE.print(data.id);
|
||||
if(data.tStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout* Turnout::get(int n){
|
||||
Turnout *tt;
|
||||
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::remove(int n){
|
||||
Turnout *tt,*pp;
|
||||
|
||||
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstTurnout)
|
||||
firstTurnout=tt->nextTurnout;
|
||||
else
|
||||
pp->nextTurnout=tt->nextTurnout;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::show(int n){
|
||||
Turnout *tt;
|
||||
|
||||
if(firstTurnout==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){
|
||||
INTERFACE.print("<H");
|
||||
INTERFACE.print(tt->data.id);
|
||||
if(n==1){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.address);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.subAddress);
|
||||
}
|
||||
if(tt->data.tStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::parse(char *c){
|
||||
int n,s,m;
|
||||
Turnout *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown)
|
||||
t=get(n);
|
||||
if(t!=NULL)
|
||||
t->activate(s);
|
||||
else
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
|
||||
case 3: // argument is string with id number of turnout followed by an address and subAddress
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show(1); // verbose show
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::load(){
|
||||
struct TurnoutData data;
|
||||
Turnout *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nTurnouts;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.id,data.address,data.subAddress);
|
||||
tt->data.tStatus=data.tStatus;
|
||||
tt->num=EEStore::pointer();
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::store(){
|
||||
Turnout *tt;
|
||||
|
||||
tt=firstTurnout;
|
||||
EEStore::eeStore->data.nTurnouts=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
tt->num=EEStore::pointer();
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextTurnout;
|
||||
EEStore::eeStore->data.nTurnouts++;
|
||||
}
|
||||
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout *Turnout::create(int id, int add, int subAdd, int v){
|
||||
Turnout *tt;
|
||||
|
||||
if(firstTurnout==NULL){
|
||||
firstTurnout=(Turnout *)calloc(1,sizeof(Turnout));
|
||||
tt=firstTurnout;
|
||||
} else if((tt=get(id))==NULL){
|
||||
tt=firstTurnout;
|
||||
while(tt->nextTurnout!=NULL)
|
||||
tt=tt->nextTurnout;
|
||||
tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout));
|
||||
tt=tt->nextTurnout;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.id=id;
|
||||
tt->data.address=add;
|
||||
tt->data.subAddress=subAdd;
|
||||
tt->data.tStatus=0;
|
||||
if(v==1)
|
||||
INTERFACE.print("<O>");
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout *Turnout::firstTurnout=NULL;
|
||||
|
||||
|
||||
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
Accessories.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifndef Accessories_h
|
||||
#define Accessories_h
|
||||
|
||||
struct TurnoutData {
|
||||
byte tStatus;
|
||||
byte subAddress;
|
||||
int id;
|
||||
int address;
|
||||
};
|
||||
|
||||
struct Turnout{
|
||||
static Turnout *firstTurnout;
|
||||
int num;
|
||||
struct TurnoutData data;
|
||||
Turnout *nextTurnout;
|
||||
void activate(int s);
|
||||
static void parse(char *c);
|
||||
static Turnout* get(int);
|
||||
static void remove(int);
|
||||
static void load();
|
||||
static void store();
|
||||
static Turnout *create(int, int, int, int=0);
|
||||
static void show(int=0);
|
||||
}; // Turnout
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
|
||||
Comm.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#if COMM_TYPE == 1 // Ethernet Shield Card Selected
|
||||
|
||||
#if COMM_INTERFACE == 1
|
||||
#define COMM_SHIELD_NAME "ARDUINO-CC ETHERNET SHIELD (WIZNET 5100)"
|
||||
#include <Ethernet.h> // built-in Arduino.cc library
|
||||
|
||||
#elif COMM_INTERFACE == 2
|
||||
#define COMM_SHIELD_NAME "ARDUINO-ORG ETHERNET-2 SHIELD (WIZNET 5500)"
|
||||
#include <Ethernet2.h> // https://github.com/arduino-org/Arduino
|
||||
|
||||
#elif COMM_INTERFACE == 3
|
||||
#define COMM_SHIELD_NAME "SEEED STUDIO ETHERNET SHIELD (WIZNET 5200)"
|
||||
#include <EthernetV2_0.h> // https://github.com/Seeed-Studio/Ethernet_Shield_W5200
|
||||
|
||||
#endif
|
||||
|
||||
extern EthernetServer INTERFACE;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
58
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**********************************************************************
|
||||
|
||||
Config.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE:
|
||||
//
|
||||
// 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL)
|
||||
// 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL)
|
||||
|
||||
#define MOTOR_SHIELD_TYPE 0
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE NUMBER OF MAIN TRACK REGISTER
|
||||
|
||||
#define MAX_MAIN_REGISTERS 12
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE COMMUNICATIONS INTERFACE
|
||||
//
|
||||
// 0 = Built-in Serial Port
|
||||
// 1 = Arduino.cc Ethernet/SD-Card Shield
|
||||
// 2 = Arduino.org Ethernet/SD-Card Shield
|
||||
// 3 = Seeed Studio Ethernet/SD-Card Shield W5200
|
||||
|
||||
#define COMM_INTERFACE 0
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
|
||||
//
|
||||
|
||||
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||
//
|
||||
|
||||
#define ETHERNET_PORT 2560
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||
//
|
||||
|
||||
#define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
CurrentMonitor.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "CurrentMonitor.h"
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CurrentMonitor::CurrentMonitor(int pin, char *msg){
|
||||
this->pin=pin;
|
||||
this->msg=msg;
|
||||
current=0;
|
||||
} // CurrentMonitor::CurrentMonitor
|
||||
|
||||
boolean CurrentMonitor::checkTime(){
|
||||
if(millis()-sampleTime<CURRENT_SAMPLE_TIME) // no need to check current yet
|
||||
return(false);
|
||||
sampleTime=millis(); // note millis() uses TIMER-0. For UNO, we change the scale on Timer-0. For MEGA we do not. This means CURENT_SAMPLE_TIME is different for UNO then MEGA
|
||||
return(true);
|
||||
} // CurrentMonitor::checkTime
|
||||
|
||||
void CurrentMonitor::check(){
|
||||
current=analogRead(pin)*CURRENT_SAMPLE_SMOOTHING+current*(1.0-CURRENT_SAMPLE_SMOOTHING); // compute new exponentially-smoothed current
|
||||
if(current>CURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){ // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together)
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); // disable both Motor Shield Channels
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); // regardless of which caused current overload
|
||||
INTERFACE.print(msg); // print corresponding error message
|
||||
}
|
||||
} // CurrentMonitor::check
|
||||
|
||||
long int CurrentMonitor::sampleTime=0;
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**********************************************************************
|
||||
|
||||
CurrentMonitor.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef CurrentMonitor_h
|
||||
#define CurrentMonitor_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define CURRENT_SAMPLE_SMOOTHING 0.01
|
||||
#define CURRENT_SAMPLE_MAX 300
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
#define CURRENT_SAMPLE_TIME 10
|
||||
#else // Configuration for MEGA
|
||||
#define CURRENT_SAMPLE_TIME 1
|
||||
#endif
|
||||
|
||||
struct CurrentMonitor{
|
||||
static long int sampleTime;
|
||||
int pin;
|
||||
float current;
|
||||
char *msg;
|
||||
CurrentMonitor(int, char *);
|
||||
static boolean checkTime();
|
||||
void check();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
135
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**********************************************************************
|
||||
|
||||
DCCpp_Uno.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#ifndef DCCpp_Uno_h
|
||||
#define DCCpp_Uno_h
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// RELEASE VERSION
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define VERSION "1.2.1+"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// AUTO-SELECT ARDUINO BOARD
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
|
||||
#define ARDUINO_AVR_MEGA2560
|
||||
#endif
|
||||
|
||||
#if defined ARDUINO_AVR_UNO
|
||||
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
|
||||
#define DCC_SIGNAL_PIN_MAIN 10 // Ardunio Uno - uses OC1B
|
||||
#define DCC_SIGNAL_PIN_PROG 5 // Arduino Uno - uses OC0B
|
||||
|
||||
#if COMM_INTERFACE != 0 // Serial was not selected
|
||||
|
||||
#error CANNOT COMPILE - DCC++ FOR THE UNO CAN ONLY USE SERIAL COMMUNICATION - PLEASE SELECT THIS IN THE CONFIG FILE
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined ARDUINO_AVR_MEGA2560
|
||||
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
|
||||
#define DCC_SIGNAL_PIN_MAIN 12 // Arduino Mega - uses OC1B
|
||||
#define DCC_SIGNAL_PIN_PROG 2 // Arduino Mega - uses OC3B
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - DCC++ ONLY WORKS WITH AN ARDUINO UNO OR AN ARDUINO MEGA 1280/2560
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SELECT MOTOR SHIELD
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if MOTOR_SHIELD_TYPE == 0
|
||||
|
||||
#define MOTOR_SHIELD_NAME "ARDUINO MOTOR SHIELD"
|
||||
|
||||
#define SIGNAL_ENABLE_PIN_MAIN 3
|
||||
#define SIGNAL_ENABLE_PIN_PROG 11
|
||||
|
||||
#define CURRENT_MONITOR_PIN_MAIN A0
|
||||
#define CURRENT_MONITOR_PIN_PROG A1
|
||||
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_A 12
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_B 13
|
||||
|
||||
#elif MOTOR_SHIELD_TYPE == 1
|
||||
|
||||
#define MOTOR_SHIELD_NAME "POLOLU MC33926 MOTOR SHIELD"
|
||||
|
||||
#define SIGNAL_ENABLE_PIN_MAIN 9
|
||||
#define SIGNAL_ENABLE_PIN_PROG 11
|
||||
|
||||
#define CURRENT_MONITOR_PIN_MAIN A0
|
||||
#define CURRENT_MONITOR_PIN_PROG A1
|
||||
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_A 7
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_B 8
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - PLEASE SELECT A PROPER MOTOR SHIELD TYPE
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SELECT COMMUNICATION INTERACE
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if COMM_INTERFACE == 0
|
||||
|
||||
#define COMM_TYPE 0
|
||||
#define INTERFACE Serial
|
||||
|
||||
#elif (COMM_INTERFACE==1) || (COMM_INTERFACE==2) || (COMM_INTERFACE==3)
|
||||
|
||||
#define COMM_TYPE 1
|
||||
#define INTERFACE eServer
|
||||
#define SDCARD_CS 4
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - Please select a proper value for COMM_INTERFACE in CONFIG.H file
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SET WHETHER TO SHOW PACKETS - DIAGNOSTIC MODE ONLY
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// If SHOW_PACKETS is set to 1, then for select main operations track commands that modify an internal DCC packet register,
|
||||
// if printFlag for that command is also set to 1, DCC++ BASE STATION will additionally return the
|
||||
// DCC packet contents of the modified register in the following format:
|
||||
|
||||
// <* REG: B1 B2 ... Bn CSUM / REPEAT>
|
||||
//
|
||||
// REG: the number of the main operations track packet register that was modified
|
||||
// B1: the first hexidecimal byte of the DCC packet
|
||||
// B2: the second hexidecimal byte of the DCC packet
|
||||
// Bn: the nth hexidecimal byte of the DCC packet
|
||||
// CSUM: a checksum byte that is required to be the final byte in any DCC packet
|
||||
// REPEAT: the number of times the DCC packet was re-transmitted to the tracks after its iniital transmission
|
||||
|
||||
#define SHOW_PACKETS 0 // set to zero to disable printing of every packet for select main operations track commands
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
563
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino
Normal file
@@ -0,0 +1,563 @@
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION
|
||||
COPYRIGHT (c) 2013-2016 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++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega
|
||||
using the Arduino IDE 1.6.6.
|
||||
|
||||
It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others)
|
||||
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.
|
||||
|
||||
This version of DCC++ BASE STATION supports:
|
||||
|
||||
* 2-byte and 4-byte locomotive addressing
|
||||
* Simultaneous control of multiple locomotives
|
||||
* 128-step speed throttling
|
||||
* Cab functions F0-F28
|
||||
* Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses
|
||||
- includes optional functionailty to monitor and store of the direction of any connected turnouts
|
||||
* 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
|
||||
- set/clear specific configuration variable bits
|
||||
- read configuration variable bytes
|
||||
|
||||
DCC++ BASE STATION is controlled with simple text commands received via
|
||||
the Arduino's serial interface. Users can type these commands directly
|
||||
into the Arduino IDE Serial Monitor, or can send such commands from another
|
||||
device or computer program.
|
||||
|
||||
When compiled for the Arduino Mega, an Ethernet Shield can be used for network
|
||||
communications instead of using serial communications.
|
||||
|
||||
DCC++ CONTROLLER, available separately under a similar open-source
|
||||
license, is a Java program written using the Processing library and Processing IDE
|
||||
that provides a complete and configurable graphic interface to control model train layouts
|
||||
via the DCC++ BASE STATION.
|
||||
|
||||
With the exception of a standard 15V power supply 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/index-nmra-standards-and-recommended-practices
|
||||
Arduino: http://www.arduino.cc/
|
||||
Processing: http://processing.org/
|
||||
GNU General Public License: http://opensource.org/licenses/GPL-3.0
|
||||
|
||||
BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION:
|
||||
|
||||
DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0,
|
||||
and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V
|
||||
unipolar signals that each properly encode zero and one bits conforming with
|
||||
DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B.
|
||||
|
||||
Series of DCC bit streams are bundled into Packets that each form the basis of
|
||||
a standard DCC instruction. Packets are stored in Packet Registers that contain
|
||||
methods for updating and queuing according to text commands sent by the user
|
||||
(or another program) over the serial interface. There is one set of registers that controls
|
||||
the main operations track and one that controls the programming track.
|
||||
|
||||
For the main operations track, packets to store cab throttle settings are stored in
|
||||
registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp_Uno.h).
|
||||
It is generally considered good practice to continuously send throttle control packets
|
||||
to every cab so that if an engine should momentarily lose electrical connectivity with the tracks,
|
||||
it will very quickly receive another throttle control signal as soon as connectivity is
|
||||
restored (such as when a trin passes over rough portion of track or the frog of a turnout).
|
||||
|
||||
DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter
|
||||
that has been loaded with a throttle control setting for a given cab. For each register, it
|
||||
transmits the appropriate DCC packet bits to the track, then moves onto the next register
|
||||
without any pausing to ensure continuous bi-polar power is being provided to the tracks.
|
||||
Updates to the throttle setting stored in any given packet register are done in a double-buffered
|
||||
fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits
|
||||
can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal.
|
||||
The cabs identified in each stored throttle setting should be unique across registers. If two registers
|
||||
contain throttle setting for the same cab, the throttle in the engine will oscillate between the two,
|
||||
which is probably not a desireable outcome.
|
||||
|
||||
For both the main operations track and the programming track there is also a special packet register with id=0
|
||||
that is used to store all other DCC packets that do not require continious transmittal to the tracks.
|
||||
This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables.
|
||||
It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure
|
||||
proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet
|
||||
and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself.
|
||||
Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of
|
||||
verifying proper transmittal before acting on the instructions contained in those packets
|
||||
|
||||
An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached
|
||||
on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals
|
||||
produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals.
|
||||
|
||||
This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10)
|
||||
to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect
|
||||
the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13).
|
||||
|
||||
For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the
|
||||
Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2)
|
||||
to the Motor Shield's DIRECTION B input (pin 13).
|
||||
|
||||
Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp_Uno.h for details).
|
||||
|
||||
When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be
|
||||
connected directly to the tracks. This software assumes CHANNEL A is connected
|
||||
to the Main Operations Track, and CHANNEL B is connected to the Programming Track.
|
||||
|
||||
DCC++ BASE STATION in split into multiple modules, each with its own header file:
|
||||
|
||||
DCCpp_Uno: declares required global objects and contains initial Arduino setup()
|
||||
and Arduino loop() functions, as well as interrput code for OC0B and OC1B.
|
||||
Also includes declarations of optional array of Turn-Outs and optional array of Sensors
|
||||
|
||||
SerialCommand: contains methods to read and interpret text commands from the serial line,
|
||||
process those instructions, and, if necessary call appropriate Packet RegisterList methods
|
||||
to update either the Main Track or Programming Track Packet Registers
|
||||
|
||||
PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions
|
||||
|
||||
CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and
|
||||
CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload
|
||||
is detected
|
||||
|
||||
Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled
|
||||
by a DCC stationary accessory decoder.
|
||||
|
||||
Sensor: contains methods to monitor and report on the status of optionally-defined infrared
|
||||
sensors embedded in the Main Track and connected to various pins on the Arudino Uno
|
||||
|
||||
Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use
|
||||
|
||||
EEStore: contains methods to store, update, and load various DCC settings and status
|
||||
(e.g. the states of all defined turnouts) in the EEPROM for recall after power-up
|
||||
|
||||
DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "PacketRegister.h"
|
||||
#include "CurrentMonitor.h"
|
||||
#include "Sensor.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "Accessories.h"
|
||||
#include "EEStore.h"
|
||||
#include "Config.h"
|
||||
#include "Comm.h"
|
||||
|
||||
void showConfiguration();
|
||||
|
||||
// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE
|
||||
|
||||
#if COMM_TYPE == 1
|
||||
byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server)
|
||||
EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer
|
||||
#endif
|
||||
|
||||
// NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS.
|
||||
// NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES
|
||||
|
||||
volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets
|
||||
volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets
|
||||
|
||||
CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,"<p2>"); // create monitor for current on Main Track
|
||||
CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,"<p3>"); // create monitor for current on Program Track
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MAIN ARDUINO LOOP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void loop(){
|
||||
|
||||
SerialCommand::process(); // check for, and process, and new serial commands
|
||||
|
||||
if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
|
||||
mainMonitor.check();
|
||||
progMonitor.check();
|
||||
}
|
||||
|
||||
Sensor::check(); // check sensors for activate/de-activate
|
||||
|
||||
} // loop
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// INITIAL SETUP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void setup(){
|
||||
|
||||
Serial.begin(115200); // configure serial interface
|
||||
Serial.flush();
|
||||
|
||||
#ifdef SDCARD_CS
|
||||
pinMode(SDCARD_CS,OUTPUT);
|
||||
digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card
|
||||
#endif
|
||||
|
||||
EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM
|
||||
|
||||
pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt
|
||||
digitalWrite(A5,HIGH);
|
||||
if(!digitalRead(A5))
|
||||
showConfiguration();
|
||||
|
||||
Serial.print("<iDCC++ BASE STATION FOR ARDUINO "); // Print Status to Serial Line regardless of COMM_TYPE setting so use can open Serial Monitor and check configurtion
|
||||
Serial.print(ARDUINO_TYPE);
|
||||
Serial.print(" / ");
|
||||
Serial.print(MOTOR_SHIELD_NAME);
|
||||
Serial.print(": V-");
|
||||
Serial.print(VERSION);
|
||||
Serial.print(" / ");
|
||||
Serial.print(__DATE__);
|
||||
Serial.print(" ");
|
||||
Serial.print(__TIME__);
|
||||
Serial.print(">");
|
||||
|
||||
#if COMM_TYPE == 1
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address
|
||||
#else
|
||||
Ethernet.begin(mac); // Start networking using DHCP to get an IP Address
|
||||
#endif
|
||||
INTERFACE.begin();
|
||||
#endif
|
||||
|
||||
SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line
|
||||
|
||||
Serial.print("<N");
|
||||
Serial.print(COMM_TYPE);
|
||||
Serial.print(": ");
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
Serial.print("SERIAL>");
|
||||
#elif COMM_TYPE == 1
|
||||
Serial.print(Ethernet.localIP());
|
||||
Serial.print(">");
|
||||
#endif
|
||||
|
||||
// CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS
|
||||
|
||||
// Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK
|
||||
// Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin
|
||||
// Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A
|
||||
|
||||
bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A
|
||||
bitSet(TCCR1A,WGM11);
|
||||
bitSet(TCCR1B,WGM12);
|
||||
bitSet(TCCR1B,WGM13);
|
||||
|
||||
bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR1A,COM1B0);
|
||||
|
||||
bitClear(TCCR1B,CS12); // set Timer 1 prescale=1
|
||||
bitClear(TCCR1B,CS11);
|
||||
bitSet(TCCR1B,CS10);
|
||||
|
||||
OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1;
|
||||
OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A
|
||||
|
||||
mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B)
|
||||
|
||||
// CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
|
||||
// Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin
|
||||
// Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with as-close-as-possible to 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
|
||||
|
||||
bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A
|
||||
bitSet(TCCR0A,WGM01);
|
||||
bitSet(TCCR0B,WGM02);
|
||||
|
||||
bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR0A,COM0B0);
|
||||
|
||||
bitClear(TCCR0B,CS02); // set Timer 0 prescale=64
|
||||
bitSet(TCCR0B,CS01);
|
||||
bitSet(TCCR0B,CS00);
|
||||
|
||||
OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0;
|
||||
OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
|
||||
|
||||
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B)
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
|
||||
// Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin
|
||||
// Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
|
||||
|
||||
bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A
|
||||
bitSet(TCCR3A,WGM31);
|
||||
bitSet(TCCR3B,WGM32);
|
||||
bitSet(TCCR3B,WGM33);
|
||||
|
||||
bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR3A,COM3B0);
|
||||
|
||||
bitClear(TCCR3B,CS32); // set Timer 3 prescale=1
|
||||
bitClear(TCCR3B,CS31);
|
||||
bitSet(TCCR3B,CS30);
|
||||
|
||||
OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3;
|
||||
OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
|
||||
|
||||
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B)
|
||||
|
||||
#endif
|
||||
|
||||
} // setup
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
|
||||
// It is designed to read the current bit of the current register packet and
|
||||
// updates the OCNA and OCNB counters of Timer-N to values that will either produce
|
||||
// a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
|
||||
// DCC ZERO and DCC ONE bits.
|
||||
|
||||
// These are hardware-driven interrupts that will be called automatically when triggered regardless of what
|
||||
// DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled.
|
||||
// Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
|
||||
// (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
|
||||
// in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic
|
||||
// of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB
|
||||
// registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
|
||||
// interrupt code completes and can be called again.
|
||||
|
||||
// A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
|
||||
// DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
|
||||
|
||||
// Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
|
||||
// Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro
|
||||
// that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating
|
||||
// some of the logic for each interrupt, but saves additional time.
|
||||
|
||||
// As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
|
||||
// when a new register is loaded and the logic needs to switch active register packet pointers.
|
||||
|
||||
// THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1)
|
||||
|
||||
#define DCC_SIGNAL(R,N) \
|
||||
if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \
|
||||
R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \
|
||||
if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \
|
||||
R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \
|
||||
} else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \
|
||||
R.currentReg=R.nextReg; /* update currentReg to nextReg */ \
|
||||
R.nextReg=NULL; /* reset nextReg to NULL */ \
|
||||
R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \
|
||||
R.currentReg->activePacket=R.currentReg->updatePacket; \
|
||||
R.currentReg->updatePacket=R.tempPacket; \
|
||||
} else{ /* ELSE simply move to next Register */ \
|
||||
if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \
|
||||
R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \
|
||||
R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \
|
||||
} /* END-ELSE */ \
|
||||
} /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \
|
||||
\
|
||||
if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \
|
||||
OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \
|
||||
OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \
|
||||
} else{ /* ELSE it is a ZERO */ \
|
||||
OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \
|
||||
OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \
|
||||
} /* END-ELSE */ \
|
||||
\
|
||||
R.currentBit++; /* point to next bit in current Packet */
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
|
||||
|
||||
ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
|
||||
DCC_SIGNAL(mainRegs,1)
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
|
||||
DCC_SIGNAL(progRegs,0)
|
||||
}
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
|
||||
DCC_SIGNAL(progRegs,3)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE
|
||||
// - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH
|
||||
|
||||
void showConfiguration(){
|
||||
|
||||
int mac_address[]=MAC_ADDRESS;
|
||||
|
||||
Serial.print("\n*** DCC++ CONFIGURATION ***\n");
|
||||
|
||||
Serial.print("\nVERSION: ");
|
||||
Serial.print(VERSION);
|
||||
Serial.print("\nCOMPILED: ");
|
||||
Serial.print(__DATE__);
|
||||
Serial.print(" ");
|
||||
Serial.print(__TIME__);
|
||||
|
||||
Serial.print("\nARDUINO: ");
|
||||
Serial.print(ARDUINO_TYPE);
|
||||
|
||||
Serial.print("\n\nMOTOR SHIELD: ");
|
||||
Serial.print(MOTOR_SHIELD_NAME);
|
||||
|
||||
Serial.print("\n\nDCC SIG MAIN: ");
|
||||
Serial.print(DCC_SIGNAL_PIN_MAIN);
|
||||
Serial.print("\n DIRECTION: ");
|
||||
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A);
|
||||
Serial.print("\n ENABLE: ");
|
||||
Serial.print(SIGNAL_ENABLE_PIN_MAIN);
|
||||
Serial.print("\n CURRENT: ");
|
||||
Serial.print(CURRENT_MONITOR_PIN_MAIN);
|
||||
|
||||
Serial.print("\n\nDCC SIG PROG: ");
|
||||
Serial.print(DCC_SIGNAL_PIN_PROG);
|
||||
Serial.print("\n DIRECTION: ");
|
||||
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B);
|
||||
Serial.print("\n ENABLE: ");
|
||||
Serial.print(SIGNAL_ENABLE_PIN_PROG);
|
||||
Serial.print("\n CURRENT: ");
|
||||
Serial.print(CURRENT_MONITOR_PIN_PROG);
|
||||
|
||||
Serial.print("\n\nNUM TURNOUTS: ");
|
||||
Serial.print(EEStore::eeStore->data.nTurnouts);
|
||||
Serial.print("\n SENSORS: ");
|
||||
Serial.print(EEStore::eeStore->data.nSensors);
|
||||
Serial.print("\n OUTPUTS: ");
|
||||
Serial.print(EEStore::eeStore->data.nOutputs);
|
||||
|
||||
Serial.print("\n\nINTERFACE: ");
|
||||
#if COMM_TYPE == 0
|
||||
Serial.print("SERIAL");
|
||||
#elif COMM_TYPE == 1
|
||||
Serial.print(COMM_SHIELD_NAME);
|
||||
Serial.print("\nMAC ADDRESS: ");
|
||||
for(int i=0;i<5;i++){
|
||||
Serial.print(mac_address[i],HEX);
|
||||
Serial.print(":");
|
||||
}
|
||||
Serial.print(mac_address[5],HEX);
|
||||
Serial.print("\nPORT: ");
|
||||
Serial.print(ETHERNET_PORT);
|
||||
Serial.print("\nIP ADDRESS: ");
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address
|
||||
#else
|
||||
Ethernet.begin(mac); // Start networking using DHCP to get an IP Address
|
||||
#endif
|
||||
|
||||
Serial.print(Ethernet.localIP());
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Serial.print(" (STATIC)");
|
||||
#else
|
||||
Serial.print(" (DHCP)");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO");
|
||||
|
||||
while(true);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
83
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**********************************************************************
|
||||
|
||||
EEStore.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include "Accessories.h"
|
||||
#include "Sensor.h"
|
||||
#include "Outputs.h"
|
||||
#include <EEPROM.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::init(){
|
||||
|
||||
|
||||
eeStore=(EEStore *)calloc(1,sizeof(EEStore));
|
||||
|
||||
EEPROM.get(0,eeStore->data); // get eeStore data
|
||||
|
||||
if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID
|
||||
sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
|
||||
eeStore->data.nTurnouts=0;
|
||||
eeStore->data.nSensors=0;
|
||||
eeStore->data.nOutputs=0;
|
||||
EEPROM.put(0,eeStore->data);
|
||||
}
|
||||
|
||||
reset(); // set memory pointer to first free EEPROM space
|
||||
Turnout::load(); // load turnout definitions
|
||||
Sensor::load(); // load sensor definitions
|
||||
Output::load(); // load output definitions
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::clear(){
|
||||
|
||||
sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
|
||||
eeStore->data.nTurnouts=0;
|
||||
eeStore->data.nSensors=0;
|
||||
eeStore->data.nOutputs=0;
|
||||
EEPROM.put(0,eeStore->data);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::store(){
|
||||
reset();
|
||||
Turnout::store();
|
||||
Sensor::store();
|
||||
Output::store();
|
||||
EEPROM.put(0,eeStore->data);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::advance(int n){
|
||||
eeAddress+=n;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::reset(){
|
||||
eeAddress=sizeof(EEStore);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int EEStore::pointer(){
|
||||
return(eeAddress);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EEStore *EEStore::eeStore=NULL;
|
||||
int EEStore::eeAddress=0;
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**********************************************************************
|
||||
|
||||
EEStore.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef EEStore_h
|
||||
#define EEStore_h
|
||||
|
||||
#define EESTORE_ID "DCC++"
|
||||
|
||||
struct EEStoreData{
|
||||
char id[sizeof(EESTORE_ID)];
|
||||
int nTurnouts;
|
||||
int nSensors;
|
||||
int nOutputs;
|
||||
};
|
||||
|
||||
struct EEStore{
|
||||
static EEStore *eeStore;
|
||||
EEStoreData data;
|
||||
static int eeAddress;
|
||||
static void init();
|
||||
static void reset();
|
||||
static int pointer();
|
||||
static void advance(int);
|
||||
static void store();
|
||||
static void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
256
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
/**********************************************************************
|
||||
|
||||
Outputs.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes.
|
||||
Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW.
|
||||
However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH.
|
||||
|
||||
Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up.
|
||||
The default is to set each defined pin to active or inactive according to its restored state.
|
||||
However, the default behavior can be modified so that any pin can be forced to be either active or inactive
|
||||
upon power-up regardless of its previous state before power-down.
|
||||
|
||||
To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete
|
||||
output definitions using the following variation of the "Z" command:
|
||||
|
||||
<Z ID PIN IFLAG>: creates a new output ID, with specified PIN and IFLAG values.
|
||||
if output ID already exists, it is updated with specificed PIN and IFLAG.
|
||||
note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW
|
||||
according to IFLAG value specifcied (see below).
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<Z ID>: deletes definition of output ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<Z>: lists all defined output pins
|
||||
returns: <Y ID PIN IFLAG STATE> for each defined output pin or <X> if no output pins defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the output
|
||||
PIN: the arduino pin number to use for the output
|
||||
STATE: the state of the output (0=INACTIVE / 1=ACTIVE)
|
||||
IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows:
|
||||
|
||||
IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW)
|
||||
1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH)
|
||||
|
||||
IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending
|
||||
on state before power-down; state of pin set to INACTIVE when first created
|
||||
1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE
|
||||
depending on IFLAG, bit 2
|
||||
|
||||
IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created
|
||||
1 = state of pin set to ACTIVE uponm power-up or when first created
|
||||
|
||||
Once all outputs have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the output definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
To change the state of outputs that have been defined use:
|
||||
|
||||
<Z ID STATE>: sets output ID to either ACTIVE or INACTIVE state
|
||||
returns: <Y ID STATE>, or <X> if turnout ID does not exist
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
STATE: the state of the output (0=INACTIVE / 1=ACTIVE)
|
||||
|
||||
When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so
|
||||
that it is retained even without power. A list of the current states of each output in the form <Y ID STATE> is generated
|
||||
by this sketch whenever the <s> status command is invoked. This provides an efficient way of initializing
|
||||
the state of any outputs being monitored or controlled by a separate interface or GUI program.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Outputs.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::activate(int s){
|
||||
data.oStatus=(s>0); // if s>0, set status to active, else inactive
|
||||
digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
|
||||
if(num>0)
|
||||
EEPROM.put(num,data.oStatus);
|
||||
INTERFACE.print("<Y");
|
||||
INTERFACE.print(data.id);
|
||||
if(data.oStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output* Output::get(int n){
|
||||
Output *tt;
|
||||
for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::remove(int n){
|
||||
Output *tt,*pp;
|
||||
|
||||
for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstOutput)
|
||||
firstOutput=tt->nextOutput;
|
||||
else
|
||||
pp->nextOutput=tt->nextOutput;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::show(int n){
|
||||
Output *tt;
|
||||
|
||||
if(firstOutput==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){
|
||||
INTERFACE.print("<Y");
|
||||
INTERFACE.print(tt->data.id);
|
||||
if(n==1){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pin);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.iFlag);
|
||||
}
|
||||
if(tt->data.oStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::parse(char *c){
|
||||
int n,s,m;
|
||||
Output *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH)
|
||||
t=get(n);
|
||||
if(t!=NULL)
|
||||
t->activate(s);
|
||||
else
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
|
||||
case 3: // argument is string with id number of output followed by a pin number and invert flag
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show(1); // verbose show
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::load(){
|
||||
struct OutputData data;
|
||||
Output *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nOutputs;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.id,data.pin,data.iFlag);
|
||||
tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
|
||||
digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0));
|
||||
pinMode(tt->data.pin,OUTPUT);
|
||||
tt->num=EEStore::pointer();
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::store(){
|
||||
Output *tt;
|
||||
|
||||
tt=firstOutput;
|
||||
EEStore::eeStore->data.nOutputs=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
tt->num=EEStore::pointer();
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextOutput;
|
||||
EEStore::eeStore->data.nOutputs++;
|
||||
}
|
||||
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output *Output::create(int id, int pin, int iFlag, int v){
|
||||
Output *tt;
|
||||
|
||||
if(firstOutput==NULL){
|
||||
firstOutput=(Output *)calloc(1,sizeof(Output));
|
||||
tt=firstOutput;
|
||||
} else if((tt=get(id))==NULL){
|
||||
tt=firstOutput;
|
||||
while(tt->nextOutput!=NULL)
|
||||
tt=tt->nextOutput;
|
||||
tt->nextOutput=(Output *)calloc(1,sizeof(Output));
|
||||
tt=tt->nextOutput;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.id=id;
|
||||
tt->data.pin=pin;
|
||||
tt->data.iFlag=iFlag;
|
||||
tt->data.oStatus=0;
|
||||
|
||||
if(v==1){
|
||||
tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
|
||||
digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0));
|
||||
pinMode(tt->data.pin,OUTPUT);
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output *Output::firstOutput=NULL;
|
||||
|
||||
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
Outputs.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifndef Outputs_h
|
||||
#define Outputs_h
|
||||
|
||||
struct OutputData {
|
||||
byte oStatus;
|
||||
int id;
|
||||
byte pin;
|
||||
byte iFlag;
|
||||
};
|
||||
|
||||
struct Output{
|
||||
static Output *firstOutput;
|
||||
int num;
|
||||
struct OutputData data;
|
||||
Output *nextOutput;
|
||||
void activate(int s);
|
||||
static void parse(char *c);
|
||||
static Output* get(int);
|
||||
static void remove(int);
|
||||
static void load();
|
||||
static void store();
|
||||
static Output *create(int, int, int, int=0);
|
||||
static void show(int=0);
|
||||
}; // Output
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
476
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp
Normal file
@@ -0,0 +1,476 @@
|
||||
/**********************************************************************
|
||||
|
||||
PacketRegister.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "PacketRegister.h"
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Register::initPackets(){
|
||||
activePacket=packet;
|
||||
updatePacket=packet+1;
|
||||
} // Register::initPackets
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RegisterList::RegisterList(int maxNumRegs){
|
||||
this->maxNumRegs=maxNumRegs;
|
||||
reg=(Register *)calloc((maxNumRegs+1),sizeof(Register));
|
||||
for(int i=0;i<=maxNumRegs;i++)
|
||||
reg[i].initPackets();
|
||||
regMap=(Register **)calloc((maxNumRegs+1),sizeof(Register *));
|
||||
speedTable=(int *)calloc((maxNumRegs+1),sizeof(int *));
|
||||
currentReg=reg;
|
||||
regMap[0]=reg;
|
||||
maxLoadedReg=reg;
|
||||
nextReg=NULL;
|
||||
currentBit=0;
|
||||
nRepeat=0;
|
||||
} // RegisterList::RegisterList
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE)
|
||||
// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS
|
||||
// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS)
|
||||
|
||||
void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile {
|
||||
|
||||
nReg=nReg%((maxNumRegs+1)); // force nReg to be between 0 and maxNumRegs, inclusive
|
||||
|
||||
while(nextReg!=NULL); // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
|
||||
|
||||
if(regMap[nReg]==NULL) // first time this Register Number has been called
|
||||
regMap[nReg]=maxLoadedReg+1; // set Register Pointer for this Register Number to next available Register
|
||||
|
||||
Register *r=regMap[nReg]; // set Register to be updated
|
||||
Packet *p=r->updatePacket; // set Packet in the Register to be updated
|
||||
byte *buf=p->buf; // set byte buffer in the Packet to be updated
|
||||
|
||||
b[nBytes]=b[0]; // copy first byte into what will become the checksum byte
|
||||
for(int i=1;i<nBytes;i++) // XOR remaining bytes into checksum byte
|
||||
b[nBytes]^=b[i];
|
||||
nBytes++; // increment number of bytes in packet to include checksum byte
|
||||
|
||||
buf[0]=0xFF; // first 8 bytes of 22-byte preamble
|
||||
buf[1]=0xFF; // second 8 bytes of 22-byte preamble
|
||||
buf[2]=0xFC + bitRead(b[0],7); // last 6 bytes of 22-byte preamble + data start bit + b[0], bit 7
|
||||
buf[3]=b[0]<<1; // b[0], bits 6-0 + data start bit
|
||||
buf[4]=b[1]; // b[1], all bits
|
||||
buf[5]=b[2]>>1; // b[2], bits 7-1
|
||||
buf[6]=b[2]<<7; // b[2], bit 0
|
||||
|
||||
if(nBytes==3){
|
||||
p->nBits=49;
|
||||
} else{
|
||||
buf[6]+=b[3]>>2; // b[3], bits 7-2
|
||||
buf[7]=b[3]<<6; // b[3], bit 1-0
|
||||
if(nBytes==4){
|
||||
p->nBits=58;
|
||||
} else{
|
||||
buf[7]+=b[4]>>3; // b[4], bits 7-3
|
||||
buf[8]=b[4]<<5; // b[4], bits 2-0
|
||||
if(nBytes==5){
|
||||
p->nBits=67;
|
||||
} else{
|
||||
buf[8]+=b[5]>>4; // b[5], bits 7-4
|
||||
buf[9]=b[5]<<4; // b[5], bits 3-0
|
||||
p->nBits=76;
|
||||
} // >5 bytes
|
||||
} // >4 bytes
|
||||
} // >3 bytes
|
||||
|
||||
nextReg=r;
|
||||
this->nRepeat=nRepeat;
|
||||
maxLoadedReg=max(maxLoadedReg,nextReg);
|
||||
|
||||
if(printFlag && SHOW_PACKETS) // for debugging purposes
|
||||
printPacket(nReg,b,nBytes,nRepeat);
|
||||
|
||||
} // RegisterList::loadPacket
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setThrottle(char *s) volatile{
|
||||
byte b[5]; // save space for checksum byte
|
||||
int nReg;
|
||||
int cab;
|
||||
int tSpeed;
|
||||
int tDirection;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&nReg,&cab,&tSpeed,&tDirection)!=4)
|
||||
return;
|
||||
|
||||
if(nReg<1 || nReg>maxNumRegs)
|
||||
return;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0x3F; // 128-step speed control byte
|
||||
if(tSpeed>=0)
|
||||
b[nB++]=tSpeed+(tSpeed>0)+tDirection*128; // max speed is 126, but speed codes range from 2-127 (0=stop, 1=emergency stop)
|
||||
else{
|
||||
b[nB++]=1;
|
||||
tSpeed=0;
|
||||
}
|
||||
|
||||
loadPacket(nReg,b,nB,0,1);
|
||||
|
||||
INTERFACE.print("<T");
|
||||
INTERFACE.print(nReg); INTERFACE.print(" ");
|
||||
INTERFACE.print(tSpeed); INTERFACE.print(" ");
|
||||
INTERFACE.print(tDirection);
|
||||
INTERFACE.print(">");
|
||||
|
||||
speedTable[nReg]=tDirection==1?tSpeed:-tSpeed;
|
||||
|
||||
} // RegisterList::setThrottle()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setFunction(char *s) volatile{
|
||||
byte b[5]; // save space for checksum byte
|
||||
int cab;
|
||||
int fByte, eByte;
|
||||
int nParams;
|
||||
byte nB=0;
|
||||
|
||||
nParams=sscanf(s,"%d %d %d",&cab,&fByte,&eByte);
|
||||
|
||||
if(nParams<2)
|
||||
return;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
|
||||
if(nParams==2){ // this is a request for functions FL,F1-F12
|
||||
b[nB++]=(fByte | 0x80) & 0xBF; // for safety this guarantees that first nibble of function byte will always be of binary form 10XX which should always be the case for FL,F1-F12
|
||||
} else { // this is a request for functions F13-F28
|
||||
b[nB++]=(fByte | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28)
|
||||
b[nB++]=eByte;
|
||||
}
|
||||
|
||||
loadPacket(0,b,nB,4,1);
|
||||
|
||||
} // RegisterList::setFunction()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setAccessory(char *s) volatile{
|
||||
byte b[3]; // save space for checksum byte
|
||||
int aAdd; // the accessory address (0-511 = 9 bits)
|
||||
int aNum; // the accessory number within that address (0-3)
|
||||
int activate; // flag indicated whether accessory should be activated (1) or deactivated (0) following NMRA recommended convention
|
||||
|
||||
if(sscanf(s,"%d %d %d",&aAdd,&aNum,&activate)!=3)
|
||||
return;
|
||||
|
||||
b[0]=aAdd%64+128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
b[1]=((((aAdd/64)%8)<<4) + (aNum%4<<1) + activate%2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||
|
||||
loadPacket(0,b,2,4,1);
|
||||
|
||||
} // RegisterList::setAccessory()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeTextPacket(char *s) volatile{
|
||||
|
||||
int nReg;
|
||||
byte b[6];
|
||||
int nBytes;
|
||||
volatile RegisterList *regs;
|
||||
|
||||
nBytes=sscanf(s,"%d %x %x %x %x %x",&nReg,b,b+1,b+2,b+3,b+4)-1;
|
||||
|
||||
if(nBytes<2 || nBytes>5){ // invalid valid packet
|
||||
INTERFACE.print("<mInvalid Packet>");
|
||||
return;
|
||||
}
|
||||
|
||||
loadPacket(nReg,b,nBytes,0,1);
|
||||
|
||||
} // RegisterList::writeTextPacket()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::readCV(char *s) volatile{
|
||||
byte bRead[4];
|
||||
int bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d",&cv,&callBack,&callBackSub)!=3) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
|
||||
bRead[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bRead[1]=lowByte(cv);
|
||||
|
||||
bValue=0;
|
||||
|
||||
for(int i=0;i<8;i++){
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bRead[2]=0xE8+i;
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bRead,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
bitWrite(bValue,i,d);
|
||||
}
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bRead[0]=0x74+(highByte(cv)&0x03); // set-up to re-verify entire byte
|
||||
bRead[2]=bValue;
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bRead,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::readCV()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVByte(char *s) volatile{
|
||||
byte bWrite[4];
|
||||
int bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&cv,&bValue,&callBack,&callBackSub)!=4) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
|
||||
bWrite[0]=0x7C+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bWrite[1]=lowByte(cv);
|
||||
bWrite[2]=bValue;
|
||||
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,bWrite,3,4);
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,idlePacket,2,10);
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bWrite[0]=0x74+(highByte(cv)&0x03); // set-up to re-verify entire byte
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bWrite,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::writeCVByte()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVBit(char *s) volatile{
|
||||
byte bWrite[4];
|
||||
int bNum,bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d %d",&cv,&bNum,&bValue,&callBack,&callBackSub)!=5) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
bValue=bValue%2;
|
||||
bNum=bNum%8;
|
||||
|
||||
bWrite[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bWrite[1]=lowByte(cv);
|
||||
bWrite[2]=0xF0+bValue*8+bNum;
|
||||
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,bWrite,3,4);
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,idlePacket,2,10);
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bitClear(bWrite[2],4); // change instruction code from Write Bit to Verify Bit
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bWrite,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bNum);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::writeCVBit()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVByteMain(char *s) volatile{
|
||||
byte b[6]; // save space for checksum byte
|
||||
int cab;
|
||||
int cv;
|
||||
int bValue;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d",&cab,&cv,&bValue)!=3)
|
||||
return;
|
||||
cv--;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0xEC+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
b[nB++]=lowByte(cv);
|
||||
b[nB++]=bValue;
|
||||
|
||||
loadPacket(0,b,nB,4);
|
||||
|
||||
} // RegisterList::writeCVByteMain()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVBitMain(char *s) volatile{
|
||||
byte b[6]; // save space for checksum byte
|
||||
int cab;
|
||||
int cv;
|
||||
int bNum;
|
||||
int bValue;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&cab,&cv,&bNum,&bValue)!=4)
|
||||
return;
|
||||
cv--;
|
||||
|
||||
bValue=bValue%2;
|
||||
bNum=bNum%8;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0xE8+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
b[nB++]=lowByte(cv);
|
||||
b[nB++]=0xF0+bValue*8+bNum;
|
||||
|
||||
loadPacket(0,b,nB,4);
|
||||
|
||||
} // RegisterList::writeCVBitMain()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::printPacket(int nReg, byte *b, int nBytes, int nRepeat) volatile {
|
||||
|
||||
INTERFACE.print("<*");
|
||||
INTERFACE.print(nReg);
|
||||
INTERFACE.print(":");
|
||||
for(int i=0;i<nBytes;i++){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(b[i],HEX);
|
||||
}
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(nRepeat);
|
||||
INTERFACE.print(">");
|
||||
} // RegisterList::printPacket()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
byte RegisterList::idlePacket[3]={0xFF,0x00,0}; // always leave extra byte for checksum computation
|
||||
byte RegisterList::resetPacket[3]={0x00,0x00,0};
|
||||
|
||||
byte RegisterList::bitMask[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; // masks used in interrupt routine to speed the query of a single bit in a Packet
|
||||
64
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**********************************************************************
|
||||
|
||||
PacketRegister.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef PacketRegister_h
|
||||
#define PacketRegister_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
// Define constants used for reading CVs from the Programming Track
|
||||
|
||||
#define ACK_BASE_COUNT 100 // number of analogRead samples to take before each CV verify to establish a baseline current
|
||||
#define ACK_SAMPLE_COUNT 500 // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent
|
||||
#define ACK_SAMPLE_SMOOTHING 0.2 // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent
|
||||
#define ACK_SAMPLE_THRESHOLD 30 // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT
|
||||
|
||||
// Define a series of registers that can be sequentially accessed over a loop to generate a repeating series of DCC Packets
|
||||
|
||||
struct Packet{
|
||||
byte buf[10];
|
||||
byte nBits;
|
||||
}; // Packet
|
||||
|
||||
struct Register{
|
||||
Packet packet[2];
|
||||
Packet *activePacket;
|
||||
Packet *updatePacket;
|
||||
void initPackets();
|
||||
}; // Register
|
||||
|
||||
struct RegisterList{
|
||||
int maxNumRegs;
|
||||
Register *reg;
|
||||
Register **regMap;
|
||||
Register *currentReg;
|
||||
Register *maxLoadedReg;
|
||||
Register *nextReg;
|
||||
Packet *tempPacket;
|
||||
byte currentBit;
|
||||
byte nRepeat;
|
||||
int *speedTable;
|
||||
static byte idlePacket[];
|
||||
static byte resetPacket[];
|
||||
static byte bitMask[];
|
||||
RegisterList(int);
|
||||
void loadPacket(int, byte *, int, int, int=0) volatile;
|
||||
void setThrottle(char *) volatile;
|
||||
void setFunction(char *) volatile;
|
||||
void setAccessory(char *) volatile;
|
||||
void writeTextPacket(char *) volatile;
|
||||
void readCV(char *) volatile;
|
||||
void writeCVByte(char *) volatile;
|
||||
void writeCVBit(char *) volatile;
|
||||
void writeCVByteMain(char *) volatile;
|
||||
void writeCVBitMain(char *s) volatile;
|
||||
void printPacket(int, byte *, int, int) volatile;
|
||||
};
|
||||
|
||||
#endif
|
||||
248
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/**********************************************************************
|
||||
|
||||
Sensor.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION supports Sensor inputs that can be connected to any Aruidno Pin
|
||||
not in use by this program. Sensors can be of any type (infrared, magentic, mechanical...).
|
||||
The only requirement is that when "activated" the Sensor must force the specified Arduino
|
||||
Pin LOW (i.e. to ground), and when not activated, this Pin should remain HIGH (e.g. 5V),
|
||||
or be allowed to float HIGH if use of the Arduino Pin's internal pull-up resistor is specified.
|
||||
|
||||
To ensure proper voltage levels, some part of the Sensor circuitry
|
||||
MUST be tied back to the same ground as used by the Arduino.
|
||||
|
||||
The Sensor code below utilizes exponential smoothing to "de-bounce" spikes generated by
|
||||
mechanical switches and transistors. This avoids the need to create smoothing circuitry
|
||||
for each sensor. You may need to change these parameters through trial and error for your specific sensors.
|
||||
|
||||
To have this sketch monitor one or more Arduino pins for sensor triggers, first define/edit/delete
|
||||
sensor definitions using the following variation of the "S" command:
|
||||
|
||||
<S ID PIN PULLUP>: creates a new sensor ID, with specified PIN and PULLUP
|
||||
if sensor ID already exists, it is updated with specificed PIN and PULLUP
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<S ID>: deletes definition of sensor ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<S>: lists all defined sensors
|
||||
returns: <Q ID PIN PULLUP> for each defined sensor or <X> if no sensors defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the sensor
|
||||
PIN: the arduino pin number the sensor is connected to
|
||||
PULLUP: 1=use internal pull-up resistor for PIN, 0=don't use internal pull-up resistor for PIN
|
||||
|
||||
Once all sensors have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the sensor definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
All sensors defined as per above are repeatedly and sequentially checked within the main loop of this sketch.
|
||||
If a Sensor Pin is found to have transitioned from one state to another, one of the following serial messages are generated:
|
||||
|
||||
<Q ID> - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered)
|
||||
<q ID> - for transition of Sensor ID from LOW state to HIGH state (i.e. the sensor is no longer triggered)
|
||||
|
||||
Depending on whether the physical sensor is acting as an "event-trigger" or a "detection-sensor," you may
|
||||
decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "Sensor.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::check(){
|
||||
Sensor *tt;
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY;
|
||||
|
||||
if(!tt->active && tt->signal<0.5){
|
||||
tt->active=true;
|
||||
INTERFACE.print("<Q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
} else if(tt->active && tt->signal>0.9){
|
||||
tt->active=false;
|
||||
INTERFACE.print("<q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
} // loop over all sensors
|
||||
|
||||
} // Sensor::check
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor *Sensor::create(int snum, int pin, int pullUp, int v){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
firstSensor=(Sensor *)calloc(1,sizeof(Sensor));
|
||||
tt=firstSensor;
|
||||
} else if((tt=get(snum))==NULL){
|
||||
tt=firstSensor;
|
||||
while(tt->nextSensor!=NULL)
|
||||
tt=tt->nextSensor;
|
||||
tt->nextSensor=(Sensor *)calloc(1,sizeof(Sensor));
|
||||
tt=tt->nextSensor;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.snum=snum;
|
||||
tt->data.pin=pin;
|
||||
tt->data.pullUp=(pullUp==0?LOW:HIGH);
|
||||
tt->active=false;
|
||||
tt->signal=1;
|
||||
pinMode(pin,INPUT); // set mode to input
|
||||
digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor
|
||||
|
||||
if(v==1)
|
||||
INTERFACE.print("<O>");
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor* Sensor::get(int n){
|
||||
Sensor *tt;
|
||||
for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextSensor);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::remove(int n){
|
||||
Sensor *tt,*pp;
|
||||
|
||||
for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstSensor)
|
||||
firstSensor=tt->nextSensor;
|
||||
else
|
||||
pp->nextSensor=tt->nextSensor;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::show(){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
INTERFACE.print("<Q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pin);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pullUp);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::status(){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
INTERFACE.print(tt->active?"<Q":"<q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::parse(char *c){
|
||||
int n,s,m;
|
||||
Sensor *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH)
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show();
|
||||
break;
|
||||
|
||||
case 2: // invalid number of arguments
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::load(){
|
||||
struct SensorData data;
|
||||
Sensor *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nSensors;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.snum,data.pin,data.pullUp);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::store(){
|
||||
Sensor *tt;
|
||||
|
||||
tt=firstSensor;
|
||||
EEStore::eeStore->data.nSensors=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextSensor;
|
||||
EEStore::eeStore->data.nSensors++;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor *Sensor::firstSensor=NULL;
|
||||
|
||||
41
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**********************************************************************
|
||||
|
||||
Sensor.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef Sensor_h
|
||||
#define Sensor_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define SENSOR_DECAY 0.03
|
||||
|
||||
struct SensorData {
|
||||
int snum;
|
||||
byte pin;
|
||||
byte pullUp;
|
||||
};
|
||||
|
||||
struct Sensor{
|
||||
static Sensor *firstSensor;
|
||||
SensorData data;
|
||||
boolean active;
|
||||
float signal;
|
||||
Sensor *nextSensor;
|
||||
static void load();
|
||||
static void store();
|
||||
static Sensor *create(int, int, int, int=0);
|
||||
static Sensor* get(int);
|
||||
static void remove(int);
|
||||
static void show();
|
||||
static void status();
|
||||
static void parse(char *c);
|
||||
static void check();
|
||||
}; // Sensor
|
||||
|
||||
#endif
|
||||
|
||||
571
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
/**********************************************************************
|
||||
|
||||
SerialCommand.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
// DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS
|
||||
// WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS
|
||||
// ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER
|
||||
// COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED.
|
||||
|
||||
// See SerialCommand::parse() below for defined text commands.
|
||||
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "Accessories.h"
|
||||
#include "Sensor.h"
|
||||
#include "Outputs.h"
|
||||
#include "EEStore.h"
|
||||
#include "Comm.h"
|
||||
|
||||
extern int __heap_start, *__brkval;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char SerialCommand::commandString[MAX_COMMAND_LENGTH+1];
|
||||
volatile RegisterList *SerialCommand::mRegs;
|
||||
volatile RegisterList *SerialCommand::pRegs;
|
||||
CurrentMonitor *SerialCommand::mMonitor;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){
|
||||
mRegs=_mRegs;
|
||||
pRegs=_pRegs;
|
||||
mMonitor=_mMonitor;
|
||||
sprintf(commandString,"");
|
||||
} // SerialCommand:SerialCommand
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::process(){
|
||||
char c;
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
|
||||
while(INTERFACE.available()>0){ // while there is data on the serial line
|
||||
c=INTERFACE.read();
|
||||
if(c=='<') // start of new command
|
||||
sprintf(commandString,"");
|
||||
else if(c=='>') // end of new command
|
||||
parse(commandString);
|
||||
else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from serial line
|
||||
sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>')
|
||||
} // while
|
||||
|
||||
#elif COMM_TYPE == 1
|
||||
|
||||
EthernetClient client=INTERFACE.available();
|
||||
|
||||
if(client){
|
||||
while(client.connected() && client.available()){ // while there is data on the network
|
||||
c=client.read();
|
||||
if(c=='<') // start of new command
|
||||
sprintf(commandString,"");
|
||||
else if(c=='>') // end of new command
|
||||
parse(commandString);
|
||||
else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from network
|
||||
sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>')
|
||||
} // while
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // SerialCommand:process
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::parse(char *com){
|
||||
|
||||
switch(com[0]){
|
||||
|
||||
/***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/
|
||||
|
||||
case 't': // <t REGISTER CAB SPEED DIRECTION>
|
||||
/*
|
||||
* sets the throttle for a given register/cab combination
|
||||
*
|
||||
* REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0)
|
||||
* DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train
|
||||
*
|
||||
* returns: <T REGISTER SPEED DIRECTION>
|
||||
*
|
||||
*/
|
||||
mRegs->setThrottle(com+1);
|
||||
break;
|
||||
|
||||
/***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/
|
||||
|
||||
case 'f': // <f CAB BYTE1 [BYTE2]>
|
||||
/*
|
||||
* turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL)
|
||||
* NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
*
|
||||
* To set functions F0-F4 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F5-F8 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F9-F12 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F13-F20 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 222
|
||||
* BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128
|
||||
*
|
||||
* To set functions F21-F28 on (=1) of off (=0):
|
||||
*
|
||||
* BYTE1: 223
|
||||
* BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128
|
||||
*
|
||||
* returns: NONE
|
||||
*
|
||||
*/
|
||||
mRegs->setFunction(com+1);
|
||||
break;
|
||||
|
||||
/***** OPERATE STATIONARY ACCESSORY DECODERS ****/
|
||||
|
||||
case 'a': // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
/*
|
||||
* turns an accessory (stationary) decoder on or off
|
||||
*
|
||||
* ADDRESS: the primary address of the decoder (0-511)
|
||||
* SUBADDRESS: the subaddress of the decoder (0-3)
|
||||
* ACTIVATE: 1=on (set), 0=off (clear)
|
||||
*
|
||||
* Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N,
|
||||
* from 1 through a max of 2044, where
|
||||
*
|
||||
* N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* ADDRESS = INT((N - 1) / 4) + 1
|
||||
* SUBADDRESS = (N - 1) % 4
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->setAccessory(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/
|
||||
|
||||
case 'T': // <T ID THROW>
|
||||
/*
|
||||
* <T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position
|
||||
*
|
||||
* ID: the numeric ID (0-32767) of the turnout to control
|
||||
* THROW: 0 (unthrown) or 1 (thrown)
|
||||
*
|
||||
* returns: <H ID THROW> or <X> if turnout ID does not exist
|
||||
*
|
||||
* *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS
|
||||
*/
|
||||
Turnout::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/
|
||||
|
||||
case 'Z': // <Z ID ACTIVATE>
|
||||
/*
|
||||
* <Z ID ACTIVATE>: sets output ID to either the "active" or "inactive" state
|
||||
*
|
||||
* ID: the numeric ID (0-32767) of the output to control
|
||||
* ACTIVATE: 0 (active) or 1 (inactive)
|
||||
*
|
||||
* returns: <Y ID ACTIVATE> or <X> if output ID does not exist
|
||||
*
|
||||
* *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS
|
||||
*/
|
||||
Output::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/
|
||||
|
||||
case 'S':
|
||||
/*
|
||||
* *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS
|
||||
*/
|
||||
Sensor::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** SHOW STATUS OF ALL SENSORS ****/
|
||||
|
||||
case 'Q': // <Q>
|
||||
/*
|
||||
* returns: the status of each sensor ID in the form <Q ID> (active) or <q ID> (not active)
|
||||
*/
|
||||
Sensor::status();
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'w': // <w CAB CV VALUE>
|
||||
/*
|
||||
* writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* VALUE: the value to be written to the Configuration Variable memory location (0-255)
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeCVByteMain(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'b': // <b CAB CV BIT VALUE>
|
||||
/*
|
||||
* writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* BIT: the bit number of the Configurarion Variable regsiter to write (0-7)
|
||||
* VALUE: the value of the bit to be written (0-1)
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeCVBitMain(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'W': // <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* VALUE: the value to be written to the Configuration Variable memory location (0-255)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV Value)
|
||||
* where VALUE is a number from 0-255 as read from the requested CV, or -1 if verificaiton read fails
|
||||
*/
|
||||
pRegs->writeCVByte(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'B': // <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* BIT: the bit number of the Configurarion Variable memory location to write (0-7)
|
||||
* VALUE: the value of the bit to be written (0-1)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)
|
||||
* where VALUE is a number from 0-1 as read from the requested CV bit, or -1 if verificaiton read fails
|
||||
*/
|
||||
pRegs->writeCVBit(com+1);
|
||||
break;
|
||||
|
||||
/***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'R': // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* reads a Configuration Variable from the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV VALUE)
|
||||
* where VALUE is a number from 0-255 as read from the requested CV, or -1 if read could not be verified
|
||||
*/
|
||||
pRegs->readCV(com+1);
|
||||
break;
|
||||
|
||||
/***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/
|
||||
|
||||
case '1': // <1>
|
||||
/*
|
||||
* enables power from the motor shield to the main operations and programming tracks
|
||||
*
|
||||
* returns: <p1>
|
||||
*/
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH);
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH);
|
||||
INTERFACE.print("<p1>");
|
||||
break;
|
||||
|
||||
/***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/
|
||||
|
||||
case '0': // <0>
|
||||
/*
|
||||
* disables power from the motor shield to the main operations and programming tracks
|
||||
*
|
||||
* returns: <p0>
|
||||
*/
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW);
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW);
|
||||
INTERFACE.print("<p0>");
|
||||
break;
|
||||
|
||||
/***** READ MAIN OPERATIONS TRACK CURRENT ****/
|
||||
|
||||
case 'c': // <c>
|
||||
/*
|
||||
* reads current being drawn on main operations track
|
||||
*
|
||||
* returns: <a CURRENT>
|
||||
* where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme
|
||||
*/
|
||||
INTERFACE.print("<a");
|
||||
INTERFACE.print(int(mMonitor->current));
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** READ STATUS OF DCC++ BASE STATION ****/
|
||||
|
||||
case 's': // <s>
|
||||
/*
|
||||
* returns status messages containing track power status, throttle status, turn-out status, and a version number
|
||||
* NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings
|
||||
*
|
||||
* returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings
|
||||
*/
|
||||
if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN
|
||||
INTERFACE.print("<p0>");
|
||||
else
|
||||
INTERFACE.print("<p1>");
|
||||
|
||||
for(int i=1;i<=MAX_MAIN_REGISTERS;i++){
|
||||
if(mRegs->speedTable[i]==0)
|
||||
continue;
|
||||
INTERFACE.print("<T");
|
||||
INTERFACE.print(i); INTERFACE.print(" ");
|
||||
if(mRegs->speedTable[i]>0){
|
||||
INTERFACE.print(mRegs->speedTable[i]);
|
||||
INTERFACE.print(" 1>");
|
||||
} else{
|
||||
INTERFACE.print(-mRegs->speedTable[i]);
|
||||
INTERFACE.print(" 0>");
|
||||
}
|
||||
}
|
||||
INTERFACE.print("<iDCC++ BASE STATION FOR ARDUINO ");
|
||||
INTERFACE.print(ARDUINO_TYPE);
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(MOTOR_SHIELD_NAME);
|
||||
INTERFACE.print(": V-");
|
||||
INTERFACE.print(VERSION);
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(__DATE__);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(__TIME__);
|
||||
INTERFACE.print(">");
|
||||
|
||||
INTERFACE.print("<N");
|
||||
INTERFACE.print(COMM_TYPE);
|
||||
INTERFACE.print(": ");
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
INTERFACE.print("SERIAL>");
|
||||
#elif COMM_TYPE == 1
|
||||
INTERFACE.print(Ethernet.localIP());
|
||||
INTERFACE.print(">");
|
||||
#endif
|
||||
|
||||
Turnout::show();
|
||||
Output::show();
|
||||
|
||||
break;
|
||||
|
||||
/***** STORE SETTINGS IN EEPROM ****/
|
||||
|
||||
case 'E': // <E>
|
||||
/*
|
||||
* stores settings for turnouts and sensors EEPROM
|
||||
*
|
||||
* returns: <e nTurnouts nSensors>
|
||||
*/
|
||||
|
||||
EEStore::store();
|
||||
INTERFACE.print("<e ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nTurnouts);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nSensors);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nOutputs);
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** CLEAR SETTINGS IN EEPROM ****/
|
||||
|
||||
case 'e': // <e>
|
||||
/*
|
||||
* clears settings for Turnouts in EEPROM
|
||||
*
|
||||
* returns: <O>
|
||||
*/
|
||||
|
||||
EEStore::clear();
|
||||
INTERFACE.print("<O>");
|
||||
break;
|
||||
|
||||
/***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/
|
||||
|
||||
case ' ': // < >
|
||||
/*
|
||||
* simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window
|
||||
*
|
||||
* returns: a carriage return
|
||||
*/
|
||||
INTERFACE.println("");
|
||||
break;
|
||||
|
||||
///
|
||||
/// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES
|
||||
/// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW
|
||||
///
|
||||
|
||||
/***** ENTER DIAGNOSTIC MODE ****/
|
||||
|
||||
case 'D': // <D>
|
||||
/*
|
||||
* changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED
|
||||
* SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS
|
||||
*/
|
||||
|
||||
Serial.println("\nEntering Diagnostic Mode...");
|
||||
delay(1000);
|
||||
|
||||
bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8
|
||||
bitSet(TCCR1B,CS11);
|
||||
bitClear(TCCR1B,CS10);
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4
|
||||
bitClear(TCCR0B,CS01);
|
||||
bitClear(TCCR0B,CS00);
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8
|
||||
bitSet(TCCR3B,CS31);
|
||||
bitClear(TCCR3B,CS30);
|
||||
|
||||
#endif
|
||||
|
||||
CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256
|
||||
CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS
|
||||
|
||||
break;
|
||||
|
||||
/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'M': // <M REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]>
|
||||
/*
|
||||
* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track
|
||||
* FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER
|
||||
*
|
||||
* REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet
|
||||
* BYTE1: first hexidecimal byte in the packet
|
||||
* BYTE2: second hexidecimal byte in the packet
|
||||
* BYTE3: optional third hexidecimal byte in the packet
|
||||
* BYTE4: optional fourth hexidecimal byte in the packet
|
||||
* BYTE5: optional fifth hexidecimal byte in the packet
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeTextPacket(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'P': // <P REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]>
|
||||
/*
|
||||
* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track
|
||||
* FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER
|
||||
*
|
||||
* REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet
|
||||
* BYTE1: first hexidecimal byte in the packet
|
||||
* BYTE2: second hexidecimal byte in the packet
|
||||
* BYTE3: optional third hexidecimal byte in the packet
|
||||
* BYTE4: optional fourth hexidecimal byte in the packet
|
||||
* BYTE5: optional fifth hexidecimal byte in the packet
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
pRegs->writeTextPacket(com+1);
|
||||
break;
|
||||
|
||||
/***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/
|
||||
|
||||
case 'F': // <F>
|
||||
/*
|
||||
* measure amount of free SRAM memory left on the Arduino based on trick found on the internet.
|
||||
* Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM.
|
||||
* Unfortunately not very reliable --- would be great to find a better method
|
||||
*
|
||||
* returns: <f MEM>
|
||||
* where MEM is the number of free bytes remaining in the Arduino's SRAM
|
||||
*/
|
||||
int v;
|
||||
INTERFACE.print("<f");
|
||||
INTERFACE.print((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval));
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/
|
||||
|
||||
case 'L': // <L>
|
||||
/*
|
||||
* lists the packet contents of the main operations track registers and the programming track registers
|
||||
* FOR DIAGNOSTIC AND TESTING USE ONLY
|
||||
*/
|
||||
INTERFACE.println("");
|
||||
for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){
|
||||
INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t");
|
||||
INTERFACE.print((int)p); INTERFACE.print("\t");
|
||||
INTERFACE.print((int)p->activePacket); INTERFACE.print("\t");
|
||||
INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t");
|
||||
for(int i=0;i<10;i++){
|
||||
INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
}
|
||||
for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){
|
||||
INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t");
|
||||
INTERFACE.print((int)p); INTERFACE.print("\t");
|
||||
INTERFACE.print((int)p->activePacket); INTERFACE.print("\t");
|
||||
INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t");
|
||||
for(int i=0;i<10;i++){
|
||||
INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
break;
|
||||
|
||||
} // switch
|
||||
}; // SerialCommand::parse
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
31
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/**********************************************************************
|
||||
|
||||
SerialCommand.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SerialCommand_h
|
||||
#define SerialCommand_h
|
||||
|
||||
#include "PacketRegister.h"
|
||||
#include "CurrentMonitor.h"
|
||||
|
||||
#define MAX_COMMAND_LENGTH 30
|
||||
|
||||
struct SerialCommand{
|
||||
static char commandString[MAX_COMMAND_LENGTH+1];
|
||||
static volatile RegisterList *mRegs, *pRegs;
|
||||
static CurrentMonitor *mMonitor;
|
||||
static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *);
|
||||
static void parse(char *);
|
||||
static void process();
|
||||
}; // SerialCommand
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
37
DCC-Centrale/Firmware/BaseStation/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
What’s 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.
|
||||
|
||||
What’s in this Repository
|
||||
-------------------------
|
||||
|
||||
This repository, BaseStation-Uno, contains a complete DCC++ Base Station sketch designed for compiling and uploading into an Arduino Uno. All sketch files are in the folder named DCCpp_Uno. More information about the sketch can be found in the included PDF file.
|
||||
|
||||
To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Uno.ino within the DCCpp_Uno folder using your Arduino IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code.
|
||||
|
||||
The latest production release of the Master branch is 1.2.1:
|
||||
|
||||
* Supports both the Arduino Uno and Arduino Mega
|
||||
* Built-in configuration for both the original Arduino Motor Shield as well as a Pololu MC33926 Motor Shield
|
||||
* Built-in configuration and support of Ethernet Shields (for use with Mega only)
|
||||
|
||||
For more information on the overall DCC++ system, please follow the links in the PDF file.
|
||||
|
||||
Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository
|
||||
|
||||
The Master branch contains all of the Base Station functionality showed in the DCC++ YouTube channel with the exception of 2 layout-specific modules:
|
||||
|
||||
* Control for an RGB LED Light Strip using pins 44, 45, and 46 on the Mega
|
||||
* An embedded AutoPilot routine that randomly selects a train to run through the entire layout, after which it is brought back into its original siding and the the patterns repeats with another randomly-selected train. This is the AutoPilot routine showed on the DCC++ YouTube channel. It does not require any computer, not DCC++ Controller to be running (DCC++ Controller contains a much more complicated 3-train Auto Pilot mode, also as shown on the DCC++ YouTube channel).
|
||||
|
||||
Since these modules are very layout-specififc, they are not included in the Master branch. However, they are included in the Development branch. Please feel free to download and copy any relevant code to customize your own version of DCC++ Base Station.
|
||||
|
||||
-December 27, 2015
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
528
DCC-Centrale/Firmware/Controller/DCCpp_Controller/dCabs.pde
Normal 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
|
||||
139
DCC-Centrale/Firmware/Controller/DCCpp_Controller/dRoutes.pde
Normal 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
|
||||
815
DCC-Centrale/Firmware/Controller/DCCpp_Controller/dSensors.pde
Normal 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
|
||||
264
DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTracks.pde
Normal 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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
153
DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTurnouts.pde
Normal 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
|
||||
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 648 KiB |
@@ -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>
|
||||
@@ -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
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
168
DCC-Centrale/Firmware/Controller/DCCpp_Controller/gButtons.pde
Normal 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
|
||||
254
DCC-Centrale/Firmware/Controller/DCCpp_Controller/gTextBoxes.pde
Normal 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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
291
DCC-Centrale/Firmware/Controller/DCCpp_Controller/gWindows.pde
Normal 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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
58
DCC-Centrale/Firmware/Controller/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
What’s 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.
|
||||
|
||||
What’s 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!
|
||||
|
||||
|
||||
|
||||
|
||||
5151
DCC-Centrale/Hardware/Doc/Locoduino.html
Normal file
145
DCC-Centrale/README.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# DCC-Central
|
||||
|
||||
DCC-Central consist in a complete system to drive DCC scale models.
|
||||
|
||||
## Principles
|
||||
|
||||
System is based on ESP32-S3 WROOM devKit, with some peripherals :
|
||||
|
||||
- ILI9341 4.0" display module
|
||||
To display what is happening
|
||||
- Touch screen
|
||||
To allow direct managing
|
||||
- MCT2515 can interface
|
||||
Used for accessory and feedback
|
||||
- LM 18200 Hbridge (Drive track power / accessories )
|
||||
- DCC signals or DC - can be switched to program,
|
||||
- second to send square signal - powers accessories
|
||||
- ACS712 current sensor
|
||||
Read DCC power send to track
|
||||
- RAILCOM detector - input pin
|
||||
- KY-040 rotary encoder
|
||||
set speed / enventually help to navigate in menus
|
||||
- relay
|
||||
Swith 2 Rails / 3 rails
|
||||
|
||||
|
||||
## Functionnalities
|
||||
|
||||
DCC-Central is connected with CDM-Rail (protocol to choose), but can work alone. It will be RailCom compatible
|
||||
|
||||
On display we must have :
|
||||
- Select Autonomous / CDM-Rail driven switch
|
||||
- Select DC / DCC type
|
||||
- Select 2Rails / 3 Rails
|
||||
|
||||
If autonomous and DCC
|
||||
- Select Programming mode
|
||||
- Select Loco ID
|
||||
- Display Functions buttons
|
||||
- Add a tab to display informations returned
|
||||
|
||||
|
||||
### Running
|
||||
|
||||
1. User choose a DCC-id to drive a loco with help of display
|
||||
2. he selects type of signal DCC/DC (display)
|
||||
3. track type ( 2rails / 3 rails)
|
||||
4. Switch 'ON' track power
|
||||
|
||||
System response
|
||||
|
||||
- Switch relay ON (2 rails), OFF (3 rails)
|
||||
- generate PWM (DC) / DCC signal (DCC)
|
||||
- if DCC signal, set DCC loco id and drive DCC signal
|
||||
- if DC signal, set DC (PWM)
|
||||
- Power ON LM 18200
|
||||
|
||||
## Wiring
|
||||
|
||||
|
||||
|
||||
## Wiring
|
||||
|
||||
Below is a general guide on how to wire the main elements of the DCC-Central system, with suggested ESP32 GPIO numbers:
|
||||
|
||||
- **ESP32-S3 WROOM devKit**: Serves as the main controller. All peripherals connect to its GPIO pins.
|
||||
|
||||
- **ILI9341 3.2" Display Module**: Connect the SPI pins to the ESP32 as follows (suggested):
|
||||
- MOSI: GPIO 23
|
||||
- MISO: GPIO 19
|
||||
- SCK: GPIO 18
|
||||
- CS: GPIO 5
|
||||
- DC: GPIO 21
|
||||
- RESET: GPIO 22
|
||||
Power the display with 3.3V or 5V as required by the module.
|
||||
|
||||
- **Touch Screen**: If integrated with the display, connect the touch controller (often XPT2046) SPI pins as follows (suggested):
|
||||
- T_MOSI: GPIO 13
|
||||
- T_MISO: GPIO 12
|
||||
- T_SCK: GPIO 14
|
||||
- T_CS: GPIO 15
|
||||
- T_IRQ: GPIO 27
|
||||
You may share SPI bus with the display if supported. Connect IRQ pin to a free GPIO.
|
||||
|
||||
- **MCP2515 CAN Interface**: Connect SPI pins as follows (suggested):
|
||||
- MOSI: GPIO 23 (shared with display)
|
||||
- MISO: GPIO 19 (shared with display)
|
||||
- SCK: GPIO 18 (shared with display)
|
||||
- CS: GPIO 4
|
||||
- INT: GPIO 16
|
||||
Connect CANH and CANL to the CAN bus. Power with 3.3V or 5V as required.
|
||||
|
||||
- **LM18200 H-Bridge**: Connect control pins as follows (suggested):
|
||||
- IN1: GPIO 32
|
||||
- IN2: GPIO 33
|
||||
- PWM: GPIO 25
|
||||
Connect motor outputs to the track or accessories. Power the H-Bridge with the appropriate voltage for your track/accessories.
|
||||
|
||||
- **ACS712 Current Sensor**: Connect VCC and GND to power, OUT to ESP32 GPIO 36 (analog input, VP). Place the sensor in series with the track power line.
|
||||
|
||||
- **RailCom Detector**: Connect the detector output to ESP32 GPIO 26 (digital input). Follow the detector's datasheet for power and signal wiring.
|
||||
|
||||
- **KY-040 Rotary Encoder**: Connect as follows (suggested):
|
||||
- CLK: GPIO 34
|
||||
- DT: GPIO 35
|
||||
- SW: GPIO 17 (if used)
|
||||
Use pull-up resistors if required.
|
||||
|
||||
- **Relay**: Connect relay control pin to ESP32 GPIO 2. Use a transistor driver circuit if the relay requires more current than the ESP32 can provide. Connect relay contacts to switch between 2-rail and 3-rail track wiring.
|
||||
|
||||
### ESP32 Pin Usage Summary
|
||||
|
||||
| Peripheral | Signal | ESP32 GPIO |
|
||||
|---------------------------|----------------|------------|
|
||||
| ILI9341 Display | MOSI | 23 |
|
||||
| | MISO | 19 |
|
||||
| | SCK | 18 |
|
||||
| | CS | 5 |
|
||||
| | DC | 21 |
|
||||
| | RESET | 22 |
|
||||
| Touch Screen (XPT2046) | T_MOSI | 13 |
|
||||
| | T_MISO | 12 |
|
||||
| | T_SCK | 14 |
|
||||
| | T_CS | 15 |
|
||||
| | T_IRQ | 27 |
|
||||
| MCP2515 CAN Interface | CS | 4 |
|
||||
| | INT | 16 |
|
||||
| LM18200 H-Bridge | IN1 | 32 |
|
||||
| | IN2 | 33 |
|
||||
| | PWM | 25 |
|
||||
| ACS712 Current Sensor | OUT | 36 (VP) |
|
||||
| RailCom Detector | OUT | 26 |
|
||||
| KY-040 Rotary Encoder | CLK | 34 |
|
||||
| | DT | 35 |
|
||||
| | SW | 17 |
|
||||
| Relay | CTRL | 2 |
|
||||
|
||||
|
||||
**Tips:**
|
||||
- Double-check voltage compatibility for each module.
|
||||
- Use common ground for all modules.
|
||||
- Keep power and signal wires as short as possible to reduce noise.
|
||||
- Refer to each module's datasheet for exact pinouts and requirements.
|
||||
|
||||
**NB** a PCB card will be designed with KiCad Software for easier assembly.
|
||||