initial attempt to implement a feedhold.
[clinton/Smoothieware.git] / src / libs / USBDevice / USBSerial / USBSerial.cpp
index 9b0a32d..cd7ab1f 100644 (file)
-/* Copyright (c) 2010-2011 mbed.org, MIT License\r
-*\r
-* Permission is hereby granted, free of charge, to any person obtaining a copy of this software\r
-* and associated documentation files (the "Software"), to deal in the Software without\r
-* restriction, including without limitation the rights to use, copy, modify, merge, publish,\r
-* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\r
-* Software is furnished to do so, subject to the following conditions:\r
-*\r
-* The above copyright notice and this permission notice shall be included in all copies or\r
-* substantial portions of the Software.\r
-*\r
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\r
-* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\r
-* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
-* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
-*/\r
-\r
-#include <cstdint>\r
-#include <cstdio>\r
-\r
-#include "USBSerial.h"\r
-\r
-#include "libs/Kernel.h"\r
-#include "libs/SerialMessage.h"\r
-\r
-// extern void setled(int, bool);\r
-#define setled(a, b) do {} while (0)\r
-\r
-#define iprintf(...) do { } while (0)\r
-\r
-USBSerial::USBSerial(USB *u): USBCDC(u), rxbuf(256 + 8), txbuf(128 + 8)\r
-{\r
-    usb = u;\r
-    nl_in_rx = 0;\r
-    attach = attached = false;\r
-    flush_to_nl = false;\r
-}\r
-\r
-void USBSerial::ensure_tx_space(int space)\r
-{\r
-    while (txbuf.free() < space)\r
-    {\r
-        usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);\r
-        usb->usbisr();\r
-    }\r
-}\r
-\r
-int USBSerial::_putc(int c)\r
-{\r
-    if (!attached)\r
-        return 1;\r
-    ensure_tx_space(1);\r
-    txbuf.queue(c);\r
-\r
-    usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);\r
-    return 1;\r
-}\r
-\r
-int USBSerial::_getc()\r
-{\r
-    if (!attached)\r
-        return 0;\r
-    uint8_t c = 0;\r
-    setled(4, 1); while (rxbuf.isEmpty()); setled(4, 0);\r
-    rxbuf.dequeue(&c);\r
-    if (rxbuf.free() == MAX_PACKET_SIZE_EPBULK)\r
-    {\r
-        usb->endpointSetInterrupt(CDC_BulkOut.bEndpointAddress, true);\r
-        iprintf("rxbuf has room for another packet, interrupt enabled\n");\r
-    }\r
-    else if ((rxbuf.free() < MAX_PACKET_SIZE_EPBULK) && (nl_in_rx == 0))\r
-    {\r
-        // handle potential deadlock where a short line, and the beginning of a very long line are bundled in one usb packet\r
-        rxbuf.flush();\r
-        flush_to_nl = true;\r
-\r
-        usb->endpointSetInterrupt(CDC_BulkOut.bEndpointAddress, true);\r
-        iprintf("rxbuf has room for another packet, interrupt enabled\n");\r
-    }\r
-    if (nl_in_rx > 0)\r
-        if (c == '\n' || c == '\r')\r
-            nl_in_rx--;\r
-\r
-    return c;\r
-}\r
-\r
-int USBSerial::puts(const char *str)\r
-{\r
-    if (!attached)\r
-        return strlen(str);\r
-    int i = 0;\r
-    while (*str)\r
-    {\r
-        ensure_tx_space(1);\r
-        txbuf.queue(*str);\r
-        if ((txbuf.available() % 64) == 0)\r
-            usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);\r
-        i++;\r
-        str++;\r
-    }\r
-    usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);\r
-    return i;\r
-}\r
-\r
-uint16_t USBSerial::writeBlock(const uint8_t * buf, uint16_t size)\r
-{\r
-    if (!attached)\r
-        return size;\r
-    if (size > txbuf.free())\r
-    {\r
-        size = txbuf.free();\r
-    }\r
-    if (size > 0)\r
-    {\r
-        for (uint8_t i = 0; i < size; i++)\r
-        {\r
-            txbuf.queue(buf[i]);\r
-        }\r
-        usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);\r
-    }\r
-    return size;\r
-}\r
-\r
-bool USBSerial::USBEvent_EPIn(uint8_t bEP, uint8_t bEPStatus)\r
-{\r
-    /*\r
-     * Called in ISR context\r
-     */\r
-\r
-//     static bool needToSendNull = false;\r
-\r
-    bool r = true;\r
-\r
-    if (bEP != CDC_BulkIn.bEndpointAddress)\r
-        return false;\r
-\r
-    iprintf("USBSerial:EpIn: 0x%02X\n", bEPStatus);\r
-\r
-    uint8_t b[MAX_PACKET_SIZE_EPBULK];\r
-\r
-    int l = txbuf.available();\r
-    iprintf("%d bytes queued\n", l);\r
-    if (l > 0)\r
-    {\r
-        if (l > MAX_PACKET_SIZE_EPBULK)\r
-            l = MAX_PACKET_SIZE_EPBULK;\r
-        iprintf("Sending %d bytes:\n\t", l);\r
-        int i;\r
-        for (i = 0; i < l; i++) {\r
-            txbuf.dequeue(&b[i]);\r
-            if (b[i] >= 32 && b[i] < 128)\r
-                iprintf("%c", b[i]);\r
-            else {\r
-                iprintf("\\x%02X", b[i]);\r
-            }\r
-        }\r
-        iprintf("\nSending...\n");\r
-        send(b, l);\r
-        iprintf("Sent\n");\r
-        if (txbuf.available() == 0)\r
-            r = false;\r
-    }\r
-    else\r
-    {\r
-        r = false;\r
-    }\r
-    iprintf("USBSerial:EpIn Complete\n");\r
-    return r;\r
-}\r
-\r
-bool USBSerial::USBEvent_EPOut(uint8_t bEP, uint8_t bEPStatus)\r
-{\r
-    /*\r
-     * Called in ISR context\r
-     */\r
-\r
-    bool r = true;\r
-\r
-    iprintf("USBSerial:EpOut\n");\r
-    if (bEP != CDC_BulkOut.bEndpointAddress)\r
-        return false;\r
-\r
-    if (rxbuf.free() < MAX_PACKET_SIZE_EPBULK)\r
-    {\r
-//         usb->endpointSetInterrupt(bEP, false);\r
-        return false;\r
-    }\r
-\r
-    uint8_t c[MAX_PACKET_SIZE_EPBULK];\r
-    uint32_t size = 64;\r
-\r
-    //we read the packet received and put it on the circular buffer\r
-    readEP(c, &size);\r
-    iprintf("Read %ld bytes:\n\t", size);\r
-    for (uint8_t i = 0; i < size; i++) {\r
-\r
-        if (flush_to_nl == false)\r
-            rxbuf.queue(c[i]);\r
-\r
-        if (c[i] >= 32 && c[i] < 128)\r
-        {\r
-            iprintf("%c", c[i]);\r
-        }\r
-        else\r
-        {\r
-            iprintf("\\x%02X", c[i]);\r
-        }\r
-\r
-        if (c[i] == '\n' || c[i] == '\r')\r
-        {\r
-            if (flush_to_nl)\r
-                flush_to_nl = false;\r
-            else\r
-                nl_in_rx++;\r
-        }\r
-        else if (rxbuf.isFull() && (nl_in_rx == 0))\r
-        {\r
-            // to avoid a deadlock with very long lines, we must dump the buffer\r
-            // and continue flushing to the next newline\r
-            rxbuf.flush();\r
-            flush_to_nl = true;\r
-        }\r
-    }\r
-    iprintf("\nQueued, %d empty\n", rxbuf.free());\r
-\r
-    if (rxbuf.free() < MAX_PACKET_SIZE_EPBULK)\r
-    {\r
-        // if buffer is full, stall endpoint, do not accept more data\r
-        r = false;\r
-\r
-        if (nl_in_rx == 0)\r
-        {\r
-            // we have to check for long line deadlock here too\r
-            flush_to_nl = true;\r
-            rxbuf.flush();\r
-\r
-            // and since our buffer is empty, we can accept more data\r
-            r = true;\r
-        }\r
-    }\r
-\r
-    usb->readStart(CDC_BulkOut.bEndpointAddress, MAX_PACKET_SIZE_EPBULK);\r
-    iprintf("USBSerial:EpOut Complete\n");\r
-    return r;\r
-}\r
-\r
-uint8_t USBSerial::available()\r
-{\r
-    return rxbuf.available();\r
-}\r
-\r
-void USBSerial::on_module_loaded()\r
-{\r
-    this->register_for_event(ON_MAIN_LOOP);\r
-}\r
-\r
-void USBSerial::on_main_loop(void *argument)\r
-{\r
-    // apparently some OSes don't assert DTR when a program opens the port\r
-    if (available() && !attach)\r
-        attach = true;\r
-\r
-    if (attach != attached)\r
-    {\r
-        if (attach)\r
-        {\r
-            attached = true;\r
-            kernel->streams->append_stream(this);\r
-            writeBlock((const uint8_t *) "Smoothie\nok\n", 12);\r
-        }\r
-        else\r
-        {\r
-            attached = false;\r
-            kernel->streams->remove_stream(this);\r
-            txbuf.flush();\r
-            rxbuf.flush();\r
-            nl_in_rx = 0;\r
-        }\r
-    }\r
-    if (nl_in_rx)\r
-    {\r
-        string received;\r
-        while (available())\r
-        {\r
-            char c = _getc();\r
-            if( c == '\n' || c == '\r')\r
-            {\r
-                struct SerialMessage message;\r
-                message.message = received;\r
-                message.stream = this;\r
-                iprintf("USBSerial Received: %s\n", message.message.c_str());\r
-                THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );\r
-                return;\r
-            }\r
-            else\r
-            {\r
-                received += c;\r
-            }\r
-        }\r
-    }\r
-}\r
-\r
-void USBSerial::on_attach()\r
-{\r
-    attach = true;\r
-}\r
-\r
-void USBSerial::on_detach()\r
-{\r
-    attach = false;\r
-}\r
+/* Copyright (c) 2010-2011 mbed.org, MIT License
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+* and associated documentation files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or
+* substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <cstdint>
+#include <cstdio>
+
+#include "USBSerial.h"
+
+#include "libs/Kernel.h"
+#include "libs/SerialMessage.h"
+#include "StreamOutputPool.h"
+
+// extern void setled(int, bool);
+#define setled(a, b) do {} while (0)
+
+#define iprintf(...) do { } while (0)
+
+USBSerial::USBSerial(USB *u): USBCDC(u), rxbuf(256 + 8), txbuf(128 + 8)
+{
+    usb = u;
+    nl_in_rx = 0;
+    attach = attached = false;
+    flush_to_nl = false;
+    halt_flag = false;
+    query_flag = false;
+    last_char_was_dollar = false;
+}
+
+void USBSerial::ensure_tx_space(int space)
+{
+    while (txbuf.free() < space) {
+        usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);
+        usb->usbisr();
+    }
+}
+
+int USBSerial::_putc(int c)
+{
+    if (!attached)
+        return 1;
+    ensure_tx_space(1);
+    txbuf.queue(c);
+
+    usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);
+    return 1;
+}
+
+int USBSerial::_getc()
+{
+    if (!attached)
+        return 0;
+    uint8_t c = 0;
+    setled(4, 1); while (rxbuf.isEmpty()); setled(4, 0);
+    rxbuf.dequeue(&c);
+    if (rxbuf.free() == MAX_PACKET_SIZE_EPBULK) {
+        usb->endpointSetInterrupt(CDC_BulkOut.bEndpointAddress, true);
+        iprintf("rxbuf has room for another packet, interrupt enabled\n");
+    } else if ((rxbuf.free() < MAX_PACKET_SIZE_EPBULK) && (nl_in_rx == 0)) {
+        // handle potential deadlock where a short line, and the beginning of a very long line are bundled in one usb packet
+        rxbuf.flush();
+        flush_to_nl = true;
+
+        usb->endpointSetInterrupt(CDC_BulkOut.bEndpointAddress, true);
+        iprintf("rxbuf has room for another packet, interrupt enabled\n");
+    }
+    if (nl_in_rx > 0)
+        if (c == '\n' || c == '\r')
+            nl_in_rx--;
+
+    return c;
+}
+
+int USBSerial::puts(const char *str)
+{
+    if (!attached)
+        return strlen(str);
+    int i = 0;
+    while (*str) {
+        ensure_tx_space(1);
+        txbuf.queue(*str);
+        if ((txbuf.available() % 64) == 0)
+            usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);
+        i++;
+        str++;
+    }
+    usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);
+    return i;
+}
+
+uint16_t USBSerial::writeBlock(const uint8_t * buf, uint16_t size)
+{
+    if (!attached)
+        return size;
+    if (size > txbuf.free()) {
+        size = txbuf.free();
+    }
+    if (size > 0) {
+        for (uint8_t i = 0; i < size; i++) {
+            txbuf.queue(buf[i]);
+        }
+        usb->endpointSetInterrupt(CDC_BulkIn.bEndpointAddress, true);
+    }
+    return size;
+}
+
+bool USBSerial::USBEvent_EPIn(uint8_t bEP, uint8_t bEPStatus)
+{
+    /*
+     * Called in ISR context
+     */
+
+//     static bool needToSendNull = false;
+
+    bool r = true;
+
+    if (bEP != CDC_BulkIn.bEndpointAddress)
+        return false;
+
+    iprintf("USBSerial:EpIn: 0x%02X\n", bEPStatus);
+
+    uint8_t b[MAX_PACKET_SIZE_EPBULK];
+
+    int l = txbuf.available();
+    if (l > 0) {
+        // Use MAX_PACKET_SIZE_EPBULK-1 below instead of MAX_PACKET_SIZE_EPBULK
+        // to work around a problem sending packets that are exactly MAX_PACKET_SIZE_EPBULK
+        // bytes in length. The problem is that these packets don't flush properly.
+        if (l > MAX_PACKET_SIZE_EPBULK-1)
+            l = MAX_PACKET_SIZE_EPBULK-1;
+        int i;
+        for (i = 0; i < l; i++) {
+            txbuf.dequeue(&b[i]);
+        }
+        send(b, l);
+        if (txbuf.available() == 0)
+            r = false;
+    } else {
+        r = false;
+    }
+    iprintf("USBSerial:EpIn Complete\n");
+    return r;
+}
+
+bool USBSerial::USBEvent_EPOut(uint8_t bEP, uint8_t bEPStatus)
+{
+    /*
+     * Called in ISR context
+     */
+
+    bool r = true;
+
+    iprintf("USBSerial:EpOut\n");
+    if (bEP != CDC_BulkOut.bEndpointAddress)
+        return false;
+
+    if (rxbuf.free() < MAX_PACKET_SIZE_EPBULK) {
+//         usb->endpointSetInterrupt(bEP, false);
+        return false;
+    }
+
+    uint8_t c[MAX_PACKET_SIZE_EPBULK];
+    uint32_t size = 64;
+
+    //we read the packet received and put it on the circular buffer
+    readEP(c, &size);
+    iprintf("Read %ld bytes:\n\t", size);
+    for (uint8_t i = 0; i < size; i++) {
+
+        // handle backspace and delete by deleting the last character in the buffer if there is one
+        if(c[i] == 0x08 || c[i] == 0x7F) {
+            if(!rxbuf.isEmpty()) rxbuf.pop();
+            continue;
+        }
+
+        if(c[i] == 'X' - 'A' + 1) { // ^X
+            //THEKERNEL->set_feed_hold(false); // required to free stuff up
+            halt_flag = true;
+            continue;
+        }
+
+        if(c[i] == '?') { // ?
+            query_flag = true;
+            continue;
+        }
+
+        if(THEKERNEL->is_grbl_mode()) {
+            if(c[i] == '!') { // safe pause
+                THEKERNEL->set_feed_hold(true);
+                continue;
+            }
+
+            if(c[i] == '~') { // safe resume
+                THEKERNEL->set_feed_hold(false);
+                continue;
+            }
+        }
+
+        last_char_was_dollar = (c[i] == '$');
+
+        if (flush_to_nl == false)
+            rxbuf.queue(c[i]);
+
+        // if (c[i] >= 32 && c[i] < 128)
+        // {
+        //     iprintf("%c", c[i]);
+        // }
+        // else
+        // {
+        //     iprintf("\\x%02X", c[i]);
+        // }
+
+        if (c[i] == '\n' || c[i] == '\r') {
+            if (flush_to_nl)
+                flush_to_nl = false;
+            else
+                nl_in_rx++;
+        } else if (rxbuf.isFull() && (nl_in_rx == 0)) {
+            // to avoid a deadlock with very long lines, we must dump the buffer
+            // and continue flushing to the next newline
+            rxbuf.flush();
+            flush_to_nl = true;
+        }
+    }
+    iprintf("\nQueued, %d empty\n", rxbuf.free());
+
+    if (rxbuf.free() < MAX_PACKET_SIZE_EPBULK) {
+        // if buffer is full, stall endpoint, do not accept more data
+        r = false;
+
+        if (nl_in_rx == 0) {
+            // we have to check for long line deadlock here too
+            flush_to_nl = true;
+            rxbuf.flush();
+
+            // and since our buffer is empty, we can accept more data
+            r = true;
+        }
+    }
+
+    usb->readStart(CDC_BulkOut.bEndpointAddress, MAX_PACKET_SIZE_EPBULK);
+    iprintf("USBSerial:EpOut Complete\n");
+    return r;
+}
+
+uint8_t USBSerial::available()
+{
+    return rxbuf.available();
+}
+
+bool USBSerial::ready()
+{
+    return rxbuf.available();
+}
+
+void USBSerial::on_module_loaded()
+{
+    this->register_for_event(ON_MAIN_LOOP);
+    this->register_for_event(ON_IDLE);
+}
+
+void USBSerial::on_idle(void *argument)
+{
+    if(halt_flag) {
+        halt_flag = false;
+        THEKERNEL->call_event(ON_HALT, nullptr);
+        if(THEKERNEL->is_grbl_mode()) {
+            puts("ALARM: Abort during cycle\r\n");
+        } else {
+            puts("HALTED, M999 or $X to exit HALT state\r\n");
+        }
+        rxbuf.flush(); // flush the recieve buffer, hopefully upstream has stopped sending
+        nl_in_rx = 0;
+    }
+
+    if(query_flag) {
+        query_flag = false;
+        puts(THEKERNEL->get_query_string().c_str());
+    }
+
+}
+
+void USBSerial::on_main_loop(void *argument)
+{
+    // apparently some OSes don't assert DTR when a program opens the port
+    if (available() && !attach)
+        attach = true;
+
+    if (attach != attached) {
+        if (attach) {
+            attached = true;
+            THEKERNEL->streams->append_stream(this);
+            puts("Smoothie\r\nok\r\n");
+        } else {
+            attached = false;
+            THEKERNEL->streams->remove_stream(this);
+            txbuf.flush();
+            rxbuf.flush();
+            nl_in_rx = 0;
+        }
+    }
+
+    // if we are in feed hold we do not process anything
+    //if(THEKERNEL->get_feed_hold()) return;
+
+    if (nl_in_rx) {
+        string received;
+        while (available()) {
+            char c = _getc();
+            if( c == '\n' || c == '\r') {
+                struct SerialMessage message;
+                message.message = received;
+                message.stream = this;
+                iprintf("USBSerial Received: %s\n", message.message.c_str());
+                THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
+                return;
+            } else {
+                received += c;
+            }
+        }
+    }
+}
+
+void USBSerial::on_attach()
+{
+    attach = true;
+}
+
+void USBSerial::on_detach()
+{
+    attach = false;
+}