I’ve spent a lot of time on conference calls, probably too much.  I try not to let that stop me from doing productive work while I’m on them though.  With a good headset, I can participate on a call without disturbing others and have my hands free to do whatever other computer work I have to.

Landline Headset

Typical Mono Landline Headset

One thing that annoys me about most phone headsets is that they are really nothing more than an altered phone handset that you wear on your head.  They are mono, with a single, wimpy speaker, like the one on the left.  Staying on the phone for a long duration with only one ear participating is kind of annoying.

I’d much rather have a more comfortable PC headset that I use for skype conversations.  I purchased a 3.5mm to PC Headset adapter so that I could use my PC headset with my mobile phone (most have 3.5mm jacks).  This adapter worked well, and allows PC headsets to be used with 3.5mm jacks.

3.5mm to PC Headset adapter

Since that worked well, I wanted to do the same thing for my standard telephone, which had a 2.5mm jack.

I started by looking at converting the 3.5mm plug from the adapter I used with my mobile phone to a 2.5mm plug, required by my office phone.

An adapter like this cost only a few dollars, so I ordered it.  This worked, but not quite the way I had expected.  Even though my PC headset had two headphone speakers, sound only came out of one side.  This makes sense, since traditional phones are mono devices.

What I really wanted is the sound to come out of both earphone speakers (right and left) – even if it was the same mono signal going to both sides.

I could have tried an alternate path, like purchasing a PC to 2.5mm adapter, but I suspected that this would produce the same result.  Instead, I started searching for an adapter with 2.5mm plug on one end and 3.5mm jack on the other end.  In addition, the sound on the single audio channel of the 2.5mm plug should be duplicated to both channels of the 3.5mm jack.  After much searching, I could not find such an adapter.  I did find an adapter that did this for audio only,  but did not handle the microphone signal.  I can’t believe such a thing wasn’t available for purchase, and I still think it’s my error in not phrasing the search correctly.  However, it didn’t seem that hard to just make one from parts, so that’s what I did.  I also didn’t find anyone else describing a simple DIY project to handle this, so I figured I’d do that at the same time.

So what I’m trying to build is an adapter that plugs into a 2.5mm traditional mono telephone headset jack, so it should look just like the jack on a standard one-headphone headset:

2.5mm Plug

and on the other side ends in a 3.5mm 4 conductor jack:

3.5mm jack

capable of receiving this 3.5mm 4 conductor plug:

3.5mm plug

What I essentially want to do is map the mono audio channel to both the right and left audio channel of the 3.5mm stereo plug, like this:

Desired connections between plugs.

In my case, this mapping works.  Different devices use different pinouts.  I discovered this largely through trial and error.

The 2.5mm jack I used was purchased at Mouser.   I’m sure there’s a better option for this, as this one was quite difficult for me to work with.  I didn’t find another option, so I made that work. I had to remove the outer ring to get to a decent solder point, then I used a multimeter to figure out what connects to what, as shown:

2.5mm plug, with connections

It was difficult to get the points hot enough to take the solder, but after ruining one plug, I was careful and eventually successful at the second attempt (always buy an extra if the components are cheap).

2.5mm connected

It’s not pretty, but it works.  The jack I purchased was much easier to use given my meager soldering skills.  Its connected to the plug like this:

Jack connection

After the jack’s cover gets screwed on and I put some protective heat shrink tubing on it, it looks like this, and works like a charm.

All done

Update: Eventually, the solder connections on the plug wore out. I searched for ‘2.5mm stereo male plug’ on ebay – plenty of hits and easier to work with. I purchased 20 for $5 of these:

plug_with_cover

2.5mm male plug from ebay

Connections work out like this:

plug

2.5mm male plug, without cover

and the finished product:

soldered


zFinished
All soldered and finished

Retractable wheel Desk

October 11, 2010

I needed a desk that looked solid and part of the builtins surrounding it, but that could be moved fairly easily without lifting it. To move around easily, I wanted it to have wheels, but I didn’t want them to be visible at all. I also didn’t want the desk to move once it was in position (no accidental rolling). If I installed braking casters under the desk, the whole desk would have to be elevated and the wheels would be partially visible. Furthermore, I’d have to get to the break levers on each of the wheels, which would be awkward.

I went in a different direction, inspired by an old episode of The New Yankee Workshop. In the episode, Norm creates a work table that sits solidly on legs at the same height as the table saw. Simple retractable wheels are made with door hinges and casters. I liked this so much for the workshop that I made one.

My desk would use a similar technique, only more compact and, because of that, slighly more mechanically complicated. The wheels are completely hidden when retracted.

The best way to describe this is through a series of pictures. First, to provide the overall picture, the desk currently looks like this (the drawers do not yet have fronts – I’m in the process of making raised panel fronts for them)

SANY0077

Hidden inside the bottom of both columns of the desk (below the bottom drawer) are the components of the retractable wheel system.

Wheels

The first component are the wheels/casters. The casters are mounted on strips of plywood, with a total of two casters per strip and two strips per column. The strips are attached to the inside of the columns with hinges. Here’s one of the strips, all attached:

SANY1754

And here’s a view of both strips attached, looking down from the top of one of the desk’s columns.

SANY1756

At this point, the plywood mounted wheels flip up and down freely on the hinges.

Stilts

Mounted above the wheels is a piece of plywood that forms the bottom of the storage area (the part where the drawers will be mounted) of the desk. The underside of this bottom piece has mounted a couple of strips of plywood, reinforced (and weighed down) with some angle iron. These strips are also mounted on hinges.

SANY1783

When in the down position, these stilts support the wheels, keeping them from retracting. When up, they allow the wheels to flip up and out of the way.

Controls

In the New Yankee Workshop table, the stilts were very long. When the table was lifted, the stilts swung down with only the power of gravity and locked the wheels down. This did not work so reliably for the desk, since the stilts are only a few inches wide. To assist the process, I installed some cord that could be pulled to lift the stilts up and pull them down. To make things work smoothly, I used some small pulleys in places where the cord changed directions.

This picture shows the first cord, which, when the middle pulley is pulled, forces the stilts into the downward position (where they will hold the wheels up).

SANY1811

Two separate cord setups are needed for each desk column. Pulling one of the cords pulls the stilts down, pulling the other pulls the stilts up. Here’s a video that shows this work in isolation:

Here’s a picture of the full array of pulleys and controls, installed into one of the desk’s columns.

SANY1894

And a short video of it in action, on its side:

Operation

Once all assembled into the desk, it’s operated in the following manner. From a rested (wheels hidden) position, you lift the desk a little bit (while sitting on a chair at the desk) with your knee and simultaenously pull the cord that moves the stilts into the down position (perpendicular to the bottom). At this point the wheels will take the load of the desk so you can wheel it around.

From the wheels down position, to get it to the rested (wheels hidden) position, once again lift the desk slightly with your knee and pull the cord that draws the stilts up (into a more parallel position with the bottom). Stop lifting with your knee and it should be resting completely on the floor with the wheels totally retracted and hidden.

This short video demonstrates the process:

For a more detailed description of the build, see this flickr set.

I spend a great deal of time operating from the command line, be it on OSX, Linux or Cygwin (on Windows). My primary machine runs OSX, and from time to time I wished I could view the files in the current working directory in the OSX Finder. For whatever reason, I’m usually in too much of a rush to search for or think about a quicker way to invoke the finder with the current directory from the command line. Instead, I wind up just opening the finder and (re)navigating to the same directory.

The other day, for whatever reason, I tried something that just seems sooo obvious in hindsight – use the OSX open command, passing in the current working directory.

flurge:~ user$ open .

Voila – new finder window is opened right off the command line. I should have thought about it sooner.

I’ve recently completed a device that I’ve been idly threatening to make for years. This is a device that has no purpose other than entertainment, and it’s fairly limited in that regard too. It was fun building it, and it was a great arduino and electronics learning project for me.

For years my kids have argued over which one of them gets to push the buttons on the elevator control panels in malls and hotels that we would go to. The inside buttons are much more desireable to push than the outside call buttons and thus it would lead to debate outside the elevator and the inevitable delays involved in figuring things out. Since this provided so much entertainment for them, I’ve been threatening for years to just build a box with buttons that light up. When I finally got around to carrying it out, things got way out of hand and I wound up just building the entire elevator panel.

So here’s the completed panel, front and back:

SANY1571

SANY1586

For a quick demo of it in action:

And just to make things a bit interesting, it’s got an ‘alarm’ mode that lights up randomly upon floor button push (there’s a one out of 25 chance of it going off).

In addition, I wanted to add some games, but haven’t gotten around to it yet. The first will be a variant of a ‘Whac-a-mole’ game. One button will light up for a brief time and if the player hits that button, he gets a point. The game gets progressively harder as the user gains points.

How it’s built

