/* * File: traffic_signal.c, author: John Sauter, date: September 6, 2021 * Operate a simple traffic signal. */ /* * Copyright © 2021 by John Sauter * 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 . * The author's contact information is as follows: * John Sauter * System Eyes Computer Store * 20A Northwest Blvd. Ste 345 * Nashua, NH 03063-4066 * telephone: (603) 424-1188 * e-mail: John_Sauter@systemeyescomputerstore.com */ /* * Project 5 - Controlling Traffic - in the second edition of the Arduino * Workshop book by John Boxall from No Starch Press, presents an algorithm * for controlling traffic on a one-way bridge that is, in my opinion, * too simple. Here is a better one. * * To run this software on your Arduino, find the directory in which * the Arduino IDE stores your projects. Create a new directory named * traffic_signal. Copy this file, traffic_signal.ino, into that * directory. When you run the Arduino IDE you should have a new project * named traffic_signal. You can compile that project and upload it * to your Arduino. Build your traffic lights as described for * project 5 and you can watch the lights change in response to the * buttons. When the program starts up it turns both lights red for * 50 seconds to be sure the bridge is clear, so have some patience. * * * The towns of East Hamlet and West Hamlet are separated by a river. The * river flows from North to South, fed by snow melt in the mountains to * the North. If Winter has seen heavy snowfall, in Spring the river * floods, overflowing its usual banks. This means that the bridge * between the towns must be quite long, and for that reason it is built * as a single-lane bridge. * * The bridge is in three sections. The longest is a straight stretch * of 2000 feet spanning the river, posted at 55 miles per hour. * At either end is a curve joining the main span to the local roadway, * posted at 35 miles per hour. Vehicles coming off the bridge are * directed to the right lane of the approach road, and there is a * stop light for vehicles entering the bridge. Each stop light * has a vehicle detector buried in the roadway 60 feet long * whose front end is at the stop line. The approach to the bridge is * slightly down hill, but the last 100 feet are level. * * The bridge normally sees little traffic, but occasionally somebody * throws a party, and everyone from the other town attends, all * trying to arrive just as the party starts. When the party ends * all those attendees return to their homes, across the river. * * A few times a year, parties are thrown in both towns on the same day. * Half the people in each town attend the party in the other town, and * return home when the party is over. This is the biggest challenge * for the stoplight, since it has lots of traffic from both directions * at the same time. To handle this situation efficiently we need a * modern stoplight algorithm. * * A good reference for stoplight algorithms is the Signal Timing Manual, * second edition (2015), National Cooperative Highway Research Program * (NCHRP) report 812, ISBN 978-0-309-30888-5. I have based my algorithm * on its recommendations, though greatly simplified because the bridge * has only two stoplights. * * Because the algorithm will be programmed into an Arduino I will make it * event-driven. An event is the detection of a vehicle or the completion * of a timer. A timer has three states: stopped, running and completed. * While a timer is running it is counted down by the Arduino's clock, * and when it reaches 0 it is completed. * * Each stoplight has its own set of timers and its color, either dark, * red, yellow or green. There is also one timer that is global to both * lights: the clearance timer. * * To operate efficiently when traffic is sparse we use a technique * known as "red rest". When the bridge is clear and there are no * vehicles waiting at either side, we set both ights red. When a * vehicle approaches from either side it gets an immediate green * since it doesn't have to wait for a yellow at the far side. * * To handle the case of heavy traffic from one direction and light * traffic from the other, when the light is green we watch * for a break in the traffic, and end the green light early if there is * a vehicle waiting on the other side. If there is no traffic at all * in one direction, the heavy traffic in the other direction * never sees a red light. * * When the Arduino starts up it knows nothing about what vehicles might be * on the bridge, or which way they are going. It sets both lights to red * and starts the clearance timer. When the clearance timer completes * the Arduino knows that the bridge is clear. */ /* We wish to use a computer running GNU/Linux to debug this software, * but also run it on the Arduino. Define Arduino 1 to compile for * the Arduino; define Arduino 0 to compile for GNU/Linux. */ #ifndef Arduino #define Arduino 1 #endif /* We may wish to print a description of what the computer is doing. * This is valuable during debugging, and is the only output available * on GNU/Linux, since it doesn't have the Arduino's lights. * Define logging 1 to include logging; define logging 0 to omit * logging. * * On the Arduino logging is done to the native serial port. * On GNU/Linux, logging is done to standard output. * * By detault, we do logging on GNU/Linux but not on the Arduino. */ #ifndef logging #if Arduino #define logging 0 #else #define logging 1 #endif #endif /* We may wish to simulate vehicles. GNU/Linux has no buttons, so only * the vehicle simulator can provide traffic. On the Arduino the vehicle * simulator can provide a repeatable test of the algorithm. * Define vehicle_simulation 1 to enable vehicle simulation; * define vehicle_simulation 0 to disable vehicle simulation. * * By default, we simulate vehicles on GNU/Linux but not on the Arduino. */ #ifndef vehicle_simulation #if Arduino #define vehicle_simulation 0 #else #define vehicle_simulation 1 #endif #endif /* On the Arduino we can use interrupts to sense the buttons. * This avoids the chance of missing a button press because the * computer is busy, which is likely when the computer is logging. * Define use_interrupts 1 to use interrupts; define use_interrupts 0 * to not use interrupts. By default, we use interrupts if we are * logging on the Arduino. */ #ifndef use_interrupts #if Arduino #if logging #define use_interrupts 1 #else #define use_interrupts 0 #endif #else #define use_interrupts 0 #endif #endif /* Define the pins that the buttons and lights are connected to. * If we are logging we cannot use digital pins 0 and 1, since * they are the native serial port. If we are using interrupts, * the buttons must be on digital pins 2 and 3. Thus, if we are * logging or using interrupts move the west lights pins from 0-2 * to 4-6, and move the buttons from 3 and 13 to 2 and 3. */ #if logging | use_interrupts #define westButton 2 #define eastButton 3 #define westRed 6 #define westYellow 5 #define westGreen 4 #else #define westButton 3 #define westRed 2 #define westYellow 1 #define westGreen 0 #define eastButton 13 #endif #define eastRed 12 #define eastYellow 11 #define eastGreen 10 #if Arduino #else /* Libraries we need for this program on GNU/Linux. */ #include #include #include /* getopt_long() */ #include #include #include #include #include #include #include /* Simulate the Arduino millis function on GNU/Linux. * Milis returns the number of milliseconds since the computer * was switched on. The value is an unsigned 32-bit integer. */ static unsigned long int millis() { struct timespec ts_m; const clockid_t id_m = CLOCK_MONOTONIC; double the_time; unsigned long int return_value; clock_gettime(id_m, &ts_m); the_time = (double)ts_m.tv_sec + (double)(ts_m.tv_nsec / 1e9); the_time = the_time * 1000.0; return_value = the_time; return (return_value); } #endif /* We have some parameters based on the layout of the bridge. * Times are in units of tenths of a second. */ /* The time needed for a vehicle to cross the bridge. * The bridge is 2000 feet long and posted at 55 miles per hour, * but in the worst case the last vehicles on the bridge might be * moving only 35 miles per hour, since that is the posted * limit on the bridge exit and the road beyond. Therefore * we should allow 40 seconds to cross the main span of the * bridge, plus 5 for the entrance and 5 for the exit, for a total * of 50 seconds. */ #define clearance_time 500 /* How long to show a yellow light before it turns red. */ #define yellow_time 34 /* The minimum time to show green, to get the traffic moving. */ #define minimum_green_time 100 /* The maximum time to let traffic flow before letting it flow * in the other direction. */ #define maximum_green_time 600 /* If there is this much time since the last car passed, we are * experiencing a gap in the traffic. If there are vehicles waiting * on the other side, this would be a good time to switch directions. */ #define passage_time 14 /* The maximum time a vehicle should have to wait. Waiting longer * than this means there is an error. The worst case for wait time * assumes you stopped just as the light turned yellow, and there * is lots of traffic waiting to cross from the other side. You * wait 3.4 seconds for the light to turn red, 50 seconds for the * bridge to clear, 60 seconds of green for the opposing traffic, * 3.4 seconds for their yellow, and 50 seconds for the bridge to * clear again. 3.4+50+60+3.4+50 = 166.8 secibds, */ #define max_wait_time 1670 /* When we are flashing yellow, this is the length of time the light * is yellow. After being yellow it is dark for this same length of time. */ #define flashing_yellow_time 30 /* A light is represented by a structure containing * its state and timers. Before we define that structure * we must define a timer. However, when a timer completes * it calls a subroutine which takes the light as an argument, * so we need a partial definition of a light before we can * define a timer. */ struct light; typedef void (*completion_subroutine)(struct light *the_light); /* A timer knows its current state and what to do when it completes. * If its state is running, it also has a counter. */ enum timer_state { stopped, running, completed }; struct timer { const char *name; /* The counter's name, for logging */ enum timer_state state; /* stopped, running or completed */ int counter; /* how long until completion */ completion_subroutine completion; /* what to do when the timer completes */ }; /* Now we can complete the definition of a light. * It knows whether it is dark or lit, and if it is lit * it knows its color. It has five timers which control * the sequencing of colors. It also has a pointer to * the other light so the lights can coordinate with each other. */ enum light_color { dark, red, yellow, green }; struct light { const char *name; /* The light's name, for logging */ enum light_color color; /* dark, red, yellow or green */ int red_pin; /* Which digital pin turns on the red LED */ int yellow_pin; /* Which the yellow LED */ int green_pin; /* and which the green LED */ struct timer waiting_for_green; /* the light waiting longer gets service */ struct timer yellow_interval; /* showing yellow */ struct timer minimum_green; /* don't make green too short */ struct timer maximum_green; /* don't make opposing traffic wait too long*/ struct timer passage; /* watch for gaps in the traffic */ struct light *other_light; /* where to find the opposing light */ unsigned int last_vehicle_detection_time; /* avoid double detection */ }; /* These are the names of the subroutines which run when a timer completes. * We need to know their names so we can initialize the timers, but we * don't need to know what they do yet. */ static void waiting_for_green_completed(struct light *the_light); static void yellow_interval_completed(struct light *the_light); static void minimum_green_completed(struct light *the_light); static void maximum_green_completed(struct light *the_light); static void passage_completed(struct light *the_light); static void clearance_timer_completed(struct light *the_light); /* The east light and the west light are instances of structure light. * When the computer starts, it has no idea where the vehicles are. * Assume the worst case, which is that a vehicle has just started * to cross the bridge. We don't know which way it is going, so * stop both directions until the bridge is clear, */ struct west_light; static struct light east_light = { "east", red, eastRed, eastYellow, eastGreen, { "waiting for green", stopped, 0, waiting_for_green_completed }, { "yellow", stopped, 0, yellow_interval_completed }, { "minimum green", stopped, 0, minimum_green_completed }, { "maximum green", stopped, 0, maximum_green_completed }, { "passage", stopped, 0, passage_completed }, NULL, 0 }; static struct light west_light = { "west", red, westRed, westYellow, westGreen, { "waiting for green", stopped, 0, waiting_for_green_completed }, { "yellow", stopped, 0, yellow_interval_completed }, { "minimum green", stopped, 0, minimum_green_completed }, { "maximum green", stopped, 0, maximum_green_completed }, { "passage", stopped, 0, passage_completed }, NULL, 0 }; /* There is an additional timer, not associated with any light, * which keeps track of the last vehicle's passage across the bridge. * We start with that timer running. When it completes we will know * that the bridge is clear. */ static struct timer clearance = { "clearance", running, clearance_time, clearance_timer_completed }; /* The fairness flag lets us alternate between sides when there are * simultaneous calls for service. */ static int fairness = 0; /* We remember the value of the millisecond timer from the last time * we updated our timers, from when we started, and from the beginning * of the current loop. That lets us update our timers 10 times * per second and do logging based on when the program started. */ static unsigned long int start_time, previous_timer_update_time, current_time; /* Mode lets us switch to flashing yellow when there is a problem * or when the traffic is handled manully. */ static enum { normal, flashing_yellow } mode = normal; #if Arduino | vehicle_simulation /* We number the vehicles for logging. */ static unsigned int vehicle_count = 0; #endif #if vehicle_simulation /* The vehicle simulator needs to know when it was last called * so it doesn't run the random number generator so frequently * that it always thinks it sees a vehicle. */ static unsigned long int vehicle_simulator_last_call; #endif #if Arduino & logging /* If we are logging on the ARduino, remember if we were able to * open the serial port. */ static int serial_port_is_available = 0; #endif /* Interrupts must be used carefully. When in an interrupt we do not * dare to modify, or even examine, data structures used by the non-interrupt * code, since it may be in the middle of being modified when the interrupt * fired. Therefore, the interrupt code just sets a flag which can be * examined and reset by the non-interrupt code. We declare the flag * variables volatile to tell the compiler that their values can be * changed by conditions not visible to the compiler. We declare them * as ints in the expectation that testing and clearing them by * the non-interrupt code will be done with single instructions * and therefore not be interrupted half way through the modification. */ volatile static int west_detector_changed = 0; volatile static int east_detector_changed = 0; /* We store the values of the detectors in these variables: */ static int west_detector_value, east_detector_value; /* These log functions allow us to write meaningful messages as the * algorithm operates. */ /* Send a string to the log file. */ static void log_string([[maybe_unused]] const char *the_string) { #if logging #if Arduino if (serial_port_is_available == 1) { Serial.print(the_string); } #else printf("%s", the_string); #endif #endif return; } /* Send an unsigned number to the log file. */ static void log_number([[maybe_unused]] unsigned int the_number) { #if logging #if Arduino if (serial_port_is_available == 1) { Serial.print(the_number); } #else printf("%d", the_number); #endif #endif return; } /* Send an unsigned number to the log file, padded with "0" to three digits. */ static void log_number_3(unsigned long int the_number) { if (the_number < 100) { log_string("0"); } if (the_number < 10) { log_string("0"); } log_number(the_number); return; } /* Send an unsigned number to the log file, padded with "0" to five digits. */ static void log_number_5(unsigned long int the_number) { if (the_number < 10000) { log_string("0"); } if (the_number < 1000) { log_string("0"); } log_number_3(the_number); return; } /* Send a time in milliseconds to the log file. */ static void log_time(unsigned long int the_time) { /* Show the seconds, a decimal point, and the milliseconds. */ log_number_5(the_time / 1000); log_string("."); log_number_3(the_time % 1000); return; } /* Log the color of a light. */ static void log_light_color(struct light *the_light) { switch (the_light->color) { case dark: log_string("dark "); break; case red: log_string("red "); break; case yellow: log_string("yellow"); break; case green: log_string("green "); break; } return; } /* The end of a log record. */ static void log_end_line() { #if logging #if Arduino if (serial_port_is_available == 1) { Serial.println(""); } #else log_string("\n"); #endif #endif return; } /* Start a new record in the log file. */ static void log_start_line(struct light *the_light) { /* Put a standard prefix on each line. Begin with the time since the program started. */ log_time(current_time - start_time); log_string(" "); /* Show the color of each light. */ log_light_color(&west_light); log_string(" "); log_light_color(&east_light); log_string(" "); /* SHow the status of the vehicle detectors. */ if ((west_detector_value != 0) || (west_detector_changed != 0)) { log_string ("H"); } else { log_string ("L"); } if ((east_detector_value != 0) || (east_detector_changed != 0)) { log_string ("H "); } else { log_string ("L "); } /* Log which side of the bridge is creating the record, if either. */ if (the_light == NULL) { /* No side information provided. */ log_string(" "); } else { log_string(the_light->name); } log_string(" : "); return; } /* Subroutine to set a traffic light to a particular color. */ static void set_color(struct light *the_light, enum light_color the_color) { the_light->color = the_color; #if Arduino log_start_line (the_light); log_string ("set light color "); /* Log the numbers of the digital pins we use, in case the * Arduino is not wired correctly. */ switch (the_color) { case dark: digitalWrite(the_light->red_pin, LOW); log_number (the_light->red_pin); log_string (": LOW, "); digitalWrite(the_light->yellow_pin, LOW); log_number (the_light->yellow_pin); log_string (": LOW, "); digitalWrite(the_light->green_pin, LOW); log_number (the_light->green_pin); log_string (": LOW"); break; case red: digitalWrite(the_light->red_pin, HIGH); log_number (the_light->red_pin); log_string (": HIGH, "); digitalWrite(the_light->yellow_pin, LOW); log_number (the_light->yellow_pin); log_string (": LOW, "); digitalWrite(the_light->green_pin, LOW); log_number (the_light->green_pin); log_string (": LOW"); break; case yellow: digitalWrite(the_light->red_pin, LOW); log_number (the_light->red_pin); log_string (": LOW, "); digitalWrite(the_light->yellow_pin, HIGH); log_number (the_light->yellow_pin); log_string (": HIGH, "); digitalWrite(the_light->green_pin, LOW); log_number (the_light->green_pin); log_string (": LOW"); break; case green: digitalWrite(the_light->red_pin, LOW); log_number (the_light->red_pin); log_string (": LOW, "); digitalWrite(the_light->yellow_pin, LOW); log_number (the_light->yellow_pin); log_string (": LOW, "); digitalWrite(the_light->green_pin, HIGH); log_number (the_light->green_pin); log_string (": HIGH"); break; } log_end_line(); #endif return; } /* Subroutine to stop a timer. */ static void stop_timer(struct timer *the_timer) { the_timer->state = stopped; return; } /* Subroutine to start a timer. */ static void start_timer(struct timer *the_timer, int the_time, struct light *the_light) { the_timer->counter = the_time; /* The counters are updated every 100 milliseconds based on the * Arduino clock. If the current time is not on a 100-milliseconds * boundary, add 100 milliseconds to the time so the timer does * not expire early. */ if (((current_time - start_time) % 100) != 0) { the_timer->counter = the_timer->counter + 1; } the_timer->state = running; log_start_line(the_light); log_string("start timer "); log_string(the_timer->name); log_string(" at "); log_number(the_timer->counter); log_end_line(); return; } #if use_interrupts /* These interrupt subroutines just aet a flag, which is tested and reset * in the non-interrupt code. */ static void west_detector_interrupt() { west_detector_changed = 1; } static void east_detector_interrupt() { east_detector_changed = 1; } #endif /* Do all the necessary initializatons. */ void setup() { /* Finish the initialization of the light structures. */ west_light.other_light = &east_light; east_light.other_light = &west_light; /* Remember the initial value of the millisecond clock. * We use it to count down the timers and for vehicle * simulation. */ start_time = millis(); previous_timer_update_time = start_time - 1; west_light.last_vehicle_detection_time = start_time; east_light.last_vehicle_detection_time = start_time; #if vehicle_simulation /* The vehicle simulator needs to know when it was last called. */ vehicle_simulator_last_call = start_time; #endif #if Arduino /* Set up the digital I/O pins. */ pinMode(westButton, INPUT); pinMode(eastButton, INPUT); pinMode(westRed, OUTPUT); pinMode(westYellow, OUTPUT); pinMode(westGreen, OUTPUT); pinMode(eastRed, OUTPUT); pinMode(eastYellow, OUTPUT); pinMode(eastGreen, OUTPUT); #if use_interrupts /* Attach the interrupts for the buttons to the appropriate functions. * We want an interrupt every time the button changes. */ attachInterrupt(digitalPinToInterrupt(westButton), west_detector_interrupt, CHANGE); attachInterrupt(digitalPinToInterrupt(eastButton), east_detector_interrupt, CHANGE); #endif /* If we are logging, see if the serial port is available. * Wait up to one second, if necessary, for it to become ready. */ #if logging Serial.begin(9600); for (int count = 0; count < 1000; count++) { serial_port_is_available = Serial; if (serial_port_is_available) break; delay(1); } #endif #endif /* Set the initial state for the lights: both are red. */ set_color(&west_light, red); set_color(&east_light, red); return; } /* Subroutine to turn a light red */ static void give_red(struct light *the_light) { log_start_line(the_light); log_string("turn light red"); log_end_line(); set_color(the_light, red); stop_timer(&the_light->yellow_interval); stop_timer(&the_light->passage); return; } /* Subroutine to turn a light yellow */ static void give_yellow(struct light *the_light) { log_start_line(the_light); log_string("turn light yellow"); log_end_line(); set_color(the_light, yellow); if (mode == normal) { start_timer(&the_light->yellow_interval, yellow_time, the_light); } else { start_timer(&the_light->yellow_interval, flashing_yellow_time, the_light); } stop_timer(&the_light->maximum_green); stop_timer(&the_light->minimum_green); stop_timer(&the_light->passage); stop_timer(&the_light->waiting_for_green); return; } /* Subroutine to turn a light green */ static void give_green(struct light *the_light) { log_start_line(the_light); log_string("turn light green"); log_end_line(); set_color(the_light, green); start_timer(&the_light->minimum_green, minimum_green_time, the_light); start_timer(&the_light->maximum_green, maximum_green_time, the_light); stop_timer(&the_light->waiting_for_green); stop_timer(&the_light->passage); stop_timer(&the_light->yellow_interval); /* Since we are presumably letting a vehicle through, * start the clearance timer. That will prevent * the other light from turning green until the bridge is clear. */ start_timer(&clearance, clearance_time, the_light); return; } #if Arduino | vehicle_simulation /* Subroutine to handle the detection of a vehicle */ static void vehicle_detected(struct light *the_light) { /* In normal mode we wait for green if the light is red, * or just record that a vehicle has passed if the light is green. */ if (mode == normal) { /* If a vehicle was detected recently, this is likely a repeat * detection of the same vehicle, so ignore it. We need to * worry about the time counter wrapping around, so we check * for the current time being after the last detected time. * Missing the last vehicle to run the yellow light is not an * issue because we will have seen the previous vehicle just * 100 milliseconds ahead of it. */ if ((current_time >= the_light->last_vehicle_detection_time) && (current_time < (the_light->last_vehicle_detection_time + 100))) { log_start_line (the_light); log_string ("rejecting quick vehicle detection "); log_time (the_light->last_vehicle_detection_time + 100); log_end_line (); return; } the_light->last_vehicle_detection_time = current_time; vehicle_count = vehicle_count + 1; if ((the_light->color == red) || (the_light->color == yellow)) { /* The light is red or yellow. Call for a green light since * the vehicle might have stopped on yellow. */ if ((clearance.state != running) && (the_light->other_light->color == red) && (the_light->other_light->waiting_for_green.state != running)) { /* The bridge is clear, the other light is red and has * no vehicle waiting. We can have a green without waiting, * even if we are yellow. */ log_start_line(the_light); log_string("vehicle "); log_number(vehicle_count); log_string(" arrives at a clear bridge"); log_end_line(); give_green(the_light); return; } /* Either the bridge is not clear or the other light is not red. * Tell the other light that there is a vehicle waiting here. * We will get a green when the other light is red and the bridge * is clear. */ log_start_line(the_light); log_string("vehicle "); log_number(vehicle_count); if (the_light->color == yellow) { log_string(" may have"); } log_string(" stopped"); log_end_line(); /* If the timer is already running let it continue to run. * we are measuring the wait time of the first car in line. */ if (the_light->waiting_for_green.state != running) { start_timer(&the_light->waiting_for_green, max_wait_time, the_light); } /* If the other light is looking at a gap in the traffic, * turn it yellow now rather than have it wait for maximum green. */ if ((the_light->other_light->color == green) && (the_light->other_light->minimum_green.state == completed) && (the_light->other_light->passage.state != running)) { log_start_line(the_light); log_string("vehicle "); log_number(vehicle_count); log_string(" causes early yellow"); log_end_line(); give_yellow(the_light->other_light); } } if (the_light->color == yellow) { /* The light is yellow. Assume the vehicle passed through, * so we have a vehicle that entered the bridge just now. */ log_start_line(the_light); log_string("vehicle "); log_number(vehicle_count); log_string(" may have passed through yellow"); log_end_line(); start_timer(&clearance, clearance_time, the_light); } if (the_light->color == green) { /* The light is green. Note the passage of a vehicle, * which will stretch the green, and time the * last vehicle that passed so we can know when the bridge * is empty. */ log_start_line(the_light); log_string("vehicle "); log_number(vehicle_count); log_string(" passed through green"); log_end_line(); start_timer(&the_light->passage, passage_time, the_light); start_timer(&clearance, clearance_time, the_light); } return; } /* The mode is flashing yellow. Ignore vehicles. */ the_light->last_vehicle_detection_time = current_time; return; } #endif /* Subroutine to handle the completion of the yellow interval timer. */ static void yellow_interval_completed(struct light *the_light) { log_start_line(the_light); log_string("yellow completion"); log_end_line(); /* In normal mode, red comes after yellow. */ if (mode == normal) { give_red(the_light); /* If the bridge is clear and the other light is waiting * for a green, give it. */ if ((clearance.state != running) && (the_light->other_light->waiting_for_green.state == running)) { give_green(the_light->other_light); } return; } /* The mode is flashing yellow. We use the yellow interval timer * to alternate between yellow and dark. */ if (the_light->color == yellow) { set_color(the_light, dark); } else { set_color(the_light, yellow); } start_timer(&the_light->yellow_interval, flashing_yellow_time, the_light); return; } /* Subroutine to initiate a change to red in the opposing light, * if necessary and the light has not been green only a short time. * If the light is already red our light gets an immediate green. * We know the bridge is clear and there is a vehicle waiting * on this side. */ static void get_green(struct light *the_light) { if (the_light->other_light->color == red) { /* The other light is red so we get an immediate green. */ give_green(the_light); return; } if ((the_light->other_light->color == green) && (the_light->other_light->minimum_green.state != running)) { /* The other light is green, and has been green for a while, * so change it to yellow. When the yellow interval is complete * we will get a green since we have a vehicle waiting. */ give_yellow(the_light->other_light); return; } /* If the other light is yellow we need do nothing, since the * completion of the yellow interval will give us a green light. */ return; } /* Subroutine to handle the completion of the clearance timer */ static void clearance_timer_completed(struct light *the_light) { /* It is simpler to declare the clearance timer as though it were * associated with a light, like all the other timers. Actually, when * the clearance timer completes this subroutine is called with * NULL for the light. */ unsigned long int seconds_waiting; log_start_line(the_light); log_string("clearance timer completed"); log_end_line(); if ((east_light.waiting_for_green.state != running) && (west_light.waiting_for_green.state != running)) { /* The bridge is empty and there are no vehicles at either end. * Make the lights red. When a vehicle arrives its light will * immediately turn green. */ log_start_line(the_light); log_string("the bridge is clear and no vehicles are waiting"); log_end_line(); if (east_light.color == green) { give_yellow(&east_light); } if (west_light.color == green) { give_yellow(&west_light); } return; } if ((east_light.waiting_for_green.state == stopped) && (west_light.waiting_for_green.state != stopped)) { /* There is a vehicle on the west side. */ log_start_line(NULL); log_string("there is a vehicle waiting on the west side"); log_end_line(); /* Move the light towards green. */ get_green(&west_light); return; } if ((west_light.waiting_for_green.state == stopped) && (east_light.waiting_for_green.state != stopped)) { /* There is a vehicle on the east side. */ log_start_line(the_light); log_string("there is a vehicle waiting on the east side"); log_end_line(); /* Move the light towards green. */ get_green(&east_light); return; } /* Vehicles are waiting on both sides. Move towards green the side * that has been waiting longer. */ log_start_line(the_light); log_string("there are vehicles waiting on both sides"); log_end_line(); log_start_line(&west_light); seconds_waiting = max_wait_time; seconds_waiting = seconds_waiting - west_light.waiting_for_green.counter; seconds_waiting = seconds_waiting * 100; log_time(seconds_waiting); log_string(" seconds waiting"); log_end_line(); log_start_line(&east_light); seconds_waiting = max_wait_time; seconds_waiting = seconds_waiting - east_light.waiting_for_green.counter; seconds_waiting = seconds_waiting * 100; log_time(seconds_waiting); log_string(" seconds waiting"); log_end_line(); if (west_light.waiting_for_green.counter < east_light.waiting_for_green.counter) { get_green(&west_light); return; } if (east_light.waiting_for_green.counter < west_light.waiting_for_green.counter) { get_green(&east_light); return; } /* Vehicles have been waiting for the same amount of time on each side. * Alternate who gets to pass first. */ if (fairness == 0) { fairness = 1; get_green(&east_light); return; } fairness = 0; get_green(&west_light); return; } /* Subroutine to handle the minimum green timer completing */ static void minimum_green_completed(struct light *the_light) { /* With the completion of the minimum green timer we are allowed * to turn red. Don't turn unless there is a vehicle waiting * on the other side and there has been a gap in the traffic from * this side. */ log_start_line(the_light); log_string("minimum green timer completion"); log_end_line(); if ((the_light->other_light->waiting_for_green.state == running) && (the_light->passage.state != running)) { give_yellow(the_light); } return; } /* Subroutine to handle the completion of the passage timer. */ static void passage_completed(struct light *the_light) { /* Completion of the passage timer means there is a gap in the traffic. * If we have been green for at least the minimum time, and the other * light is waiting for a green light, we can turn red and give the * bridge to him. */ log_start_line(the_light); log_string("passage timer completion"); log_end_line(); if ((the_light->other_light->waiting_for_green.state == running) && (the_light->minimum_green.state != running)) { give_yellow(the_light); } return; } /* Subroutine to handle the completion of the maximum green timer */ static void maximum_green_completed(struct light *the_light) { /* At the completion of maximum green we stop caring about gaps in the * traffic; we will turn red as soon as there is a vehicle on the other * side that is waiting for a green light. */ log_start_line(the_light); log_string("maximum green timer completion"); log_end_line(); if (the_light->other_light->waiting_for_green.state == running) { give_yellow(the_light); return; } /* However, if the bridge is empty we turn red early, to save the * yellow time if the next vehicle is on the other side of the bridge. */ if (clearance.state != running) { give_yellow(the_light); return; } return; } /* Subroutine to set us into flashing yellow mode. */ static void set_flashing_yellow(struct light *the_light) { mode = flashing_yellow; log_start_line(the_light); log_string("mode is flashing yellow"); if (clearance.state == running) { log_string("; clearance timer = "); log_number(clearance.counter); } log_end_line(); give_yellow(the_light); give_yellow(the_light->other_light); set_color(the_light, dark); return; } /* Subroutine to handle the completion of the waiting for green timer. * If it completes something is seriously wrong with the algorithm, * since the algorithm should give a green light to a waiting vehicle * quickly enough that the timer never completes. */ static void waiting_for_green_completed(struct light *the_light) { log_start_line(the_light); log_string("waiting for green completion"); log_end_line(); /* To indicate an error, set both lights flashing yellow. */ set_flashing_yellow(the_light); return; } /* Subroutine to update a timer */ static void update_timer(struct timer *the_timer, struct light *the_light) { /* If the timer is not running, do nothing. */ if (the_timer->state != running) return; /* Count down the timer and if it reaches 0 call its completion subroutine. */ the_timer->counter = the_timer->counter - 1; if (the_timer->counter == 0) { the_timer->state = completed; the_timer->completion(the_light); } else { if ((the_timer->counter % 100) == 0) { log_start_line(the_light); log_string("timer "); log_string(the_timer->name); log_string(" counter = "); log_number(the_timer->counter); log_end_line(); } } return; } /* Subroutine to update a light's timers. * Each light has five timers, as listed below. */ static void update_light_timers(struct light *the_light) { update_timer(&the_light->minimum_green, the_light); update_timer(&the_light->maximum_green, the_light); update_timer(&the_light->waiting_for_green, the_light); update_timer(&the_light->passage, the_light); update_timer(&the_light->yellow_interval, the_light); return; } /* Subroutine to update the timers. It is called every 100 * milliseconds. Update each light's timers and the global timer. */ static void update_timers() { update_light_timers(&east_light); update_light_timers(&west_light); update_timer(&clearance, NULL); } #if vehicle_simulation /* The vehicle simulator. Since the roads leading to the bridge * are posted at 35 miles per hour, we can get a miximum of three * vehicles per second. We start with light traffic and build * up to that limit. There is some randomness in the interval * between vehicles. */ static void vehicle_simulator(struct light *the_light) { unsigned long int random_value, random_scale; /* The maximum arrival rate of vehicles at a light is 3 per second. * Deal with wraparound of the clock by also allowing a vehicle * when the clock wraps. */ if ((current_time < the_light->last_vehicle_detection_time) || (current_time >= (the_light->last_vehicle_detection_time + 333))) { /* Add some randomness to the detection of a vehicle. */ /* The random function is only required to produce values between 0 * and 32767, so take the low-order 15 bits of its value. */ random_value = random() & 077777;; random_scale = (current_time - start_time) / 100000; if (random_value < random_scale) { /* Simulate the detection of a vehicle at this light. */ log_start_line(the_light); log_string("vehicle simulated: "); log_number(random_value); log_string (", "); log_number (random_scale); log_end_line(); vehicle_detected(the_light); } } } #endif /* The loop runs over and over again, updating the timers and * testing for the presence of a vehicle at either light. */ void loop() { current_time = millis(); /* Check for the lights being in incompatible states. */ if ((mode == normal) && (east_light.color != red) && (west_light.color != red)) { log_start_line(NULL); log_string("lights are in a dangerous state"); log_end_line(); set_flashing_yellow(&west_light); } #if Arduino /* If a button has changed recently then either a vehicle has * entered the detector or a vehicle has left the detector. * In either case a vehicle has been detected. * If a detector has not changed recently but is showing the * presence of a vehicle, a vehicle has been detected. * * If we are not using interrupts the button_changed values * will always be 0. However, we always read the value of the * button, so we may miss some changes but if we are not logging * we will miss only very quick changes from low to high and back * to low again. */ west_detector_value = digitalRead(westButton); east_detector_value = digitalRead(eastButton); if (west_detector_changed || (west_detector_value == HIGH)) { west_detector_changed = 0; vehicle_detected(&west_light); } if (east_detector_changed || (east_detector_value == HIGH)) { east_detector_changed = 0; vehicle_detected(&east_light); } #endif #if vehicle_simulation /* In the absence of buttons we can simulate vehicles. */ /* If the clock has not ticked, do nothing. */ if (current_time != vehicle_simulator_last_call) { vehicle_simulator_last_call = current_time; vehicle_simulator(&east_light); vehicle_simulator(&west_light); } #endif /* Update the timers. The timers tick every 100 milliseconds. * It is unlikely that the running time of the loop would ever * exceed 100 milliseconds, but in case it does do multiple updates * of the timers, if necessary, to catch them up to the current time. */ while ((unsigned long)(current_time - previous_timer_update_time) > 100) { update_timers(); previous_timer_update_time = previous_timer_update_time + 100; } return; } #if Arduino #else /* This program is intended to run on the Arduino, but to run it * under GNU/Linux, copy file traffic_signal.ino to a fresh directory, * rename it to traffic_signal.cpp, edit it to set Arduino to 0 * at the top of the file and compile it using the following Make file: * * # File: Makefile, author: John Sauter, date: July 4, 2021. * # Commands to compile and link the traffic_signal program * * SHELL = /bin/sh * .SUFFIXES: * * traffic_signal : traffic_signal.o * gcc $< -o $@ * * traffic_signal.o : traffic_signal.cpp * g++ -o 2 -Wall -g -c $< -o $@ * * # End of file Makefile * * With the above Makefile in place, the make command will compile * and link traffic_signal. When you run the program it will use * the vehicle simulator to create traffic and show you wnat it is * doing on standard output. */ /* Command-line processing, which is not needed on the Arduino. */ /* Print a helpful message. */ static void usage(FILE *fp, int argc, char **argv) { if (argc >= 1) { fprintf(fp, "Usage: %s [options]\n\n" "Operate a simple traffic signal.\n" " Version 1.5 2021-09-06\n" "Options:\n" "-h | --help Print this message\n" "-t | --time Number of seconds to run\n" "", argv[0]); } } /* Options */ static const char short_options[] = "ht:"; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "time", required_argument, NULL, 't' }, { 0, 0, 0, 0 } }; /* main program: parse options, run program and exit. */ int main(int argc, char **argv) { int time_span = 0; /* Scan the options on the command line. */ for (;;) { int index; int c; c = getopt_long(argc, argv, short_options, long_options, &index); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'h': usage(stdout, argc, argv); exit(EXIT_SUCCESS); case 't': time_span = atoi(optarg); break; default: usage(stderr, argc, argv); exit(EXIT_FAILURE); } } /* Do the initialization. This sets start_time. */ setup(); /* Perform the processing until time is up, or indefinitely if no * time limit was specified. */ while (1) { /* Run the real-time logic. This sets current_time. */ loop(); /* If the specified time is up, we are done. Otherwise, * continue looping. */ if ((time_span > 0) && (current_time > (start_time + (time_span*1000)))) break; } exit(EXIT_SUCCESS); } #endif