Open Source EMDR Machine. The Whole Blog.
The whole code for this EMDR machine can be found on GitHub https://github.com/ilvyanyatka/Emdr-App-With-WiFi-Arduino-Option
The main calculation on how far the light ball should move and when to start/stop motors/sound is basically just Math. It is done in the Step() function. You can see it’s code in this project GitHub repository.
Below is some code to explain the other most important parts of the machine.
The Arduino side code
Code for the Sound
In the initialization section:
#define LEFT_SOUND_PIN 9 #define RIGHT_SOUND_PIN 10
In the loop() code, if we are less than ⅓ away from the start or end point - start the sound
if(sound>0) { if(!sound_left_on){ // send signal to start left sound tone(LEFT_SOUND_PIN,440); sound_left_on = true; } }
440 here means 440Hz. The right channel sound is started exactly the same way.
And the code to stop the sound:
if(sound_left_on){ noTone(LEFT_SOUND_PIN); sound_left_on = false; }
Code for the Smart LED strip
I learned about the FastLED library from this github repository’s code and it’s creator https://github.com/sensboston/ .
First, I installed FastLED library in Arduino IDE (Tools -> Manage Libraries -> Search for “FastLED”)
In the initialization section:
#include <FastLED.h> // number of leds in the strip. In my case 5 m * 60 per m #define MAX_N_LEDS 300 #define LED_PIN 2 CRGB leds[MAX_N_LEDS];
Initialization of LED strip in setup()
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, MAX_N_LEDS);
In the loop(), code to change the light ball position:
if(light > 0) { // turn off previous led setLED(prev_ledNum, 0, 0, 0); // highlight current led setLED(ledNum, r, g, b); }
Where r(ed), g(reen), b(lue) are parameters that Arduino Server gets through GET requests. There are also to_led and from_led variables, so the light ball runs only inside its borders.
To turn off all LEDs during “stop” request I used
FastLED.showColor(CRGB::Black);
Code for the motors
In the initialization section:
#define MOTOR1_LEFT_PIN 3 #define MOTOR1_RIGHT_PIN 4 #define MOTOR2_LEFT_PIN 5 #define MOTOR2_RIGHT_PIN 6 #define MOTOR_ON HIGH #define MOTOR_OFF LOW
In the setup():
pinMode(MOTOR1_LEFT_PIN, OUTPUT); pinMode(MOTOR1_RIGHT_PIN, OUTPUT); pinMode(MOTOR2_LEFT_PIN, OUTPUT); pinMode(MOTOR2_LEFT_PIN, OUTPUT);
The same as for the sound, I start the motor if I am less than ⅓ away from the start or end point. In the loop() (code is identical for all four motors)
if(motor2>0) { if(!motor2_left_on){ // send signal to start left motor2 digitalWrite(MOTOR2_LEFT_PIN, MOTOR_ON); motor1_left_on = true; } }
And to stop the motors
if(motor2_left_on){ digitalWrite(MOTOR2_LEFT_PIN, MOTOR_OFF); motor1_left_on = false; }
Code for the server
To use WiFi and run the server I needed to install WiFiNINA library by Arduino first ( (Arduino IDE: Tools -> Manage Libraries -> Search for “WiFiNINA”)
Initialization of the server
In the initialization section I added:
#include <WiFiNINA.h> #include "arduino_secrets.h" // this is where ssid and passkey are stored char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) IPAddress local_IP(192, 168, 1, 128); #define USE_STATIC_IP int status = WL_IDLE_STATUS; WiFiServer server(80);
To create a server
I used one of the numerous examples that come with WiFiNINA library
#ifdef USE_STATIC_IP WiFi.config(local_IP); #endif // attempt to connect to WiFi network: while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.begin(ssid, pass); // wait 10 seconds for connection: delay(10000); } server.begin();
To get a raw GET request and create a web page on the server
To get the body of raw data of request I also used slightly modified sample code. In addition this code creates a webpage on the Arduino server where the therapist can start/stop the machine with default parameters. It is done just in case the therapist doesn’t have software but only the Arduino part of the machine.
String GetRequestValue() { String result = ""; String currentLine = ""; // make a String to hold incoming data from the client WiFiClient client = server.available(); // listen for incoming clients if (client) { // if you get a client, Serial.println("new client"); // print a message out the serial port while (client.connected()) { // loop while the client's connected delayMicroseconds(10); // This is required for the Arduino Nano RP2040 Connect - otherwise it will loop so fast that SPI will never be served. if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then Serial.write(c); // print it out the serial monitor if (c == '\n') { // if the byte is a newline character if(currentLine.startsWith("GET")){ result = currentLine; } // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println(); // the content of the HTTP response follows the header: client.print("Click <a href=\"/stop\">here</a> to stop emdr machine<br>"); client.print("Click <a href=\"/start\">here</a> to start emdr machine<br>"); // The HTTP response ends with another blank line: client.println(); // break out of the while loop: break; } else { // if you got a newline, then clear currentLine: currentLine = ""; } } else if (c != '\r') { // if you got anything else but a carriage return character, currentLine += c; // add it to the end of the currentLine } } } // close the connection: client.stop(); Serial.println("client disconnected"); } return result; }
To get parameters from a GET request string
I used this code:
////////// // get param from command string in format param1=Aaa¶m2=Bbb... ////////// String getParam(String data, String paramName) { String result = ""; int index = data.indexOf(paramName); if (index >= 0) { String param = data.substring(index + paramName.length()); index = param.indexOf("="); if (index >= 0) { param = param.substring(index + 1); index = param.indexOf("&"); if (index >= 0) param.remove(index); result = param; } } return result; }
The timers
The timer for recalculation of light position, sound and motors is set to 20ms. The timer to get new data from WiFi is set to 120ms
In initialization section:
// ms, how often run the check and update #define STEP_INTERVAL 20 #define PARAMS_UPDATE_STEP_INTERVAL 120
In the loop():
// if less then STEP_INTERVAL ms passed - do nothing if(millis() - last_step_time < STEP_INTERVAL) { delay(STEP_INTERVAL); return; } // set time for the next check last_step_time = millis(); // if less then PARAMS_UPDATE_STEP_INTERVAL ms passed - do nothing if(millis() - last_params_update_step_time > PARAMS_UPDATE_STEP_INTERVAL) { last_params_update_step_time = millis(); // get new data from wifi … } // do light/sound/motor update …
The desktop application side code
I wrote desktop application in c#.NET, using Xamarin forms. The idea was to make it cross platform and to work on Universal Windows Platform (UWP), Android and iOS. I cannot test it on iOS because I don’t have any Apple devices, so I kept the iOS project just in case. For Android I tested it on VS.NET Android emulator, I haven't tested it on a real Android phone yet. I assume code that involve calls to HttpClient need to be changed, probably using dependency injection like described here Xamarin and the HttpClient For iOS, Android and Windows
I just didn’t have enough time to work on it because I decided to replace the communication between the application and Arduino from BLE to WiFi at the last moment.
All the communication related code is stored in ArduinoHTTPUtils.cs file
Code to send a request to start the machine
public static void SendStart(EmdrModel emdrModel) { string requestString = "http://" + IP + "/start?" + CreateParamsString(emdrModel); // Call asynchronous network methods in a try/catch block to handle exceptions. try { client.GetAsync(requestString); } catch (HttpRequestException e) { Debug.WriteLine("\nException Caught!"); Debug.WriteLine("Message :{0} ", e.Message); } }
Code to send a request to stop the machine
public static void SendStop() { string requestString = "http://" + IP + "/stop"; // Call asynchronous network methods in a try/catch block to handle exceptions. try { client.GetAsync(requestString); } catch (HttpRequestException e) { Debug.WriteLine("\nException Caught!"); Debug.WriteLine("Message :{0} ", e.Message); } }
Code to create parameters of the request from EMDR model
public static string CreateParamsString(EmdrModel emdrModel) { string result = ""; //Ex. result = "light=1&sound=1&motor1=1&brightness=45&speed=20&red=128&green=0&blue=128"; result = string.Format("light={0}&sound={1}&motor1={2}&motor2={3}&speed={4}&red={5}&green={6}&blue={7}&brightness={8}", emdrModel.UseLight? 1 : -1, emdrModel.UseSound ? 1 : -1, emdrModel.UseSmallTappers ? 1 : -1, emdrModel.UseLargeTappers ? 1 : -1, emdrModel.Speed, emdrModel.Color.R, emdrModel.Color.G, emdrModel.Color.B, emdrModel.Brightness); return result; }
When “start” request is sent - example
In the Xamarin form, on each PropertyChanged event for each value I send a new GET request
Ex.
private void speedSlider_ValueChanged(object sender, ValueChangedEventArgs e) { emdrModel.Speed = (int)Math.Round(e.NewValue); // send data to Arduino if (emdrModel.Platform == TargetPlatform.Arduino) ArduinoHTTPUtils.SendStart(emdrModel); }
When “stop” request is sent - example
When Back button is pressed or application is closed I send a GET request to stop the machine
Ex.
protected override void OnSleep() { ArduinoHTTPUtils.SendStop(); }
This is pretty much it. The rest of desktop application code is GUI and the part of the application that does light/sound on PC itself.