The essential hardware components are:

  • Boarduino (arduino clone from Adafruit industries)
  • 74HC595 8-Bit Serial-to-Parallel Shift Register (I got these from Electronix Express
  • 7 Segment display (also from Electronix Express
  • LED Illuminated Momentary push buttons (I got mine from ebay – real elevator buttons are fairly expensive but available from the major electronics dealers like digikey or mouser).
  • Piezo Buzzer
  • Up, Down LEDs
  • Super bright, flashing red and blue LEDs (for the alarm)

I took many pictures of it during it’s construction and posted them into a flickr set. There’s much more visual detail there.

The Arduino is controlling the LEDs, 7 segment display and buzzer using an elevator algorithm (everyone is familiar with this one – if it’s going up, it stops at each selected floor until it has reached the top floor requested, then it reverses course to get floors in the opposite direction). It obviously can’t actually go up, so it’s just waiting a predetermined amount of time before ‘reaching’ the next floor (which triggers the 7 segment display to change and the piezo buzzer to give off an elevator-like chirp). Since we don’t have any doors to open when we reach a floor where we want to stop, the 7 segment display does a left to right, then right to left ‘swipe’ effect along with as close to a ‘swoosh’ sound effect that a piezo buzzer can make. That’s the best I could do with the limited hardware.

Since there’s a total of 20 LEDs (8 button LEDs, 8 for the 7 segment display plus up/down and alarm LEDs) to control and that alone outnumbers the output pins on an arduino, I needed to use something else to control them. I chained 3 595 Shift Registers together and thus with only 3 output pins, I can control up to 24 outputs. (Use of these IC’s is described in a tutorial on the Arduino web site.

The Code

The code is structured around a giant loop looking for events (just like the early days of windows programming, prior to the availability of more event driven models). As events (like ‘reached a floor’ or ‘button pressed’) are detected, event processing functions are called to trigger the buzzer, change the display or keep a button LED lit.

When I started this, I could not find any event driven libraries for Arduino. That would have made things easier to code and understand. A quick search now yields the Aiko framework, which sounds pretty interesting.

The current Arduino sketch looks like this:

/********************************************************************************
 * Arduino sketch that simulates the interactivity of an elevator control panel.*
 * Hardware includes:                                                           *
 * - 8 momentary illuminated floor buttons                                      *
 * - seven segment display                                                      *
 * - piezo buzzer                                                               *
 * - up and down LEDs                                                           *
 * - flashing red and blue alarm LEDs                                           *
 ********************************************************************************/
//CONSTANTS
// Arduino data pins -----------------------------------------------------------
// toggle is hooked up to ANALOG IN 0
#define MODETOGGLE  0
// the rest is hooked up to digital in.
#define BUTTON1     2
#define BUTTON2     3
#define BUTTON3     4
#define BUTTON4     5
#define BUTTON5     6
#define BUTTON6     7
#define BUTTON7     8
#define BUTTON8     9
#define BUZZER      10
//Pin connected to ST_CP of all 74HC595 shift registers
#define LATCHPIN    11
//Pin connected to SH_CP of all 74HC595 shift registers
#define CLOCKPIN    12
////Pin connected to DS of first 74HC595 shift register
#define DATAPIN     13
// Byte shift positions of various LEDs ---------------------------------------
#define UPARROW_SHIFT 0
#define DOWNARROW_SHIFT 1
#define REDFLASHLED_SHIFT 2
#define BLUEFLASHLED_SHIFT 3
// other constants ------------------------------------------------------------
// debug related
#define ERROR 100
#define WARN 200
#define INFO 300
#define DEBUG 400
#define TRACE 500
#define ELEVATOR_MODE 0
#define WACKAMOLE_MODE 1
// analog level sent to piezo at floor arrival beep
#define FLOORBUZZLEVEL  150
// # of millis we buzz for a floor
#define FLOORBUZZMILLIS  300
// how long between the parts of the door swish animation
#define DOOR_SWISH_MILLIS 95
// how long to wait before running swish
#define PRE_SWISH_DELAY_MILLIS 250
// how long to wait after running swish
#define POST_SWISH_DELAY_MILLIS 300
// how long it takes to move from one floor to another
#define FLOOR_TRAVELTIME_MILLIS 1700
#define NUM_FLOOR_BUTTONS  8
#define HALF_NUM_FLOOR_BUTTONS  4
// the number of bytes needed to control all our 595 shift regs.
#define NUM_SHIFT_BYTES    3
// the number of phases for the 'door swipe' effect on the seven segment display
#define SWIPE_PHASE_COUNT  4
// stuff used by the startup routine
#define STARTUP_DELAY_MILLIS 750
#define STARTUP_BUZZ_MILLIS 250
#define STARTUP_BUZZ_ITERS 2
#define STARTUP_ITER_BUZZLEVEL 100
// siren stuff
#define SIREN_START_LEVEL 70
#define SIREN_STOP_LEVEL 200
#define SIREN_STEP_AMOUNT 10
#define SIREN_STEP_MILLIS 50
#define SIREN_SLEEP_BETW 200
// FREAKOUT -----------------------------------------------------------------------
#define FREAKOUT_CHANCE 25
// the number of phases of animation the seven seg goes through
#define SEVEN_SEG_FREAK_PHASES 5
// num millis of wait between each phase of animation
#define FREAK_CUMULATIVE_DELAY 175
// num millis of wait after last phase
#define FREAK_CUMULATIVE_END_DELAY 750
// num iterations of up and down motion (this is how many iterations of the entire effect)
#define FREAK_UP_DOWN_ITERATIONS 2
// num falling iterations
#define FREAK_FALLING_ITERATIONS 12
// num round and round iterations (this is a max, since the actual
//   iterations will be randomly chosen from 1-FREAK_ROUND_ITERATIONS
#define FREAK_ROUND_ITERATIONS 5
// --------------------------------------------------------------------------------
// binary representation of the bit patterns needed to display 0 .. 9 on the 7 segment display
// when it is attached to a 595 shift register IC.
// ASSUMES A attached to pin 1, B attached to pin 2, and so on (pin 0 attached to DP)
//   so the bits that matter are A-G (for any standard 7 segment display):      GFEDCBA-
// 7 seg display led pattern:
//   -A-
//  F   B
//   -G-
//  E   C
//   -D-    DP
//                                 GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-
byte BINARY_NUMBER_PATTERNS[10]= {B01111110, B00001100, B10110110, B10011110, B11001100, B11011010, B11111010, B00001110, B11111110, B11011110};
// 7 segment effects, round and round --------------------------------
byte SEVEN_SEGMENT_ROUND_N_ROUND[] =       { B00000010, B00000100, B00001000, B00010000, B00100000, B01000000 };
// 7 segment effects, side to side -----------------------------------
byte SEVEN_SEGMENT_LEFT_TO_RIGHT_MASK[SWIPE_PHASE_COUNT] = {       B10011110, B00001100, B00000000, B00000001  };
byte SEVEN_SEGMENT_RIGHT_TO_LEFT_MASK[SWIPE_PHASE_COUNT] = {       B00000001, B00001100, B10011110, B11111110  };
// 7 segment effects, top to bottom ----------------------------------
// single row ON, top to bottom {off,A,F+B,E+C,D}                               GFEDCBA-   GFEDCBA-   GFEDCBA-   GFEDCBA-    GFEDCBA-
byte SEVEN_SEGMENT_SINGLE_ROW_TOP_TO_BOTTOM[SEVEN_SEG_FREAK_PHASES] =         {B00000000, B00000010, B01000100, B00101000, B00010000};
// single row ON, bottom to top {off, D,E+C,F+B,A}
byte SEVEN_SEGMENT_SINGLE_ROW_BOTTOM_TO_TOP[SEVEN_SEG_FREAK_PHASES] =         {B00000000, B00010000, B00101000, B01000100, B00000010};
// cumulative rows ON, top to bottom {off,A,AFB,AFBEC,AFBECD}
byte SEVEN_SEGMENT_CUMULATIVE_ROWS_ON_TOP_TO_BOTTOM[SEVEN_SEG_FREAK_PHASES] = {B00000000, B00000010, B01000110, B11101110, B11111110};
// cumulative rows ON, bottom to top {off,D,ECD,FBECD,FBECDA}
byte SEVEN_SEGMENT_CUMULATIVE_ROWS_ON_BOTTOM_TO_TOP[SEVEN_SEG_FREAK_PHASES] = {B00000000, B00010000, B00111000, B11111010, B11111110};
// cumulative rows OFF, bottom to top {FEDCBA, FECBA, FBA, A, off}
byte SEVEN_SEGMENT_CUMULATIVE_ROWS_OFF_BOTTOM_TO_TOP[SEVEN_SEG_FREAK_PHASES] ={B01111110, B01101110, B01000110, B00000010, B00000000};
// cumulative rows OFF,top to bottom { off, A, FBA,FECBA, FEDCBA }
byte SEVEN_SEGMENT_CUMULATIVE_ROWS_OFF_TOP_TO_BOTTOM[SEVEN_SEG_FREAK_PHASES] ={B01111110, B01111100, B00111000, B00010000, B00000000};
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// Control Vars
int LOG_LEVEL = INFO;
// direction of elevator (-1=down, 0=dormant, +1=up)
int _currentDirection = 0;
// floor the elevator is on (1 - 8)
int _currentFloor = 1;
// when we should get to the next floor
unsigned long _nextFloorArrivalTime = 0;
// if we're buzzing, this is when we stop
unsigned long _currentBuzzStopTime = 0;
// the previous values of buttons 1-8 [indexes 0-7, respectively]; Values are 0 or 1
int _prevFloorButtonValues[NUM_FLOOR_BUTTONS] = {0,0,0,0,0,0,0,0};
int _floorButtonInputPins[NUM_FLOOR_BUTTONS] = {BUTTON1, BUTTON2, BUTTON3, BUTTON4, BUTTON5, BUTTON6, BUTTON7, BUTTON8};
// current values of other LED's
int _currentUpArrowLedVal = LOW;
int _currentDownArrowLedVal = LOW;
int _currentRedFlashLedVal = LOW;
int _currentBlueFlashLedVal = LOW;
// this is what we want the current value of the 7 segment display to be.
//  see the comments near the constant BINARY_NUMBER_PATTERNS for the value
//  of each bit.
byte _currentSevenSegByte = B00000000;
// mode of 0 is elevator mode, nonzero is whackamole (tbd)
byte _mode = ELEVATOR_MODE;
boolean _modeChangeFlag = false;
// flag that tells us that the last loop finished a floor buzz.
boolean _doneFloorBuzzEventFlag = false;

// --------------------------------------------------------------------------------
//  Startup
// --------------------------------------------------------------------------------

void setup() {
 log(INFO, "setup()", "Starting up...");
 log(INFO, "setup()", true);
 log(INFO, "setup()", 99);
 log(INFO, "      high: ", HIGH);
 log(INFO, "      low: ", LOW);

 //  pinMode(MODETOGGLE, INPUT);
 for(int i=0; i<NUM_FLOOR_BUTTONS; i++) {
 pinMode(_floorButtonInputPins[i], INPUT);
 }
 randomSeed(analogRead(0));

 pinMode(BUZZER, OUTPUT);
 pinMode(LATCHPIN, OUTPUT);
 pinMode(CLOCKPIN, OUTPUT);
 pinMode(DATAPIN, OUTPUT);
 Serial.begin(9600);

 initState();
 // todo: run a diagnostic routine to excercise all components
}

void initState() {
 _currentDirection = 0;
 _currentFloor = 1;
 _nextFloorArrivalTime = 0;
 _currentBuzzStopTime = 0;
 _currentUpArrowLedVal  = LOW;
 _currentDownArrowLedVal = LOW;
 _currentRedFlashLedVal = LOW;
 _currentBlueFlashLedVal = LOW;
 _currentSevenSegByte = B00000000;
 _modeChangeFlag = false;
 _doneFloorBuzzEventFlag = false;
 setAllFloorButtonLEDs(0);
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
}

// --------------------------------------------------------------------------------
//  Main Loop
// --------------------------------------------------------------------------------
void loop() {
 // runStartupRoutine();
 // delay(3000);
 loopAction();

 // test the 7segment
 //testSevenSegment();
}

/* run the main loop depending on what mode the toggle button is in.
 * There is only one mode implemented (elevator simulation) at this time.
 */
void loopAction() {
 log(TRACE, "TOP of loopAction, mode: ", _mode);
 // was a button pushed? if so, update state
 checkForAnyButtonPresses();
 // now, everything else is mode dependent, so breakout.
 if(_mode==ELEVATOR_MODE) elevatorModeLoopAction();
 else // it's wackamole
 wackamoleLoopAction();
 // update all display LEDs controlled thru shift regs.
 log(TRACE, "*updateShiftRegs ", "");
 updateShiftRegisters();
}

/* Placeholder for TBD mode -
 *  This mode would emulate a wackamole type carnival game with the floor buttons.
 */
void wackamoleLoopAction() {
 // todo: implement wackamole loop
 elevatorModeLoopAction();
}

/* Control loop for the elevator mode.  Each iteration checks for conditions and reacts to them.
 * It is really a problem that could use an event driven model solution.  Since we don't have that
 * we just loop over and look for conditions in a big if .. then .. else ..
 */
void elevatorModeLoopAction() {
 log(TRACE, "elevatorModeLoopAction  ----- fl. ", _currentFloor);
 // have we arrived at a floor yet (assuming we're moving, that is)?
 if(isFloorReached()) { // floor arrival ------------------------------------------------
 log(TRACE, "*floor reached ", "");
 int newFloor = _currentFloor + _currentDirection;
 // update _currentFloor, re-init _nextFloorArrivalTime, starts buzz
 processFloorArrivalEvent(newFloor);
 } else if(isFloorBuzzing()) { // floor buzz in progress --------------------------------
 log(TRACE, "*floor buzzing ", "");
 // clear _currentBuzzStopTime and stop buzzing
 _doneFloorBuzzEventFlag = processInFloorBuzzEvent();
 } else if (_doneFloorBuzzEventFlag) {   // floor buzz over -----------------------------
 log(TRACE, "*doneFloorBuzzEvent triggered ", "");
 _doneFloorBuzzEventFlag = false;
 // are we stopping on this floor???
 if(isStoppingOnFloor(_currentFloor)) {
 log(TRACE, "*stoppingOnFloor: ", _currentFloor);
 // ok, turn off button light, open/close door
 processStopAtFloorEvent(_currentFloor);
 }
 // should this be broken out of the parent conditional?  should it happen this cycle, or wait for the next?
 //    figure out directional logic:(figure out if we're still moving, in what direction, etc.)
 if(isAFloorRequestedInDirection(_currentDirection)) {
 log(TRACE, "*floorRequestedInDirection, curFl: ", _currentFloor);
 // start countdown to next floor,
 processElevatorMovementStartingEvent();
 } else if(isAFloorRequestedInDirection(getOppositeDirection(_currentDirection))) {
 log(TRACE, "*floorRequestedInOPPOSITEDirection, curFl: ", _currentFloor);
 // this resets _currentDirection and the up/down LEDs
 processDirectionMovementChangeEvent(getOppositeDirection(_currentDirection));
 // start countdown to next floor,
 processElevatorMovementStartingEvent();
 } else {
 log(TRACE, "*NOT MOVING ANYMORE, curFl: ", _currentFloor);
 // we're officially not moving
 // this resets _currentDirection and the up/down LEDs
 processDirectionMovementChangeEvent(0);
 }
 } else if(_currentDirection==0) {   // elevator is IDLE --------------------------------
 log(TRACE, "*ELEVATOR idle, curFl: ", _currentFloor);
 if(isAFloorRequestedInDirection(1)) {
 log(TRACE, "*Floor REQUESTED in DIRECTION: ", "up");
 processDirectionMovementChangeEvent(1);
 processElevatorMovementStartingEvent();
 } else if(isAFloorRequestedInDirection(-1)) {
 log(TRACE, "*Floor REQUESTED in DIRECTION: ", "down");
 processDirectionMovementChangeEvent(-1);
 processElevatorMovementStartingEvent();
 }
 }
 log(TRACE, "*THE END elevatorModeLoopAction() --------", "");
}

// --------------------------------------------------------------------------------
//  Startup Routine
// --------------------------------------------------------------------------------
// run a sequence of display actions, used to verify hardware's working and
//  offer a bit of LED eye candy.
void runStartupRoutine() {
 // display 0 ---------------------------------------------------------------
 setCurrentSevenSegmentToDecimalNumber(0);
 updateShiftRegisters();
 delay(STARTUP_DELAY_MILLIS);
 // foreach floor, 1..8, display number, beep, light floor button -----------
 setUpArrowLEDCurrentState(HIGH);
 for(_currentFloor=1; _currentFloor<=NUM_FLOOR_BUTTONS; _currentFloor++) {
 setAllFloorButtonLEDs(0);
 setFloorButtonLED(_currentFloor, 1); // turn on the current floor
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 if(_currentFloor==NUM_FLOOR_BUTTONS) setUpArrowLEDCurrentState(LOW);
 updateShiftRegisters();
 runBeep(FLOORBUZZLEVEL, STARTUP_BUZZ_MILLIS, STARTUP_DELAY_MILLIS);
 }
 // beep, beep --------------------------------------------------------------
 runStartupBeepBeep();
 // run swipe ---------------------------------------------------------------
 processDoorOpenCloseEvent();
 // beep, beep --------------------------------------------------------------
 runStartupBeepBeep();
 // now back go down --------------------------------------------------------
 setDownArrowLEDCurrentState(HIGH);
 for(_currentFloor=NUM_FLOOR_BUTTONS; _currentFloor>0; _currentFloor--) {
 setAllFloorButtonLEDs(0);
 setFloorButtonLED(_currentFloor, 1); // turn on the current floor
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 if(_currentFloor==1) setDownArrowLEDCurrentState(LOW);
 updateShiftRegisters();
 runBeep(FLOORBUZZLEVEL, STARTUP_BUZZ_MILLIS, STARTUP_DELAY_MILLIS);
 }
 // beep, beep --------------------------------------------------------------
 runStartupBeepBeep();
 // flash alarm and beep ----------------------------------------------------
 setRedFlashLEDCurrentState(HIGH);
 setBlueFlashLEDCurrentState(HIGH);
 updateShiftRegisters();
 runSirenSound(5);
 // lights out. sleep.
 initState();
 updateShiftRegisters();
 delay(300);
}

void runStartupBeepBeep() {
 for(int i=0; i<STARTUP_BUZZ_ITERS; i++)
 runBeep(STARTUP_ITER_BUZZLEVEL, STARTUP_BUZZ_MILLIS, STARTUP_DELAY_MILLIS);
}

void runSirenSound(int pNumberSirens) {
 for(int i=0; i<pNumberSirens; i++) {
 int curLevel = SIREN_START_LEVEL;
 while(curLevel<SIREN_STOP_LEVEL) {
 analogWrite(BUZZER, curLevel);
 delay(SIREN_STEP_MILLIS);
 curLevel += SIREN_STEP_AMOUNT;
 }
 analogWrite(BUZZER, 0);
 delay(SIREN_SLEEP_BETW);
 }
}

// run the buzzer at analog level pBuzzLevel for pBuzzDurationMillis.
//  then turn it off and delay an additional pPostBuzzMillis
void runBeep(int pBuzzLevel, int pBuzzDurationMillis, int pPostBuzzMillis) {
 analogWrite(BUZZER, pBuzzLevel);
 delay(pBuzzDurationMillis);
 analogWrite(BUZZER, 0);
 if(pPostBuzzMillis>0) delay(pPostBuzzMillis);
}

// --------------------------------------------------------------------------------
//  Event Processing Functions
// --------------------------------------------------------------------------------

// called when it is determined that we are in a floor buzz.
// Figures out if we have to stop buzzing.  If so, turn off up/down LED
// returns true if buzz is DONE.
boolean processInFloorBuzzEvent() {
 unsigned long now = millis();
 boolean buzzDone  = false;
 if(isFloorBuzzing()) {
 if(_currentBuzzStopTime<now) {
 _currentBuzzStopTime=0;
 // we should stop buzzing
 analogWrite(BUZZER, 0);
 buzzDone=true;
 }
 }
 return buzzDone;
}

// called when it is determined that we have reached a floor
// kick off the beep, reset the floor arrival timer, update the floor number
void processFloorArrivalEvent(int pFloorNumber) {
 unsigned long now     = millis();
 _nextFloorArrivalTime = 0;
 _currentFloor         = pFloorNumber;
 _currentBuzzStopTime  = now + FLOORBUZZMILLIS;
 analogWrite(BUZZER, FLOORBUZZLEVEL);
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
}

// Called when we are to stop at a requested floor.
// we must turn off button light and 'open/close' the door
void processStopAtFloorEvent(int pFloorNumber) {
 checkForAnyButtonPresses();
 delay(PRE_SWISH_DELAY_MILLIS);
 checkForAnyButtonPresses();
 processDoorOpenCloseEvent();
 checkForAnyButtonPresses();
 setFloorButtonLED(pFloorNumber, LOW);
 delay(POST_SWISH_DELAY_MILLIS);
 checkForAnyButtonPresses();
}

// called when we arrive at a floor that has been selected for stopping.
//  This is called AFTER the audio effect is over.
//  Run a quickie visual effect - turn on the 7seg's decimal point, then run a
//  right to left swipe 'animation', leaving everything on.  This is followed
//  by a left to right swipe leaving the current floor number.
//  At the same time, simulate a swoosh sound effect on the piezo.
void processDoorOpenCloseEvent() {
 // start off with the floor number and turn on the decimal point
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 // setCurrentSevenSegmentDecimalPoint(true);
 // update the 7 seg, buttons, etc.
 updateShiftRegisters();
 delay(DOOR_SWISH_MILLIS);
 checkForAnyButtonPresses();
 // let's still respond to button presses while we're running the animation
 checkForAnyButtonPresses();
 // now step through the LEFT to RIGHT mask
 for(int i;i<SWIPE_PHASE_COUNT; i++) {
 _currentSevenSegByte = _currentSevenSegByte & SEVEN_SEGMENT_LEFT_TO_RIGHT_MASK[i];
 analogWrite(BUZZER, 40*i);
 updateShiftRegisters();
 delay(DOOR_SWISH_MILLIS);
 checkForAnyButtonPresses();
 }
 // now step through the RIGHT to LEFT mask
 for(int i;i<SWIPE_PHASE_COUNT; i++) {
 // in right to left, we always start with the actual number
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 setCurrentSevenSegmentDecimalPoint(true);
 _currentSevenSegByte = _currentSevenSegByte & SEVEN_SEGMENT_RIGHT_TO_LEFT_MASK[i];
 analogWrite(BUZZER, ((40*SWIPE_PHASE_COUNT)/i));
 updateShiftRegisters();
 delay(DOOR_SWISH_MILLIS);
 checkForAnyButtonPresses();
 }
 analogWrite(BUZZER, 0);
 // now return back to the number w/out the decimal point
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 updateShiftRegisters();
}

// called when the elevator changes direction (up, then down) or stops moving
//  because the top/bottom floor has been reached or no further floors have
//  been requested in the current direction.
// pDirection: >0 when new direction is up, <0 when down, =0 when stop
void processDirectionMovementChangeEvent(int pDirection) {
 int priorDirection = _currentDirection;
 _currentDirection  = pDirection;
 // reinitialize directional LED state vars
 setUpArrowLEDCurrentState(LOW);
 setDownArrowLEDCurrentState(LOW);
 // now set accordingly
 if(_currentDirection>0)      setUpArrowLEDCurrentState(HIGH);
 else if(_currentDirection<0) setDownArrowLEDCurrentState(HIGH);
}

// start countdown to next floor,
void processElevatorMovementStartingEvent() {
 unsigned long now     = millis();
 _nextFloorArrivalTime = now + FLOOR_TRAVELTIME_MILLIS;
}

// called when we determine that a floor button was just pushed.
void processFloorButtonPushEvent(int pFloorNumber) {
 // update state var
 setFloorButtonLED(pFloorNumber, HIGH);
 // todo: do we need to do anything else?
 log(DEBUG, "EVENT: floorbtnPush fired, floor: ", pFloorNumber);
 // every once in a while, make the panel flash like crazy just for fun.
 if( (_mode==ELEVATOR_MODE) && isFreakoutAlarmTriggered())    processFreakoutAlarmEvent();
}

// --------------------------------------------------------------------------------
//  Freakout Alarm
// --------------------------------------------------------------------------------
// determine if we should trigger a freakout alarm, when
//  all kinds of crazy stuff happens.
boolean isFreakoutAlarmTriggered() {
 long rand = random(FREAKOUT_CHANCE);
 if(rand == 1) {
 log(INFO, "FREAKOUT!", "");
 return true;
 }
 return false;
}

// save the current state of the elevator panel, then madly flash
//  the flasher diodes, play a siren on the piezo, flash all the buttons, 7seg
//  and up/down indicators for a few seconds.  Then return to normal.
// run a visual effect on the elevator panel, including:
// * blue and red flashing LEDs light up
// * buzzer is squeeling
// * 7segment flashes
// * button leds flash
void processFreakoutAlarmEvent() {
 // setup ---------------------------
 _currentFloor = 1;
 setRedFlashLEDCurrentState(HIGH);
 setBlueFlashLEDCurrentState(HIGH);
 setAllFloorButtonLEDs(LOW);
 setCurrentSevenSegmentToDecimalNumber(8);
 setCurrentSevenSegmentDecimalPoint(false);
 updateShiftRegisters();
 analogWrite(BUZZER, 0);
 delay(FREAK_CUMULATIVE_END_DELAY);
 for(int i=0; i<FREAK_UP_DOWN_ITERATIONS; i++) {
 // simulate rapid downward motion
 setDownArrowLEDCurrentState(HIGH);
 setUpArrowLEDCurrentState(LOW);
 runFreakoutEffect(SEVEN_SEGMENT_CUMULATIVE_ROWS_OFF_TOP_TO_BOTTOM,
 SEVEN_SEGMENT_SINGLE_ROW_TOP_TO_BOTTOM,
 SEVEN_SEGMENT_CUMULATIVE_ROWS_ON_TOP_TO_BOTTOM,
 false);
 analogWrite(BUZZER, 0);
 delay(FREAK_CUMULATIVE_END_DELAY);
 // go round and round
 _currentSevenSegByte = B00000000;
 setDownArrowLEDCurrentState(LOW);
 setUpArrowLEDCurrentState(LOW);
 updateShiftRegisters();
 for(int j=0; j<2; j++) {
 int r1 = random(FREAK_ROUND_ITERATIONS);
 for(int i=0; i<r1; i++) runFreakout7segRound(true);
 int r2 = random(FREAK_ROUND_ITERATIONS);
 for(int i=0; i<r2; i++) runFreakout7segRound(false);
 }

 // simulate rapid upward motion
 setDownArrowLEDCurrentState(LOW);
 setUpArrowLEDCurrentState(HIGH);
 runFreakoutEffect(SEVEN_SEGMENT_CUMULATIVE_ROWS_OFF_BOTTOM_TO_TOP,
 SEVEN_SEGMENT_SINGLE_ROW_BOTTOM_TO_TOP,
 SEVEN_SEGMENT_CUMULATIVE_ROWS_ON_BOTTOM_TO_TOP,
 false);
 }
 // re-init ---------------------------
 _currentFloor = 1;
 setRedFlashLEDCurrentState(LOW);
 setBlueFlashLEDCurrentState(LOW);
 setAllFloorButtonLEDs(LOW);
 setCurrentSevenSegmentToDecimalNumber(_currentFloor);
 setCurrentSevenSegmentDecimalPoint(false);
 setDownArrowLEDCurrentState(LOW);
 setUpArrowLEDCurrentState(LOW);
 updateShiftRegisters();
 analogWrite(BUZZER, 0);

 delay(FREAK_CUMULATIVE_END_DELAY);
}

boolean checkButtonForExitFreakout() {
 for(int i=1; i<=NUM_FLOOR_BUTTONS; i++) if(isFloorButtonJustPushed(i)) return true;
 return false;
}

void runFreakoutEffect(byte *pLeavingByte, byte *pGoingByte, byte *pArrivingByte, boolean pFloorButtonUp) {
 setCurrentSevenSegmentToDecimalNumber(8);
 setCurrentSevenSegmentDecimalPoint(false);
 updateShiftRegisters();
 adjustFreakoutBuzzer(1);
 delay(FREAK_CUMULATIVE_DELAY);
 // run leaving effect, ie going 'down' - rows disappear one at a time starting from bottom
 byte *bytePtr;
 for(int i=0; i<SEVEN_SEG_FREAK_PHASES; i++) {
 if(checkButtonForExitFreakout()) return;
 bytePtr = pLeavingByte + (i*sizeof(byte));
 _currentSevenSegByte = *bytePtr;
 setAllFloorButtonLEDs((i%2)==0 ? LOW:HIGH);
 updateShiftRegisters();
 adjustFreakoutBuzzer(i);
 delay(FREAK_CUMULATIVE_DELAY);
 }
 // run going effect, ie, fall, one row at a time
 int count = 0;
 int half_of_floors = (pFloorButtonUp) ? 0 : HALF_NUM_FLOOR_BUTTONS;
 for(int i=0; i<FREAK_FALLING_ITERATIONS; i++) {
 if(checkButtonForExitFreakout()) return;
 bytePtr = pGoingByte + ( (count % SEVEN_SEG_FREAK_PHASES) * sizeof(byte));
 _currentSevenSegByte = *bytePtr;
 // if 'going' down, we want to start with the top 2 floor buttons, otherwise bottom 2
 int floor = abs((half_of_floors - (count%HALF_NUM_FLOOR_BUTTONS)) * 2);
 setAllFloorButtonLEDs(LOW);
 setFloorButtonLED(floor, HIGH);
 setFloorButtonLED(floor-1, HIGH);
 updateShiftRegisters();
 adjustFreakoutBuzzer(i);
 delay(FREAK_CUMULATIVE_DELAY);
 count++;
 }
 // accumulate at the bottom
 for(int i=0; i<SEVEN_SEG_FREAK_PHASES; i++) {
 if(checkButtonForExitFreakout()) return;
 bytePtr = pArrivingByte + (i*sizeof(byte));
 _currentSevenSegByte = *bytePtr;
 setAllFloorButtonLEDs((i%2)==0 ? LOW:HIGH);
 updateShiftRegisters();
 adjustFreakoutBuzzer(i);
 delay(FREAK_CUMULATIVE_DELAY);
 }
 setAllFloorButtonLEDs(LOW);
}

void runFreakout7segRound(boolean pClockwise) {
 int num_round_n_round_phases = sizeof(SEVEN_SEGMENT_ROUND_N_ROUND) / sizeof(byte);
 if(pClockwise)
 for(int i=0; i< num_round_n_round_phases; i++) {
 runFreakout7segRound_1(i, pClockwise);
 }
 else
 for(int i=num_round_n_round_phases; i>0; i--) {
 runFreakout7segRound_1(i-1, pClockwise);
 }
}
void runFreakout7segRound_1(int pIndex, boolean pClockwise) {
 if(checkButtonForExitFreakout()) return;
 _currentFloor = nextFloorRoundRound(_currentFloor, pClockwise);
 setAllFloorButtonLEDs(LOW);
 setFloorButtonLED(_currentFloor, HIGH);

 _currentSevenSegByte = SEVEN_SEGMENT_ROUND_N_ROUND[pIndex];
 updateShiftRegisters();
 adjustFreakoutBuzzer(pIndex);
 delay(FREAK_CUMULATIVE_DELAY);
}

int nextFloorRoundRound(int pFloorNum, boolean pClockwise) {
 if(pClockwise) {
 switch(pFloorNum) {
 case 1 : return 3;
 case 2 : return 1;
 case 3 : return 5;
 case 4 : return 2;
 case 5 : return 7;
 case 6 : return 4;
 case 7 : return 8;
 case 8 : return 6;
 default : return 1;
 }
 } else { // counter clockwise
 switch(pFloorNum) {
 case 1 : return 2;
 case 2 : return 4;
 case 3 : return 1;
 case 4 : return 6;
 case 5 : return 3;
 case 6 : return 8;
 case 7 : return 5;
 case 8 : return 7;
 default : return 1;
 }
 }
}
void adjustFreakoutBuzzer(int pNum) {
 int HI = 180;
 int LO = 100;
 int lvl = (pNum % 2) == 0 ? LO : HI;
 analogWrite(BUZZER, lvl);
}

// --------------------------------------------------------------------------------
//  Condition Evaluation Functions
// --------------------------------------------------------------------------------

boolean isFloorButtonJustPushed(int pFloorNumber) {
 int index    = pFloorNumber-1;
 int inputPin = _floorButtonInputPins[index];
 int oldValue = _prevFloorButtonValues[index];
 log(TRACE, "isFloorButtonJustPushed? fl#: ", pFloorNumber);
 return (isButtonJustPushed(inputPin, oldValue));
}

boolean isButtonJustPushed(int pInputPin, int pOldValue) {
 log(TRACE, "  pOldValue: ", pOldValue);
 return ( isButtonPushed(pInputPin) && (pOldValue==LOW) );
}

boolean isButtonPushed(int pInputPin) {
 int val = digitalRead(pInputPin);
 boolean retval = (val==HIGH);
 log(TRACE, "  isButtonPushed on input pin: ", pInputPin);
 log(TRACE, "    is this pin high?: ", retval);
 return retval;
}

boolean isFloorBuzzing() {
 return (_currentBuzzStopTime>0);
}

// returns true if we're moving and we _just_ reached a floor
// reaching a floor means that the _nextFloorArrivalTime has been reached.
boolean isFloorReached() {
 unsigned long now = millis();
 return ( (_nextFloorArrivalTime>0) &&  (_nextFloorArrivalTime<now) ) ;
}
// are we supposed to be stopping on pFloorNum?
//  pFloorNum starts at 1
boolean isStoppingOnFloor(int pFloorNum) {
 boolean stopping = false;
 if( (pFloorNum>0) && (pFloorNum<=NUM_FLOOR_BUTTONS) ) {
 stopping = (_prevFloorButtonValues[pFloorNum-1] != 0);
 }
 return stopping;
}

// given the current direction and floor, is there another stop in this direction?
// pDirection is either -1 (down), +1(up) or 0(stationary)
boolean isAFloorRequestedInDirection(int pDirection) {
 boolean floorRequested = false;
 int nextFloor          = _currentFloor + pDirection;
 if(pDirection!=0) {
 while( !floorRequested && (nextFloor>0) && (nextFloor<=NUM_FLOOR_BUTTONS) ) {
 log(TRACE, "   is floor requested?: ", nextFloor);
 floorRequested = isStoppingOnFloor(nextFloor);
 nextFloor      += pDirection;
 }
 log(DEBUG, "floorRequested: ", floorRequested);
 if(floorRequested) log(INFO, "  nextFloor: ", nextFloor);
 }
 return floorRequested;
}

// --------------------------------------------------------------------------------
//  State Setting Functions
// --------------------------------------------------------------------------------

// set our internal representation of what the Up Arrow LED should be.
// pVal: either HIGH or LOW
void setUpArrowLEDCurrentState(int pVal) {
 _currentUpArrowLedVal = pVal;
}

// set our internal representation of what the Down Arrow LED should be.
// pVal: either HIGH or LOW
void setDownArrowLEDCurrentState(int pVal) {
 _currentDownArrowLedVal = pVal;
}

// set our internal representation of what the Red Flash LED should be.
// pVal: either HIGH or LOW
void setRedFlashLEDCurrentState(int pVal) {
 _currentRedFlashLedVal = pVal;
}

// set our internal representation of what the Blue Flash LED should be.
// pVal: either HIGH or LOW
void setBlueFlashLEDCurrentState(int pVal) {
 _currentBlueFlashLedVal = pVal;
}

// set our internal representation of what pFloorNum's LED should be
// pVal: either HIGH or LOW
// pFloorNum: the floor number from 1 to 8 (NUM_FLOOR_BUTTONS)
void setFloorButtonLED(int pFloorNum, int pVal) {
 if(pVal==LOW) log(DEBUG, "turning off button for floor: ", pFloorNum);
 _prevFloorButtonValues[pFloorNum-1] = pVal;
}

void setAllFloorButtonLEDs(int pVal) {
 for(int j=1; j<=NUM_FLOOR_BUTTONS; j++) setFloorButtonLED(j, pVal);
}

// set the internal state's working variable for the pattern of what
//  should be displayed on the 7segment display to the given decimal integer.
void setCurrentSevenSegmentToDecimalNumber(int pNumForDisplay) {
 _currentSevenSegByte = BINARY_NUMBER_PATTERNS[pNumForDisplay];
 log(DEBUG, "set 7seg decimal: ", pNumForDisplay);
 log(TRACE, "  -->7seg pattern: ", (int)_currentSevenSegByte, BIN);
}

// turns on or off the decimal point
void setCurrentSevenSegmentDecimalPoint(boolean pShowDP) {
 log(TRACE, "set 7seg DP?: ", pShowDP);
 log(TRACE, "      7seg pattern BEFORE: ", (int)_currentSevenSegByte, BIN);
 if(pShowDP)  _currentSevenSegByte = _currentSevenSegByte | 1 ;
 else         _currentSevenSegByte = _currentSevenSegByte & B11111110 ;
 log(DEBUG, "  -->7seg pattern AFTER : ", (int)_currentSevenSegByte, BIN);
}

// --------------------------------------------------------------------------------
//  Input Processors
// --------------------------------------------------------------------------------

// checks for any button presses of any sort
void checkForAnyButtonPresses() {
 checkForFloorButtonPresses();
 // todo: check the other buttons and switches
 // read toggle button - MODETOGGLE input
 checkForModeButtonPress();
}

// TODO: since we have only one mode, this does nothing right now.
void checkForModeButtonPress() {
 //  int newMode = digitalRead(MODETOGGLE);
 /*  int newMode = analogRead(MODETOGGLE);
 if( (_mode!=ELEVATOR_MODE) && (newMode==ELEVATOR_MODE) ) {
 // we went from whackamole to elevator mode
 processModeChange(ELEVATOR_MODE);
 } else if( (_mode==ELEVATOR_MODE) && (newMode!=ELEVATOR_MODE) ) {
 // we went from elevator mode to whackamole mode
 processModeChange(WACKAMOLE_MODE);
 }
 */
}

void processModeChange(int pNewMode) {
 _mode           = pNewMode;
 _modeChangeFlag = true;
 // todo: do whatever else it takes to do a modechange.
}

// check to see if any buttons are being pushed right now
// if so, update the internal state and call the button event handler
//  does NOT update shift regs
void checkForFloorButtonPresses() {
 for(int i=1; i<=NUM_FLOOR_BUTTONS; i++) {
 if((i!=_currentFloor) && isFloorButtonJustPushed(i)) {
 // this button was just pressed, and wasn't in the pressed state before
 processFloorButtonPushEvent(i);
 }
 }
}

// --------------------------------------------------------------------------------
//  Miscellaneous Functions
// --------------------------------------------------------------------------------

// directions are either +1(up), -1(down) or 0(stationary)
// pDirection should be either +1 or -1
// returns an integer representing the opposite direction of pDirection -1->+1, +1->-1, 0->0
int getOppositeDirection(int pDirection) {
 return pDirection*=-1;
}

void log(int pLogLevel, char* pPrefix, char* pMsg)   {  if(logPrePayload(pLogLevel, pPrefix))   Serial.println(pMsg);  }
void log(int pLogLevel, char* pPrefix, int pMsg)     {  log(pLogLevel, pPrefix, pMsg, DEC);  }
void log(int pLogLevel, char* pPrefix, int pMsg, int FORMAT)     {  if(logPrePayload(pLogLevel, pPrefix))   Serial.println(pMsg, FORMAT);  }
void log(int pLogLevel, char* pPrefix, boolean pMsg) {  if(logPrePayload(pLogLevel, pPrefix))   {Serial.println(pMsg?"TRUE":"FALSE");  } }

// internal function - return true if logLevel says we should be logging this.
boolean logPrePayload(int pLogLevel, char* pPrefix) {
 boolean shouldLog = (pLogLevel<=LOG_LEVEL);
 if(shouldLog) {
 Serial.print(millis());
 Serial.print(" ");
 if(pPrefix) {
 Serial.print(pPrefix);
 Serial.print(" ");
 }
 }
 return shouldLog;
}

// --------------------------------------------------------------------------------
//  Shift Register Related Functions
// --------------------------------------------------------------------------------
// some code dealing with the 595 adapted from http://arduino.cc/en/Tutorial/ShftOut11
//
// ARDUINO PINS:
// -------------       +-------+
// DATAPIN(13)-------->| 595 Q0|----> * UP Led
// CLOCKPIN(12)----+-->| #1  Q1|----> * DOWN Led               ie, sending shiftOutByte a byte of 00110000
// LATCHPIN(11)-+----->|     Q2|----> * RED flashing Led           powers the RED and BLUE flashing LEDs only.
//              |  |   |     Q3|----> * BLUE flashing Led
//              |  |   |     Q4|----> X
//              |  |   |     Q5|----> X
//              |  |   |     Q6|----> X
//              |  |   |     Q7|----> X
//              |  |   +-------+
//              |  |       | (data)
//              |  |       |
//              |  |   +-------+
//              |  |   | 595 Q0|----> O Button LED floor L
//              |  +-->| #2  Q1|----> O Button LED floor 2      ie, sending shiftOutByte a byte of 00001001
//              +--|-->|     Q2|----> O Button LED floor 3          turns on floor #4 and L's LED.
//              |  |   |     Q3|----> O Button LED floor 4
//              |  |   |     Q4|----> O Button LED floor 5
//              |  |   |     Q5|----> O Button LED floor 6
//              |  |   |     Q6|----> O Button LED floor 7
//              |  |   |     Q7|----> O Button LED floor 8
//              |  |   +-------+
//              |  |       | (data)
//              |  |       |
//              |  |   +-------+     +---------------------+
//              |  |   | 595 Q0|---->|(DP) pin DP   *7seg* |    ie, sending shiftOutByte a byte of 10110110
//              |  +-->| #3  Q1|---->|(A) pin A       ___  |        displays the number '2'
//              +----->|     Q2|---->|(B) pin B      |   | |     see descr. for BINARY_NUMBER_PATTERNS above
//                     |     Q3|---->|(C) pin C      |   | |    Seven Segment display layout:
//                     |     Q4|---->|(D) pin D       ---  |         -A-
//                     |     Q5|---->|(E) pin E      |   | |        F   B
//                     |     Q6|---->|(F) pin F      |   | |         -G-
//                     |     Q7|---->|(G) pin G       ---  |        E   C
//                     +-------+     +---------------------+         -D-
//
// So, blasting out a full set of data to the entire sequence of 595's would involve sending the 7seg's byte first,
//     then the button LED array's byte, and finally the up/down/flashing LEDs byte

// Based on the internal state of a pile of variables, construct the 3 bytes that we send
//  to the shift registers.
// Based on the assumption that the 595 shift registers are
//  hooked up as depicted above, it assumes that:
//  * the first byte we shift is for the seven segment display (hooked
//     up last in the chain)
//  * the second byte is for the button LEDs
//  * the third, and final byte is for the remaing LEDs
void updateShiftRegisters() {
 byte bytes[3] = {0,0,0};
 // 1) Seven Segment display
 //    easy, this is calculated for us elsewhere
 bytes[0] = _currentSevenSegByte;
 // 2) Button LEDs
 //    Use _prevFloorButtonValues array, which should now be set for what we want to happen
 //    Q7 maps to the most significant (left most) bit, down to Q0 mapped to the LSB (right most)
 for(int i=0; i<8; i++) {
 // bytes is initialized to 0, so we can ignore anybutton that's off
 if(_prevFloorButtonValues[i]!=0) {
 // ie, if i=2 and _prevFloorButtonValues[i]=1, we will or together bytes[1] and 00000100
 //       that will turn on floor 3's button led.
 byte currentMask = 1 << i;
 bytes[1] = bytes[1] | currentMask;
 }
 }
 // 3) other LEDs

 if(_currentUpArrowLedVal==HIGH)   bytes[2] = bytes[2] | (1 << UPARROW_SHIFT);
 if(_currentDownArrowLedVal==HIGH) bytes[2] = bytes[2] | (1 << DOWNARROW_SHIFT);
 if(_currentRedFlashLedVal==HIGH)  bytes[2] = bytes[2] | (1 << REDFLASHLED_SHIFT);
 if(_currentBlueFlashLedVal==HIGH)  bytes[2] = bytes[2] | (1 << BLUEFLASHLED_SHIFT);

 log(DEBUG, "updateShiftRegisters ", "-----------------------");
 log(DEBUG, "  byte 0 (7seg)   : ", (int)bytes[0], BIN);
 log(DEBUG, "  byte 1 (fl btns): ", (int)bytes[1], BIN);
 log(DEBUG, "  byte 2 (LEDs)   : ", (int)bytes[2], BIN);
 log(DEBUG, "/updateShiftRegisters ", "----------------------");
 // Now DO IT.
 shiftBytes(DATAPIN, CLOCKPIN, LATCHPIN, 3, bytes);
}

// shift out pNumBytes number of bytes to our 595s.  We're going to start with the FIRST
//  byte and move forward.
// the pLatchPin is set low before all shifting, and finally set high when shifting is done (causing the
//   shift register to affect the changes.
void shiftBytes(int pDataPin, int pClockPin, int pLatchPin, int pNumBytes, byte *pDataOut) {
 // latchPin is low while sending data to chip.
 digitalWrite(pLatchPin, 0);
 // pinMode(pClockPin, OUTPUT);
 // pinMode(pDataPin, OUTPUT);

 //clear everything out just in case to
 //prepare shift register for bit shifting
 digitalWrite(pDataPin, 0);
 digitalWrite(pClockPin, 0);

 //internal function setup
 int i=0;
 byte *current_byte;
 for(i=0; i<pNumBytes; i++) {
 current_byte = pDataOut + i;
 shiftOutByte(pDataPin, pClockPin, *current_byte);
 }
 //stop shifting
 digitalWrite(pClockPin, 0);
 // set the latch pin high to signal chip that it no longer needs to listen for information
 digitalWrite(pLatchPin, 1);
}

// shift out a single byte.  Start with the most significant bit (which maps to Q7),
//  all the way down to the least significant bit (which maps to Q0)
void shiftOutByte(int pDataPin, int pClockPin, byte pDataOut) {
 int i=0;
 int pinState;

 //for each bit in the byte pDataOut
 //NOTICE THAT WE ARE COUNTING DOWN in our for loop
 //This means that %00000001 or "1" will go through such
 //that it will be pin Q0 that lights.
 for (i=7; i>=0; i--)  {
 digitalWrite(pClockPin, 0);
 //if the value passed to pDataOut and a bitmask result
 // true then set pinState to HIGH, else LOW
 // ie, if we are at i=6 and our value is
 // %11010100 it would the code compares it to %01000000
 // and proceeds to set pinState to 1.
 if ( pDataOut & (1<<i) ) {
 pinState= HIGH;
 } else {
 pinState= LOW;
 }

 //Sets the pin to HIGH or LOW depending on pinState
 digitalWrite(pDataPin, pinState);
 //register shifts bits on upstroke of clock pin
 digitalWrite(pClockPin, 1);
 //zero the data pin after shift to prevent bleed through
 digitalWrite(pDataPin, 0);
 }
}

// --------------------------------------------------------------------------------
// TEST ROUTINES
// --------------------------------------------------------------------------------

// display 0
// when button pushed, display that button's floor num on 7seg and beep
// run this in the loop and look at the Serial output
void testSevenSegment() {
 //  LOG_LEVEL=DEBUG;
 log(INFO, "      high: ", HIGH);
 log(INFO, "      low: ", LOW);
 // display 0 and dec pt. --------------------------------------------------------
 setCurrentSevenSegmentToDecimalNumber(0);
 setCurrentSevenSegmentDecimalPoint(true);
 updateShiftRegisters();
 runBeep(FLOORBUZZLEVEL, STARTUP_BUZZ_MILLIS, STARTUP_DELAY_MILLIS);
 delay(STARTUP_DELAY_MILLIS);

 while(true) {
 updateShiftRegisters();
 checkForFloorButtonPresses();
 for(int i=0; i<NUM_FLOOR_BUTTONS; i++) {
 if(_prevFloorButtonValues[i]>0) {
 // set 7seg to that floor's button value
 setCurrentSevenSegmentToDecimalNumber(i+1);
 updateShiftRegisters();
 runBeep(FLOORBUZZLEVEL, STARTUP_BUZZ_MILLIS, STARTUP_DELAY_MILLIS);
 // test the swipe
 _currentFloor = i+1;
 processDoorOpenCloseEvent();
 // turn off that floor
 _prevFloorButtonValues[i]=0;
 updateShiftRegisters();
 }
 }
 delay(100);
 }
}

I recently analyzed one of our large Actionscript 2 apps for the possibility of converting it over to Actionscript 3. Doing this is a little bit behind the times, since AS3 came out years ago, but this app is huge, so procrastination set in. The application in question has over 100,000 lines of code, not including comments or other non compiling lines. In short, it is fairly monstrous in size for an AS2 application. The goal was to see how hard it would be to migrate it over to AS3 so that it would compile and function as expected.

For information on why you would want to move to AS3, look at summaries like this. Also, Adobe’s migration site is a good place to start.

Since this app had many output artifacts (swfs and what not), I chose one of them, changed the corresponding fla file’s publish settings to Actionscript 3 and tried to compile. This lead to a lengthy and sometimes frustrating cycle of:

  • Attempt publish, resulting in many compiler errors
  • Fix compilation errors
  • Repeat

After many cycles, I eventually wound up with no compilation errors, and discovered a new, albeit familiar cycle of:

  • Attempt execution of swf, resulting in run-time errors
  • Fix cause of run-time error
  • Repeat

After many cycles of this, I had a working swf. Along the way, I took notes as to the types of changes I was making so that I could later look at the entire code base to estimate how much change was potentially there. Ultimately, it was decided that we would likely not be putting the effort to convert the entire app at this time, but the list of changes may help others who are going through a similar migration.

This list is not complete, for sure. It’s also not guaranteed that I didn’t mess up in taking my notes, since the whole process was fairly frustrating. Your mileage may vary and I offer no warranty of any kind :)

Basic Stuff

These changes applied to all classes that existed in the app.

  • Add package declarations, and make sure classes are public. For example:
            package foo.bar {
              import ...;
              public class Photny extends Blork {
                ...
              }
            }
  • Make sure methods and properties called by external package classes are public
  • private variables in parent classes accessed in children must be marked protected in order to be accessed in the child class.
  • Use the override attribute when overriding a parent class method:
    	override function showMessage() { ... }
  • Include import statements where missing (AS3 requires and will complain when it’s not present), for example:
    • MovieClip: import flash.display.MovieClip;
    • TextFormat: import flash.text.TextFormat;
  • Change mx.controls.* to fl.controls.*
  • Replace Void declarations with void:
    	function set doSomething():Void { ... }

    becomes

    	function set doSomething():void { ... }
  • Functions that have parameters but are called without them, for instance:
        	private function processMode(pCleanup:Boolean) { ... }
            //  and is called like this:
           	processMode();

    This is no longer allowed. Use function defaults in AS3:

        	private function processMode(pCleanup:Boolean=true) { ... }
            //  now you can call it like this:
           	processMode();
  • Default values for objects have changed
    • Number: default values for Number class is now NaN, so beware of this:
                var num:Number;
                if((num==undefined) || (num==null)) { ... }// this will not be true
    • Boolean: default value is false, (not null)
    • int, uint: default is 0
  • Look for use of arguments.length or arguments[] – use the ...rest construct instead

Forms

This app happened to make limited use of Flash MX forms (mx.screens.Form class). Forms support does not seem to be carried over into Actionscript 3 at all, so the use of these had to be refactored out entirely. I simulated forms, placing various input elements (TextInputs, Buttons, etc) into separate layers and then grouping them together so I could make them visible and invisible in the same way that the forms were displayed.

XML

The XML class is different,now based on E4X. For example, look for stuff like:

          xml.firstChild
          // or
          if (allNodes[u].nodeName == "address") { ... }
          // or
          var outputNodes = allNodes[u].childNodes;

and get rid of it in favor of E4X constructs, which allow you to access things directly, like:

          xml..address[0].street_address[0]
          ...
          myFunc( xml..address[0].street_address[0].@type );
          ...
          for each(var address:XML in xml..address) {
              var childNodes:XMLList = address.children();
              ...
          }

MovieClip differences

  • Replace calls to MovieClip properties, for instance:
    Old New
    _visible visible
    _x x
    _y y
    _width width
    _height height
  • The MovieClip.createEmptyMovieClip() is no longer used, given the new DisplayList architecture, so code like this:
            mc.createEmptyMovieClip("myChildMc", 1000);

    Has got to go in favor of something like this:

            var myChildMc:MovieClip = new MovieClip();
            addChild(myMc); // assuming you're a DisplayObject

    Further, if you go and address your child MC with code like this:

            mc.myChildMc

    You’ll have to refactor that too. There is no association outside of what the display list does (via addchild()). As a real hack, since MovieClip is a dynamic AS3 class, you could manually set the child to the parent by doing this after you create the child:

            mc.myChildMc = myChildMc;

    And then it should work. Another alternative is to add myChildMc with a name:

            myChildMc.name="child-mc";
            mc.addChild(myChildMc)

    Then get it later this way:

            // less efficient than mc.getChildAt(int) but it works.
            (mc.getChildByName("child-mc") as MovieClip)
  • Calls that set the depth to the highest possible, like this:
            stage.createEmptyMovieClip("upperMC", 1000);

    Cannot be done with AS3. You will need to make sure that the added DisplayObject has the highest possible index value via DisplayObjectContainer.setChildIndex(). So in other words, either add the thing you want on top last, or change it after you’ve added everything so that it has the highest index value.

  • Drawing related methods have been moved off of the MovieClip class:
    • MovieClip.clear() is no longer available, replace with MovieClip.graphics.clear()
    • MovieClip.lineTo(0,0) is no longer available, replace with MovieClip.graphics.lineTo(0, 0)
    • MovieClip.endFill() is no longer available, replace with MovieClip.graphics.endFill()
    • MovieClip.lineStyle(2,lnColor) is no longer available, replace with MovieClip.graphics.lineStyle(2,lnColor)
    • MovieClip.beginFill(color, fillAlpha) is no longer available, replace with MovieClip.graphics.beginFill(color, fillAlpha)
    • MovieClip.moveTo(50,25) is no longer available, replace with MovieClip.graphics.moveTo(50,25)
  • MovieClipLoader() calls are no longer allowed, so replace this type of stuff:
               var listenerObj = {};
               listenerObj.homeref = this;
               listenerObj.onLoadInit = Delegate.create(this, mcLoaded);
               listenerObj.onLoadError = Delegate.create(this, mcNotLoaded);
    	   loader.addListener(listenerObj);
               loader.loadClip(theUrl, _mainMC);

    with something like this:

               _loader = new Loader();
               _loader.contentLoaderInfo.addEventListener(Event.INIT, onMcLoaded);
               _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onMcIOError);
               _loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, onMcStatus);
               // start a timer, if it triggers, we have our timeout condition
               _mcTimer = new Timer(20000, 1); // 20 seconds
               _mcTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onMcTimeout);
               _mcTimer.start();
               trace("about to load mc (" + theUrl + ")");
               _loader.load(new URLRequest(theUrl));
               ...
               // BEGIN: mc loading handlers
               private function onMcLoaded(pEvent:Event):void { /* turn off timer */ }
               private function onMcIOError(pEvent:IOErrorEvent):void { /* turn off timer */ }
               private function onMcStatus(pEvent:HTTPStatusEvent) { /* turn off timer, make sure we got 200 status */ }
               private function onMcTimeout(pEvent:TimerEvent):void { }
               // END: mc loading handlers
  • _root no longer exists the way it did in AS2. This is a topic unto itself. If you absolutely need to have something like the root MC, you could keep a reference to it in some type of global object and set it yourself.

