This program connects to a few sensors via I2C and sends the data collected to a WNC Cellular Module which is located on an Avnet WNC-Shield card.

Dependencies:   FXOS8700CQ MODSERIAL mbed

/media/uploads/kevinkeryk/avnet_logo_tagline_rgb.png

Avnet Cellular IoT Instructions

  • One problematic area is setting the MY_SERVER_URL. When you copy the URL from the flow, you must make sure the MY_SERVER_URL is also set to the appropriate server. It can be either "run-east.att.io" or "run-west.att.io".

Useful Links

Adding Additional Sensors

The FLOW_DEVICE_NAME field must contain the name of the instance of the Virtual Starter Kit in FLOW you will be communicating with. Usually this is "vstarterkit001", but if you have problems communicating you can verify this is correct. Note: This device will not be created until you click the “Initialize” input on the Virtual Device tab of the Starter Kit project in FLOW. At that point, it becomes available in M2X and you can see it as the DEVICE SERIAL field under Devices as in the image below. /media/uploads/JMF/vstarterkit.png

Sensors: When executing, the FRDM-K64F board uploads sensor measurements to AT&T’s Flow environment every 5 seconds, using the Cellular shield board. You can adjust how often you want to do this by editing the SENSOR_UPDATE_INTERVAL_MS value in the header file.

Temperature and humidity: By default, the board reports readings from the HTS221 temperature and humidity sensor. These two values are sent to the HTTP IN /climate port in FLOW with field names “temp” and “humidity”. Temperature is in degrees Fahrenheit and humidity is a %. This default assignment is: iSensorsToReport = TEMP_HUMIDITY_ONLY;

Accelerometer: If you want to expand and use the onboard motion sensor, you can also send 3-axis accelerometer information from the board as “accelX”, “accelY”, and “accelZ”. This is useful if you want to know the stationary position of the board with regards to gravity, or whether it is in motion. These readings are in g’s. To send these values, change the assignment to: iSensorsToReport = TEMP_HUMIDITY_ACCELEROMETER;

PMOD Sensors: If you have a Silicon Labs sensor module that can plug into the PMOD connector on the Cellular shield, you are able to measure proximity, UV light, ambient visible and infrared light from the Si1145 sensor. This PMOD also has a temperature and humidity sensor, but in this case it is redundant. When enabled, the fields “proximity”, “light_uv”, “light_vis” and “light_ir” are also sent. To enable all these sensors, change the assignment to: iSensorsToReport = TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS;

Connecting the PMOD sensors: Because the pinouts do not align, the SiLabs PMOD sensor board cannot be plugged into the J10 PMOD receptacle on the shield directly. The following wiring instructions must be followed:

SignalJ10ShieldPMOD Color in the image below
VCCPin 6Pin 6Red
GNDPin 5Pin 5Black
SDAPin4Pin 3Green
SCLPin3Pin 2Yellow

/media/uploads/JMF/xyz.jpg

AT&T M2X and FLOW Instructions

M2X & FLOW Instructions

Link to AT&T M2X

M2X

Link to AT&T Flow

FLOW

Avnet WNC-Shield Information

Getting Started with the Avnet WNC-Shield Software

  • This project uses Revision 119 of the MBED library because of I2C implementation differences with the tip (Revision 121).
  • This project uses Revision 4 of the FXOS8700CQ library for sensors.

Easily Modifiable Parameters

