Working read code

Dependencies:   SDFileSystem emic2 mbed-rtos mbed

Files at this revision

API Documentation at this revision

Comitter:
nnguyen45
Date:
Mon Dec 04 19:58:40 2017 +0000
Child:
1:f3d363ca2343
Commit message:
Working read code

Changed in this revision

SDFileSystem.lib Show annotated file Show diff for this revision Revisions of this file
button.cpp Show annotated file Show diff for this revision Revisions of this file
button.h Show annotated file Show diff for this revision Revisions of this file
buttonArray.cpp Show annotated file Show diff for this revision Revisions of this file
buttonArray.h Show annotated file Show diff for this revision Revisions of this file
emic2.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-rtos.lib Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SDFileSystem.lib	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,1 @@
+http://developer.mbed.org/users/mbed_official/code/SDFileSystem/#8db0d3b02cec
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/button.cpp	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,170 @@
+#include "mbed.h"
+#include "button.h"
+#include "emic2.h"
+
+//emic2 myTTS(p28, p27); //serial RX,TX pins to emic
+DigitalOut led4(LED4);
+DigitalOut led3(LED3);
+DigitalOut led2(LED2);
+
+// button constructor
+button::button(PwmOut servo, DigitalIn pb, int id)
+    : servo(servo), pb(pb), state(0), press(0), id(id) {}
+/*button::button(PwmOut servo, DigitalIn pb, AnalogIn lp)
+    : servo(servo), pb(pb), linpot(lp), press(0), state(0) {}
+*/
+//Serial pc(USBTX, USBRX);
+
+// FUNCTIONS
+
+// get servo pin
+PwmOut button::getServoPin()
+{
+    return servo;
+}
+
+// get servo pin
+void button::setState(int mystate)
+{
+    state = mystate;
+}
+
+void button::setPress(int mypress)
+{
+    press = mypress;
+}
+
+// get servo pin
+/*void button::setMode(int mymode)
+{
+    mode = mymode;
+}*/
+
+// get current state of the button
+int button::getState()
+{
+    return state;
+}
+
+int button::getID()
+{
+    return id;
+}
+
+
+int button::getPress()
+{
+    //pc.printf("%d", press);
+    return press;
+}
+
+// get current state of the button
+int button::getLp()
+{
+    /*    if (linpot < 2)
+            return 1;
+        else*/
+    return 0;
+}
+
+// move servo into the slot
+void button::moveServoIn()
+{
+    //myled = 1;
+    // rotate 90 degrees one way
+    for(int i=4; i<=7; i++) {
+        servo = i/100.0;
+        wait(0.01);
+    }
+    //press = 1;
+    switch (id) {
+        case 1:
+            led2 = 0;
+            led3 = 0;
+            led4 = 1;
+            break;
+        case 2:
+            led2 = 0;
+            led3 = 1;
+            led4 = 0;
+            break;
+        case 3:
+            led2 = 0;
+            led3 = 1;
+            led4 = 1;
+            break;
+        case 4:
+            led2 = 1;
+            led3 = 0;
+            led4 = 0;
+            break;
+        case 5:
+            led2 = 1;
+            led3 = 0;
+            led4 = 1;
+            break;
+        case 6:
+            led2 = 1;
+            led3 = 1;
+            led4 = 0;
+            break;
+    }
+}
+
+// move servo out of the slot
+void button::moveServoOut()
+{
+    //myled = 0;
+    for(int i=7; i>4; i--) {
+        servo = i/100.0;
+        wait(0.01);
+    }
+    led2 = 0;
+    led3 = 0;
+    led4 = 0;
+}
+
+int button::updateState()
+{
+    //myled = 0;
+    // state 0 - button is up, pb = 0
+    if (pb == 0 && state == 3) {
+        // nothing happens here, servo is still
+        state = 0;
+    }
+    // state 1 - button is moving down, pb = 1
+    if (pb == 1 && state == 0) {
+        moveServoIn();
+        state = 1;
+        press = 1;
+
+        // Speaker says stuff
+        /*myTTS.volume(18); //max volume
+        myTTS.speakf("S");//Speak command starts with "S"
+        myTTS.speakf("Hey, you pressed a pin!");  // Send the desired string to convert to speech
+        myTTS.speakf("\r"); //marks end of speak command*/
+    }
+    // state 2 - button is down, pb = 0
+    if (pb == 0 && state == 1) {
+        // nothing happens here, servo is still
+        state = 2;
+    }
+    // state 3 - button is moving up, pb = 1
+    if (pb == 1 && state == 2) {
+        moveServoOut();
+        state = 3;
+        press = 0;
+    }
+    // state 4 - handle debouncing while button is down
+    /*if (pb1 = 1 && state == 2) {
+        count++;
+    }*/
+    return state;
+}
+
+void button::setup() {
+    for(int i=0; i<=4; i++) {
+        servo = i/100.0;
+        wait(0.01);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/button.h	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,42 @@
+#include "mbed.h"
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+// This is a button class for our custom button
+class button {
+
+// pins connected to the button
+private:
+    PwmOut servo;
+    DigitalIn pb;
+    int state; // where is the button (0 - 4)
+    int press; // is the button up or down
+    int id;    // this is the ID, each button should have a unique id
+    // int mode; // is the system in reading or typing mode
+    //AnalogIn linpot;
+
+public:
+    // constructors
+    button(); // Default
+    button(PwmOut servo, DigitalIn pb, int id);
+    //button(PwmOut servo, DigitalIn pb, AnalogIn linpot);
+
+    // button(PwmOut servo, DigitalIn pb, AnalogIn linpot);
+    // functions
+    PwmOut getServoPin(); // get the servo pin
+    //void setState(int state); // set state
+    //void setMode(int mode); // set mode
+    void setState(int);     // set what state the button is in - up or down
+    void setPress(int);     // set the button press
+    void moveServoIn();   // move servo into the slot
+    void moveServoOut();  // move servo out of the slot
+    int getID();
+    int updateState();
+    int getState();
+    int getPress();
+    int getLp();
+    void setup();
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/buttonArray.cpp	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,158 @@
+#include "mbed.h"
+#include "buttonArray.h"
+
+// type mode
+
+// buttonArray constructor
+buttonArray::buttonArray(button b1, button b2, button b3, button b4, button b5, button b6)
+    : button1(b1), button2(b2), button3(b3), button4(b4), button5(b5), button6(b6) {}
+
+//Serial pc(USBTX, USBRX);
+
+// FUNCTIONS
+
+// map input braille to ascii
+// braille respresentation here - https://en.wikipedia.org/wiki/Braille_ASCII
+char buttonArray::checkVal(char* braille)
+{
+    //pc.printf(" checkVal \n");
+    //char* braille;
+    char val = 'K';
+    /*int test = button1.getPress();
+    sprintf(braille, "%d%d%d%d%d%d", test, button2.getPress(),
+            button3.getPress(), button4.getPress(), button5.getPress(), button6.getPress());*/
+    //pc.printf(" %s \n", braille);
+    if (strcmp(braille, "000000") == 0) val = 'X';
+    if (strcmp(braille, "011111") == 0) val = 'A';
+    if (strcmp(braille, "001111") == 0) val = 'B';
+    if (strcmp(braille, "011011") == 0) val = 'C';
+    if (strcmp(braille, "011001") == 0) val = 'D';
+    if (strcmp(braille, "011101") == 0) val = 'E';
+    if (strcmp(braille, "001011") == 0) val = 'F';
+    if (strcmp(braille, "001001") == 0) val = 'G';
+    if (strcmp(braille, "001101") == 0) val = 'H';
+    if (strcmp(braille, "101101") == 0) val = 'I';
+    if (strcmp(braille, "101001") == 0) val = 'J';
+    if (strcmp(braille, "010011") == 0) val = 'M';
+    if (strcmp(braille, "010101") == 0) val = 'O';
+    /*if (strcmp(braille, "011111") == 0) val = 'K';
+    if (strcmp(braille, "011111") == 0) val = 'L';
+    if (strcmp(braille, "011111") == 0) val = 'N';
+    if (strcmp(braille, "011111") == 0) val = 'P';
+    if (strcmp(braille, "011111") == 0) val = 'Q';
+    if (strcmp(braille, "011111") == 0) val = 'R';
+    if (strcmp(braille, "011111") == 0) val = 'S';
+    if (strcmp(braille, "011111") == 0) val = 'T';
+    if (strcmp(braille, "011111") == 0) val = 'U';
+    if (strcmp(braille, "011111") == 0) val = 'V';
+    if (strcmp(braille, "011111") == 0) val = 'W';
+    if (strcmp(braille, "011111") == 0) val = 'X';
+    if (strcmp(braille, "011111") == 0) val = 'Y';
+    if (strcmp(braille, "011111") == 0) val = 'Z';*/
+    // check if reset
+    if (strcmp(braille, "111111") == 0) val = 'Z';
+    //pc.printf(" %c \n", val);
+    return val;
+}
+
+// get braille represention of char
+char* buttonArray::getBraille(char val)
+{
+    char* braille;
+    if (val == 'A') braille = "011111";
+    if (val == 'X') braille = "000000";
+    if (val == 'Z') braille = "111111";
+    if (val == 'M') braille = "010011";
+    if (val == 'O') braille = "010101";
+    if (val == 'K') braille = "100000";
+    // check if reset
+    if (val == ' ') braille = "111111";
+    return braille;
+}
+
+// return an array of which pins need to be up
+int* buttonArray::pinsUp(char val)
+{
+    int* pinsup;
+    //pinsup = new int[7];
+    char* braille = getBraille(val);
+    int j = 1;
+    for (int i = 0; i < 6; i++) {
+        if (braille[i] == '0') {
+            pinsup[j] = i+1;
+            j++;
+        }
+    }
+    // record size of array in the first element
+    pinsup[0] = j;
+    return pinsup;
+}
+
+// return feedback on which pins need to be corrected
+// takes in current and actual char as input and returns status of each char
+int* buttonArray::wrongPins(char* inarr, char actual)
+{
+    // TODO check initial value of wrong
+    int* wrong;
+    wrong = new int[7];
+    char* actarr = getBraille(actual);
+    //pc.printf("wrong pins");
+    int j = 1;
+    for (int i = 0; i < 6; i++) {
+        if(inarr[i] != actarr[i]) {
+            wrong[j] = i+1;
+            //pc.printf("%d ", wrong[j]);
+            j++;
+        }
+    }
+    // record size of array in the first element
+    wrong[0] = j;
+    return wrong;
+}
+
+// release buttons
+void buttonArray::releaseButtons()
+{
+    if (button1.getPress()) {
+        button1.moveServoOut();
+        button1.setState(3);
+        button1.setPress(0);
+    }
+    if (button2.getPress()) {
+        button2.moveServoOut();
+        button2.setState(3);
+        button2.setPress(0);
+    }
+    if (button3.getPress()) {
+        button3.moveServoOut();
+        button3.setState(3);
+        button3.setPress(0);
+    }
+    if (button4.getPress()) {
+        button4.moveServoOut();
+        button4.setState(3);
+        button4.setPress(0);
+    }
+    if (button5.getPress()) {
+        button5.moveServoOut();
+        button5.setState(3);
+        button5.setPress(0);
+    }
+    if (button6.getPress()) {
+        button6.moveServoOut();
+        button6.setState(3);
+        button6.setPress(0);
+    }
+}
+
+void buttonArray::setup()
+{
+    // servos begin at 30 degrees
+    // replace with a button setup function
+    button1.setup();
+    button2.setup();
+    button3.setup();
+    button4.setup();
+    button5.setup();
+    button6.setup();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/buttonArray.h	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,29 @@
+#include "mbed.h"
+#include "button.h"
+
+// This is a button class for our custom button
+class buttonArray {
+
+// buttons
+private:
+// 6 buttons here 
+    button button1;
+    button button2;
+    button button3;
+    button button4;
+    button button5;
+    button button6;
+    int currVal;
+    
+public:
+    // constructors
+    buttonArray(); // Default
+    buttonArray(button button1, button button2, button button3, button button4, button button5, button button6);
+    // functions
+    int* wrongPins(char* input, char actual);
+    char checkVal(char* braille); // return buttons ascii representation
+    void releaseButtons();// release all buttons;
+    int* pinsUp(char val);    
+    char* getBraille(char val);
+    void setup();
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/emic2.lib	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/4180_1/code/emic2/#b95ede38e19d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,328 @@
+#include "mbed.h"
+#include "rtos.h"
+#include "SDFileSystem.h"
+#include "button.h"
+#include "buttonArray.h"
+#include <string>
+#include <iostream>
+#include "emic2.h"
+
+using namespace std;
+
+// DEFINE I/O
+PwmOut myservo(p21);
+DigitalIn pb1 (p20);
+PwmOut myservo2(p22);
+DigitalIn pb2 (p19);
+PwmOut myservo3(p23);
+DigitalIn pb3 (p18);
+PwmOut myservo4(p24);
+DigitalIn pb4 (p17);
+PwmOut myservo5(p25);
+DigitalIn pb5 (p16);
+PwmOut myservo6(p26);
+DigitalIn pb6 (p15);
+
+Serial pc(USBTX, USBRX);
+SDFileSystem sd(p5, p6, p7, p8, "sd"); //SD card
+button button1(myservo, pb1, 1);
+button button2(myservo2, pb2, 2);
+button button3(myservo3, pb3, 3);
+button button4(myservo4, pb4, 4);
+button button5(myservo5, pb5, 5);
+button button6(myservo6, pb6, 6);
+
+buttonArray buttonarr(button1, button2, button3, button4, button5, button6);
+
+emic2 myTTS(p28, p27); //serial RX,TX pins to emic
+
+// INITIALIZE VARIABLES
+// add mode, reset buttons
+int start = 0;
+int submit = 0;
+// Buttons begins in up state
+int state = 0;
+int state2 = 0;
+int state3 = 0;
+int state4 = 0;
+int state5 = 0;
+int state6 = 0;
+int count = 0;
+
+// THREADS
+void button_thread()
+{
+    while(true) {
+        state = button1.updateState();
+        state6 = button6.updateState();
+        Thread::wait(100); // wait till thread is done
+    }
+}
+
+void button2_thread()
+{
+    while(true) {
+        state2 = button2.updateState();
+        Thread::wait(200); // wait till thread is done
+    }
+}
+
+void button3_thread()
+{
+    while(true) {
+        state3 = button3.updateState();
+        Thread::wait(200); // wait till thread is done
+    }
+}
+
+void button4_thread()
+{
+    while(true) {
+        state4 = button4.updateState();
+        Thread::wait(200); // wait till thread is done
+    }
+}
+
+void button5_thread()
+{
+    while(true) {
+        state5 = button5.updateState();
+        Thread::wait(200); // wait till thread is done
+    }
+}
+
+void button6_thread()
+{
+    while(true) {
+        state6 = button6.updateState();
+        Thread::wait(200); // wait till thread is done
+    }
+}
+
+void submit_thread()
+{
+    Thread::wait(500); // wait till thread is done
+}
+
+void start_thread()
+{
+    start = 1;
+    Thread::wait(500); // wait till thread is done
+}
+
+int main()
+{
+    // SETUP; pull up the pushbutton to prevent bouncing
+    pb1.mode(PullUp);
+    pb2.mode(PullUp);
+    pb3.mode(PullUp);
+    pb4.mode(PullUp);
+    pb5.mode(PullUp);
+    pb6.mode(PullUp);
+    wait(.001);
+
+    // servo setup up function; servos begin at 30 degrees
+    buttonarr.setup();
+
+    // PARSE INPUT FILE FOR LETTERS AND WORDS
+    char delimiter = ',';
+    string letter[2];
+    string word[2];
+    char check;
+    string temp;
+    string tempword = "";
+    int counter = 0;
+    FILE *fp = fopen("/sd/plan.txt", "r"); //create file
+    check = fgetc(fp); //grabs a char from file
+    while(check != '\n') {  //while not at the end of line for letters
+        if((check == delimiter) && (temp.length() == 1)) {  //at a comma and have a letter stored
+            letter[counter] = temp; //write letter
+            counter = counter + 1;  //increment counter
+        } else {
+            temp = check;   //store letter
+        }
+        check = fgetc(fp);  //grabs next char
+    }
+    counter = 0;    //reset counter
+    check = fgetc(fp);  //grabs next char
+    while(!feof(fp)) {  //while not at the end of line for words
+        if(check == delimiter) {  //when at the comma at the end of a word
+            word[counter] = tempword;   //write word
+            tempword = "";
+            counter = counter + 1;  //increment counter
+        } else {
+            tempword = tempword + check;    //concatenate letters to build word
+        }
+        check = fgetc(fp);  //grabs next char
+    }
+    fclose(fp); //close file
+    
+    //INITIALIZE THREADS
+    Thread t1(button_thread);
+    Thread t2(button2_thread);
+    Thread t3(button3_thread);
+    Thread t4(button4_thread);
+    Thread t5(button5_thread);
+    t1.start(button_thread);
+    t2.start(button2_thread);
+    t3.start(button3_thread);
+    t4.start(button4_thread);
+    t5.start(button5_thread);
+
+    char currletter;
+    int lettersize = sizeof(letter)/sizeof(letter[0]);
+    int type = 0;
+
+    //TEXT-TO-SPEECH LOGIC
+    myTTS.volume(1); //max volume is 18
+    myTTS.voice(2);
+
+    char* braille;
+    char userinput;
+
+    // INITIAL RESET
+    if (type == 0) {
+        int reset = 1;
+        myTTS.speakf("SWelcome to Bat, the Braille Assistive Teacher. This device will help you learn how to write and type braille. Please setup the device by pressing down all the buttons.\r");
+        myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+        while(reset == 1) {
+            wait(3);
+            sprintf(braille, "%d%d%d%d%d%d", button1.getPress(), button2.getPress(),
+                    button3.getPress(), button4.getPress(), button5.getPress(), button6.getPress());
+            userinput = buttonarr.checkVal(braille);
+            if(userinput == 'Z') {
+                reset = 0;
+            } else {
+                myTTS.speakf("SSetup failed. Please try again.\r");
+                myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+            }
+        }
+    }
+
+    for(int i = 0; i < lettersize; i++) {  //iterate through the letter array
+        char currletter = letter[i][0];
+        int* pinsup = buttonarr.pinsUp(currletter);
+        int currpress;
+        int numpinsups = pinsup[0];  // size of array is first element of pinsup
+        string presspin = "STo write the letter ";
+        presspin = presspin + letter[i];
+        presspin = presspin + ", press buttons";
+
+        for (int j = 1; j < numpinsups; j++) {  // get what pins to press
+            currpress = pinsup[j];
+            switch (currpress) {
+                case 1:
+                    presspin = presspin + " 1,";
+                    break;
+                case 2:
+                    presspin = presspin + " 2,";
+                    break;
+                case 3:
+                    presspin = presspin + " 3,";
+                    break;
+                case 4:
+                    presspin = presspin + " 4,";
+                    break;
+                case 5:
+                    presspin = presspin + " 5,";
+                    break;
+                case 6:
+                    presspin = presspin + " 6,";
+                    break;
+            }
+        }
+        myTTS.speakf("%s\r",presspin);
+        myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+        wait(2);
+
+        char* braille;
+        char userinput;
+        char* oldbraille;
+        if (type == 0) {
+            sprintf(braille, "%d%d%d%d%d%d", button1.getPress(), button2.getPress(),
+                    button3.getPress(), button4.getPress(), button5.getPress(), button6.getPress());
+            userinput = buttonarr.checkVal(braille);
+        } else {
+            // TYPE MODE
+            sprintf(braille, "%d%d%d%d%d%d", !button1.getPress(), !button2.getPress(),
+                    !button3.getPress(), !button4.getPress(), !button5.getPress(), !button6.getPress());
+            userinput = buttonarr.checkVal(braille);
+            oldbraille = braille;
+        }
+
+        int currwrong;
+        string wrongpin;
+        // check result
+        pc.printf("\n %c %c", userinput, currletter);
+        int* wrongpins = buttonarr.wrongPins(braille, currletter);
+        int test = 1;
+        while(test == 1) {
+            wrongpin = "SYour answer is incorrect. Buttons";
+            for (int j = 1; j < wrongpins[0]; j++) {  // get what pins are wrong
+                currwrong = wrongpins[j];
+                switch (currwrong) {
+                    case 1:
+                        wrongpin = wrongpin + " 1,";
+                        break;
+                    case 2:
+                        wrongpin = wrongpin + " 2,";
+                        break;
+                    case 3:
+                        wrongpin = wrongpin + " 3,";
+                        break;
+                    case 4:
+                        wrongpin = wrongpin + " 4,";
+                        break;
+                    case 5:
+                        wrongpin = wrongpin + " 5,";
+                        break;
+                    case 6:
+                        wrongpin = wrongpin + " 6,";
+                        break;
+                }
+            }
+            if (wrongpins[0] > 1) {
+                wrongpin = wrongpin + " are wrong. Please try again.";
+                myTTS.speakf("%s\r",wrongpin);
+                myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+                wait(2);
+                //UPDATE THE PINS THAT ARE WRONG BY CHECKING AGAIN BELOW
+                sprintf(braille, "%d%d%d%d%d%d", button1.getPress(), button2.getPress(),
+                button3.getPress(), button4.getPress(), button5.getPress(), button6.getPress());
+                userinput = buttonarr.checkVal(braille);
+                
+                // check result
+                wrongpins = buttonarr.wrongPins(braille, currletter);
+            } else {
+                test = 0;
+                myTTS.speakf("SGood job!\r");
+                myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+            }
+        }
+
+        if (type == 0) {
+            int reset = 1;
+            myTTS.speakf("SNow reset by pressing down all the buttons.\r");
+            myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+            while(reset == 1) {
+                char* braille;
+                sprintf(braille, "%d%d%d%d%d%d", button1.getPress(), button2.getPress(),
+                        button3.getPress(), button4.getPress(), button5.getPress(), button6.getPress());
+                userinput = buttonarr.checkVal(braille);
+                if(userinput == 'Z') {
+                    reset = 0;
+                } else {
+                    wait(2);
+                    myTTS.speakf("SPlease try again.\r");
+                    myTTS.ready(); //ready waits for speech to finish from last command with a ":" response
+                }
+            }
+        }
+    }
+
+// MAIN THREAD
+    while(true) {
+
+        Thread::wait(500); // wait till thread is done
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-rtos.lib	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed-rtos/#58563e6cba1e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Mon Dec 04 19:58:40 2017 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/ef9c61f8c49f
\ No newline at end of file