Various Component differences

  • You can no longer create TextFields from MovieClip objects. So instead of stuff like this:
                mc.createTextField("myHeader",mc.getNextHighestDepth(), 60, 35, 200, 30);

    You should be doing something like this:

            var myHeader:TextField = new TextField();
            myHeader.x=60;
            myHeader.y=35;
            myHeader.width=200;
            myHeader.height=30;
            parentMC.addChild(myHeader);
  • Replace calls to TextField.setNewTextFormat(myFmt) with TextField.defaultTextFormat = myFmt
  • Replace calls to
            mc.createClassObject(CheckBox,"myCheck",mc.getNextHighestDepth(), {label:"Enable Logging"});

    with something like this:

            var myCheck:CheckBox = new CheckBox();
            myCheck.label = "Enable Logging";
            mc.addChild(myCheck);
  • Setting the targets to UIScrollBar objects has changed. Change this:
            myScroll.setScrollTarget(mc.someTextArea);

    with something like this:

            myScroll.scrollTarget = someTextArea;
  • TextField (vertical) scroll positions used to be controlled like this:
            _errorMesgBox.scroll = 0;

    but is now this:

            _errorMesgBox.scrollV = 0;
  • Setting html text on mx.controls.Labels is different, so change:
            label.html = true;
            label.text = "hello world";

    to this:

            label.htmlText = "hello world";