Inside the mbed Avnet_ATT_Cellular_IOT project, the parameters needed to customize your board are in the config_me.h file.

  • FLOW parameters: This project assumes you are using a fork of the Starter Kit Base project, which is a reference design created using AT&T’s FLOW (https://flow.att.com) that allows the creation of online virtualization and other IoT functionality. The default parameters in the config_me.h file are done for a specific instance of this project. When you fork the original project, you get your own instance and it will have its own base address. At the bottom of the FLOW environment, when you click on the Endpoints tab, URL information that is specific to your instance is displayed. Of note is the Base URL. In the example below (as in the default mbed project), the Base URL is: https://run-west.att.io/1e464b19cdcde/774c88d68202/86694923d5bf28a/in/flow You have to take note of two parts of this address. The run-west.att.io part is the server URL, and you have to make sure the
  • MY_SERVER_URL field in config_me.h matches this. The rest of the base URL, in green above, needs to be pasted into the FLOW_BASE_URL field.

There is also a FLOW_INPUT_NAME field. This should match the name of the HTTP IN port in the FLOW project that you want to send sensor data to. The default is "/climate", as in the FLOW image below.

/media/uploads/JMF/sf.png

Where is the Binary I compiled

When the COMPILE button is pressed, it compiles your project and links it. The result is placed in the DOWNLOAD folder you use when downloading files from the Internet. It will be called AvnetATT_shape_hackathon_K64F.bin.

Additional Information on Compiling/Configuring

Comprehensive instructions can be found at: Quick Start Instructions

Committer:
stefanrousseau
Date:
Mon Aug 01 18:29:04 2016 +0000
Revision:
61:f6b93129f954
Parent:
59:7ebb9436b2bd
Child:
63:90d7c69993cd
Moved cellular modem routines from main.cpp to cell_modem.cpp; Moved HTS221 routines from main.cpp to sensors.cpp

Who changed what in which revision?

UserRevisionLine numberNew contents of line
stefanrousseau 55:3abf9e3f42e6 1 #include "mbed.h"
JMF 0:9d5134074d84 2 #include <cctype>
JMF 0:9d5134074d84 3 #include <string>
JMF 0:9d5134074d84 4 #include "SerialBuffered.h"
JMF 2:0e2ef866af95 5 #include "config_me.h"
stefanrousseau 4:f83bedd9cab4 6 #include "sensors.h"
stefanrousseau 61:f6b93129f954 7 #include "cell_modem.h"
stefanrousseau 11:e6602513730f 8 #include "hardware.h"
stefanrousseau 11:e6602513730f 9
stefanrousseau 61:f6b93129f954 10 I2C i2c(PTC11, PTC10); //SDA, SCL -- define the I2C pins being used
stefanrousseau 56:cb42ff383dab 11 MODSERIAL pc(USBTX, USBRX, 256, 256); // tx, rx with default tx, rx buffer sizes
fkellermavnet 38:564b312a719f 12 SerialBuffered mdm(PTD3, PTD2, 4096);
stefanrousseau 16:17c5916f2d12 13 DigitalOut led_green(LED_GREEN);
stefanrousseau 16:17c5916f2d12 14 DigitalOut led_red(LED_RED);
stefanrousseau 16:17c5916f2d12 15 DigitalOut led_blue(LED_BLUE);
JMF 0:9d5134074d84 16
JMF 0:9d5134074d84 17
stefanrousseau 3:26b3cc155f39 18 //********************************************************************************************************************************************
stefanrousseau 12:7c94ec5069dc 19 //* Create string with sensor readings that can be sent to flow as an HTTP get
stefanrousseau 3:26b3cc155f39 20 //********************************************************************************************************************************************
stefanrousseau 12:7c94ec5069dc 21 K64F_Sensors_t SENSOR_DATA =
stefanrousseau 3:26b3cc155f39 22 {
stefanrousseau 12:7c94ec5069dc 23 .Temperature = "0",
stefanrousseau 12:7c94ec5069dc 24 .Humidity = "0",
stefanrousseau 12:7c94ec5069dc 25 .AccelX = "0",
stefanrousseau 12:7c94ec5069dc 26 .AccelY = "0",
stefanrousseau 12:7c94ec5069dc 27 .AccelZ = "0",
stefanrousseau 12:7c94ec5069dc 28 .MagnetometerX = "0",
stefanrousseau 12:7c94ec5069dc 29 .MagnetometerY = "0",
stefanrousseau 12:7c94ec5069dc 30 .MagnetometerZ = "0",
stefanrousseau 12:7c94ec5069dc 31 .AmbientLightVis = "0",
stefanrousseau 12:7c94ec5069dc 32 .AmbientLightIr = "0",
stefanrousseau 12:7c94ec5069dc 33 .UVindex = "0",
stefanrousseau 12:7c94ec5069dc 34 .Proximity = "0",
stefanrousseau 12:7c94ec5069dc 35 .Temperature_Si7020 = "0",
stefanrousseau 55:3abf9e3f42e6 36 .Humidity_Si7020 = "0",
stefanrousseau 55:3abf9e3f42e6 37 .Virtual_Sensor1 = "0",
stefanrousseau 55:3abf9e3f42e6 38 .Virtual_Sensor2 = "0",
stefanrousseau 55:3abf9e3f42e6 39 .Virtual_Sensor3 = "0",
stefanrousseau 55:3abf9e3f42e6 40 .Virtual_Sensor4 = "0",
stefanrousseau 55:3abf9e3f42e6 41 .Virtual_Sensor5 = "0",
stefanrousseau 55:3abf9e3f42e6 42 .Virtual_Sensor6 = "0",
stefanrousseau 55:3abf9e3f42e6 43 .Virtual_Sensor7 = "0",
stefanrousseau 55:3abf9e3f42e6 44 .Virtual_Sensor8 = "0"
stefanrousseau 3:26b3cc155f39 45 };
stefanrousseau 12:7c94ec5069dc 46
stefanrousseau 3:26b3cc155f39 47 void GenerateModemString(char * modem_string)
stefanrousseau 3:26b3cc155f39 48 {
stefanrousseau 12:7c94ec5069dc 49 switch(iSensorsToReport)
stefanrousseau 12:7c94ec5069dc 50 {
stefanrousseau 12:7c94ec5069dc 51 case TEMP_HUMIDITY_ONLY:
stefanrousseau 12:7c94ec5069dc 52 {
stefanrousseau 12:7c94ec5069dc 53 sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, FLOW_URL_TYPE, MY_SERVER_URL);
stefanrousseau 12:7c94ec5069dc 54 break;
stefanrousseau 12:7c94ec5069dc 55 }
stefanrousseau 12:7c94ec5069dc 56 case TEMP_HUMIDITY_ACCELEROMETER:
stefanrousseau 12:7c94ec5069dc 57 {
stefanrousseau 12:7c94ec5069dc 58 sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, FLOW_URL_TYPE, MY_SERVER_URL);
stefanrousseau 12:7c94ec5069dc 59 break;
stefanrousseau 12:7c94ec5069dc 60 }
stefanrousseau 12:7c94ec5069dc 61 case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS:
stefanrousseau 12:7c94ec5069dc 62 {
stefanrousseau 12:7c94ec5069dc 63 sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s&proximity=%s&light_uv=%s&light_vis=%s&light_ir=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr, FLOW_URL_TYPE, MY_SERVER_URL);
stefanrousseau 12:7c94ec5069dc 64 break;
stefanrousseau 12:7c94ec5069dc 65 }
stefanrousseau 55:3abf9e3f42e6 66 case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS_VIRTUALSENSORS:
stefanrousseau 55:3abf9e3f42e6 67 {
stefanrousseau 55:3abf9e3f42e6 68 sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s&proximity=%s&light_uv=%s&light_vis=%s&light_ir=%s&virt_sens1=%s&virt_sens2=%s&virt_sens3=%s&virt_sens4=%s&virt_sens5=%s&virt_sens6=%s&virt_sens7=%s&virt_sens8=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr, SENSOR_DATA.Virtual_Sensor1, SENSOR_DATA.Virtual_Sensor2, SENSOR_DATA.Virtual_Sensor3, SENSOR_DATA.Virtual_Sensor4, SENSOR_DATA.Virtual_Sensor5, SENSOR_DATA.Virtual_Sensor6, SENSOR_DATA.Virtual_Sensor7, SENSOR_DATA.Virtual_Sensor8, FLOW_URL_TYPE, MY_SERVER_URL);
stefanrousseau 55:3abf9e3f42e6 69 break;
stefanrousseau 55:3abf9e3f42e6 70 }
stefanrousseau 12:7c94ec5069dc 71 default:
stefanrousseau 12:7c94ec5069dc 72 {
stefanrousseau 12:7c94ec5069dc 73 sprintf(modem_string, "Invalid sensor selected\r\n\r\n");
stefanrousseau 12:7c94ec5069dc 74 break;
stefanrousseau 12:7c94ec5069dc 75 }
stefanrousseau 16:17c5916f2d12 76 } //switch(iSensorsToReport)
stefanrousseau 3:26b3cc155f39 77 } //GenerateModemString
stefanrousseau 3:26b3cc155f39 78
stefanrousseau 3:26b3cc155f39 79
stefanrousseau 3:26b3cc155f39 80 //Periodic timer
stefanrousseau 3:26b3cc155f39 81 Ticker OneMsTicker;
stefanrousseau 3:26b3cc155f39 82 volatile bool bTimerExpiredFlag = false;
stefanrousseau 3:26b3cc155f39 83 int OneMsTicks = 0;
stefanrousseau 3:26b3cc155f39 84 int iTimer1Interval_ms = 1000;
stefanrousseau 3:26b3cc155f39 85 //********************************************************************************************************************************************
stefanrousseau 3:26b3cc155f39 86 //* Periodic 1ms timer tick
stefanrousseau 3:26b3cc155f39 87 //********************************************************************************************************************************************
stefanrousseau 3:26b3cc155f39 88 void OneMsFunction()
stefanrousseau 3:26b3cc155f39 89 {
stefanrousseau 3:26b3cc155f39 90 OneMsTicks++;
stefanrousseau 3:26b3cc155f39 91 if ((OneMsTicks % iTimer1Interval_ms) == 0)
stefanrousseau 3:26b3cc155f39 92 {
stefanrousseau 3:26b3cc155f39 93 bTimerExpiredFlag = true;
stefanrousseau 3:26b3cc155f39 94 }
stefanrousseau 3:26b3cc155f39 95 } //OneMsFunction()
stefanrousseau 3:26b3cc155f39 96
stefanrousseau 16:17c5916f2d12 97 //********************************************************************************************************************************************
stefanrousseau 16:17c5916f2d12 98 //* Set the RGB LED's Color
stefanrousseau 16:17c5916f2d12 99 //* LED Color 0=Off to 7=White. 3 bits represent BGR (bit0=Red, bit1=Green, bit2=Blue)
stefanrousseau 16:17c5916f2d12 100 //********************************************************************************************************************************************
stefanrousseau 16:17c5916f2d12 101 void SetLedColor(unsigned char ucColor)
stefanrousseau 16:17c5916f2d12 102 {
stefanrousseau 16:17c5916f2d12 103 //Note that when an LED is on, you write a 0 to it:
stefanrousseau 16:17c5916f2d12 104 led_red = !(ucColor & 0x1); //bit 0
stefanrousseau 16:17c5916f2d12 105 led_green = !(ucColor & 0x2); //bit 1
stefanrousseau 16:17c5916f2d12 106 led_blue = !(ucColor & 0x4); //bit 2
stefanrousseau 16:17c5916f2d12 107 } //SetLedColor()
stefanrousseau 16:17c5916f2d12 108
stefanrousseau 16:17c5916f2d12 109 //********************************************************************************************************************************************
stefanrousseau 61:f6b93129f954 110 //* Process the JSON response. In this example we are only extracting a LED color.
stefanrousseau 16:17c5916f2d12 111 //********************************************************************************************************************************************
stefanrousseau 16:17c5916f2d12 112 bool parse_JSON(char* json_string)
stefanrousseau 16:17c5916f2d12 113 {
stefanrousseau 16:17c5916f2d12 114 char* beginquote;
stefanrousseau 16:17c5916f2d12 115 char token[] = "\"LED\":\"";
stefanrousseau 16:17c5916f2d12 116 beginquote = strstr(json_string, token );
stefanrousseau 16:17c5916f2d12 117 if ((beginquote != 0))
stefanrousseau 16:17c5916f2d12 118 {
stefanrousseau 16:17c5916f2d12 119 char cLedColor = beginquote[strlen(token)];
stefanrousseau 16:17c5916f2d12 120 printf(GRN "LED Found : %c" DEF "\r\n", cLedColor);
stefanrousseau 16:17c5916f2d12 121 switch(cLedColor)
stefanrousseau 16:17c5916f2d12 122 {
stefanrousseau 16:17c5916f2d12 123 case 'O':
stefanrousseau 16:17c5916f2d12 124 { //Off
stefanrousseau 16:17c5916f2d12 125 SetLedColor(0);
stefanrousseau 16:17c5916f2d12 126 break;
stefanrousseau 16:17c5916f2d12 127 }
stefanrousseau 16:17c5916f2d12 128 case 'R':
stefanrousseau 16:17c5916f2d12 129 { //Red
stefanrousseau 16:17c5916f2d12 130 SetLedColor(1);
stefanrousseau 16:17c5916f2d12 131 break;
stefanrousseau 16:17c5916f2d12 132 }
stefanrousseau 16:17c5916f2d12 133 case 'G':
stefanrousseau 16:17c5916f2d12 134 { //Green
stefanrousseau 16:17c5916f2d12 135 SetLedColor(2);
stefanrousseau 16:17c5916f2d12 136 break;
stefanrousseau 16:17c5916f2d12 137 }
stefanrousseau 16:17c5916f2d12 138 case 'Y':
stefanrousseau 16:17c5916f2d12 139 { //Yellow
stefanrousseau 16:17c5916f2d12 140 SetLedColor(3);
stefanrousseau 16:17c5916f2d12 141 break;
stefanrousseau 16:17c5916f2d12 142 }
stefanrousseau 16:17c5916f2d12 143 case 'B':
stefanrousseau 16:17c5916f2d12 144 { //Blue
stefanrousseau 16:17c5916f2d12 145 SetLedColor(4);
stefanrousseau 16:17c5916f2d12 146 break;
stefanrousseau 16:17c5916f2d12 147 }
stefanrousseau 16:17c5916f2d12 148 case 'M':
stefanrousseau 16:17c5916f2d12 149 { //Magenta
stefanrousseau 16:17c5916f2d12 150 SetLedColor(5);
stefanrousseau 16:17c5916f2d12 151 break;
stefanrousseau 16:17c5916f2d12 152 }
stefanrousseau 16:17c5916f2d12 153 case 'T':
stefanrousseau 16:17c5916f2d12 154 { //Turquoise
stefanrousseau 16:17c5916f2d12 155 SetLedColor(6);
stefanrousseau 16:17c5916f2d12 156 break;
stefanrousseau 16:17c5916f2d12 157 }
stefanrousseau 16:17c5916f2d12 158 case 'W':
stefanrousseau 16:17c5916f2d12 159 { //White
stefanrousseau 16:17c5916f2d12 160 SetLedColor(7);
stefanrousseau 16:17c5916f2d12 161 break;
stefanrousseau 16:17c5916f2d12 162 }
stefanrousseau 16:17c5916f2d12 163 default:
stefanrousseau 16:17c5916f2d12 164 {
stefanrousseau 16:17c5916f2d12 165 break;
stefanrousseau 16:17c5916f2d12 166 }
stefanrousseau 16:17c5916f2d12 167 } //switch(cLedColor)
stefanrousseau 16:17c5916f2d12 168 return true;
stefanrousseau 16:17c5916f2d12 169 }
stefanrousseau 16:17c5916f2d12 170 else
stefanrousseau 16:17c5916f2d12 171 {
stefanrousseau 16:17c5916f2d12 172 return false;
stefanrousseau 16:17c5916f2d12 173 }
stefanrousseau 16:17c5916f2d12 174 } //parse_JSON
stefanrousseau 16:17c5916f2d12 175
JMF 0:9d5134074d84 176 int main() {
stefanrousseau 61:f6b93129f954 177 static unsigned ledOnce = 0;
fkellermavnet 20:27a4f27254d0 178
stefanrousseau 61:f6b93129f954 179 pc.baud(115200);
stefanrousseau 61:f6b93129f954 180 pc.printf(GRN "Hello World from the Cellular IoT Kit!\r\n\r\n");
JMF 0:9d5134074d84 181
stefanrousseau 61:f6b93129f954 182 //Initialize the I2C sensors that are present
stefanrousseau 11:e6602513730f 183 sensors_init();
stefanrousseau 12:7c94ec5069dc 184 read_sensors();
stefanrousseau 11:e6602513730f 185
stefanrousseau 61:f6b93129f954 186 // Set LED to RED until init finishes
stefanrousseau 61:f6b93129f954 187 SetLedColor(0x1); //Red
JMF 0:9d5134074d84 188 // Initialize the modem
stefanrousseau 61:f6b93129f954 189 pc.printf("\r\n");
stefanrousseau 61:f6b93129f954 190 cell_modem_init();
stefanrousseau 61:f6b93129f954 191 // Set LED BLUE for partial init
stefanrousseau 61:f6b93129f954 192 SetLedColor(0x4); //Blue
JMF 0:9d5134074d84 193
stefanrousseau 3:26b3cc155f39 194 //Create a 1ms timer tick function:
stefanrousseau 61:f6b93129f954 195 iTimer1Interval_ms = SENSOR_UPDATE_INTERVAL_MS;
stefanrousseau 3:26b3cc155f39 196 OneMsTicker.attach(OneMsFunction, 0.001f) ;
fkellermavnet 26:8d6e7e7cdcae 197
JMF 2:0e2ef866af95 198 // Send and receive data perpetually
JMF 2:0e2ef866af95 199 while(1) {
stefanrousseau 55:3abf9e3f42e6 200 #ifdef USE_VIRTUAL_SENSORS
stefanrousseau 55:3abf9e3f42e6 201 ProcessUsbInterface();
stefanrousseau 55:3abf9e3f42e6 202 #endif
stefanrousseau 3:26b3cc155f39 203 if (bTimerExpiredFlag)
stefanrousseau 3:26b3cc155f39 204 {
stefanrousseau 3:26b3cc155f39 205 bTimerExpiredFlag = false;
stefanrousseau 4:f83bedd9cab4 206 read_sensors(); //read available external sensors from a PMOD and the on-board motion sensor
stefanrousseau 3:26b3cc155f39 207 char modem_string[512];
stefanrousseau 3:26b3cc155f39 208 GenerateModemString(&modem_string[0]);
stefanrousseau 61:f6b93129f954 209 char myJsonResponse[512];
stefanrousseau 61:f6b93129f954 210 if (cell_modem_Sendreceive(&modem_string[0], &myJsonResponse[0]))
fkellermavnet 20:27a4f27254d0 211 {
stefanrousseau 41:9b14c40eebf6 212 if (!ledOnce)
stefanrousseau 41:9b14c40eebf6 213 {
stefanrousseau 41:9b14c40eebf6 214 ledOnce = 1;
stefanrousseau 61:f6b93129f954 215 SetLedColor(0x2); //Green
stefanrousseau 41:9b14c40eebf6 216 }
stefanrousseau 61:f6b93129f954 217 parse_JSON(&myJsonResponse[0]);
stefanrousseau 16:17c5916f2d12 218 }
stefanrousseau 3:26b3cc155f39 219 } //bTimerExpiredFlag
stefanrousseau 3:26b3cc155f39 220 } //forever loop
JMF 0:9d5134074d84 221 }