Skip to content

Commit 5d59017

Browse files
RIC-1139: New sample TrafficLightWeb
1 parent eaa0d46 commit 5d59017

20 files changed

Lines changed: 10983 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Represents the pedestrian light that can show either
2+
// the Stop or Go symbols.
3+
capsule PedLight {
4+
service behavior port pedControl : PedLightControl;
5+
service behavior notify port server~ : PedLightControl;
6+
7+
statemachine {
8+
state DoNotWalk {
9+
entry
10+
`
11+
server.stop().send();
12+
`;
13+
};
14+
state Walk {
15+
entry
16+
`
17+
server.walk().send();
18+
`;
19+
20+
countdown: on pedControl.timeRemaining
21+
`
22+
int time = * static_cast<const int*>(rtdata);
23+
server.timeRemaining(time).send();
24+
`;
25+
};
26+
state WaitForUIReady;
27+
initial -> WaitForUIReady;
28+
WaitForUIReady -> DoNotWalk on server.rtBound;
29+
walk: DoNotWalk -> Walk on pedControl.walk;
30+
stop: Walk -> DoNotWalk on pedControl.stop;
31+
};
32+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This protocol is used both for internal communication between the
2+
// traffic light and the pedestrian light, but also for the communication
3+
// between the pedestrian light and the web server.
4+
protocol PedLightControl {
5+
in stop();
6+
in walk();
7+
8+
// Notify about how many seconds remain until the light goes from Walk to Stop
9+
in timeRemaining(`int`);
10+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# traffic-light-web
2+
<img src="webapp/public/images/screenshot.png" width="100" height="100">
3+
4+
This sample implements a traffic light system consisting of two lights (one for traffic and one for pedestrians) and a push button (for pedestrians to push when they want to safely cross the street). The realtime application uses the [TCPServer library](../TcpServer/README.md) in order to communicate with a Node.js web application. Information about current states of the lights are updated on the web page in real time, and pushing the button on the web page will inject the request from the pedestrian to cross the street.
5+
6+
To be able to intercept the outgoing messages about light changes, the Node.js application embeds a simple TCP server. Hence, this sample implements bi-directional communication using two separate TCP servers (one in the Node.js application and one in the realtime C++ application).
7+
8+
## Starting the web server
9+
`cd webapp`
10+
11+
`npm install`
12+
13+
`node app.js`
14+
15+
Open [http://localhost:4000/](http://localhost:4000/) in a web browser
16+
17+
## Launch the realtime application
18+
`TLSystem.exe -URTS_DEBUG=quit -port=<P> -remotePort=<RP> -remoteHost="<RH>"`
19+
20+
All arguments are optional and have default values:
21+
* port: 9911 (TCP port used by realtime app)
22+
* remotePort: 2234 (TCP port used by web app)
23+
* remoteHost: `127.0.0.1` (IP address or hostname where the web app runs)
24+
25+
## How the application works
26+
When the application starts the top capsule `TLSystem` incarnates the optional `server` part with a `WebServer` capsule instance. This capsule inherits from the `TCPServer` capsule which is responsible for the communication with the Node.js application. The `WebServer` runs in its own thread to avoid that the TCP communication affects the performance of the rest of the application.
27+
28+
The other capsules `TrafficLight` and `PedLight` represent the two lights and have state machines that implement the switching of the lights based on timers. The `TrafficLight` controls the `PedLight` so that pedestrians can safely cross the street when they request to do so. Note that both light capsule state machines have to wait initially for the `WebServer` to become available, and they do this by means of notification ports.
29+
30+
The Node.js application makes a "pedestrian" request when the button is pushed on the web page. From the realtime application's point of view this becomes an incoming TCP request which is mapped to the injection of the `pedestrian` event on the `trafficLight` port of `WebServer`. In the other direction, outgoing events about light changes become TCP requests which the TCP server in the Node.js application will handle. It then uses web sockets for updating the web time accordingly.
31+
32+
The application also supports a `pedestrianCustomTime` event which has a parameter the specifies how many seconds the pedestrians will get for crossing the street. There is no user interface on the web page for sending this event, but you can use the Art Debugger for injecting it. This emulates a situation where a new feature has been implemented in a realtime application for which no user interface (or hardware) is yet available.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Top capsule representing a traffic light system that consists of a 3-color light
2+
// and a pedestrian button. The traffic light circles red->green->yellow->red->... until
3+
// the pedestrian button is pressed. Then the traffic light turns red for some time
4+
// so the pedestrian can cross the street. After that it resumes circling through the colors.
5+
// A user interface, implemented as a Node.js web application, shows the current color of the traffic light and provides
6+
// the pedestrian button to push.
7+
8+
capsule TLSystem {
9+
[[rt::header_preface]]
10+
`
11+
#include <string>
12+
`
13+
[[rt::decl]]
14+
`
15+
private:
16+
unsigned int port = 0; // TCP server port of realtime app
17+
unsigned int remotePort = 0; // TCP server port of Node.js app
18+
std::string remoteHost = ""; // TCP server host of Node.js app
19+
void parseCommandLine();
20+
`
21+
22+
[[rt::impl]]
23+
`
24+
void TLSystem::parseCommandLine() {
25+
int ac = RTMain::argCount();
26+
const char * const * av = RTMain::argStrings();
27+
std::string arg_remotePort = "-remotePort=", arg_remoteHost = "-remoteHost=", arg_port = "-port=";
28+
29+
while( --ac > 0 ) {
30+
std::string arg = *++av;
31+
if (arg.compare(0, arg_remotePort.size(), arg_remotePort) == 0 )
32+
remotePort = RTMemoryUtil::atoi(*av + arg_remotePort.size());
33+
else if (arg.compare(0, arg_remoteHost.size(), arg_remoteHost) == 0 ) {
34+
remoteHost = arg.substr(arg_remoteHost.size());
35+
}
36+
else if (arg.compare(0, arg_port.size(), arg_port) == 0 )
37+
port = RTMemoryUtil::atoi(*av + arg_port.size());
38+
}
39+
}
40+
`
41+
42+
behavior port frame : Frame;
43+
44+
part trafficLight : TrafficLight;
45+
optional part server [[rt::properties(rule_config="X0039")]] : WebServer; // Intentionally unconnected
46+
part pedLight : PedLight;
47+
48+
connect server.trafficLight with trafficLight.server;
49+
connect pedLight.pedControl with trafficLight.pedLightControl;
50+
connect pedLight.server with server.pedLightControl;
51+
52+
statemachine {
53+
state State;
54+
initial -> State
55+
`
56+
parseCommandLine();
57+
58+
RTActorId id = frame.incarnateCustom(server,
59+
RTActorFactory([this](RTController * c, RTActorRef * a, int index) {
60+
return new WebServer(ServerThread, a, port, remotePort, remoteHost);
61+
}
62+
));
63+
`;
64+
};
65+
};
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// A traffic light that can show either red, green or yellow. It can accept a pedestrian request
2+
// and will then control a pedestrian light so the pedestrian can safely cross the street.
3+
capsule TrafficLight {
4+
[[rt::decl]]
5+
`
6+
private:
7+
RTTimerId cycleTimer;
8+
int pedestrianWalkTime;
9+
`
10+
11+
behavior port log : Log;
12+
behavior port timer : Timing;
13+
behavior port pedTimer : Timing;
14+
15+
service behavior notify port server : TrafficLightControl;
16+
service behavior port pedLightControl~ : PedLightControl;
17+
18+
statemachine {
19+
state WaitUntilServerReady;
20+
state CycleLight {
21+
entrypoint ep, pedReady;
22+
exitpoint pedRequest;
23+
24+
state Red[[rt::properties(color="#e60f0f")]] {
25+
entry
26+
`
27+
server.red().send();
28+
`;
29+
},
30+
Green[[rt::properties(color="#11d80e")]] {
31+
entry
32+
`
33+
server.green().send();
34+
`;
35+
},
36+
Yellow[[rt::properties(color="#edc707")]] {
37+
entry
38+
`
39+
server.yellow().send();
40+
`;
41+
};
42+
43+
ep -> Red;
44+
red_to_green: Red -> Green on timer.timeout
45+
`
46+
log.log("Red -> Green");
47+
log.commit();
48+
`
49+
;
50+
green_to_yellow: Green -> Yellow on timer.timeout
51+
`
52+
log.log("Green -> Yellow");
53+
log.commit();
54+
`
55+
;
56+
yellow_to_red: Yellow -> Red on timer.timeout
57+
`
58+
log.log("Yellow -> Red");
59+
log.commit();
60+
`
61+
;
62+
63+
pedReady -> Red;
64+
};
65+
initial -> WaitUntilServerReady;
66+
67+
WaitUntilServerReady -> CycleLight.ep on server.rtBound
68+
`
69+
log.log("TrafficLight starts up");
70+
log.commit();
71+
cycleTimer = timer.informEvery(RTTimespec(2,0));
72+
`;
73+
74+
state PedestrianCrossing {
75+
entrypoint pedRequest;
76+
exitpoint pedReady;
77+
78+
entry
79+
`
80+
int pedTimerData = -1;
81+
pedTimer.informIn(RTTimespec(2,0), &pedTimerData, &RTType_int);
82+
`;
83+
84+
state Red [[rt::properties(color="#e60f0f")]] {
85+
entry
86+
`
87+
log.log("Enter Red");
88+
log.commit();
89+
server.red().send();
90+
`;
91+
};
92+
93+
pedRequest -> Red;
94+
95+
updatePedLight: on pedTimer.timeout
96+
`
97+
int pedTimerData = * static_cast<const int*>(rtdata);
98+
99+
if (pedTimerData == -1) {
100+
pedLightControl.walk().send();
101+
pedTimerData = pedestrianWalkTime; // Count down number of seconds until we switch from Walk to Stop
102+
pedLightControl.timeRemaining(pedTimerData).send();
103+
pedTimer.informIn(RTTimespec(1,0), &pedTimerData, &RTType_int);
104+
// Time for pedestrians to cross
105+
}
106+
else if (pedTimerData == 0) {
107+
pedLightControl.stop().send();
108+
timer.informIn(RTTimespec(2,0));
109+
}
110+
else {
111+
pedTimerData--;
112+
pedLightControl.timeRemaining(pedTimerData).send();
113+
pedTimer.informIn(RTTimespec(1,0), &pedTimerData, &RTType_int);
114+
}
115+
`;
116+
117+
pedRequest_ignored: on server.pedestrian, server.pedestrianCustomTime
118+
`
119+
// Ignore additional requests from pedestrians since we already are in the PedestriansCrossing state.
120+
`;
121+
};
122+
123+
junction j;
124+
125+
pedRequest : CycleLight.pedRequest -> j on server.pedestrian
126+
`
127+
// Use a default walk time for the pedestrian of 4 seconds
128+
pedestrianWalkTime = 4;
129+
`;
130+
131+
pedRequestCustom : CycleLight.pedRequest -> j on server.pedestrianCustomTime
132+
`
133+
pedestrianWalkTime = * static_cast<const int*>(rtdata);
134+
`;
135+
136+
j -> PedestrianCrossing.pedRequest
137+
`
138+
log.log("Pedestrian requests to cross");
139+
log.commit();
140+
timer.cancelTimer(cycleTimer);
141+
`;
142+
143+
pedReady: PedestrianCrossing.pedReady -> CycleLight.pedReady on timer.timeout
144+
`
145+
log.log("Pedestrians ready");
146+
log.commit();
147+
cycleTimer = timer.informEvery(RTTimespec(2,0));
148+
`;
149+
};
150+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
protocol TrafficLightControl {
2+
// Events to notify that the traffic light color has changed
3+
out green();
4+
out red();
5+
out yellow();
6+
7+
in pedestrian(); // Sent when a pedestrian pushes the button
8+
9+
in pedestrianCustomTime(`int`); // Request pedestrian crossing with custom time
10+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Interface to the web server
2+
capsule WebServer : TCPServer {
3+
[[rt::header_preface]]
4+
`
5+
#include <string>
6+
`
7+
[[rt::impl_preface]]
8+
`
9+
#include <iostream>
10+
`
11+
[[rt::decl]]
12+
`
13+
public:
14+
WebServer(RTController* rtg_rts, RTActorRef* rtg_ref, unsigned int port, unsigned int remotePort, std::string& remoteHost);
15+
`
16+
17+
[[rt::impl]]
18+
`
19+
WebServer::WebServer(RTController* rtg_rts, RTActorRef* rtg_ref, unsigned int port, unsigned int remotePort, std::string& remoteHost)
20+
: SUPER(rtg_rts, rtg_ref) {
21+
if (port != 0)
22+
config.port = port;
23+
if (remotePort != 0)
24+
config.remotePort = remotePort;
25+
if (!remoteHost.empty()) {
26+
config.remoteHost = remoteHost;
27+
}
28+
29+
config.logToStdOut = false;
30+
std::cout << "Server started!" << std::endl;
31+
std::cout << "port=" << config.port << " remotePort=" << config.remotePort << " remoteHost=" << config.remoteHost << "!" << std::endl;
32+
}
33+
`
34+
35+
service behavior port trafficLight~ : TrafficLightControl;
36+
service behavior port pedLightControl: PedLightControl;
37+
38+
statemachine {
39+
40+
};
41+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//Transformation Configuration File
2+
let tc = TCF.define(TCF.ART_TO_CPP);
3+
tc.targetFolder = 'TrafficLightWeb_target';
4+
tc.prerequisites = ['../TcpServer/tcpServerLib.tcjs'];
5+
6+
// Uncomment to debug
7+
/*
8+
tc.compileArguments = '$(DEBUG_TAG)';
9+
tc.linkArguments = '/DEBUG';
10+
tc.targetConfiguration = "WinT.x64-VisualC++-17.0";
11+
tc.targetRTSLocation = "C:/git/rsarte-target-rts/rsa_rt/C++/TargetRTS";
12+
*/
13+
tc.topCapsule = "TLSystem";
14+
tc.threads = [
15+
{
16+
name: 'MainThread',
17+
implClass: 'RTPeerController',
18+
stackSize: '20000',
19+
priority: 'DEFAULT_MAIN_PRIORITY'
20+
},
21+
{
22+
name: 'ServerThread',
23+
implClass: 'RTPeerController',
24+
stackSize: '20000',
25+
priority: 'DEFAULT_MAIN_PRIORITY',
26+
logical: [
27+
'ServerThread'
28+
]
29+
}
30+
];

0 commit comments

Comments
 (0)