Nickels and Dimes

There are alot of smaller changes that were not very frequent, but nonetheless were allowed by AS2 but not AS3.

  • Remove nulls in function definitions. Change this:
    		someHandler.onFocusOut = function(null, mc) { ... }

    to this:

                    someHandler.onFocusOut = function(pUnused:Object, mc) { ... }
  • Operators ‘and’, ‘or’ are no longer valid. Replace with ‘&&’, ‘||’. Change this:
            if ( foo == null or foo == 0) { ... }

    to this:

            if ( foo == null || foo == 0 ) { ... }
  • The CSSStyleDeclartion don’t exist in AS3. Use the flash.text.StyleSheet class instead.
  • Delegates must be removed, so replace and refactor calls like this:
            myCheck.addEventListener("click", Delegate.create(this, toggleStuff));

    into something like this:

           myCheck.addEventListener(MouseEvent.CLICK, toggleStuff);

    then alter

            function toggleStuff() { ... }

    to:

            function toggleStuff(pEvent:MouseEvent) { ... }

    Be sure to examine the inside of the toggleStuff() function and make sure that we use pEvent.target when we need it, etc.

  • References to _global.whatever are no longer allowed. One solution is to use a homebrewed standin Global class, then, something like:
            _global.styles.helloStyle.styleName = "helloStyle";

    becomes:

           Global.global.styles.helloStyle = new mx.styles.CSSStyleDeclaration();
  • The setInterval() is different. What was previously something like this:
            _myTimer = setInterval(this, "checkStuff", 100);

    turns into this:

            import flash.utils.*;
            ...
            _myTimer = setInterval(checkStuff, 100);
  • There is no longer a Color class that can change the color of a MovieClip, so this type of thing cannot be done any longer:
    	var colr = new Color(mc);
    	colr.setRGB(0x000000);

    use this instead:

            import flash.geom.ColorTransform;
            ...
            var nuColor:ColorTransform = new ColorTransform();
            nuColor.color = 0x000000;
            mc.transform.colorTransform = nuColor;
  • Calls to System.capabilities.os were moved, depending on what you’re looking for, replace with:
             import flash.system.Capabilities;
             Capabilities.os...
  • Include import for setting system’s clipboard
              System.setClipboard(myTextArea.text);

    to:

              import flash.system.System;
              ...
              System.setClipboard(myTextArea.text);
  • Calls to fscommand(string, bool) require import, so replace with:
            import flash.system.fscommand;
            fscommand("foo", "true");
  • This now requires import:
            System.security.allowDomain(_theUrl);

    so include this:

            import flash.system.Security;
             ...
            Security.allowDomain(_theUrl);
  • the Key class is no longer available, so this:
    	var keyListener:Object = new Object();
    	keyListener.homeref    = this;
    	keyListener.onKeyDown = function() {
    		if (Key.isDown(Key.CONTROL) && Key.isDown(Key.SHIFT) &&(Key.getCode() == 54) ) {
    			trace("you pushed ctrl-shft-6" );
    			this.homeref.onDKeyPress();
    		}
    	};
    	Key.addListener(keyListener);

    Should be rewritten like this:

            import flash.ui.Keyboard;
            ...
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            ...
            private function onKeyDown(pEvent:KeyboardEvent):void {
              if(pEvent.controlKey && pEvent.shiftKey && Keyboard.NUMBER_6) {
              	trace("you pushed ctrl-shft-6" );
    		onDKeyPress();
              }
            }

