It’s a moderately complex program with multiple tabs. I’ll include the main source.
This program runs as-is fine under 3.5.4. It’s only processing 4 that has the problem. That’s why I asked if there is an FAQ/list of differences between 4 and 3.
I ZIPped the program (sourcefile below plus three others; 14K total) here on my own website: https://www.sr-ix.com/temp/MeowWolf_soundbox_tuner2.zip
/*
manage the soundbox flock
requires a flockNest (flockBase) to be attached.
TODO:
window resize doesn't work. fix is here.
https://forum.processing.org/two/discussion/27804/event-when-i-change-size
Structure needs looking at. It works, as-is, but some concepts outmoded, eg. next-channel,
other key commands. Need 'help' list for commands. Display sucks.
Probably best to leave this as debug/tuning tool and re-think new Flock app that does
smarter things.
19 jan 2023 Serial bit rate now 115200.
13 dec 2022 Substantial rearrangement of messages.
08 dec 2022 Minor edits for changes to SolarBirds.
05 oct 2020 Processing doesn't capture window resize. Slow bird update was simply
report rate. MSGREPORTINTERVAL is unintuitive; it sets time between
individual tx, not the rate at which the bird sends (all) its data.
05 oct 2020 Cleanups. Changed protcol packet types for new paradigm, improved
local sentence printout.
03 oct 2020 After many changes to SolarBird, including changing all codes,
tuner is in line, can change any datum by letter (only). Does not
display all data; since this has no central datum repository
that would be PITA. arrow keys navigate birds.
18 sep 2020 Rearranged the furniture again eg displayed data. Finally fixed
bad-channel display.
14 sep 2020 Rewrite of Flock, removed base vs bird distinction. Removed
bad channel concept. Needs display rearranged.
02 sep 2020 Coordinated messages with SolarBird. Dropped displaying rxpower
and load. Added gain and stimulus.
28 aug 2020 Now accepts lower case commands, SolarBird updates.
25 aug 2020 Stripping Flock header address, solves some update/display issues.
24 aug 2020 For new SolarBirds, changed Flock Base Adapter to prefix radio
messages with = so that all debug etc messages can be anything NOT beginning
with # or =.
Explicitly ignore birds alleged to be @ or space. Thats a bug elsewhere.
Commented out adapter and bird init.
30 sep 2015 logging charge.
28 sep 2015 adjusted Flock settings; parameterized log bird.
16 sep 2015 added x, X commands. moved & _ to not need birds.
added min, max chan init.
15 sep 2015 added missing R command, fixed sign of battery-charge value
(delivers as unsigned). simplified base info display.
14 sep 2015 added charge (battery rate of change).
13 sep 2015 bird uptime is now in minutes. rationalized bird command set,
added command letter and radio message (X) to label, shrunk
help area. added bird datums.
12 sep 2015 added K command (keep Peep connected) and T command to set
bird dusk solar mV threshold. fixed (i think) data record age.
10 sep 2015 larger window room for edit.
07 sep 2015 changed log to log month-day-hour-minute instead of
uptime, and rate to 10 minutes. added data age.
05 sep 2015 no longer issuing base and flock commands.
03 sep 2015 the frequent sending of 0A commands ("send status") was keeping
Peep awake and draining batteries.
02 sep 2015 meow wolf boxen all conform to this code and to documentation.
01 sep 2015 tweaked to match reality; Meow Wolf boxen updated to
current code base.
25 aug 2015 added stuff for solar bird box/Stroll. fixed bugs
in base discovery: retries on sending # command,
and closing connected but unwanted Serial ports,
and also closing the connected Serial on exit.
commented out redraw() and now text appears in processing 3.
added rudimentary logging. draw() now needs cleanup.
can't seem to figure out how to handle Serial object
within a class.
12 aug 2015 processing 3 breaks too many simple things. code there
but commented out that handles window resize (surface vs frame).
but textFont courier doesn't seem to work, no labels or help
text display (wrong FG color?). 3 seems too incompatible.
07 jun 2015 window cleanup.
05 jun 2015 eep! there was an error= true in arduino that caused exit
instead of retry. for some reason first open of COM7 on laptop
fails succeeds on 2nd.
17 may 2015 using new divert format for bird messages through the adapter.
14 may 2015 minor tweaks. needs cleanup. slowed down ack req
and update stuff; removed ! from most commands.
increased radio chatter means increased collisions
and lower reliability.
03 may 2015 code for searching base adapters, not tested.
01 may 2015 some problem with statMessage in 'R' command?
26 apr 2015 minor changes after large upgrade to Flock code.
rationalizing radio commands. need to add display of
more radio stats.
add "new channel" command.
24 apr 2015 minor tweaks, working with channel mapping.
21 apr 2015 minor bugs etc in arduino commands
19 apr 2015 mostly working right after Flock v3 code. bird ID
here is numeric but a character in the birds (65=A).
12 apr 2015 now accepts WPS commands with or without spaces.
11 apr 2015 two birds now. bug fixes, adding commands. needs
real GUI interface for settings, enable, etc.
not getting settings updates often enough.
make parser work without spaces?
10 apr 2015 close to deliverable; simulated multiple birds with a
hacked bird that reports random ID 0..5. age working.
serial event updates table only, draw paints screen
handling aging and resize. needs multiple rows to deal with
large number of birds. resize not filling background
past original screen dimensions?
08 apr 2015 generalized age, and made draw() handle all bird data
display, especially seleced bird.
07 apr 2015 mostly working.
05 apr 2015 imported from superIOGargleBlaster tuner
copyright Tom Jennings
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/>.
*/
import processing.serial.*; // for talking arduino
import java.io.File; // for finding files in folders
// base and bird initialization command strings.
//
// Supported messages:
// @ debug
// c min channel
// d max channel
// f rx AR (deciseconds)
// g rx AT (deciseconds)
// h sk AR (milliseconds)
// h sk AT (milliseconds)
// t meta report interval
//
String baseInitString = "200f 400g 50h 700i";
// adapter information.
//
Serial Arduino;
String devName = "unknown";
int arduinoState = 0; // tracks arduino setup state
boolean arduinoBaseReady = false; // true when we receive initial "#OK"
boolean keepPeepConnected= false;
int baseChannel;
String baseBadChans;
int baseAckThresh;
int baseAckBal;
char logBird= 'Y';
boolean error = false; // true if something goes wrong during setup()
int MAXBIRDS = 64;
int numBirds = 0; // discovered dynamically
class _bird {
public int x, y; // location X, Y
public int w, h; // width and height
public int datumX, datumY; // X, Y coord where first value goes
public int status;
public int control;
public int sensor; // bird sensor data
public int stimulus; // issued stimulus
public int events; // resettable event count
public int stimCount; // issued stimulus count
public int eventSpace;
public int temp; // internal temperature
public int eventVol; // event volume
public int program; // autoplay program
public int interval; // autoplay interval, dS
public int playVol; // sound volume
public int played; // last song played, or error
public int tracks; // songs on device
public int resets; // power on/reset count
public int voltage; // battery voltage, mV
public int solar; // solar panel output, mV
public int battLowLimit; // low-battery low limit, mV
public int battHiLimit; // low-battery high limit, mV
public int LEDBr;
public int LEDOn;
public char id; // bird ID (0 means empty slot)
public int channel;
public int chanMin;
public int chanMax;
public int rxAR;
public int rxAT;
public int skAR;
public int skAT;
public int reportInterval; // radio report rate, dS
public int uptime; // device up time, min
public int folder; // mood folder
public int sensorGain;
public int sensorThresh;
public int shared1;
public int shared2;
public int shared3;
public int shared4;
public int timeStamp; // local: record millis() timestamp
};
_bird[] Bird = new _bird [MAXBIRDS]; // (only declares pointers to...)
// colors
int BLACK = 0;
int WHITE = 255;
int GRAY1 = 190; // light gray
int GRAY2 = 128; // dark gray
int GRAY3 = 90; // darker gray
int BGCOLOR = GRAY2; // generic grey background
int BirdBGCOLOR = GRAY1; // bird box data background
int BirdSELCOLOR = WHITE; // bird background when selected for editing
int BirdPTRCOLOR = BLACK; // text in bird box (black)
int TEXTCOLOR = BLACK;
int EDITBGCOLOR = GRAY1;
int EDITFGCOLOR = BLACK;
// WINDOW dimensions. width is calculated from the number
// of birds. height is fixed.
//
int MINWIDTH= 800; // X
int MINHEIGHT= 900; // Y the smallest window
int MAXWIDTH= 1024;
int window_width = MINWIDTH; // these are calc'ed in drawBirds()
int window_height = MINHEIGHT;
// BIRD rectangle, one per bird.
// the white (to grey) rectangle that per-bird data is written over.
//
int bird_data_rect_width= 70; // width of rectangle
int bird_data_rect_height= 730; // height of rectangle (about 30 per datum)
int bird_data_rect_gap= 10; // space between each
int bird_data_rect_datum_height= 20; // space between each datum
// bird data window area.
//
int left_margin= 170; // where bird data begins
int left_label= 10; // where we write labels "B autoplay" etc
int right_margin = 20; // blank space to the right of bird data
int top_margin= 120; // space above bird data (for flockNest and help)
int bottom_margin= 10; // dead space at the bottom
// where static help text goes.
//
int help_top = 30; // leave space above for flockNest info
int help_left = 20;
int help_width= 500;
int help_height = 70;
// flockNest interface info on the top line of the box.
//
int baseX= left_label;
int baseY = 20;
int baseH = 20;
int baseW= 500;
char selectedBirdID = 0; // the ID of the bird selected
int inputVal = 0; // value-editor input
boolean allBirds = false; // on input, * means "all birds"
// Setting this true will cause everything to redraw.
//
boolean windowResized = false;
// command editor; below the birds
//
int editH= 20; // editor box height (one line)
int editY= window_height - editH - 0; // up from the bottom of the window
int editX= left_label; // left bound of edit rectangle
int editW= MINWIDTH - editX; // edit box width
// the edit-message area, right above the editor. error messages, runtime
// messages, arduino commands, etc
//
int statX= left_label;
int statY= editY - 10;
int statW= editW;
int statH= editH;
// help and context-dependent hints; top of the window
//
String helpCommands =
"FLOCK OF BIRDS\n" +
" SPACE play now/clear ! stimulate all - silence all\n" +
"";
// state machine &c for the command input stuff.
int keyState = 0; // follows states command, arg, enter
int nnn = 0; // decimal arg builds here
// this is gross; need to import the SRTimer stuff.
//
int T1, T2, T3;
int reportTimer;
// sets initial window size (can be resized), inits arduino.
//
void setup() {
size (640, 480); // MUST be first in setup()
background (255);
// fixed-width font needed for the terrible help command menu.
//
textFont (createFont ("Courier", 12));
baseChannel= 0;
baseAckBal= 0;
baseAckThresh= 0;
baseBadChans= "none";
for (int i= 0; i < MAXBIRDS; i++) { // instantiate birds
Bird[i]= new _bird(); // i forget why this isn't statically done
Bird[i].id= 0; // empty slot
Bird[i].timeStamp= -1;
}
background (BGCOLOR);
surface.setSize (window_width, window_height); // was size() in 2.
surface.setResizable(true);
noStroke();
windowResized= true; // force draw() to draw first time
drawBirds();
// cannot proceed without the Arduino baseToFlock interface.
//
while (! connect ("#flockNest", 115200)) {
println ("awaiting Flock Nest adapter");
delay (2000);
}
baseMessage (baseInitString);
flockInitMessages();
}
// draw bird data, using timestamp to determine background color as data age indicator. the
// selected bird ID is highlighted white. recent updates flash white/gray/white/gray.
//
void draw() {
if (error) {
println ("EXIT");
Arduino.stop(); // disconnect serial
stop();
exit();
}
if (windowResized) {
drawBirds();
windowResized= false;
}
// periodically log bird Y's battery and solar voltages.
//
if (millis() > reportTimer) {
reportTimer= millis() + 10 * 60 * 1000;
int i= findBird (logBird);
if (i >= 0) {
String tod= nf (month(), 2) + nf (day(), 2) + nf(hour(), 2) + nf (minute(), 2);
int age= (millis() - Bird[i].timeStamp + 500) / 1000;
println ("report: bird " + logBird + " " + tod +
", battery=" + Bird[i].voltage +
", solar=" + Bird[i].solar +
", age=", age
);
appendTextToFile ("/Users/tomic/Desktop/SolarBirdLog.csv",
tod + "," +
Bird[i].voltage + "," +
Bird[i].solar + "," +
age
);
}
}
// update bird display.
//
if (millis() > T1) {
T1= millis() + 11;
// display base adapter info.
//
fill (BGCOLOR);
rect (baseX, baseY - baseH + 2, width, baseH);
String s= devName +
" channel " + Integer.toString (baseChannel) +
" ack balance " + Integer.toString (baseAckBal) + // numerator
"/" + Integer.toString (baseAckThresh) + // denominator
" bad channels /" + baseBadChans + "/";
fill (TEXTCOLOR);
text (s, baseX, baseY);
// display each bird's data.
//
for (int i= 0; i < numBirds; i++) {
if (Bird[i].id == 0) continue;
// The arduino rx code set timeStamp, which is how
// we tell here what has changed recently.
//
int age= millis() - Bird[i].timeStamp;
if (Bird[i].id == selectedBirdID) BirdBGCOLOR= WHITE;
else if (age < 30) BirdBGCOLOR= WHITE; // flash the background
else if (age < 90) BirdBGCOLOR= GRAY3;
else if (age < 120) BirdBGCOLOR= WHITE;
else if (age < 150) BirdBGCOLOR= GRAY3;
else if (age < 180) BirdBGCOLOR= WHITE;
else BirdBGCOLOR= GRAY3;
plot (i);
}
}
// if enabled this pings the currently selected bird with an A (send settings)
// message. this keeps Peep birds connected so that we can configure them,
// but also drains the battery.
//
if (keepPeepConnected && (millis() > T3)) {
T3= millis() + 1109; // often enough
if (selectedBirdID != '\0') {
println ("ping bird " + selectedBirdID);
birdMessage (selectedBirdID, 0, "A");
}
}
}
// poorly named, this actually sets the bird X,Y location data within each
// bird slot for the plot() function, depending on the number of birds
// and screen dimensions.
//
void drawBirds () {
// calc how much width is needed. there's a minimum size independent of the
// number of birds, and if too many, we need to make more rows.
//
if (window_width < MINWIDTH) {
window_width= left_margin + numBirds * (bird_data_rect_width + bird_data_rect_gap) + right_margin;
}
background (BGCOLOR);
// what is this.
//
//surface.setSize (window_width, window_height);
//frame.setSize (window_width, window_height);
//redraw();
// this gets overwritten when the first bird shows up.
//
fill (TEXTCOLOR);
text ("wait...", left_margin, top_margin + bird_data_rect_datum_height);
// calculate the display position of each bird.
//
for (int i= 0; i < MAXBIRDS; i++) {
Bird[i].x= left_margin + ((bird_data_rect_width + bird_data_rect_gap) * i);
Bird[i].y= top_margin; // FIXME plus row...
Bird[i].h= bird_data_rect_height; //
Bird[i].w= bird_data_rect_width;
Bird[i].datumX= Bird[i].x + 5; // datum in box, small left margin
Bird[i].datumY= Bird[i].y + bird_data_rect_datum_height; // datum in box, bottom of text
}
// the text labels go in the left margin, at the same Y (row) as its datum.
//
fill (TEXTCOLOR);
int r= 1;
text ("BIRD ", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("status A", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("sense ev count I", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("stim ev count J", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("stim ev space K", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("temperature L", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("resets R", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("battery, mV S", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("solar, mV T", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("bat lo lim, mV U", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("bat hi lim, mV V", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("uptime X", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("LED br 0..255 Y", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("LED on 0..60 Z", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("play vol 0..255 E", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("ev vol 0..255 F", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("program G", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("interval H", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("last played O", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("tracks P", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("folder Q", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("channel b", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("chan min c", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("chan max d", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("rx request e", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("rx timeout f", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("seek request g", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("seek timeout h", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("update rate sec k", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("sensor gain q", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("sensor thresh r", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("shared1 m", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("shared2 n", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("shared3 o", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("shared4 p", left_label, top_margin + bird_data_rect_datum_height * r++);
text ("data age ", left_label, top_margin + bird_data_rect_datum_height * r++);
help (helpCommands);
}
// display the data for bird in slot i.
//
void plot (int i) {
fill (BirdBGCOLOR);
rect (Bird[i].x, Bird[i].y, Bird[i].w, Bird[i].h); // draw/erase blank Bird box
fill (BirdPTRCOLOR);
int r= 0;
text (String.format ("%c", Bird[i].id), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].status), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].events), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].stimCount), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].eventSpace), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].temp), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].resets), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].voltage), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].solar), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].battLowLimit), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].battHiLimit), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].uptime), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].LEDBr), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].LEDOn), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].playVol), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].eventVol), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].program), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].interval), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].played), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].tracks), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].folder), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].channel), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].chanMin), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].chanMax), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].rxAR), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].rxAT), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].skAR), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].skAT), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].reportInterval), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].sensorGain), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].sensorThresh), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].shared1), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].shared2), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].shared3), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
text (Integer.toString (Bird[i].shared4), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
int age= millis() - Bird[i].timeStamp;
text (mSToTime (age), Bird[i].datumX, Bird[i].datumY + r++ * bird_data_rect_datum_height);
// println ("bird " + i + " battV " + Bird[i].voltage);
}
String mSToTime (int mS) {
return secToTime ((mS + 500) / 1000);
}
String secToTime (int secs) {
String s;
int mins= secs / 60;
int hrs= (mins > 59 ? mins / 60 : 0);
mins %= 60;
secs %= 60;
if (hrs > 0) s= String.format ("%02d:%02d:%02d", hrs, mins, secs);
else s= String.format ("%02d:%02d", mins, secs);
return s;
}
// given a bird ID number, find it in the structure; if it doesn't exist, return -1.
// allow entering 0 to select no bird, to allow Peeps to power down.
//
int findBird (int n) {
int i;
for (i= 0; i < numBirds; i++) {
if (n == Bird[i].id) return i; // found it
}
return -1;
}
// necessary overhead: release all the resources we consumed during setup().
//
void stop() {
super.stop();
}