And the list, I’m sure, goes well beyond this.  Good luck if you consider migrating such an app. You should carefully weigh the benefit of doing so. If you do, just remember that the code you wind up with will likely be far from ‘best practices’ AS3, but if successful you’ll wind up with a faster app due to the more efficient AS3 virtual machine, among other potential benefits.

I’m not a fan of conflicts, especially source control merge conflicts. Recently for a project, I had to merge some code into our main trunk in svn from a maintenance branch and ran into bunch of problems due to merge conflicts. Since it wasn’t my code that conflicted and there were alot of conflicts over many, long lines, looking at svn’s diff output made me a bit confused and dizzy. The time had come to start using a more graphical merge tool with svn.

I looked around and found several merge tools. Some were shareware, some free. I settled on trying Diffmerge: it was free, available on mac (and windows and linux) and seemed pretty feature rich.

I’ve always found merging a bit confusing, and to make matters worse, it’s not altogether straightforward as to how one gets SVN to use external merge tools. There’s some info in the manual, notably here and here

First, download diffmerge. Once you’ve downloaded the diskimage file, open it and you’ll see a DiffMerge.app directory. Drop that in your /Applications folder.

Next, you’ll need a little wrapper shell script to invoke DiffMerge from svn. I’m using this, and placing it in my home directory: ~/scripts/diffmerge/diffmerge-svnmerge.sh:

#!/bin/bash
DIFFMERGE_PATH=/Applications/DiffMerge/DiffMerge.app
DIFFMERGE_EXEC=${DIFFMERGE_PATH}/Contents/MacOS/DiffMerge
# svn will invoke this with a bunch of arguments.  These are:
# $1 - path to the file that is the original
# $2 - path to the file that's the incoming merge version
# $3 - path to the file that's the latest from trunk (current working copy)
# $4 - path to where svn expects the merged output to be written
${DIFFMERGE_EXEC} --nosplash -m -t1="Incoming Merge Changes"  -t2="Original (merged)" -t3="Current Working Copy changes" -r="$4" "$2" "$1" "$3"

As noted in the comments, svn will call this wrapper script with arguments. These are briefly described in a paragraph in here.

Now, we need to get subversion to call this script at the right time. To do so, we can either set the SVN_MERGE environment variable to point to the path to the script or edit our subversion config file. I’ll choose the latter, so open ~/.subversion/config and add a line under the [helpers] section like this:

merge-tool-cmd = <HOME>/scripts/diffmerge/diffmerge-svnmerge.sh

Make sure your path is correct, and you should probably make it absolute.  Also make sure your script has its executable script set by running:

chmod +x ~/scripts/diffmerge/diffmerge-svnmerge.sh

Now, we’re all set. Let’s run through a bit of a contrived example of how this might be used. I will use example addresses and locations for the svn urls.

Let’s assume we have an svn repository at a url like this : https://svn.example.org/svn/my-example-repo/trunk/test

We will add a new file to the test directory called test.txt with the following contents:

apple pie
orange sherbet
pear sorbet
banana pudding

We’ll add and commit this. Now let’s create a branch to work on this privately with the following command:

svn copy https://svn.example.org/svn/my-example-repo/trunk/test https://svn.example.org/svn/my-example-repo/branches/my_private_test_branch

We will now checkout and work on the branch, editing the branch’s test.txt in the branch so that it looks like this:

peaches and cream   #branch
apple pie
cherry              #branch
orange sherbet
kiwi freeze         #branch
pear sorbet
banana pudding

We’ve clearly marked the line changes with a #branch ‘comment’. Then, we commit this to the branch.

Now let’s say someone else has been changing the trunk version of the file, like so:

peach pie              #trunk
apple pie
orange sherbet
blueberry pancakes     #trunk
pear sorbet
strawberries and cream #trunk
banana pudding

they committed their changes to trunk. For clarity, I’ve marked the changed lines with a #trunk comment

Merge Conflict Time

It’s time to move the changes from the branch to the trunk. We go into our local working copy of the trunk, make sure we have no local changes and we’re up to date with what’s in trunk. From the root of our local copy, we run a merge, resulting in:

mymac:test user$ svn merge https://svn.example.org/svn/my-example-repo/branches/my_private_test_branch
Conflict discovered in 'test.txt'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

If we select diff-full, we get this:

Select: (p) postpone, (df) diff-full, (e) edit,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options: df
--- /var/folders/IL/ILlIoHEjED4wET70+ahtVE+++TM/-Tmp-/tempfile.3.tmp	Wed Oct  7 13:31:33 2009
+++ .svn/tmp/test.txt.tmp	Wed Oct  7 13:31:33 2009
@@ -1,4 +1,16 @@
+<<<<<<< .working +peach pie              #trunk +======= +peaches and cream   #branch +>>>>>>> .merge-right.r10787
 apple pie
+cherry              #branch
 orange sherbet
+<<<<<<< .working +blueberry pancakes     #trunk +======= +kiwi freeze         #branch +>>>>>>> .merge-right.r10787
 pear sorbet
+strawberries and cream #trunk
 banana pudding
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

Which, even in our simple but contrived example, is a bit confusing and definetely an eyesore. It’s time to use our custom external merge software.

Use option l, which, due to our config file changes, will launch DiffMerge ('l' is not seen as an option, but it is one. Use 's' to see it listed). DiffMerge should launch and you should see something like this:

what was passed to diffmerge were three files:

  • The Original file from which both versions were derived from (ie, the file as it stood when the svn copy was made)
  • The file from the branch (as it is now).  This is the Incoming Merge changes.
  • The working copy, which is basically the latest copy from the trunk

Since svn could not automagically merge it, we’re passing it to DiffMerge. The report mentions that DiffMerge has automatically merged 2 changes, but 2 others could not be done automatically. Those are left for us to merge with the help of the DiffMerge display. Click OK and we get a screen that looks like the following:

The center column is marked ‘Original (merged)’. This is the base/original file from which the branch version and Current working copy (trunk) version are derived. However, since DiffMerge automatically made two merges on it’s own, it’s no longer exactly like the original. You can see that DiffMerge merged in the ‘cherry’ line from the branch copy and the ‘strawberries and cream’ from the working trunk copy.

However, since both the branch and the working copy have incoming changes to the same line, that is the line that we need to merge manually. There are two cases of this (2 conflicts):

For the conflict in the middle of the file (‘kiwi freeze’ vs. ‘blueberry pancakes’), let’s say we want both the changes from the right and the changes from the left to be in the merged (middle) version. So in this case, click on the line ‘kiwi freeze’, then either right click and select ‘insert this’ or press the ‘apply change from right’ button. That gets us this:

We also want the change from the right, so right click on the ‘blueberry pancakes’ line and select ‘Prepend this’.

Selecting that will prepend the ‘blueberry pancakes’ line to what we just moved. Now it looks like this:

That takes care of the first conflict, let’s move on to the other one (the one in the first line). You can see that line 1 in both the right and left have changes, and could thus not be automatically merged. (Notice how DiffMerge tells you what parts of the line match and what parts don’t by the colors, which is very helpful for long lines of configuration options where only one character or word changed). Let’s take the one from the right, so click on ‘peach pie’ and then hit the ‘apply change from right’ button. You’ll get this:

However, let’s change it in the final merge, from ‘peach pie’ to ‘peach cobbler’. To do so, click in the middle column, right after peach pie. you’ll get a text cursor. Delete ‘pie’ and make it ‘cobbler’. You now see this:

Perfect, now the middle column represents exactly how we want this merged.

Just to point out a feature, if we hit the ‘Reference View’ button in the bottom center, we get to see all three files as they were passed into DiffMerge from svn. It looks like this:

That can be a handy view, since it shows the state of the files prior to any automatic or manually done merge editing occured.

Clicking back to the Edit View, we’re now ready to save this (when we save, it saves what is in the middle column). Save it using the save button or File | Save. Quit out of DiffMerge and we’re back at the command prompt with another message like this:

Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

We know that we just manually merged this file, and since we saved it, we should be good to go, select 'r' for resolved and we get:

Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options: r
--- Merging r10785 through r10787 into '.':
U    test.txt
 U   .
mymac:test user$

The file as it now stands in the working copy is:

mymac:test user$ cat test.txt
peach cobbler              #trunk
apple pie
cherry              #branch
orange sherbet
blueberry pancakes     #trunk
kiwi freeze         #branch
pear sorbet
strawberries and cream #trunk
banana pudding
mymac:test user$

That’s exactly what we wanted, also indicated by the diff:

mymac:test user$ svn diff test.txt
Index: test.txt
===================================================================
--- test.txt	(revision 10787)
+++ test.txt	(working copy)
@@ -1,7 +1,9 @@
-peach pie              #trunk
+peach cobbler              #trunk
 apple pie
+cherry              #branch
 orange sherbet
 blueberry pancakes     #trunk
+kiwi freeze         #branch
 pear sorbet
 strawberries and cream #trunk
 banana pudding
mymac:test user$

We check this in and we’re done.

Today, I learned something new about bash scripting – the power of brace expansion.  Brace expansion in bash is a way to quickly generate variations of strings used for command line arguments.  I won’t explain this since  this Linux Journal article and the Bash Reference Manual already do an excellent job of it.

First, let me describe my problem.  Some years ago, during a construction project, I set up a cheap web cam in a shed at the site and wrote a quick script that grabbed an image every minute and stored it.  I did this so that I could later create a stop motion video animation of the whole project.  If you’re interested, I posted the videos I made to Flickr.  If you want to look at the one with the most action, just look at this video.

For the duration of the project (about 5 weeks) I wound up with over 60,000 images, all consistently named with timestamps and in directories by day.  I browsed through the thumbnails of each day, and wrote a small text file containing the date and time ranges I was interested in for the video (about 12,000 images).  So I wound up with a manually created text file that had lines that looked like this:

8/25 - 8:56 -> 4:49 - smoke, jackhammer

I planned on using Quicktime Pro to create the stop motion video, by using its feature to open an ‘Image Sequence’.  An image sequence is a directory full of images that are named in a sequence (like picture1.jpg, picture2.jpg, picture3.jpg, etc).  Using ‘Open Image Sequence’, select picture1.jpg and QTPro will find all the sibling images in that directory and create a stop action movie for you in the order the files are named.

This is great, but I had 60,000 files to sort through.  I needed a way to copy (and rename) the images of the time ranges I wanted and put them into a directory for quicktimepro to use.  I decided to specify my time ranges using bash brace expression arguments.  So the images, which were consistently named like this:

YEAR-MONTH-DAY/cam_YEAR-MONTH-DAY_HOUR_MIN_SEC.jpg
for example: 2004-08-25/cam_2004-08-25_13_59_00.jpg

I then changed my text file with human readable date ranges, from above, into something that had lines like this:

2004-08-25/cam_2004-08-25_{08_5{6..9},09,{10..15},16_0,16_{10..49}}*.jpg #8/25-8:56->4:49-smoke,jackhammer

I just needed to write a script that kept a counter, processed each line of my date range file by cp’g each matching file into a new directory and renaming that file based on a counter.  Sounds easy.  First though, I had to learn something about the brace expansion.

Brace expansion only happens once, right after the command line is tokenized.  So this works:

$ echo foo{1,2,3}
foo1 foo2 foo3

That’s great, but this does not work:

$ myline=foo{1,2,3}
$ echo $myline
foo{1,2,3}

This does not work since the brace expansion (foo{1,2,3} -> foo1 foo2 foo3) happens prior to the shell parameter expansion ($myline -> foo{1,2,3}).  To examplify the order, try the reverse experiment.  It should work out the same way:

$ one=1
$ two=2
$ echo values_{$one,$two}
values_1 values_2

So it makes sense, but if we still want to force our $myline variable value through brace expansion, we’ll need to have bash evaluate it twice.  for this, we’ll need the bash builtin ‘eval’ command.  eval evaluates a command line for you, and using it with echo and backtics you can get it to double evaluate our variable, like this:

$ myline=foo{1,2,3}
$ evaluatedline=`eval echo $myline`
$ echo $evaluatedline
foo1 foo2 foo3

And there you go.  Now, using a similar mechanism, we can iterate through our file based list of brace expressions, expand each one and copy the files I want to a new location and sequenced name.  The full script to do this is here:

#!/bin/bash
OUTPUT_DIR=./quicktime/qt_input_files
OUTPUT_FILE_PREFIX=p
OUTPUT_FILE_SUFFIX=.jpg
FILE_OF_PATTERNS=file-list.txt

# make the output directory if it doesn't exist
if [ ! -e $OUTPUT_DIR ]
then
 mkdir -p $OUTPUT_DIR
fi

# clear out previous run's output
# use this construct since passing such a huge amount (12,000) of
#   files to rm (via rm *.jpg)
#    will fail otherwise (due to 'too many args' error)
ls -tr $OUTPUT_DIR | xargs -t -I{} rm -f ${OUTPUT_DIR}/{}

COUNT=1
# $FILE_OF_PATTERNS is the path to a file that contains path specifiers,
#   one per line, like this:
# 2004-10-26/cam_2004-10-26_{08,09_00}*.jpg
# each line specifies some files we want to include in the animation
# do this for each pattern spec I want to grab
for i in `egrep -o '^[^#]*'  $FILE_OF_PATTERNS`
do
 echo =========== PROCESSING: $i ================
 # force the brace expression through another evaluation, like so:
 foo=`eval echo $i`
 # for every file that matches this sub pattern, copy that image
 #   to a file named with a more simple sequence (p1.jpg, p2.jpg, ...)
 for file in `ls $foo 2> /dev/null`
 do
   cp -p ${file} ${OUTPUT_DIR}/${OUTPUT_FILE_PREFIX}${COUNT}${OUTPUT_FILE_SUFFIX}
   let COUNT=$COUNT+1
 done
done