From dd41d3358af107a961cf526ee6823aec4c300f8a Mon Sep 17 00:00:00 2001 From: Geekoid Date: Wed, 18 Dec 2019 21:26:27 +0100 Subject: [PATCH] Update de grbl avec un plus grand buffer et un peu de doc en plus --- Grbl_Esp32-master/.gitattributes | 2 + .../.github/ISSUE_TEMPLATE/bug-report.md | 22 + .../.github/ISSUE_TEMPLATE/feature_request.md | 16 + .../ISSUE_TEMPLATE/general-discussion.md | 10 + .../problem-compiling-firmware.md | 22 + Grbl_Esp32-master/.gitignore | 6 + Grbl_Esp32-master/.travis.yml | 48 + Grbl_Esp32-master/Grbl_Esp32/BTconfig.cpp | 198 + Grbl_Esp32-master/Grbl_Esp32/BTconfig.h | 65 + Grbl_Esp32-master/Grbl_Esp32/Grbl_Esp32.ino | 131 + Grbl_Esp32-master/Grbl_Esp32/atari_1020.cpp | 324 + Grbl_Esp32-master/Grbl_Esp32/atari_1020.h | 173 + Grbl_Esp32-master/Grbl_Esp32/commands.cpp | 2219 ++++++ Grbl_Esp32-master/Grbl_Esp32/commands.h | 58 + Grbl_Esp32-master/Grbl_Esp32/config.h | 722 ++ .../Grbl_Esp32/coolant_control.cpp | 140 + .../Grbl_Esp32/coolant_control.h | 50 + Grbl_Esp32-master/Grbl_Esp32/cpu_map.h | 1130 +++ Grbl_Esp32-master/Grbl_Esp32/data/favicon.ico | Bin 0 -> 1150 bytes .../Grbl_Esp32/data/index.html.gz | Bin 0 -> 128570 bytes Grbl_Esp32-master/Grbl_Esp32/defaults.h | 320 + Grbl_Esp32-master/Grbl_Esp32/espresponse.cpp | 112 + Grbl_Esp32-master/Grbl_Esp32/espresponse.h | 50 + Grbl_Esp32-master/Grbl_Esp32/gcode.cpp | 1460 ++++ Grbl_Esp32-master/Grbl_Esp32/gcode.h | 257 + Grbl_Esp32-master/Grbl_Esp32/grbl.h | 95 + Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.cpp | 43 + Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.h | 31 + Grbl_Esp32-master/Grbl_Esp32/grbl_limits.cpp | 496 ++ Grbl_Esp32-master/Grbl_Esp32/grbl_limits.h | 57 + Grbl_Esp32-master/Grbl_Esp32/grbl_sd.cpp | 233 + Grbl_Esp32-master/Grbl_Esp32/grbl_sd.h | 53 + .../Grbl_Esp32/grbl_trinamic.cpp | 231 + Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.h | 32 + .../Grbl_Esp32/grbl_unipolar.cpp | 236 + Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.h | 54 + Grbl_Esp32-master/Grbl_Esp32/inputbuffer.cpp | 108 + Grbl_Esp32-master/Grbl_Esp32/inputbuffer.h | 71 + Grbl_Esp32-master/Grbl_Esp32/jog.cpp | 53 + Grbl_Esp32-master/Grbl_Esp32/jog.h | 35 + .../Grbl_Esp32/motion_control.cpp | 483 ++ Grbl_Esp32-master/Grbl_Esp32/motion_control.h | 74 + Grbl_Esp32-master/Grbl_Esp32/nofile.h | 452 ++ .../Grbl_Esp32/notifications_service.cpp | 411 ++ .../Grbl_Esp32/notifications_service.h | 57 + Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.cpp | 189 + Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.h | 99 + Grbl_Esp32-master/Grbl_Esp32/planner.cpp | 511 ++ Grbl_Esp32-master/Grbl_Esp32/planner.h | 152 + .../Grbl_Esp32/polar_coaster.cpp | 299 + Grbl_Esp32-master/Grbl_Esp32/polar_coaster.h | 158 + Grbl_Esp32-master/Grbl_Esp32/print.cpp | 149 + Grbl_Esp32-master/Grbl_Esp32/print.h | 54 + Grbl_Esp32-master/Grbl_Esp32/probe.cpp | 78 + Grbl_Esp32-master/Grbl_Esp32/probe.h | 46 + Grbl_Esp32-master/Grbl_Esp32/protocol.cpp | 849 +++ Grbl_Esp32-master/Grbl_Esp32/protocol.h | 56 + Grbl_Esp32-master/Grbl_Esp32/report.cpp | 874 +++ Grbl_Esp32-master/Grbl_Esp32/report.h | 167 + Grbl_Esp32-master/Grbl_Esp32/serial.cpp | 348 + Grbl_Esp32-master/Grbl_Esp32/serial.h | 57 + .../Grbl_Esp32/serial2socket.cpp | 181 + Grbl_Esp32-master/Grbl_Esp32/serial2socket.h | 81 + Grbl_Esp32-master/Grbl_Esp32/servo_axis.cpp | 362 + Grbl_Esp32-master/Grbl_Esp32/servo_axis.h | 133 + Grbl_Esp32-master/Grbl_Esp32/servo_pen.cpp | 176 + Grbl_Esp32-master/Grbl_Esp32/servo_pen.h | 76 + Grbl_Esp32-master/Grbl_Esp32/settings.cpp | 452 ++ Grbl_Esp32-master/Grbl_Esp32/settings.h | 159 + Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.cpp | 123 + Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.h | 53 + .../Grbl_Esp32/spindle_control.cpp | 257 + .../Grbl_Esp32/spindle_control.h | 47 + Grbl_Esp32-master/Grbl_Esp32/stepper.cpp | 1540 ++++ Grbl_Esp32-master/Grbl_Esp32/stepper.h | 131 + Grbl_Esp32-master/Grbl_Esp32/system.cpp | 614 ++ Grbl_Esp32-master/Grbl_Esp32/system.h | 231 + Grbl_Esp32-master/Grbl_Esp32/tdef.h | 7 + .../Grbl_Esp32/telnet_server.cpp | 239 + Grbl_Esp32-master/Grbl_Esp32/telnet_server.h | 68 + .../Grbl_Esp32/tests/parsetest.nc | 6 + Grbl_Esp32-master/Grbl_Esp32/web_server.cpp | 1889 +++++ Grbl_Esp32-master/Grbl_Esp32/web_server.h | 97 + Grbl_Esp32-master/Grbl_Esp32/wificonfig.cpp | 557 ++ Grbl_Esp32-master/Grbl_Esp32/wificonfig.h | 129 + Grbl_Esp32-master/Grbl_Esp32/wifiservices.cpp | 173 + Grbl_Esp32-master/Grbl_Esp32/wifiservices.h | 39 + Grbl_Esp32-master/LICENSE | 674 ++ Grbl_Esp32-master/README.md | 56 + Grbl_Esp32-master/command.sh | 19 + Grbl_Esp32-master/doc/Commands.txt | 153 + .../doc/csv/alarm_codes_en_US.csv | 10 + .../doc/csv/build_option_codes_en_US.csv | 22 + .../doc/csv/error_codes_en_US.csv | 44 + .../doc/csv/setting_codes_en_US.csv | 46 + .../doc/script/fit_nonlinear_spindle.py | 373 + .../doc/script/piecewise_example.png | Bin 0 -> 38812 bytes Grbl_Esp32-master/doc/script/simple_stream.py | 66 + Grbl_Esp32-master/doc/script/stream.py | 202 + Grbl_Esp32-master/embedded/build.bat | 16 + Grbl_Esp32-master/embedded/embedded.h | 427 ++ Grbl_Esp32-master/embedded/footer.txt | 1 + Grbl_Esp32-master/embedded/gulpfile.js | 125 + Grbl_Esp32-master/embedded/header.txt | 24 + Grbl_Esp32-master/embedded/package-lock.json | 6263 +++++++++++++++++ Grbl_Esp32-master/embedded/package.json | 29 + Grbl_Esp32-master/embedded/tool.html.gz | Bin 0 -> 6728 bytes Grbl_Esp32-master/embedded/www/css/style.css | 105 + Grbl_Esp32-master/embedded/www/js/script.js | 505 ++ Grbl_Esp32-master/embedded/www/tool.html | 142 + .../libraries/ESP32SSDP/ESP32SSDP.cpp | 464 ++ .../libraries/ESP32SSDP/ESP32SSDP.h | 128 + .../libraries/ESP32SSDP/README.rst | 22 + .../ESP32SSDP/examples/SSDP/SSDP.ino | 52 + .../libraries/ESP32SSDP/keywords.txt | 53 + .../libraries/ESP32SSDP/library.properties | 9 + .../libraries/arduinoWebSockets/.gitignore | 29 + .../libraries/arduinoWebSockets/.travis.yml | 40 + .../libraries/arduinoWebSockets/LICENSE | 502 ++ .../libraries/arduinoWebSockets/README.md | 98 + .../Nginx/esp8266.ssl.reverse.proxy.conf | 83 + .../WebSocketClientAVR/WebSocketClientAVR.ino | 84 + .../esp32/WebSocketClient/WebSocketClient.ino | 110 + .../WebSocketClientSSL/WebSocketClientSSL.ino | 106 + .../esp32/WebSocketServer/WebSocketServer.ino | 104 + .../WebSocketClient/WebSocketClient.ino | 92 + .../WebSocketClientSSL/WebSocketClientSSL.ino | 88 + .../WebSocketClientSocketIO.ino | 113 + .../WebSocketClientStomp.ino | 149 + .../WebSocketClientStompOverSockJs.ino | 150 + .../WebSocketServer/WebSocketServer.ino | 86 + .../WebSocketServerAllFunctionsDemo.ino | 132 + .../WebSocketServerFragmentation.ino | 94 + .../WebSocketServerHttpHeaderValidation.ino | 86 + .../WebSocketServer_LEDcontrol.ino | 121 + .../ParticleWebSocketClient/application.cpp | 46 + .../libraries/arduinoWebSockets/library.json | 25 + .../arduinoWebSockets/library.properties | 9 + .../arduinoWebSockets/src/WebSockets.cpp | 655 ++ .../arduinoWebSockets/src/WebSockets.h | 311 + .../src/WebSocketsClient.cpp | 762 ++ .../arduinoWebSockets/src/WebSocketsClient.h | 136 + .../src/WebSocketsServer.cpp | 873 +++ .../arduinoWebSockets/src/WebSocketsServer.h | 212 + .../arduinoWebSockets/src/libb64/AUTHORS | 7 + .../arduinoWebSockets/src/libb64/LICENSE | 29 + .../arduinoWebSockets/src/libb64/cdecode.c | 98 + .../src/libb64/cdecode_inc.h | 28 + .../arduinoWebSockets/src/libb64/cencode.c | 119 + .../src/libb64/cencode_inc.h | 31 + .../arduinoWebSockets/src/libsha1/libsha1.c | 202 + .../arduinoWebSockets/src/libsha1/libsha1.h | 21 + .../arduinoWebSockets/tests/webSocket.html | 49 + .../tests/webSocketServer/index.js | 57 + .../tests/webSocketServer/package.json | 27 + .../arduinoWebSockets/travis/common.sh | 53 + Grbl_Esp32-master/platformio.ini | 29 + Ressource/README.md | 85 +- .../{configGRBL_Probe.txt => configGRBL.txt} | 33 +- 159 files changed, 38922 insertions(+), 18 deletions(-) create mode 100644 Grbl_Esp32-master/.gitattributes create mode 100644 Grbl_Esp32-master/.github/ISSUE_TEMPLATE/bug-report.md create mode 100644 Grbl_Esp32-master/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 Grbl_Esp32-master/.github/ISSUE_TEMPLATE/general-discussion.md create mode 100644 Grbl_Esp32-master/.github/ISSUE_TEMPLATE/problem-compiling-firmware.md create mode 100644 Grbl_Esp32-master/.gitignore create mode 100644 Grbl_Esp32-master/.travis.yml create mode 100644 Grbl_Esp32-master/Grbl_Esp32/BTconfig.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/BTconfig.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/Grbl_Esp32.ino create mode 100644 Grbl_Esp32-master/Grbl_Esp32/atari_1020.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/atari_1020.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/commands.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/commands.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/config.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/coolant_control.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/coolant_control.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/cpu_map.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/data/favicon.ico create mode 100644 Grbl_Esp32-master/Grbl_Esp32/data/index.html.gz create mode 100644 Grbl_Esp32-master/Grbl_Esp32/defaults.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/espresponse.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/espresponse.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/gcode.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/gcode.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_limits.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_limits.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_sd.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_sd.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/inputbuffer.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/inputbuffer.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/jog.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/jog.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/motion_control.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/motion_control.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/nofile.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/notifications_service.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/notifications_service.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/planner.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/planner.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/polar_coaster.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/polar_coaster.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/print.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/print.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/probe.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/probe.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/protocol.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/protocol.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/report.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/report.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/serial.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/serial.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/serial2socket.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/serial2socket.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/servo_axis.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/servo_axis.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/servo_pen.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/servo_pen.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/settings.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/settings.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/spindle_control.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/spindle_control.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/stepper.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/stepper.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/system.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/system.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/tdef.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/telnet_server.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/telnet_server.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/tests/parsetest.nc create mode 100644 Grbl_Esp32-master/Grbl_Esp32/web_server.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/web_server.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/wificonfig.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/wificonfig.h create mode 100644 Grbl_Esp32-master/Grbl_Esp32/wifiservices.cpp create mode 100644 Grbl_Esp32-master/Grbl_Esp32/wifiservices.h create mode 100644 Grbl_Esp32-master/LICENSE create mode 100644 Grbl_Esp32-master/README.md create mode 100644 Grbl_Esp32-master/command.sh create mode 100644 Grbl_Esp32-master/doc/Commands.txt create mode 100644 Grbl_Esp32-master/doc/csv/alarm_codes_en_US.csv create mode 100644 Grbl_Esp32-master/doc/csv/build_option_codes_en_US.csv create mode 100644 Grbl_Esp32-master/doc/csv/error_codes_en_US.csv create mode 100644 Grbl_Esp32-master/doc/csv/setting_codes_en_US.csv create mode 100644 Grbl_Esp32-master/doc/script/fit_nonlinear_spindle.py create mode 100644 Grbl_Esp32-master/doc/script/piecewise_example.png create mode 100644 Grbl_Esp32-master/doc/script/simple_stream.py create mode 100644 Grbl_Esp32-master/doc/script/stream.py create mode 100644 Grbl_Esp32-master/embedded/build.bat create mode 100644 Grbl_Esp32-master/embedded/embedded.h create mode 100644 Grbl_Esp32-master/embedded/footer.txt create mode 100644 Grbl_Esp32-master/embedded/gulpfile.js create mode 100644 Grbl_Esp32-master/embedded/header.txt create mode 100644 Grbl_Esp32-master/embedded/package-lock.json create mode 100644 Grbl_Esp32-master/embedded/package.json create mode 100644 Grbl_Esp32-master/embedded/tool.html.gz create mode 100644 Grbl_Esp32-master/embedded/www/css/style.css create mode 100644 Grbl_Esp32-master/embedded/www/js/script.js create mode 100644 Grbl_Esp32-master/embedded/www/tool.html create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.cpp create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.h create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/README.rst create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/examples/SSDP/SSDP.ino create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/keywords.txt create mode 100644 Grbl_Esp32-master/libraries/ESP32SSDP/library.properties create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/.gitignore create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/.travis.yml create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/LICENSE create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/README.md create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/examples/particle/ParticleWebSocketClient/application.cpp create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/library.json create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/library.properties create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.cpp create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.cpp create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.cpp create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/AUTHORS create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/LICENSE create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode.c create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode_inc.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode.c create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode_inc.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.c create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.h create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocket.html create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/index.js create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/package.json create mode 100644 Grbl_Esp32-master/libraries/arduinoWebSockets/travis/common.sh create mode 100644 Grbl_Esp32-master/platformio.ini rename Ressource/{configGRBL_Probe.txt => configGRBL.txt} (89%) diff --git a/Grbl_Esp32-master/.gitattributes b/Grbl_Esp32-master/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/Grbl_Esp32-master/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/bug-report.md b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..a0a6a4b --- /dev/null +++ b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,22 @@ +--- +name: Bug Report +about: Use this template to report bugs. +title: '' +labels: bug +assignees: '' + +--- + +Please answer the following questions. + +What version of the firmware are you using? + +Is the problem repeatable? + +Under what conditions does the bug occur? + +**Important** If you paste firmware code, please use [Markdown Code and Syntax Highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code) with language C++. Use the three back tick method. + +```C++ + #define EASIER_TO_READ true +``` diff --git a/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/feature_request.md b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..44b5308 --- /dev/null +++ b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Please describe the feature you would like implemented** + +**Why do you think this would improve Grbl_ESP32?** + +**What do you need the feature for?** + +**Will this feature appear to a lot of users?** diff --git a/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/general-discussion.md b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/general-discussion.md new file mode 100644 index 0000000..6e1a488 --- /dev/null +++ b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/general-discussion.md @@ -0,0 +1,10 @@ +--- +name: General Discussion +about: A general topic of interest to the group +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/problem-compiling-firmware.md b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/problem-compiling-firmware.md new file mode 100644 index 0000000..0cb8a23 --- /dev/null +++ b/Grbl_Esp32-master/.github/ISSUE_TEMPLATE/problem-compiling-firmware.md @@ -0,0 +1,22 @@ +--- +name: Problem Compiling Firmware +about: Use this template to submit compiling issues +title: Problems Compiling Firmware +labels: help wanted +assignees: '' + +--- + +Please answer the following questions: + +Have you read the [wiki regarding how to compile](https://github.com/bdring/Grbl_Esp32/wiki/Compiling-the-firmware)? + +What version of the Arduino IDE are you using? + +What version (commit date) of the [Arduino core for the ESP32](https://github.com/espressif/arduino-esp32) are you using? + +Are you using the master branch of Grbl_ESP32? + +Have you made any edits or configuration changes (list them) to the firmware? + +Please paste the compiler error text here: diff --git a/Grbl_Esp32-master/.gitignore b/Grbl_Esp32-master/.gitignore new file mode 100644 index 0000000..0bea06f --- /dev/null +++ b/Grbl_Esp32-master/.gitignore @@ -0,0 +1,6 @@ +.pioenvs/ +Thumbs.db +.DS_Store +*.orig +embedded/node_modules +embedded/dist diff --git a/Grbl_Esp32-master/.travis.yml b/Grbl_Esp32-master/.travis.yml new file mode 100644 index 0000000..2e8c816 --- /dev/null +++ b/Grbl_Esp32-master/.travis.yml @@ -0,0 +1,48 @@ +sudo: false + +language: bash + +os: + - linux + +before_script: + - "export DISPLAY=:99.0" + - sleep 3 # give xvfb some time to start + - wget http://downloads.arduino.cc/arduino-1.8.5-linux64.tar.xz + - tar xf arduino-1.8.5-linux64.tar.xz + - mv arduino-1.8.5 $HOME/arduino_ide + - cd $HOME/arduino_ide/hardware + - mkdir esp32 + - cd esp32 + - git clone https://github.com/espressif/arduino-esp32.git esp32 + - cd esp32/tools + - python get.py + - cd .. + - mv $TRAVIS_BUILD_DIR/libraries/ESP32SSDP $HOME/arduino_ide/libraries/ + - mv $TRAVIS_BUILD_DIR/libraries/arduinoWebSockets $HOME/arduino_ide/libraries/ + +script: + - cd $TRAVIS_BUILD_DIR + - source command.sh + - export PATH="$HOME/arduino_ide:$PATH" + - arduino --board esp32:esp32:esp32:PartitionScheme=min_spiffs,FlashFreq=40 --pref compiler.warning_level=all --save-prefs + - sed -n '48,72p;73q' $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -i "s/\/\/#define ENABLE_BLUETOOTH/#define ENABLE_BLUETOOTH/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -i "s/#define ENABLE_BLUETOOTH/\/\/#define ENABLE_BLUETOOTH/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -i "s/\/\/#define ENABLE_WIFI/#define ENABLE_WIFI/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -n '48,72p;73q' $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - build_sketch $TRAVIS_BUILD_DIR/Grbl_Esp32/Grbl_Esp32.ino + - sed -i "s/\/\/#define ENABLE_BLUETOOTH/#define ENABLE_BLUETOOTH/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -i "s/#define ENABLE_WIFI/\/\/#define ENABLE_WIFI/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -n '48,72p;73q' $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - build_sketch $TRAVIS_BUILD_DIR/Grbl_Esp32/Grbl_Esp32.ino + - sed -i "s/\/\/#define ENABLE_BLUETOOTH/#define ENABLE_BLUETOOTH/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -i "s/\/\/#define ENABLE_WIFI/#define ENABLE_WIFI/g" $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - sed -n '48,72p;73q' $TRAVIS_BUILD_DIR/Grbl_Esp32/config.h + - build_sketch $TRAVIS_BUILD_DIR/Grbl_Esp32/Grbl_Esp32.ino + + +notifications: + email: + on_success: change + on_failure: change diff --git a/Grbl_Esp32-master/Grbl_Esp32/BTconfig.cpp b/Grbl_Esp32-master/Grbl_Esp32/BTconfig.cpp new file mode 100644 index 0000000..46129e9 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/BTconfig.cpp @@ -0,0 +1,198 @@ +/* + BTconfig.cpp - Bluetooth functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" + +#ifdef ENABLE_BLUETOOTH +#include +#include "BluetoothSerial.h" +#include "BTconfig.h" +#include "commands.h" +#include "report.h" + +BTConfig bt_config; +BluetoothSerial SerialBT; +#ifdef __cplusplus +extern "C" { +#endif +const uint8_t *esp_bt_dev_get_address(void); +#ifdef __cplusplus +} +#endif + +String BTConfig::_btname = ""; +String BTConfig::_btclient = ""; + +BTConfig::BTConfig(){ +} + +BTConfig::~BTConfig(){ + end(); +} + +static void my_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + switch (event) + { + case ESP_SPP_SRV_OPEN_EVT://Server connection open + { + char str[18]; + str[17]='\0'; + uint8_t * addr = param->srv_open.rem_bda; + sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + BTConfig::_btclient = str; + grbl_sendf(CLIENT_ALL,"[MSG:BT Connected with %s]\r\n", str); + } + break; + + case ESP_SPP_CLOSE_EVT://Client connection closed + grbl_send(CLIENT_ALL,"[MSG:BT Disconnected]\r\n"); + BTConfig::_btclient=""; + break; + default: + break; + } +} + +const char *BTConfig::info(){ + static String result; + String tmp; + result = "[MSG:"; + if(Is_BT_on()) { + result += "Mode=BT:Name="; + result += _btname; + result += "("; + result += device_address(); + result += "):Status="; + if (SerialBT.hasClient()){ + result += "Connected with " + _btclient; + } else result += "Not connected"; + } + else result+="No BT"; + result+= "]\r\n"; + return result.c_str(); +} +/** + * Check if BlueTooth string is valid + */ + +bool BTConfig::isBTnameValid (const char * hostname){ + //limited size + char c; + if (strlen (hostname) > MAX_BTNAME_LENGTH || strlen (hostname) < MIN_BTNAME_LENGTH) { + return false; + } + //only letter and digit + for (int i = 0; i < strlen (hostname); i++) { + c = hostname[i]; + if (! (isdigit (c) || isalpha (c) || c == '_') ) { + return false; + } + } + return true; +} + +const char* BTConfig::device_address(){ + const uint8_t* point = esp_bt_dev_get_address(); + static char str[18]; + str[17]='\0'; + sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", (int)point[0], (int)point[1], (int)point[2], (int)point[3], (int)point[4], (int)point[5]); + return str; +} + +/** + * begin WiFi setup + */ +void BTConfig::begin() { + Preferences prefs; + //stop active services + end(); + prefs.begin(NAMESPACE, true); + //Get hostname + String defV = DEFAULT_BT_NAME; + _btname = prefs.getString(BT_NAME_ENTRY, defV); + int8_t wifiMode = prefs.getChar(ESP_RADIO_MODE, DEFAULT_RADIO_MODE); + prefs.end(); + if (wifiMode == ESP_BT) { + if (!SerialBT.begin(_btname)) + { + report_status_message(STATUS_BT_FAIL_BEGIN, CLIENT_ALL); + } else { + SerialBT.register_callback(&my_spp_cb); + grbl_sendf(CLIENT_ALL,"[MSG:BT Started with %s]\r\n", _btname.c_str()); + } + + }else end(); + +} + +/** + * End WiFi + */ +void BTConfig::end() { + SerialBT.end(); +} + +/** + * Reset ESP + */ +void BTConfig::reset_settings(){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + String sval; + int8_t bbuf; + bool error = false; + sval = DEFAULT_BT_NAME; + if (prefs.putString(BT_NAME_ENTRY, sval) == 0){ + error = true; + } + bbuf = DEFAULT_RADIO_MODE; + if (prefs.putChar(ESP_RADIO_MODE, bbuf) ==0 ) { + error = true; + } + prefs.end(); + if (error) { + grbl_send(CLIENT_ALL,"[MSG:BT reset error]\r\n"); + } else { + grbl_send(CLIENT_ALL,"[MSG:BT reset done]\r\n"); + } +} + +/** + * Check if BT is on and working + */ +bool BTConfig::Is_BT_on(){ + return btStarted(); +} + +/** + * Handle not critical actions that must be done in sync environement + */ +void BTConfig::handle() { + //If needed + COMMANDS::wait(0); +} + + +#endif // ENABLE_BLUETOOTH + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/BTconfig.h b/Grbl_Esp32-master/Grbl_Esp32/BTconfig.h new file mode 100644 index 0000000..fd1cc50 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/BTconfig.h @@ -0,0 +1,65 @@ +/* + BTconfig.h - Bluetooth functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED) + #error Bluetooth is not enabled! Please run `make menuconfig` to and enable it +#endif + +//Preferences entries +#define BT_NAME_ENTRY "BT_NAME" + +//defaults values +#define DEFAULT_BT_NAME "btgrblesp" + + +//boundaries +#define MAX_BTNAME_LENGTH 32 +#define MIN_BTNAME_LENGTH 1 + +#define BT_EVENT_DISCONNECTED 0 +#define BT_EVENT_CONNECTED 1 + + +#ifndef _BT_CONFIG_H +#define _BT_CONFIG_H +#include "BluetoothSerial.h" +extern BluetoothSerial SerialBT; + +class BTConfig { +public: + BTConfig(); + ~BTConfig(); + static const char *info(); + static void BTEvent(uint8_t event); + static bool isBTnameValid (const char * hostname); + static String BTname(){return _btname;} + static const char* device_address(); + static void begin(); + static void end(); + static void handle(); + static void reset_settings(); + static bool Is_BT_on(); + static String _btclient; + private : + static String _btname; +}; + +extern BTConfig bt_config; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/Grbl_Esp32.ino b/Grbl_Esp32-master/Grbl_Esp32/Grbl_Esp32.ino new file mode 100644 index 0000000..1d4ee71 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/Grbl_Esp32.ino @@ -0,0 +1,131 @@ +/* + Grbl_ESP32.ino - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +// Declare system global variable structure +system_t sys; +int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. +int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. +volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. +volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. +volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. +volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. +#ifdef DEBUG + volatile uint8_t sys_rt_exec_debug; +#endif + + + +void setup() { + + serial_init(); // Setup serial baud rate and interrupts + settings_init(); // Load Grbl settings from EEPROM + + stepper_init(); // Configure stepper pins and interrupt timers + system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files) + + memset(sys_position,0,sizeof(sys_position)); // Clear machine position. + + #ifdef USE_PEN_SERVO + servo_init(); + #endif + + #ifdef USE_SERVO_AXES + init_servos(); + #endif + + #ifdef USE_PEN_SOLENOID + solenoid_init(); + #endif + + #ifdef USE_MACHINE_INIT + machine_init(); // user supplied function for special initialization + #endif + + // Initialize system state. + #ifdef FORCE_INITIALIZATION_ALARM + // Force Grbl into an ALARM state upon a power-cycle or hard reset. + sys.state = STATE_ALARM; + #else + sys.state = STATE_IDLE; + #endif + + // Check for power-up and set system alarm if homing is enabled to force homing cycle + // by setting Grbl's alarm state. Alarm locks out all g-code commands, including the + // startup scripts, but allows access to settings and internal commands. Only a homing + // cycle '$H' or kill alarm locks '$X' will disable the alarm. + // NOTE: The startup script will run after successful completion of the homing cycle, but + // not after disabling the alarm locks. Prevents motion startup blocks from crashing into + // things uncontrollably. Very bad. + #ifdef HOMING_INIT_LOCK + if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { sys.state = STATE_ALARM; } + #endif +#ifdef ENABLE_WIFI + wifi_config.begin(); +#endif +#ifdef ENABLE_BLUETOOTH + bt_config.begin(); +#endif + inputBuffer.begin(); +} + +void loop() { + + // Reset system variables. + uint8_t prior_state = sys.state; + memset(&sys, 0, sizeof(system_t)); // Clear system struct variable. + sys.state = prior_state; + sys.f_override = DEFAULT_FEED_OVERRIDE; // Set to 100% + sys.r_override = DEFAULT_RAPID_OVERRIDE; // Set to 100% + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; // Set to 100% + memset(sys_probe_position,0,sizeof(sys_probe_position)); // Clear probe position. + sys_probe_state = 0; + sys_rt_exec_state = 0; + sys_rt_exec_alarm = 0; + sys_rt_exec_motion_override = 0; + sys_rt_exec_accessory_override = 0; + + // Reset Grbl primary systems. + serial_reset_read_buffer(CLIENT_ALL); // Clear serial read buffer + + gc_init(); // Set g-code parser to default state + + spindle_init(); + coolant_init(); + limits_init(); + probe_init(); + + plan_reset(); // Clear block buffer and planner variables + st_reset(); // Clear stepper subsystem variables + // Sync cleared gcode and planner positions to current system position. + plan_sync_position(); + gc_sync_position(); + + + + // put your main code here, to run repeatedly: + report_init_message(CLIENT_ALL); + + // Start Grbl main loop. Processes program inputs and executes them. + protocol_main_loop(); + +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/atari_1020.cpp b/Grbl_Esp32-master/Grbl_Esp32/atari_1020.cpp new file mode 100644 index 0000000..f26741e --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/atari_1020.cpp @@ -0,0 +1,324 @@ +/* + atari_1020.cpp + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + -------------------------------------------------------------- + + This contains all the special features required to control an + Atari 1010 Pen Plotter +*/ +#include "grbl.h" + +#ifdef ATARI_1020 + +#define HOMING_PHASE_FULL_APPROACH 0 // move to right end +#define HOMING_PHASE_CHECK 1 // check reed switch +#define HOMING_PHASE_RETRACT 2 // retract +#define HOMING_PHASE_SHORT_APPROACH 3 // retract + +static TaskHandle_t solenoidSyncTaskHandle = 0; +static TaskHandle_t atariHomingTaskHandle = 0; +uint16_t solenoid_pull_count; +bool atari_homing = false; +uint8_t homing_phase = HOMING_PHASE_FULL_APPROACH; +uint8_t current_tool; + +void machine_init() +{ + solenoid_pull_count = 0; // initialize + + grbl_send(CLIENT_SERIAL, "[MSG:Atari 1020 Solenoid]\r\n"); + + // setup PWM channel + ledcSetup(SOLENOID_CHANNEL_NUM, SOLENOID_PWM_FREQ, SOLENOID_PWM_RES_BITS); + ledcAttachPin(SOLENOID_PEN_PIN, SOLENOID_CHANNEL_NUM); + + pinMode(SOLENOID_DIRECTION_PIN, OUTPUT); // this sets the direction of the solenoid current + pinMode(REED_SW_PIN, INPUT_PULLUP); // external pullup required + + // setup a task that will calculate solenoid position + xTaskCreatePinnedToCore( solenoidSyncTask, // task + "solenoidSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &solenoidSyncTaskHandle, + 0 // core + ); + // setup a task that will do the custom homing sequence + xTaskCreatePinnedToCore( atari_home_task, // task + "atari_home_task", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &atariHomingTaskHandle, + 0 // core + ); +} + +// this task tracks the Z position and sets the solenoid +void solenoidSyncTask(void *pvParameters) +{ + int32_t current_position[N_AXIS]; // copy of current location + float m_pos[N_AXIS]; // machine position in mm + TickType_t xLastWakeTime; + const TickType_t xSolenoidFrequency = SOLENOID_TASK_FREQ; // in ticks (typically ms) + + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while(true) { // don't ever return from this or the task dies + + memcpy(current_position,sys_position,sizeof(sys_position)); // get current position in step + system_convert_array_steps_to_mpos(m_pos,current_position); // convert to millimeters + calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos + + vTaskDelayUntil(&xLastWakeTime, xSolenoidFrequency); + } +} + +// to do...have this return a true or false. This could be used by the normal homing feature to +// continue with regular homing after setup +// return true if this completes homing + +bool user_defined_homing() { + // create and start a task to do the special homing + homing_phase = HOMING_PHASE_FULL_APPROACH; + atari_homing = true; + return true; // this does it...skip the rest of mc_homing_cycle(...) +} + +/* + Do a custom homing routine. + + A task is used because it needs to wait until until idle after each move. + + 1) Do a full travel move to the right. OK to stall if the pen started closer + 2) Check for pen 1 + 3) If fail Retract + 4) move to right end + 5) Check... + ....repeat up to 12 times to try to find pen one + + TODO can the retract, move back be 1 phase rather than 2? + +*/ +void atari_home_task(void *pvParameters) { + uint8_t homing_attempt = 0; // how many times have we tried to home + TickType_t xLastWakeTime; + const TickType_t xHomingTaskFrequency = 100; // in ticks (typically ms) .... need to make sure there is enough time to get out of idle + char gcode_line[20]; + + while(true) { // this task will only last as long as it is homing + + if (atari_homing) { + // must be in idle or alarm state + if (sys.state == STATE_IDLE) { + switch(homing_phase) { + case HOMING_PHASE_FULL_APPROACH: // a full width move to insure it hits left end + inputBuffer.push("G90G0Z1\r"); // lift the pen + sprintf(gcode_line, "G91G0X%3.2f\r", -ATARI_PAPER_WIDTH + ATARI_HOME_POS - 3.0); // plus a little extra + inputBuffer.push(gcode_line); + homing_attempt = 1; + homing_phase = HOMING_PHASE_CHECK; + break; + case HOMING_PHASE_CHECK: // check the limits switch + if (digitalRead(REED_SW_PIN) == 0) { // see if reed switch is grounded + inputBuffer.push("G4P0.1\n"); // dramtic pause + + sys_position[X_AXIS] = ATARI_HOME_POS * settings.steps_per_mm[X_AXIS]; + sys_position[Y_AXIS] = 0.0; + sys_position[Z_AXIS] = 1.0 * settings.steps_per_mm[Y_AXIS]; + + gc_sync_position(); + plan_sync_position(); + + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls + inputBuffer.push(gcode_line); + + current_tool = 1; // local copy for reference...until actual M6 change + gc_state.tool = current_tool; + atari_homing = false; // done with homing sequence + } + else { + homing_phase = HOMING_PHASE_RETRACT; + homing_attempt++; + } + break; + case HOMING_PHASE_RETRACT: + sprintf(gcode_line, "G0X%3.2f\r", -ATARI_HOME_POS); + inputBuffer.push(gcode_line); + sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); + inputBuffer.push(gcode_line); + homing_phase = HOMING_PHASE_CHECK; + break; + default: + grbl_sendf(CLIENT_SERIAL, "[MSG:Homing phase error %d]\r\n", homing_phase); + atari_homing = false;; // kills task + break; + } + + if (homing_attempt > ATARI_HOMING_ATTEMPTS) { // try all positions plus 1 + grbl_send(CLIENT_SERIAL, "[MSG: Atari homing failed]\r\n"); + inputBuffer.push("G90\r"); + atari_homing = false;; + } + } + } + vTaskDelayUntil(&xLastWakeTime, xHomingTaskFrequency); + } +} + + +// calculate and set the PWM value for the servo +void calc_solenoid(float penZ) +{ + bool isPenUp; + static bool previousPenState = false; + uint32_t solenoid_pen_pulse_len; // duty cycle of solenoid + + isPenUp = ( (penZ > 0) || (sys.state == STATE_ALARM) ); // is pen above Z0 or is there an alarm + + // if the state has not change, we only count down to the pull time + if (previousPenState == isPenUp) { // if state is unchanged + if (solenoid_pull_count > 0) { + solenoid_pull_count--; + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // stay at full power while counting down + } + else { + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_HOLD; // pull in delay has expired so lower duty cycle + } + } + else { // pen direction has changed + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // go to full power + solenoid_pull_count = SOLENOID_PULL_DURATION; // set the time to count down + } + + previousPenState = isPenUp; // save the prev state + + digitalWrite(SOLENOID_DIRECTION_PIN, isPenUp); + + // skip setting value if it is unchanged + if (ledcRead(SOLENOID_CHANNEL_NUM) == solenoid_pen_pulse_len) + return; + + // update the PWM value + // ledcWrite appears to have issues with interrupts, so make this a critical section + portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&myMutex); + ledcWrite(SOLENOID_CHANNEL_NUM, solenoid_pen_pulse_len); + portEXIT_CRITICAL(&myMutex); +} + + +/* + A tool (pen) change is done by bumping the carriage against the right edge 3 times per + position change. Pen 1-4 is valid range. +*/ +void user_tool_change(uint8_t new_tool) { + uint8_t move_count; + char gcode_line[20]; + + protocol_buffer_synchronize(); // wait for all previous moves to complete + + if ((new_tool < 1) || (new_tool > MAX_PEN_NUMBER)) { + grbl_sendf(CLIENT_ALL, "[MSG: Requested Pen#%d is out of 1-4 range]\r\n", new_tool); + return; + } + + if (new_tool == current_tool) + return; + + if (new_tool > current_tool) { + move_count = BUMPS_PER_PEN_CHANGE * (new_tool - current_tool); + } + else { + move_count = BUMPS_PER_PEN_CHANGE * ((MAX_PEN_NUMBER - current_tool) + new_tool); + } + sprintf(gcode_line, "G0Z%3.2f\r", ATARI_TOOL_CHANGE_Z); // go to tool change height + inputBuffer.push(gcode_line); + for (uint8_t i = 0; i < move_count; i++) { + sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); // + inputBuffer.push(gcode_line); + inputBuffer.push("G0X0\r"); + } + + current_tool = new_tool; + + grbl_sendf(CLIENT_ALL, "[MSG: Change to Pen#%d]\r\n", current_tool); + +} + +// move from current tool to next tool.... +void atari_next_pen() { + if (current_tool < MAX_PEN_NUMBER) { + gc_state.tool = current_tool + 1; + } + else { + gc_state.tool = 1; + } + user_tool_change(gc_state.tool); +} + +// Polar coaster has macro buttons, this handles those button pushes. +void user_defined_macro(uint8_t index) +{ + char gcode_line[20]; + + switch (index) { + #ifdef MACRO_BUTTON_0_PIN + case CONTROL_PIN_INDEX_MACRO_0: + grbl_send(CLIENT_SERIAL, "[MSG: Pen Switch]\r\n"); + inputBuffer.push("$H\r"); + break; + #endif + + #ifdef MACRO_BUTTON_1_PIN + case CONTROL_PIN_INDEX_MACRO_1: + grbl_send(CLIENT_SERIAL, "[MSG: Color Switch]\r\n"); + atari_next_pen(); + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls + inputBuffer.push(gcode_line); + break; + #endif + + #ifdef MACRO_BUTTON_2_PIN + case CONTROL_PIN_INDEX_MACRO_2: + // feed out some paper and reset the Y 0 + grbl_send(CLIENT_SERIAL, "[MSG: Paper Switch]\r\n"); + inputBuffer.push("G0Y-25\r"); + inputBuffer.push("G4P0.1\r"); // sync...forces wait for planner to clear + sys_position[Y_AXIS] = 0.0; // reset the Y position + gc_sync_position(); + plan_sync_position(); + break; + #endif + + default: + grbl_sendf(CLIENT_SERIAL, "[MSG: Unknown Switch %d]\r\n", index); + break; + } +} + +void user_m30() { + char gcode_line[20]; + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // + inputBuffer.push(gcode_line); +} + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/atari_1020.h b/Grbl_Esp32-master/Grbl_Esp32/atari_1020.h new file mode 100644 index 0000000..94c0af9 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/atari_1020.h @@ -0,0 +1,173 @@ +/* + atari_1020.h + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + This contains all the special features required to control an + Atari 1010 Pen Plotter +*/ + +#define CPU_MAP_NAME "CPU_MAP_ATARI_1020" + +// ================== CPU MAP ====================== + #define USE_UNIPOLAR + + #define X_UNIPOLAR + #define X_PIN_PHASE_0 GPIO_NUM_13 + #define X_PIN_PHASE_1 GPIO_NUM_21 + #define X_PIN_PHASE_2 GPIO_NUM_16 + #define X_PIN_PHASE_3 GPIO_NUM_22 + + #define Y_UNIPOLAR + #define Y_PIN_PHASE_0 GPIO_NUM_25 + #define Y_PIN_PHASE_1 GPIO_NUM_27 + #define Y_PIN_PHASE_2 GPIO_NUM_26 + #define Y_PIN_PHASE_3 GPIO_NUM_32 + + + #define SOLENOID_DIRECTION_PIN GPIO_NUM_4 + #define SOLENOID_PEN_PIN GPIO_NUM_2 + #define SOLENOID_CHANNEL_NUM 6 + + #ifdef HOMING_CYCLE_0 + #undef HOMING_CYCLE_0 + #endif + #define HOMING_CYCLE_0 (1< +#include "report.h" +#ifdef ENABLE_SD_CARD +#include "grbl_sd.h" +#endif +#ifdef ENABLE_BLUETOOTH +#include "BTconfig.h" +#endif +#ifdef ENABLE_WIFI +#include "wificonfig.h" +#if defined (ENABLE_HTTP) +#include "web_server.h" +#endif +#ifdef ENABLE_TELNET +#include "telnet_server.h" +#endif +#endif +#ifdef ENABLE_NOTIFICATIONS +#include "notifications_service.h" +#endif +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +esp_err_t esp_task_wdt_reset(); +#ifdef __cplusplus +} +#endif + +bool COMMANDS::restart_ESP_module = false; + +/* + * delay is to avoid with asyncwebserver and may need to wait sometimes + */ +void COMMANDS::wait(uint32_t milliseconds){ + uint32_t timeout = millis(); + esp_task_wdt_reset(); //for a wait 0; + //wait feeding WDT + while ( (millis() - timeout) < milliseconds) { + esp_task_wdt_reset(); + } +} + +bool COMMANDS::execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level, ESPResponseStream *espresponse) +{ + bool response = true; + level_authenticate_type auth_type = auth_level; + if (!espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + + if (isadmin(cmd_params)) { + auth_type = LEVEL_ADMIN; + } + if (isuser (cmd_params) && (auth_type != LEVEL_ADMIN) ) { + auth_type = LEVEL_USER; + } +#endif + //manage parameters + String parameter; + switch (cmd) { +#ifdef ENABLE_WIFI + //STA SSID + //[ESP100][pwd=] + case 100: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_STA_SSID; + espresponse->println(prefs.getString(STA_SSID_ENTRY, defV).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + if (!WiFiConfig::isSSIDValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect SSID!"); + response = false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(STA_SSID_ENTRY, parameter) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; + //STA Password + //[ESP101][pwd=] + case 101: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if (!WiFiConfig::isPasswordValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect password!"); + response = false; + return false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(STA_PWD_ENTRY, parameter) != parameter.length()){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + break; + //Change STA IP mode (DHCP/STATIC) + //[ESP102]pwd= + case 102: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t resp = prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE); + if (resp == DHCP_MODE) espresponse->println("DHCP"); + else if (resp == STATIC_MODE) espresponse->println("STATIC"); + else espresponse->println("???"); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter.toUpperCase(); + if (!((parameter == "STATIC") || (parameter == "DHCP"))) { + if(espresponse)espresponse->println ("Error: only STATIC or DHCP mode supported!"); + response = false; + return false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + int8_t bbuf = (parameter == "DHCP")?DHCP_MODE:STATIC_MODE; + if (prefs.putChar(STA_IP_MODE_ENTRY, bbuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; + //Change STA IP/Mask/GW + //[ESP103]IP= MSK= GW= pwd= + case 103: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + //IP + String defV = DEFAULT_STA_IP; + int32_t IP = prefs.getInt(STA_IP_ENTRY, wifi_config.IP_int_from_string(defV)); + //GW + defV = DEFAULT_STA_GW; + int32_t GW = prefs.getInt(STA_GW_ENTRY, wifi_config.IP_int_from_string(defV)); + //MK + defV = DEFAULT_STA_MK; + int32_t MK = prefs.getInt(STA_MK_ENTRY, wifi_config.IP_int_from_string(defV)); + defV = "IP:" + wifi_config.IP_string_from_int(IP) + ", GW:" + wifi_config.IP_string_from_int(GW) + ", MSK:" + wifi_config.IP_string_from_int(MK); + espresponse->println(defV.c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + + String IP = get_param (cmd_params, "IP=", false); + String GW = get_param (cmd_params, "GW=", false); + String MSK = get_param (cmd_params, "MSK=", false); + Serial.println(IP); + Serial.println(GW); + if ( !WiFiConfig::isValidIP(IP.c_str())) { + if(espresponse)espresponse->println ("Error: Incorrect IP!"); + response = false; + return false; + } + if ( !WiFiConfig::isValidIP(GW.c_str())) { + if(espresponse)espresponse->println ("Error: Incorrect Gateway!"); + response = false; + return false; + } + if ( !WiFiConfig::isValidIP(MSK.c_str())) { + if(espresponse)espresponse->println ("Error: Incorrect Mask!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + if ((prefs.putInt(STA_IP_ENTRY, wifi_config.IP_int_from_string(IP)) == 0) || + (prefs.putInt(STA_GW_ENTRY, wifi_config.IP_int_from_string(GW)) == 0) || + (prefs.putInt(STA_MK_ENTRY, wifi_config.IP_int_from_string(MSK)) == 0)){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + break; + + //Change AP SSID + //[ESP105]pwd= + case 105: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_AP_SSID; + espresponse->println(prefs.getString(AP_SSID_ENTRY, defV).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + if (!WiFiConfig::isSSIDValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect SSID!"); + response = false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(AP_SSID_ENTRY, parameter) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + break; + //Change AP Password + //[ESP106]pwd= + case 106: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if (!WiFiConfig::isPasswordValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect password!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(AP_PWD_ENTRY, parameter) != parameter.length()){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + + } + break; + //Change AP IP + //[ESP107]pwd= + case 107: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + //IP + String defV = DEFAULT_AP_IP; + int32_t IP = prefs.getInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(defV)); + espresponse->println(wifi_config.IP_string_from_int(IP).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + if ( !WiFiConfig::isValidIP(parameter.c_str())) { + if(espresponse)espresponse->println ("Error: Incorrect IP!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(parameter)) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + break; + //Change AP channel + //[ESP108]pwd= + case 108: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t channel = prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL); + espresponse->println(String(channel).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + int8_t bbuf = parameter.toInt(); + if ((bbuf > MAX_CHANNEL) || (bbuf < MIN_CHANNEL)) { + if(espresponse)espresponse->println ("Error: Incorrect channel!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putChar(AP_CHANNEL_ENTRY, bbuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + break; +#endif +#if defined( ENABLE_WIFI) || defined( ENABLE_BLUETOOTH) + //Set radio state at boot which can be BT, STA, AP, OFF + //[ESP110]pwd= + case 110: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t wifiMode = prefs.getChar(ESP_RADIO_MODE, DEFAULT_RADIO_MODE); + if (wifiMode == ESP_RADIO_OFF) espresponse->println("OFF"); + else if (wifiMode == ESP_BT) espresponse->println("BT"); + else if (wifiMode == ESP_WIFI_AP) espresponse->println("AP"); + else if (wifiMode == ESP_WIFI_STA) espresponse->println("STA"); + else espresponse->println("??"); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter.toUpperCase(); + if (!( +#if defined( ENABLE_BLUETOOTH) + (parameter == "BT") || +#endif +#if defined( ENABLE_WIFI) + (parameter == "STA") || (parameter == "AP") || +#endif + (parameter == "OFF"))) { + + if(espresponse)espresponse->println ("Error: only " +#ifdef ENABLE_BLUETOOTH + "BT or " +#endif +#ifdef ENABLE_WIFI + "STA or AP or " +#endif + "OFF mode supported!"); + response = false; + return false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + int8_t bbuf = ESP_RADIO_OFF; +#ifdef ENABLE_WIFI + if(parameter == "STA")bbuf = ESP_WIFI_STA; + if(parameter == "AP")bbuf = ESP_WIFI_AP; +#endif +#ifdef ENABLE_BLUETOOTH + if(parameter == "BT")bbuf = ESP_BT; +#endif + if (prefs.putChar(ESP_RADIO_MODE, bbuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; +#endif +#ifdef ENABLE_WIFI + //Get current IP + //[ESP111]
+ case 111: + { + if (!espresponse) return false; + String currentIP = cmd_params; + if (WiFi.getMode() == WIFI_STA) { + currentIP += WiFi.localIP().toString(); + } else { + currentIP += WiFi.softAPIP().toString(); + } + espresponse->println (currentIP.c_str()); + } + break; + + //Get/Set hostname + //[ESP112] pwd= + case 112: { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //Get hostname + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_HOSTNAME; + espresponse->println(prefs.getString(HOSTNAME_ENTRY, defV).c_str()); + prefs.end(); + } else { //set host name +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + if (!wifi_config.isHostnameValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect hostname!"); + response = false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(HOSTNAME_ENTRY, parameter) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; +#endif +#if defined (ENABLE_WIFI) || defined (ENABLE_BLUETOOTH) + //Set immediate radio state which can be ON, OFF + //[ESP115]pwd= + case 115:{ +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + //get + if (parameter.length() == 0) { + bool on =false; +#if defined (ENABLE_WIFI) + if (WiFi.getMode() != WIFI_MODE_NULL)on = true; +#endif +#if defined (ENABLE_BLUETOOTH) + if (bt_config.Is_BT_on())on = true; +#endif + espresponse->println ((on)?"ON":"OFF"); + } else { + parameter.toUpperCase(); + if (!((parameter == "ON") || (parameter == "OFF"))) { + if(espresponse)espresponse->println ("Error: only ON or OFF mode supported!"); + return false; + } else { + //Stop everything +#if defined (ENABLE_WIFI) + if (WiFi.getMode() != WIFI_MODE_NULL)wifi_config.StopWiFi(); +#endif +#if defined (ENABLE_BLUETOOTH) + if (bt_config.Is_BT_on())bt_config.end(); +#endif + + //if On start proper service + if (parameter == "ON") { //On + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t wifiMode = prefs.getChar(ESP_RADIO_MODE, DEFAULT_RADIO_MODE); + prefs.end(); + if ((wifiMode == ESP_WIFI_AP) || (wifiMode == ESP_WIFI_STA)){ +#if defined (ENABLE_WIFI) + wifi_config.begin(); +#else + if(espresponse)espresponse->println ("Error: WiFi is not enabled!"); + return false; +#endif + } + else if (wifiMode == ESP_BT) { +#if defined (ENABLE_BLUETOOTH) + bt_config.begin(); +#else + if(espresponse)espresponse->println ("Error: Bluetooth is not enabled!"); + return false; +#endif + } else { + if(espresponse)espresponse->println ("[MSG: Radio is Off]"); + return false; + } + } else if(espresponse)espresponse->println ("[MSG: Radio is Off]"); + } + } + } + break; +#endif +#ifdef ENABLE_WIFI + //Set HTTP state which can be ON, OFF + //[ESP120]pwd= + case 120: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t Mode = prefs.getChar(HTTP_ENABLE_ENTRY, DEFAULT_HTTP_STATE); + espresponse->println((Mode == 0)?"OFF":"ON"); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter.toUpperCase(); + if (!((parameter == "ON") || (parameter == "OFF"))) { + if(espresponse)espresponse->println ("Error: only ON or OFF mode supported!"); + response = false; + return false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + int8_t bbuf = (parameter == "ON")?1:0; + if (prefs.putChar(HTTP_ENABLE_ENTRY, bbuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; + //Set HTTP port + //[ESP121]pwd= + case 121: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int port = prefs.getUShort(HTTP_PORT_ENTRY, DEFAULT_WEBSERVER_PORT); + espresponse->println(String(port).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + int ibuf = parameter.toInt(); + if ((ibuf > MAX_HTTP_PORT) || (ibuf < MIN_HTTP_PORT)) { + if(espresponse)espresponse->println ("Error: Incorrect port!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + + if (prefs.putUShort(HTTP_PORT_ENTRY, ibuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + + } + break; + //Set Telnet state which can be ON, OFF + //[ESP130]pwd= + case 130: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t Mode = prefs.getChar(TELNET_ENABLE_ENTRY, DEFAULT_TELNET_STATE); + espresponse->println((Mode == 0)?"OFF":"ON"); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter.toUpperCase(); + if (!((parameter == "ON") || (parameter == "OFF"))) { + if(espresponse)espresponse->println ("Error: only ON or OFF mode supported!"); + response = false; + return false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + int8_t bbuf = (parameter == "ON")?1:0; + if (prefs.putChar(TELNET_ENABLE_ENTRY, bbuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; + //Set Telnet port + //[ESP131]pwd= + case 131: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + int port = prefs.getUShort(TELNET_PORT_ENTRY, DEFAULT_TELNETSERVER_PORT); + espresponse->println(String(port).c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + int ibuf = parameter.toInt(); + if ((ibuf > MAX_TELNET_PORT) || (ibuf < MIN_TELNET_PORT)) { + if(espresponse)espresponse->println ("Error: Incorrect port!"); + response = false; + return false; + } + Preferences prefs; + prefs.begin(NAMESPACE, false); + + if (prefs.putUShort(TELNET_PORT_ENTRY, ibuf) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + + } + break; +#endif + +#ifdef ENABLE_BLUETOOTH + //Get/Set btname + //[ESP140]< Bluetooth name> pwd= + case 140: { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + if ((auth_type != LEVEL_ADMIN) && (parameter.length() > 0)) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + //Get btname + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_BT_NAME; + espresponse->println(prefs.getString(BT_NAME_ENTRY, defV).c_str()); + prefs.end(); + } else { //set BT name + if (!bt_config.isBTnameValid (parameter.c_str() ) ) { + if(espresponse)espresponse->println ("Error: Incorrect name!"); + response = false; + } else { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(BT_NAME_ENTRY, parameter) == 0){ + response = false; + if(espresponse)espresponse->println ("Error: Set failed!"); + } else if(espresponse)espresponse->println ("ok"); + prefs.end(); + } + } + } + break; +#endif + //Get SD Card Status + //[ESP200] + case 200: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + + } +#endif + if (!espresponse) return false; + String resp = "No SD card"; +#ifdef ENABLE_SD_CARD + + int8_t state = get_sd_state(true); + if (state == SDCARD_IDLE)resp="SD card detected"; + else if (state == SDCARD_NOT_PRESENT)resp="No SD card"; + else resp="Busy"; +#endif + espresponse->println (resp.c_str()); + } + break; +#ifdef ENABLE_SD_CARD + //Get SD Card Content + //[ESP210] + case 210: + { + if (!espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + + } +#endif + int8_t state = get_sd_state(true); + if (state == SDCARD_IDLE) { + listDir(SD, "/", 10, espresponse->client()); + String ssd = "[SD Free:" + ESPResponseStream::formatBytes(SD.totalBytes() - SD.usedBytes()); + ssd +=" Used:" + ESPResponseStream::formatBytes(SD.usedBytes()); + ssd +=" Total:" + ESPResponseStream::formatBytes(SD.totalBytes()); + ssd +="]"; + espresponse->println (""); + espresponse->println (ssd.c_str()); + } + else espresponse->println ((state == SDCARD_NOT_PRESENT) ? "No SD card" : "Busy"); + } + break; + + //Delete SD Card file / directory + //[ESP215]pwd= + case 215: + { + if (!espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + + } +#endif + parameter = get_param (cmd_params, "", true); + if (parameter.length() != 0) { + int8_t state = get_sd_state(true); + parameter.trim(); + if (parameter[0] != '/'){ + parameter = "/" + parameter; + } + if (state == SDCARD_IDLE) { + File file2del = SD.open(parameter.c_str()); + if (file2del) { + if (file2del.isDirectory()) { + if (!SD.rmdir((char *)parameter.c_str())) { + espresponse->println ("Error: Cannot delete directory! Is directory empty?"); + } else { + espresponse->println ("Directory deleted."); + } + } else { + if (!SD.remove((char *)parameter.c_str())) { + espresponse->println ("Error: Cannot delete file!"); + } else { + espresponse->println ("File deleted."); + } + } + } else { + espresponse->println ("Error: Cannot stat file!"); + } + file2del.close(); + } else { + espresponse->println ((state == SDCARD_NOT_PRESENT) ? "No SD card" : "Busy"); + } + } else { + espresponse->println ("Error: Missing file name!"); + } + } + break; + //print SD file + //[ESP220] + case 220: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if(espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if (parameter.length() == 0){ + if(espresponse)espresponse->println ("Error: Missing file name!"); + return false; + } + int8_t state = get_sd_state(true); + if (state != SDCARD_IDLE) { + espresponse->println ((state == SDCARD_NOT_PRESENT) ? "No SD card" : "Busy"); + return false; + } + if (sys.state != STATE_IDLE) { + if(espresponse)espresponse->println ("Busy"); + return false; + } + + if (!openFile(SD, parameter.c_str())){ + report_status_message(STATUS_SD_FAILED_READ, (espresponse)?espresponse->client(): CLIENT_ALL); + espresponse->println (""); + return false; + } + char fileLine[255]; + SD_client = (espresponse)?espresponse->client(): CLIENT_ALL; + if (!readFileLine(fileLine)) { + //No need notification here it is just a macro + closeFile(); + espresponse->println (""); + return false; + } else { + report_status_message(gc_execute_line(fileLine, (espresponse)?espresponse->client(): CLIENT_ALL), (espresponse)?espresponse->client(): CLIENT_ALL); // execute the first line + } + report_realtime_status( (espresponse)?espresponse->client(): CLIENT_ALL); + espresponse->println (""); + } + break; +#endif + //Get full ESP32 settings content + //[ESP400] + case 400: + { + String v; + String defV; + Preferences prefs; + if (!espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + + espresponse->print("{\"EEPROM\":["); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + prefs.begin(NAMESPACE, true); +#ifdef ENABLE_WIFI + int8_t vi; + //1 - Hostname + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HOSTNAME_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (wifi_config.Hostname().c_str()); + espresponse->print ("\",\"H\":\"Hostname\" ,\"S\":\""); + espresponse->print (String(MAX_HOSTNAME_LENGTH).c_str()); + espresponse->print ("\", \"M\":\""); + espresponse->print (String(MIN_HOSTNAME_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); +#ifdef ENABLE_HTTP + //2 - http protocol mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HTTP_ENABLE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(HTTP_ENABLE_ENTRY, 1); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"HTTP protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + //3 - http port + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HTTP_PORT_ENTRY); + espresponse->print ("\",\"T\":\"I\",\"V\":\""); + espresponse->print (String(web_server.port()).c_str()); + espresponse->print ("\",\"H\":\"HTTP Port\",\"S\":\""); + espresponse->print (String(MAX_HTTP_PORT).c_str()); + espresponse->print ("\",\"M\":\""); + espresponse->print (String(MIN_HTTP_PORT).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); +#endif + +#ifdef ENABLE_TELNET + //4 - telnet protocol mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (TELNET_ENABLE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(TELNET_ENABLE_ENTRY, DEFAULT_TELNET_STATE); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"Telnet protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + //5 - telnet Port + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (TELNET_PORT_ENTRY); + espresponse->print ("\",\"T\":\"I\",\"V\":\""); + espresponse->print (String(telnet_server.port()).c_str()); + espresponse->print ("\",\"H\":\"Telnet Port\",\"S\":\""); + espresponse->print (String(MAX_TELNET_PORT).c_str()); + espresponse->print ("\",\"M\":\""); + espresponse->print (String(MIN_TELNET_PORT).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); +#endif + //6 - radio mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (ESP_RADIO_MODE); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(ESP_RADIO_MODE, ESP_RADIO_OFF); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"Radio mode\",\"O\":[{\"None\":\"0\"}"); +#ifdef ENABLE_WIFI + espresponse->print (",{\"STA\":\"1\"},{\"AP\":\"2\"}"); +#endif +#ifdef ENABLE_BLUETOOTH + espresponse->print (",{\"BT\":\"3\"}"); +#endif + espresponse->print ("]}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //7 - STA SSID + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_SSID_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_STA_SSID; + espresponse->print (prefs.getString(STA_SSID_ENTRY, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_SSID_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Station SSID\",\"M\":\""); + espresponse->print (String(MIN_SSID_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //8 - STA password + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_PWD_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (HIDDEN_PASSWORD); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_PASSWORD_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Station Password\",\"M\":\""); + espresponse->print (String(MIN_PASSWORD_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + // 9 - STA IP mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_IP_MODE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + espresponse->print (String(prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE)).c_str()); + espresponse->print ("\",\"H\":\"Station IP Mode\",\"O\":[{\"DHCP\":\"0\"},{\"Static\":\"1\"}]}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //10-STA static IP + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_IP_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_IP_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static IP\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //11-STA static Gateway + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_GW_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_GW_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static Gateway\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //12-STA static Mask + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_MK_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_MK_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static Mask\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //13 - AP SSID + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_SSID_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_AP_SSID; + espresponse->print (prefs.getString(AP_SSID_ENTRY, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_SSID_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"AP SSID\",\"M\":\""); + espresponse->print (String(MIN_SSID_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //14 - AP password + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_PWD_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (HIDDEN_PASSWORD); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_PASSWORD_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"AP Password\",\"M\":\""); + espresponse->print (String(MIN_PASSWORD_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //15 - AP static IP + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_IP_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + defV = DEFAULT_AP_IP; + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(defV))).c_str()); + espresponse->print ("\",\"H\":\"AP Static IP\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //16 - AP Channel + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_CHANNEL_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + espresponse->print (String(prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL)).c_str()); + espresponse->print ("\",\"H\":\"AP Channel\",\"O\":["); + for (int i = MIN_CHANNEL; i <= MAX_CHANNEL ; i++) { + espresponse->print ("{\""); + espresponse->print (String(i).c_str()); + espresponse->print ("\":\""); + espresponse->print (String(i).c_str()); + espresponse->print ("\"}"); + if (i < MAX_CHANNEL) { + espresponse->print (","); + } + } + espresponse->print ("]}"); +#ifdef ENABLE_NOTIFICATIONS + espresponse->print (","); + //Notification type + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (NOTIFICATION_TYPE); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_TYPE); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"Notification type\",\"O\":[{\"None\":\"0\"}"); + espresponse->print (",{\"Line\":\"3\"},{\"Pushover\":\"1\"}"); + espresponse->print (",{\"Email\":\"2\"}"); + espresponse->print ("]}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //Notification token 1 + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (NOTIFICATION_T1); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_TOKEN; + espresponse->print (prefs.getString(NOTIFICATION_T1, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_NOTIFICATION_TOKEN_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Notification Token 1\",\"M\":\""); + espresponse->print (String(MIN_NOTIFICATION_TOKEN_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //Notification token 2 + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (NOTIFICATION_T2); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_TOKEN; + espresponse->print (prefs.getString(NOTIFICATION_T2, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_NOTIFICATION_TOKEN_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Notification Token 2\",\"M\":\""); + espresponse->print (String(MIN_NOTIFICATION_TOKEN_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + + //Notification settings + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (NOTIFICATION_TS); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_TOKEN; + espresponse->print (prefs.getString(NOTIFICATION_TS, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_NOTIFICATION_SETTING_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Notification Settings\",\"M\":\""); + espresponse->print (String(MIN_NOTIFICATION_TOKEN_LENGTH).c_str()); + espresponse->print ("\"}"); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); +#endif //ENABLE_NOTIFICATIONS +#endif + espresponse->print ("]}"); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + prefs.end(); + } + break; + + //Set EEPROM setting + //[ESP401]P= T= V= pwd= + case 401: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + //check validity of parameters + String spos = get_param (cmd_params, "P=", false); + String styp = get_param (cmd_params, "T=", false); + String sval = get_param (cmd_params, "V=", true); + spos.trim(); + sval.trim(); + if (spos.length() == 0) { + response = false; + } + if (! (styp == "B" || styp == "S" || styp == "A" || styp == "I" || styp == "F") ) { + response = false; + } + if ((sval.length() == 0) +#if defined (ENABLE_WIFI) + && !((spos==AP_PWD_ENTRY) || (spos==STA_PWD_ENTRY)) +#endif + ){ + response = false; + } + + if (response) { + Preferences prefs; + prefs.begin(NAMESPACE, false); + //Byte value + if ((styp == "B") || (styp == "F")){ + int8_t bbuf = sval.toInt(); + if (prefs.putChar(spos.c_str(), bbuf) ==0 ) { + response = false; + } else { +#if defined (ENABLE_WIFI) + //dynamique refresh is better than restart the board + if (spos == ESP_RADIO_MODE){ + //TODO + } + if (spos == AP_CHANNEL_ENTRY) { + //TODO + } +#if defined (ENABLE_HTTP) + if (spos == HTTP_ENABLE_ENTRY) { + //TODO + } +#endif +#if defined (ENABLE_TELNET) + if (spos == TELNET_ENABLE_ENTRY) { + //TODO + } +#endif +#endif + } + } + //Integer value + if (styp == "I") { + int16_t ibuf = sval.toInt(); + if (prefs.putUShort(spos.c_str(), ibuf) == 0) { + response = false; + } else { +#if defined (ENABLE_WIFI) +#if defined (ENABLE_HTTP) + if (spos == HTTP_PORT_ENTRY){ + //TODO + } +#endif +#if defined (ENABLE_TELNET) + if (spos == TELNET_PORT_ENTRY){ + //TODO + //Serial.println(ibuf); + } +#endif +#endif + } + + } + //String value + if (styp == "S") { + if (prefs.putString(spos.c_str(), sval) != sval.length()) { + response = false; + } else { +#if defined (ENABLE_WIFI) + if (spos == HOSTNAME_ENTRY){ + //TODO + } + if (spos == STA_SSID_ENTRY){ + //TODO + } + if (spos == STA_PWD_ENTRY){ + //TODO + } + if (spos == AP_SSID_ENTRY){ + //TODO + } + if (spos == AP_PWD_ENTRY){ + //TODO + } +#endif + } + + } +#if defined (ENABLE_WIFI) + //IP address + if (styp == "A") { + if (prefs.putInt(spos.c_str(), wifi_config.IP_int_from_string(sval)) == 0) { + response = false; + } else { + + if (spos == STA_IP_ENTRY){ + //TODO + } + if (spos == STA_GW_ENTRY){ + //TODO + } + if (spos == STA_MK_ENTRY){ + //TODO + } + if (spos == AP_IP_ENTRY){ + //TODO + } + } + } +#endif + prefs.end(); + } + if (!response) { + if (espresponse) espresponse->println ("Error: Incorrect Command"); + } else { + if (espresponse) espresponse->println ("ok"); + } + + } + break; +#if defined (ENABLE_WIFI) + //Get available AP list (limited to 30) + //output is JSON + //[ESP410] + case 410: { + if (!espresponse)return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + espresponse->print("{\"AP_LIST\":["); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + int n = WiFi.scanComplete(); + if (n == -2) { + WiFi.scanNetworks (true); + } else if (n) { + for (int i = 0; i < n; ++i) { + if (i > 0) { + espresponse->print (","); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + } + espresponse->print ("{\"SSID\":\""); + espresponse->print (WiFi.SSID (i).c_str()); + espresponse->print ("\",\"SIGNAL\":\""); + espresponse->print (String(wifi_config.getSignal (WiFi.RSSI (i) )).c_str()); + espresponse->print ("\",\"IS_PROTECTED\":\""); + + if (WiFi.encryptionType (i) == WIFI_AUTH_OPEN) { + espresponse->print ("0"); + } else { + espresponse->print ("1"); + } + espresponse->print ("\"}"); + } + } + WiFi.scanDelete(); + if (WiFi.scanComplete() == -2) { + WiFi.scanNetworks (true); + } + espresponse->print ("]}"); + if(espresponse->client() != CLIENT_WEBUI)espresponse->println(""); + } + break; +#endif + //Get ESP current status + case 420: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + if (!espresponse)return false; + espresponse->print ("Chip ID: "); + espresponse->print (String ( (uint16_t) (ESP.getEfuseMac() >> 32) ).c_str()); + espresponse->println(""); + espresponse->print ("CPU Frequency: "); + espresponse->print (String (ESP.getCpuFreqMHz() ).c_str()); + espresponse->print ("Mhz"); + espresponse->println(""); + espresponse->print ("CPU Temperature: "); + espresponse->print (String (temperatureRead(), 1).c_str()); + if(espresponse->client() == CLIENT_WEBUI)espresponse->print ("°"); + espresponse->print ("C"); + espresponse->println(""); + espresponse->print ("Free memory: "); + espresponse->print (ESPResponseStream::formatBytes (ESP.getFreeHeap()).c_str()); + espresponse->println(""); + espresponse->print ("SDK: "); + espresponse->print (ESP.getSdkVersion()); + espresponse->println(""); + espresponse->print ("Flash Size: "); + espresponse->print (ESPResponseStream::formatBytes (ESP.getFlashChipSize()).c_str()); + espresponse->println(""); +#if defined (ENABLE_WIFI) + if (WiFi.getMode() != WIFI_MODE_NULL){ + espresponse->print ("Available Size for update: "); + //Is OTA available ? + size_t flashsize = 0; + if (esp_ota_get_running_partition()) { + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + if (partition) { + flashsize = partition->size; + } + } + espresponse->print (ESPResponseStream::formatBytes (flashsize).c_str()); + + espresponse->println(""); + } + if (WiFi.getMode() != WIFI_MODE_NULL){ + espresponse->print ("Available Size for SPIFFS: "); + espresponse->print (ESPResponseStream::formatBytes (SPIFFS.totalBytes()).c_str()); + espresponse->println(""); + } +#endif + espresponse->print ("Baud rate: "); + long br = Serial.baudRate(); + //workaround for ESP32 + if (br == 115201) { + br = 115200; + } + if (br == 230423) { + br = 230400; + } + espresponse->print (String(br).c_str()); + espresponse->println(""); + espresponse->print ("Sleep mode: "); + if (WiFi.getSleep())espresponse->print ("Modem"); + else espresponse->print ("None"); + espresponse->println(""); +#if defined (ENABLE_WIFI) +#if defined (ENABLE_HTTP) + if (WiFi.getMode() != WIFI_MODE_NULL){ + espresponse->print ("Web port: "); + espresponse->print (String(web_server.port()).c_str()); + espresponse->println(""); + } +#endif +#if defined (ENABLE_TELNET) + if (WiFi.getMode() != WIFI_MODE_NULL){ + espresponse->print ("Data port: "); + espresponse->print (String(telnet_server.port()).c_str()); + espresponse->println(""); + } +#endif + if (WiFi.getMode() != WIFI_MODE_NULL){ + espresponse->print ("Hostname: "); + espresponse->print ( wifi_config.Hostname().c_str()); + espresponse->println(""); + } + espresponse->print ("Current WiFi Mode: "); + if (WiFi.getMode() == WIFI_STA) { + espresponse->print ("STA ("); + espresponse->print ( WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + espresponse->print ("Connected to: "); + if (WiFi.isConnected()){ //in theory no need but ... + espresponse->print (WiFi.SSID().c_str()); + espresponse->println(""); + espresponse->print ("Signal: "); + espresponse->print ( String(wifi_config.getSignal (WiFi.RSSI())).c_str()); + espresponse->print ("%"); + espresponse->println(""); + uint8_t PhyMode; + esp_wifi_get_protocol (ESP_IF_WIFI_STA, &PhyMode); + espresponse->print ("Phy Mode: "); + if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)) espresponse->print ("11n"); + else if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G)) espresponse->print ("11g"); + else if (PhyMode == (WIFI_PROTOCOL_11B )) espresponse->print ("11b"); + else espresponse->print ("???"); + espresponse->println(""); + espresponse->print ("Channel: "); + espresponse->print (String (WiFi.channel()).c_str()); + espresponse->println(""); + espresponse->print ("IP Mode: "); + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcpc_get_status (TCPIP_ADAPTER_IF_STA, &dhcp_status); + if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("DHCP"); + else espresponse->print ("Static"); + espresponse->println(""); + espresponse->print ("IP: "); + espresponse->print (WiFi.localIP().toString().c_str()); + espresponse->println(""); + espresponse->print ("Gateway: "); + espresponse->print (WiFi.gatewayIP().toString().c_str()); + espresponse->println(""); + espresponse->print ("Mask: "); + espresponse->print (WiFi.subnetMask().toString().c_str()); + espresponse->println(""); + espresponse->print ("DNS: "); + espresponse->print (WiFi.dnsIP().toString().c_str()); + espresponse->println(""); + } //this is web command so connection => no command + espresponse->print ("Disabled Mode: "); + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + } else if (WiFi.getMode() == WIFI_AP) { + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + wifi_config_t conf; + esp_wifi_get_config (ESP_IF_WIFI_AP, &conf); + espresponse->print ("SSID: "); + espresponse->print ((const char*) conf.ap.ssid); + espresponse->println(""); + espresponse->print ("Visible: "); + espresponse->print ( (conf.ap.ssid_hidden == 0) ? "Yes" : "No"); + espresponse->println(""); + espresponse->print ("Authentication: "); + if (conf.ap.authmode == WIFI_AUTH_OPEN) { + espresponse->print ("None"); + } else if (conf.ap.authmode == WIFI_AUTH_WEP) { + espresponse->print ("WEP"); + } else if (conf.ap.authmode == WIFI_AUTH_WPA_PSK) { + espresponse->print ("WPA"); + } else if (conf.ap.authmode == WIFI_AUTH_WPA2_PSK) { + espresponse->print ("WPA2"); + } else { + espresponse->print ("WPA/WPA2"); + } + espresponse->println(""); + espresponse->print ("Max Connections: "); + espresponse->print (String(conf.ap.max_connection).c_str()); + espresponse->println(""); + espresponse->print ("DHCP Server: "); + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcps_get_status (TCPIP_ADAPTER_IF_AP, &dhcp_status); + if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("Started"); + else espresponse->print ("Stopped"); + espresponse->println(""); + espresponse->print ("IP: "); + espresponse->print (WiFi.softAPIP().toString().c_str()); + espresponse->println(""); + tcpip_adapter_ip_info_t ip_AP; + tcpip_adapter_get_ip_info (TCPIP_ADAPTER_IF_AP, &ip_AP); + espresponse->print ("Gateway: "); + espresponse->print (IPAddress (ip_AP.gw.addr).toString().c_str()); + espresponse->println(""); + espresponse->print ("Mask: "); + espresponse->print (IPAddress (ip_AP.netmask.addr).toString().c_str()); + espresponse->println(""); + espresponse->print ("Connected clients: "); + wifi_sta_list_t station; + tcpip_adapter_sta_list_t tcpip_sta_list; + esp_wifi_ap_get_sta_list (&station); + tcpip_adapter_get_sta_list (&station, &tcpip_sta_list); + espresponse->print (String(station.num).c_str()); + espresponse->println(""); + for (int i = 0; i < station.num; i++) { + espresponse->print (wifi_config.mac2str(tcpip_sta_list.sta[i].mac)); + espresponse->print (" "); + espresponse->print ( IPAddress (tcpip_sta_list.sta[i].ip.addr).toString().c_str()); + espresponse->println(""); + } + espresponse->print ("Disabled Mode: "); + espresponse->print ("STA ("); + espresponse->print (WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + } else if (WiFi.getMode() == WIFI_AP_STA) //we should not be in this state but just in case .... + { + espresponse->print ("Mixed"); + espresponse->println(""); + espresponse->print ("STA ("); + espresponse->print (WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->println(""); + + } else { //we should not be there if no wifi .... + espresponse->print ("Off"); + espresponse->println(""); + } +#endif +#ifdef ENABLE_BLUETOOTH + espresponse->print ("Current BT Mode: "); + if (bt_config.Is_BT_on()){ + espresponse->println("On"); + espresponse->print ("BT Name: "); + espresponse->print (bt_config.BTname().c_str()); + espresponse->print ("("); + espresponse->print (bt_config.device_address()); + espresponse->println(")"); + espresponse->print ("Status: "); + if(SerialBT.hasClient()) { + espresponse->print ("Connected with "); + espresponse->print (bt_config._btclient.c_str()); + } + else espresponse->print ("Not connected"); + } else{ + espresponse->print ("Off"); + } + espresponse->println(""); +#endif +#ifdef ENABLE_NOTIFICATIONS + espresponse->print ("Notifications: "); + espresponse->print (notificationsservice.started()?"Enabled":"Disabled"); + if (notificationsservice.started()) { + espresponse->print ("("); + espresponse->print (notificationsservice.getTypeString()); + espresponse->print (")"); + } + espresponse->println(""); +#endif + //TODO to complete + espresponse->print ("FW version: "); + espresponse->print (GRBL_VERSION); + espresponse->print (" ("); + espresponse->print (GRBL_VERSION_BUILD); + espresponse->print (") (ESP32)"); + espresponse->println(""); + } + break; + //Set ESP mode + //cmd is RESTART + //[ESP444] + case 444: + parameter = get_param(cmd_params,"", true); +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + { + if (parameter=="RESTART") { + grbl_send(CLIENT_ALL,"[MSG:Restart ongoing]\r\n"); + COMMANDS::restart_ESP(); + } else response = false; + } + if (!response) { + if (espresponse)espresponse->println ("Error: Incorrect Command"); + } else { + if (espresponse)espresponse->println ("ok"); + } + break; +#ifdef ENABLE_AUTHENTICATION + //Change / Reset user password + //[ESP555] + case 555: { + if (auth_type == LEVEL_ADMIN) { + parameter = get_param (cmd_params, "", true); + if (parameter.length() == 0) { + Preferences prefs; + parameter = DEFAULT_USER_PWD; + prefs.begin(NAMESPACE, false); + if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()){ + response = false; + if (espresponse)espresponse->println ("error"); + } else if (espresponse)espresponse->println ("ok"); + prefs.end(); + + } else { + if (isLocalPasswordValid (parameter.c_str() ) ) { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()) { + response = false; + if (espresponse)espresponse->println ("error"); + } else if (espresponse)espresponse->println ("ok"); + prefs.end(); + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + } + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + break; + } +#endif +#ifdef ENABLE_NOTIFICATIONS + case 600: { //Send message + +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if (parameter.length() == 0) { + if (espresponse)espresponse->println ("Invalid message!"); + return false; + } + if (notificationsservice.sendMSG("GRBL Notification", parameter.c_str())) { + if (espresponse)espresponse->println ("ok"); + } else { + if (espresponse)espresponse->println ("Cannot send message!"); + return false; + } + } + break; + //Set/Get Notification settings + //[ESP610]type= T1= T2= TS= [pwd=] + //Get will give type and settings only not the protected T1/T2 + case 610: { //Send message + +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ((parameter.length() == 0) && !espresponse) return false; + //get + if (parameter.length() == 0) { + Preferences prefs; + prefs.begin(NAMESPACE, true); + uint8_t vi = prefs.getChar(NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_TYPE); + String defV = DEFAULT_TOKEN; + parameter = (vi == ESP_PUSHOVER_NOTIFICATION)?"PUSHOVER":(vi == ESP_LINE_NOTIFICATION)?"LINE":(vi == ESP_EMAIL_NOTIFICATION)?"EMAIL":"NONE"; + parameter+=" "; + parameter+=prefs.getString(NOTIFICATION_TS, defV); + espresponse->println(parameter.c_str()); + prefs.end(); + } else { //set +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + response = false; + parameter = get_param (cmd_params, "type=", false); + if (parameter.length() !=0) { + uint8_t bbuf = (parameter == "NONE")?0:(parameter == "PUSHOVER")?ESP_PUSHOVER_NOTIFICATION:(parameter == "LINE")?ESP_LINE_NOTIFICATION:(parameter == "EMAIL")?ESP_EMAIL_NOTIFICATION:255; + if (bbuf != 255){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putChar(NOTIFICATION_TYPE, bbuf) == 0){ + if(espresponse)espresponse->println ("Error: Set failed!"); + response = false; + } else { + response = true; + } + prefs.end(); + } else{ + if(espresponse)espresponse->println ("Error: wrong type!"); + response = false; + } + } + + parameter = get_param (cmd_params, "T1=", false); + if (parameter.length() !=0) { + if (parameter.length() <=MAX_NOTIFICATION_TOKEN_LENGTH ){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(NOTIFICATION_T1, parameter) == 0){ + if(espresponse)espresponse->println ("Error: Set failed!"); + response = false; + } else { + response = true; + } + prefs.end(); + } else{ + if(espresponse)espresponse->println ("Error: token 1!"); + response = false; + } + } + parameter = get_param (cmd_params, "T2=", false); + if (parameter.length() !=0) { + if (parameter.length() <=MAX_NOTIFICATION_TOKEN_LENGTH ){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(NOTIFICATION_T2, parameter) == 0){ + if(espresponse)espresponse->println ("Error: Set failed!"); + response = false; + } else { + response = true; + } + prefs.end(); + } else{ + if(espresponse)espresponse->println ("Error: token 2!"); + response = false; + } + } + parameter = get_param (cmd_params, "TS=", false); + if (parameter.length() !=0) { + if (parameter.length() <=MAX_NOTIFICATION_SETTING_LENGTH ){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(NOTIFICATION_TS, parameter) == 0){ + if(espresponse)espresponse->println ("Error: Set failed!"); + response = false; + } else { + response = true; + } + prefs.end(); + } else{ + if(espresponse)espresponse->println ("Error: settings!"); + response = false; + } + } + } + //update settings + notificationsservice.begin(); + if(espresponse && response)espresponse->println ("ok"); + + } + break; +#endif //ENABLE_NOTIFICATIONS + //[ESP700] pwd= + case 700: { //read local file + +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + if ( (parameter.length() > 0) && (parameter[0] != '/') ) { + parameter = "/" + parameter; + } + if (!SPIFFS.exists(parameter)){ + if (espresponse)espresponse->println ("Error:No such file!"); + response = false; + } else { + File currentfile = SPIFFS.open (parameter, FILE_READ); + if (currentfile) {//if file open success + //until no line in file + while (currentfile.available()) { + String currentline = currentfile.readStringUntil('\n'); + currentline.replace("\n",""); + currentline.replace("\r",""); + if (currentline.length() > 0) { + int ESPpos = currentline.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = currentline.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = currentline.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //is there space for parameters? + if (ESPpos2 < currentline.length() ) { + cmd_part2 = currentline.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if(cmd_part1.toInt()!=0) { + if (!execute_internal_command(cmd_part1.toInt(),cmd_part2, auth_type, espresponse)) response = false; + } + //if not is not a valid [ESPXXX] command ignore it + } + } else { + //preprocess line + String processedline = ""; + char c; + uint8_t line_flags = 0; + for (uint16_t index=0; index < currentline.length(); index++){ + c = currentline[index]; + if (c == '\r' || c == ' ' || c == '\n') { + // ignore these whitespace items + } + else if (c == '(') { + line_flags |= LINE_FLAG_COMMENT_PARENTHESES; + } + else if (c == ')') { + // End of '()' comment. Resume line allowed. + if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { line_flags &= ~(LINE_FLAG_COMMENT_PARENTHESES); } + } + else if (c == ';') { + // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. + if (!(line_flags & LINE_FLAG_COMMENT_PARENTHESES)) // semi colon inside parentheses do not mean anything + line_flags |= LINE_FLAG_COMMENT_SEMICOLON; + } + + else { // add characters to the line + if (!line_flags) { + c = toupper(c); // make upper case + processedline += c; + } + } + } + if (processedline.length() > 0)gc_execute_line((char *)processedline.c_str(), CLIENT_WEBUI); + wait (1); + } + wait (1); + } + } + currentfile.close(); + if (espresponse)espresponse->println ("ok"); + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + } + break; + } + //Format SPIFFS + //[ESP710]FORMAT pwd= + case 710: +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + if (espresponse)espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + { + if (parameter == "FORMAT") { + if (espresponse)espresponse->print ("Formating"); + SPIFFS.format(); + if (espresponse)espresponse->println ("...Done"); + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + } + break; + //SPIFFS total size and used size + //[ESP720]
+ case 720: + if (!espresponse)return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) { + espresponse->println ("Error: Wrong authentication!"); + return false; + } +#endif + parameter = get_param (cmd_params, "", true); + espresponse->print (parameter.c_str()); + espresponse->print ("SPIFFS Total:"); + espresponse->print (ESPResponseStream::formatBytes (SPIFFS.totalBytes() ).c_str()); + espresponse->print (" Used:"); + espresponse->println (ESPResponseStream::formatBytes (SPIFFS.usedBytes() ).c_str()); + break; + //get fw version / fw target / hostname / authentication + //[ESP800] + case 800: + { + if (!espresponse)return false; + String resp; + resp = "FW version:"; + resp += GRBL_VERSION ; + resp += " ("; + resp += GRBL_VERSION_BUILD; + resp += ")"; + resp += " # FW target:grbl-embedded # FW HW:"; + #ifdef ENABLE_SD_CARD + resp += "Direct SD"; + #else + resp += "No SD"; + #endif + resp += " # primary sd:/sd # secondary sd:none # authentication:"; + #ifdef ENABLE_AUTHENTICATION + resp += "yes"; + #else + resp += "no"; + #endif + #if defined (ENABLE_WIFI) + #if defined (ENABLE_HTTP) + resp += " # webcommunication: Sync: "; + resp += String(web_server.port() + 1); + resp += ":"; + if (WiFi.getMode() == WIFI_MODE_AP) { + resp += WiFi.softAPIP().toString(); + } else if (WiFi.getMode() == WIFI_MODE_STA){ + resp += WiFi.localIP().toString(); + } else if (WiFi.getMode() == WIFI_MODE_APSTA) { + resp += WiFi.softAPIP().toString(); + } else { + resp += "0.0.0.0"; + } + #endif + resp += " # hostname:"; + resp += wifi_config.Hostname(); + if (WiFi.getMode() == WIFI_AP)resp += "(AP mode)"; + #endif + //to save time in decoding `?` + resp += " # axis:"; + resp += String(N_AXIS); + if (espresponse)espresponse->println (resp.c_str()); + } + break; + default: + if (espresponse)espresponse->println ("Error: Incorrect Command"); + response = false; + break; + } + return response; +} + + +String COMMANDS::get_param(String & cmd_params, const char * id, bool withspace) +{ + static String parameter; + String sid = id; + int start; + int end = -1; + if (cmd_params.indexOf("pwd=") == 0)cmd_params = " " + cmd_params; + parameter = ""; + //if no id it means it is first part of cmd + if (strlen (id) == 0) { + start = 0; + } + //else find id position + else { + start = cmd_params.indexOf (id); + } + //if no id found and not first part leave + if (start == -1 ) { + return parameter; + } + //password and SSID can have space so handle it + //if no space expected use space as delimiter + if (!withspace) { + end = cmd_params.indexOf (" ", start); + } +#ifdef ENABLE_AUTHENTICATION + //if space expected only one parameter but additional password may be present + else if (sid != " pwd=") { + end = cmd_params.indexOf (" pwd=", start); + } +#endif + //if no end found - take all + if (end == -1) { + end = cmd_params.length(); + } + //extract parameter + parameter = cmd_params.substring (start + strlen (id), end); + //be sure no extra space + parameter.trim(); + return parameter; +} + +#ifdef ENABLE_AUTHENTICATION + +bool COMMANDS::isLocalPasswordValid (const char * password) +{ + char c; + //limited size + if ( (strlen (password) > MAX_LOCAL_PASSWORD_LENGTH) || (strlen (password) < MIN_LOCAL_PASSWORD_LENGTH) ) { + return false; + } + //no space allowed + for (int i = 0; i < strlen (password); i++) { + c = password[i]; + if (c == ' ') { + return false; + } + } + return true; +} + +//check admin password +bool COMMANDS::isadmin (String & cmd_params) +{ String adminpassword; + String sadminPassword; + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_ADMIN_PWD; + sadminPassword = prefs.getString(ADMIN_PWD_ENTRY, defV); + prefs.end(); + adminpassword = get_param (cmd_params, "pwd=", true); + if (!sadminPassword.equals (adminpassword) ) { + return false; + } else { + return true; + } +} +//check user password - admin password is also valid +bool COMMANDS::isuser (String & cmd_params) +{ + String userpassword; + String suserPassword; + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_USER_PWD; + suserPassword = prefs.getString(USER_PWD_ENTRY, defV); + prefs.end(); + userpassword = get_param (cmd_params, "pwd=", true); + //it is not user password + if (!suserPassword.equals (userpassword) ) { + //check admin password + return isadmin (cmd_params); + } else { + return true; + } +} +#endif + +//check is valid [ESPXXX] command +//return XXX as cmd and command as cmd_params +bool COMMANDS::check_command (const char * line, int * cmd, String & cmd_params) +{ + String buffer = line; + bool result = false; + int ESPpos = buffer.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = buffer.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = buffer.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //is there space for parameters? + if (ESPpos2 < buffer.length() ) { + cmd_part2 = buffer.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if (cmd_part1.toInt() != 0) { + *cmd = cmd_part1.toInt(); + cmd_params = cmd_part2; + result = true; + } + //if not is not a valid [ESPXXX] command + } + } + return result; +} + +/** + * Restart ESP + */ +void COMMANDS::restart_ESP(){ + restart_ESP_module=true; +} + +/** + * Handle not critical actions that must be done in sync environement + */ +void COMMANDS::handle() { + COMMANDS::wait(0); + //in case of restart requested + if (restart_ESP_module) { + ESP.restart(); + while (1) {}; + } +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/commands.h b/Grbl_Esp32-master/Grbl_Esp32/commands.h new file mode 100644 index 0000000..60a85e6 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/commands.h @@ -0,0 +1,58 @@ +/* + commands.h - ESP3D configuration class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef COMMANDS_h +#define COMMANDS_h +#include "config.h" + +//Authentication level +typedef enum { + LEVEL_GUEST = 0, + LEVEL_USER = 1, + LEVEL_ADMIN = 2 +} level_authenticate_type; + +// Define line flags. Includes comment type tracking and line overflow detection. +#define LINE_FLAG_OVERFLOW bit(0) +#define LINE_FLAG_COMMENT_PARENTHESES bit(1) +#define LINE_FLAG_COMMENT_SEMICOLON bit(2) + +class ESPResponseStream; + + +class COMMANDS +{ +public: + static bool check_command (const char *, int * cmd, String & cmd_params); + static String get_param (String & cmd_params, const char * id, bool withspace); + static bool execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level = LEVEL_GUEST , ESPResponseStream *espresponse= NULL); + static void wait(uint32_t milliseconds); + static void handle(); + static void restart_ESP(); +#ifdef ENABLE_AUTHENTICATION + static bool isadmin (String & cmd_params); + static bool isuser (String & cmd_params); + static bool isLocalPasswordValid (const char * password); +#endif + private : + static bool restart_ESP_module; +}; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/config.h b/Grbl_Esp32-master/Grbl_Esp32/config.h new file mode 100644 index 0000000..57bee12 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/config.h @@ -0,0 +1,722 @@ +/* + config.h - compile time configuration + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +// This file contains compile-time configurations for Grbl's internal system. For the most part, +// users will not need to directly modify these, but they are here for specific needs, i.e. +// performance tuning or adjusting to non-typical machines. + +// IMPORTANT: Any changes here requires a full re-compiling of the source code to propagate them. + +/* +ESP 32 Notes + +Some features should not be changed. See notes below. + +*/ + +#ifndef config_h +#define config_h +#include + +//#define ESP_DEBUG +#define N_AXIS 3 // Number of axes defined (valid range: 3 to 6) + +// Define CPU pin map and default settings. +// NOTE: OEMs can avoid the need to maintain/update the defaults.h and cpu_map.h files and use only +// one configuration file by placing their specific defaults and pin map at the bottom of this file. +// If doing so, simply comment out these two defines and see instructions below. +#define CPU_MAP_TEST_DRIVE // these are defined in cpu_map.h +#define VERBOSE_HELP // adds addition help info, but could confuse some senders + + +// Serial baud rate +#define BAUD_RATE 115200 + +//#define ENABLE_BLUETOOTH // enable bluetooth ... turns of if $I= something + +//#define ENABLE_SD_CARD // enable use of SD Card to run jobs + +//#define ENABLE_WIFI //enable wifi + +#define ENABLE_HTTP //enable HTTP and all related services +#define ENABLE_OTA //enable OTA +#define ENABLE_TELNET //enable telnet +#define ENABLE_TELNET_WELCOME_MSG //display welcome string when connect to telnet +#define ENABLE_MDNS //enable mDNS discovery +#define ENABLE_SSDP //enable UPNP discovery +#define ENABLE_NOTIFICATIONS //enable notifications + +#define ENABLE_SERIAL2SOCKET_IN +#define ENABLE_SERIAL2SOCKET_OUT + +#define ENABLE_CAPTIVE_PORTAL +//#define ENABLE_AUTHENTICATION + +#define NAMESPACE "GRBL" +#define ESP_RADIO_MODE "RADIO_MODE" + +#ifdef ENABLE_AUTHENTICATION +#define DEFAULT_ADMIN_PWD "admin" +#define DEFAULT_USER_PWD "user"; +#define DEFAULT_ADMIN_LOGIN "admin" +#define DEFAULT_USER_LOGIN "user" +#define ADMIN_PWD_ENTRY "ADMIN_PWD" +#define USER_PWD_ENTRY "USER_PWD" +#define AUTH_ENTRY_NB 20 +#define MAX_LOCAL_PASSWORD_LENGTH 16 +#define MIN_LOCAL_PASSWORD_LENGTH 1 +#endif + +//Radio Mode +#define ESP_RADIO_OFF 0 +#define ESP_WIFI_STA 1 +#define ESP_WIFI_AP 2 +#define ESP_BT 3 + + //Default mode +#ifdef ENABLE_WIFI +#define DEFAULT_RADIO_MODE ESP_WIFI_AP +#else + #undef ENABLE_NOTIFICATIONS + #ifdef ENABLE_BLUETOOTH + #define DEFAULT_RADIO_MODE ESP_BT + #else + #define DEFAULT_RADIO_MODE ESP_RADIO_OFF + #endif +#endif + +// Define realtime command special characters. These characters are 'picked-off' directly from the +// serial read data stream and are not passed to the grbl line execution parser. Select characters +// that do not and must not exist in the streamed g-code program. ASCII control characters may be +// used, if they are available per user setup. Also, extended ASCII codes (>127), which are never in +// g-code programs, maybe selected for interface programs. +// NOTE: If changed, manually update help message in report.c. + +#define CMD_RESET 0x18 // ctrl-x. +#define CMD_STATUS_REPORT '?' +#define CMD_CYCLE_START '~' +#define CMD_FEED_HOLD '!' + +// NOTE: All override realtime commands must be in the extended ASCII character set, starting +// at character value 128 (0x80) and up to 255 (0xFF). If the normal set of realtime commands, +// such as status reports, feed hold, reset, and cycle start, are moved to the extended set +// space, serial.c's RX ISR will need to be modified to accommodate the change. +// #define CMD_RESET 0x80 +// #define CMD_STATUS_REPORT 0x81 +// #define CMD_CYCLE_START 0x82 +// #define CMD_FEED_HOLD 0x83 +#define CMD_SAFETY_DOOR 0x84 +#define CMD_JOG_CANCEL 0x85 +#define CMD_DEBUG_REPORT 0x86 // Only when DEBUG enabled, sends debug report in '{}' braces. +#define CMD_FEED_OVR_RESET 0x90 // Restores feed override value to 100%. +#define CMD_FEED_OVR_COARSE_PLUS 0x91 +#define CMD_FEED_OVR_COARSE_MINUS 0x92 +#define CMD_FEED_OVR_FINE_PLUS 0x93 +#define CMD_FEED_OVR_FINE_MINUS 0x94 +#define CMD_RAPID_OVR_RESET 0x95 // Restores rapid override value to 100%. +#define CMD_RAPID_OVR_MEDIUM 0x96 +#define CMD_RAPID_OVR_LOW 0x97 +// #define CMD_RAPID_OVR_EXTRA_LOW 0x98 // *NOT SUPPORTED* +#define CMD_SPINDLE_OVR_RESET 0x99 // Restores spindle override value to 100%. +#define CMD_SPINDLE_OVR_COARSE_PLUS 0x9A +#define CMD_SPINDLE_OVR_COARSE_MINUS 0x9B +#define CMD_SPINDLE_OVR_FINE_PLUS 0x9C +#define CMD_SPINDLE_OVR_FINE_MINUS 0x9D +#define CMD_SPINDLE_OVR_STOP 0x9E +#define CMD_COOLANT_FLOOD_OVR_TOGGLE 0xA0 +#define CMD_COOLANT_MIST_OVR_TOGGLE 0xA1 + +// If homing is enabled, homing init lock sets Grbl into an alarm state upon power up. This forces +// the user to perform the homing cycle (or override the locks) before doing anything else. This is +// mainly a safety feature to remind the user to home, since position is unknown to Grbl. +#define HOMING_INIT_LOCK // Comment to disable + +// Define the homing cycle patterns with bitmasks. The homing cycle first performs a search mode +// to quickly engage the limit switches, followed by a slower locate mode, and finished by a short +// pull-off motion to disengage the limit switches. The following HOMING_CYCLE_x defines are executed +// in order starting with suffix 0 and completes the homing routine for the specified-axes only. If +// an axis is omitted from the defines, it will not home, nor will the system update its position. +// Meaning that this allows for users with non-standard Cartesian machines, such as a lathe (x then z, +// with no y), to configure the homing cycle behavior to their needs. +// NOTE: The homing cycle is designed to allow sharing of limit pins, if the axes are not in the same +// cycle, but this requires some pin settings changes in cpu_map.h file. For example, the default homing +// cycle can share the Z limit pin with either X or Y limit pins, since they are on different cycles. +// By sharing a pin, this frees up a precious IO pin for other purposes. In theory, all axes limit pins +// may be reduced to one pin, if all axes are homed with separate cycles, or vice versa, all three axes +// on separate pin, but homed in one cycle. Also, it should be noted that the function of hard limits +// will not be affected by pin sharing. + +// NOTE: Defaults are set for a traditional 3-axis CNC machine. Z-axis first to clear, followed by X & Y. +#define HOMING_CYCLE_0 (1< 3us, and, when added with the +// user-supplied step pulse time, the total time must not exceed 127us. Reported successful +// values for certain setups have ranged from 5 to 20us. +// must use #define USE_RMT_STEPS for this to work +//#define STEP_PULSE_DELAY 10 // Step pulse delay in microseconds. Default disabled. + +// The number of linear motions in the planner buffer to be planned at any give time. The vast +// majority of RAM that Grbl uses is based on this buffer size. Only increase if there is extra +// available RAM, like when re-compiling for a Mega2560. Or decrease if the Arduino begins to +// crash due to the lack of available RAM or if the CPU is having trouble keeping up with planning +// new incoming motions as they are executed. + #define BLOCK_BUFFER_SIZE 32 // Uncomment to override default in planner.h. + +// Governs the size of the intermediary step segment buffer between the step execution algorithm +// and the planner blocks. Each segment is set of steps executed at a constant velocity over a +// fixed time defined by ACCELERATION_TICKS_PER_SECOND. They are computed such that the planner +// block velocity profile is traced exactly. The size of this buffer governs how much step +// execution lead time there is for other Grbl processes have to compute and do their thing +// before having to come back and refill this buffer, currently at ~50msec of step moves. +// #define SEGMENT_BUFFER_SIZE 6 // Uncomment to override default in stepper.h. + +// Line buffer size from the serial input stream to be executed. Also, governs the size of +// each of the startup blocks, as they are each stored as a string of this size. Make sure +// to account for the available EEPROM at the defined memory address in settings.h and for +// the number of desired startup blocks. +// NOTE: 80 characters is not a problem except for extreme cases, but the line buffer size +// can be too small and g-code blocks can get truncated. Officially, the g-code standards +// support up to 256 characters. In future versions, this default will be increased, when +// we know how much extra memory space we can re-invest into this. +// #define LINE_BUFFER_SIZE 80 // Uncomment to override default in protocol.h + +// Serial send and receive buffer size. The receive buffer is often used as another streaming +// buffer to store incoming blocks to be processed by Grbl when its ready. Most streaming +// interfaces will character count and track each block send to each block response. So, +// increase the receive buffer if a deeper receive buffer is needed for streaming and avaiable +// memory allows. The send buffer primarily handles messages in Grbl. Only increase if large +// messages are sent and Grbl begins to stall, waiting to send the rest of the message. +// NOTE: Grbl generates an average status report in about 0.5msec, but the serial TX stream at +// 115200 baud will take 5 msec to transmit a typical 55 character report. Worst case reports are +// around 90-100 characters. As long as the serial TX buffer doesn't get continually maxed, Grbl +// will continue operating efficiently. Size the TX buffer around the size of a worst-case report. + #define RX_BUFFER_SIZE 254 // (1-254) Uncomment to override defaults in serial.h + #define TX_BUFFER_SIZE 128 // (1-254) + +// A simple software debouncing feature for hard limit switches. When enabled, the limit +// switch interrupt unblock a waiting task which will recheck the limit switch pins after +// a short delay. Default disabled +#define ENABLE_SOFTWARE_DEBOUNCE // Default disabled. Uncomment to enable. +#define DEBOUNCE_PERIOD 32 // in milliseconds default 32 microseconds + +// Configures the position after a probing cycle during Grbl's check mode. Disabled sets +// the position to the probe target, when enabled sets the position to the start position. +// #define SET_CHECK_MODE_PROBE_TO_START // Default disabled. Uncomment to enable. + +// Force Grbl to check the state of the hard limit switches when the processor detects a pin +// change inside the hard limit ISR routine. By default, Grbl will trigger the hard limits +// alarm upon any pin change, since bouncing switches can cause a state check like this to +// misread the pin. When hard limits are triggered, they should be 100% reliable, which is the +// reason that this option is disabled by default. Only if your system/electronics can guarantee +// that the switches don't bounce, we recommend enabling this option. This will help prevent +// triggering a hard limit when the machine disengages from the switch. +// NOTE: This option has no effect if SOFTWARE_DEBOUNCE is enabled. +// #define HARD_LIMIT_FORCE_STATE_CHECK // Default disabled. Uncomment to enable. + +// Adjusts homing cycle search and locate scalars. These are the multipliers used by Grbl's +// homing cycle to ensure the limit switches are engaged and cleared through each phase of +// the cycle. The search phase uses the axes max-travel setting times the SEARCH_SCALAR to +// determine distance to look for the limit switch. Once found, the locate phase begins and +// uses the homing pull-off distance setting times the LOCATE_SCALAR to pull-off and re-engage +// the limit switch. +// NOTE: Both of these values must be greater than 1.0 to ensure proper function. +// #define HOMING_AXIS_SEARCH_SCALAR 1.5 // Uncomment to override defaults in limits.c. +// #define HOMING_AXIS_LOCATE_SCALAR 10.0 // Uncomment to override defaults in limits.c. + +// Enable the '$RST=*', '$RST=$', and '$RST=#' eeprom restore commands. There are cases where +// these commands may be undesirable. Simply comment the desired macro to disable it. +// NOTE: See SETTINGS_RESTORE_ALL macro for customizing the `$RST=*` command. +#define ENABLE_RESTORE_EEPROM_WIPE_ALL // '$RST=*' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // '$RST=$' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // '$RST=#' Default enabled. Comment to disable. + +// Defines the EEPROM data restored upon a settings version change and `$RST=*` command. Whenever the +// the settings or other EEPROM data structure changes between Grbl versions, Grbl will automatically +// wipe and restore the EEPROM. This macro controls what data is wiped and restored. This is useful +// particularily for OEMs that need to retain certain data. For example, the BUILD_INFO string can be +// written into the Arduino EEPROM via a seperate .INO sketch to contain product data. Altering this +// macro to not restore the build info EEPROM will ensure this data is retained after firmware upgrades. +// NOTE: Uncomment to override defaults in settings.h +// #define SETTINGS_RESTORE_ALL (SETTINGS_RESTORE_DEFAULTS | SETTINGS_RESTORE_PARAMETERS | SETTINGS_RESTORE_STARTUP_LINES | SETTINGS_RESTORE_BUILD_INFO) + +// Additional settings have been added to the original set that you see with the $$ command +// Some senders may not be able to parse anything different from the original set +// You can still set these like $33=5000, but you cannot read them back. +// Default is off to limit support issues...you can enable here or in your cpu_map +// #define SHOW_EXTENDED_SETTINGS + +// Enable the '$I=(string)' build info write command. If disabled, any existing build info data must +// be placed into EEPROM via external means with a valid checksum value. This macro option is useful +// to prevent this data from being over-written by a user, when used to store OEM product data. +// NOTE: If disabled and to ensure Grbl can never alter the build info line, you'll also need to enable +// the SETTING_RESTORE_ALL macro above and remove SETTINGS_RESTORE_BUILD_INFO from the mask. +// NOTE: See the included grblWrite_BuildInfo.ino example file to write this string seperately. +#define ENABLE_BUILD_INFO_WRITE_COMMAND // '$I=' Default enabled. Comment to disable. + +// AVR processors require all interrupts to be disabled during an EEPROM write. This includes both +// the stepper ISRs and serial comm ISRs. In the event of a long EEPROM write, this ISR pause can +// cause active stepping to lose position and serial receive data to be lost. This configuration +// option forces the planner buffer to completely empty whenever the EEPROM is written to prevent +// any chance of lost steps. +// However, this doesn't prevent issues with lost serial RX data during an EEPROM write, especially +// if a GUI is premptively filling up the serial RX buffer simultaneously. It's highly advised for +// GUIs to flag these gcodes (G10,G28.1,G30.1) to always wait for an 'ok' after a block containing +// one of these commands before sending more data to eliminate this issue. +// NOTE: Most EEPROM write commands are implicitly blocked during a job (all '$' commands). However, +// coordinate set g-code commands (G10,G28/30.1) are not, since they are part of an active streaming +// job. At this time, this option only forces a planner buffer sync with these g-code commands. +#define FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // Default enabled. Comment to disable. + +// In Grbl v0.9 and prior, there is an old outstanding bug where the `WPos:` work position reported +// may not correlate to what is executing, because `WPos:` is based on the g-code parser state, which +// can be several motions behind. This option forces the planner buffer to empty, sync, and stop +// motion whenever there is a command that alters the work coordinate offsets `G10,G43.1,G92,G54-59`. +// This is the simplest way to ensure `WPos:` is always correct. Fortunately, it's exceedingly rare +// that any of these commands are used need continuous motions through them. +#define FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // Default enabled. Comment to disable. + +// By default, Grbl disables feed rate overrides for all G38.x probe cycle commands. Although this +// may be different than some pro-class machine control, it's arguable that it should be this way. +// Most probe sensors produce different levels of error that is dependent on rate of speed. By +// keeping probing cycles to their programmed feed rates, the probe sensor should be a lot more +// repeatable. If needed, you can disable this behavior by uncommenting the define below. +// #define ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES // Default disabled. Uncomment to enable. + +// Enables and configures parking motion methods upon a safety door state. Primarily for OEMs +// that desire this feature for their integrated machines. At the moment, Grbl assumes that +// the parking motion only involves one axis, although the parking implementation was written +// to be easily refactored for any number of motions on different axes by altering the parking +// source code. At this time, Grbl only supports parking one axis (typically the Z-axis) that +// moves in the positive direction upon retracting and negative direction upon restoring position. +// The motion executes with a slow pull-out retraction motion, power-down, and a fast park. +// Restoring to the resume position follows these set motions in reverse: fast restore to +// pull-out position, power-up with a time-out, and plunge back to the original position at the +// slower pull-out rate. +// NOTE: Still a work-in-progress. Machine coordinates must be in all negative space and +// does not work with HOMING_FORCE_SET_ORIGIN enabled. Parking motion also moves only in +// positive direction. +//#define PARKING_ENABLE // Default disabled. Uncomment to enable + +// Configure options for the parking motion, if enabled. +#define PARKING_AXIS Z_AXIS // Define which axis that performs the parking motion +#define PARKING_TARGET -5.0 // Parking axis target. In mm, as machine coordinate [-max_travel,0]. +#define PARKING_RATE 500.0 // Parking fast rate after pull-out in mm/min. +#define PARKING_PULLOUT_RATE 100.0 // Pull-out/plunge slow feed rate in mm/min. +#define PARKING_PULLOUT_INCREMENT 5.0 // Spindle pull-out and plunge distance in mm. Incremental distance. + // Must be positive value or equal to zero. + +// Enables a special set of M-code commands that enables and disables the parking motion. +// These are controlled by `M56`, `M56 P1`, or `M56 Px` to enable and `M56 P0` to disable. +// The command is modal and will be set after a planner sync. Since it is g-code, it is +// executed in sync with g-code commands. It is not a real-time command. +// NOTE: PARKING_ENABLE is required. By default, M56 is active upon initialization. Use +// DEACTIVATE_PARKING_UPON_INIT to set M56 P0 as the power-up default. +// #define ENABLE_PARKING_OVERRIDE_CONTROL // Default disabled. Uncomment to enable +// #define DEACTIVATE_PARKING_UPON_INIT // Default disabled. Uncomment to enable. + +// This option will automatically disable the laser during a feed hold by invoking a spindle stop +// override immediately after coming to a stop. However, this also means that the laser still may +// be reenabled by disabling the spindle stop override, if needed. This is purely a safety feature +// to ensure the laser doesn't inadvertently remain powered while at a stop and cause a fire. +#define DISABLE_LASER_DURING_HOLD // Default enabled. Comment to disable. + +// Enables a piecewise linear model of the spindle PWM/speed output. Requires a solution by the +// 'fit_nonlinear_spindle.py' script in the /doc/script folder of the repo. See file comments +// on how to gather spindle data and run the script to generate a solution. +// #define ENABLE_PIECEWISE_LINEAR_SPINDLE // Default disabled. Uncomment to enable. + +// N_PIECES, RPM_MAX, RPM_MIN, RPM_POINTxx, and RPM_LINE_XX constants are all set and given by +// the 'fit_nonlinear_spindle.py' script solution. Used only when ENABLE_PIECEWISE_LINEAR_SPINDLE +// is enabled. Make sure the constant values are exactly the same as the script solution. +// NOTE: When N_PIECES < 4, unused RPM_LINE and RPM_POINT defines are not required and omitted. +/* +#define N_PIECES 4 // Integer (1-4). Number of piecewise lines used in script solution. +#define RPM_MAX 11686.4 // Max RPM of model. $30 > RPM_MAX will be limited to RPM_MAX. +#define RPM_MIN 202.5 // Min RPM of model. $31 < RPM_MIN will be limited to RPM_MIN. +#define RPM_POINT12 6145.4 // Used N_PIECES >=2. Junction point between lines 1 and 2. +#define RPM_POINT23 9627.8 // Used N_PIECES >=3. Junction point between lines 2 and 3. +#define RPM_POINT34 10813.9 // Used N_PIECES = 4. Junction point between lines 3 and 4. +#define RPM_LINE_A1 3.197101e-03 // Used N_PIECES >=1. A and B constants of line 1. +#define RPM_LINE_B1 -3.526076e-1 +#define RPM_LINE_A2 1.722950e-2 // Used N_PIECES >=2. A and B constants of line 2. +#define RPM_LINE_B2 8.588176e+01 +#define RPM_LINE_A3 5.901518e-02 // Used N_PIECES >=3. A and B constants of line 3. +#define RPM_LINE_B3 4.881851e+02 +#define RPM_LINE_A4 1.203413e-01 // Used N_PIECES = 4. A and B constants of line 4. +#define RPM_LINE_B4 1.151360e+03 +*/ + +#define N_PIECES 3 +#define RPM_MAX 23935.2 +#define RPM_MIN 2412.2 +#define RPM_POINT12 6283.9 +#define RPM_POINT23 11866.0 +#define RPM_LINE_A1 4.390865e-03 +#define RPM_LINE_B1 7.591787e+00 +#define RPM_LINE_A2 1.074874e-02 +#define RPM_LINE_B2 4.754411e+01 +#define RPM_LINE_A3 9.528342e-03 +#define RPM_LINE_B3 3.306286e+01 + + +/* --------------------------------------------------------------------------------------- + OEM Single File Configuration Option + + Instructions: Paste the cpu_map and default setting definitions below without an enclosing + #ifdef. Comment out the CPU_MAP_xxx and DEFAULT_xxx defines at the top of this file, and + the compiler will ignore the contents of defaults.h and cpu_map.h and use the definitions + below. +*/ + +// Paste CPU_MAP definitions here. + +// Paste default settings definitions here. + + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/coolant_control.cpp b/Grbl_Esp32-master/Grbl_Esp32/coolant_control.cpp new file mode 100644 index 0000000..10e0833 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/coolant_control.cpp @@ -0,0 +1,140 @@ +/* + coolant_control.c - coolant control methods + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + + +void coolant_init() +{ + #ifdef COOLANT_FLOOD_PIN + pinMode(COOLANT_FLOOD_PIN, OUTPUT); + #endif + + + #ifdef COOLANT_MIST_PIN + pinMode(COOLANT_MIST_PIN, OUTPUT); + #endif + coolant_stop(); +} + + +// Returns current coolant output state. Overrides may alter it from programmed state. +uint8_t coolant_get_state() +{ + uint8_t cl_state = COOLANT_STATE_DISABLE; + + #ifdef COOLANT_FLOOD_PIN + #ifdef INVERT_COOLANT_FLOOD_PIN + if (! digitalRead(COOLANT_FLOOD_PIN)) { + #else + if (digitalRead(COOLANT_FLOOD_PIN)) { + #endif + cl_state |= COOLANT_STATE_FLOOD; + } + #endif + + + #ifdef COOLANT_MIST_PIN + #ifdef INVERT_COOLANT_MIST_PIN + if (! digitalRead(COOLANT_MIST_PIN)) { + #else + if (digitalRead(COOLANT_MIST_PIN)) { + #endif + cl_state |= COOLANT_STATE_MIST; + } + #endif + + return(cl_state); + +} + + +// Directly called by coolant_init(), coolant_set_state(), and mc_reset(), which can be at +// an interrupt-level. No report flag set, but only called by routines that don't need it. +void coolant_stop() +{ + #ifdef COOLANT_FLOOD_PIN + #ifdef INVERT_COOLANT_FLOOD_PIN + digitalWrite(COOLANT_FLOOD_PIN, 1); + #else + digitalWrite(COOLANT_FLOOD_PIN, 0); + #endif + #endif + + #ifdef COOLANT_MIST_PIN + #ifdef INVERT_COOLANT_MIST_PIN + digitalWrite(COOLANT_MIST_PIN, 1); + #else + digitalWrite(COOLANT_MIST_PIN, 0); + #endif + #endif +} + + +// Main program only. Immediately sets flood coolant running state and also mist coolant, +// if enabled. Also sets a flag to report an update to a coolant state. +// Called by coolant toggle override, parking restore, parking retract, sleep mode, g-code +// parser program end, and g-code parser coolant_sync(). +void coolant_set_state(uint8_t mode) +{ + if (sys.abort) { return; } // Block during abort. + + if (mode == COOLANT_DISABLE) { + + coolant_stop(); + + } else { + + #ifdef COOLANT_FLOOD_PIN + if (mode & COOLANT_FLOOD_ENABLE) { + #ifdef INVERT_COOLANT_FLOOD_PIN + digitalWrite(COOLANT_FLOOD_PIN, 0); + #else + digitalWrite(COOLANT_FLOOD_PIN, 1); + #endif + } + #endif + + #ifdef COOLANT_MIST_PIN + if (mode & COOLANT_MIST_ENABLE) { + #ifdef INVERT_COOLANT_MIST_PIN + digitalWrite(COOLANT_MIST_PIN, 0); + #else + digitalWrite(COOLANT_MIST_PIN, 1); + #endif + } + #endif + + } + sys.report_ovr_counter = 0; // Set to report change immediately +} + + +// G-code parser entry-point for setting coolant state. Forces a planner buffer sync and bails +// if an abort or check-mode is active. +void coolant_sync(uint8_t mode) +{ + if (sys.state == STATE_CHECK_MODE) { return; } + protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. + coolant_set_state(mode); +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/coolant_control.h b/Grbl_Esp32-master/Grbl_Esp32/coolant_control.h new file mode 100644 index 0000000..1511319 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/coolant_control.h @@ -0,0 +1,50 @@ +/* + coolant_control.h - spindle control methods + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef coolant_control_h +#define coolant_control_h + +#define COOLANT_NO_SYNC false +#define COOLANT_FORCE_SYNC true + +#define COOLANT_STATE_DISABLE 0 // Must be zero +#define COOLANT_STATE_FLOOD bit(0) +#define COOLANT_STATE_MIST bit(1) + + +// Initializes coolant control pins. +void coolant_init(); + +// Returns current coolant output state. Overrides may alter it from programmed state. +uint8_t coolant_get_state(); + +// Immediately disables coolant pins. +void coolant_stop(); + +// Sets the coolant pins according to state specified. +void coolant_set_state(uint8_t mode); + +// G-code parser entry-point for setting coolant states. Checks for and executes additional conditions. +void coolant_sync(uint8_t mode); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/cpu_map.h b/Grbl_Esp32-master/Grbl_Esp32/cpu_map.h new file mode 100644 index 0000000..e558f09 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/cpu_map.h @@ -0,0 +1,1130 @@ +/* + cpu_map.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef cpu_map_h +//#define cpu_map_h + + /* + Not all pins can can work for all functions. + Check features like pull-ups, pwm, etc before + re-assigning numbers + + (gpio34-39) are inputs only and don't have software pullup/down functions + You MUST use external pull-ups or noise WILL cause problems. + + Unlike the AVR version certain pins are not forced into the same port. + Therefore, bit masks are not use the same way and typically should not be + changed. They are just preserved right now to make it easy to stay in sync + with AVR grbl + + */ + +//Set your pin definition +//let -1 to use default board pin +#define GRBL_SPI_SS -1 +#define GRBL_SPI_MOSI -1 +#define GRBL_SPI_MISO -1 +#define GRBL_SPI_SCK -1 +//Set your frequency +#define GRBL_SPI_FREQ 4000000 + +#ifdef CPU_MAP_TEST_DRIVE + /* + This is just a demo CPU_MAP for test driving. It creates a basic 3 axis machine, but + no actual i/o is used. It will appear that axes are moving, but they are virtual + + This can be uploaded to an unattached ESP32 or attached to unknown hardware. There is no risk + pins trying to output signals into a short, etc that could dmamge the ESP32 + + Assuming no changes have been made to the config.h file it is also a way to get he basic program + running so OTA (over the air) firmware loading can be done. + + */ + #define CPU_MAP_NAME "CPU_MAP_DEFAULT - Demo Only No I/O!" + + #define CONTROL_FEED_HOLD_PIN GPIO_NUM_21 // Uno A1 + + #define LIMIT_MASK 0 // no limit pins +#endif + +#ifdef CPU_MAP_ESP32 + // This is the CPU Map for the ESP32 Development Controller + // https://github.com/bdring/Grbl_ESP32_Development_Controller + // https://www.tindie.com/products/33366583/grbl_esp32-cnc-development-board-v35/ + + // Select the version (uncomment one of them) + #define CPU_MAP_V3p5 // version 3.5 and earlier + //#define CPU_MAP_V4 // version 4 or higher (in developement) + + #define USE_RMT_STEPS + + // It is OK to comment out any step and direction pins. This + // won't affect operation except that there will be no output + // form the pins. Grbl will virtually move the axis. This could + // be handy if you are using a servo, etc. for another axis. + #if (defined CPU_MAP_V4) + #define CPU_MAP_NAME "CPU_MAP_ESP32_V4" + #define X_DIRECTION_PIN GPIO_NUM_14 + #define Y_STEP_PIN GPIO_NUM_26 + #define Y_DIRECTION_PIN GPIO_NUM_15 + //#define COOLANT_FLOOD_PIN GPIO_NUM_25 + #define SPINDLE_PWM_PIN GPIO_NUM_2 + #define X_LIMIT_PIN GPIO_NUM_17 + #define Z_LIMIT_PIN GPIO_NUM_16 + #elif (defined CPU_MAP_V3p5) + #define CPU_MAP_NAME "CPU_MAP_ESP32_V3.5" + #define X_DIRECTION_PIN GPIO_NUM_26 + #define Y_STEP_PIN GPIO_NUM_14 + #define Y_DIRECTION_PIN GPIO_NUM_25 + //#define COOLANT_FLOOD_PIN GPIO_NUM_16 + #define SPINDLE_PWM_PIN GPIO_NUM_17 + #define X_LIMIT_PIN GPIO_NUM_13 // CRA4 + #define Z_LIMIT_PIN GPIO_NUM_15 + #endif + + #define X_STEP_PIN GPIO_NUM_12 + + #define X_RMT_CHANNEL 0 + + // #define Y_STEP_PIN (see versions above) + #define Y_RMT_CHANNEL 1 + + #define Z_STEP_PIN GPIO_NUM_27 + #define Z_DIRECTION_PIN GPIO_NUM_33 + #define Z_RMT_CHANNEL 2 + + // OK to comment out to use pin for other features + #define STEPPERS_DISABLE_PIN GPIO_NUM_2 // CRA4 + + //#define COOLANT_MIST_PIN GPIO_NUM_21 + #define USER_DIGITAL_PIN_1 GPIO_NUM_21 + #define USER_DIGITAL_PIN_2 GPIO_NUM_25 + + + #define SPINDLE_PWM_CHANNEL 0 + #define SPINDLE_PWM_BIT_PRECISION 8 + #define SPINDLE_ENABLE_PIN GPIO_NUM_22 + + // see versions for X and Z + #define Y_LIMIT_PIN GPIO_NUM_4 + #define LIMIT_MASK B111 + + #define PROBE_PIN GPIO_NUM_32 + + #define CONTROL_SAFETY_DOOR_PIN GPIO_NUM_35 // needs external pullup + #define CONTROL_RESET_PIN GPIO_NUM_34 // needs external pullup + #define CONTROL_FEED_HOLD_PIN GPIO_NUM_36 // needs external pullup + #define CONTROL_CYCLE_START_PIN GPIO_NUM_39 // needs external pullup +#endif + +#ifdef CPU_MAP_ESPDUINO_32 + // !!!! Experimental Untested !!!!! + // This is a CPU MAP for ESPDUINO-32 Boards and Protoneer V3 boards + // Note: Probe pin is mapped, but will require a 10k external pullup to 3.3V to work. + #define CPU_MAP_NAME "CPU_MAP_ESPDUINO_32" + + #define USE_RMT_STEPS + + #define X_STEP_PIN GPIO_NUM_26 // Uno D2 + #define X_DIRECTION_PIN GPIO_NUM_16 // Uno D5 + #define X_RMT_CHANNEL 0 + + #define Y_STEP_PIN GPIO_NUM_25 // Uno D3 + #define Y_DIRECTION_PIN GPIO_NUM_27 // Uno D6 + #define Y_RMT_CHANNEL 1 + + #define Z_STEP_PIN GPIO_NUM_17 // Uno D4 + #define Z_DIRECTION_PIN GPIO_NUM_14 // Uno D7 + #define Z_RMT_CHANNEL 2 + + // OK to comment out to use pin for other features + #define STEPPERS_DISABLE_PIN GPIO_NUM_12 // Uno D8 + + #define SPINDLE_PWM_PIN GPIO_NUM_19 // Uno D11 + #define SPINDLE_PWM_CHANNEL 0 + #define SPINDLE_PWM_BIT_PRECISION 8 // be sure to match this with SPINDLE_PWM_MAX_VALUE + #define SPINDLE_DIR_PIN GPIO_NUM_18 // Uno D13 + + #define COOLANT_FLOOD_PIN GPIO_NUM_34 // Uno A3 + #define COOLANT_MIST_PIN GPIO_NUM_36// Uno A4 + + #define X_LIMIT_PIN GPIO_NUM_13 // Uno D9 + #define Y_LIMIT_PIN GPIO_NUM_5 // Uno D10 + #define Z_LIMIT_PIN GPIO_NUM_19 // Uno D12 + #define LIMIT_MASK B111 + + #define PROBE_PIN GPIO_NUM_39 // Uno A5 + + // comment out #define IGNORE_CONTROL_PINS in config.h to use control pins + #define CONTROL_RESET_PIN GPIO_NUM_2 // Uno A0 + #define CONTROL_FEED_HOLD_PIN GPIO_NUM_4 // Uno A1 + #define CONTROL_CYCLE_START_PIN GPIO_NUM_35 // Uno A2 ... ESP32 needs external pullup +#endif + +#ifdef CPU_MAP_ESP32_ESC_SPINDLE + // This is an example of using a Brushless DC Hobby motor as + // a spindle motor + // See this wiki page for more info + // https://github.com/bdring/Grbl_Esp32/wiki/BESC-Spindle-Feature + + #define CPU_MAP_NAME "CPU_MAP_ESP32_ESC_SPINDLE" + + #define USE_RMT_STEPS + + #define X_STEP_PIN GPIO_NUM_12 + #define X_DIRECTION_PIN GPIO_NUM_14 + #define X_RMT_CHANNEL 0 + + #define Y_STEP_PIN GPIO_NUM_26 + #define Y_DIRECTION_PIN GPIO_NUM_15// #define Y_STEP_PIN (see versions above) + #define Y_RMT_CHANNEL 1 + + #define Z_STEP_PIN GPIO_NUM_27 + #define Z_DIRECTION_PIN GPIO_NUM_33 + #define Z_RMT_CHANNEL 2 + + // OK to comment out to use pin for other features + #define STEPPERS_DISABLE_PIN GPIO_NUM_13 + + #define SPINDLE_PWM_PIN GPIO_NUM_2 + #define SPINDLE_ENABLE_PIN GPIO_NUM_22 + #define SPINDLE_PWM_CHANNEL 0 + + // Begin RC ESC Based Spindle Information ====================== + #define SPINDLE_PWM_BIT_PRECISION 16 // 16 bit recommended for ESC (don't change) + /* + Important ESC Settings + $33=50 // Hz this is the typical good frequency for an ESC + + Determine the typical min and max pulse length of your ESC + min_pulse is typically 1ms (0.001 sec) or less + max_pulse is typically 2ms (0.002 sec) or more + + determine PWM_period. It is (1/freq) if freq = 50...period = 0.02 + + determine pulse length for min_pulse and max_pulse in percent. + + (pulse / PWM_period) + + min_pulse = (0.001 / 0.02) = 0.035 = 3.5% so ... $33 and $34 = 3.5 + max_pulse = (0.002 / .02) = 0.1 = 10% so ... $36=10 + + */ + + + // End RC ESC Based Spindle #defines =========================== + + #define X_LIMIT_PIN GPIO_NUM_17 + #define Y_LIMIT_PIN GPIO_NUM_4 + #define Z_LIMIT_PIN GPIO_NUM_16 + #define LIMIT_MASK B111 + + #define PROBE_PIN GPIO_NUM_32 + + #define CONTROL_SAFETY_DOOR_PIN GPIO_NUM_35 // needs external pullup + #define CONTROL_RESET_PIN GPIO_NUM_34 // needs external pullup + #define CONTROL_FEED_HOLD_PIN GPIO_NUM_36 // needs external pullup + #define CONTROL_CYCLE_START_PIN GPIO_NUM_39 // needs external pullup +#endif + +#ifdef CPU_MAP_PEN_LASER // The Buildlog.net pen laser controller V1 & V2 + + // For pen mode be sure to uncomment #define USE_PEN_SERVO in config.h + // For solenoid mode be sure to uncomment #define USE_PEN_SERVO in config.h + // For laser mode, you do not need to change anything + // Note: You can use all 3 modes at the same time if you want + + #define CPU_MAP_NAME "CPU_MAP_PEN_LASER" + + #define USE_RMT_STEPS + + // Pick a board version + //#define PEN_LASER_V1 + #define PEN_LASER_V2 + + #define X_STEP_PIN GPIO_NUM_12 + #define X_DIRECTION_PIN GPIO_NUM_26 + #define X_RMT_CHANNEL 0 + + + #define Y_STEP_PIN GPIO_NUM_14 + #define Y_DIRECTION_PIN GPIO_NUM_25 + #define Y_RMT_CHANNEL 1 + + #define STEPPERS_DISABLE_PIN GPIO_NUM_13 + + #ifdef PEN_LASER_V1 + #define X_LIMIT_PIN GPIO_NUM_2 + #endif + #ifdef PEN_LASER_V2 + #define X_LIMIT_PIN GPIO_NUM_15 + #endif + #define Y_LIMIT_PIN GPIO_NUM_4 + #define LIMIT_MASK B11 + + // If SPINDLE_PWM_PIN is commented out, this frees up the pin, but Grbl will still + // use a virtual spindle. Do not comment out the other parameters for the spindle. + #define SPINDLE_PWM_PIN GPIO_NUM_17 // Laser PWM + #define SPINDLE_PWM_CHANNEL 0 + #define SPINDLE_PWM_BIT_PRECISION 8 // be sure to match this with SPINDLE_PWM_MAX_VALUE + + + #define USING_SERVO // uncomment to use this feature + //#define USING_SOLENOID // uncomment to use this feature + + #ifdef USING_SERVO + #define USE_SERVO_AXES + #define SERVO_Z_PIN GPIO_NUM_27 + #define SERVO_Z_CHANNEL_NUM 3 + #define SERVO_Z_RANGE_MIN 0 + #define SERVO_Z_RANGE_MAX 10 + #endif + + #ifdef USING_SOLENOID + #define USE_PEN_SOLENOID + #define SOLENOID_PEN_PIN GPIO_NUM_16 + #define SOLENOID_CHANNEL_NUM 6 + #endif + + + // defaults + #define DEFAULT_STEP_PULSE_MICROSECONDS 3 + #define DEFAULT_STEPPER_IDLE_LOCK_TIME 250 // stay on + + #define DEFAULT_STEPPING_INVERT_MASK 0 // uint8_t + #define DEFAULT_DIRECTION_INVERT_MASK 0 // uint8_t + #define DEFAULT_INVERT_ST_ENABLE 0 // boolean + #define DEFAULT_INVERT_LIMIT_PINS 1 // boolean + #define DEFAULT_INVERT_PROBE_PIN 0 // boolean + + #define DEFAULT_STATUS_REPORT_MASK 1 + + #define DEFAULT_JUNCTION_DEVIATION 0.01 // mm + #define DEFAULT_ARC_TOLERANCE 0.002 // mm + #define DEFAULT_REPORT_INCHES 0 // false + + #define DEFAULT_SOFT_LIMIT_ENABLE 0 // false + #define DEFAULT_HARD_LIMIT_ENABLE 0 // false + + #define DEFAULT_HOMING_ENABLE 0 + #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir Z, negative X,Y + #define DEFAULT_HOMING_FEED_RATE 200.0 // mm/min + #define DEFAULT_HOMING_SEEK_RATE 1000.0 // mm/min + #define DEFAULT_HOMING_DEBOUNCE_DELAY 250 // msec (0-65k) + #define DEFAULT_HOMING_PULLOFF 3.0 // mm + + #define DEFAULT_SPINDLE_RPM_MAX 1000.0 // rpm + #define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm + + #define DEFAULT_LASER_MODE 0 // false + + #define DEFAULT_X_STEPS_PER_MM 80 + #define DEFAULT_Y_STEPS_PER_MM 80 + #define DEFAULT_Z_STEPS_PER_MM 100.0 // This is percent in servo mode...used for calibration + + #define DEFAULT_X_MAX_RATE 5000.0 // mm/min + #define DEFAULT_Y_MAX_RATE 5000.0 // mm/min + #define DEFAULT_Z_MAX_RATE 5000.0 // mm/min + + #define DEFAULT_X_ACCELERATION (50.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 + #define DEFAULT_Y_ACCELERATION (50.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 + #define DEFAULT_Z_ACCELERATION (50.0*60*60) + + #define DEFAULT_X_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. + #define DEFAULT_Y_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. + #define DEFAULT_Z_MAX_TRAVEL 100.0 // This is percent in servo mode...used for calibration + + +#endif + +#ifdef CPU_MAP_MIDTBOT // Buildlog.net midtbot + + #define CPU_MAP_NAME "CPU_MAP_MIDTBOT" + + #define USE_RMT_STEPS + + #define X_STEP_PIN GPIO_NUM_12 + #define Y_STEP_PIN GPIO_NUM_14 + #define X_RMT_CHANNEL 0 + + #define X_DIRECTION_PIN GPIO_NUM_26 + #define Y_DIRECTION_PIN GPIO_NUM_25 + #define Y_RMT_CHANNEL 1 + + #ifndef COREXY // maybe set in config.h + #define COREXY + #endif + + #define STEPPERS_DISABLE_PIN GPIO_NUM_13 + + #define X_LIMIT_PIN GPIO_NUM_2 + #define Y_LIMIT_PIN GPIO_NUM_4 + #define LIMIT_MASK B11 + + #ifndef USE_SERVO_AXES // maybe set in config.h + #define USE_SERVO_AXES + #endif + + #define SERVO_Z_PIN GPIO_NUM_27 + #define SERVO_Z_CHANNEL_NUM 5 + #define SERVO_Z_RANGE_MIN 0.0 + #define SERVO_Z_RANGE_MAX 5.0 + #define SERVO_Z_HOMING_TYPE SERVO_HOMING_TARGET // during homing it will instantly move to a target value + #define SERVO_Z_HOME_POS SERVO_Z_RANGE_MAX // move to max during homing + #define SERVO_Z_MPOS false // will not use mpos, uses work coordinates + + #ifndef IGNORE_CONTROL_PINS // maybe set in config.h + #define IGNORE_CONTROL_PINS + #endif + + + + // redefine some stuff from config.h + #ifdef HOMING_CYCLE_0 + #undef HOMING_CYCLE_0 + #endif + #define HOMING_CYCLE_0 (1<v4B-Xgs`_=LZ&R zdC!m)-du0BS{?IS&05UNdx|;zFEA6{=!+Qs*gJYzr|&4$OQ98Gac=6n&GZs;`q}>L zxjn16@UFl!`272Xb8D+bf9$C>;SlDa4JYuFaBj^tdz#S~5v>n#W)FQlYR;+kz|Uh( zKWgL^=&~;p2k-{=`0R^>b8D^*lDA9l-W_o57^<)gM-XSZ=G@v6*kiAC_txPm?eB3cKI<8#?#@5fyC$3pXWZ~)g3{+MUYwI6pM z{dvUayJ;jUdF?NA|L4u0OYIG*$tjzQ{ANr@57W}=sC2gfeOjp_?Ttt`W76xI_ zJ8|1CZME~Nn{jv5gWM3m0u41$pO?eu?Ll3)ulVWDr$vfs^Eryc_qr{$)YjF$%7(wr zI@)y0p_Ahmah|yzn3(f&Im|HFcgB33Ts5^e_rmR4*2s)!p^YpKehf>YUOL*z@P1k* zqUq~>DGbI@NLp`yJrePCl#DQ^@6{F^p^ehPV=+*y+T{f+mwWw=eP926z5m+E{WI;_ zSy6?wy44x|X778#+h>!(F~8fk*CV*7Ch?YYGuPD5{rj`SDld9!cRm`I#^cN?2(9U! z&$Gc)-;Yme#ZyPXx$d2{&j|H^W=-p|PV*Sqt1jNSMJuq!A5PoajqjLo`fK}fZ~m4z zLj0a1XHXDL)E#A@QIAt5QGqenc$e}}c8h*_Stor{XmAmU?C0mzS7FfSSPOiujXLnL zc&S-mE#qm&2J26v%}q<^Uve5h{||Q{akU`-(`RCyNpqhr$`u`qVSMuRqJytT%-FsS ztl_GzNv89yhoZ^)9K-!z56(ySVqJf~H?rQh)xBRc=823jd>OBdAimmaPaAKmHn*=x zP@|6?+^|=T!p%9D?hG``k$+l2v>WKh2p%?g*3}r7ZtmSiJf+Pz>=2tVKRfs?V&1mM zHElgJy*w|h8`lY0BMZ0r!W=#~zS6*a;r*!HCVH8zh|F}=w6w0g5M0}UQ%PRB-(wAY4)0Sx8*(xD7&m%t_3nsDf}gD z)1_@?iKa{+?Fb;Bx>h5S4kE}Dc`+H|D(A;K!ewX+<@Zg7$$7VLM<_`;M93v~9p^u8@a!Ped5|-PjydpCBMstB?YZ(VKIZo76?y^!wN?KF zHo5=C^;X**-l!14lYAi|DYt6p+Y8db=kh9;2-9`)s!1(E>!eL5xOv(!?=5RJ5LRS+ zI;Pi;Ze`)4{_OGz9UrTg0Skc_&yP@Q75}pM(FKyEl^AQ_mjS7w46A$VIf^x3t$)EE z62lCbQxqpibVDdX9z>y$?aJl1ccqMXX*sEY->GU+a87$B+8@MeIhW7wP$$PPK`FUY zv7sno+t*9IGvJjM*Nibtc{aC^TGbMN41+OVn7A>}X;{+Zx;3wQyZ@cjsZ&XXr_&di zbxdQxUrg=f_-&0ANH-sLEo8h<=YGNQ*06Db2j51@Rt=1>7E6salv)JMR3TGlv=<2UQOLhmmkuu*>w@xT(FJ-ULK?MCMGij94#x)vWVmGPOvi`y z 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%0DdRl?ZA6;!JN)nka?IWYwM)67!iGXU5`5QC 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 z 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 literal 0 HcmV?d00001 diff --git a/Grbl_Esp32-master/Grbl_Esp32/defaults.h b/Grbl_Esp32-master/Grbl_Esp32/defaults.h new file mode 100644 index 0000000..3d7514f --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/defaults.h @@ -0,0 +1,320 @@ +/* + defaults.h - defaults settings configuration file + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +/* The defaults.h file serves as a central default settings selector for different machine + types, from DIY CNC mills to CNC conversions of off-the-shelf machines. The settings + files listed here are supplied by users, so your results may vary. However, this should + give you a good starting point as you get to know your machine and tweak the settings for + your nefarious needs. + NOTE: Ensure one and only one of these DEFAULTS_XXX values is defined in config.h */ + +#ifndef defaults_h + +/* + All of these settings check to see if they have been defined already + before defining them. This allows to to easily set them eslewhere. + You only need to set ones that are important or unique to your + machine. The rest will be pulled from here. +*/ + + // Grbl generic default settings. Should work across different machines. +#ifndef DEFAULT_STEP_PULSE_MICROSECONDS + #define DEFAULT_STEP_PULSE_MICROSECONDS 3 // $0 +#endif + +#ifndef DEFAULT_STEPPER_IDLE_LOCK_TIME + #define DEFAULT_STEPPER_IDLE_LOCK_TIME 250 // $1 msec (0-254, 255 keeps steppers enabled) +#endif + +#ifndef DEFAULT_STEPPING_INVERT_MASK + #define DEFAULT_STEPPING_INVERT_MASK 0 // $2 uint8_t +#endif + +#ifndef DEFAULT_DIRECTION_INVERT_MASK + #define DEFAULT_DIRECTION_INVERT_MASK 0 // $3 uint8_ +#endif + +#ifndef DEFAULT_INVERT_ST_ENABLE + #define DEFAULT_INVERT_ST_ENABLE 0 // $4 boolean +#endif + +#ifndef DEFAULT_INVERT_LIMIT_PINS + #define DEFAULT_INVERT_LIMIT_PINS 1 // $5 boolean +#endif + +#ifndef DEFAULT_INVERT_PROBE_PIN +#define DEFAULT_INVERT_PROBE_PIN 0 // $6 boolean +#endif + +#ifndef DEFAULT_STATUS_REPORT_MASK + #define DEFAULT_STATUS_REPORT_MASK 1 // $10 +#endif + +#ifndef DEFAULT_JUNCTION_DEVIATION + #define DEFAULT_JUNCTION_DEVIATION 0.01 // $11 mm +#endif + +#ifndef DEFAULT_ARC_TOLERANCE + #define DEFAULT_ARC_TOLERANCE 0.002 // $12 mm +#endif + +#ifndef DEFAULT_REPORT_INCHES +#define DEFAULT_REPORT_INCHES 0 // $13 false +#endif + +#ifndef DEFAULT_SOFT_LIMIT_ENABLE + #define DEFAULT_SOFT_LIMIT_ENABLE 0 // $20 false +#endif + +#ifndef DEFAULT_HARD_LIMIT_ENABLE +#define DEFAULT_HARD_LIMIT_ENABLE 0 // $21 false +#endif + +#ifndef DEFAULT_HOMING_ENABLE + #define DEFAULT_HOMING_ENABLE 0 // $22 false +#endif + +#ifndef DEFAULT_HOMING_DIR_MASK +#define DEFAULT_HOMING_DIR_MASK 3 // $23 move positive dir Z, negative X,Y +#endif + +#ifndef DEFAULT_HOMING_FEED_RATE + #define DEFAULT_HOMING_FEED_RATE 200.0 // $24 mm/min +#endif + +#ifndef DEFAULT_HOMING_SEEK_RATE +#define DEFAULT_HOMING_SEEK_RATE 2000.0 // $25 mm/min +#endif + +#ifndef DEFAULT_HOMING_DEBOUNCE_DELAY + #define DEFAULT_HOMING_DEBOUNCE_DELAY 250 // $26 msec (0-65k) +#endif + +#ifndef DEFAULT_HOMING_PULLOFF + #define DEFAULT_HOMING_PULLOFF 1.0 // $27 mm +#endif + +// ======== sPINDLE STUFF ==================== + +#ifndef DEFAULT_SPINDLE_FREQ + #define DEFAULT_SPINDLE_FREQ 5000.0 // $33 Hz (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_OFF_VALUE + #define DEFAULT_SPINDLE_OFF_VALUE 0.0 // $34 Percent (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_MIN_VALUE + #define DEFAULT_SPINDLE_MIN_VALUE 0.0 // $35 Percent (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_MAX_VALUE + #define DEFAULT_SPINDLE_MAX_VALUE 100.0 // $36 Percent (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_RPM_MAX + #define DEFAULT_SPINDLE_RPM_MAX 1000.0 // rpm +#endif + + +#ifndef DEFAULT_SPINDLE_RPM_MIN + #define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm +#endif + +#ifndef DEFAULT_LASER_MODE + #define DEFAULT_LASER_MODE 0 // false +#endif + + +// =========== AXIS RESOLUTION ====== + +#ifndef DEFAULT_X_STEPS_PER_MM + #define DEFAULT_X_STEPS_PER_MM 800.0 +#endif +#ifndef DEFAULT_Y_STEPS_PER_MM + #define DEFAULT_Y_STEPS_PER_MM 800.0 +#endif +#ifndef DEFAULT_Z_STEPS_PER_MM + #define DEFAULT_Z_STEPS_PER_MM 800.0 +#endif +#ifndef DEFAULT_A_STEPS_PER_MM + #define DEFAULT_A_STEPS_PER_MM 800.0 +#endif +#ifndef DEFAULT_B_STEPS_PER_MM + #define DEFAULT_B_STEPS_PER_MM 800.0 +#endif +#ifndef DEFAULT_C_STEPS_PER_MM + #define DEFAULT_C_STEPS_PER_MM 800.0 +#endif + +// ============ AXIS MAX SPPED ========= + +#ifndef DEFAULT_X_MAX_RATE + #define DEFAULT_X_MAX_RATE 5000.0 // mm/min +#endif +#ifndef DEFAULT_Y_MAX_RATE + #define DEFAULT_Y_MAX_RATE 5000.0 // mm/min +#endif +#ifndef DEFAULT_Z_MAX_RATE + #define DEFAULT_Z_MAX_RATE 5000.0 // mm/min +#endif +#ifndef DEFAULT_A_MAX_RATE + #define DEFAULT_A_MAX_RATE 5000.0 // mm/min +#endif +#ifndef DEFAULT_B_MAX_RATE + #define DEFAULT_B_MAX_RATE 5000.0 // mm/min +#endif +#ifndef DEFAULT_C_MAX_RATE + #define DEFAULT_C_MAX_RATE 5000.0 // mm/min +#endif + +// ============== Axis Acceleration ========= + +#ifndef DEFAULT_X_ACCELERATION + #define DEFAULT_X_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif +#ifndef DEFAULT_Y_ACCELERATION + #define DEFAULT_Y_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif +#ifndef DEFAULT_Z_ACCELERATION + #define DEFAULT_Z_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif +#ifndef DEFAULT_A_ACCELERATION + #define DEFAULT_A_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif +#ifndef DEFAULT_B_ACCELERATION + #define DEFAULT_B_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif +#ifndef DEFAULT_C_ACCELERATION + #define DEFAULT_C_ACCELERATION (200.0*60*60) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#endif + +// ========= AXIS MAX TRAVEL ============ + +#ifndef DEFAULT_X_MAX_TRAVEL + #define DEFAULT_X_MAX_TRAVEL 300.0 // $130 mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_Y_MAX_TRAVEL + #define DEFAULT_Y_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_Z_MAX_TRAVEL + #define DEFAULT_Z_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_A_TRAVEL + #define DEFAULT_A_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_B_MAX_TRAVEL + #define DEFAULT_B_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_C_MAX_TRAVEL + #define DEFAULT_C_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif + +// ========== Motor current (SPI Drivers ) ============= +#ifndef DEFAULT_X_CURRENT + #define DEFAULT_X_CURRENT 0.25 // $140 current in amps (extended set) +#endif +#ifndef DEFAULT_Y_CURRENT + #define DEFAULT_Y_CURRENT 0.25 // $141 current in amps (extended set) +#endif +#ifndef DEFAULT_Z_CURRENT + #define DEFAULT_Z_CURRENT 0.25 // $142 current in amps (extended set) +#endif +#ifndef DEFAULT_A_CURRENT + #define DEFAULT_A_CURRENT 0.25 // $143 current in amps (extended set) +#endif +#ifndef DEFAULT_B_CURRENT + #define DEFAULT_B_CURRENT 0.25 // $144 current in amps (extended set) +#endif +#ifndef DEFAULT_C_CURRENT + #define DEFAULT_C_CURRENT 0.25 // $145 current in amps (extended set) +#endif + +// ========== Motor hold current (SPI Drivers ) ============= + +#ifndef DEFAULT_X_HOLD_CURRENT + #define DEFAULT_X_HOLD_CURRENT 50 // $150 percent of run current (extended set) +#endif +#ifndef DEFAULT_Y_HOLD_CURRENT + #define DEFAULT_Y_HOLD_CURRENT 50 // $151 percent of run current (extended set) +#endif +#ifndef DEFAULT_Z_HOLD_CURRENT + #define DEFAULT_Z_HOLD_CURRENT 50 // $152 percent of run current (extended set) +#endif +#ifndef DEFAULT_A_HOLD_CURRENT + #define DEFAULT_A_HOLD_CURRENT 50 // $153 percent of run current (extended set) +#endif +#ifndef DEFAULT_B_HOLD_CURRENT + #define DEFAULT_B_HOLD_CURRENT 50 // $154 percent of run current (extended set) +#endif +#ifndef DEFAULT_C_HOLD_CURRENT + #define DEFAULT_C_HOLD_CURRENT 50 // $154 percent of run current (extended set) +#endif + +// ========== Microsteps (SPI Drivers ) ================ + +#ifndef DEFAULT_X_MICROSTEPS + #define DEFAULT_X_MICROSTEPS 16 // $160 micro steps (extended set) +#endif +#ifndef DEFAULT_Y_MICROSTEPS + #define DEFAULT_Y_MICROSTEPS 16 // $161 micro steps (extended set) +#endif +#ifndef DEFAULT_Z_MICROSTEPS + #define DEFAULT_Z_MICROSTEPS 16 // $162 micro steps (extended set) +#endif +#ifndef DEFAULT_A_MICROSTEPS + #define DEFAULT_A_MICROSTEPS 16 // $163 micro steps (extended set) +#endif +#ifndef DEFAULT_B_MICROSTEPS + #define DEFAULT_B_MICROSTEPS 16 // $164 micro steps (extended set) +#endif +#ifndef DEFAULT_C_MICROSTEPS + #define DEFAULT_C_MICROSTEPS 16 // $165 micro steps (extended set) +#endif + +// ========== Stallguard (SPI Drivers ) ================ + +#ifndef DEFAULT_X_STALLGUARD + #define DEFAULT_X_STALLGUARD 16 // $170 stallguard (extended set) +#endif +#ifndef DEFAULT_Y_STALLGUARD + #define DEFAULT_Y_STALLGUARD 16 // $171 stallguard (extended set) +#endif +#ifndef DEFAULT_Z_STALLGUARD + #define DEFAULT_Z_STALLGUARD 16 // $172 stallguard (extended set) +#endif +#ifndef DEFAULT_A_STALLGUARD + #define DEFAULT_A_STALLGUARD 16 // $173 stallguard (extended set) +#endif +#ifndef DEFAULT_B_STALLGUARD + #define DEFAULT_B_STALLGUARD 16 // $174 stallguard (extended set) +#endif +#ifndef DEFAULT_C_STALLGUARD + #define DEFAULT_C_STALLGUARD 16 // $175 stallguard (extended set) +#endif + + + + + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/espresponse.cpp b/Grbl_Esp32-master/Grbl_Esp32/espresponse.cpp new file mode 100644 index 0000000..7c51da9 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/espresponse.cpp @@ -0,0 +1,112 @@ +/* + espresponse.cpp - GRBL_ESP response class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "config.h" +#include "espresponse.h" +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +#include "web_server.h" +#include +#endif + +#include "report.h" + +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +ESPResponseStream::ESPResponseStream(WebServer * webserver){ + _header_sent=false; + _webserver = webserver; + _client = CLIENT_WEBUI; +} +#endif + +ESPResponseStream::ESPResponseStream(){ + _client = CLIENT_INPUT; +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + _header_sent=false; + _webserver = NULL; +#endif +} + +ESPResponseStream::ESPResponseStream(uint8_t client, bool byid){ + (void)byid; //fake parameter to avoid confusion with pointer one (NULL == 0) + _client = client; +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + _header_sent=false; + _webserver = NULL; +#endif +} + +void ESPResponseStream::println(const char *data){ + print(data); + if (_client == CLIENT_TELNET) print("\r\n"); + else print("\n"); +} + +//helper to format size to readable string +String ESPResponseStream::formatBytes (uint64_t bytes) +{ + if (bytes < 1024) { + return String ((uint16_t)bytes) + " B"; + } else if (bytes < (1024 * 1024) ) { + return String ((float)(bytes / 1024.0),2) + " KB"; + } else if (bytes < (1024 * 1024 * 1024) ) { + return String ((float)(bytes / 1024.0 / 1024.0),2) + " MB"; + } else { + return String ((float)(bytes / 1024.0 / 1024.0 / 1024.0),2) + " GB"; + } +} + +void ESPResponseStream::print(const char *data){ + if (_client == CLIENT_INPUT) return; +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + if (_webserver) { + if (!_header_sent) { + _webserver->setContentLength(CONTENT_LENGTH_UNKNOWN); + _webserver->sendHeader("Content-Type","text/html"); + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200); + _header_sent = true; + } + _buffer+=data; + if (_buffer.length() > 1200) { + //send data + _webserver->sendContent(_buffer); + //reset buffer + _buffer = ""; + } + return; + } +#endif + if (_client == CLIENT_WEBUI) return; //this is sanity check + grbl_send(_client, data); +} + +void ESPResponseStream::flush(){ +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + if (_webserver) { + if(_header_sent) { + //send data + if(_buffer.length() > 0)_webserver->sendContent(_buffer); + //close connection + _webserver->sendContent(""); + } + _header_sent = false; + _buffer = ""; + } +#endif +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/espresponse.h b/Grbl_Esp32-master/Grbl_Esp32/espresponse.h new file mode 100644 index 0000000..a907db0 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/espresponse.h @@ -0,0 +1,50 @@ +/* + espresponse.h - GRBL_ESP response class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ESPRESPONSE_h +#define ESPRESPONSE_h +#include "config.h" + +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +class WebServer; +#endif + +class ESPResponseStream{ + public: + void print(const char *data); + void println(const char *data); + void flush(); + static String formatBytes (uint64_t bytes); + uint8_t client() {return _client;} +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + ESPResponseStream(WebServer * webserver); +#endif + ESPResponseStream(uint8_t client, bool byid = true); + ESPResponseStream(); + private: + uint8_t _client; +#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + bool _header_sent; + WebServer * _webserver; + String _buffer; +#endif +}; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/gcode.cpp b/Grbl_Esp32-master/Grbl_Esp32/gcode.cpp new file mode 100644 index 0000000..4000026 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/gcode.cpp @@ -0,0 +1,1460 @@ +/* + gcode.c - rs274/ngc parser. + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +// NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an +// arbitrary value, and some GUIs may require more. So we increased it based on a max safe +// value when converting a float (7.2 digit precision)s to an integer. +#define MAX_LINE_NUMBER 10000000 +#define MAX_TOOL_NUMBER 255 // Limited by max unsigned 8-bit value + +#define AXIS_COMMAND_NONE 0 +#define AXIS_COMMAND_NON_MODAL 1 +#define AXIS_COMMAND_MOTION_MODE 2 +#define AXIS_COMMAND_TOOL_LENGTH_OFFSET 3 // *Undefined but required + +// Declare gc extern struct +parser_state_t gc_state; +parser_block_t gc_block; + +#define FAIL(status) return(status); + + +void gc_init() +{ + memset(&gc_state, 0, sizeof(parser_state_t)); + + // Load default G54 coordinate system. + if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { + report_status_message(STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); + } +} + + +// Sets g-code parser position in mm. Input in steps. Called by the system abort and hard +// limit pull-off routines. +void gc_sync_position() +{ + system_convert_array_steps_to_mpos(gc_state.position,sys_position); +} + + +// Executes one line of 0-terminated G-Code. The line is assumed to contain only uppercase +// characters and signed floating point values (no whitespace). Comments and block delete +// characters have been removed. In this function, all units and positions are converted and +// exported to grbl's internal functions in terms of (mm, mm/min) and absolute machine +// coordinates, respectively. +uint8_t gc_execute_line(char *line, uint8_t client) +{ + /* ------------------------------------------------------------------------------------- + STEP 1: Initialize parser block struct and copy current g-code state modes. The parser + updates these modes and commands as the block line is parser and will only be used and + executed after successful error-checking. The parser block struct also contains a block + values struct, word tracking variables, and a non-modal commands tracker for the new + block. This struct contains all of the necessary information to execute the block. */ + + memset(&gc_block, 0, sizeof(parser_block_t)); // Initialize the parser block struct. + memcpy(&gc_block.modal,&gc_state.modal,sizeof(gc_modal_t)); // Copy current modes + + uint8_t axis_command = AXIS_COMMAND_NONE; + uint8_t axis_0, axis_1, axis_linear; + uint8_t coord_select = 0; // Tracks G10 P coordinate selection for execution + + // Initialize bitflag tracking variables for axis indices compatible operations. + uint8_t axis_words = 0; // XYZ tracking + uint8_t ijk_words = 0; // IJK tracking + + // Initialize command and value words and parser flags variables. + uint16_t command_words = 0; // Tracks G and M command words. Also used for modal group violations. + uint16_t value_words = 0; // Tracks value words. + uint8_t gc_parser_flags = GC_PARSER_NONE; + + // Determine if the line is a jogging motion or a normal g-code block. + if (line[0] == '$') { // NOTE: `$J=` already parsed when passed to this function. + // Set G1 and G94 enforced modes to ensure accurate error checks. + gc_parser_flags |= GC_PARSER_JOG_MOTION; + gc_block.modal.motion = MOTION_MODE_LINEAR; + gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; +#ifdef USE_LINE_NUMBERS + gc_block.values.n = JOG_LINE_NUMBER; // Initialize default line number reported during jog. +#endif + } + + /* ------------------------------------------------------------------------------------- + STEP 2: Import all g-code words in the block line. A g-code word is a letter followed by + a number, which can either be a 'G'/'M' command or sets/assigns a command value. Also, + perform initial error-checks for command word modal group violations, for any repeated + words, and for negative values set for the value words F, N, P, T, and S. */ + + uint8_t word_bit = 0; // Bit-value for assigning tracking variables + uint8_t char_counter; + char letter; + float value; + uint8_t int_value = 0; + uint16_t mantissa = 0; + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + char_counter = 3; // Start parsing after `$J=` + } else { + char_counter = 0; + } + + while (line[char_counter] != 0) { // Loop until no more g-code words in line. + + // Import the next g-code word, expecting a letter followed by a value. Otherwise, error out. + letter = line[char_counter]; + if((letter < 'A') || (letter > 'Z')) { + FAIL(STATUS_EXPECTED_COMMAND_LETTER); // [Expected word letter] + } + char_counter++; + if (!read_float(line, &char_counter, &value)) { + FAIL(STATUS_BAD_NUMBER_FORMAT); // [Expected word value] + } + + // Convert values to smaller uint8 significand and mantissa values for parsing this word. + // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more + // accurate than the NIST gcode requirement of x10 when used for commands, but not quite + // accurate enough for value words that require integers to within 0.0001. This should be + // a good enough compromise and catch most all non-integer errors. To make it compliant, + // we would simply need to change the mantissa to int16, but this add compiled flash space. + // Maybe update this later. + int_value = trunc(value); + mantissa = round(100*(value - int_value)); // Compute mantissa for Gxx.x commands. + // NOTE: Rounding must be used to catch small floating point errors. + + // Check if the g-code word is supported or errors due to modal group violations or has + // been repeated in the g-code block. If ok, update the command or record its value. + switch(letter) { + + /* 'G' and 'M' Command Words: Parse commands and check for modal group violations. + NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ + + case 'G': + // Determine 'G' command and its modal group + switch(int_value) { + case 10: + case 28: + case 30: + case 92: + // Check for G10/28/30/92 being called with G0/1/2/3/38 on same block. + // * G43.1 is also an axis command but is not explicitly defined this way. + if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1 + if (axis_command) { + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] + } + axis_command = AXIS_COMMAND_NON_MODAL; + } + // No break. Continues to next line. + case 4: + case 53: + word_bit = MODAL_GROUP_G0; + gc_block.non_modal_command = int_value; + if ((int_value == 28) || (int_value == 30) || (int_value == 92)) { + if (!((mantissa == 0) || (mantissa == 10))) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); + } + gc_block.non_modal_command += mantissa; + mantissa = 0; // Set to zero to indicate valid non-integer G command. + } + break; + case 0: + case 1: + case 2: + case 3: + + case 38: + #ifndef PROBE_PIN //only allow G38 "Probe" commands if a probe pin is defined. + if (int_value == 38) { + grbl_send(CLIENT_SERIAL, "[MSG:No probe pin defined!]\r\n"); + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] + } + #endif + // Check for G0/1/2/3/38 being called with G10/28/30/92 on same block. + // * G43.1 is also an axis command but is not explicitly defined this way. + if (axis_command) { + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] + } + axis_command = AXIS_COMMAND_MOTION_MODE; + // No break. Continues to next line. + case 80: + word_bit = MODAL_GROUP_G1; + gc_block.modal.motion = int_value; + if (int_value == 38) { + if (!((mantissa == 20) || (mantissa == 30) || (mantissa == 40) || (mantissa == 50))) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G38.x command] + } + gc_block.modal.motion += (mantissa/10)+100; + mantissa = 0; // Set to zero to indicate valid non-integer G command. + } + break; + case 17: + case 18: + case 19: + word_bit = MODAL_GROUP_G2; + gc_block.modal.plane_select = int_value - 17; + break; + case 90: + case 91: + if (mantissa == 0) { + word_bit = MODAL_GROUP_G3; + gc_block.modal.distance = int_value - 90; + } else { + word_bit = MODAL_GROUP_G4; + if ((mantissa != 10) || (int_value == 90)) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G90.1 not supported] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + // Otherwise, arc IJK incremental mode is default. G91.1 does nothing. + } + break; + case 93: + case 94: + word_bit = MODAL_GROUP_G5; + gc_block.modal.feed_rate = 94 - int_value; + break; + case 20: + case 21: + word_bit = MODAL_GROUP_G6; + gc_block.modal.units = 21 - int_value; + break; + case 40: + word_bit = MODAL_GROUP_G7; + // NOTE: Not required since cutter radius compensation is always disabled. Only here + // to support G40 commands that often appear in g-code program headers to setup defaults. + // gc_block.modal.cutter_comp = CUTTER_COMP_DISABLE; // G40 + break; + case 43: + case 49: + word_bit = MODAL_GROUP_G8; + // NOTE: The NIST g-code standard vaguely states that when a tool length offset is changed, + // there cannot be any axis motion or coordinate offsets updated. Meaning G43, G43.1, and G49 + // all are explicit axis commands, regardless if they require axis words or not. + if (axis_command) { + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); + } // [Axis word/command conflict] } + axis_command = AXIS_COMMAND_TOOL_LENGTH_OFFSET; + if (int_value == 49) { // G49 + gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL; + } else if (mantissa == 10) { // G43.1 + gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC; + } else { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G43.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; + case 54: + case 55: + case 56: + case 57: + case 58: + case 59: + // NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.) + word_bit = MODAL_GROUP_G12; + gc_block.modal.coord_select = int_value - 54; // Shift to array indexing. + break; + case 61: + word_bit = MODAL_GROUP_G13; + if (mantissa != 0) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G61.1 not supported] + } + // gc_block.modal.control = CONTROL_MODE_EXACT_PATH; // G61 + break; + default: + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] + } + if (mantissa > 0) { + FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [Unsupported or invalid Gxx.x command] + } + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if ( bit_istrue(command_words,bit(word_bit)) ) { + FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); + } + command_words |= bit(word_bit); + break; + + case 'M': + + // Determine 'M' command and its modal group + if (mantissa > 0) { + FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [No Mxx.x commands] + } + switch(int_value) { + case 0: + case 1: + case 2: + case 30: + word_bit = MODAL_GROUP_M4; + switch(int_value) { + case 0: + gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; + break; // Program pause + case 1: + break; // Optional stop not supported. Ignore. + default: + gc_block.modal.program_flow = int_value; // Program end and reset + } + break; + + case 3: + case 4: + case 5: + #ifndef SPINDLE_PWM_PIN + grbl_send(CLIENT_SERIAL, "[MSG:No spindle pin defined]\r\n"); + #endif + word_bit = MODAL_GROUP_M7; + switch(int_value) { + case 3: + gc_block.modal.spindle = SPINDLE_ENABLE_CW; + break; + case 4: // Supported if SPINDLE_DIR_PIN is defined or laser mode is on. +#ifndef SPINDLE_DIR_PIN + // if laser mode is not on then this is an unsupported command + if bit_isfalse(settings.flags,BITFLAG_LASER_MODE) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); + break; + } +#endif + gc_block.modal.spindle = SPINDLE_ENABLE_CCW; + break; + case 5: + gc_block.modal.spindle = SPINDLE_DISABLE; + break; + } + break; + case 6: // too change + word_bit = MODAL_GROUP_M6; + gc_block.modal.tool_change = TOOL_CHANGE; + #ifdef USE_TOOL_CHANGE + //tool_change(gc_state.tool); + #endif + break; + case 7: + case 8: + case 9: + word_bit = MODAL_GROUP_M8; + switch(int_value) { +#ifdef COOLANT_MIST_PIN + case 7: + gc_block.modal.coolant = COOLANT_MIST_ENABLE; + break; +#endif +#ifdef COOLANT_FLOOD_PIN + case 8: + gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; + break; +#endif + case 9: + gc_block.modal.coolant = COOLANT_DISABLE; + break; + default: + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] + } + break; + case 62: + case 63: + //grbl_sendf(CLIENT_SERIAL,"M%d...\r\n", int_value); + word_bit = MODAL_GROUP_M10; + switch (int_value) { + case 62: + gc_block.modal.io_control = NON_MODAL_IO_ENABLE; + break; + case 63: + gc_block.modal.io_control = NON_MODAL_IO_DISABLE; + break; + default: + break; + } + + break; + default: + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] + } + + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if ( bit_istrue(command_words,bit(word_bit)) ) { + FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); + } + command_words |= bit(word_bit); + break; + + // NOTE: All remaining letters assign values. + default: + + /* Non-Command Words: This initial parsing phase only checks for repeats of the remaining + legal g-code words and stores their value. Error-checking is performed later since some + words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */ + switch(letter) { +#if (N_AXIS > A_AXIS) + case 'A': + word_bit = WORD_A; + gc_block.values.xyz[A_AXIS] = value; + axis_words |= (1< B_AXIS) + case 'B': + word_bit = WORD_B; + gc_block.values.xyz[B_AXIS] = value; + axis_words |= (1< C_AXIS) + case 'C': + word_bit = WORD_C; + gc_block.values.xyz[C_AXIS] = value; + axis_words |= (1< MAX_TOOL_NUMBER) { + FAIL(STATUS_GCODE_MAX_VALUE_EXCEEDED); + } + grbl_sendf(CLIENT_ALL, "[MSG:Tool No: %d]\r\n", int_value); + gc_state.tool = int_value; + break; + case 'X': + word_bit = WORD_X; + gc_block.values.xyz[X_AXIS] = value; + axis_words |= (1< MAX_LINE_NUMBER) { + FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); // [Exceeds max line number] + } + } + // bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // Track for unused words at the end of error-checking. + // NOTE: Single-meaning value words are removed all at once at the end of error-checking, because + // they are always used when present. This was done to save a few bytes of flash. For clarity, the + // single-meaning value words may be removed as they are used. Also, axis words are treated in the + // same way. If there is an explicit/implicit axis command, XYZ words are always used and are + // are removed at the end of error-checking. + + // [1. Comments ]: MSG's NOT SUPPORTED. Comment handling performed by protocol. + + // [2. Set feed rate mode ]: G93 F word missing with G1,G2/3 active, implicitly or explicitly. Feed rate + // is not defined after switching to G94 from G93. + // NOTE: For jogging, ignore prior feed rate mode. Enforce G94 and check for required F word. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + if (bit_isfalse(value_words,bit(WORD_F))) { + FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); + } + if (gc_block.modal.units == UNITS_MODE_INCHES) { + gc_block.values.f *= MM_PER_INCH; + } + } else { + if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93 + // NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here. + if (axis_command == AXIS_COMMAND_MOTION_MODE) { + if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) { + if (bit_isfalse(value_words,bit(WORD_F))) { + FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [F word missing] + } + } + } + // NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would + // accomplish the exact same thing if the feed rate value is always reset to zero and undefined after each + // inverse time block, since the commands that use this value already perform undefined checks. This would + // also allow other commands, following this switch, to execute and not error out needlessly. This code is + // combined with the above feed rate mode and the below set feed rate error-checking. + + // [3. Set feed rate ]: F is negative (done.) + // - In inverse time mode: Always implicitly zero the feed rate value before and after block completion. + // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word + // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error + // out in the motion modes error-checking. However, if no F word is passed with NO motion command that requires + // a feed rate, we simply move on and the state feed rate value gets updated to zero and remains undefined. + } else { // = G94 + // - In units per mm mode: If F word passed, ensure value is in mm/min, otherwise push last state value. + if (gc_state.modal.feed_rate == FEED_RATE_MODE_UNITS_PER_MIN) { // Last state is also G94 + if (bit_istrue(value_words,bit(WORD_F))) { + if (gc_block.modal.units == UNITS_MODE_INCHES) { + gc_block.values.f *= MM_PER_INCH; + } + } else { + gc_block.values.f = gc_state.feed_rate; // Push last state feed rate + } + } // Else, switching to G94 from G93, so don't push last state feed rate. Its undefined or the passed F word value. + } + } + // bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [4. Set spindle speed ]: S is negative (done.) + if (bit_isfalse(value_words,bit(WORD_S))) { + gc_block.values.s = gc_state.spindle_speed; + } + // bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [5. Select tool ]: NOT SUPPORTED. Only tracks value. T is negative (done.) Not an integer. Greater than max tool value. + // bit_false(value_words,bit(WORD_T)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [6. Change tool ]: N/A + // [7. Spindle control ]: N/A + // [8. Coolant control ]: N/A + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. + + // [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below. + if (gc_block.non_modal_command == NON_MODAL_DWELL) { + if (bit_isfalse(value_words,bit(WORD_P))) { + FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] + } + bit_false(value_words,bit(WORD_P)); + } + + if ( (gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE)) { + if (bit_isfalse(value_words,bit(WORD_P))) { + FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] + } + bit_false(value_words,bit(WORD_P)); + } + + // [11. Set active plane ]: N/A + switch (gc_block.modal.plane_select) { + case PLANE_SELECT_XY: + axis_0 = X_AXIS; + axis_1 = Y_AXIS; + axis_linear = Z_AXIS; + break; + case PLANE_SELECT_ZX: + axis_0 = Z_AXIS; + axis_1 = X_AXIS; + axis_linear = Y_AXIS; + break; + default: // case PLANE_SELECT_YZ: + axis_0 = Y_AXIS; + axis_1 = Z_AXIS; + axis_linear = X_AXIS; + } + + // [12. Set length units ]: N/A + // Pre-convert XYZ coordinate values to millimeters, if applicable. + uint8_t idx; + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx=0; idx N_COORDINATE_SYSTEM) { + FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] + } + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + if (!(settings_read_coord_data(gc_block.modal.coord_select,block_coord_system))) { + FAIL(STATUS_SETTING_READ_FAIL); + } + } + } + + // [16. Set path control mode ]: N/A. Only G61. G61.1 and G64 NOT SUPPORTED. + // [17. Set distance mode ]: N/A. Only G91.1. G90.1 NOT SUPPORTED. + // [18. Set retract mode ]: NOT SUPPORTED. + + // [19. Remaining non-modal actions ]: Check go to predefined position, set G10, or set axis offsets. + // NOTE: We need to separate the non-modal commands that are axis word-using (G10/G28/G30/G92), as these + // commands all treat axis words differently. G10 as absolute offsets or computes current position as + // the axis value, G92 similarly to G10 L20, and G28/30 as an intermediate target position that observes + // all the current coordinate system and G92 offsets. + switch (gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + // [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.) + // [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing. + // [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS) + }; // [No axis words] + if (bit_isfalse(value_words,((1< N_COORDINATE_SYSTEM) { + FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] + } + if (gc_block.values.l != 20) { + if (gc_block.values.l == 2) { + if (bit_istrue(value_words,bit(WORD_R))) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G10 L2 R not supported] + } + } else { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported L] + } + } + bit_false(value_words,(bit(WORD_L)|bit(WORD_P))); + + // Determine coordinate system to change and try to load from EEPROM. + if (coord_select > 0) { + coord_select--; // Adjust P1-P6 index to EEPROM coordinate data indexing. + } else { + coord_select = gc_block.modal.coord_select; // Index P0 as the active coordinate system + } + + // NOTE: Store parameter data in IJK values. By rule, they are not in use with this command. + // FIXME: Instead of IJK, we'd better use: float vector[N_AXIS]; // [DG] + if (!settings_read_coord_data(coord_select,gc_block.values.ijk)) { + FAIL(STATUS_SETTING_READ_FAIL); // [EEPROM read fail] + } + + // Pre-calculate the coordinate data changes. + for (idx=0; idx WCS = MPos - G92 - TLO - WPos + gc_block.values.ijk[idx] = gc_state.position[idx]-gc_state.coord_offset[idx]-gc_block.values.xyz[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) { + gc_block.values.ijk[idx] -= gc_state.tool_length_offset; + } + } else { + // L2: Update coordinate system axis to programmed value. + gc_block.values.ijk[idx] = gc_block.values.xyz[idx]; + } + } // Else, keep current stored value. + } + break; + case NON_MODAL_SET_COORDINATE_OFFSET: + // [G92 Errors]: No axis words. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] + } + + // Update axes defined only in block. Offsets current system to defined value. Does not update when + // active coordinate system is selected, but is still active unless G92.1 disables it. + for (idx=0; idx G92 = MPos - WCS - TLO - WPos + gc_block.values.xyz[idx] = gc_state.position[idx]-block_coord_system[idx]-gc_block.values.xyz[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) { + gc_block.values.xyz[idx] -= gc_state.tool_length_offset; + } + } else { + gc_block.values.xyz[idx] = gc_state.coord_offset[idx]; + } + } + break; + + default: + + // At this point, the rest of the explicit axis commands treat the axis values as the traditional + // target position with the coordinate system offsets, G92 offsets, absolute override, and distance + // modes applied. This includes the motion mode commands. We can now pre-compute the target position. + // NOTE: Tool offsets may be appended to these conversions when/if this feature is added. + if (axis_command != AXIS_COMMAND_TOOL_LENGTH_OFFSET ) { // TLO block any axis command. + if (axis_words) { + for (idx=0; idx C -----------------+--------------- T <- [x,y] + | <------ d/2 ---->| + + C - Current position + T - Target position + O - center of circle that pass through both C and T + d - distance from C to T + r - designated radius + h - distance from center of CT to O + + Expanding the equations: + + d -> sqrt(x^2 + y^2) + h -> sqrt(4 * r^2 - x^2 - y^2)/2 + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + + Which can be written: + + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + + Which we for size and speed reasons optimize to: + + h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) + i = (x - (y * h_x2_div_d))/2 + j = (y + (x * h_x2_div_d))/2 + */ + + // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller + // than d. If so, the sqrt of a negative number is complex and error out. + float h_x2_div_d = 4.0 * gc_block.values.r*gc_block.values.r - x*x - y*y; + + if (h_x2_div_d < 0) { + FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); // [Arc radius error] + } + + // Finish computing h_x2_div_d. + h_x2_div_d = -sqrt(h_x2_div_d)/hypot_f(x,y); // == -(h * 2 / d) + // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) + if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) { + h_x2_div_d = -h_x2_div_d; + } + + /* The counter clockwise circle lies to the left of the target direction. When offset is positive, + the left hand circle will be generated - when it is negative the right hand circle is generated. + + T <-- Target position + + ^ + Clockwise circles with this center | Clockwise circles with this center will have + will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! + \ | / + center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative + | + | + + C <-- Current position + */ + // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), + // even though it is advised against ever generating such circles in a single line of g-code. By + // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of + // travel and thus we get the unadvisably long arcs as prescribed. + if (gc_block.values.r < 0) { + h_x2_div_d = -h_x2_div_d; + gc_block.values.r = -gc_block.values.r; // Finished with r. Set to positive for mc_arc + } + // Complete the operation by calculating the actual center of the arc + gc_block.values.ijk[axis_0] = 0.5*(x-(y*h_x2_div_d)); + gc_block.values.ijk[axis_1] = 0.5*(y+(x*h_x2_div_d)); + + } else { // Arc Center Format Offset Mode + if (!(ijk_words & (bit(axis_0)|bit(axis_1)))) { + FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); // [No offsets in plane] + } + bit_false(value_words,(bit(WORD_I)|bit(WORD_J)|bit(WORD_K))); + + // Convert IJK values to proper units. + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx=0; idx 0.005) { + if (delta_r > 0.5) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.5mm + } + if (delta_r > (0.001*gc_block.values.r)) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.005mm AND 0.1% radius + } + } + } + break; + case MOTION_MODE_PROBE_TOWARD_NO_ERROR: + case MOTION_MODE_PROBE_AWAY_NO_ERROR: + gc_parser_flags |= GC_PARSER_PROBE_IS_NO_ERROR; // No break intentional. + case MOTION_MODE_PROBE_TOWARD: + case MOTION_MODE_PROBE_AWAY: + if ((gc_block.modal.motion == MOTION_MODE_PROBE_AWAY) || + (gc_block.modal.motion == MOTION_MODE_PROBE_AWAY_NO_ERROR)) { + gc_parser_flags |= GC_PARSER_PROBE_IS_AWAY; + } + // [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate + // is undefined. Probe is triggered. NOTE: Probe check moved to probe cycle. Instead of returning + // an error, it issues an alarm to prevent further motion to the probe. It's also done there to + // allow the planner buffer to empty and move off the probe trigger before another probing cycle. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] + } + if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target] + } + break; + } + } + } + + // [21. Program flow ]: No error checks required. + + // [0. Non-specific error-checks]: Complete unused value words check, i.e. IJK used when in arc + // radius mode, or axis words that aren't used in the block. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + // Jogging only uses the F feed rate and XYZ value words. N is valid, but S and T are invalid. + bit_false(value_words,(bit(WORD_N)|bit(WORD_F))); + } else { + bit_false(value_words,(bit(WORD_N)|bit(WORD_F)|bit(WORD_S)|bit(WORD_T))); // Remove single-meaning value words. + } + + if (axis_command) { + bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z)|bit(WORD_A)|bit(WORD_B)|bit(WORD_C))); // Remove axis words. + } + if (value_words) { + FAIL(STATUS_GCODE_UNUSED_WORDS); // [Unused words] + } + + /* ------------------------------------------------------------------------------------- + STEP 4: EXECUTE!! + Assumes that all error-checking has been completed and no failure modes exist. We just + need to update the state and execute the block according to the order-of-execution. + */ + + // Initialize planner data struct for motion blocks. + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); // Zero pl_data struct + + // Intercept jog commands and complete error checking for valid jog commands and execute. + // NOTE: G-code parser state is not updated, except the position to ensure sequential jog + // targets are computed correctly. The final parser position after a jog is updated in + // protocol_execute_realtime() when jogging completes or is canceled. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + // Only distance and unit modal commands and G53 absolute override command are allowed. + // NOTE: Feed rate word and axis word checks have already been performed in STEP 3. + if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6) | bit(MODAL_GROUP_G0)) ) { + FAIL(STATUS_INVALID_JOG_COMMAND) + }; + if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) { + FAIL(STATUS_INVALID_JOG_COMMAND); + } + + // Initialize planner data to current spindle and coolant modal state. + pl_data->spindle_speed = gc_state.spindle_speed; + plan_data.condition = (gc_state.modal.spindle | gc_state.modal.coolant); + + uint8_t status = jog_execute(&plan_data, &gc_block); + if (status == STATUS_OK) { + memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); + } + return(status); + } + + // If in laser mode, setup laser power based on current and past parser conditions. + if (bit_istrue(settings.flags,BITFLAG_LASER_MODE)) { + if ( !((gc_block.modal.motion == MOTION_MODE_LINEAR) || (gc_block.modal.motion == MOTION_MODE_CW_ARC) + || (gc_block.modal.motion == MOTION_MODE_CCW_ARC)) ) { + gc_parser_flags |= GC_PARSER_LASER_DISABLE; + } + + // Any motion mode with axis words is allowed to be passed from a spindle speed update. + // NOTE: G1 and G0 without axis words sets axis_command to none. G28/30 are intentionally omitted. + // TODO: Check sync conditions for M3 enabled motions that don't enter the planner. (zero length). + if (axis_words && (axis_command == AXIS_COMMAND_MOTION_MODE)) { + gc_parser_flags |= GC_PARSER_LASER_ISMOTION; + } else { + // M3 constant power laser requires planner syncs to update the laser when changing between + // a G1/2/3 motion mode state and vice versa when there is no motion in the line. + if (gc_state.modal.spindle == SPINDLE_ENABLE_CW) { + if ((gc_state.modal.motion == MOTION_MODE_LINEAR) || (gc_state.modal.motion == MOTION_MODE_CW_ARC) + || (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { + if (bit_istrue(gc_parser_flags,GC_PARSER_LASER_DISABLE)) { + gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; // Change from G1/2/3 motion mode. + } + } else { + // When changing to a G1 motion mode without axis words from a non-G1/2/3 motion mode. + if (bit_isfalse(gc_parser_flags,GC_PARSER_LASER_DISABLE)) { + gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; + } + } + } + } + } + + // [0. Non-specific/common error-checks and miscellaneous setup]: + // NOTE: If no line number is present, the value is zero. + gc_state.line_number = gc_block.values.n; +#ifdef USE_LINE_NUMBERS + pl_data->line_number = gc_state.line_number; // Record data for planner use. +#endif + + // [1. Comments feedback ]: NOT SUPPORTED + + // [2. Set feed rate mode ]: + gc_state.modal.feed_rate = gc_block.modal.feed_rate; + if (gc_state.modal.feed_rate) { + pl_data->condition |= PL_COND_FLAG_INVERSE_TIME; // Set condition flag for planner use. + } + + // [3. Set feed rate ]: + gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking. + pl_data->feed_rate = gc_state.feed_rate; // Record data for planner use. + + // [4. Set spindle speed ]: + if ((gc_state.spindle_speed != gc_block.values.s) || bit_istrue(gc_parser_flags,GC_PARSER_LASER_FORCE_SYNC)) { + if (gc_state.modal.spindle != SPINDLE_DISABLE) { +#ifdef VARIABLE_SPINDLE + if (bit_isfalse(gc_parser_flags,GC_PARSER_LASER_ISMOTION)) { + if (bit_istrue(gc_parser_flags,GC_PARSER_LASER_DISABLE)) { + spindle_sync(gc_state.modal.spindle, 0.0); + } else { + spindle_sync(gc_state.modal.spindle, gc_block.values.s); + } + } +#else + spindle_sync(gc_state.modal.spindle, 0.0); +#endif + } + gc_state.spindle_speed = gc_block.values.s; // Update spindle speed state. + } + // NOTE: Pass zero spindle speed for all restricted laser motions. + if (bit_isfalse(gc_parser_flags,GC_PARSER_LASER_DISABLE)) { + pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use. + } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already. + + // [5. Select tool ]: NOT SUPPORTED. Only tracks tool value. + // gc_state.tool = gc_block.values.t; + + // [6. Change tool ]: NOT SUPPORTED + if (gc_block.modal.tool_change == TOOL_CHANGE) { + #ifdef USE_TOOL_CHANGE + user_tool_change(gc_state.tool); + #endif + } + + // [7. Spindle control ]: + if (gc_state.modal.spindle != gc_block.modal.spindle) { + // Update spindle control and apply spindle speed when enabling it in this block. + // NOTE: All spindle state changes are synced, even in laser mode. Also, pl_data, + // rather than gc_state, is used to manage laser state for non-laser motions. + spindle_sync(gc_block.modal.spindle, pl_data->spindle_speed); + gc_state.modal.spindle = gc_block.modal.spindle; + } + pl_data->condition |= gc_state.modal.spindle; // Set condition flag for planner use. + + // [8. Coolant control ]: + if (gc_state.modal.coolant != gc_block.modal.coolant) { + // NOTE: Coolant M-codes are modal. Only one command per line is allowed. But, multiple states + // can exist at the same time, while coolant disable clears all states. + coolant_sync(gc_block.modal.coolant); + if (gc_block.modal.coolant == COOLANT_DISABLE) { + gc_state.modal.coolant = COOLANT_DISABLE; + } else { + gc_state.modal.coolant |= gc_block.modal.coolant; + } + } + pl_data->condition |= gc_state.modal.coolant; // Set condition flag for planner use. + + // turn on/off an i/o pin + if ( (gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE) ) { + if (gc_block.values.p <= MAX_USER_DIGITAL_PIN) { + sys_io_control(1<<(int)gc_block.values.p, (gc_block.modal.io_control == NON_MODAL_IO_ENABLE)); + } + else { + FAIL(STATUS_P_PARAM_MAX_EXCEEDED); + } + } + + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. Always enabled. + + // [10. Dwell ]: + if (gc_block.non_modal_command == NON_MODAL_DWELL) { + mc_dwell(gc_block.values.p); + } + + // [11. Set active plane ]: + gc_state.modal.plane_select = gc_block.modal.plane_select; + + // [12. Set length units ]: + gc_state.modal.units = gc_block.modal.units; + + // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED + // gc_state.modal.cutter_comp = gc_block.modal.cutter_comp; // NOTE: Not needed since always disabled. + + // [14. Cutter length compensation ]: G43.1 and G49 supported. G43 NOT SUPPORTED. + // NOTE: If G43 were supported, its operation wouldn't be any different from G43.1 in terms + // of execution. The error-checking step would simply load the offset value into the correct + // axis of the block XYZ value array. + if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET ) { // Indicates a change. + gc_state.modal.tool_length = gc_block.modal.tool_length; + if (gc_state.modal.tool_length == TOOL_LENGTH_OFFSET_CANCEL) { // G49 + gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] = 0.0; + } // else G43.1 + if ( gc_state.tool_length_offset != gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] ) { + gc_state.tool_length_offset = gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]; + system_flag_wco_change(); + } + } + + // [15. Coordinate system selection ]: + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + gc_state.modal.coord_select = gc_block.modal.coord_select; + memcpy(gc_state.coord_system,block_coord_system,N_AXIS*sizeof(float)); + system_flag_wco_change(); + } + + // [16. Set path control mode ]: G61.1/G64 NOT SUPPORTED + // gc_state.modal.control = gc_block.modal.control; // NOTE: Always default. + + // [17. Set distance mode ]: + gc_state.modal.distance = gc_block.modal.distance; + + // [18. Set retract mode ]: NOT SUPPORTED + + // [19. Go to predefined position, Set G10, or Set axis offsets ]: + switch(gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + settings_write_coord_data(coord_select,gc_block.values.ijk); + // Update system coordinate system if currently active. + if (gc_state.modal.coord_select == coord_select) { + memcpy(gc_state.coord_system,gc_block.values.ijk,N_AXIS*sizeof(float)); + system_flag_wco_change(); + } + break; + case NON_MODAL_GO_HOME_0: + case NON_MODAL_GO_HOME_1: + // Move to intermediate position before going home. Obeys current coordinate system and offsets + // and absolute and incremental modes. + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + if (axis_command) { + mc_line(gc_block.values.xyz, pl_data); // kinematics kinematics not used for homing righ now + } + mc_line(gc_block.values.ijk, pl_data); + memcpy(gc_state.position, gc_block.values.ijk, N_AXIS*sizeof(float)); + break; + case NON_MODAL_SET_HOME_0: + settings_write_coord_data(SETTING_INDEX_G28,gc_state.position); + break; + case NON_MODAL_SET_HOME_1: + settings_write_coord_data(SETTING_INDEX_G30,gc_state.position); + break; + case NON_MODAL_SET_COORDINATE_OFFSET: + memcpy(gc_state.coord_offset,gc_block.values.xyz,sizeof(gc_block.values.xyz)); + system_flag_wco_change(); + break; + case NON_MODAL_RESET_COORDINATE_OFFSET: + clear_vector(gc_state.coord_offset); // Disable G92 offsets by zeroing offset vector. + system_flag_wco_change(); + break; + } + + + // [20. Motion modes ]: + // NOTE: Commands G10,G28,G30,G92 lock out and prevent axis words from use in motion modes. + // Enter motion modes only if there are axis words or a motion mode command word in the block. + gc_state.modal.motion = gc_block.modal.motion; + if (gc_state.modal.motion != MOTION_MODE_NONE) { + if (axis_command == AXIS_COMMAND_MOTION_MODE) { + uint8_t gc_update_pos = GC_UPDATE_POS_TARGET; + if (gc_state.modal.motion == MOTION_MODE_LINEAR) { + //mc_line(gc_block.values.xyz, pl_data); + mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + } else if (gc_state.modal.motion == MOTION_MODE_SEEK) { + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + //mc_line(gc_block.values.xyz, pl_data); + mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + } else if ((gc_state.modal.motion == MOTION_MODE_CW_ARC) || (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { + mc_arc(gc_block.values.xyz, pl_data, gc_state.position, gc_block.values.ijk, gc_block.values.r, + axis_0, axis_1, axis_linear, bit_istrue(gc_parser_flags,GC_PARSER_ARC_IS_CLOCKWISE)); + } else { + // NOTE: gc_block.values.xyz is returned from mc_probe_cycle with the updated position value. So + // upon a successful probing cycle, the machine position and the returned value should be the same. +#ifndef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES + pl_data->condition |= PL_COND_FLAG_NO_FEED_OVERRIDE; +#endif + gc_update_pos = mc_probe_cycle(gc_block.values.xyz, pl_data, gc_parser_flags); + } + + // As far as the parser is concerned, the position is now == target. In reality the + // motion control system might still be processing the action and the real tool position + // in any intermediate location. + if (gc_update_pos == GC_UPDATE_POS_TARGET) { + memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); // gc_state.position[] = gc_block.values.xyz[] + } else if (gc_update_pos == GC_UPDATE_POS_SYSTEM) { + gc_sync_position(); // gc_state.position[] = sys_position + } // == GC_UPDATE_POS_NONE + } + } + + // [21. Program flow ]: + // M0,M1,M2,M30: Perform non-running program flow actions. During a program pause, the buffer may + // refill and can only be resumed by the cycle start run-time command. + gc_state.modal.program_flow = gc_block.modal.program_flow; + if (gc_state.modal.program_flow) { + protocol_buffer_synchronize(); // Sync and finish all remaining buffered motions before moving on. + if (gc_state.modal.program_flow == PROGRAM_FLOW_PAUSED) { + if (sys.state != STATE_CHECK_MODE) { + system_set_exec_state_flag(EXEC_FEED_HOLD); // Use feed hold for program pause. + protocol_execute_realtime(); // Execute suspend. + } + } else { // == PROGRAM_FLOW_COMPLETED + // Upon program complete, only a subset of g-codes reset to certain defaults, according to + // LinuxCNC's program end descriptions and testing. Only modal groups [G-code 1,2,3,5,7,12] + // and [M-code 7,8,9] reset to [G1,G17,G90,G94,G40,G54,M5,M9,M48]. The remaining modal groups + // [G-code 4,6,8,10,13,14,15] and [M-code 4,5,6] and the modal words [F,S,T,H] do not reset. + gc_state.modal.motion = MOTION_MODE_LINEAR; + gc_state.modal.plane_select = PLANE_SELECT_XY; + gc_state.modal.distance = DISTANCE_MODE_ABSOLUTE; + gc_state.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; + // gc_state.modal.cutter_comp = CUTTER_COMP_DISABLE; // Not supported. + gc_state.modal.coord_select = 0; // G54 + gc_state.modal.spindle = SPINDLE_DISABLE; + gc_state.modal.coolant = COOLANT_DISABLE; + // gc_state.modal.override = OVERRIDE_DISABLE; // Not supported. + +#ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END + sys.f_override = DEFAULT_FEED_OVERRIDE; + sys.r_override = DEFAULT_RAPID_OVERRIDE; + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; +#endif + + // Execute coordinate change and spindle/coolant stop. + if (sys.state != STATE_CHECK_MODE) { + if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { + FAIL(STATUS_SETTING_READ_FAIL); + } + system_flag_wco_change(); // Set to refresh immediately just in case something altered. + spindle_set_state(SPINDLE_DISABLE,0.0); + coolant_set_state(COOLANT_DISABLE); + } + report_feedback_message(MESSAGE_PROGRAM_END); + #ifdef USE_M30 + user_m30(); + #endif + } + gc_state.modal.program_flow = PROGRAM_FLOW_RUNNING; // Reset program flow. + } + + // TODO: % to denote start of program. + + return(STATUS_OK); +} + + +/* + Not supported: + + - Canned cycles + - Tool radius compensation + - A,B,C-axes + - Evaluation of expressions + - Variables + - Override control (TBD) + - Tool changes + - Switches + + (*) Indicates optional parameter, enabled through config.h and re-compile + group 0 = {G92.2, G92.3} (Non modal: Cancel and re-enable G92 offsets) + group 1 = {G81 - G89} (Motion modes: Canned cycles) + group 4 = {M1} (Optional stop, ignored) + group 6 = {M6} (Tool change) + group 7 = {G41, G42} cutter radius compensation (G40 is supported) + group 8 = {G43} tool length offset (G43.1/G49 are supported) + group 8 = {M7*} enable mist coolant (* Compile-option) + group 9 = {M48, M49} enable/disable feed and speed override switches + group 10 = {G98, G99} return mode canned cycles + group 13 = {G61.1, G64} path control mode (G61 is supported) +*/ diff --git a/Grbl_Esp32-master/Grbl_Esp32/gcode.h b/Grbl_Esp32-master/Grbl_Esp32/gcode.h new file mode 100644 index 0000000..f5f3cae --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/gcode.h @@ -0,0 +1,257 @@ +/* + gcode.h - rs274/ngc parser. + Part of Grbl + + Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ +#ifndef gcode_h +#define gcode_h + + +// Define modal group internal numbers for checking multiple command violations and tracking the +// type of command that is called in the block. A modal group is a group of g-code commands that are +// mutually exclusive, or cannot exist on the same line, because they each toggle a state or execute +// a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, +// and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc). +// NOTE: Modal group define values must be sequential and starting from zero. +#define MODAL_GROUP_G0 0 // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal +#define MODAL_GROUP_G1 1 // [G0,G1,G2,G3,G38.2,G38.3,G38.4,G38.5,G80] Motion +#define MODAL_GROUP_G2 2 // [G17,G18,G19] Plane selection +#define MODAL_GROUP_G3 3 // [G90,G91] Distance mode +#define MODAL_GROUP_G4 4 // [G91.1] Arc IJK distance mode +#define MODAL_GROUP_G5 5 // [G93,G94] Feed rate mode +#define MODAL_GROUP_G6 6 // [G20,G21] Units +#define MODAL_GROUP_G7 7 // [G40] Cutter radius compensation mode. G41/42 NOT SUPPORTED. +#define MODAL_GROUP_G8 8 // [G43.1,G49] Tool length offset +#define MODAL_GROUP_G12 9 // [G54,G55,G56,G57,G58,G59] Coordinate system selection +#define MODAL_GROUP_G13 10 // [G61] Control mode + +#define MODAL_GROUP_M4 11 // [M0,M1,M2,M30] Stopping +#define MODAL_GROUP_M6 14 // [M6] Tool change +#define MODAL_GROUP_M7 12 // [M3,M4,M5] Spindle turning +#define MODAL_GROUP_M8 13 // [M7,M8,M9] Coolant control +#define MODAL_GROUP_M10 14 // [M62, M63] User Defined http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups + +// #define OTHER_INPUT_F 14 +// #define OTHER_INPUT_S 15 +// #define OTHER_INPUT_T 16 + +// Define command actions for within execution-type modal groups (motion, stopping, non-modal). Used +// internally by the parser to know which command to execute. +// NOTE: Some macro values are assigned specific values to make g-code state reporting and parsing +// compile a litte smaller. Necessary due to being completely out of flash on the 328p. Although not +// ideal, just be careful with values that state 'do not alter' and check both report.c and gcode.c +// to see how they are used, if you need to alter them. + +// Modal Group G0: Non-modal actions +#define NON_MODAL_NO_ACTION 0 // (Default: Must be zero) +#define NON_MODAL_DWELL 4 // G4 (Do not alter value) +#define NON_MODAL_SET_COORDINATE_DATA 10 // G10 (Do not alter value) +#define NON_MODAL_GO_HOME_0 28 // G28 (Do not alter value) +#define NON_MODAL_SET_HOME_0 38 // G28.1 (Do not alter value) +#define NON_MODAL_GO_HOME_1 30 // G30 (Do not alter value) +#define NON_MODAL_SET_HOME_1 40 // G30.1 (Do not alter value) +#define NON_MODAL_ABSOLUTE_OVERRIDE 53 // G53 (Do not alter value) +#define NON_MODAL_SET_COORDINATE_OFFSET 92 // G92 (Do not alter value) +#define NON_MODAL_RESET_COORDINATE_OFFSET 102 //G92.1 (Do not alter value) + +// Modal Group G1: Motion modes +#define MOTION_MODE_SEEK 0 // G0 (Default: Must be zero) +#define MOTION_MODE_LINEAR 1 // G1 (Do not alter value) +#define MOTION_MODE_CW_ARC 2 // G2 (Do not alter value) +#define MOTION_MODE_CCW_ARC 3 // G3 (Do not alter value) +#define MOTION_MODE_PROBE_TOWARD 140 // G38.2 (Do not alter value) +#define MOTION_MODE_PROBE_TOWARD_NO_ERROR 141 // G38.3 (Do not alter value) +#define MOTION_MODE_PROBE_AWAY 142 // G38.4 (Do not alter value) +#define MOTION_MODE_PROBE_AWAY_NO_ERROR 143 // G38.5 (Do not alter value) +#define MOTION_MODE_NONE 80 // G80 (Do not alter value) + +// Modal Group G2: Plane select +#define PLANE_SELECT_XY 0 // G17 (Default: Must be zero) +#define PLANE_SELECT_ZX 1 // G18 (Do not alter value) +#define PLANE_SELECT_YZ 2 // G19 (Do not alter value) + +// Modal Group G3: Distance mode +#define DISTANCE_MODE_ABSOLUTE 0 // G90 (Default: Must be zero) +#define DISTANCE_MODE_INCREMENTAL 1 // G91 (Do not alter value) + +// Modal Group G4: Arc IJK distance mode +#define DISTANCE_ARC_MODE_INCREMENTAL 0 // G91.1 (Default: Must be zero) + +// Modal Group M4: Program flow +#define PROGRAM_FLOW_RUNNING 0 // (Default: Must be zero) +#define PROGRAM_FLOW_PAUSED 3 // M0 +#define PROGRAM_FLOW_OPTIONAL_STOP 1 // M1 NOTE: Not supported, but valid and ignored. +#define PROGRAM_FLOW_COMPLETED_M2 2 // M2 (Do not alter value) +#define PROGRAM_FLOW_COMPLETED_M30 30 // M30 (Do not alter value) + +// Modal Group G5: Feed rate mode +#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) +#define FEED_RATE_MODE_INVERSE_TIME 1 // G93 (Do not alter value) + +// Modal Group G6: Units mode +#define UNITS_MODE_MM 0 // G21 (Default: Must be zero) +#define UNITS_MODE_INCHES 1 // G20 (Do not alter value) + +// Modal Group G7: Cutter radius compensation mode +#define CUTTER_COMP_DISABLE 0 // G40 (Default: Must be zero) + +// Modal Group G13: Control mode +#define CONTROL_MODE_EXACT_PATH 0 // G61 (Default: Must be zero) + +// Modal Group M7: Spindle control +#define SPINDLE_DISABLE 0 // M5 (Default: Must be zero) +#define SPINDLE_ENABLE_CW PL_COND_FLAG_SPINDLE_CW // M3 (NOTE: Uses planner condition bit flag) +#define SPINDLE_ENABLE_CCW PL_COND_FLAG_SPINDLE_CCW // M4 (NOTE: Uses planner condition bit flag) + +// Modal Group M8: Coolant control +#define COOLANT_DISABLE 0 // M9 (Default: Must be zero) +#define COOLANT_FLOOD_ENABLE PL_COND_FLAG_COOLANT_FLOOD // M8 (NOTE: Uses planner condition bit flag) +#define COOLANT_MIST_ENABLE PL_COND_FLAG_COOLANT_MIST // M7 (NOTE: Uses planner condition bit flag) + +// modal Group M10: User I/O control +#define NON_MODAL_IO_ENABLE 1 +#define NON_MODAL_IO_DISABLE 2 +#define MAX_USER_DIGITAL_PIN 4 + +// Modal Group G8: Tool length offset +#define TOOL_LENGTH_OFFSET_CANCEL 0 // G49 (Default: Must be zero) +#define TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC 1 // G43.1 + +#define TOOL_CHANGE 1 + +// Modal Group G12: Active work coordinate system +// N/A: Stores coordinate system value (54-59) to change to. + +// Define parameter word mapping. +#define WORD_F 0 +#define WORD_I 1 +#define WORD_J 2 +#define WORD_K 3 +#define WORD_L 4 +#define WORD_N 5 +#define WORD_P 6 +#define WORD_R 7 +#define WORD_S 8 +#define WORD_T 9 +#define WORD_X 10 +#define WORD_Y 11 +#define WORD_Z 12 +#define WORD_A 13 +#define WORD_B 14 +#define WORD_C 15 + +// Define g-code parser position updating flags +#define GC_UPDATE_POS_TARGET 0 // Must be zero +#define GC_UPDATE_POS_SYSTEM 1 +#define GC_UPDATE_POS_NONE 2 + +// Define probe cycle exit states and assign proper position updating. +#define GC_PROBE_FOUND GC_UPDATE_POS_SYSTEM +#define GC_PROBE_ABORT GC_UPDATE_POS_NONE +#define GC_PROBE_FAIL_INIT GC_UPDATE_POS_NONE +#define GC_PROBE_FAIL_END GC_UPDATE_POS_TARGET +#ifdef SET_CHECK_MODE_PROBE_TO_START + #define GC_PROBE_CHECK_MODE GC_UPDATE_POS_NONE +#else + #define GC_PROBE_CHECK_MODE GC_UPDATE_POS_TARGET +#endif + +// Define gcode parser flags for handling special cases. +#define GC_PARSER_NONE 0 // Must be zero. +#define GC_PARSER_JOG_MOTION bit(0) +#define GC_PARSER_CHECK_MANTISSA bit(1) +#define GC_PARSER_ARC_IS_CLOCKWISE bit(2) +#define GC_PARSER_PROBE_IS_AWAY bit(3) +#define GC_PARSER_PROBE_IS_NO_ERROR bit(4) +#define GC_PARSER_LASER_FORCE_SYNC bit(5) +#define GC_PARSER_LASER_DISABLE bit(6) +#define GC_PARSER_LASER_ISMOTION bit(7) + + +// NOTE: When this struct is zeroed, the above defines set the defaults for the system. +typedef struct { + uint8_t motion; // {G0,G1,G2,G3,G38.2,G80} + uint8_t feed_rate; // {G93,G94} + uint8_t units; // {G20,G21} + uint8_t distance; // {G90,G91} + // uint8_t distance_arc; // {G91.1} NOTE: Don't track. Only default supported. + uint8_t plane_select; // {G17,G18,G19} + // uint8_t cutter_comp; // {G40} NOTE: Don't track. Only default supported. + uint8_t tool_length; // {G43.1,G49} + uint8_t coord_select; // {G54,G55,G56,G57,G58,G59} + // uint8_t control; // {G61} NOTE: Don't track. Only default supported. + uint8_t program_flow; // {M0,M1,M2,M30} + uint8_t coolant; // {M7,M8,M9} + uint8_t spindle; // {M3,M4,M5} + uint8_t tool_change; // {M6} + uint8_t io_control; // {M62, M63} +} gc_modal_t; + +typedef struct { + float f; // Feed + float ijk[N_AXIS]; // I,J,K Axis arc offsets + uint8_t l; // G10 or canned cycles parameters + int32_t n; // Line number + float p; // G10 or dwell parameters + // float q; // G82 peck drilling + float r; // Arc radius + float s; // Spindle speed + uint8_t t; // Tool selection + float xyz[N_AXIS]; // X,Y,Z Translational axes +} gc_values_t; + + +typedef struct { + gc_modal_t modal; + + float spindle_speed; // RPM + float feed_rate; // Millimeters/min + uint8_t tool; // Tracks tool number. NOT USED. + int32_t line_number; // Last line number sent + + float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code + + float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine + // position in mm. Loaded from EEPROM when called. + float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to + // machine zero in mm. Non-persistent. Cleared upon reset and boot. + float tool_length_offset; // Tracks tool length offset value when enabled. +} parser_state_t; +extern parser_state_t gc_state; + + +typedef struct { + uint8_t non_modal_command; + gc_modal_t modal; + gc_values_t values; +} parser_block_t; + + +// Initialize the parser +void gc_init(); + +// Execute one block of rs275/ngc/g-code +uint8_t gc_execute_line(char *line, uint8_t client); + +// Set g-code parser position. Input in steps. +void gc_sync_position(); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl.h b/Grbl_Esp32-master/Grbl_Esp32/grbl.h new file mode 100644 index 0000000..02b5230 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl.h @@ -0,0 +1,95 @@ +/* + grbl.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +// Grbl versioning system +#define GRBL_VERSION "1.1f" +#define GRBL_VERSION_BUILD "20191208" + +//#include +#include +#include +#include +#include +#include + +#include "driver/timer.h" + +// Define the Grbl system include files. NOTE: Do not alter organization. +#include "config.h" +#include "nuts_bolts.h" +#include "cpu_map.h" +#include "tdef.h" + +#include "defaults.h" +#include "settings.h" +#include "system.h" + +#include "planner.h" +#include "coolant_control.h" +#include "grbl_eeprom.h" +#include "gcode.h" +#include "grbl_limits.h" +#include "motion_control.h" +#include "print.h" +#include "probe.h" +#include "protocol.h" +#include "report.h" +#include "serial.h" +#include "spindle_control.h" +#include "stepper.h" +#include "jog.h" +#include "inputbuffer.h" + +#ifdef ENABLE_BLUETOOTH + #include "BTconfig.h" +#endif + +#ifdef ENABLE_SD_CARD + #include "grbl_sd.h" +#endif + +#ifdef ENABLE_WIFI + #include "wificonfig.h" + #ifdef ENABLE_HTTP + #include "serial2socket.h" + #endif + #ifdef ENABLE_TELNET + #include "telnet_server.h" + #endif + #ifdef ENABLE_NOTIFICATIONS + #include "notifications_service.h" + #endif +#endif + +#include "servo_pen.h" +#include "solenoid_pen.h" + +#ifdef USE_SERVO_AXES + #include "servo_axis.h" +#endif + +#ifdef USE_TRINAMIC + #include "grbl_trinamic.h" +#endif + +#ifdef USE_UNIPOLAR + #include "grbl_unipolar.h" +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.cpp b/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.cpp new file mode 100644 index 0000000..f8cfe49 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.cpp @@ -0,0 +1,43 @@ +/* + eeprom.cpp - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size) { + unsigned char checksum = 0; + for(; size > 0; size--) { + checksum = (checksum << 1) || (checksum >> 7); + checksum += *source; + EEPROM.write(destination++, *(source++)); + } + EEPROM.write(destination, checksum); + EEPROM.commit(); +} + +int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size) { + unsigned char data, checksum = 0; + for(; size > 0; size--) { + data = EEPROM.read(source++); + checksum = (checksum << 1) || (checksum >> 7); + checksum += data; + *(destination++) = data; + } + return(checksum == EEPROM.read(source)); +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.h b/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.h new file mode 100644 index 0000000..3256cc7 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_eeprom.h @@ -0,0 +1,31 @@ +/* + eeprom.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef eeprom_memcpy_h +#define eeprom_memcpy_h + +#include "grbl.h" + +//unsigned char eeprom_get_char(unsigned int addr); +//void eeprom_put_char(unsigned int addr, unsigned char new_value); +void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size); +int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_limits.cpp b/Grbl_Esp32-master/Grbl_Esp32/grbl_limits.cpp new file mode 100644 index 0000000..e0d6659 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_limits.cpp @@ -0,0 +1,496 @@ +/* + limits.c - code pertaining to limit-switches and performing the homing cycle + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + 2018-12-29 - Wolfgang Lienbacher renamed file from limits.h to grbl_limits.h + fixing ambiguation issues with limit.h in the esp32 Arduino Framework + when compiling with VS-Code/PlatformIO. + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +xQueueHandle limit_sw_queue; // used by limit switch debouncing + +// Homing axis search distance multiplier. Computed by this value times the cycle travel. +#ifndef HOMING_AXIS_SEARCH_SCALAR + #define HOMING_AXIS_SEARCH_SCALAR 1.1 // Must be > 1 to ensure limit switch will be engaged. +#endif +#ifndef HOMING_AXIS_LOCATE_SCALAR + #define HOMING_AXIS_LOCATE_SCALAR 5.0 // Must be > 1 to ensure limit switch is cleared. +#endif + +void IRAM_ATTR isr_limit_switches() +{ + // Ignore limit switches if already in an alarm state or in-process of executing an alarm. + // When in the alarm state, Grbl should have been reset or will force a reset, so any pending + // moves in the planner and serial buffers are all cleared and newly sent blocks will be + // locked out until a homing cycle or a kill lock command. Allows the user to disable the hard + // limit setting if their limits are constantly triggering after a reset and move their axes. + + if ( ( sys.state != STATE_ALARM) & (bit_isfalse(sys.state, STATE_HOMING)) ) { + if (!(sys_rt_exec_alarm)) { + + #ifdef ENABLE_SOFTWARE_DEBOUNCE + // we will start a task that will recheck the switches after a small delay + int evt; + xQueueSendFromISR(limit_sw_queue, &evt, NULL); + #else + #ifdef HARD_LIMIT_FORCE_STATE_CHECK + // Check limit pin state. + if (limits_get_state()) { + mc_reset(); // Initiate system kill. + system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event + } + #else + mc_reset(); // Initiate system kill. + system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event + #endif + #endif + } + } +} + +// Homes the specified cycle axes, sets the machine position, and performs a pull-off motion after +// completing. Homing is a special motion case, which involves rapid uncontrolled stops to locate +// the trigger point of the limit switches. The rapid stops are handled by a system level axis lock +// mask, which prevents the stepper algorithm from executing step pulses. Homing motions typically +// circumvent the processes for executing motions in normal operation. +// NOTE: Only the abort realtime command can interrupt this process. +// TODO: Move limit pin-specific calls to a general function for portability. +void limits_go_home(uint8_t cycle_mask) +{ + if (sys.abort) { return; } // Block if system reset has been issued. + + // Initialize plan data struct for homing motion. Spindle and coolant are disabled. + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); + pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE); + #ifdef USE_LINE_NUMBERS + pl_data->line_number = HOMING_CYCLE_LINE_NUMBER; + #endif + + // Initialize variables used for homing computations. + uint8_t n_cycle = (2*N_HOMING_LOCATE_CYCLE+1); + uint8_t step_pin[N_AXIS]; + float target[N_AXIS]; + float max_travel = 0.0; + uint8_t idx; + for (idx=0; idxfeed_rate = homing_rate; // Set current homing rate. + plan_buffer_line(target, pl_data); // Bypass mc_line(). Directly plan homing motion. + + sys.step_control = STEP_CONTROL_EXECUTE_SYS_MOTION; // Set to execute homing motion and clear existing flags. + st_prep_buffer(); // Prep and fill segment buffer from newly planned block. + st_wake_up(); // Initiate motion + do { + if (approach) { + // Check limit state. Lock out cycle axes when they change. + limit_state = limits_get_state(); + for (idx=0; idx 0); + + // The active cycle axes should now be homed and machine limits have been located. By + // default, Grbl defines machine space as all negative, as do most CNCs. Since limit switches + // can be on either side of an axes, check and set axes machine zero appropriately. Also, + // set up pull-off maneuver from axes limit switches that have been homed. This provides + // some initial clearance off the switches and should also help prevent them from falsely + // triggering when hard limits are enabled or when more than one axes shares a limit pin. + int32_t set_axis_position; + // Set machine positions for homed limit switches. Don't update non-homed axes. + for (idx=0; idx. +*/ + +#ifndef grbl_limits_h +#define grbl_limits_h + +#define SQUARING_MODE_DUAL 0 // both motors run +#define SQUARING_MODE_A 1 // A motor runs +#define SQUARING_MODE_B 2 // B motor runs + +// Initialize the limits module +void limits_init(); + +// Disables hard limits. +void limits_disable(); + +// Returns limit state as a bit-wise uint8 variable. +uint8_t limits_get_state(); + +// Perform one portion of the homing cycle based on the input settings. +void limits_go_home(uint8_t cycle_mask); + +// Check for soft limit violations +void limits_soft_check(float *target); + +void isr_limit_switches(); + +bool axis_is_squared(uint8_t axis_mask); + +// A task that runs after a limit switch interrupt. +void limitCheckTask(void *pvParameters); + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.cpp b/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.cpp new file mode 100644 index 0000000..85b2022 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.cpp @@ -0,0 +1,233 @@ +/* + grbl_sd.cpp - Adds SD Card Features to Grbl_ESP32 + Part of Grbl_ESP32 + + Copyright (c) 2018 Barton Dring Buildlog.net + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl_sd.h" + +// Define line flags. Includes comment type tracking and line overflow detection. +#define LINE_FLAG_OVERFLOW bit(0) +#define LINE_FLAG_COMMENT_PARENTHESES bit(1) +#define LINE_FLAG_COMMENT_SEMICOLON bit(2) + +File myFile; +bool SD_ready_next = false; // Grbl has processed a line and is waiting for another +uint8_t SD_client = CLIENT_SERIAL; +uint32_t sd_current_line_number; // stores the most recent line number read from the SD +static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. + +// attempt to mount the SD card +/*bool sd_mount() +{ + if(!SD.begin()) { + report_status_message(STATUS_SD_FAILED_MOUNT, CLIENT_SERIAL); + return false; + } + return true; +}*/ + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels, uint8_t client) +{ + //char temp_filename[128]; // to help filter by extension TODO: 128 needs a definition based on something + + File root = fs.open(dirname); + if(!root) { + report_status_message(STATUS_SD_FAILED_OPEN_DIR, client); + return; + } + if(!root.isDirectory()) { + report_status_message(STATUS_SD_DIR_NOT_FOUND, client); + return; + } + + File file = root.openNextFile(); + while(file) { + if(file.isDirectory()) { + if(levels) { + listDir(fs, file.name(), levels -1, client); + } + } else { + grbl_sendf(CLIENT_ALL, "[FILE:%s|SIZE:%d]\r\n", file.name(), file.size()); + } + file = root.openNextFile(); + } +} + +boolean openFile(fs::FS &fs, const char * path) +{ + myFile = fs.open(path); + + if(!myFile) { + //report_status_message(STATUS_SD_FAILED_READ, CLIENT_SERIAL); + return false; + } + + set_sd_state(SDCARD_BUSY_PRINTING); + SD_ready_next = false; // this will get set to true when Grbl issues "ok" message + sd_current_line_number = 0; + return true; +} + +boolean closeFile() +{ + if(!myFile) { + return false; + } + + set_sd_state(SDCARD_IDLE); + SD_ready_next = false; + sd_current_line_number = 0; + myFile.close(); + return true; +} + +/* + read a line from the SD card + strip whitespace + strip comments per http://linuxcnc.org/docs/ja/html/gcode/overview.html#gcode:comments + make uppercase + return true if a line is +*/ +boolean readFileLine(char *line) +{ + char c; + uint8_t index = 0; + uint8_t line_flags = 0; + uint8_t comment_char_counter = 0; + + if (!myFile) { + report_status_message(STATUS_SD_FAILED_READ, SD_client); + return false; + } + + sd_current_line_number += 1; + + while(myFile.available()) { + c = myFile.read(); + + if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { // capture all characters into a comment buffer + comment[comment_char_counter++] = c; + } + + if (c == '\r' || c == ' ' ) { + // ignore these whitespace items + } else if (c == '(') { + line_flags |= LINE_FLAG_COMMENT_PARENTHESES; + } else if (c == ')') { + // End of '()' comment. Resume line allowed. + if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { + line_flags &= ~(LINE_FLAG_COMMENT_PARENTHESES); + comment[comment_char_counter] = 0; // null terminate + report_gcode_comment(comment); + } + } else if (c == ';') { + // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. + if (!(line_flags & LINE_FLAG_COMMENT_PARENTHESES)) { // semi colon inside parentheses do not mean anything + line_flags |= LINE_FLAG_COMMENT_SEMICOLON; + } + } else if (c == '%') { + // discard this character + } else if (c == '\n') { // found the newline, so mark the end and return true + line[index] = '\0'; + return true; + } else { // add characters to the line + if (!line_flags) { + c = toupper(c); // make upper case + line[index] = c; + index++; + } + } + + if (index == 255) { // name is too long so return false + line[index] = '\0'; + report_status_message(STATUS_OVERFLOW, SD_client); + return false; + } + } + // some files end without a newline + if (index !=0) { + line[index] = '\0'; + return true; + } else { // empty line after new line + return false; + } +} + +// return a percentage complete 50.5 = 50.5% +float sd_report_perc_complete() +{ + if (!myFile) { + return 0.0; + } + + return ((float)myFile.position() / (float)myFile.size() * 100.0); +} + +uint32_t sd_get_current_line_number() +{ + return sd_current_line_number; +} + + +uint8_t sd_state = SDCARD_IDLE; + +uint8_t get_sd_state(bool refresh) +{ +#if defined(SDCARD_DET_PIN) && SDCARD_SD_PIN != -1 + //no need to go further if SD detect is not correct + if (!((digitalRead (SDCARD_DET_PIN) == SDCARD_DET_VAL) ? true : false)) { + sd_state = SDCARD_NOT_PRESENT; + return sd_state; + } +#endif + //if busy doing something return state + if (!((sd_state == SDCARD_NOT_PRESENT) || (sd_state == SDCARD_IDLE))) { + return sd_state; + } + if (!refresh) { + return sd_state; //to avoid refresh=true + busy to reset SD and waste time + } + //SD is idle or not detected, let see if still the case + + SD.end(); + sd_state = SDCARD_NOT_PRESENT; + //using default value for speed ? should be parameter + //refresh content if card was removed + if (SD.begin((GRBL_SPI_SS == -1)?SS:GRBL_SPI_SS, SPI, GRBL_SPI_FREQ)) { + if ( SD.cardSize() > 0 )sd_state = SDCARD_IDLE; + } + return sd_state; +} + +uint8_t set_sd_state(uint8_t flag) +{ + sd_state = flag; + return sd_state; +} + +void sd_get_current_filename(char* name) +{ + + if (myFile) { + strcpy(name, myFile.name()); + } else { + name[0] = 0; + } +} + + diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.h b/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.h new file mode 100644 index 0000000..9ac285b --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_sd.h @@ -0,0 +1,53 @@ +/* + * Connect the SD card to the following pins: + * + * SD Card | ESP32 + * D2 - + * D3 SS + * CMD MOSI + * VSS GND + * VDD 3.3V + * CLK SCK + * VSS GND + * D0 MISO + * D1 - + */ + +#ifndef grbl_sd_h +#define grbl_sd_h + + + #include "grbl.h" + #include "FS.h" + #include "SD.h" + #include "SPI.h" + +#define FILE_TYPE_COUNT 5 // number of acceptable gcode file types in array + +#define SDCARD_DET_PIN -1 +#define SDCARD_DET_VAL 0 + +#define SDCARD_IDLE 0 +#define SDCARD_NOT_PRESENT 1 +#define SDCARD_BUSY_PRINTING 2 +#define SDCARD_BUSY_UPLOADING 4 +#define SDCARD_BUSY_PARSING 8 + + + +extern bool SD_ready_next; // Grbl has processed a line and is waiting for another +extern uint8_t SD_client; + +//bool sd_mount(); +uint8_t get_sd_state(bool refresh); +uint8_t set_sd_state(uint8_t flag); +void listDir(fs::FS &fs, const char * dirname, uint8_t levels, uint8_t client); +boolean openFile(fs::FS &fs, const char * path); +boolean closeFile(); +boolean readFileLine(char *line); +void readFile(fs::FS &fs, const char * path); +float sd_report_perc_complete(); +uint32_t sd_get_current_line_number(); +void sd_get_current_filename(char* name); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.cpp b/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.cpp new file mode 100644 index 0000000..da662ef --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.cpp @@ -0,0 +1,231 @@ +/* + grbl_trinamic.cpp - Support for Trinamic Stepper Drivers SPI Mode + using the TMCStepper library + + Part of Grbl_ESP32 + + Copyright (c) 2019 Barton Dring for Buildlog.net LLC + + Grbl_ESP32 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ +#include "grbl.h" + +#ifdef USE_TRINAMIC +/* + The drivers can use SPI daisy chaining to allow the use of only (1) CS_PIN. + The PCB must be designed for this, with SDO pins being coonect to the + next driver's SDI pin. The final SDO goes back to the controller. + + This is setup in cpu_map. + add #define TRINAMIC_DAISY_CHAIN to your map + Make every axis CS_PIN definition be for the same pin like this... + #define X_CS_PIN GPIO_NUM_17 + #define Y_CS_PIN GPIO_NUM_17 + ...etc. + + Indexes are assigned to each axis in daisy chain mode as shown below. + This assumes your first SPI driver axis is X and there are no gaps until + the last SPI driver. + +*/ +#ifndef TRINAMIC_DAISY_CHAIN + #define X_DRIVER_SPI_INDEX -1 + #define Y_DRIVER_SPI_INDEX -1 + #define Z_DRIVER_SPI_INDEX -1 + #define A_DRIVER_SPI_INDEX -1 + #define B_DRIVER_SPI_INDEX -1 + #define C_DRIVER_SPI_INDEX -1 +#else + #define X_DRIVER_SPI_INDEX 1 + #define Y_DRIVER_SPI_INDEX 2 + #define Z_DRIVER_SPI_INDEX 3 + #define A_DRIVER_SPI_INDEX 4 + #define B_DRIVER_SPI_INDEX 5 + #define C_DRIVER_SPI_INDEX 6 +#endif + +// TODO try to use the #define ## method to clean this up +//#define DRIVER(driver, axis) driver##Stepper = TRINAMIC_axis## = driver##Stepper(axis##_CS_PIN, axis##_RSENSE); + +#ifdef X_TRINAMIC + #ifdef X_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_X = TMC2130Stepper(X_CS_PIN, X_RSENSE, X_DRIVER_SPI_INDEX); + #endif + #ifdef X_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_X = TMC2209Stepper(X_CS_PIN, X_RSENSE, X_DRIVER_SPI_INDEX); + #endif + #ifdef X_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_X = TMC5160Stepper(X_CS_PIN, X_RSENSE, X_DRIVER_SPI_INDEX); + #endif +#endif + +#ifdef Y_TRINAMIC + #ifdef Y_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_Y = TMC2130Stepper(Y_CS_PIN, Y_RSENSE, Y_DRIVER_SPI_INDEX); + #endif + #ifdef Y_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_Y = TMC2209Stepper(Y_CS_PIN, Y_RSENSE, Y_DRIVER_SPI_INDEX); + #endif + #ifdef Y_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_Y = TMC5160Stepper(Y_CS_PIN, Y_RSENSE, Y_DRIVER_SPI_INDEX); + #endif +#endif + +#ifdef Z_TRINAMIC + #ifdef Z_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_Z = TMC2130Stepper(Z_CS_PIN, Z_RSENSE, Z_DRIVER_SPI_INDEX); + #endif + #ifdef Z_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_Z = TMC2209Stepper(Z_CS_PIN, Z_RSENSE, Z_DRIVER_SPI_INDEX); + #endif + #ifdef Z_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_Z = TMC5160Stepper(Z_CS_PIN, Z_RSENSE, Z_DRIVER_SPI_INDEX); + #endif +#endif + +#ifdef A_TRINAMIC + #ifdef A_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_A = TMC2130Stepper(A_CS_PIN, A_RSENSE, A_DRIVER_SPI_INDEX); + #endif + #ifdef A_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_A = TMC2209Stepper(A_CS_PIN, A_RSENSE, A_DRIVER_SPI_INDEX); + #endif + #ifdef A_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_A = TMC5160Stepper(A_CS_PIN, A_RSENSE, A_DRIVER_SPI_INDEX); + #endif +#endif + +#ifdef B_TRINAMIC + #ifdef B_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_B = TMC2130Stepper(B_CS_PIN, B_RSENSE, B_DRIVER_SPI_INDEX); + #endif + #ifdef B_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_B = TMC2209Stepper(B_CS_PIN, B_RSENSE, B_DRIVER_SPI_INDEX); + #endif + #ifdef B_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_B = TMC5160Stepper(B_CS_PIN, B_RSENSE, B_DRIVER_SPI_INDEX); + #endif +#endif + +#ifdef C_TRINAMIC + #ifdef C_DRIVER_TMC2130 + TMC2130Stepper TRINAMIC_c = TMC2130Stepper(C_CS_PIN, C_RSENSE, C_DRIVER_SPI_INDEX); + #endif + #ifdef C_DRIVER_TMC2209 + TMC2209Stepper TRINAMIC_C = TMC2209Stepper(C_CS_PIN, C_RSENSE, C_DRIVER_SPI_INDEX); + #endif + #ifdef C_DRIVER_TMC5160 + TMC5160Stepper TRINAMIC_C = TMC5160Stepper(C_CS_PIN, C_RSENSE, C_DRIVER_SPI_INDEX); + #endif +#endif + +// TODO ABC Axes + +void Trinamic_Init() +{ + grbl_sendf(CLIENT_SERIAL, "[MSG:TMCStepper Init using Library Ver 0x%06x]\r\n", TMCSTEPPER_VERSION); + + SPI.begin(); + + #ifdef X_TRINAMIC + TRINAMIC_X.begin(); // Initiate pins and registries + TRINAMIC_X.toff(5); + TRINAMIC_X.microsteps(settings.microsteps[X_AXIS]); + TRINAMIC_X.rms_current(settings.current[X_AXIS] * 1000.0, settings.hold_current[X_AXIS]/100.0); + TRINAMIC_X.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_X.pwm_autoscale(1); + #endif + + #ifdef Y_TRINAMIC + TRINAMIC_Y.begin(); // Initiate pins and registries + TRINAMIC_Y.toff(5); + TRINAMIC_Y.microsteps(settings.microsteps[Y_AXIS]); + TRINAMIC_X.rms_current(settings.current[Y_AXIS] * 1000.0, settings.hold_current[Y_AXIS]/100.0); + TRINAMIC_Y.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_Y.pwm_autoscale(1); + #endif + + #ifdef Z_TRINAMIC + TRINAMIC_Z.begin(); // Initiate pins and registries + TRINAMIC_Z.toff(5); + TRINAMIC_Z.microsteps(settings.microsteps[Z_AXIS]); + TRINAMIC_X.rms_current(settings.current[Z_AXIS] * 1000.0, settings.hold_current[Z_AXIS]/100.0); + TRINAMIC_Z.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_Z.pwm_autoscale(1); + #endif + + #ifdef A_TRINAMIC + TRINAMIC_A.begin(); // Initiate pins and registries + TRINAMIC_A.toff(5); + TRINAMIC_A.microsteps(settings.microsteps[A_AXIS]); + TRINAMIC_X.rms_current(settings.current[A_AXIS] * 1000.0, settings.hold_current[A_AXIS]/100.0); + TRINAMIC_A.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_A.pwm_autoscale(1); + #endif + + #ifdef B_TRINAMIC + TRINAMIC_B.begin(); // Initiate pins and registries + TRINAMIC_B.toff(5); + TRINAMIC_B.microsteps(settings.microsteps[B_AXIS]); + TTRINAMIC_X.rms_current(settings.current[B_AXIS] * 1000.0, settings.hold_current[B_AXIS]/100.0); + TRINAMIC_B.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_B.pwm_autoscale(1); + #endif + + #ifdef C_TRINAMIC + TRINAMIC_C.begin(); // Initiate pins and registries + TRINAMIC_C.toff(5); + TRINAMIC_C.microsteps(settings.microsteps[C_AXIS]); + TRINAMIC_X.rms_current(settings.current[C_AXIS] * 1000.0, settings.hold_current[C_AXIS]/100.0); + TRINAMIC_C.en_pwm_mode(1); // Enable extremely quiet stepping + TRINAMIC_C.pwm_autoscale(1); + #endif + + +} + +void trinamic_change_settings() +{ + #ifdef X_TRINAMIC + TRINAMIC_X.microsteps(settings.microsteps[X_AXIS]); + TRINAMIC_X.rms_current(settings.current[X_AXIS] * 1000.0, settings.hold_current[X_AXIS]/100.0); + #endif + + #ifdef Y_TRINAMIC + TRINAMIC_Y.microsteps(settings.microsteps[Y_AXIS]); + TRINAMIC_X.rms_current(settings.current[Y_AXIS] * 1000.0, settings.hold_current[Y_AXIS]/100.0); + #endif + + #ifdef Z_TRINAMIC + TRINAMIC_Z.microsteps(settings.microsteps[Z_AXIS]); + TRINAMIC_X.rms_current(settings.current[Z_AXIS] * 1000.0, settings.hold_current[Z_AXIS]/100.0); + #endif + + #ifdef A_TRINAMIC + TRINAMIC_A.microsteps(settings.microsteps[A_AXIS]); + TRINAMIC_X.rms_current(settings.current[A_AXIS] * 1000.0, settings.hold_current[A_AXIS]/100.0); + #endif + + #ifdef B_TRINAMIC + TRINAMIC_B.microsteps(settings.microsteps[B_AXIS]); + TTRINAMIC_X.rms_current(settings.current[B_AXIS] * 1000.0, settings.hold_current[B_AXIS]/100.0); + #endif + + #ifdef C_TRINAMIC + TRINAMIC_C.microsteps(settings.microsteps[C_AXIS]); + TRINAMIC_X.rms_current(settings.current[C_AXIS] * 1000.0, settings.hold_current[C_AXIS]/100.0); + #endif +} + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.h b/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.h new file mode 100644 index 0000000..9723124 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_trinamic.h @@ -0,0 +1,32 @@ +/* + grbl_trinamic.h - Support for TMC2130 Stepper Drivers SPI Mode + Part of Grbl_ESP32 + + Copyright (c) 2019 Barton Dring for Buildlog.net LLC + + GrblESP32 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef GRBL_TRINAMIC_h + #define GRBL_TRINAMIC_h + +#include "grbl.h" + +#ifdef USE_TRINAMIC + #include // https://github.com/teemuatlut/TMCStepper + void Trinamic_Init(); + void trinamic_change_settings(); +#endif + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.cpp b/Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.cpp new file mode 100644 index 0000000..aaef076 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/grbl_unipolar.cpp @@ -0,0 +1,236 @@ +/* + unipolar.cpp + Part of Grbl_ESP32 + + copyright (c) 2019 - Bart Dring. This file was intended for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + Unipolar Class + + This class allows you to control a unipolar motor. Unipolar motors have 5 wires. One + is typically tied to a voltage, while the other 4 are switched to ground in a + sequence + + To take a step simply call the step(step, direction) function. + +*/ +#include "grbl.h" + +#ifdef USE_UNIPOLAR + + // assign the I/O pins used for each coil of the motors + #ifdef X_UNIPOLAR + Unipolar X_Unipolar(X_PIN_PHASE_0, X_PIN_PHASE_1, X_PIN_PHASE_2, X_PIN_PHASE_3, true); + #endif + + #ifdef Y_UNIPOLAR + Unipolar Y_Unipolar(Y_PIN_PHASE_0, Y_PIN_PHASE_1, Y_PIN_PHASE_2, Y_PIN_PHASE_3, true); + #endif + + #ifdef Z_UNIPOLAR + Unipolar Z_Unipolar(Z_PIN_PHASE_0, Z_PIN_PHASE_1, Z_PIN_PHASE_2, Z_PIN_PHASE_3, true); + #endif + + void unipolar_init(){ + #ifdef X_UNIPOLAR + X_Unipolar.init(); + grbl_send(CLIENT_SERIAL, "[MSG:X Unipolar]\r\n"); + #endif + #ifdef Y_UNIPOLAR + Y_Unipolar.init(); + grbl_send(CLIENT_SERIAL, "[MSG:Y Unipolar]\r\n"); + #endif + #ifdef Z_UNIPOLAR + Z_Unipolar.init(); + grbl_send(CLIENT_SERIAL, "[MSG:Z Unipolar]\r\n"); + #endif + } + + void unipolar_step(uint8_t step_mask, uint8_t dir_mask) + { + #ifdef X_UNIPOLAR + X_Unipolar.step(step_mask & (1<. + + Unipolar Class + + This class allows you to control a unipolar motor. Unipolar motors have 5 wires. One + is typically tied to a voltage, while the other 4 are switched to ground in a + sequence + + To take a step simply call the step(direction) function. It will take + +*/ +#ifndef grbl_unipolar_h + #define grbl_unipolar_h + + void unipolar_init(); + void unipolar_step(uint8_t step_mask, uint8_t dir_mask); + void unipolar_disable(bool enable); + + class Unipolar{ + public: + Unipolar(uint8_t pin_phase0, uint8_t pin_phase1, uint8_t pin_phase2, uint8_t pin_phase3, bool half_step); // constructor + void set_enabled(bool enabled); + void step(bool step, bool dir_forward); + void init(); + + private: + uint8_t _current_phase = 0; + bool _enabled = false; + bool _half_step = true; // default is half step, full step + uint8_t _pin_phase0; + uint8_t _pin_phase1; + uint8_t _pin_phase2; + uint8_t _pin_phase3; + + }; +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.cpp b/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.cpp new file mode 100644 index 0000000..f336155 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.cpp @@ -0,0 +1,108 @@ +/* + inputbuffer.cpp - inputbuffer functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" +#include "inputbuffer.h" + +InputBuffer inputBuffer; + + +InputBuffer::InputBuffer(){ + _RXbufferSize = 0; + _RXbufferpos = 0; +} +InputBuffer::~InputBuffer(){ + _RXbufferSize = 0; + _RXbufferpos = 0; +} +void InputBuffer::begin(){ + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +void InputBuffer::end(){ + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +InputBuffer::operator bool() const +{ + return true; +} + +int InputBuffer::available(){ + return _RXbufferSize; +} + +size_t InputBuffer::write(uint8_t c) +{ + //No need currently + //keep for compatibility + return 1; +} + +size_t InputBuffer::write(const uint8_t *buffer, size_t size) +{ + //No need currently + //keep for compatibility + return size; +} + +int InputBuffer::peek(void){ + if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; + else return -1; +} + +bool InputBuffer::push (const char * data){ + int data_size = strlen(data); + if ((data_size + _RXbufferSize) <= RXBUFFERSIZE){ + int current = _RXbufferpos + _RXbufferSize; + if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE; + for (int i = 0; i < data_size; i++){ + if (current > (RXBUFFERSIZE-1)) current = 0; + _RXbuffer[current] = data[i]; + current ++; + } + _RXbufferSize+=strlen(data); + return true; + } + return false; +} + +int InputBuffer::read(void){ + if (_RXbufferSize > 0) { + int v = _RXbuffer[_RXbufferpos]; + _RXbufferpos++; + if (_RXbufferpos > (RXBUFFERSIZE-1))_RXbufferpos = 0; + _RXbufferSize--; + return v; + } else return -1; +} + +void InputBuffer::flush(void){ + //No need currently + //keep for compatibility +} + + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.h b/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.h new file mode 100644 index 0000000..9986d6a --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/inputbuffer.h @@ -0,0 +1,71 @@ +/* + inputbuffer.h - inputbuffer functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _INPUT_BUFFER_H_ +#define _INPUT_BUFFER_H_ + +#include "Print.h" +#define RXBUFFERSIZE 128 +class InputBuffer: public Print{ + public: + InputBuffer(); + ~InputBuffer(); + size_t write(uint8_t c); + size_t write(const uint8_t *buffer, size_t size); + + inline size_t write(const char * s) + { + return write((uint8_t*) s, strlen(s)); + } + inline size_t write(unsigned long n) + { + return write((uint8_t) n); + } + inline size_t write(long n) + { + return write((uint8_t) n); + } + inline size_t write(unsigned int n) + { + return write((uint8_t) n); + } + inline size_t write(int n) + { + return write((uint8_t) n); + } + void begin(); + void end(); + int available(); + int peek(void); + int read(void); + bool push (const char * data); + void flush(void); + operator bool() const; + private: + uint8_t _RXbuffer[RXBUFFERSIZE]; + uint16_t _RXbufferSize; + uint16_t _RXbufferpos; +}; + + +extern InputBuffer inputBuffer; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/jog.cpp b/Grbl_Esp32-master/Grbl_Esp32/jog.cpp new file mode 100644 index 0000000..7b6b81b --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/jog.cpp @@ -0,0 +1,53 @@ +/* + jog.h - Jogging methods + Part of Grbl + + Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + + +// Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. +uint8_t jog_execute(plan_line_data_t *pl_data, parser_block_t *gc_block) +{ + // Initialize planner data struct for jogging motions. + // NOTE: Spindle and coolant are allowed to fully function with overrides during a jog. + pl_data->feed_rate = gc_block->values.f; + pl_data->condition |= PL_COND_FLAG_NO_FEED_OVERRIDE; + #ifdef USE_LINE_NUMBERS + pl_data->line_number = gc_block->values.n; + #endif + + if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { + if (system_check_travel_limits(gc_block->values.xyz)) { return(STATUS_TRAVEL_EXCEEDED); } + } + + // Valid jog command. Plan, set state, and execute. + mc_line(gc_block->values.xyz,pl_data); + if (sys.state == STATE_IDLE) { + if (plan_get_current_block() != NULL) { // Check if there is a block to execute. + sys.state = STATE_JOG; + st_prep_buffer(); + st_wake_up(); // NOTE: Manual start. No state machine required. + } + } + + return(STATUS_OK); +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/jog.h b/Grbl_Esp32-master/Grbl_Esp32/jog.h new file mode 100644 index 0000000..07bddba --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/jog.h @@ -0,0 +1,35 @@ +/* + jog.h - Jogging methods + Part of Grbl + + Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef jog_h +#define jog_h + +#include "grbl.h" + + +// System motion line numbers must be zero. +#define JOG_LINE_NUMBER 0 + +// Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. +uint8_t jog_execute(plan_line_data_t *pl_data, parser_block_t *gc_block); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/motion_control.cpp b/Grbl_Esp32-master/Grbl_Esp32/motion_control.cpp new file mode 100644 index 0000000..fa23fca --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/motion_control.cpp @@ -0,0 +1,483 @@ +/* + motion_control.c - high level interface for issuing motion commands + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +uint8_t ganged_mode = SQUARING_MODE_DUAL; + + +// this allows kinematics to be used. +void mc_line_kins(float *target, plan_line_data_t *pl_data, float *position) +{ + #ifndef USE_KINEMATICS + mc_line(target, pl_data); + #else // else use kinematics + inverse_kinematics(target, pl_data, position); + #endif +} + +// Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second +// unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in +// (1 minute)/feed_rate time. +// NOTE: This is the primary gateway to the grbl planner. All line motions, including arc line +// segments, must pass through this routine before being passed to the planner. The seperation of +// mc_line and plan_buffer_line is done primarily to place non-planner-type functions from being +// in the planner and to let backlash compensation or canned cycle integration simple and direct. +void mc_line(float *target, plan_line_data_t *pl_data) +{ + // If enabled, check for soft limit violations. Placed here all line motions are picked up + // from everywhere in Grbl. + if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { + // NOTE: Block jog state. Jogging is a special case and soft limits are handled independently. + if (sys.state != STATE_JOG) { limits_soft_check(target); } + } + + // If in check gcode mode, prevent motion by blocking planner. Soft limits still work. + if (sys.state == STATE_CHECK_MODE) { return; } + + // NOTE: Backlash compensation may be installed here. It will need direction info to track when + // to insert a backlash line motion(s) before the intended line motion and will require its own + // plan_check_full_buffer() and check for system abort loop. Also for position reporting + // backlash steps will need to be also tracked, which will need to be kept at a system level. + // There are likely some other things that will need to be tracked as well. However, we feel + // that backlash compensation should NOT be handled by Grbl itself, because there are a myriad + // of ways to implement it and can be effective or ineffective for different CNC machines. This + // would be better handled by the interface as a post-processor task, where the original g-code + // is translated and inserts backlash motions that best suits the machine. + // NOTE: Perhaps as a middle-ground, all that needs to be sent is a flag or special command that + // indicates to Grbl what is a backlash compensation motion, so that Grbl executes the move but + // doesn't update the machine position values. Since the position values used by the g-code + // parser and planner are separate from the system machine positions, this is doable. + + // If the buffer is full: good! That means we are well ahead of the robot. + // Remain in this loop until there is room in the buffer. + do { + protocol_execute_realtime(); // Check for any run-time commands + if (sys.abort) { return; } // Bail, if system abort. + if ( plan_check_full_buffer() ) { protocol_auto_cycle_start(); } // Auto-cycle start when buffer is full. + else { break; } + } while (1); + + // Plan and queue motion into planner buffer + // uint8_t plan_status; // Not used in normal operation. + plan_buffer_line(target, pl_data); +} + + +// Execute an arc in offset mode format. position == current xyz, target == target xyz, +// offset == offset from current xyz, axis_X defines circle plane in tool space, axis_linear is +// the direction of helical travel, radius == circle radius, isclockwise boolean. Used +// for vector transformation direction. +// The arc is approximated by generating a huge number of tiny, linear segments. The chordal tolerance +// of each segment is configured in settings.arc_tolerance, which is defined to be the maximum normal +// distance from segment to the circle when the end points both lie on the circle. +void mc_arc(float *target, plan_line_data_t *pl_data, float *position, float *offset, float radius, + uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc) +{ + float center_axis0 = position[axis_0] + offset[axis_0]; + float center_axis1 = position[axis_1] + offset[axis_1]; + float r_axis0 = -offset[axis_0]; // Radius vector from center to current location + float r_axis1 = -offset[axis_1]; + float rt_axis0 = target[axis_0] - center_axis0; + float rt_axis1 = target[axis_1] - center_axis1; + +#ifdef USE_KINEMATICS + float previous_position[N_AXIS]; + uint16_t n; + for (n = 0; n < N_AXIS; n++) { + previous_position[n] = position[n]; + } +#endif + + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1); + if (is_clockwise_arc) { // Correct atan2 output per direction + if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2*M_PI; } + } else { + if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2*M_PI; } + } + + // NOTE: Segment end points are on the arc, which can lead to the arc diameter being smaller by up to + // (2x) settings.arc_tolerance. For 99% of users, this is just fine. If a different arc segment fit + // is desired, i.e. least-squares, midpoint on arc, just change the mm_per_arc_segment calculation. + // For the intended uses of Grbl, this value shouldn't exceed 2000 for the strictest of cases. + uint16_t segments = floor(fabs(0.5*angular_travel*radius)/ + sqrt(settings.arc_tolerance*(2*radius - settings.arc_tolerance)) ); + + if (segments) { + // Multiply inverse feed_rate to compensate for the fact that this movement is approximated + // by a number of discrete segments. The inverse feed_rate should be correct for the sum of + // all segments. + if (pl_data->condition & PL_COND_FLAG_INVERSE_TIME) { + pl_data->feed_rate *= segments; + bit_false(pl_data->condition,PL_COND_FLAG_INVERSE_TIME); // Force as feed absolute mode over arc segments. + } + + float theta_per_segment = angular_travel/segments; + float linear_per_segment = (target[axis_linear] - position[axis_linear])/segments; + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. Single precision values can accumulate error greater than tool precision in rare + cases. So, exact arc path correction is implemented. This approach avoids the problem of too many very + expensive trig operations [sin(),cos(),tan()] which can take 100-200 usec each to compute. + + Small angle approximation may be used to reduce computation overhead further. A third-order approximation + (second order sin() has too much error) holds for most, if not, all CNC applications. Note that this + approximation will begin to accumulate a numerical drift error when theta_per_segment is greater than + ~0.25 rad(14 deg) AND the approximation is successively used without correction several dozen times. This + scenario is extremely unlikely, since segment lengths and theta_per_segment are automatically generated + and scaled by the arc tolerance setting. Only a very large arc tolerance setting, unrealistic for CNC + applications, would cause this numerical drift error. However, it is best to set N_ARC_CORRECTION from a + low of ~4 to a high of ~20 or so to avoid trig operations while keeping arc generation accurate. + + This approximation also allows mc_arc to immediately insert a line segment into the planner + without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + This is important when there are successive arc motions. + */ + // Computes: cos_T = 1 - theta_per_segment^2/2, sin_T = theta_per_segment - theta_per_segment^3/6) in ~52usec + float cos_T = 2.0 - theta_per_segment*theta_per_segment; + float sin_T = theta_per_segment*0.16666667*(cos_T + 4.0); + cos_T *= 0.5; + + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + uint8_t count = 0; + + for (i = 1; i. +*/ + + + +#ifndef motion_control_h + #define motion_control_h + +#include "grbl.h" + + +// System motion commands must have a line number of zero. +#define HOMING_CYCLE_LINE_NUMBER 0 +#define PARKING_MOTION_LINE_NUMBER 0 + +#define HOMING_CYCLE_ALL 0 // Must be zero. +#define HOMING_CYCLE_X bit(X_AXIS) +#define HOMING_CYCLE_Y bit(Y_AXIS) +#define HOMING_CYCLE_Z bit(Z_AXIS) +#define HOMING_CYCLE_A bit(A_AXIS) +#define HOMING_CYCLE_B bit(B_AXIS) +#define HOMING_CYCLE_C bit(C_AXIS) + + +// Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second +// unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in +// (1 minute)/feed_rate time. +void mc_line_kins(float *target, plan_line_data_t *pl_data, float *position); +void mc_line(float *target, plan_line_data_t *pl_data); + +// Execute an arc in offset mode format. position == current xyz, target == target xyz, +// offset == offset from current xyz, axis_XXX defines circle plane in tool space, axis_linear is +// the direction of helical travel, radius == circle radius, is_clockwise_arc boolean. Used +// for vector transformation direction. +void mc_arc(float *target, plan_line_data_t *pl_data, float *position, float *offset, float radius, + uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc); + +// Dwell for a specific number of seconds +void mc_dwell(float seconds); + +// Perform homing cycle to locate machine zero. Requires limit switches. +void mc_homing_cycle(uint8_t cycle_mask); + +// Perform tool length probe cycle. Requires probe switch. +uint8_t mc_probe_cycle(float *target, plan_line_data_t *pl_data, uint8_t parser_flags); + +// Plans and executes the single special motion case for parking. Independent of main planner buffer. +void mc_parking_motion(float *parking_target, plan_line_data_t *pl_data); + +// Performs system reset. If in motion state, kills all motion and sets system alarm. +void mc_reset(); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/nofile.h b/Grbl_Esp32-master/Grbl_Esp32/nofile.h new file mode 100644 index 0000000..7c34dd3 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/nofile.h @@ -0,0 +1,452 @@ +/* + nofile.h - ESP3D data file + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//data generated by https://github.com/AraHaan/bin2c +//bin2c Conversion Tool v0.14.0 - Windows - [FINAL]. +#ifndef __nofile_h +#define __nofile_h +/* Generated by bin2c, do not edit manually */ + +/* Contents of file tool.html.gz */ +#define PAGE_NOFILES_SIZE 6728 +const char PAGE_NOFILES[6728] PROGMEM = { + 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xED, 0x3C, 0x89, 0x72, 0xDB, 0xC6, + 0x92, 0xBF, 0x82, 0x20, 0x15, 0x93, 0x58, 0x02, 0x24, 0x2E, 0xDE, 0xA2, 0xBC, 0x49, 0x2C, 0x27, + 0xDA, 0xB2, 0x63, 0x97, 0x24, 0xAF, 0xF7, 0x95, 0xE3, 0x52, 0x81, 0xC4, 0x50, 0xC4, 0x1A, 0x04, + 0x28, 0x60, 0x28, 0x4A, 0x96, 0xB9, 0xDF, 0xBE, 0xDD, 0x3D, 0x83, 0x8B, 0x97, 0x8E, 0xE7, 0xB7, + 0x2F, 0x5B, 0xF5, 0xA2, 0x90, 0x00, 0xE6, 0xE8, 0xE9, 0xE9, 0xBB, 0x1B, 0x43, 0x1F, 0xCD, 0xF8, + 0x3C, 0x3C, 0x3E, 0x9A, 0x31, 0xCF, 0x3F, 0x3E, 0x4A, 0xF9, 0x5D, 0xC8, 0x8E, 0xB1, 0xE5, 0x7E, + 0x1A, 0x47, 0xDC, 0x98, 0x7A, 0xF3, 0x20, 0xBC, 0x1B, 0xA4, 0x5E, 0x94, 0x1A, 0x29, 0x4B, 0x82, + 0xE9, 0xD0, 0x98, 0xA7, 0x06, 0x67, 0xB7, 0xDC, 0x48, 0x83, 0xAF, 0xCC, 0xF0, 0xFC, 0xFF, 0x5E, + 0xA6, 0x7C, 0x60, 0x99, 0xE6, 0x4F, 0x43, 0x63, 0xC5, 0xC6, 0x5F, 0x02, 0xBE, 0xA7, 0x97, 0xC0, + 0x61, 0x2B, 0x3C, 0x2E, 0x6E, 0xD7, 0xE3, 0xD8, 0xBF, 0xAB, 0x2C, 0xA1, 0xFE, 0xCE, 0xC2, 0x1B, + 0xC6, 0x83, 0x89, 0xA7, 0xFC, 0xC1, 0x96, 0x4C, 0xD5, 0xF3, 0x67, 0xFD, 0xE7, 0x24, 0xF0, 0x42, + 0xBD, 0x84, 0x43, 0x09, 0x96, 0xBB, 0xB8, 0x1D, 0x86, 0x41, 0xC4, 0x8C, 0x19, 0x0B, 0xAE, 0x66, + 0xB0, 0x56, 0xD3, 0xB5, 0x7B, 0xED, 0xAE, 0xE5, 0x3A, 0xC3, 0x49, 0x1C, 0xC6, 0xC9, 0xE0, 0x47, + 0xC7, 0x71, 0x86, 0x63, 0x6F, 0xF2, 0xE5, 0x2A, 0x89, 0x97, 0x91, 0x6F, 0xC8, 0xD6, 0xE9, 0x74, + 0xBA, 0xE6, 0xDE, 0x38, 0x64, 0xF7, 0xE3, 0x38, 0xF1, 0x59, 0x32, 0x30, 0x87, 0xE2, 0xC6, 0x48, + 0x17, 0xDE, 0x24, 0x88, 0xAE, 0xA0, 0x61, 0xEE, 0xDD, 0x1A, 0xAB, 0xC0, 0xE7, 0x33, 0xDA, 0xC1, + 0x9A, 0xFB, 0xF7, 0xAB, 0x59, 0xC0, 0x19, 0x8D, 0x60, 0x83, 0x28, 0x5E, 0x25, 0xDE, 0x62, 0xB8, + 0xF0, 0x7C, 0x1F, 0x87, 0xDB, 0xF3, 0xF9, 0x9A, 0xCF, 0xEE, 0x69, 0xF3, 0x5E, 0x18, 0x5C, 0x45, + 0x83, 0x90, 0x4D, 0xF9, 0xBA, 0x49, 0x8B, 0x1C, 0x73, 0xDC, 0xEF, 0x31, 0x4F, 0x8E, 0xB9, 0xAF, + 0x6F, 0x35, 0xCD, 0xF2, 0x26, 0x62, 0x42, 0x75, 0x54, 0xDE, 0x34, 0xBB, 0xCF, 0x96, 0xEA, 0xED, + 0xDF, 0xF3, 0x0D, 0x4B, 0x90, 0x64, 0xA1, 0x44, 0x81, 0xC7, 0x8B, 0x6C, 0x5B, 0x70, 0x3B, 0xB0, + 0x16, 0xB7, 0x4A, 0x1A, 0x87, 0x81, 0xAF, 0xFC, 0xE8, 0xFB, 0xBE, 0xC4, 0xCD, 0x48, 0x79, 0x12, + 0x2C, 0x98, 0x9F, 0x23, 0x34, 0x88, 0xF8, 0xCC, 0x88, 0xA7, 0x06, 0xBF, 0x5B, 0xB0, 0x7A, 0xEC, + 0xFB, 0xDA, 0xFD, 0x0E, 0xF2, 0xF5, 0xF1, 0x6F, 0xED, 0xDD, 0x2F, 0xE2, 0x34, 0xE0, 0x41, 0x1C, + 0x0D, 0x12, 0x16, 0x7A, 0x3C, 0xB8, 0x61, 0x43, 0x3F, 0x48, 0x17, 0xA1, 0x77, 0x37, 0x18, 0x87, + 0xF1, 0xE4, 0x4B, 0x4E, 0x1E, 0x64, 0xBA, 0x62, 0xB5, 0x01, 0x73, 0xA2, 0x90, 0xCF, 0x26, 0x71, + 0xE2, 0xD1, 0xC4, 0x28, 0x8E, 0x58, 0xC6, 0xAB, 0xC9, 0x64, 0xB2, 0x6E, 0x7A, 0x13, 0x84, 0x73, + 0x5F, 0x30, 0x6A, 0x07, 0xFB, 0x4C, 0xD3, 0xCC, 0x06, 0x2A, 0x9E, 0xEE, 0x0D, 0xA6, 0xF1, 0x64, + 0x99, 0xC2, 0x75, 0x16, 0x03, 0x05, 0x4A, 0x53, 0xD7, 0xCD, 0x85, 0x17, 0xB1, 0xF0, 0x7E, 0xEE, + 0x25, 0x57, 0x41, 0x64, 0x8C, 0x63, 0xCE, 0xE3, 0xF9, 0xC0, 0x06, 0x64, 0x76, 0xCB, 0x84, 0xA4, + 0xD6, 0x06, 0xA5, 0x32, 0x1A, 0x26, 0x9E, 0x1F, 0x2C, 0xD3, 0x01, 0xCA, 0x5C, 0x26, 0xEC, 0xE3, + 0xF8, 0xD6, 0x48, 0x67, 0x9E, 0x1F, 0xAF, 0x06, 0xA6, 0x82, 0xB3, 0xF0, 0x93, 0x5C, 0x8D, 0xBD, + 0xBA, 0xA9, 0xE3, 0x5F, 0xD3, 0x6C, 0x6B, 0xC3, 0xC7, 0x0C, 0x92, 0x98, 0x1A, 0xA4, 0x18, 0x39, + 0xD5, 0x80, 0x60, 0x59, 0x07, 0x0A, 0x02, 0xB4, 0xDD, 0x6F, 0x53, 0xF4, 0xB0, 0xA0, 0xB7, 0xF1, + 0x2F, 0xDB, 0x81, 0x6C, 0x2C, 0xED, 0x09, 0xE4, 0xC2, 0x48, 0x50, 0x8C, 0xB2, 0xDD, 0x39, 0x48, + 0x9B, 0xA2, 0x0F, 0xA5, 0x78, 0x47, 0x97, 0xA4, 0xE4, 0xA6, 0x44, 0x4D, 0xE3, 0x64, 0x0E, 0x8B, + 0x44, 0x3C, 0x89, 0xC3, 0xFB, 0xAA, 0x24, 0x08, 0x4D, 0xF2, 0x96, 0x3C, 0x1E, 0x4A, 0xB9, 0x75, + 0x90, 0x90, 0xD9, 0x76, 0x3A, 0xB8, 0x1B, 0x1B, 0x1A, 0x9E, 0xA4, 0xDC, 0xED, 0x76, 0x7B, 0x1F, + 0x23, 0x8B, 0xD6, 0x60, 0xEE, 0x5D, 0x31, 0x21, 0x67, 0xDB, 0xEC, 0x05, 0x91, 0x7B, 0x1C, 0x7B, + 0x83, 0x28, 0x65, 0x5C, 0xD9, 0xC3, 0xBF, 0x6E, 0x95, 0xCB, 0x0F, 0x8E, 0x35, 0x62, 0x83, 0x27, + 0x60, 0xD0, 0x84, 0xEE, 0x94, 0x99, 0xA3, 0x30, 0x2F, 0x65, 0x06, 0xC8, 0x6A, 0xBC, 0xE4, 0x4A, + 0xD3, 0x6A, 0xA7, 0x7A, 0x01, 0x77, 0xAB, 0xAF, 0x4A, 0x70, 0xA1, 0x05, 0xF7, 0x55, 0x56, 0x77, + 0x3A, 0xDE, 0x94, 0xF5, 0x87, 0x30, 0x03, 0x29, 0x09, 0x56, 0xED, 0x19, 0x5B, 0xD3, 0x4D, 0xE8, + 0xEC, 0x65, 0x1D, 0x96, 0x69, 0xEB, 0x56, 0xB7, 0xAD, 0xDB, 0x8E, 0xA3, 0x37, 0x3B, 0x9A, 0xC4, + 0x01, 0x69, 0xBD, 0xD8, 0xD0, 0x33, 0x21, 0xBE, 0x63, 0x1E, 0xE5, 0xA2, 0x10, 0x44, 0xC4, 0x4F, + 0x21, 0x11, 0xD5, 0xC1, 0xA6, 0xE0, 0xFC, 0x4A, 0xB0, 0xDA, 0x35, 0xCD, 0x61, 0xC9, 0x96, 0x4E, + 0x58, 0xC4, 0x59, 0xB2, 0x69, 0xDE, 0xE6, 0x81, 0xEF, 0x87, 0x4C, 0xB8, 0xA4, 0x78, 0x39, 0x99, + 0x19, 0x68, 0x11, 0x80, 0x9E, 0x73, 0x2F, 0x0A, 0x16, 0xCB, 0x90, 0xEC, 0xCB, 0x70, 0x7F, 0xCF, + 0x64, 0x99, 0xA4, 0x40, 0xA2, 0x45, 0x1C, 0x10, 0xF0, 0x47, 0x4A, 0x0C, 0xF1, 0x6D, 0xE1, 0x25, + 0x80, 0xD1, 0xF0, 0x80, 0x3F, 0x78, 0xA2, 0x3C, 0xEF, 0x10, 0xC1, 0x79, 0xFC, 0xD5, 0x58, 0xA6, + 0xE8, 0x91, 0x58, 0xC8, 0x26, 0x5C, 0xA0, 0x83, 0x7B, 0xDD, 0x6A, 0xDC, 0x6C, 0x20, 0x9A, 0x1B, + 0x8B, 0x04, 0xB6, 0x91, 0xDC, 0x1D, 0x36, 0xA4, 0x8E, 0xD3, 0xF5, 0xC6, 0xDD, 0x0D, 0xF3, 0x60, + 0xB3, 0x8E, 0xEF, 0xB9, 0x15, 0x28, 0xD2, 0xD8, 0xEA, 0x95, 0x36, 0x61, 0x75, 0x2B, 0x4D, 0x64, + 0x80, 0x2B, 0x4D, 0x83, 0x1D, 0x33, 0x07, 0xDB, 0x33, 0xB7, 0x4C, 0xF7, 0x0E, 0x64, 0xED, 0x5E, + 0xC7, 0xEC, 0x9B, 0x1B, 0xC8, 0x5A, 0xB6, 0x3D, 0x76, 0x4D, 0x42, 0x36, 0x98, 0x5F, 0xDD, 0x4B, + 0xA6, 0xCE, 0xBC, 0x68, 0xD3, 0x6C, 0x77, 0x72, 0xEB, 0x55, 0xD6, 0x7F, 0x72, 0x12, 0x62, 0xAE, + 0x44, 0x61, 0x87, 0x3D, 0x31, 0xF1, 0x6F, 0x63, 0xDD, 0xCE, 0x04, 0xFF, 0x9E, 0xAD, 0x4E, 0x28, + 0x1F, 0x57, 0x09, 0xBB, 0x7B, 0x8A, 0xD9, 0xA8, 0x4C, 0x24, 0xAC, 0x09, 0xCD, 0xC3, 0xDB, 0x76, + 0x4C, 0xA9, 0x84, 0xD9, 0xD8, 0x87, 0xB6, 0xF9, 0xCF, 0xDC, 0x51, 0x08, 0x48, 0x81, 0x86, 0x7C, + 0xD1, 0x8B, 0xDB, 0x41, 0x35, 0x1E, 0x20, 0xCF, 0x5F, 0x74, 0x56, 0xA4, 0x06, 0xFB, 0x82, 0x68, + 0xB1, 0xE4, 0x9F, 0x30, 0x76, 0x19, 0x4D, 0x83, 0x90, 0x7D, 0x1E, 0x0C, 0xB2, 0xFD, 0xE0, 0xA3, + 0xB1, 0x5C, 0x84, 0xB1, 0xE7, 0x1B, 0xE3, 0x25, 0xD8, 0x9C, 0x7F, 0x99, 0xA5, 0xFF, 0x5B, 0xB3, + 0x34, 0x3C, 0xA8, 0xDC, 0xED, 0xF1, 0xC4, 0xF4, 0xD9, 0x86, 0x92, 0xB9, 0x9D, 0x71, 0xCF, 0xF7, + 0x9E, 0xC4, 0x54, 0xE9, 0x05, 0xFF, 0xC5, 0xDA, 0xBF, 0x0E, 0x6B, 0x1D, 0x6B, 0x6C, 0xFA, 0x9B, + 0x31, 0xA8, 0x35, 0xEE, 0xF8, 0xBD, 0xF6, 0xD3, 0x58, 0x2B, 0xB4, 0xFD, 0x5F, 0xAC, 0xFD, 0x8B, + 0xB3, 0xD6, 0xEE, 0xF4, 0xBD, 0xF1, 0x24, 0x4B, 0x5C, 0xA6, 0x71, 0x0C, 0x14, 0x39, 0x90, 0xB7, + 0x58, 0x5D, 0xB3, 0xB7, 0x0B, 0xF6, 0x23, 0x52, 0x97, 0xAD, 0x04, 0xE4, 0x9F, 0xB0, 0xE4, 0x3C, + 0xF6, 0xBD, 0x22, 0xD9, 0x21, 0x92, 0xE5, 0x59, 0xF1, 0x34, 0xB8, 0x65, 0xFE, 0xF0, 0x2B, 0xC4, + 0xEC, 0x3E, 0xBB, 0xC5, 0x32, 0x02, 0x48, 0xA2, 0xC4, 0x4A, 0xC0, 0x32, 0x31, 0x15, 0xC5, 0x1C, + 0x0B, 0x44, 0x16, 0x1B, 0xCC, 0x61, 0x51, 0x71, 0xC8, 0xF2, 0x24, 0xBA, 0x47, 0xC9, 0x9F, 0x86, + 0xE0, 0x52, 0x29, 0x83, 0xDA, 0x99, 0x11, 0x6F, 0xB7, 0x96, 0xDD, 0xAD, 0xAB, 0x49, 0x54, 0x29, + 0x5D, 0x00, 0x81, 0xBB, 0xDF, 0x93, 0xE5, 0x59, 0x66, 0x35, 0x03, 0xAC, 0x64, 0x87, 0xE5, 0x4E, + 0xA1, 0x6B, 0x7B, 0xE7, 0xCA, 0xEE, 0x7D, 0xD3, 0x07, 0x76, 0x41, 0xC7, 0x3C, 0x0A, 0x2D, 0xE5, + 0xC9, 0x98, 0x6F, 0x58, 0x28, 0xF8, 0x66, 0x25, 0x6A, 0xB0, 0xB5, 0xE1, 0x76, 0xCD, 0x41, 0x28, + 0xBF, 0x20, 0x4D, 0xC6, 0xF4, 0x1D, 0xE4, 0xF8, 0x71, 0xCA, 0xF0, 0x2F, 0xA3, 0x03, 0x66, 0xD4, + 0x25, 0x29, 0xB1, 0xE5, 0x82, 0x99, 0x90, 0x50, 0x34, 0xB4, 0x53, 0x48, 0x6C, 0xFC, 0xDB, 0x97, + 0x24, 0x3F, 0x91, 0x7C, 0x95, 0x5C, 0x74, 0x8A, 0x7F, 0x19, 0x7A, 0xD5, 0x4A, 0x80, 0x29, 0xB1, + 0xCB, 0x7A, 0x37, 0x45, 0xBC, 0x93, 0x61, 0x2F, 0x85, 0xC6, 0x6D, 0xB6, 0xD9, 0xFC, 0xE9, 0x5B, + 0xD9, 0x46, 0xE7, 0xEF, 0xE4, 0xF6, 0xBA, 0x39, 0x0B, 0x7C, 0x76, 0x19, 0xF0, 0x8A, 0x86, 0xAC, + 0xFF, 0x7D, 0xCE, 0xFC, 0xC0, 0x53, 0xEA, 0x73, 0xB0, 0xD9, 0x42, 0xE2, 0xBB, 0x1D, 0xE0, 0xB8, + 0x76, 0xBF, 0x21, 0xA3, 0xA2, 0xAF, 0xDD, 0x43, 0x48, 0xD9, 0xA4, 0x74, 0x92, 0x30, 0x16, 0x29, + 0x10, 0xEA, 0xC2, 0xFC, 0xBC, 0x46, 0xD7, 0xED, 0x74, 0xF7, 0xCE, 0xA7, 0xFA, 0xDD, 0xFA, 0xA8, + 0x25, 0xCA, 0x9B, 0x47, 0x3C, 0xE0, 0x70, 0x39, 0x39, 0x7F, 0xEF, 0xBC, 0x52, 0x78, 0x1C, 0x87, + 0xCA, 0x02, 0x2C, 0xF4, 0x51, 0x4B, 0x34, 0x1F, 0xB5, 0x44, 0x29, 0x94, 0xAA, 0x61, 0x47, 0x7E, + 0x70, 0xA3, 0x4C, 0x42, 0x2F, 0x4D, 0x47, 0x2A, 0x99, 0x16, 0x15, 0x66, 0x63, 0xD5, 0x4C, 0x21, + 0xC0, 0x23, 0x15, 0x21, 0x63, 0x5B, 0x02, 0x1F, 0x98, 0xE4, 0x65, 0x83, 0x45, 0x46, 0xA1, 0x2A, + 0xB3, 0x84, 0x4D, 0x47, 0xEA, 0x8C, 0xF3, 0x45, 0x3A, 0x68, 0xB5, 0xAE, 0x02, 0x3E, 0x5B, 0x8E, + 0x9B, 0x93, 0x78, 0xDE, 0x1A, 0xFB, 0x09, 0xF0, 0xAD, 0xF5, 0x5B, 0x32, 0x0E, 0x2F, 0x4F, 0xD2, + 0x85, 0x63, 0xAB, 0x0A, 0x07, 0x29, 0x66, 0x7C, 0xA4, 0x5E, 0x42, 0x78, 0x1B, 0x7D, 0x01, 0xA8, + 0xE9, 0xCD, 0x55, 0xBE, 0x0E, 0x9B, 0x03, 0x30, 0x62, 0xAD, 0x7C, 0xB8, 0x09, 0xD8, 0xEA, 0x97, + 0xF8, 0x76, 0xA4, 0x62, 0x08, 0x6D, 0x39, 0x26, 0x7C, 0xD9, 0xA6, 0x09, 0xB3, 0xAE, 0x84, 0x57, + 0xC1, 0xAC, 0x7C, 0xA4, 0xD2, 0x2D, 0x68, 0x09, 0xAB, 0xB7, 0x4D, 0x1D, 0x07, 0x68, 0x40, 0x3E, + 0x2F, 0x64, 0x75, 0x4B, 0x57, 0x0C, 0x4B, 0x83, 0xE1, 0x0B, 0x8F, 0xCF, 0x14, 0x7F, 0xA4, 0xBE, + 0xED, 0x20, 0x08, 0xAB, 0xEB, 0x5E, 0x3B, 0x0E, 0x40, 0xEC, 0xBA, 0x8A, 0xD1, 0x0E, 0x9D, 0x1E, + 0x8C, 0x6A, 0xDB, 0x61, 0x1B, 0x2E, 0xD7, 0x6E, 0x1F, 0xBE, 0x5D, 0xA5, 0x0F, 0x3D, 0x4E, 0x1F, + 0x9B, 0xEC, 0xD0, 0x72, 0x5C, 0xA5, 0x67, 0x5E, 0x77, 0x2C, 0xC5, 0x70, 0x7B, 0x8A, 0x65, 0x42, + 0x97, 0x65, 0xB6, 0x43, 0xA3, 0x67, 0xC2, 0x8D, 0xE3, 0x86, 0x0E, 0x00, 0xB9, 0xB6, 0x61, 0xA8, + 0xEB, 0x2A, 0x0E, 0x4C, 0xEF, 0x3B, 0x21, 0x0C, 0xED, 0x84, 0x00, 0x13, 0x80, 0xF4, 0xAE, 0xB1, + 0xC7, 0x51, 0xE0, 0xBB, 0xEB, 0x5C, 0xC3, 0x14, 0x07, 0x17, 0x85, 0x07, 0x37, 0x34, 0xE4, 0x08, + 0xB8, 0x81, 0xF1, 0xD7, 0xF0, 0x08, 0x23, 0xFB, 0xB8, 0x30, 0x01, 0x31, 0x10, 0x70, 0x28, 0x57, + 0xB9, 0xC6, 0xB5, 0x0D, 0xC4, 0xA1, 0x40, 0x80, 0x10, 0xB3, 0x42, 0x84, 0xE6, 0x5C, 0xE3, 0xEA, + 0x06, 0x62, 0x21, 0x51, 0x37, 0x08, 0x77, 0x43, 0x6C, 0xCE, 0x52, 0xAE, 0x11, 0x07, 0xB1, 0x2E, + 0xA2, 0x6B, 0xD0, 0xFE, 0xF1, 0xA1, 0x4D, 0x63, 0x60, 0x08, 0xCE, 0xB0, 0xAF, 0x11, 0x01, 0xD8, + 0x3F, 0x42, 0x11, 0x40, 0x1C, 0xB1, 0x8E, 0xD1, 0xB3, 0xAE, 0x8D, 0x8E, 0xA9, 0x20, 0x16, 0x88, + 0x01, 0x22, 0xD0, 0x43, 0x9E, 0xB8, 0x88, 0x27, 0x00, 0x84, 0xA5, 0x5D, 0x44, 0xA4, 0xA7, 0x20, + 0xEA, 0xB6, 0xD2, 0x09, 0x69, 0x5D, 0xD8, 0xBF, 0xD1, 0x51, 0x5C, 0xD8, 0x67, 0x07, 0xC8, 0x0D, + 0xFB, 0x87, 0x85, 0xE1, 0x0E, 0x48, 0x44, 0x9D, 0x21, 0x0C, 0xBC, 0xB6, 0x1C, 0x04, 0x2B, 0x66, + 0x3A, 0x8A, 0xA0, 0x2C, 0x6E, 0xD9, 0xED, 0x2A, 0xB0, 0x61, 0x58, 0x89, 0x56, 0xB3, 0x60, 0x26, + 0xF4, 0x84, 0x88, 0x25, 0xAC, 0x04, 0xEB, 0x09, 0x1C, 0xA1, 0x37, 0xA4, 0x1D, 0x40, 0x33, 0x92, + 0x19, 0xF7, 0xF4, 0x95, 0x18, 0xDD, 0x03, 0x82, 0x5E, 0x1B, 0xBD, 0x3E, 0xEE, 0x94, 0x48, 0xDD, + 0x71, 0x38, 0x7C, 0x88, 0x20, 0xCD, 0x36, 0x2F, 0xEE, 0xB2, 0x4E, 0xBC, 0xC2, 0x05, 0x3A, 0x44, + 0xBB, 0x51, 0xDC, 0x89, 0xAE, 0xAF, 0x20, 0x4B, 0x2D, 0x14, 0x26, 0xB8, 0x5C, 0xC1, 0x07, 0x84, + 0xF7, 0x58, 0x39, 0x82, 0x70, 0x26, 0xCA, 0x75, 0x22, 0xCB, 0xDA, 0xD4, 0xE3, 0xD7, 0x41, 0x32, + 0x5F, 0x41, 0xD8, 0x03, 0xC3, 0x60, 0x00, 0x8C, 0xF6, 0xE0, 0x83, 0x0A, 0xF4, 0x08, 0x25, 0x5A, + 0xAD, 0x56, 0xCD, 0x92, 0x22, 0x85, 0xCB, 0x89, 0x21, 0x1E, 0x5B, 0xA4, 0xD1, 0xC6, 0xC7, 0x93, + 0x5F, 0x3E, 0x9C, 0xB6, 0x38, 0xD8, 0x88, 0x96, 0xDD, 0xB4, 0xFE, 0x1A, 0x6A, 0x65, 0xF6, 0xDD, + 0xEB, 0x9E, 0x8D, 0x10, 0x3B, 0x66, 0x13, 0xA5, 0xCF, 0x46, 0xD2, 0xBA, 0x40, 0xFC, 0x76, 0x9F, + 0x5B, 0x56, 0x07, 0xDB, 0x7A, 0xD8, 0xD6, 0x77, 0xF1, 0xB6, 0x0F, 0x1C, 0xE8, 0xD1, 0xC5, 0xB5, + 0xF3, 0x2E, 0x14, 0xBD, 0x76, 0x97, 0x08, 0x9E, 0xDF, 0xA1, 0xE0, 0x52, 0xA7, 0xD1, 0xE9, 0xC9, + 0x89, 0x46, 0x0E, 0xC2, 0x28, 0x03, 0x36, 0xB2, 0xD5, 0x80, 0x5D, 0xFD, 0x1C, 0x05, 0xF9, 0x60, + 0xE7, 0x23, 0x68, 0x00, 0x4D, 0x13, 0xB3, 0x08, 0x58, 0x3F, 0x83, 0xDF, 0x17, 0x4B, 0x66, 0x00, + 0x15, 0x42, 0x22, 0xBB, 0x12, 0xAA, 0xD4, 0x05, 0xB8, 0xF7, 0xDB, 0x0A, 0xCF, 0xE6, 0x96, 0xE0, + 0xC9, 0x25, 0x04, 0x15, 0x70, 0xD5, 0xAF, 0x6F, 0x7B, 0xBD, 0x1E, 0xF4, 0xF5, 0x49, 0xC5, 0x51, + 0xCB, 0x2D, 0x90, 0x57, 0x9B, 0x13, 0x82, 0x64, 0x39, 0xDA, 0x5D, 0x94, 0x67, 0x40, 0xAA, 0x8F, + 0x16, 0xC2, 0xB2, 0x51, 0xDF, 0x80, 0x36, 0x36, 0x0C, 0xC2, 0x2F, 0x7C, 0x12, 0x37, 0x78, 0x85, + 0x1E, 0xB8, 0xBD, 0xC6, 0x45, 0x14, 0x1B, 0x04, 0xD4, 0x02, 0xB2, 0x2B, 0x56, 0x5F, 0x71, 0x69, + 0x39, 0xC0, 0xB9, 0x8B, 0x5B, 0x87, 0x11, 0x46, 0x17, 0x80, 0x75, 0xD0, 0xA0, 0x75, 0x10, 0x6A, + 0x0F, 0x8C, 0x88, 0x85, 0x32, 0xDF, 0x51, 0x84, 0xA9, 0x31, 0x91, 0x17, 0x70, 0x05, 0x14, 0xAF, + 0x6D, 0xB4, 0x44, 0xA0, 0xA8, 0x5D, 0x30, 0x0A, 0x16, 0xC7, 0x89, 0x3D, 0x9B, 0xF7, 0x05, 0x63, + 0x2C, 0xD8, 0x1D, 0x1A, 0x8F, 0x1E, 0x6E, 0xCE, 0x71, 0x88, 0xB0, 0xB8, 0x98, 0x7C, 0xB0, 0x5D, + 0xEA, 0xA7, 0x6E, 0x9A, 0xD1, 0x43, 0x8D, 0xE9, 0x9A, 0xE2, 0x0A, 0x10, 0xBB, 0xB0, 0xD0, 0xB5, + 0x05, 0x9A, 0x0C, 0x24, 0x53, 0xDC, 0x8C, 0xAE, 0x2E, 0xF4, 0x5E, 0x1B, 0x7D, 0xB2, 0xC7, 0x88, + 0x14, 0xEC, 0xA4, 0xD7, 0xFF, 0xFA, 0xD6, 0x05, 0x53, 0xD0, 0xB5, 0xBB, 0x60, 0x55, 0xD0, 0x9A, + 0x48, 0xAB, 0x48, 0x1F, 0xE2, 0xA8, 0x83, 0xAB, 0x10, 0xF3, 0x69, 0xBE, 0x03, 0x53, 0x91, 0x13, + 0xB8, 0x2D, 0x0B, 0x2C, 0x09, 0x6E, 0xCD, 0x51, 0x1C, 0x92, 0x13, 0xCB, 0xE2, 0x0E, 0x32, 0xC5, + 0xEA, 0x86, 0x00, 0x0B, 0xEC, 0x09, 0x2C, 0x8A, 0xF4, 0x47, 0x14, 0x11, 0x71, 0xC0, 0xA2, 0x23, + 0x6F, 0xC9, 0x7A, 0xA2, 0x01, 0x05, 0x63, 0x01, 0xE8, 0xC0, 0xA2, 0x84, 0xAD, 0x61, 0x03, 0xA9, + 0x4D, 0x6E, 0x38, 0x36, 0xD2, 0xF3, 0x49, 0xCA, 0x7F, 0x8A, 0x19, 0xD2, 0x14, 0xD2, 0x9C, 0xE7, + 0x68, 0xFF, 0x21, 0x17, 0xDA, 0x5A, 0x05, 0x5F, 0x82, 0xBF, 0x86, 0xC2, 0x5B, 0xDD, 0xEE, 0x35, + 0x32, 0xCF, 0x04, 0xB1, 0x03, 0xDA, 0xB9, 0x6D, 0x94, 0x8F, 0x9E, 0x2B, 0xA4, 0x0F, 0xAC, 0xA9, + 0xED, 0x90, 0xD4, 0x21, 0xC3, 0xDA, 0x42, 0x1B, 0x5D, 0x6E, 0x94, 0x6E, 0x4B, 0x03, 0x8C, 0xD2, + 0x3C, 0xA3, 0x80, 0x46, 0xB7, 0xE2, 0x4E, 0x0C, 0xA0, 0x7E, 0x9C, 0x27, 0xA7, 0x11, 0x34, 0x04, + 0x96, 0xDF, 0x14, 0x9D, 0xC5, 0x8C, 0x0C, 0xCA, 0xD7, 0xB7, 0x6D, 0x50, 0x9E, 0xBE, 0x0B, 0xAE, + 0xCC, 0x26, 0xAF, 0x00, 0x1A, 0x64, 0xB4, 0xA5, 0xA1, 0x37, 0x6C, 0xD4, 0x07, 0x90, 0x72, 0x29, + 0x64, 0x24, 0x60, 0xC2, 0x67, 0x48, 0x13, 0x83, 0x02, 0x88, 0xFA, 0x09, 0xAA, 0x6A, 0xD3, 0x65, + 0x66, 0x39, 0xD6, 0xB5, 0x83, 0x70, 0x14, 0x90, 0x30, 0xCB, 0xBA, 0xEE, 0x60, 0x87, 0x4D, 0x7A, + 0xDF, 0x13, 0x28, 0xF5, 0xAE, 0x6D, 0xA4, 0xB9, 0x43, 0xB0, 0x2C, 0x5C, 0xC1, 0xA2, 0x5B, 0x1B, + 0x96, 0x20, 0x58, 0xB0, 0x70, 0x17, 0x83, 0x02, 0xD8, 0x2B, 0x0A, 0x2F, 0xB8, 0x4B, 0x8B, 0xFC, + 0x15, 0xA9, 0x19, 0x12, 0x89, 0xC4, 0x9E, 0x0C, 0x21, 0x2E, 0x4A, 0x20, 0x0C, 0xD4, 0x56, 0xAB, + 0x8B, 0x44, 0x11, 0xCA, 0x88, 0x36, 0x8E, 0xB4, 0x03, 0xFA, 0x00, 0x7F, 0x17, 0xD5, 0x06, 0x10, + 0x56, 0xA8, 0x11, 0xB1, 0xE7, 0x84, 0x94, 0x01, 0xC3, 0x67, 0x16, 0x84, 0x21, 0x82, 0x67, 0x4A, + 0x8F, 0x3B, 0x84, 0xA9, 0x83, 0x16, 0xC3, 0xED, 0x71, 0x1B, 0xD7, 0xEA, 0x22, 0x0D, 0xC1, 0x9A, + 0x9B, 0x80, 0x1E, 0x9A, 0x03, 0x60, 0x70, 0xCF, 0x51, 0x38, 0x99, 0x09, 0x30, 0x82, 0x40, 0x21, + 0x1C, 0xE5, 0x10, 0xF9, 0x3B, 0xA0, 0x51, 0x3D, 0x6C, 0x41, 0xE3, 0x03, 0x4E, 0xB7, 0x0B, 0x60, + 0x4C, 0x73, 0x06, 0xD8, 0x98, 0x80, 0x81, 0x49, 0x1B, 0xE9, 0xE6, 0xF8, 0x0B, 0xBB, 0x04, 0xDF, + 0x37, 0x34, 0x80, 0x76, 0xA3, 0xE4, 0x8D, 0x3C, 0x1F, 0x39, 0xC3, 0x5E, 0x9A, 0x4D, 0x4D, 0xD8, + 0xD7, 0x25, 0x3D, 0x86, 0x99, 0x62, 0xA2, 0x65, 0xD2, 0x40, 0x6A, 0x92, 0x06, 0x0F, 0x3E, 0x4F, + 0x52, 0xD0, 0xDF, 0x59, 0xB8, 0xD8, 0xA1, 0x9B, 0x0A, 0x85, 0xCF, 0x23, 0xB5, 0x88, 0xA9, 0xD5, + 0xA2, 0x2F, 0x8E, 0x26, 0x61, 0x30, 0xF9, 0x32, 0x52, 0xCF, 0xDE, 0xD4, 0xB5, 0xA1, 0xAA, 0x04, + 0xA0, 0x0A, 0x61, 0x0C, 0x59, 0x59, 0x00, 0xA1, 0xB8, 0xBA, 0xAD, 0xD6, 0x55, 0xAD, 0x6C, 0x3A, + 0x15, 0xBD, 0x6C, 0xDA, 0xDF, 0x5D, 0x33, 0xA7, 0x41, 0x18, 0xCA, 0x4D, 0xAA, 0xA4, 0xA6, 0x7D, + 0x8C, 0x82, 0x4C, 0xF3, 0xC6, 0x26, 0x6E, 0xF6, 0xA4, 0x11, 0x87, 0x00, 0x4F, 0xC4, 0x34, 0x24, + 0xE1, 0xD8, 0x32, 0x33, 0x60, 0x65, 0x08, 0x95, 0x6C, 0xE2, 0x98, 0x6B, 0x09, 0x27, 0xDA, 0xA6, + 0x58, 0xD8, 0xBA, 0x01, 0xC1, 0x43, 0x9E, 0xE2, 0x08, 0x97, 0x84, 0xB3, 0x2B, 0xFC, 0x7B, 0x9F, + 0x44, 0x51, 0xC8, 0xA7, 0x89, 0x5C, 0xED, 0xD0, 0x32, 0x38, 0xA8, 0x68, 0xE5, 0xC5, 0xE0, 0x19, + 0xA0, 0x73, 0x4D, 0x10, 0xA8, 0x8D, 0xE6, 0x0B, 0x91, 0xC3, 0xD9, 0x62, 0x32, 0xAE, 0x9D, 0xB7, + 0x71, 0x23, 0x1F, 0x48, 0xEB, 0x83, 0x77, 0x90, 0x1B, 0x12, 0x62, 0x60, 0x5B, 0x68, 0xD0, 0x1D, + 0xD4, 0x60, 0xD4, 0x3F, 0x90, 0xC1, 0x19, 0xA0, 0xAA, 0x48, 0x95, 0x43, 0xB9, 0x22, 0x3B, 0x40, + 0x8A, 0x21, 0xA5, 0x8E, 0x76, 0xFA, 0x55, 0x6D, 0x95, 0x44, 0x64, 0x17, 0xFB, 0x4B, 0x95, 0x34, + 0xCA, 0xF6, 0x90, 0x95, 0x28, 0x47, 0xC8, 0xF1, 0xD7, 0x1F, 0xFF, 0xF3, 0xE4, 0xEC, 0xFC, 0xF4, + 0xDD, 0x1F, 0xEA, 0x0E, 0xB1, 0xCA, 0x45, 0x0A, 0xE1, 0xB5, 0x30, 0x65, 0x6A, 0x89, 0x43, 0x0E, + 0x47, 0x2D, 0x48, 0xB3, 0x76, 0xE6, 0x5A, 0xA2, 0x54, 0x77, 0x7C, 0x34, 0xB3, 0x09, 0xFC, 0xDB, + 0xF3, 0xDF, 0x10, 0xCC, 0xCC, 0x86, 0xAF, 0xAC, 0x6B, 0xF7, 0x5C, 0x45, 0x66, 0x9D, 0x42, 0x10, + 0x5F, 0x9F, 0xBE, 0x39, 0x39, 0xFF, 0xDB, 0xF9, 0xC5, 0xC9, 0x5B, 0x75, 0x7B, 0x68, 0xF6, 0x66, + 0x1D, 0xA2, 0x52, 0x68, 0x9D, 0x29, 0xAF, 0x83, 0x90, 0xA5, 0x77, 0x29, 0x67, 0xF3, 0x3D, 0xB0, + 0x29, 0x33, 0x07, 0x40, 0x54, 0xBA, 0x54, 0xA8, 0x74, 0xA9, 0x62, 0xB1, 0x52, 0xAC, 0x45, 0x65, + 0x4B, 0x51, 0x3F, 0x53, 0x95, 0xC8, 0x9B, 0x43, 0xE7, 0xFC, 0x0E, 0x1B, 0xD3, 0x4F, 0x9F, 0x55, + 0x65, 0xBE, 0x0C, 0x79, 0xB0, 0x40, 0x32, 0x66, 0x77, 0x2A, 0xE8, 0xA1, 0x80, 0x54, 0x28, 0x88, + 0x52, 0x7A, 0x31, 0xA6, 0xCA, 0x15, 0x44, 0x09, 0x54, 0xAC, 0x51, 0xA9, 0x8A, 0xAA, 0x85, 0xEE, + 0x9D, 0xB3, 0xC8, 0xC7, 0xA5, 0x48, 0x03, 0x6F, 0xBC, 0x70, 0x09, 0xF3, 0x3E, 0xD0, 0x58, 0xF5, + 0xF8, 0x45, 0x34, 0x4E, 0x17, 0x43, 0xF1, 0x7D, 0xB4, 0x48, 0xE2, 0xAB, 0x84, 0xA5, 0x69, 0xC6, + 0xD3, 0x9B, 0x20, 0x0D, 0xC6, 0x41, 0x18, 0xF0, 0xBB, 0x01, 0x10, 0xCE, 0x67, 0x51, 0x86, 0xFA, + 0x22, 0xB9, 0x12, 0x4B, 0xD2, 0x0D, 0x64, 0xDB, 0x94, 0xF2, 0x92, 0x31, 0x91, 0x20, 0x20, 0x53, + 0x4E, 0xC4, 0x67, 0x07, 0xFF, 0xF6, 0x91, 0x4E, 0xF2, 0x5D, 0xA4, 0xCD, 0x99, 0x15, 0x20, 0x7B, + 0xF2, 0x14, 0x52, 0x54, 0xF6, 0xFD, 0x6B, 0x3C, 0x9F, 0x7B, 0x91, 0x5F, 0xAF, 0x85, 0x41, 0xCA, + 0x6B, 0x7A, 0xCD, 0x0B, 0xC3, 0x5A, 0x89, 0x0C, 0x67, 0x6C, 0x0A, 0xD8, 0xCE, 0x4A, 0x16, 0xAB, + 0xBC, 0x2A, 0xE2, 0x99, 0x43, 0xFB, 0x35, 0x61, 0x60, 0x4E, 0xFC, 0x20, 0xA9, 0x6B, 0xEA, 0x21, + 0xAB, 0xE5, 0x9A, 0x85, 0xC9, 0xC2, 0xFB, 0x8A, 0xBD, 0x72, 0xF1, 0x7F, 0x18, 0x9F, 0x80, 0x1C, + 0x28, 0xD0, 0xD6, 0x56, 0x95, 0x3B, 0xA4, 0x9D, 0x9A, 0xCD, 0x76, 0x4A, 0xB3, 0x6D, 0xB8, 0x4F, + 0x60, 0x90, 0x0D, 0x97, 0x3B, 0xBA, 0x08, 0x73, 0x25, 0xCB, 0xAB, 0xA8, 0x92, 0x19, 0x1C, 0x1C, + 0x7A, 0x47, 0xE0, 0x32, 0xDB, 0xD9, 0x2E, 0x19, 0xCE, 0xF6, 0x83, 0x70, 0x50, 0x7B, 0x11, 0x8E, + 0x25, 0x10, 0xB2, 0xE1, 0x92, 0x17, 0x95, 0xA1, 0xB5, 0x27, 0x1F, 0x57, 0x12, 0x22, 0x18, 0x94, + 0x0C, 0x08, 0xD5, 0xA7, 0xD5, 0xE3, 0x06, 0x10, 0x10, 0x60, 0xE4, 0x06, 0x82, 0x54, 0x64, 0x83, + 0xA6, 0xD2, 0x37, 0x20, 0x55, 0x49, 0x76, 0xC0, 0x00, 0xE7, 0x94, 0x0C, 0xA2, 0x69, 0x9C, 0x49, + 0x63, 0x79, 0x76, 0xC5, 0x20, 0x88, 0x1A, 0x8B, 0x9C, 0x21, 0x1E, 0x2A, 0x87, 0x95, 0xD4, 0x4C, + 0x70, 0x8B, 0x8A, 0x3A, 0x4A, 0x95, 0xA8, 0xDC, 0x90, 0x54, 0xCD, 0xCA, 0xFC, 0xBD, 0x00, 0xB1, + 0x01, 0xD8, 0x33, 0x6C, 0x3F, 0xFE, 0x03, 0x04, 0x3B, 0x7F, 0x38, 0x87, 0x6D, 0x67, 0x0F, 0xC2, + 0x54, 0x9C, 0x5F, 0xF2, 0x60, 0x0E, 0xFB, 0xBC, 0x08, 0x8A, 0x61, 0x15, 0x59, 0xD9, 0x68, 0xCB, + 0xFD, 0xE0, 0x2C, 0xDF, 0x83, 0x44, 0x03, 0x65, 0x3D, 0x37, 0x09, 0x97, 0x28, 0x96, 0x34, 0x4E, + 0x14, 0x96, 0x0E, 0x9B, 0x3E, 0x59, 0xDE, 0x13, 0xAA, 0x97, 0x72, 0x8F, 0x2F, 0x53, 0x35, 0xA7, + 0xF5, 0xD6, 0xF7, 0x03, 0xC6, 0xEF, 0xE3, 0x87, 0xF7, 0xAF, 0x7E, 0xBE, 0x38, 0x39, 0x6C, 0xFA, + 0x64, 0x42, 0xAE, 0x7C, 0x58, 0xF8, 0x20, 0xFC, 0x0F, 0x58, 0xBE, 0x8A, 0xFA, 0xEE, 0x35, 0x84, + 0xAB, 0xBD, 0x66, 0xB0, 0x14, 0xEE, 0x3F, 0xD9, 0xF4, 0xC1, 0x43, 0x49, 0xF3, 0x85, 0x75, 0xDB, + 0xB6, 0x79, 0xB8, 0x89, 0xF2, 0x32, 0x4F, 0x31, 0x78, 0xD3, 0x55, 0x6E, 0xF2, 0xF0, 0x76, 0xB7, + 0xD1, 0xCB, 0x21, 0xE7, 0xBE, 0x6F, 0x9E, 0x5E, 0xA9, 0xFB, 0xC1, 0x1F, 0x9F, 0x31, 0xE0, 0x63, + 0xC2, 0x81, 0xDA, 0xBA, 0x02, 0x66, 0xDF, 0x4B, 0x99, 0xB2, 0xF2, 0x02, 0xDE, 0x84, 0xFF, 0x32, + 0xC7, 0x98, 0x83, 0x9A, 0xC4, 0x4B, 0x74, 0x6E, 0x0F, 0xBB, 0xCC, 0x82, 0x4D, 0x79, 0xBC, 0x85, + 0xB5, 0xCC, 0x5C, 0xD9, 0xA8, 0x18, 0x5A, 0x65, 0x7C, 0xA5, 0x3E, 0xBA, 0xAB, 0x4B, 0x94, 0xC5, + 0xA1, 0x67, 0xE6, 0x1C, 0x9F, 0x02, 0xEA, 0x3C, 0x98, 0x06, 0x13, 0x7A, 0xC9, 0x05, 0x9E, 0xD7, + 0xD9, 0x21, 0x73, 0x45, 0xB9, 0x5A, 0x86, 0x02, 0xC7, 0x95, 0xC0, 0x52, 0x74, 0xA3, 0xCD, 0x50, + 0x95, 0x3C, 0x58, 0x3B, 0xFE, 0x90, 0x82, 0xDA, 0xCA, 0xED, 0x6D, 0x38, 0xC0, 0xF2, 0x99, 0xA6, + 0x4C, 0x04, 0xC4, 0x74, 0xDA, 0x24, 0xF2, 0xBF, 0x12, 0x89, 0x62, 0xD9, 0xBF, 0x20, 0xD5, 0x2C, + 0x79, 0x3C, 0x12, 0xEF, 0xA1, 0x6F, 0x05, 0x16, 0xE4, 0x09, 0x88, 0x2C, 0xE4, 0x14, 0x89, 0xCC, + 0xE2, 0x30, 0x32, 0xE3, 0x5D, 0x01, 0x4A, 0xB9, 0x80, 0xBF, 0x11, 0x46, 0x64, 0x92, 0xBE, 0x4F, + 0x1F, 0x0A, 0x8F, 0xF7, 0xE6, 0x0C, 0xBD, 0x93, 0x94, 0xF7, 0xF3, 0xE5, 0x78, 0x1E, 0xF0, 0x9D, + 0x16, 0x22, 0x9D, 0x80, 0xC1, 0xE4, 0xC7, 0x37, 0x5E, 0xA2, 0xAC, 0xD2, 0xCB, 0x34, 0x5E, 0x26, + 0x13, 0xA6, 0xDF, 0xCE, 0x43, 0xCC, 0xA7, 0x45, 0x18, 0xA1, 0x4F, 0x96, 0x09, 0xBE, 0x80, 0x44, + 0x2B, 0x3D, 0x52, 0x5B, 0xAA, 0x0E, 0x5B, 0x98, 0x21, 0xDB, 0x05, 0xD3, 0x47, 0x3F, 0x58, 0xFA, + 0x8A, 0x8D, 0xD3, 0x78, 0xF2, 0x85, 0xF1, 0xCB, 0x45, 0x9C, 0xF0, 0x91, 0x59, 0x6A, 0x38, 0x7D, + 0x3F, 0x52, 0x61, 0x4A, 0x7A, 0x17, 0x4D, 0x2E, 0xA1, 0x15, 0xF2, 0xF2, 0xF9, 0x32, 0x2A, 0x4D, + 0x45, 0x71, 0xBC, 0x44, 0x52, 0xA9, 0x3A, 0x88, 0xE7, 0x65, 0x3C, 0x9D, 0x56, 0x01, 0x92, 0x52, + 0x30, 0x1F, 0x1B, 0x59, 0xBA, 0xB8, 0x64, 0x49, 0x12, 0x27, 0x97, 0x73, 0x50, 0x31, 0x98, 0x87, + 0x93, 0x8A, 0xC6, 0x49, 0xEC, 0x33, 0x58, 0x1A, 0x09, 0x25, 0x10, 0x1F, 0x99, 0xC3, 0xE9, 0x32, + 0xA2, 0x77, 0xB1, 0xA0, 0xBC, 0x37, 0x63, 0x0F, 0x1C, 0xF6, 0x3D, 0x6E, 0x14, 0x26, 0x96, 0x4C, + 0x94, 0xAA, 0xF3, 0x51, 0x69, 0x8B, 0xCD, 0x74, 0x01, 0x7A, 0x59, 0x87, 0x8D, 0x6A, 0x7A, 0x44, + 0xFB, 0x0D, 0x46, 0xD6, 0x10, 0x58, 0x5D, 0x67, 0x0D, 0x9C, 0xE7, 0x4B, 0xE2, 0xD7, 0x84, 0xCB, + 0xAF, 0x29, 0x39, 0xD1, 0xFF, 0x54, 0xCB, 0x94, 0xAA, 0xB5, 0x6A, 0x43, 0x65, 0x7F, 0xE0, 0xF1, + 0xA7, 0x7A, 0xDC, 0x22, 0xB5, 0x55, 0x87, 0xC1, 0x11, 0x6F, 0x86, 0x2C, 0xBA, 0xE2, 0x10, 0xA8, + 0x0F, 0xB5, 0x3D, 0xAB, 0xEC, 0x59, 0x44, 0x6D, 0xD4, 0xA3, 0xC6, 0x88, 0x7F, 0x0A, 0x3E, 0x37, + 0x10, 0xE3, 0x86, 0xFA, 0xD0, 0xA2, 0x6A, 0x43, 0x0C, 0xCE, 0x0D, 0x94, 0xC4, 0x42, 0x0F, 0x1A, + 0x8D, 0x61, 0xC2, 0xF8, 0x32, 0x89, 0x14, 0x42, 0xA1, 0x6C, 0x4D, 0xD4, 0x75, 0x4E, 0x48, 0x50, + 0x8E, 0x74, 0x76, 0x89, 0x49, 0x1B, 0x10, 0x53, 0x8C, 0x57, 0xB3, 0xE0, 0xA3, 0xD6, 0xB6, 0x6B, + 0x10, 0x34, 0xD4, 0x2C, 0xB8, 0x40, 0x98, 0x51, 0xEB, 0xD4, 0x30, 0xCC, 0xC0, 0x8B, 0xF0, 0x85, + 0x35, 0xBB, 0x5D, 0xCB, 0x62, 0x91, 0x5A, 0xB7, 0x26, 0x15, 0xA3, 0x86, 0xE1, 0xC3, 0x20, 0x61, + 0xFE, 0xB0, 0xA6, 0xB4, 0x00, 0x91, 0x6D, 0x70, 0xBB, 0x01, 0xD8, 0x55, 0x00, 0x14, 0x7E, 0x6C, + 0x81, 0x70, 0x4C, 0x01, 0xA2, 0xB7, 0x07, 0xA3, 0x4E, 0xB7, 0x00, 0x08, 0x36, 0xFC, 0x61, 0x9C, + 0xEC, 0x2A, 0x40, 0xCB, 0x14, 0x10, 0xF1, 0x2A, 0x41, 0xF6, 0xCA, 0x20, 0xDD, 0x47, 0x43, 0xB4, + 0xFB, 0x3B, 0x21, 0x38, 0x8F, 0xD9, 0xA5, 0x2B, 0x40, 0xB8, 0x8E, 0x40, 0xAA, 0x2B, 0x70, 0xEA, + 0xE6, 0x00, 0x4B, 0xF0, 0x3A, 0x8F, 0x02, 0xD8, 0xF9, 0xDE, 0x00, 0x7B, 0xDF, 0x03, 0xA0, 0x08, + 0x29, 0x11, 0x6C, 0x11, 0x65, 0xD7, 0x6C, 0xB7, 0x24, 0x12, 0x70, 0x9F, 0x45, 0xD9, 0x35, 0xAA, + 0x0A, 0xD8, 0x58, 0x2D, 0xEE, 0xD5, 0x8E, 0xBF, 0xA7, 0x88, 0xFE, 0xBD, 0xF2, 0xF9, 0x7D, 0x85, + 0xF3, 0x3B, 0x4B, 0xE6, 0xDF, 0x2B, 0x96, 0xDF, 0x57, 0x26, 0xBF, 0xAF, 0x40, 0xFE, 0x43, 0xA4, + 0xB1, 0x30, 0x8D, 0xF8, 0xFA, 0x7C, 0xD3, 0x32, 0x3E, 0x56, 0x50, 0x6D, 0x17, 0xFE, 0xAF, 0xE5, + 0x55, 0xE2, 0xDA, 0xDB, 0xAE, 0xEE, 0x28, 0x6F, 0x6C, 0xBD, 0xA7, 0xBC, 0xE9, 0xEA, 0x96, 0x43, + 0xDF, 0xA6, 0xF2, 0xC6, 0x92, 0x97, 0x9E, 0x6E, 0x59, 0xE2, 0xD2, 0x16, 0x8D, 0x1D, 0xB8, 0x98, + 0x74, 0xE9, 0xEB, 0x56, 0x97, 0xBE, 0xFB, 0xD4, 0x64, 0xC3, 0x70, 0x5B, 0x5E, 0x6C, 0xDD, 0xEA, + 0xD1, 0xA5, 0x47, 0x6D, 0x1D, 0x84, 0xDA, 0x51, 0xBE, 0xE2, 0x06, 0x93, 0xF8, 0x0B, 0xEC, 0x90, + 0x8A, 0x31, 0x35, 0x91, 0xCF, 0xD5, 0x68, 0xA7, 0x3B, 0x37, 0x2A, 0xC2, 0xF6, 0x4B, 0xCC, 0x80, + 0x99, 0x76, 0x5F, 0xF2, 0x47, 0x8D, 0x11, 0x43, 0x37, 0xA4, 0x97, 0x3D, 0x90, 0x4A, 0x89, 0x8D, + 0xAE, 0x82, 0x07, 0x52, 0xB5, 0x02, 0x06, 0x04, 0x02, 0x78, 0xAE, 0xE9, 0x9C, 0x63, 0x8D, 0x3E, + 0xAD, 0x33, 0x9D, 0x67, 0x44, 0xAB, 0xB3, 0x11, 0x6B, 0xF2, 0xF8, 0x4D, 0xBC, 0x62, 0xC9, 0xAF, + 0x10, 0x06, 0xD7, 0x35, 0xED, 0xA8, 0xCE, 0x47, 0x7C, 0xA3, 0xED, 0xA5, 0x61, 0x0D, 0xF8, 0x11, + 0x7B, 0x69, 0x0D, 0xCC, 0x02, 0x2A, 0x9E, 0x21, 0xF0, 0xF8, 0x64, 0x46, 0x89, 0x04, 0xA5, 0x45, + 0x88, 0x21, 0xFA, 0x7C, 0x8E, 0xC1, 0x02, 0x46, 0x1C, 0xC3, 0x60, 0x0A, 0xD0, 0xD4, 0x72, 0xB9, + 0xE3, 0x9C, 0x46, 0x0E, 0x14, 0xB5, 0xC1, 0x9A, 0x62, 0x96, 0xCE, 0x1B, 0xD5, 0x21, 0xDF, 0xCA, + 0x0F, 0x17, 0x31, 0xF7, 0x42, 0x45, 0x9C, 0xC4, 0xA2, 0x49, 0x1C, 0x1B, 0x0E, 0xCF, 0x81, 0x78, + 0xD6, 0x2F, 0x4F, 0x59, 0xC2, 0xF3, 0xE1, 0x19, 0xEF, 0x26, 0x93, 0xE5, 0x42, 0xFC, 0x06, 0x46, + 0x51, 0x69, 0xE8, 0xD1, 0x9C, 0x41, 0x48, 0xA8, 0xCC, 0x83, 0x08, 0x84, 0xA6, 0x46, 0x89, 0x87, + 0xB0, 0x0B, 0x33, 0x90, 0xAA, 0x51, 0xAD, 0x0F, 0x77, 0x22, 0xE0, 0xAB, 0xE1, 0x0A, 0x71, 0x3E, + 0x1F, 0xA2, 0x02, 0x60, 0x22, 0x4D, 0x96, 0x89, 0xF5, 0x66, 0xFF, 0x4F, 0xAA, 0xEE, 0xC7, 0x93, + 0xE5, 0x1C, 0xF8, 0xD8, 0xBC, 0x62, 0xFC, 0x24, 0x64, 0x78, 0xFB, 0xCB, 0xDD, 0x29, 0xF0, 0x4F, + 0xA6, 0x97, 0x5A, 0x33, 0x88, 0x22, 0x96, 0xFC, 0x7E, 0xF1, 0xF6, 0xCD, 0x88, 0xEB, 0x44, 0x4E, + 0x60, 0xF5, 0x0F, 0xE5, 0xD0, 0x49, 0x50, 0x3A, 0xA8, 0x44, 0x53, 0x10, 0xC5, 0xF0, 0x53, 0x3C, + 0xDD, 0xF4, 0x6E, 0x8A, 0x31, 0x95, 0x5E, 0xE9, 0x13, 0x41, 0x8F, 0xAD, 0x0D, 0x69, 0x77, 0x3C, + 0xC9, 0x34, 0xAD, 0x7C, 0x5A, 0xF9, 0x40, 0xE8, 0x53, 0x89, 0xDA, 0x60, 0x08, 0xAB, 0x9B, 0x10, + 0xC6, 0x58, 0x8F, 0x88, 0x82, 0x30, 0xC0, 0x82, 0x50, 0xA8, 0xA4, 0xB1, 0x45, 0x44, 0x04, 0xA2, + 0x19, 0x62, 0x74, 0x0E, 0xF6, 0xA1, 0x76, 0x0C, 0xF9, 0x2E, 0xE6, 0x5F, 0x59, 0x7A, 0xA5, 0xAE, + 0x59, 0x93, 0x64, 0xAB, 0x09, 0xF8, 0xF1, 0x7A, 0x26, 0x77, 0x65, 0xF1, 0xDD, 0x92, 0xEC, 0x26, + 0x26, 0x8E, 0x3A, 0xA7, 0x8B, 0xB6, 0xD6, 0x90, 0x6A, 0xA3, 0x32, 0x89, 0x5E, 0xBC, 0xA8, 0x83, + 0x5C, 0x9A, 0x1A, 0xC5, 0x98, 0x48, 0xC0, 0x10, 0x83, 0xDD, 0x18, 0xA2, 0xD7, 0xF8, 0x28, 0x5B, + 0x4D, 0x50, 0x6A, 0x18, 0x37, 0x1A, 0x9A, 0x6A, 0x58, 0x40, 0x75, 0x01, 0xBD, 0x2E, 0xFB, 0x3F, + 0xC5, 0x9F, 0x9B, 0x58, 0xA0, 0xD1, 0x00, 0x16, 0x91, 0xF2, 0xE2, 0xEC, 0x58, 0x8A, 0x0C, 0xA5, + 0xA0, 0x60, 0x84, 0xCA, 0x96, 0xA7, 0x64, 0x90, 0x76, 0x18, 0x21, 0xE5, 0x58, 0x81, 0xFF, 0x0A, + 0x4B, 0x64, 0xE9, 0x36, 0x58, 0x12, 0xDD, 0xB6, 0xD0, 0x1E, 0xD9, 0x78, 0xDF, 0x11, 0x97, 0x2E, + 0xB5, 0x59, 0x68, 0x43, 0xDE, 0x58, 0xB6, 0xFC, 0xB6, 0x14, 0x1C, 0x66, 0x3D, 0xC2, 0xAA, 0xE0, + 0xE1, 0x44, 0xE5, 0xD6, 0x12, 0xBE, 0xF8, 0x0E, 0xAF, 0x35, 0xE5, 0xD6, 0x86, 0x0B, 0x58, 0xDF, + 0x3B, 0x9B, 0xFC, 0xE0, 0x06, 0x04, 0xF1, 0x68, 0x48, 0xF4, 0xAD, 0x5A, 0x2B, 0xDB, 0xA4, 0x2C, + 0x35, 0x51, 0x1C, 0x0B, 0x0D, 0xB5, 0xA3, 0x8B, 0x57, 0x32, 0x7E, 0xFE, 0x53, 0x06, 0xD0, 0x7F, + 0x66, 0x56, 0x5C, 0xCD, 0xCF, 0x7F, 0x2D, 0x6E, 0x87, 0xF4, 0xDE, 0x41, 0xBC, 0x3F, 0xAC, 0x81, + 0x46, 0x90, 0x09, 0x2B, 0x91, 0x14, 0x39, 0xD6, 0xA8, 0xE5, 0x2F, 0x0C, 0xC5, 0xFB, 0xC2, 0x4A, + 0xAA, 0x56, 0xAA, 0x5E, 0xD7, 0x70, 0xE5, 0x8D, 0xC9, 0xBA, 0x4A, 0x07, 0xFB, 0x9A, 0xF8, 0x83, + 0xC9, 0xE6, 0xD5, 0x57, 0xE0, 0xDB, 0xC6, 0x80, 0x17, 0x2F, 0x4A, 0x23, 0xB6, 0xBB, 0xBF, 0x7D, + 0x43, 0xD1, 0xB0, 0x34, 0xB9, 0x4B, 0x91, 0xB6, 0x61, 0xC5, 0xFD, 0xE2, 0xD5, 0x31, 0xEC, 0x51, + 0xEC, 0x76, 0x43, 0x06, 0xE4, 0x58, 0xEA, 0x2D, 0x75, 0xCD, 0xBC, 0xF4, 0xDD, 0x2A, 0x7A, 0x9F, + 0xC4, 0x0B, 0x96, 0xF0, 0xBB, 0xBA, 0x4A, 0x45, 0x2C, 0xED, 0x65, 0x1D, 0x84, 0xCD, 0x14, 0x53, + 0x76, 0xC0, 0xC3, 0x41, 0x25, 0x78, 0xDA, 0x20, 0x1B, 0x28, 0xE1, 0xCB, 0xC7, 0x4C, 0xA0, 0xCC, + 0x9F, 0x6A, 0x65, 0xEA, 0xFC, 0x29, 0xAB, 0xA2, 0x7F, 0xAA, 0x25, 0x15, 0x7E, 0x05, 0xBE, 0x83, + 0xB3, 0x3A, 0x59, 0xA8, 0x2A, 0xA1, 0xD5, 0x9A, 0x86, 0xF9, 0x09, 0x02, 0x2D, 0x27, 0x19, 0x95, + 0xBD, 0xE3, 0xD2, 0x42, 0x17, 0xF1, 0x0B, 0xA5, 0xBC, 0x50, 0x9B, 0x04, 0x34, 0x26, 0xD9, 0xD4, + 0x98, 0x44, 0x6A, 0xCC, 0x68, 0x53, 0x63, 0x92, 0x6D, 0x8D, 0x79, 0x96, 0xAE, 0x94, 0xF4, 0xA4, + 0x2F, 0x9C, 0x72, 0x1F, 0xDD, 0x2B, 0xB8, 0x66, 0xF0, 0xC2, 0xF2, 0xAB, 0x8D, 0x8E, 0xD6, 0x45, + 0xBD, 0x70, 0x51, 0x93, 0xDA, 0xA4, 0x4E, 0x36, 0x0D, 0xC5, 0x0B, 0xBA, 0x67, 0x54, 0x2E, 0x87, + 0xE6, 0xB7, 0xE9, 0xDB, 0x16, 0xBA, 0x05, 0xFD, 0x8F, 0xF3, 0xCE, 0x85, 0xF0, 0x13, 0x43, 0xAA, + 0xD9, 0xA3, 0x92, 0x8B, 0x69, 0x1E, 0xCB, 0x6C, 0x1F, 0x7D, 0x2D, 0xDB, 0xD9, 0x92, 0x83, 0x2F, + 0x31, 0x2A, 0x29, 0x18, 0x35, 0xCC, 0x38, 0xB5, 0xD1, 0x57, 0x08, 0x4B, 0x2E, 0x25, 0x25, 0x71, + 0x29, 0x0D, 0xDE, 0x2D, 0x8E, 0x7A, 0x59, 0x1A, 0x9F, 0x2E, 0x55, 0xFB, 0xF1, 0x7D, 0x96, 0x60, + 0xED, 0xF5, 0x88, 0x59, 0x0D, 0xB8, 0xEC, 0x12, 0xC3, 0x97, 0xAA, 0x3A, 0x50, 0xB1, 0x28, 0x7C, + 0xC0, 0x95, 0xE2, 0xFB, 0xA9, 0xF2, 0xA4, 0xE8, 0xA5, 0x8A, 0x6F, 0x93, 0x94, 0x8A, 0x95, 0x50, + 0x82, 0x14, 0x5C, 0x7C, 0x9A, 0x96, 0x2B, 0x81, 0xA2, 0xA0, 0xA1, 0x04, 0x1C, 0xD6, 0xC8, 0x4C, + 0x56, 0xAD, 0x55, 0x93, 0x24, 0x51, 0x6A, 0x1B, 0x85, 0xA0, 0xDA, 0xF1, 0x6F, 0xB1, 0xC2, 0x63, + 0x45, 0x1C, 0x61, 0x0C, 0x8A, 0xF3, 0x14, 0xDE, 0xF1, 0x01, 0xEC, 0x8A, 0x32, 0x74, 0xD5, 0xD7, + 0xEF, 0x9D, 0x40, 0x75, 0xFB, 0xCA, 0x7E, 0x64, 0x95, 0xA5, 0x08, 0xC8, 0xA4, 0xBA, 0x63, 0x98, + 0x18, 0x47, 0xD3, 0x20, 0x99, 0xD7, 0xD5, 0x5F, 0xC5, 0x8D, 0xE2, 0x63, 0x17, 0x8E, 0x89, 0xA7, + 0x28, 0xD3, 0x22, 0x2E, 0x02, 0x7D, 0xAC, 0x04, 0x8F, 0x34, 0x08, 0x48, 0xCA, 0xB6, 0x60, 0x66, + 0xD1, 0xE7, 0x21, 0xB0, 0x30, 0x06, 0xE4, 0x38, 0x4E, 0xEE, 0x0E, 0xC0, 0x86, 0x31, 0x55, 0xF0, + 0xA5, 0xD7, 0x3B, 0xB2, 0x5A, 0xB4, 0x48, 0xC0, 0xA5, 0xF3, 0xBA, 0xFA, 0x2A, 0x03, 0x47, 0xB5, + 0x60, 0x88, 0x7F, 0x40, 0x48, 0xA2, 0x65, 0x18, 0x82, 0xDD, 0xDE, 0x00, 0x3D, 0xC9, 0x60, 0xA0, + 0xD0, 0x83, 0xE1, 0x99, 0x43, 0xC0, 0x5A, 0xAC, 0x50, 0x1E, 0x4A, 0x91, 0x03, 0x2E, 0x13, 0x8D, + 0x22, 0xB6, 0x52, 0xFE, 0xEB, 0xED, 0x9B, 0xDF, 0x39, 0x5F, 0x9C, 0xB1, 0xEB, 0x25, 0x04, 0xB0, + 0x7A, 0x30, 0x52, 0x5B, 0x24, 0xCC, 0x2F, 0xC5, 0x6F, 0x0A, 0x46, 0xB0, 0x8D, 0xFD, 0x72, 0xB9, + 0x29, 0x5E, 0x48, 0x93, 0x08, 0x30, 0x06, 0x49, 0x6A, 0x36, 0x9B, 0x58, 0xE2, 0x81, 0x70, 0x13, + 0xC1, 0x89, 0x52, 0x76, 0x83, 0x45, 0x58, 0x33, 0xFB, 0x70, 0x76, 0x5A, 0xE7, 0x9A, 0xE8, 0x14, + 0x35, 0xBE, 0x52, 0x47, 0x39, 0xBA, 0xD3, 0xA3, 0x66, 0x1C, 0xC1, 0xC6, 0xFC, 0x3B, 0x0C, 0x09, + 0xD9, 0x04, 0xC2, 0xB3, 0x2B, 0x36, 0xCA, 0x63, 0x20, 0xED, 0xDE, 0x1D, 0x8D, 0xA2, 0x26, 0x0D, + 0xC0, 0x88, 0x1A, 0x68, 0x52, 0xB7, 0x4D, 0x13, 0xDB, 0x44, 0x08, 0xF9, 0x72, 0x47, 0x74, 0xFE, + 0x1F, 0xE7, 0xEF, 0xFE, 0x00, 0xBF, 0x9B, 0x40, 0x48, 0x8F, 0x53, 0xD3, 0x45, 0x1C, 0xA5, 0xEC, + 0x82, 0xDD, 0x72, 0x4D, 0x1B, 0xB8, 0xA6, 0x55, 0x9A, 0x8C, 0xE7, 0x06, 0x06, 0x75, 0x60, 0x77, + 0x1A, 0x87, 0xAC, 0x19, 0xC6, 0x57, 0xF5, 0xAC, 0x4B, 0xD3, 0x5F, 0x7F, 0x3C, 0xC1, 0x12, 0x20, + 0x10, 0x59, 0x5B, 0x23, 0x96, 0x0B, 0x16, 0xD5, 0xD5, 0xDF, 0x4E, 0x2E, 0x60, 0xCB, 0x3A, 0x44, + 0x56, 0xD0, 0x94, 0x02, 0xC9, 0xEB, 0x1B, 0x2C, 0x10, 0x6F, 0x05, 0x24, 0x8F, 0x0F, 0x6A, 0x45, + 0xF6, 0xA2, 0x42, 0x13, 0x96, 0x05, 0x53, 0x08, 0x13, 0x9D, 0xB5, 0x70, 0x32, 0xDA, 0xFD, 0xDE, + 0xC9, 0xD5, 0x17, 0xB1, 0x5A, 0xB3, 0xF2, 0xCE, 0x35, 0x63, 0xCB, 0x7E, 0xFD, 0x4A, 0xAE, 0x60, + 0x0E, 0x59, 0xEC, 0x66, 0xF1, 0xC6, 0x40, 0xBE, 0x3D, 0x08, 0x99, 0x3A, 0x14, 0x89, 0x0D, 0xCA, + 0xCD, 0xEB, 0x38, 0x99, 0xBF, 0xF2, 0xB8, 0x37, 0xE4, 0x4D, 0x6F, 0xB1, 0xC0, 0xCD, 0x0A, 0xED, + 0x2C, 0xC7, 0xDB, 0x85, 0xAB, 0x8C, 0xC0, 0x55, 0x46, 0x47, 0x19, 0xFE, 0xC3, 0x08, 0x9C, 0xA4, + 0x0C, 0xDD, 0xD9, 0xA7, 0xE8, 0x33, 0x58, 0xE1, 0x72, 0x4E, 0x17, 0x48, 0x2B, 0x7A, 0xAE, 0x16, + 0xC0, 0x43, 0x3D, 0x10, 0xEE, 0x53, 0x2F, 0xD6, 0x2B, 0x5E, 0xDF, 0x00, 0xD5, 0xB7, 0x01, 0x68, + 0xEB, 0x7A, 0xA5, 0xAC, 0xBC, 0x43, 0xDC, 0x35, 0xC9, 0xB9, 0xF7, 0xEF, 0xCE, 0x2F, 0x30, 0xBB, + 0x20, 0x78, 0x2A, 0x71, 0xB0, 0x32, 0xB5, 0x29, 0x2F, 0xE0, 0xC3, 0x4E, 0x6E, 0x60, 0x95, 0x37, + 0x60, 0xB3, 0x18, 0x08, 0x3D, 0x52, 0x4C, 0xBC, 0x7F, 0x51, 0xF5, 0x22, 0x34, 0xD7, 0xEE, 0x81, + 0x5D, 0xD9, 0x5E, 0x41, 0xED, 0x16, 0x4B, 0x2A, 0x6A, 0x66, 0x69, 0x21, 0xCA, 0x92, 0xE7, 0x33, + 0xBF, 0x25, 0xD3, 0xB8, 0x7F, 0x83, 0x8C, 0x6A, 0xBF, 0x8A, 0x09, 0x96, 0x08, 0x36, 0x1E, 0xB0, + 0x8C, 0x0F, 0xF1, 0x5D, 0x41, 0xC6, 0x37, 0x30, 0xA5, 0x7D, 0x8D, 0xBF, 0x00, 0xA9, 0x9B, 0x1A, + 0xE6, 0x60, 0xEB, 0xB5, 0x4E, 0xC1, 0x5E, 0x51, 0xC1, 0xB6, 0x36, 0x36, 0x1E, 0x47, 0xD4, 0x5C, + 0xD2, 0x39, 0xD2, 0xB0, 0x51, 0x75, 0x94, 0xD4, 0x98, 0xFA, 0xF3, 0xD0, 0x7B, 0x86, 0x40, 0xCA, + 0x77, 0x58, 0x87, 0x5D, 0x4B, 0xA1, 0x44, 0x72, 0x39, 0x18, 0x7F, 0xD0, 0x20, 0x54, 0x37, 0x55, + 0x35, 0x0E, 0xDA, 0x40, 0x34, 0x4B, 0xBD, 0x5F, 0x6F, 0xD0, 0x89, 0xD4, 0x9D, 0x6B, 0xEB, 0x42, + 0xE1, 0x21, 0xE0, 0xF9, 0x63, 0x39, 0x1F, 0x83, 0x90, 0x90, 0xC5, 0x2D, 0x34, 0x01, 0xD9, 0x2E, + 0x63, 0x43, 0xB0, 0xE6, 0x52, 0x4C, 0x8E, 0xF8, 0x50, 0x03, 0x63, 0x6B, 0xAA, 0x8D, 0x28, 0x2B, + 0x8A, 0x47, 0x05, 0x2C, 0xD8, 0xDD, 0xFB, 0x5F, 0xD1, 0xD5, 0xE7, 0xD6, 0x03, 0x65, 0x19, 0xD4, + 0x8F, 0xE5, 0x15, 0x74, 0xA4, 0xC0, 0x6B, 0x70, 0x0D, 0x7F, 0x63, 0xE8, 0x0F, 0x1B, 0xAA, 0xA1, + 0x36, 0x4A, 0x18, 0x60, 0xEF, 0xDB, 0x38, 0xE2, 0x33, 0xE8, 0x82, 0x18, 0x6F, 0x67, 0x3F, 0x82, + 0x83, 0x20, 0x65, 0x77, 0xE7, 0xEF, 0x31, 0x64, 0xC8, 0x7B, 0x7B, 0xDF, 0x06, 0xD1, 0x92, 0xB3, + 0xFD, 0xFD, 0xE7, 0x0C, 0xCC, 0xA8, 0x2F, 0xFA, 0x8B, 0x5D, 0xFD, 0x1E, 0xF8, 0xEC, 0xE7, 0x30, + 0x44, 0x85, 0xC9, 0xDF, 0xC4, 0x98, 0xDB, 0x6F, 0x62, 0x5E, 0xBC, 0xC8, 0xDF, 0x13, 0x35, 0x27, + 0x61, 0x8C, 0xB5, 0x98, 0x82, 0xEF, 0xF4, 0x13, 0x89, 0x51, 0xF5, 0xB1, 0xA1, 0xD6, 0x81, 0xCF, + 0x13, 0xE1, 0x8A, 0x98, 0xAF, 0x3D, 0x21, 0x3E, 0x62, 0xFB, 0x87, 0x96, 0x8E, 0xEC, 0x64, 0x12, + 0x29, 0x7F, 0x40, 0x32, 0x52, 0xF1, 0x17, 0x24, 0x07, 0x56, 0xC9, 0x5F, 0x78, 0xEF, 0x9E, 0x58, + 0x50, 0x24, 0x77, 0x2B, 0xF7, 0x19, 0x6D, 0xD4, 0xD7, 0x1E, 0x88, 0xAA, 0x8F, 0xB1, 0x55, 0xF1, + 0x26, 0x0B, 0x7F, 0xEC, 0x01, 0x39, 0xC0, 0xEB, 0x8F, 0x3F, 0x94, 0x0B, 0x5C, 0xAF, 0x3F, 0xBE, + 0xFB, 0x52, 0x3F, 0xE0, 0x1A, 0xF6, 0x39, 0x6B, 0x76, 0x48, 0x01, 0x0F, 0xED, 0x9A, 0x7E, 0xE7, + 0xF8, 0xAC, 0x6D, 0x8B, 0x99, 0x05, 0xEA, 0xA7, 0x51, 0xC0, 0x3F, 0x9C, 0x4A, 0xE1, 0x8E, 0x77, + 0xC5, 0x25, 0xA0, 0xBD, 0xAD, 0x89, 0x88, 0x60, 0x5E, 0xCA, 0x2B, 0xEA, 0x65, 0x25, 0x74, 0x50, + 0x3F, 0x41, 0xF8, 0xD9, 0x33, 0xCD, 0xCF, 0x10, 0x25, 0xED, 0x79, 0xFF, 0xB7, 0xFD, 0x26, 0xB1, + 0xEC, 0xDF, 0x55, 0xC4, 0x43, 0xF9, 0x70, 0x0A, 0x79, 0x41, 0xFC, 0x40, 0xE4, 0x01, 0x16, 0x1E, + 0x82, 0x8F, 0xB8, 0x14, 0x7C, 0x64, 0x9A, 0x29, 0x0A, 0x7E, 0x64, 0x25, 0xE3, 0x2C, 0x5E, 0x90, + 0x96, 0xDF, 0xD4, 0x23, 0x9A, 0x52, 0xD8, 0x95, 0xEC, 0xDD, 0xDF, 0x8F, 0x80, 0x32, 0xCC, 0x2A, + 0x23, 0x53, 0x1D, 0x88, 0x21, 0x85, 0xB4, 0x14, 0x8E, 0x06, 0x8B, 0x98, 0x43, 0x16, 0xA6, 0x2C, + 0xB7, 0x2A, 0x01, 0xF8, 0xD7, 0xE0, 0x28, 0x1B, 0x32, 0x0C, 0x32, 0xFF, 0x1A, 0x8E, 0xA2, 0x4F, + 0xC1, 0xE7, 0x6C, 0x95, 0x01, 0xAC, 0x02, 0x5C, 0x51, 0x6E, 0x58, 0x92, 0xC2, 0x36, 0x20, 0x47, + 0x0D, 0x3F, 0x99, 0x9F, 0x65, 0xB8, 0x08, 0xC1, 0xD3, 0x01, 0x4E, 0x66, 0xA7, 0xE8, 0x2A, 0x02, + 0x74, 0xA3, 0x36, 0xC2, 0x4F, 0xD6, 0x67, 0xC8, 0x6B, 0x1A, 0x9A, 0xAE, 0x56, 0x69, 0xBB, 0x05, + 0x1C, 0x64, 0x9D, 0xDA, 0xAC, 0xAC, 0xED, 0x65, 0x7D, 0x9B, 0x1B, 0x7B, 0x31, 0x28, 0x4E, 0x6E, + 0x1E, 0x70, 0x07, 0x10, 0xAE, 0x6D, 0x82, 0x34, 0x9F, 0x0B, 0x32, 0x0B, 0x79, 0x34, 0xB9, 0xBB, + 0x4D, 0x51, 0xDA, 0xDA, 0xDF, 0xC6, 0x2B, 0xE9, 0xF0, 0x93, 0x9D, 0x75, 0x56, 0x5F, 0x4E, 0x87, + 0x9F, 0x9C, 0xBC, 0x83, 0x2C, 0xDC, 0x39, 0x75, 0xD5, 0xB3, 0x85, 0x66, 0x71, 0xCA, 0x29, 0xDA, + 0xDF, 0xCB, 0x1D, 0x61, 0xF3, 0x4A, 0x94, 0xA4, 0x99, 0x6B, 0x90, 0x47, 0xFE, 0xB2, 0xBE, 0xB7, + 0x12, 0xAE, 0x0B, 0xFB, 0xA0, 0x0D, 0x50, 0x7A, 0xD6, 0x6B, 0x14, 0x1F, 0x85, 0x02, 0xDE, 0xB8, + 0x1A, 0xF0, 0x62, 0xB7, 0x5E, 0x15, 0x44, 0x29, 0xC5, 0xDA, 0x10, 0x22, 0xEC, 0xDC, 0x44, 0x41, + 0xEC, 0x10, 0x97, 0x03, 0x5F, 0x46, 0x61, 0x53, 0xBC, 0x15, 0xF8, 0x56, 0xF6, 0x78, 0xFF, 0x28, + 0xEB, 0x5E, 0xCF, 0x9B, 0x46, 0xBB, 0xF5, 0xF8, 0x25, 0x5A, 0x88, 0x8F, 0x6C, 0x2C, 0xC1, 0xAA, + 0x2B, 0x3C, 0x88, 0xAF, 0x36, 0xCA, 0x74, 0x6E, 0x80, 0xB4, 0x37, 0xAA, 0x3C, 0x69, 0xA8, 0xAD, + 0x15, 0x04, 0x6A, 0x9F, 0x54, 0x2F, 0xF1, 0x97, 0x01, 0x88, 0xE3, 0x67, 0x6D, 0xF0, 0x2C, 0x40, + 0x15, 0x10, 0x5A, 0x73, 0x1C, 0x44, 0x90, 0x02, 0x5F, 0xD0, 0x79, 0x09, 0x2F, 0x49, 0xBC, 0xBB, + 0xF1, 0x72, 0x3A, 0x65, 0x90, 0x89, 0x15, 0x3B, 0x8B, 0x23, 0xA4, 0xD4, 0xA8, 0x1C, 0x23, 0x56, + 0x0C, 0xCF, 0xC7, 0x73, 0x55, 0xDB, 0x75, 0x06, 0xC1, 0x5C, 0x57, 0x80, 0x10, 0x81, 0x2A, 0x50, + 0x76, 0x9E, 0x5B, 0xA8, 0x80, 0xFE, 0x1F, 0x82, 0x2D, 0x1D, 0xEC, 0xB7, 0x6F, 0x29, 0xE3, 0x18, + 0x48, 0xC4, 0x4B, 0x5E, 0x2F, 0x71, 0x46, 0x77, 0x98, 0xA3, 0x55, 0xD7, 0xA2, 0x33, 0x0E, 0x07, + 0x31, 0xC6, 0x24, 0xB6, 0x32, 0x25, 0x3B, 0x25, 0xB1, 0x11, 0x0A, 0xFF, 0x00, 0x41, 0x80, 0x0F, + 0xA9, 0x82, 0x12, 0x44, 0xB0, 0x64, 0x34, 0x61, 0x90, 0x28, 0xFF, 0x8C, 0x74, 0xFA, 0x85, 0xE8, + 0xA4, 0x15, 0x81, 0x31, 0x8E, 0x2A, 0x1B, 0x2B, 0xFB, 0x68, 0x94, 0x9D, 0x54, 0x40, 0x0B, 0x22, + 0x63, 0xFC, 0xD3, 0x57, 0xA0, 0x1B, 0x1C, 0x74, 0x03, 0xDA, 0xB2, 0xD3, 0x1C, 0x1C, 0xED, 0x50, + 0xD5, 0x96, 0xBF, 0x52, 0x30, 0x0E, 0xA1, 0x6E, 0x0D, 0xCD, 0x13, 0xFD, 0xAB, 0x1D, 0xA5, 0xB9, + 0xB2, 0xEF, 0x07, 0x9A, 0xFB, 0xE2, 0x45, 0xEE, 0x71, 0x4F, 0xB9, 0x92, 0x32, 0x36, 0x4F, 0x95, + 0xBB, 0x78, 0xA9, 0xE0, 0x09, 0x34, 0x19, 0x49, 0x28, 0x53, 0xC8, 0xCA, 0x15, 0x2F, 0x8A, 0xC1, + 0xC2, 0x80, 0x65, 0x8D, 0x85, 0x28, 0xEA, 0x38, 0x2C, 0xA1, 0x71, 0x51, 0xBC, 0x52, 0xCA, 0x81, + 0x07, 0x90, 0x5D, 0x3D, 0x39, 0x3B, 0x7B, 0x77, 0x56, 0xA0, 0xBB, 0x7D, 0xA2, 0x84, 0x83, 0x9D, + 0xD8, 0x3C, 0x53, 0xB2, 0xB5, 0x19, 0x1C, 0xD4, 0x50, 0x15, 0xEC, 0x1C, 0xE0, 0x79, 0x0A, 0xEB, + 0xB3, 0xA6, 0x57, 0xC2, 0xD1, 0x8D, 0x68, 0xD4, 0x1B, 0xE3, 0xCB, 0x02, 0x4C, 0x4E, 0x4B, 0x01, + 0x69, 0x65, 0xC2, 0x3D, 0xA6, 0x93, 0x95, 0x55, 0xC1, 0x1C, 0x87, 0x0C, 0x66, 0xC9, 0xB3, 0x6A, + 0xCA, 0x94, 0x02, 0x8F, 0x3A, 0x38, 0xD8, 0xCA, 0xB0, 0x06, 0x58, 0x59, 0xA5, 0xDC, 0x28, 0x37, + 0xA2, 0x6D, 0x1D, 0x8C, 0xD1, 0x06, 0xBB, 0x00, 0x42, 0xC8, 0xA2, 0x83, 0xD1, 0x29, 0x52, 0x8E, + 0xFF, 0x07, 0x59, 0xC3, 0x5E, 0x9B, 0xAA, 0x0D, 0x32, 0x31, 0x00, 0x67, 0x8D, 0x48, 0x95, 0x0D, + 0x5F, 0xF9, 0x24, 0xE0, 0xBD, 0x70, 0xEE, 0xD5, 0xC2, 0xD1, 0xC6, 0x11, 0x47, 0xE5, 0xA5, 0xAA, + 0x3D, 0x5C, 0x1C, 0x58, 0x3D, 0xBF, 0x34, 0x80, 0x07, 0x15, 0x9F, 0x47, 0x96, 0xD2, 0xA2, 0xCF, + 0x98, 0x8E, 0xE7, 0x10, 0x0F, 0xB9, 0xD8, 0x87, 0x66, 0x96, 0x22, 0x8E, 0x67, 0x46, 0xAA, 0x0F, + 0xC4, 0xE7, 0xE2, 0x5C, 0xE5, 0xC1, 0xC2, 0x47, 0x16, 0x68, 0x55, 0x8B, 0x1F, 0xFA, 0x83, 0x75, + 0x0D, 0x15, 0xBC, 0xC9, 0xD3, 0xEA, 0x19, 0xA2, 0x9C, 0x51, 0xCC, 0xD3, 0xD6, 0xE5, 0x03, 0x66, + 0xFA, 0x93, 0x6B, 0x1A, 0x4B, 0x12, 0x2E, 0xD8, 0xDF, 0x8E, 0xB2, 0xC6, 0x3F, 0xA7, 0x9E, 0x41, + 0xB4, 0x7E, 0xB0, 0xA2, 0xB1, 0xC5, 0xFB, 0xC7, 0xD5, 0x32, 0x1E, 0x2A, 0x5F, 0x64, 0xB1, 0xF9, + 0xCE, 0x0A, 0xC6, 0xC3, 0xDA, 0xF3, 0x58, 0x13, 0xB4, 0x85, 0xFD, 0x81, 0xD3, 0xB6, 0x07, 0xC0, + 0x64, 0xE7, 0x6E, 0x9F, 0xA7, 0x3E, 0xCF, 0xD7, 0xF8, 0xCA, 0xCC, 0xEC, 0xAC, 0xF9, 0xE2, 0xF6, + 0x91, 0x36, 0xA2, 0x28, 0xB7, 0xFC, 0x83, 0x2C, 0xCA, 0xF6, 0xF4, 0x12, 0x8E, 0x43, 0x61, 0x42, + 0x1F, 0x57, 0xD7, 0x41, 0xF3, 0xA9, 0x5A, 0xF4, 0x22, 0x54, 0x88, 0xC0, 0x8B, 0x17, 0xAA, 0x5B, + 0x7D, 0x2C, 0xF7, 0x7E, 0xFB, 0x56, 0x75, 0xBB, 0xAA, 0x0D, 0xBE, 0x3D, 0xEB, 0xD4, 0xAA, 0xFE, + 0x6E, 0x82, 0xA1, 0x8E, 0xF0, 0x78, 0x94, 0xAC, 0x29, 0xB8, 0x94, 0x53, 0x1E, 0x2F, 0x14, 0x88, + 0x8C, 0xC8, 0x83, 0xFA, 0x82, 0x67, 0x2F, 0x5C, 0x53, 0xE7, 0x23, 0x08, 0xE0, 0xE8, 0xF7, 0xAA, + 0x40, 0xE4, 0x7A, 0x49, 0xAE, 0xA3, 0xC6, 0xE8, 0x40, 0xFA, 0x54, 0xD5, 0xBA, 0xE8, 0x31, 0x02, + 0x57, 0xC8, 0xAE, 0x6B, 0x19, 0x91, 0xEE, 0x9A, 0x47, 0x11, 0x04, 0x30, 0x13, 0x10, 0xDD, 0x24, + 0x5F, 0x9F, 0x63, 0x60, 0xB9, 0xE1, 0x00, 0x21, 0x26, 0xB4, 0x30, 0x98, 0xA4, 0x2D, 0x57, 0x6B, + 0x66, 0x3B, 0x9A, 0xF6, 0x95, 0xD1, 0x0A, 0x37, 0x8A, 0x59, 0xC9, 0x7E, 0xB5, 0x2C, 0x4E, 0x98, + 0x3F, 0x58, 0x64, 0xA0, 0x13, 0xCA, 0xCF, 0x82, 0x44, 0xEE, 0x63, 0xF8, 0x80, 0x67, 0x0E, 0x0B, + 0xFB, 0x90, 0xA7, 0x65, 0x07, 0x46, 0x2F, 0xB6, 0x46, 0xE3, 0xD9, 0x5F, 0x42, 0xE2, 0xE5, 0x87, + 0xF3, 0x93, 0xB3, 0x72, 0x65, 0x03, 0x6D, 0x2E, 0xA0, 0x10, 0x71, 0xB0, 0xC5, 0x0D, 0xF5, 0xC5, + 0xFB, 0x9F, 0xCF, 0xCF, 0x3F, 0xBE, 0x3B, 0x7B, 0xB5, 0x7B, 0x08, 0xC7, 0x21, 0xE7, 0x1F, 0x7E, + 0x79, 0x7B, 0x7A, 0x31, 0xBA, 0xC3, 0x6A, 0x76, 0xB0, 0xC3, 0x49, 0x0C, 0x83, 0x87, 0xDF, 0xAC, + 0x04, 0x5B, 0x6F, 0x56, 0x7E, 0x80, 0x36, 0x99, 0x2B, 0x52, 0xE6, 0x18, 0x54, 0x33, 0xC7, 0x3C, + 0x35, 0xAC, 0xC4, 0xAE, 0x41, 0x9E, 0x3E, 0x0E, 0xB2, 0x32, 0x0F, 0xB0, 0x3D, 0x28, 0xA7, 0x8E, + 0x11, 0xB9, 0xA6, 0x20, 0x4B, 0x1D, 0x57, 0x41, 0xE4, 0xC7, 0xAB, 0x1D, 0xB6, 0x3B, 0x9B, 0xBF, + 0x1E, 0x1E, 0xB5, 0xE4, 0x31, 0xF2, 0xA3, 0x96, 0xFC, 0xF5, 0x0A, 0xFD, 0xA3, 0xD1, 0xFF, 0x0B, + 0xB4, 0xEA, 0x2E, 0x52, 0x3B, 0x5A, 0x00, 0x00 +}; +#endif //__nofile_h diff --git a/Grbl_Esp32-master/Grbl_Esp32/notifications_service.cpp b/Grbl_Esp32-master/Grbl_Esp32/notifications_service.cpp new file mode 100644 index 0000000..f05308b --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/notifications_service.cpp @@ -0,0 +1,411 @@ +/* + notifications_service.cpp - notifications service functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +//Inspired by following sources +//* Line : +// - https://github.com/TridentTD/TridentTD_LineNotify +// - https://notify-bot.line.me/doc/en/ +//* Pushover: +// - https://github.com/ArduinoHannover/Pushover +// - https://pushover.net/api +//* Email: +// - https://github.com/CosmicBoris/ESP8266SMTP +// - https://www.electronicshub.org/send-an-email-using-esp8266/ + +#include "config.h" +#ifdef ENABLE_NOTIFICATIONS +#include "grbl.h" +#include "commands.h" +#include "notifications_service.h" +#include +#include +#include +#include "wificonfig.h" + +#define PUSHOVERTIMEOUT 5000 +#define PUSHOVERSERVER "api.pushover.net" +#define PUSHOVERPORT 443 + +#define LINETIMEOUT 5000 +#define LINESERVER "notify-api.line.me" +#define LINEPORT 443 + +#define EMAILTIMEOUT 5000 + +NotificationsService notificationsservice; + +bool Wait4Answer(WiFiClientSecure & client, const char * linetrigger, const char * expected_answer, uint32_t timeout) +{ + if(client.connected()) { + String answer; + uint32_t starttimeout = millis(); + while (client.connected() && ((millis() -starttimeout) < timeout)) { + answer = client.readStringUntil('\n'); + log_d("Answer: %s", answer.c_str()); + if ((answer.indexOf(linetrigger) != -1) || (strlen(linetrigger) == 0)) { + break; + } + COMMANDS::wait(10); + } + if (strlen(expected_answer) == 0) { + log_d("Answer ignored as requested"); + return true; + } + if(answer.indexOf(expected_answer) == -1) { + log_d("Did not got answer!"); + return false; + } else { + log_d("Got expected answer"); + return true; + } + } + log_d("Failed to send message"); + return false; +} + +NotificationsService::NotificationsService() +{ + _started = false; + _notificationType = 0; + _token1 = ""; + _token1 = ""; + _settings = ""; +} +NotificationsService::~NotificationsService() +{ + end(); +} + +bool NotificationsService::started() +{ + return _started; +} + +const char * NotificationsService::getTypeString() +{ + switch(_notificationType) { + case ESP_PUSHOVER_NOTIFICATION: + return "Pushover"; + case ESP_EMAIL_NOTIFICATION: + return "Email"; + case ESP_LINE_NOTIFICATION: + return "Line"; + default: + break; + } + return "None"; +} + +bool NotificationsService::sendMSG(const char * title, const char * message) +{ + if (!_started) return false; + if (!((strlen(title) == 0) && (strlen(message) == 0))) { + switch(_notificationType) { + case ESP_PUSHOVER_NOTIFICATION: + return sendPushoverMSG(title,message); + break; + case ESP_EMAIL_NOTIFICATION: + return sendEmailMSG(title,message); + break; + case ESP_LINE_NOTIFICATION : + return sendLineMSG(title,message); + break; + default: + break; + } + } + return false; +} +//Messages are currently limited to 1024 4-byte UTF-8 characters +//but we do not do any check +bool NotificationsService::sendPushoverMSG(const char * title, const char * message) +{ + String data; + String postcmd; + bool res; + WiFiClientSecure Notificationclient; + if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { + log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); + return false; + } + //build data for post + data = "user="; + data += _token1; + data += "&token="; + data += _token2;; + data +="&title="; + data += title; + data += "&message="; + data += message; + data += "&device="; + data += wifi_config.Hostname(); + //build post query + postcmd = "POST /1/messages.json HTTP/1.1\r\nHost: api.pushover.net\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Length: "; + postcmd += data.length(); + postcmd +="\r\n\r\n"; + postcmd +=data; + log_d("Query: %s", postcmd.c_str()); + //send query + Notificationclient.print(postcmd); + res = Wait4Answer(Notificationclient, "{", "\"status\":1", PUSHOVERTIMEOUT); + Notificationclient.stop(); + return res; +} +bool NotificationsService::sendEmailMSG(const char * title, const char * message) +{ + WiFiClientSecure Notificationclient; + log_d("Connect to server"); + if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { + log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); + return false; + } + //Check answer of connection + if(!Wait4Answer(Notificationclient, "220", "220", EMAILTIMEOUT)) { + log_d("Connection failed!"); + return false; + } + //Do HELO + log_d("HELO"); + Notificationclient.print("HELO friend\r\n"); + if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { + log_d("HELO failed!"); + return false; + } + log_d("AUTH LOGIN"); + //Request AUthentication + Notificationclient.print("AUTH LOGIN\r\n"); + if(!Wait4Answer(Notificationclient, "334", "334", EMAILTIMEOUT)) { + log_d("AUTH LOGIN failed!"); + return false; + } + log_d("Send LOGIN"); + //sent Login + Notificationclient.printf("%s\r\n",_token1.c_str()); + if(!Wait4Answer(Notificationclient, "334", "334", EMAILTIMEOUT)) { + log_d("Sent login failed!"); + return false; + } + log_d("Send PASSWORD"); + //Send password + Notificationclient.printf("%s\r\n",_token2.c_str()); + if(!Wait4Answer(Notificationclient, "235", "235", EMAILTIMEOUT)) { + log_d("Sent password failed!"); + return false; + } + log_d("MAIL FROM"); + //Send From + Notificationclient.printf("MAIL FROM: <%s>\r\n",_settings.c_str()); + if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { + log_d("MAIL FROM failed!"); + return false; + } + log_d("RCPT TO"); + //Send To + Notificationclient.printf("RCPT TO: <%s>\r\n",_settings.c_str()); + if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { + log_d("RCPT TO failed!"); + return false; + } + log_d("DATA"); + //Send Data + Notificationclient.print("DATA\r\n"); + if(!Wait4Answer(Notificationclient, "354", "354", EMAILTIMEOUT)) { + log_d("Preparing DATA failed!"); + return false; + } + log_d("Send message"); + //Send message + Notificationclient.printf("From:ESP3D<%s>\r\n",_settings.c_str()); + Notificationclient.printf("To: <%s>\r\n",_settings.c_str()); + Notificationclient.printf("Subject: %s\r\n\r\n",title); + Notificationclient.println(message); + + log_d("Send final dot"); + //Send Final dot + Notificationclient.print(".\r\n"); + if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { + log_d("Sending final dot failed!"); + return false; + } + log_d("QUIT"); + //Quit + Notificationclient.print("QUIT\r\n"); + if(!Wait4Answer(Notificationclient, "221", "221", EMAILTIMEOUT)) { + log_d("QUIT failed!"); + return false; + } + + Notificationclient.stop(); + return true; +} +bool NotificationsService::sendLineMSG(const char * title, const char * message) +{ + String data; + String postcmd; + bool res; + WiFiClientSecure Notificationclient; + (void)title; + if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { + log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); + return false; + } + //build data for post + data = "message="; + data += message; + //build post query + postcmd = "POST /api/notify HTTP/1.1\r\nHost: notify-api.line.me\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Type: application/x-www-form-urlencoded\r\n"; + postcmd +="Authorization: Bearer "; + postcmd += _token1 + "\r\n"; + postcmd += "Content-Length: "; + postcmd += data.length(); + postcmd +="\r\n\r\n"; + postcmd +=data; + log_d("Query: %s", postcmd.c_str()); + //send query + Notificationclient.print(postcmd); + res = Wait4Answer(Notificationclient, "{", "\"status\":200", LINETIMEOUT); + Notificationclient.stop(); + return res; +} +//Email#serveraddress:port +bool NotificationsService::getPortFromSettings() +{ + Preferences prefs; + String defV = DEFAULT_TOKEN; + prefs.begin(NAMESPACE, true); + String tmp = prefs.getString(NOTIFICATION_TS, defV); + prefs.end(); + int pos = tmp.lastIndexOf(':'); + if (pos == -1) { + return false; + } + _port= tmp.substring(pos+1).toInt(); + log_d("port : %d", _port); + if (_port > 0) { + return true; + } else { + return false; + } +} +//Email#serveraddress:port +bool NotificationsService::getServerAddressFromSettings() +{ + Preferences prefs; + String defV = DEFAULT_TOKEN; + prefs.begin(NAMESPACE, true); + String tmp = prefs.getString(NOTIFICATION_TS, defV); + prefs.end(); + int pos1 = tmp.indexOf('#'); + int pos2 = tmp.lastIndexOf(':'); + if ((pos1 == -1) || (pos2 == -1)) { + return false; + } + + //TODO add a check for valid email ? + _serveraddress = tmp.substring(pos1+1, pos2); + log_d("server : %s", _serveraddress.c_str()); + return true; +} +//Email#serveraddress:port +bool NotificationsService::getEmailFromSettings() +{ + Preferences prefs; + String defV = DEFAULT_TOKEN; + prefs.begin(NAMESPACE, true); + String tmp = prefs.getString(NOTIFICATION_TS, defV); + prefs.end(); + int pos = tmp.indexOf('#'); + if (pos == -1) { + return false; + } + _settings = tmp.substring(0, pos); + log_d("email : %s", _settings.c_str()); + //TODO add a check for valid email ? + return true; +} + + +bool NotificationsService::begin() +{ + bool res = true; + end(); + Preferences prefs; + String defV = DEFAULT_TOKEN; + prefs.begin(NAMESPACE, true); + _notificationType = prefs.getChar(NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_TYPE); + switch(_notificationType) { + case 0: //no notification = no error but no start + return true; + case ESP_PUSHOVER_NOTIFICATION: + _token1 = prefs.getString(NOTIFICATION_T1, defV); + _token2 = prefs.getString(NOTIFICATION_T2, defV); + _port = PUSHOVERPORT; + _serveraddress = PUSHOVERSERVER; + break; + case ESP_LINE_NOTIFICATION: + _token1 = prefs.getString(NOTIFICATION_T1, defV); + _port = LINEPORT; + _serveraddress = LINESERVER; + break; + case ESP_EMAIL_NOTIFICATION: + _token1 = base64::encode(prefs.getString(NOTIFICATION_T1, defV)); + _token2 = base64::encode(prefs.getString(NOTIFICATION_T2, defV)); + //log_d("%s",Settings_ESP3D::read_string(ESP_NOTIFICATION_TOKEN1)); + //log_d("%s",Settings_ESP3D::read_string(ESP_NOTIFICATION_TOKEN2)); + if(!getEmailFromSettings() || !getPortFromSettings()|| !getServerAddressFromSettings()) { + prefs.end(); + return false; + } + break; + default: + prefs.end(); + return false; + break; + } + prefs.end(); + if(WiFi.getMode() != WIFI_STA){ + res = false; + } + if (!res) { + end(); + } + _started = res; + return _started; +} +void NotificationsService::end() +{ + if(!_started) { + return; + } + _started = false; + _notificationType = 0; + _token1 = ""; + _token1 = ""; + _settings = ""; + _serveraddress = ""; + _port = 0; +} + +void NotificationsService::handle() +{ + if (_started) { + } +} + +#endif //ENABLE_NOTIFICATIONS diff --git a/Grbl_Esp32-master/Grbl_Esp32/notifications_service.h b/Grbl_Esp32-master/Grbl_Esp32/notifications_service.h new file mode 100644 index 0000000..5f6c6b9 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/notifications_service.h @@ -0,0 +1,57 @@ +/* + notifications_service.h - notifications service functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + + +#ifndef _NOTIFICATIONS_SERVICE_H +#define _NOTIFICATIONS_SERVICE_H + + +class NotificationsService +{ +public: + NotificationsService(); + ~NotificationsService(); + bool begin(); + void end(); + void handle(); + bool sendMSG(const char * title, const char * message); + const char * getTypeString(); + bool started(); +private: + bool _started; + uint8_t _notificationType; + String _token1; + String _token2; + String _settings; + String _serveraddress; + uint16_t _port; + bool sendPushoverMSG(const char * title, const char * message); + bool sendEmailMSG(const char * title, const char * message); + bool sendLineMSG(const char * title, const char * message); + bool getPortFromSettings(); + bool getServerAddressFromSettings(); + bool getEmailFromSettings(); +}; + +extern NotificationsService notificationsservice; + +#endif //_NOTIFICATIONS_SERVICE_H + diff --git a/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.cpp b/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.cpp new file mode 100644 index 0000000..70277d0 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.cpp @@ -0,0 +1,189 @@ +/* + nuts_bolts.c - Shared functions + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + + + + +#define MAX_INT_DIGITS 8 // Maximum number of digits in int32 (and float) + +// Extracts a floating point value from a string. The following code is based loosely on +// the avr-libc strtod() function by Michael Stumpf and Dmitry Xmelkov and many freely +// available conversion method examples, but has been highly optimized for Grbl. For known +// CNC applications, the typical decimal value is expected to be in the range of E0 to E-4. +// Scientific notation is officially not supported by g-code, and the 'E' character may +// be a g-code word on some CNC systems. So, 'E' notation will not be recognized. +// NOTE: Thanks to Radu-Eosif Mihailescu for identifying the issues with using strtod(). +uint8_t read_float(char *line, uint8_t *char_counter, float *float_ptr) +{ + char *ptr = line + *char_counter; + unsigned char c; + + // Grab first character and increment pointer. No spaces assumed in line. + c = *ptr++; + + // Capture initial positive/minus character + bool isnegative = false; + if (c == '-') { + isnegative = true; + c = *ptr++; + } else if (c == '+') { + c = *ptr++; + } + + // Extract number into fast integer. Track decimal in terms of exponent value. + uint32_t intval = 0; + int8_t exp = 0; + uint8_t ndigit = 0; + bool isdecimal = false; + while(1) { + c -= '0'; + if (c <= 9) { + ndigit++; + if (ndigit <= MAX_INT_DIGITS) { + if (isdecimal) { exp--; } + intval = (((intval << 2) + intval) << 1) + c; // intval*10 + c + } else { + if (!(isdecimal)) { exp++; } // Drop overflow digits + } + } else if (c == (('.'-'0') & 0xff) && !(isdecimal)) { + isdecimal = true; + } else { + break; + } + c = *ptr++; + } + + // Return if no digits have been read. + if (!ndigit) { return(false); }; + + // Convert integer into floating point. + float fval; + fval = (float)intval; + + // Apply decimal. Should perform no more than two floating point multiplications for the + // expected range of E0 to E-4. + if (fval != 0) { + while (exp <= -2) { + fval *= 0.01; + exp += 2; + } + if (exp < 0) { + fval *= 0.1; + } else if (exp > 0) { + do { + fval *= 10.0; + } while (--exp > 0); + } + } + + // Assign floating point value with correct sign. + if (isnegative) { + *float_ptr = -fval; + } else { + *float_ptr = fval; + } + + *char_counter = ptr - line - 1; // Set char_counter to next statement + + return(true); +} + +void delay_ms(uint16_t ms) +{ + delay(ms); +} + +// Non-blocking delay function used for general operation and suspend features. +void delay_sec(float seconds, uint8_t mode) +{ + uint16_t i = ceil(1000/DWELL_TIME_STEP*seconds); + while (i-- > 0) { + if (sys.abort) { return; } + if (mode == DELAY_MODE_DWELL) { + protocol_execute_realtime(); + } else { // DELAY_MODE_SYS_SUSPEND + // Execute rt_system() only to avoid nesting suspend loops. + protocol_exec_rt_system(); + if (sys.suspend & SUSPEND_RESTART_RETRACT) { return; } // Bail, if safety door reopens. + } + delay(DWELL_TIME_STEP); // Delay DWELL_TIME_STEP increment + } +} + +// Simple hypotenuse computation function. +float hypot_f(float x, float y) { return(sqrt(x*x + y*y)); } + + +float convert_delta_vector_to_unit_vector(float *vector) +{ + uint8_t idx; + float magnitude = 0.0; + for (idx=0; idx max) + return max; + + return in; +} + +float mapConstrain(float x, float in_min, float in_max, float out_min, float out_max) +{ + x = constrain_float(x, in_min, in_max); + return map_float(x, in_min, in_max, out_min, out_max); +} + + diff --git a/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.h b/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.h new file mode 100644 index 0000000..fe5edbb --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/nuts_bolts.h @@ -0,0 +1,99 @@ +/* + nuts_bolts.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef nuts_bolts_h +#define nuts_bolts_h + +#include "config.h" + +#define false 0 +#define true 1 + +#define SOME_LARGE_VALUE 1.0E+38 + +// Axis array index values. Must start with 0 and be continuous. +// Note: You set the number of axes used by changing N_AXIS. +// Be sure to define pins or servos in cpu_map.h +#define X_AXIS 0 // Axis indexing value. +#define Y_AXIS 1 +#define Z_AXIS 2 +#define A_AXIS 3 +#define B_AXIS 4 +#define C_AXIS 5 + + +// CoreXY motor assignments. DO NOT ALTER. +// NOTE: If the A and B motor axis bindings are changed, this effects the CoreXY equations. +#define A_MOTOR X_AXIS // Must be X_AXIS +#define B_MOTOR Y_AXIS // Must be Y_AXIS + + + +// Conversions +#define MM_PER_INCH (25.40) +#define INCH_PER_MM (0.0393701) +#define TICKS_PER_MICROSECOND (F_STEPPER_TIMER/1000000) // Different from AVR version + +#define DELAY_MODE_DWELL 0 +#define DELAY_MODE_SYS_SUSPEND 1 + +// Useful macros +#define clear_vector(a) memset(a, 0, sizeof(a)) +#define clear_vector_float(a) memset(a, 0.0, sizeof(float)*N_AXIS) +// #define clear_vector_long(a) memset(a, 0.0, sizeof(long)*N_AXIS) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries +#define isequal_position_vector(a,b) !(memcmp(a, b, sizeof(float)*N_AXIS)) + +// Bit field and masking macros +//Arduino.h:104:0: note: this is the location of the previous definition +//#define bit(n) (1 << n) +#define bit_true(x,mask) (x) |= (mask) +#define bit_false(x,mask) (x) &= ~(mask) +#define bit_istrue(x,mask) ((x & mask) != 0) +#define bit_isfalse(x,mask) ((x & mask) == 0) + +// Read a floating point value from a string. Line points to the input buffer, char_counter +// is the indexer pointing to the current character of the line, while float_ptr is +// a pointer to the result variable. Returns true when it succeeds +uint8_t read_float(char *line, uint8_t *char_counter, float *float_ptr); + +// Non-blocking delay function used for general operation and suspend features. +void delay_sec(float seconds, uint8_t mode); + +// Delays variable-defined milliseconds. Compiler compatibility fix for _delay_ms(). +void delay_ms(uint16_t ms); + +// Computes hypotenuse, avoiding avr-gcc's bloated version and the extra error checking. +float hypot_f(float x, float y); + +float convert_delta_vector_to_unit_vector(float *vector); +float limit_value_by_axis_maximum(float *max_value, float *unit_vec); + +float mapConstrain(float x, float in_min, float in_max, float out_min, float out_max); +float map_float(float x, float in_min, float in_max, float out_min, float out_max); +float constrain_float(float in, float min, float max); + +template void swap ( T& a, T& b ) +{ + T c(a); a=b; b=c; +} + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/planner.cpp b/Grbl_Esp32-master/Grbl_Esp32/planner.cpp new file mode 100644 index 0000000..82ebb43 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/planner.cpp @@ -0,0 +1,511 @@ +/* + planner.c - buffers movement commands and manages the acceleration profile plan + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + Copyright (c) 2011 Jens Geisler + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" +#include // PSoc Required for labs + + +static plan_block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions +static uint8_t block_buffer_tail; // Index of the block to process now +static uint8_t block_buffer_head; // Index of the next block to be pushed +static uint8_t next_buffer_head; // Index of the next buffer head +static uint8_t block_buffer_planned; // Index of the optimally planned block + +// Define planner variables +typedef struct { + int32_t position[N_AXIS]; // The planner position of the tool in absolute steps. Kept separate + // from g-code position for movements requiring multiple line motions, + // i.e. arcs, canned cycles, and backlash compensation. + float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment + float previous_nominal_speed; // Nominal speed of previous path line segment +} planner_t; +static planner_t pl; + + +// Returns the index of the next block in the ring buffer. Also called by stepper segment buffer. +uint8_t plan_next_block_index(uint8_t block_index) +{ + block_index++; + if (block_index == BLOCK_BUFFER_SIZE) { block_index = 0; } + return(block_index); +} + + +// Returns the index of the previous block in the ring buffer +static uint8_t plan_prev_block_index(uint8_t block_index) +{ + if (block_index == 0) { block_index = BLOCK_BUFFER_SIZE; } + block_index--; + return(block_index); +} + + +/* PLANNER SPEED DEFINITION + +--------+ <- current->nominal_speed + / \ + current->entry_speed -> + \ + | + <- next->entry_speed (aka exit speed) + +-------------+ + time --> + + Recalculates the motion plan according to the following basic guidelines: + + 1. Go over every feasible block sequentially in reverse order and calculate the junction speeds + (i.e. current->entry_speed) such that: + a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of + neighboring blocks. + b. A block entry speed cannot exceed one reverse-computed from its exit speed (next->entry_speed) + with a maximum allowable deceleration over the block travel distance. + c. The last (or newest appended) block is planned from a complete stop (an exit speed of zero). + 2. Go over every block in chronological (forward) order and dial down junction speed values if + a. The exit speed exceeds the one forward-computed from its entry speed with the maximum allowable + acceleration over the block travel distance. + + When these stages are complete, the planner will have maximized the velocity profiles throughout the all + of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In + other words, for all of the blocks in the planner, the plan is optimal and no further speed improvements + are possible. If a new block is added to the buffer, the plan is recomputed according to the said + guidelines for a new optimal plan. + + To increase computational efficiency of these guidelines, a set of planner block pointers have been + created to indicate stop-compute points for when the planner guidelines cannot logically make any further + changes or improvements to the plan when in normal operation and new blocks are streamed and added to the + planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are + bracketed by junction velocities at their maximums (or by the first planner block as well), no new block + added to the planner buffer will alter the velocity profiles within them. So we no longer have to compute + them. Or, if a set of sequential blocks from the first block in the planner (or a optimal stop-compute + point) are all accelerating, they are all optimal and can not be altered by a new block added to the + planner buffer, as this will only further increase the plan speed to chronological blocks until a maximum + junction velocity is reached. However, if the operational conditions of the plan changes from infrequently + used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is + recomputed as stated in the general guidelines. + + Planner buffer index mapping: + - block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed. + - block_buffer_head: Points to the buffer block after the last block in the buffer. Used to indicate whether + the buffer is full or empty. As described for standard ring buffers, this block is always empty. + - next_buffer_head: Points to next planner buffer block after the buffer head block. When equal to the + buffer tail, this indicates the buffer is full. + - block_buffer_planned: Points to the first buffer block after the last optimally planned block for normal + streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the + planner buffer that don't change with the addition of a new block, as describe above. In addition, + this block can never be less than block_buffer_tail and will always be pushed forward and maintain + this requirement when encountered by the plan_discard_current_block() routine during a cycle. + + NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short + line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't + enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and then + decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this happens and + becomes an annoyance, there are a few simple solutions: (1) Maximize the machine acceleration. The planner + will be able to compute higher velocity profiles within the same combined distance. (2) Maximize line + motion(s) distance per block to a desired tolerance. The more combined distance the planner has to use, + the faster it can go. (3) Maximize the planner buffer size. This also will increase the combined distance + for the planner to compute over. It also increases the number of computations the planner has to perform + to compute an optimal plan, so select carefully. The Arduino 328p memory is already maxed out, but future + ARM versions should have enough memory and speed for look-ahead blocks numbering up to a hundred or more. + +*/ +static void planner_recalculate() +{ + // Initialize block index to the last block in the planner buffer. + uint8_t block_index = plan_prev_block_index(block_buffer_head); + + // Bail. Can't do anything with one only one plan-able block. + if (block_index == block_buffer_planned) { return; } + + // Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last + // block in buffer. Cease planning when the last optimal planned or tail pointer is reached. + // NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan. + float entry_speed_sqr; + plan_block_t *next; + plan_block_t *current = &block_buffer[block_index]; + + // Calculate maximum entry speed for last block in buffer, where the exit speed is always zero. + current->entry_speed_sqr = MIN( current->max_entry_speed_sqr, 2*current->acceleration*current->millimeters); + + block_index = plan_prev_block_index(block_index); + if (block_index == block_buffer_planned) { // Only two plannable blocks in buffer. Reverse pass complete. + // Check if the first block is the tail. If so, notify stepper to update its current parameters. + if (block_index == block_buffer_tail) { st_update_plan_block_parameters(); } + } else { // Three or more plan-able blocks + while (block_index != block_buffer_planned) { + next = current; + current = &block_buffer[block_index]; + block_index = plan_prev_block_index(block_index); + + // Check if next block is the tail block(=planned block). If so, update current stepper parameters. + if (block_index == block_buffer_tail) { st_update_plan_block_parameters(); } + + // Compute maximum entry speed decelerating over the current block from its exit speed. + if (current->entry_speed_sqr != current->max_entry_speed_sqr) { + entry_speed_sqr = next->entry_speed_sqr + 2*current->acceleration*current->millimeters; + if (entry_speed_sqr < current->max_entry_speed_sqr) { + current->entry_speed_sqr = entry_speed_sqr; + } else { + current->entry_speed_sqr = current->max_entry_speed_sqr; + } + } + } + } + + // Forward Pass: Forward plan the acceleration curve from the planned pointer onward. + // Also scans for optimal plan breakpoints and appropriately updates the planned pointer. + next = &block_buffer[block_buffer_planned]; // Begin at buffer planned pointer + block_index = plan_next_block_index(block_buffer_planned); + while (block_index != block_buffer_head) { + current = next; + next = &block_buffer[block_index]; + + // Any acceleration detected in the forward pass automatically moves the optimal planned + // pointer forward, since everything before this is all optimal. In other words, nothing + // can improve the plan from the buffer tail to the planned pointer by logic. + if (current->entry_speed_sqr < next->entry_speed_sqr) { + entry_speed_sqr = current->entry_speed_sqr + 2*current->acceleration*current->millimeters; + // If true, current block is full-acceleration and we can move the planned pointer forward. + if (entry_speed_sqr < next->entry_speed_sqr) { + next->entry_speed_sqr = entry_speed_sqr; // Always <= max_entry_speed_sqr. Backward pass sets this. + block_buffer_planned = block_index; // Set optimal plan pointer. + } + } + + // Any block set at its maximum entry speed also creates an optimal plan up to this + // point in the buffer. When the plan is bracketed by either the beginning of the + // buffer and a maximum entry speed or two maximum entry speeds, every block in between + // cannot logically be further improved. Hence, we don't have to recompute them anymore. + if (next->entry_speed_sqr == next->max_entry_speed_sqr) { block_buffer_planned = block_index; } + block_index = plan_next_block_index( block_index ); + } +} + + +void plan_reset() +{ + memset(&pl, 0, sizeof(planner_t)); // Clear planner struct + plan_reset_buffer(); +} + + +void plan_reset_buffer() +{ + block_buffer_tail = 0; + block_buffer_head = 0; // Empty = tail + next_buffer_head = 1; // plan_next_block_index(block_buffer_head) + block_buffer_planned = 0; // = block_buffer_tail; +} + + +void plan_discard_current_block() +{ + if (block_buffer_head != block_buffer_tail) { // Discard non-empty buffer. + uint8_t block_index = plan_next_block_index( block_buffer_tail ); + // Push block_buffer_planned pointer, if encountered. + if (block_buffer_tail == block_buffer_planned) { block_buffer_planned = block_index; } + block_buffer_tail = block_index; + } +} + + +// Returns address of planner buffer block used by system motions. Called by segment generator. +plan_block_t *plan_get_system_motion_block() +{ + return(&block_buffer[block_buffer_head]); +} + + +// Returns address of first planner block, if available. Called by various main program functions. +plan_block_t *plan_get_current_block() +{ + if (block_buffer_head == block_buffer_tail) { return(NULL); } // Buffer empty + return(&block_buffer[block_buffer_tail]); +} + + +float plan_get_exec_block_exit_speed_sqr() +{ + uint8_t block_index = plan_next_block_index(block_buffer_tail); + if (block_index == block_buffer_head) { return( 0.0 ); } + return( block_buffer[block_index].entry_speed_sqr ); +} + + +// Returns the availability status of the block ring buffer. True, if full. +uint8_t plan_check_full_buffer() +{ + if (block_buffer_tail == next_buffer_head) { return(true); } + return(false); +} + + +// Computes and returns block nominal speed based on running condition and override values. +// NOTE: All system motion commands, such as homing/parking, are not subject to overrides. +float plan_compute_profile_nominal_speed(plan_block_t *block) +{ + float nominal_speed = block->programmed_rate; + if (block->condition & PL_COND_FLAG_RAPID_MOTION) { nominal_speed *= (0.01*sys.r_override); } + else { + if (!(block->condition & PL_COND_FLAG_NO_FEED_OVERRIDE)) { nominal_speed *= (0.01*sys.f_override); } + if (nominal_speed > block->rapid_rate) { nominal_speed = block->rapid_rate; } + } + if (nominal_speed > MINIMUM_FEED_RATE) { return(nominal_speed); } + return(MINIMUM_FEED_RATE); +} + + +// Computes and updates the max entry speed (sqr) of the block, based on the minimum of the junction's +// previous and current nominal speeds and max junction speed. +static void plan_compute_profile_parameters(plan_block_t *block, float nominal_speed, float prev_nominal_speed) +{ + // Compute the junction maximum entry based on the minimum of the junction speed and neighboring nominal speeds. + if (nominal_speed > prev_nominal_speed) { block->max_entry_speed_sqr = prev_nominal_speed*prev_nominal_speed; } + else { block->max_entry_speed_sqr = nominal_speed*nominal_speed; } + if (block->max_entry_speed_sqr > block->max_junction_speed_sqr) { block->max_entry_speed_sqr = block->max_junction_speed_sqr; } +} + + +// Re-calculates buffered motions profile parameters upon a motion-based override change. +void plan_update_velocity_profile_parameters() +{ + uint8_t block_index = block_buffer_tail; + plan_block_t *block; + float nominal_speed; + float prev_nominal_speed = SOME_LARGE_VALUE; // Set high for first block nominal speed calculation. + while (block_index != block_buffer_head) { + block = &block_buffer[block_index]; + nominal_speed = plan_compute_profile_nominal_speed(block); + plan_compute_profile_parameters(block, nominal_speed, prev_nominal_speed); + prev_nominal_speed = nominal_speed; + block_index = plan_next_block_index(block_index); + } + pl.previous_nominal_speed = prev_nominal_speed; // Update prev nominal speed for next incoming block. +} + + +uint8_t plan_buffer_line(float *target, plan_line_data_t *pl_data) +{ + // Prepare and initialize new block. Copy relevant pl_data for block execution. + plan_block_t *block = &block_buffer[block_buffer_head]; + memset(block,0,sizeof(plan_block_t)); // Zero all block values. + block->condition = pl_data->condition; + #ifdef VARIABLE_SPINDLE + block->spindle_speed = pl_data->spindle_speed; + #endif + #ifdef USE_LINE_NUMBERS + block->line_number = pl_data->line_number; + #endif + + // Compute and store initial move distance data. + int32_t target_steps[N_AXIS], position_steps[N_AXIS]; + float unit_vec[N_AXIS], delta_mm; + uint8_t idx; + + // Copy position data based on type of motion being planned. + if (block->condition & PL_COND_FLAG_SYSTEM_MOTION) { + #ifdef COREXY + position_steps[X_AXIS] = system_convert_corexy_to_x_axis_steps(sys_position); + position_steps[Y_AXIS] = system_convert_corexy_to_y_axis_steps(sys_position); + position_steps[Z_AXIS] = sys_position[Z_AXIS]; + #else + memcpy(position_steps, sys_position, sizeof(sys_position)); + #endif + } else { memcpy(position_steps, pl.position, sizeof(pl.position)); } + + #ifdef COREXY + target_steps[A_MOTOR] = lround(target[A_MOTOR]*settings.steps_per_mm[A_MOTOR]); + target_steps[B_MOTOR] = lround(target[B_MOTOR]*settings.steps_per_mm[B_MOTOR]); + block->steps[A_MOTOR] = labs((target_steps[X_AXIS]-position_steps[X_AXIS]) + (target_steps[Y_AXIS]-position_steps[Y_AXIS])); + block->steps[B_MOTOR] = labs((target_steps[X_AXIS]-position_steps[X_AXIS]) - (target_steps[Y_AXIS]-position_steps[Y_AXIS])); + #endif + + for (idx=0; idxsteps[idx] = labs(target_steps[idx]-position_steps[idx]); + } + block->step_event_count = MAX(block->step_event_count, block->steps[idx]); + if (idx == A_MOTOR) { + delta_mm = (target_steps[X_AXIS]-position_steps[X_AXIS] + target_steps[Y_AXIS]-position_steps[Y_AXIS])/settings.steps_per_mm[idx]; + } else if (idx == B_MOTOR) { + delta_mm = (target_steps[X_AXIS]-position_steps[X_AXIS] - target_steps[Y_AXIS]+position_steps[Y_AXIS])/settings.steps_per_mm[idx]; + } else { + delta_mm = (target_steps[idx] - position_steps[idx])/settings.steps_per_mm[idx]; + } + #else + target_steps[idx] = lround(target[idx]*settings.steps_per_mm[idx]); + block->steps[idx] = labs(target_steps[idx]-position_steps[idx]); + block->step_event_count = MAX(block->step_event_count, block->steps[idx]); + delta_mm = (target_steps[idx] - position_steps[idx])/settings.steps_per_mm[idx]; + #endif + unit_vec[idx] = delta_mm; // Store unit vector numerator + + // Set direction bits. Bit enabled always means direction is negative. + if (delta_mm < 0.0 ) { block->direction_bits |= get_direction_pin_mask(idx); } + } + + // Bail if this is a zero-length block. Highly unlikely to occur. + if (block->step_event_count == 0) { return(PLAN_EMPTY_BLOCK); } + + // Calculate the unit vector of the line move and the block maximum feed rate and acceleration scaled + // down such that no individual axes maximum values are exceeded with respect to the line direction. + // NOTE: This calculation assumes all axes are orthogonal (Cartesian) and works with ABC-axes, + // if they are also orthogonal/independent. Operates on the absolute value of the unit vector. + block->millimeters = convert_delta_vector_to_unit_vector(unit_vec); + block->acceleration = limit_value_by_axis_maximum(settings.acceleration, unit_vec); + block->rapid_rate = limit_value_by_axis_maximum(settings.max_rate, unit_vec); + + // Store programmed rate. + if (block->condition & PL_COND_FLAG_RAPID_MOTION) { block->programmed_rate = block->rapid_rate; } + else { + block->programmed_rate = pl_data->feed_rate; + if (block->condition & PL_COND_FLAG_INVERSE_TIME) { block->programmed_rate *= block->millimeters; } + } + + // TODO: Need to check this method handling zero junction speeds when starting from rest. + if ((block_buffer_head == block_buffer_tail) || (block->condition & PL_COND_FLAG_SYSTEM_MOTION)) { + + // Initialize block entry speed as zero. Assume it will be starting from rest. Planner will correct this later. + // If system motion, the system motion block always is assumed to start from rest and end at a complete stop. + block->entry_speed_sqr = 0.0; + block->max_junction_speed_sqr = 0.0; // Starting from rest. Enforce start from zero velocity. + + } else { + // Compute maximum allowable entry speed at junction by centripetal acceleration approximation. + // Let a circle be tangent to both previous and current path line segments, where the junction + // deviation is defined as the distance from the junction to the closest edge of the circle, + // colinear with the circle center. The circular segment joining the two paths represents the + // path of centripetal acceleration. Solve for max velocity based on max acceleration about the + // radius of the circle, defined indirectly by junction deviation. This may be also viewed as + // path width or max_jerk in the previous Grbl version. This approach does not actually deviate + // from path, but used as a robust way to compute cornering speeds, as it takes into account the + // nonlinearities of both the junction angle and junction velocity. + // + // NOTE: If the junction deviation value is finite, Grbl executes the motions in an exact path + // mode (G61). If the junction deviation value is zero, Grbl will execute the motion in an exact + // stop mode (G61.1) manner. In the future, if continuous mode (G64) is desired, the math here + // is exactly the same. Instead of motioning all the way to junction point, the machine will + // just follow the arc circle defined here. The Arduino doesn't have the CPU cycles to perform + // a continuous mode path, but ARM-based microcontrollers most certainly do. + // + // NOTE: The max junction speed is a fixed value, since machine acceleration limits cannot be + // changed dynamically during operation nor can the line move geometry. This must be kept in + // memory in the event of a feedrate override changing the nominal speeds of blocks, which can + // change the overall maximum entry speed conditions of all blocks. + + float junction_unit_vec[N_AXIS]; + float junction_cos_theta = 0.0; + for (idx=0; idx 0.999999) { + // For a 0 degree acute junction, just set minimum junction speed. + block->max_junction_speed_sqr = MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED; + } else { + if (junction_cos_theta < -0.999999) { + // Junction is a straight line or 180 degrees. Junction speed is infinite. + block->max_junction_speed_sqr = SOME_LARGE_VALUE; + } else { + convert_delta_vector_to_unit_vector(junction_unit_vec); + float junction_acceleration = limit_value_by_axis_maximum(settings.acceleration, junction_unit_vec); + float sin_theta_d2 = sqrt(0.5*(1.0-junction_cos_theta)); // Trig half angle identity. Always positive. + block->max_junction_speed_sqr = MAX( MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED, + (junction_acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2) ); + } + } + } + + // Block system motion from updating this data to ensure next g-code motion is computed correctly. + if (!(block->condition & PL_COND_FLAG_SYSTEM_MOTION)) { + float nominal_speed = plan_compute_profile_nominal_speed(block); + plan_compute_profile_parameters(block, nominal_speed, pl.previous_nominal_speed); + pl.previous_nominal_speed = nominal_speed; + + // Update previous path unit_vector and planner position. + memcpy(pl.previous_unit_vec, unit_vec, sizeof(unit_vec)); // pl.previous_unit_vec[] = unit_vec[] + memcpy(pl.position, target_steps, sizeof(target_steps)); // pl.position[] = target_steps[] + + // New block is all set. Update buffer head and next buffer head indices. + block_buffer_head = next_buffer_head; + next_buffer_head = plan_next_block_index(block_buffer_head); + + // Finish up by recalculating the plan with the new block. + planner_recalculate(); + } + return(PLAN_OK); +} + +// Reset the planner position vectors. Called by the system abort/initialization routine. +void plan_sync_position() +{ + // TODO: For motor configurations not in the same coordinate frame as the machine position, + // this function needs to be updated to accomodate the difference. + uint8_t idx; + for (idx=0; idx= block_buffer_tail) { return((BLOCK_BUFFER_SIZE-1)-(block_buffer_head-block_buffer_tail)); } + return((block_buffer_tail-block_buffer_head-1)); +} + + +// Returns the number of active blocks are in the planner buffer. +// NOTE: Deprecated. Not used unless classic status reports are enabled in config.h +uint8_t plan_get_block_buffer_count() +{ + if (block_buffer_head >= block_buffer_tail) { return(block_buffer_head-block_buffer_tail); } + return(BLOCK_BUFFER_SIZE - (block_buffer_tail-block_buffer_head)); +} + + +// Re-initialize buffer plan with a partially completed block, assumed to exist at the buffer tail. +// Called after a steppers have come to a complete stop for a feed hold and the cycle is stopped. +void plan_cycle_reinitialize() +{ + // Re-plan from a complete stop. Reset planner entry speeds and buffer planned pointer. + st_update_plan_block_parameters(); + block_buffer_planned = block_buffer_tail; + planner_recalculate(); +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/planner.h b/Grbl_Esp32-master/Grbl_Esp32/planner.h new file mode 100644 index 0000000..c411f24 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/planner.h @@ -0,0 +1,152 @@ +/* + planner.h - buffers movement commands and manages the acceleration profile plan + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef planner_h +#define planner_h + +// The number of linear motions that can be in the plan at any give time +#ifndef BLOCK_BUFFER_SIZE + #ifdef USE_LINE_NUMBERS + #define BLOCK_BUFFER_SIZE 15 + #else + #define BLOCK_BUFFER_SIZE 16 + #endif +#endif + +// Returned status message from planner. +#define PLAN_OK true +#define PLAN_EMPTY_BLOCK false + +// Define planner data condition flags. Used to denote running conditions of a block. +#define PL_COND_FLAG_RAPID_MOTION bit(0) +#define PL_COND_FLAG_SYSTEM_MOTION bit(1) // Single motion. Circumvents planner state. Used by home/park. +#define PL_COND_FLAG_NO_FEED_OVERRIDE bit(2) // Motion does not honor feed override. +#define PL_COND_FLAG_INVERSE_TIME bit(3) // Interprets feed rate value as inverse time when set. +#define PL_COND_FLAG_SPINDLE_CW bit(4) +#define PL_COND_FLAG_SPINDLE_CCW bit(5) +#define PL_COND_FLAG_COOLANT_FLOOD bit(6) +#define PL_COND_FLAG_COOLANT_MIST bit(7) +#define PL_COND_MOTION_MASK (PL_COND_FLAG_RAPID_MOTION|PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE) +#define PL_COND_ACCESSORY_MASK (PL_COND_FLAG_SPINDLE_CW|PL_COND_FLAG_SPINDLE_CCW|PL_COND_FLAG_COOLANT_FLOOD|PL_COND_FLAG_COOLANT_MIST) + + + +// This struct stores a linear movement of a g-code block motion with its critical "nominal" values +// are as specified in the source g-code. +typedef struct { + // Fields used by the bresenham algorithm for tracing the line + // NOTE: Used by stepper algorithm to execute the block correctly. Do not alter these values. + uint32_t steps[N_AXIS]; // Step count along each axis + uint32_t step_event_count; // The maximum step axis count and number of steps required to complete this block. + uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h) + + // Block condition data to ensure correct execution depending on states and overrides. + uint8_t condition; // Block bitflag variable defining block run conditions. Copied from pl_line_data. + #ifdef USE_LINE_NUMBERS + int32_t line_number; // Block line number for real-time reporting. Copied from pl_line_data. + #endif + + // Fields used by the motion planner to manage acceleration. Some of these values may be updated + // by the stepper module during execution of special motion cases for replanning purposes. + float entry_speed_sqr; // The current planned entry speed at block junction in (mm/min)^2 + float max_entry_speed_sqr; // Maximum allowable entry speed based on the minimum of junction limit and + // neighboring nominal speeds with overrides in (mm/min)^2 + float acceleration; // Axis-limit adjusted line acceleration in (mm/min^2). Does not change. + float millimeters; // The remaining distance for this block to be executed in (mm). + // NOTE: This value may be altered by stepper algorithm during execution. + + // Stored rate limiting data used by planner when changes occur. + float max_junction_speed_sqr; // Junction entry speed limit based on direction vectors in (mm/min)^2 + float rapid_rate; // Axis-limit adjusted maximum rate for this block direction in (mm/min) + float programmed_rate; // Programmed rate of this block (mm/min). + + //#ifdef VARIABLE_SPINDLE + // Stored spindle speed data used by spindle overrides and resuming methods. + float spindle_speed; // Block spindle speed. Copied from pl_line_data. + //#endif +} plan_block_t; + +// Planner data prototype. Must be used when passing new motions to the planner. +typedef struct { + float feed_rate; // Desired feed rate for line motion. Value is ignored, if rapid motion. + float spindle_speed; // Desired spindle speed through line motion. + uint8_t condition; // Bitflag variable to indicate planner conditions. See defines above. + #ifdef USE_LINE_NUMBERS + int32_t line_number; // Desired line number to report when executing. + #endif +} plan_line_data_t; + + + +// Initialize and reset the motion plan subsystem +void plan_reset(); // Reset all +void plan_reset_buffer(); // Reset buffer only. + +// Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position +// in millimeters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed +// rate is taken to mean "frequency" and would complete the operation in 1/feed_rate minutes. +uint8_t plan_buffer_line(float *target, plan_line_data_t *pl_data); + +// Called when the current block is no longer needed. Discards the block and makes the memory +// availible for new blocks. +void plan_discard_current_block(); + +// Gets the planner block for the special system motion cases. (Parking/Homing) +plan_block_t *plan_get_system_motion_block(); + +// Gets the current block. Returns NULL if buffer empty +plan_block_t *plan_get_current_block(); + +// Called periodically by step segment buffer. Mostly used internally by planner. +uint8_t plan_next_block_index(uint8_t block_index); + +// Called by step segment buffer when computing executing block velocity profile. +float plan_get_exec_block_exit_speed_sqr(); + +// Called by main program during planner calculations and step segment buffer during initialization. +float plan_compute_profile_nominal_speed(plan_block_t *block); + +// Re-calculates buffered motions profile parameters upon a motion-based override change. +void plan_update_velocity_profile_parameters(); + +// Reset the planner position vector (in steps) +void plan_sync_position(); + +// Reinitialize plan with a partially completed block +void plan_cycle_reinitialize(); + +// Returns the number of available blocks are in the planner buffer. +uint8_t plan_get_block_buffer_available(); + +// Returns the number of active blocks are in the planner buffer. +// NOTE: Deprecated. Not used unless classic status reports are enabled in config.h +uint8_t plan_get_block_buffer_count(); + +// Returns the status of the block ring buffer. True, if buffer is full. +uint8_t plan_check_full_buffer(); + +void plan_get_planner_mpos(float *target); + + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.cpp b/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.cpp new file mode 100644 index 0000000..b90bc89 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.cpp @@ -0,0 +1,299 @@ +/* + kinematics_polar_coaster.cpp - Implements simple inverse kinematics for Grbl_ESP32 + Part of Grbl_ESP32 + + Copyright (c) 2019 Barton Dring @buildlog + + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + Inverse kinematics determine the joint parameters required to get to a position + in 3D space. Grbl will still work as 3 axes of steps, but these steps could + represent angles, etc instead of linear units. + + Unless forward kinematics are applied to the reporting, Grbl will report raw joint + values instead of the normal Cartesian positions + + How it works... + + If you tell it to go to X10 Y10 Z10 in Cartesian space, for example, the equations + will convert those values to the required joint values. In the case of a polar machine, X represents the radius, + Y represents the polar degrees and Z would be unchanged. + + In most cases, a straight line in Cartesian space could cause a curve in the new system. + To fix this, the line is broken into very small segments and each segment is converted + to the new space. While each segment is also distorted, the amount is so small it cannot be seen. + + This segmentation is how normal Grbl draws arcs. + + Feed Rate + + Feed rate is given in steps/time. Due to the new coordinate units and non linearity issues, the + feed rate may need to be adjusted. The ratio of the step distances in the original coordinate system + determined and applied to the feed rate. + + TODO: + Add y offset, for completeness + Add ZERO_NON_HOMED_AXES option + + +*/ +#include "grbl.h" +#ifdef CPU_MAP_POLAR_COASTER +#ifdef USE_KINEMATICS + +static float last_angle = 0; +static float last_radius = 0; + +// this get called before homing +// return false to complete normal home +// return true to exit normal homing +bool kinematics_pre_homing(uint8_t cycle_mask) { + // cycle mask not used for polar coaster + + // zero the axes that are not homed + sys_position[Y_AXIS] = 0.0f; + sys_position[Z_AXIS] = SERVO_Z_RANGE_MAX * settings.steps_per_mm[Z_AXIS]; // Move pen up. + + return false; // finish normal homing cycle +} + +void kinematics_post_homing() { + // sync the X axis (do not need sync but make it for the fail safe) + last_radius = sys_position[X_AXIS]; + // reset the internal angle value + last_angle = 0; +} + +/* + Apply inverse kinematics for a polar system + + float target: The desired target location in machine space + plan_line_data_t *pl_data: Plan information like feed rate, etc + float *position: The previous "from" location of the move + + Note: It is assumed only the radius axis (X) is homed and only X and Z have offsets + + +*/ +void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *position) +{ + //static float last_angle = 0; + //static float last_radius = 0; + + float dx, dy, dz; // distances in each cartesian axis + float p_dx, p_dy, p_dz; // distances in each polar axis + + float dist, polar_dist; // the distances in both systems...used to determine feed rate + + uint32_t segment_count; // number of segments the move will be broken in to. + float seg_target[N_AXIS]; // The target of the current segment + + float polar[N_AXIS]; // target location in polar coordinates + + float x_offset = gc_state.coord_system[X_AXIS]+gc_state.coord_offset[X_AXIS]; // offset from machine coordinate system + float z_offset = gc_state.coord_system[Z_AXIS]+gc_state.coord_offset[Z_AXIS]; // offset from machine coordinate system + + //grbl_sendf(CLIENT_SERIAL, "Position: %4.2f %4.2f %4.2f \r\n", position[X_AXIS] - x_offset, position[Y_AXIS], position[Z_AXIS]); + //grbl_sendf(CLIENT_SERIAL, "Target: %4.2f %4.2f %4.2f \r\n", target[X_AXIS] - x_offset, target[Y_AXIS], target[Z_AXIS]); + + // calculate cartesian move distance for each axis + dx = target[X_AXIS] - position[X_AXIS]; + dy = target[Y_AXIS] - position[Y_AXIS]; + dz = target[Z_AXIS] - position[Z_AXIS]; + + // calculate the total X,Y axis move distance + // Z axis is the same in both coord systems, so it is ignored + dist = sqrt( (dx * dx) + (dy * dy) + (dz * dz)); + + if (pl_data->condition & PL_COND_FLAG_RAPID_MOTION) { + segment_count = 1; // rapid G0 motion is not used to draw, so skip the segmentation + } else { + segment_count = ceil(dist / SEGMENT_LENGTH); // determine the number of segments we need ... round up so there is at least 1 + } + + dist /= segment_count; // segment distance + + + for(uint32_t segment = 1; segment <= segment_count; segment++) { + // determine this segment's target + seg_target[X_AXIS] = position[X_AXIS] + (dx / float(segment_count) * segment) - x_offset; + seg_target[Y_AXIS] = position[Y_AXIS] + (dy / float(segment_count) * segment); + seg_target[Z_AXIS] = position[Z_AXIS] + (dz / float(segment_count) * segment) - z_offset; + + calc_polar(seg_target, polar, last_angle); + + // begin determining new feed rate + + // calculate move distance for each axis + p_dx = polar[RADIUS_AXIS] - last_radius; + p_dy = polar[POLAR_AXIS] - last_angle; + p_dz = dz; + + polar_dist = sqrt( (p_dx * p_dx) + (p_dy * p_dy) +(p_dz * p_dz)); // calculate the total move distance + + float polar_rate_multiply = 1.0; // fail safe rate + if (polar_dist == 0 || dist == 0) { // prevent 0 feed rate and division by 0 + polar_rate_multiply = 1.0; // default to same feed rate + } else { + // calc a feed rate multiplier + polar_rate_multiply = polar_dist / dist; + if (polar_rate_multiply < 0.5) { // prevent much slower speed + polar_rate_multiply = 0.5; + } + } + + pl_data->feed_rate *= polar_rate_multiply; // apply the distance ratio between coord systems + + // end determining new feed rate + + polar[RADIUS_AXIS] += x_offset; + polar[Z_AXIS] += z_offset; + + last_radius = polar[RADIUS_AXIS]; + last_angle = polar[POLAR_AXIS]; + + mc_line(polar, pl_data); + } + + // TO DO don't need a feedrate for rapids +} + + +/* +Forward kinematics converts position back to the original cartesian system. It is +typically used for reporting + +For example, on a polar machine, you tell it to go to a place like X10Y10. It +converts to a radius and angle using inverse kinematics. The machine posiiton is now +in those units X14.14 (radius) and Y45 (degrees). If you want to report those units as +X10,Y10, you would use forward kinematics + +position = the current machine position +converted = position with forward kinematics applied. + +*/ +void forward_kinematics(float *position) +{ + float original_position[N_AXIS]; // temporary storage of original + float print_position[N_AXIS]; + int32_t current_position[N_AXIS]; // Copy current state of the system position variable + + memcpy(current_position,sys_position,sizeof(sys_position)); + system_convert_array_steps_to_mpos(print_position,current_position); + + original_position[X_AXIS] = print_position[X_AXIS] - gc_state.coord_system[X_AXIS]+gc_state.coord_offset[X_AXIS]; + original_position[Y_AXIS] = print_position[Y_AXIS] - gc_state.coord_system[Y_AXIS]+gc_state.coord_offset[Y_AXIS]; + original_position[Z_AXIS] = print_position[Z_AXIS] - gc_state.coord_system[Z_AXIS]+gc_state.coord_offset[Z_AXIS]; + + position[X_AXIS] = cos(radians(original_position[Y_AXIS])) * original_position[X_AXIS] * -1; + position[Y_AXIS] = sin(radians(original_position[Y_AXIS])) * original_position[X_AXIS]; + position[Z_AXIS] = original_position[Z_AXIS]; // unchanged +} + +// helper functions + +/******************************************* +* Calculate polar values from Cartesian values +* float target_xyz: An array of target axis positions in Cartesian (xyz) space +* float polar: An array to return the polar values +* float last_angle: The polar angle of the "from" point. +* +* Angle calculated is 0 to 360, but you don't want a line to go from 350 to 10. This would +* be a long line backwards. You want it to go from 350 to 370. The same is true going the other way. +* +* This means the angle could accumulate to very high positive or negative values over the coarse of +* a long job. +* +*/ +void calc_polar(float *target_xyz, float *polar, float last_angle) +{ + float delta_ang; // the difference from the last and next angle + + polar[RADIUS_AXIS] = hypot_f(target_xyz[X_AXIS], target_xyz[Y_AXIS]); + + if (polar[RADIUS_AXIS] == 0) { + polar[POLAR_AXIS] = last_angle; // don't care about angle at center + } + else { + polar[POLAR_AXIS] = atan2(target_xyz[Y_AXIS], target_xyz[X_AXIS]) * 180.0 / M_PI; + + // no negative angles...we want the absolute angle not -90, use 270 + polar[POLAR_AXIS] = abs_angle(polar[POLAR_AXIS]); + } + + polar[Z_AXIS] = target_xyz[Z_AXIS]; // Z is unchanged + + delta_ang = polar[POLAR_AXIS] - abs_angle(last_angle); + + // if the delta is above 180 degrees it means we are crossing the 0 degree line + if ( fabs(delta_ang) <= 180.0) { + polar[POLAR_AXIS] = last_angle + delta_ang; + } else { + if (delta_ang > 0.0) { // crossing zero counter clockwise + polar[POLAR_AXIS] = last_angle - (360.0 - delta_ang); + } else { + polar[POLAR_AXIS] = last_angle + delta_ang + 360.0; + } + } +} + +// Return a 0-360 angle ... fix above 360 and below zero +float abs_angle(float ang) { + ang = fmod(ang, 360.0); // 0-360 or 0 to -360 + + if (ang < 0.0) { + ang = 360.0 + ang; + } + return ang; +} + +#endif + +// Polar coaster has macro buttons, this handles those button pushes. +void user_defined_macro(uint8_t index) +{ + //grbl_sendf(CLIENT_SERIAL, "[MSG: Macro #%d]\r\n", index); + switch (index) { + #ifdef MACRO_BUTTON_0_PIN + case CONTROL_PIN_INDEX_MACRO_0: + inputBuffer.push("$H\r"); // home machine + break; + #endif + #ifdef MACRO_BUTTON_1_PIN + case CONTROL_PIN_INDEX_MACRO_1: + inputBuffer.push("[ESP220]/1.nc\r"); // run SD card file 1.nc + break; + #endif + #ifdef MACRO_BUTTON_2_PIN + case CONTROL_PIN_INDEX_MACRO_2: + inputBuffer.push("[ESP220]/2.nc\r"); // run SD card file 2.nc + break; + #endif + #ifdef MACRO_BUTTON_3_PIN + case CONTROL_PIN_INDEX_MACRO_3: + break; + #endif + default: + break; + } +} + +// handle the M30 command +void user_m30() { + inputBuffer.push("$H\r"); +} + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.h b/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.h new file mode 100644 index 0000000..a782617 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/polar_coaster.h @@ -0,0 +1,158 @@ +/* + kinematics_polar_coaster.h - Implements simple kinematics for Grbl_ESP32 + Part of Grbl_ESP32 + + Copyright (c) 2019 Barton Dring @buildlog + + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#define RADIUS_AXIS 0 +#define POLAR_AXIS 1 + +#define SEGMENT_LENGTH 0.5 // segment length in mm +#define USE_KINEMATICS +#define FWD_KINEMATICS_REPORTING // report in cartesian +#define USE_M30 + +// ============= Begin CPU MAP ================ +#define CPU_MAP_NAME "CPU_MAP_POLAR_COASTER" + +#define USE_RMT_STEPS + +#define X_STEP_PIN GPIO_NUM_15 +#define X_RMT_CHANNEL 0 +#define Y_STEP_PIN GPIO_NUM_2 +#define Y_RMT_CHANNEL 1 +#define X_DIRECTION_PIN GPIO_NUM_25 +#define Y_DIRECTION_PIN GPIO_NUM_26 + +#define STEPPERS_DISABLE_PIN GPIO_NUM_17 + +#ifndef USE_SERVO_AXES // maybe set in config.h + #define USE_SERVO_AXES +#endif + +#define SERVO_Z_PIN GPIO_NUM_16 +#define SERVO_Z_CHANNEL_NUM 5 +#define SERVO_Z_RANGE_MIN 0.0 +#define SERVO_Z_RANGE_MAX 5.0 +#define SERVO_Z_HOMING_TYPE SERVO_HOMING_TARGET // during homing it will instantly move to a target value +#define SERVO_Z_HOME_POS SERVO_Z_RANGE_MAX // move to max during homing +#define SERVO_Z_MPOS false // will not use mpos, uses work coordinates + +#define X_LIMIT_PIN GPIO_NUM_4 +#define LIMIT_MASK B1 + +#ifdef IGNORE_CONTROL_PINS // maybe set in config.h + #undef IGNORE_CONTROL_PINS +#endif + +#ifndef ENABLE_CONTROL_SW_DEBOUNCE + #define ENABLE_CONTROL_SW_DEBOUNCE +#endif + +#ifdef CONTROL_SW_DEBOUNCE_PERIOD + #undef CONTROL_SW_DEBOUNCE_PERIOD +#endif +#define CONTROL_SW_DEBOUNCE_PERIOD 100 // really long debounce + +#ifdef INVERT_CONTROL_PIN_MASK + #undef INVERT_CONTROL_PIN_MASK +#endif +#define INVERT_CONTROL_PIN_MASK B11111111 + +#define MACRO_BUTTON_0_PIN GPIO_NUM_13 +#define MACRO_BUTTON_1_PIN GPIO_NUM_12 +#define MACRO_BUTTON_2_PIN GPIO_NUM_14 + +// redefine some stuff from config.h +#ifdef HOMING_CYCLE_0 + #undef HOMING_CYCLE_0 +#endif +#define HOMING_CYCLE_0 (1<. +*/ + +#include "grbl.h" + + + + +// void printIntegerInBase(unsigned long n, unsigned long base) +// { +// unsigned char buf[8 * sizeof(long)]; // Assumes 8-bit chars. +// unsigned long i = 0; +// +// if (n == 0) { +// serial_write('0'); +// return; +// } +// +// while (n > 0) { +// buf[i++] = n % base; +// n /= base; +// } +// +// for (; i > 0; i--) +// serial_write(buf[i - 1] < 10 ? +// '0' + buf[i - 1] : +// 'A' + buf[i - 1] - 10); +// } + + +// Prints an uint8 variable in base 10. +void print_uint8_base10(uint8_t n) +{ + uint8_t digit_a = 0; + uint8_t digit_b = 0; + if (n >= 100) { // 100-255 + digit_a = '0' + n % 10; + n /= 10; + } + if (n >= 10) { // 10-99 + digit_b = '0' + n % 10; + n /= 10; + } + serial_write('0' + n); + if (digit_b) { serial_write(digit_b); } + if (digit_a) { serial_write(digit_a); } +} + + +// Prints an uint8 variable in base 2 with desired number of desired digits. +void print_uint8_base2_ndigit(uint8_t n, uint8_t digits) { + unsigned char buf[digits]; + uint8_t i = 0; + + for (; i < digits; i++) { + buf[i] = n % 2 ; + n /= 2; + } + + for (; i > 0; i--) + Serial.print('0' + buf[i - 1]); +} + + +void print_uint32_base10(uint32_t n) +{ + if (n == 0) { + Serial.print('0'); + return; + } + + unsigned char buf[10]; + uint8_t i = 0; + + while (n > 0) { + buf[i++] = n % 10; + n /= 10; + } + + for (; i > 0; i--) + Serial.print('0' + buf[i-1]); +} + + +void printInteger(long n) +{ + if (n < 0) { + Serial.print('-'); + print_uint32_base10(-n); + } else { + print_uint32_base10(n); + } +} + + +// Convert float to string by immediately converting to a long integer, which contains +// more digits than a float. Number of decimal places, which are tracked by a counter, +// may be set by the user. The integer is then efficiently converted to a string. +// NOTE: AVR '%' and '/' integer operations are very efficient. Bitshifting speed-up +// techniques are actually just slightly slower. Found this out the hard way. +void printFloat(float n, uint8_t decimal_places) +{ + Serial.print(n, decimal_places); +} + + +// Floating value printing handlers for special variables types used in Grbl and are defined +// in the config.h. +// - CoordValue: Handles all position or coordinate values in inches or mm reporting. +// - RateValue: Handles feed rate and current velocity in inches or mm reporting. +void printFloat_CoordValue(float n) { + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { + printFloat(n*INCH_PER_MM,N_DECIMAL_COORDVALUE_INCH); + } else { + printFloat(n,N_DECIMAL_COORDVALUE_MM); + } +} + +// Debug tool to print free memory in bytes at the called point. +// NOTE: Keep commented unless using. Part of this function always gets compiled in. +// void printFreeMemory() +// { +// extern int __heap_start, *__brkval; +// uint16_t free; // Up to 64k values. +// free = (int) &free - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +// printInteger((int32_t)free); +// printString(" "); +// } diff --git a/Grbl_Esp32-master/Grbl_Esp32/print.h b/Grbl_Esp32-master/Grbl_Esp32/print.h new file mode 100644 index 0000000..9ee2559 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/print.h @@ -0,0 +1,54 @@ +/* + print.h - Functions for formatting output strings + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef print_h +#define print_h + + +void printString(const char *s); + +void printPgmString(const char *s); + +void printInteger(long n); + +void print_uint32_base10(uint32_t n); + +// Prints an uint8 variable in base 10. +void print_uint8_base10(uint8_t n); + +// Prints an uint8 variable in base 2 with desired number of desired digits. +void print_uint8_base2_ndigit(uint8_t n, uint8_t digits); + +void printFloat(float n, uint8_t decimal_places); + +// Floating value printing handlers for special variables types used in Grbl. +// - CoordValue: Handles all position or coordinate values in inches or mm reporting. +// - RateValue: Handles feed rate and current velocity in inches or mm reporting. +void printFloat_CoordValue(float n); +void printFloat_RateValue(float n); + +// Debug tool to print free memory in bytes at the called point. Not used otherwise. +void printFreeMemory(); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/probe.cpp b/Grbl_Esp32-master/Grbl_Esp32/probe.cpp new file mode 100644 index 0000000..4c5093f --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/probe.cpp @@ -0,0 +1,78 @@ +/* + probe.c - code pertaining to probing methods + Part of Grbl + + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + + +// Inverts the probe pin state depending on user settings and probing cycle mode. +uint8_t probe_invert_mask; + + +// Probe pin initialization routine. +void probe_init() +{ +#ifdef PROBE_PIN + #ifdef DISABLE_PROBE_PIN_PULL_UP + pinMode(PROBE_PIN, INPUT); + #else + pinMode(PROBE_PIN, INPUT_PULLUP); // Enable internal pull-up resistors. Normal high operation. + #endif + + + probe_configure_invert_mask(false); // Initialize invert mask. +#endif +} + + +// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to +// appropriately set the pin logic according to setting for normal-high/normal-low operation +// and the probing cycle modes for toward-workpiece/away-from-workpiece. +void probe_configure_invert_mask(uint8_t is_probe_away) +{ + probe_invert_mask = 0; // Initialize as zero. + if (bit_isfalse(settings.flags,BITFLAG_INVERT_PROBE_PIN)) { probe_invert_mask ^= PROBE_MASK; } + if (is_probe_away) { probe_invert_mask ^= PROBE_MASK; } +} + +// Returns the probe pin state. Triggered = true. Called by gcode parser and probe state monitor. +uint8_t probe_get_state() +{ +#ifdef PROBE_PIN + return((digitalRead(PROBE_PIN)) ^ probe_invert_mask); +#else + return false; +#endif +} + + +// Monitors probe pin state and records the system position when detected. Called by the +// stepper ISR per ISR tick. +// NOTE: This function must be extremely efficient as to not bog down the stepper ISR. +void probe_state_monitor() +{ + if (probe_get_state()) { + sys_probe_state = PROBE_OFF; + memcpy(sys_probe_position, sys_position, sizeof(sys_position)); + bit_true(sys_rt_exec_state, EXEC_MOTION_CANCEL); + } +} diff --git a/Grbl_Esp32-master/Grbl_Esp32/probe.h b/Grbl_Esp32-master/Grbl_Esp32/probe.h new file mode 100644 index 0000000..b5a3665 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/probe.h @@ -0,0 +1,46 @@ +/* + probe.h - code pertaining to probing methods + Part of Grbl + + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef probe_h +#define probe_h + +// Values that define the probing state machine. +#define PROBE_OFF 0 // Probing disabled or not in use. (Must be zero.) +#define PROBE_ACTIVE 1 // Actively watching the input pin. + +// Probe pin initialization routine. +void probe_init(); + +// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to +// appropriately set the pin logic according to setting for normal-high/normal-low operation +// and the probing cycle modes for toward-workpiece/away-from-workpiece. +void probe_configure_invert_mask(uint8_t is_probe_away); + +// Returns probe pin state. Triggered = true. Called by gcode parser and probe state monitor. +uint8_t probe_get_state(); + +// Monitors probe pin state and records the system position when detected. Called by the +// stepper ISR per ISR tick. +void probe_state_monitor(); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/protocol.cpp b/Grbl_Esp32-master/Grbl_Esp32/protocol.cpp new file mode 100644 index 0000000..d8037b2 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/protocol.cpp @@ -0,0 +1,849 @@ +/* + protocol.c - controls Grbl execution protocol and procedures + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" +#include "config.h" +#include "commands.h" +#include "espresponse.h" + +// Define line flags. Includes comment type tracking and line overflow detection. +#define LINE_FLAG_OVERFLOW bit(0) +#define LINE_FLAG_COMMENT_PARENTHESES bit(1) +#define LINE_FLAG_COMMENT_SEMICOLON bit(2) +#define LINE_FLAG_BRACKET bit(3) // square bracket for WebUI commands + + +static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. +static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. + +static void protocol_exec_rt_suspend(); + + +/* + GRBL PRIMARY LOOP: +*/ +void protocol_main_loop() +{ + //uint8_t client = CLIENT_SERIAL; // default client + + // Perform some machine checks to make sure everything is good to go. + #ifdef CHECK_LIMITS_AT_INIT + if (bit_istrue(settings.flags, BITFLAG_HARD_LIMIT_ENABLE)) { + if (limits_get_state()) { + sys.state = STATE_ALARM; // Ensure alarm state is active. + report_feedback_message(MESSAGE_CHECK_LIMITS); + } + } + #endif + // Check for and report alarm state after a reset, error, or an initial power up. + // NOTE: Sleep mode disables the stepper drivers and position can't be guaranteed. + // Re-initialize the sleep state as an ALARM mode to ensure user homes or acknowledges. + if (sys.state & (STATE_ALARM | STATE_SLEEP)) { + report_feedback_message(MESSAGE_ALARM_LOCK); + sys.state = STATE_ALARM; // Ensure alarm state is set. + } else { + // Check if the safety door is open. + sys.state = STATE_IDLE; + if (system_check_safety_door_ajar()) { + bit_true(sys_rt_exec_state, EXEC_SAFETY_DOOR); + protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state. + } + // All systems go! + system_execute_startup(line); // Execute startup script. + } + + // --------------------------------------------------------------------------------- + // Primary loop! Upon a system abort, this exits back to main() to reset the system. + // This is also where Grbl idles while waiting for something to do. + // --------------------------------------------------------------------------------- + + uint8_t line_flags = 0; + uint8_t char_counter = 0; + uint8_t comment_char_counter = 0; + uint8_t c; + + for (;;) { + + // serialCheck(); // un comment this if you do this here rather than in a separate task + + #ifdef ENABLE_SD_CARD + if (SD_ready_next) { + char fileLine[255]; + if (readFileLine(fileLine)) { + SD_ready_next = false; + report_status_message(gc_execute_line(fileLine, SD_client), SD_client); + } + else { + char temp[50]; + sd_get_current_filename(temp); + grbl_notifyf("SD print done", "%s print is successful", temp); + closeFile(); // close file and clear SD ready/running flags + } + } + #endif + + + // Process one line of incoming serial data, as the data becomes available. Performs an + // initial filtering by removing spaces and comments and capitalizing all letters. + + uint8_t client = CLIENT_SERIAL; + for (client = 0; client <= CLIENT_COUNT; client++) + { + while((c = serial_read(client)) != SERIAL_NO_DATA) { + if ((c == '\n') || (c == '\r')) { // End of line reached + + protocol_execute_realtime(); // Runtime command check point. + if (sys.abort) { return; } // Bail to calling function upon system abort + + line[char_counter] = 0; // Set string termination character. + #ifdef REPORT_ECHO_LINE_RECEIVED + report_echo_line_received(line, client); + #endif + + // Direct and execute one line of formatted input, and report status of execution. + if (line_flags & LINE_FLAG_OVERFLOW) { + // Report line overflow error. + report_status_message(STATUS_OVERFLOW, client); + } else if (line[0] == 0) { + // Empty or comment line. For syncing purposes. + report_status_message(STATUS_OK, client); + } else if (line[0] == '$') { + // Grbl '$' system command + report_status_message(system_execute_line(line, client), client); + } else if (line[0] == '[') { + int cmd = 0; + String cmd_params; + if (COMMANDS::check_command (line, &cmd, cmd_params)) { + ESPResponseStream espresponse(client, true); + if (!COMMANDS::execute_internal_command (cmd, cmd_params, LEVEL_GUEST, &espresponse)) { + report_status_message(STATUS_GCODE_UNSUPPORTED_COMMAND, CLIENT_ALL); + } + } else grbl_sendf(client, "[MSG: Unknow Command...%s]\r\n", line); + } else if (sys.state & (STATE_ALARM | STATE_JOG)) { + // Everything else is gcode. Block if in alarm or jog mode. + report_status_message(STATUS_SYSTEM_GC_LOCK, client); + } else { + // Parse and execute g-code block. + report_status_message(gc_execute_line(line, client), client); + } + + // Reset tracking data for next line. + line_flags = 0; + char_counter = 0; + comment_char_counter = 0; + } else { + + if (line_flags) { + if (line_flags & LINE_FLAG_BRACKET) { // in bracket mode all characters are accepted + line[char_counter++] = c; + } + // Throw away all (except EOL) comment characters and overflow characters. + if (c == ')') { + // End of '()' comment. Resume line allowed. + if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { + line_flags &= ~(LINE_FLAG_COMMENT_PARENTHESES); + comment[comment_char_counter] = 0; // null terminate + report_gcode_comment(comment); + } + } + if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { // capture all characters into a comment buffer + comment[comment_char_counter++] = c; + } + } else { + if (c <= ' ') { + // Throw away whitepace and control characters + } + /* + else if (c == '/') { + // Block delete NOT SUPPORTED. Ignore character. + // NOTE: If supported, would simply need to check the system if block delete is enabled. + } + */ + else if (c == '(') { + // Enable comments flag and ignore all characters until ')' or EOL. + // NOTE: This doesn't follow the NIST definition exactly, but is good enough for now. + // In the future, we could simply remove the items within the comments, but retain the + // comment control characters, so that the g-code parser can error-check it. + line_flags |= LINE_FLAG_COMMENT_PARENTHESES; + comment_char_counter = 0; + } else if (c == ';') { + // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. + line_flags |= LINE_FLAG_COMMENT_SEMICOLON; + } else if (c == '[') { + // For ESP3D bracket commands like [ESP100]pwd= + // prevents spaces being striped and converting to uppercase + line_flags |= LINE_FLAG_BRACKET; + line[char_counter++] = c; // capture this character + + // TODO: Install '%' feature + } else if (c == '%') { + // Program start-end percent sign NOT SUPPORTED. + // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, + // where, during a program, the system auto-cycle start will continue to execute + // everything until the next '%' sign. This will help fix resuming issues with certain + // functions that empty the planner buffer to execute its task on-time. + } else if (char_counter >= (LINE_BUFFER_SIZE-1)) { + // Detect line buffer overflow and set flag. + line_flags |= LINE_FLAG_OVERFLOW; + } else if (c >= 'a' && c <= 'z') { // Upcase lowercase + line[char_counter++] = c-'a'+'A'; + } else { + line[char_counter++] = c; + } + } + + } + } // while serial read + } // for clients + + + + // If there are no more characters in the serial read buffer to be processed and executed, + // this indicates that g-code streaming has either filled the planner buffer or has + // completed. In either case, auto-cycle start, if enabled, any queued moves. + protocol_auto_cycle_start(); + + protocol_execute_realtime(); // Runtime command check point. + if (sys.abort) { return; } // Bail to main() program loop to reset system. + + // check to see if we should disable the stepper drivers ... esp32 work around for disable in main loop. + if (stepper_idle) + { + if (esp_timer_get_time() > stepper_idle_counter) + { + set_stepper_disable(true); + } + } + } + + return; /* Never reached */ +} + + +// Block until all buffered steps are executed or in a cycle state. Works with feed hold +// during a synchronize call, if it should happen. Also, waits for clean cycle end. +void protocol_buffer_synchronize() +{ + // If system is queued, ensure cycle resumes if the auto start flag is present. + protocol_auto_cycle_start(); + do { + protocol_execute_realtime(); // Check and execute run-time commands + if (sys.abort) { return; } // Check for system abort + } while (plan_get_current_block() || (sys.state == STATE_CYCLE)); +} + + +// Auto-cycle start triggers when there is a motion ready to execute and if the main program is not +// actively parsing commands. +// NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes +// when one of these conditions exist respectively: There are no more blocks sent (i.e. streaming +// is finished, single commands), a command that needs to wait for the motions in the buffer to +// execute calls a buffer sync, or the planner buffer is full and ready to go. +void protocol_auto_cycle_start() +{ + if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer. + system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them! + } +} + + +// This function is the general interface to Grbl's real-time command execution system. It is called +// from various check points in the main program, primarily where there may be a while loop waiting +// for a buffer to clear space or any point where the execution time from the last check point may +// be more than a fraction of a second. This is a way to execute realtime commands asynchronously +// (aka multitasking) with grbl's g-code parsing and planning functions. This function also serves +// as an interface for the interrupts to set the system realtime flags, where only the main program +// handles them, removing the need to define more computationally-expensive volatile variables. This +// also provides a controlled way to execute certain tasks without having two or more instances of +// the same task, such as the planner recalculating the buffer upon a feedhold or overrides. +// NOTE: The sys_rt_exec_state variable flags are set by any process, step or serial interrupts, pinouts, +// limit switches, or the main program. +void protocol_execute_realtime() +{ + protocol_exec_rt_system(); + if (sys.suspend) { protocol_exec_rt_suspend(); } +} + + +// Executes run-time commands, when required. This function primarily operates as Grbl's state +// machine and controls the various real-time features Grbl has to offer. +// NOTE: Do not alter this unless you know exactly what you are doing! +void protocol_exec_rt_system() +{ + uint8_t rt_exec; // Temp variable to avoid calling volatile multiple times. + rt_exec = sys_rt_exec_alarm; // Copy volatile sys_rt_exec_alarm. + if (rt_exec) { // Enter only if any bit flag is true + // System alarm. Everything has shutdown by something that has gone severely wrong. Report + // the source of the error to the user. If critical, Grbl disables by entering an infinite + // loop until system reset/abort. + sys.state = STATE_ALARM; // Set system alarm state + report_alarm_message(rt_exec); + // Halt everything upon a critical event flag. Currently hard and soft limits flag this. + if ((rt_exec == EXEC_ALARM_HARD_LIMIT) || (rt_exec == EXEC_ALARM_SOFT_LIMIT)) { + report_feedback_message(MESSAGE_CRITICAL_EVENT); + system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset + do { + // Block everything, except reset and status reports, until user issues reset or power + // cycles. Hard limits typically occur while unattended or not paying attention. Gives + // the user and a GUI time to do what is needed before resetting, like killing the + // incoming stream. The same could be said about soft limits. While the position is not + // lost, continued streaming could cause a serious crash if by chance it gets executed. + } while (bit_isfalse(sys_rt_exec_state,EXEC_RESET)); + } + system_clear_exec_alarm(); // Clear alarm + } + + rt_exec = sys_rt_exec_state; // Copy volatile sys_rt_exec_state. + if (rt_exec) { + + // Execute system abort. + if (rt_exec & EXEC_RESET) { + sys.abort = true; // Only place this is set true. + return; // Nothing else to do but exit. + } + + // Execute and serial print status + if (rt_exec & EXEC_STATUS_REPORT) { + report_realtime_status(CLIENT_ALL); + system_clear_exec_state_flag(EXEC_STATUS_REPORT); + } + + // NOTE: Once hold is initiated, the system immediately enters a suspend state to block all + // main program processes until either reset or resumed. This ensures a hold completes safely. + if (rt_exec & (EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR | EXEC_SLEEP)) { + + // State check for allowable states for hold methods. + if (!(sys.state & (STATE_ALARM | STATE_CHECK_MODE))) { + + // If in CYCLE or JOG states, immediately initiate a motion HOLD. + if (sys.state & (STATE_CYCLE | STATE_JOG)) { + if (!(sys.suspend & (SUSPEND_MOTION_CANCEL | SUSPEND_JOG_CANCEL))) { // Block, if already holding. + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + sys.step_control = STEP_CONTROL_EXECUTE_HOLD; // Initiate suspend state with active flag. + if (sys.state == STATE_JOG) { // Jog cancelled upon any hold event, except for sleeping. + if (!(rt_exec & EXEC_SLEEP)) { sys.suspend |= SUSPEND_JOG_CANCEL; } + } + } + } + // If IDLE, Grbl is not in motion. Simply indicate suspend state and hold is complete. + if (sys.state == STATE_IDLE) { sys.suspend = SUSPEND_HOLD_COMPLETE; } + + // Execute and flag a motion cancel with deceleration and return to idle. Used primarily by probing cycle + // to halt and cancel the remainder of the motion. + if (rt_exec & EXEC_MOTION_CANCEL) { + // MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand + // to hold the CYCLE. Motion cancel is valid for a single planner block motion only, while jog cancel + // will handle and clear multiple planner block motions. + if (!(sys.state & STATE_JOG)) { sys.suspend |= SUSPEND_MOTION_CANCEL; } // NOTE: State is STATE_CYCLE. + } + + // Execute a feed hold with deceleration, if required. Then, suspend system. + if (rt_exec & EXEC_FEED_HOLD) { + // Block SAFETY_DOOR, JOG, and SLEEP states from changing to HOLD state. + if (!(sys.state & (STATE_SAFETY_DOOR | STATE_JOG | STATE_SLEEP))) { sys.state = STATE_HOLD; } + } + + // Execute a safety door stop with a feed hold and disable spindle/coolant. + // NOTE: Safety door differs from feed holds by stopping everything no matter state, disables powered + // devices (spindle/coolant), and blocks resuming until switch is re-engaged. + if (rt_exec & EXEC_SAFETY_DOOR) { + report_feedback_message(MESSAGE_SAFETY_DOOR_AJAR); + // If jogging, block safety door methods until jog cancel is complete. Just flag that it happened. + if (!(sys.suspend & SUSPEND_JOG_CANCEL)) { + // Check if the safety re-opened during a restore parking motion only. Ignore if + // already retracting, parked or in sleep state. + if (sys.state == STATE_SAFETY_DOOR) { + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { // Actively restoring + #ifdef PARKING_ENABLE + // Set hold and reset appropriate control flags to restart parking sequence. + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + sys.step_control = (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION); + sys.suspend &= ~(SUSPEND_HOLD_COMPLETE); + } // else NO_MOTION is active. + #endif + sys.suspend &= ~(SUSPEND_RETRACT_COMPLETE | SUSPEND_INITIATE_RESTORE | SUSPEND_RESTORE_COMPLETE); + sys.suspend |= SUSPEND_RESTART_RETRACT; + } + } + if (sys.state != STATE_SLEEP) { sys.state = STATE_SAFETY_DOOR; } + } + // NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions + // are executed if the door switch closes and the state returns to HOLD. + sys.suspend |= SUSPEND_SAFETY_DOOR_AJAR; + } + + } + + if (rt_exec & EXEC_SLEEP) { + if (sys.state == STATE_ALARM) { sys.suspend |= (SUSPEND_RETRACT_COMPLETE|SUSPEND_HOLD_COMPLETE); } + sys.state = STATE_SLEEP; + } + + system_clear_exec_state_flag((EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR | EXEC_SLEEP)); + } + + // Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue. + if (rt_exec & EXEC_CYCLE_START) { + // Block if called at same time as the hold commands: feed hold, motion cancel, and safety door. + // Ensures auto-cycle-start doesn't resume a hold without an explicit user-input. + if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) { + // Resume door state when parking motion has retracted and door has been closed. + if ((sys.state == STATE_SAFETY_DOOR) && !(sys.suspend & SUSPEND_SAFETY_DOOR_AJAR)) { + if (sys.suspend & SUSPEND_RESTORE_COMPLETE) { + sys.state = STATE_IDLE; // Set to IDLE to immediately resume the cycle. + } else if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { + // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR. + // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and + // the retraction execution is complete, which implies the initial feed hold is not active. To + // restore normal operation, the restore procedures must be initiated by the following flag. Once, + // they are complete, it will call CYCLE_START automatically to resume and exit the suspend. + sys.suspend |= SUSPEND_INITIATE_RESTORE; + } + } + // Cycle start only when IDLE or when a hold is complete and ready to resume. + if ((sys.state == STATE_IDLE) || ((sys.state & STATE_HOLD) && (sys.suspend & SUSPEND_HOLD_COMPLETE))) { + if (sys.state == STATE_HOLD && sys.spindle_stop_ovr) { + sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE_CYCLE; // Set to restore in suspend routine and cycle start after. + } else { + // Start cycle only if queued motions exist in planner buffer and the motion is not canceled. + sys.step_control = STEP_CONTROL_NORMAL_OP; // Restore step control to normal operation + if (plan_get_current_block() && bit_isfalse(sys.suspend,SUSPEND_MOTION_CANCEL)) { + sys.suspend = SUSPEND_DISABLE; // Break suspend state. + sys.state = STATE_CYCLE; + st_prep_buffer(); // Initialize step segment buffer before beginning cycle. + st_wake_up(); + } else { // Otherwise, do nothing. Set and resume IDLE state. + sys.suspend = SUSPEND_DISABLE; // Break suspend state. + sys.state = STATE_IDLE; + } + } + } + } + system_clear_exec_state_flag(EXEC_CYCLE_START); + } + + if (rt_exec & EXEC_CYCLE_STOP) { + // Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by + // realtime command execution in the main program, ensuring that the planner re-plans safely. + // NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper + // cycle reinitializations. The stepper path should continue exactly as if nothing has happened. + // NOTE: EXEC_CYCLE_STOP is set by the stepper subsystem when a cycle or feed hold completes. + if ((sys.state & (STATE_HOLD|STATE_SAFETY_DOOR|STATE_SLEEP)) && !(sys.soft_limit) && !(sys.suspend & SUSPEND_JOG_CANCEL)) { + // Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user + // has issued a resume command or reset. + plan_cycle_reinitialize(); + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { sys.suspend |= SUSPEND_HOLD_COMPLETE; } + bit_false(sys.step_control,(STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION)); + } else { + // Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events. + // NOTE: Motion and jog cancel both immediately return to idle after the hold completes. + if (sys.suspend & SUSPEND_JOG_CANCEL) { // For jog cancel, flush buffers and sync positions. + sys.step_control = STEP_CONTROL_NORMAL_OP; + plan_reset(); + st_reset(); + gc_sync_position(); + plan_sync_position(); + } + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { // Only occurs when safety door opens during jog. + sys.suspend &= ~(SUSPEND_JOG_CANCEL); + sys.suspend |= SUSPEND_HOLD_COMPLETE; + sys.state = STATE_SAFETY_DOOR; + } else { + sys.suspend = SUSPEND_DISABLE; + sys.state = STATE_IDLE; + } + } + system_clear_exec_state_flag(EXEC_CYCLE_STOP); + } + } + + // Execute overrides. + rt_exec = sys_rt_exec_motion_override; // Copy volatile sys_rt_exec_motion_override + if (rt_exec) { + system_clear_exec_motion_overrides(); // Clear all motion override flags. + + uint8_t new_f_override = sys.f_override; + if (rt_exec & EXEC_FEED_OVR_RESET) { new_f_override = DEFAULT_FEED_OVERRIDE; } + if (rt_exec & EXEC_FEED_OVR_COARSE_PLUS) { new_f_override += FEED_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_COARSE_MINUS) { new_f_override -= FEED_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_FINE_PLUS) { new_f_override += FEED_OVERRIDE_FINE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_FINE_MINUS) { new_f_override -= FEED_OVERRIDE_FINE_INCREMENT; } + new_f_override = MIN(new_f_override,MAX_FEED_RATE_OVERRIDE); + new_f_override = MAX(new_f_override,MIN_FEED_RATE_OVERRIDE); + + uint8_t new_r_override = sys.r_override; + if (rt_exec & EXEC_RAPID_OVR_RESET) { new_r_override = DEFAULT_RAPID_OVERRIDE; } + if (rt_exec & EXEC_RAPID_OVR_MEDIUM) { new_r_override = RAPID_OVERRIDE_MEDIUM; } + if (rt_exec & EXEC_RAPID_OVR_LOW) { new_r_override = RAPID_OVERRIDE_LOW; } + + if ((new_f_override != sys.f_override) || (new_r_override != sys.r_override)) { + sys.f_override = new_f_override; + sys.r_override = new_r_override; + sys.report_ovr_counter = 0; // Set to report change immediately + plan_update_velocity_profile_parameters(); + plan_cycle_reinitialize(); + } + } + + rt_exec = sys_rt_exec_accessory_override; + if (rt_exec) { + system_clear_exec_accessory_overrides(); // Clear all accessory override flags. + + // NOTE: Unlike motion overrides, spindle overrides do not require a planner reinitialization. + uint8_t last_s_override = sys.spindle_speed_ovr; + if (rt_exec & EXEC_SPINDLE_OVR_RESET) { last_s_override = DEFAULT_SPINDLE_SPEED_OVERRIDE; } + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_PLUS) { last_s_override += SPINDLE_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_MINUS) { last_s_override -= SPINDLE_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_FINE_PLUS) { last_s_override += SPINDLE_OVERRIDE_FINE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_FINE_MINUS) { last_s_override -= SPINDLE_OVERRIDE_FINE_INCREMENT; } + last_s_override = MIN(last_s_override,MAX_SPINDLE_SPEED_OVERRIDE); + last_s_override = MAX(last_s_override,MIN_SPINDLE_SPEED_OVERRIDE); + + if (last_s_override != sys.spindle_speed_ovr) { + bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM); + sys.spindle_speed_ovr = last_s_override; + sys.report_ovr_counter = 0; // Set to report change immediately + } + + if (rt_exec & EXEC_SPINDLE_OVR_STOP) { + // Spindle stop override allowed only while in HOLD state. + // NOTE: Report counters are set in spindle_set_state() when spindle stop is executed. + if (sys.state == STATE_HOLD) { + if (!(sys.spindle_stop_ovr)) { sys.spindle_stop_ovr = SPINDLE_STOP_OVR_INITIATE; } + else if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_ENABLED) { sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE; } + } + } + + // NOTE: Since coolant state always performs a planner sync whenever it changes, the current + // run state can be determined by checking the parser state. + if (rt_exec & (EXEC_COOLANT_FLOOD_OVR_TOGGLE | EXEC_COOLANT_MIST_OVR_TOGGLE)) { + if ((sys.state == STATE_IDLE) || (sys.state & (STATE_CYCLE | STATE_HOLD))) { + uint8_t coolant_state = gc_state.modal.coolant; + #ifdef COOLANT_FLOOD_PIN + if (rt_exec & EXEC_COOLANT_FLOOD_OVR_TOGGLE) + { + if (coolant_state & COOLANT_FLOOD_ENABLE) { + bit_false(coolant_state,COOLANT_FLOOD_ENABLE); + } + else { + coolant_state |= COOLANT_FLOOD_ENABLE; + } + } + #endif + #ifdef COOLANT_MIST_PIN + if (rt_exec & EXEC_COOLANT_MIST_OVR_TOGGLE) { + if (coolant_state & COOLANT_MIST_ENABLE) { + bit_false(coolant_state,COOLANT_MIST_ENABLE); + } + else { + coolant_state |= COOLANT_MIST_ENABLE; + } + } + #endif + + coolant_set_state(coolant_state); // Report counter set in coolant_set_state(). + gc_state.modal.coolant = coolant_state; + } + } + } + + #ifdef DEBUG + if (sys_rt_exec_debug) { + report_realtime_debug(); + sys_rt_exec_debug = 0; + } + #endif + + // Reload step segment buffer + if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_SAFETY_DOOR | STATE_HOMING | STATE_SLEEP| STATE_JOG)) { + st_prep_buffer(); + } + +} + + +// Handles Grbl system suspend procedures, such as feed hold, safety door, and parking motion. +// The system will enter this loop, create local variables for suspend tasks, and return to +// whatever function that invoked the suspend, such that Grbl resumes normal operation. +// This function is written in a way to promote custom parking motions. Simply use this as a +// template +static void protocol_exec_rt_suspend() +{ + #ifdef PARKING_ENABLE + // Declare and initialize parking local variables + float restore_target[N_AXIS]; + float parking_target[N_AXIS]; + float retract_waypoint = PARKING_PULLOUT_INCREMENT; + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); + pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE); + #ifdef USE_LINE_NUMBERS + pl_data->line_number = PARKING_MOTION_LINE_NUMBER; + #endif + #endif + + plan_block_t *block = plan_get_current_block(); + uint8_t restore_condition; + #ifdef VARIABLE_SPINDLE + float restore_spindle_speed; + if (block == NULL) { + restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); + restore_spindle_speed = gc_state.spindle_speed; + } else { + restore_condition = block->condition; + restore_spindle_speed = block->spindle_speed; + } + #ifdef DISABLE_LASER_DURING_HOLD + if (bit_istrue(settings.flags,BITFLAG_LASER_MODE)) { + system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); + } + #endif + #else + if (block == NULL) { restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); } + else { restore_condition = block->condition; } + #endif + + while (sys.suspend) { + + if (sys.abort) { return; } + + // Block until initial hold is complete and the machine has stopped motion. + if (sys.suspend & SUSPEND_HOLD_COMPLETE) { + + // Parking manager. Handles de/re-energizing, switch state checks, and parking motions for + // the safety door and sleep states. + if (sys.state & (STATE_SAFETY_DOOR | STATE_SLEEP)) { + + // Handles retraction motions and de-energizing. + if (bit_isfalse(sys.suspend,SUSPEND_RETRACT_COMPLETE)) { + + // Ensure any prior spindle stop override is disabled at start of safety door routine. + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; + + #ifndef PARKING_ENABLE + + spindle_set_state(SPINDLE_DISABLE,0.0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + + #else + + // Get current position and store restore location and spindle retract waypoint. + system_convert_array_steps_to_mpos(parking_target,sys_position); + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + memcpy(restore_target,parking_target,sizeof(parking_target)); + retract_waypoint += restore_target[PARKING_AXIS]; + retract_waypoint = MIN(retract_waypoint,PARKING_TARGET); + } + + // Execute slow pull-out parking retract motion. Parking requires homing enabled, the + // current location not exceeding the parking target location, and laser mode disabled. + // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. + #ifdef ENABLE_PARKING_OVERRIDE_CONTROL + if ((bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) && + (parking_target[PARKING_AXIS] < PARKING_TARGET) && + bit_isfalse(settings.flags,BITFLAG_LASER_MODE) && + (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { + #else + if ((bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) && + (parking_target[PARKING_AXIS] < PARKING_TARGET) && + bit_isfalse(settings.flags,BITFLAG_LASER_MODE)) { + #endif + // Retract spindle by pullout distance. Ensure retraction motion moves away from + // the workpiece and waypoint motion doesn't exceed the parking target location. + if (parking_target[PARKING_AXIS] < retract_waypoint) { + parking_target[PARKING_AXIS] = retract_waypoint; + pl_data->feed_rate = PARKING_PULLOUT_RATE; + pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Retain accessory state + pl_data->spindle_speed = restore_spindle_speed; + mc_parking_motion(parking_target, pl_data); + } + + // NOTE: Clear accessory state after retract and after an aborted restore motion. + pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE); + pl_data->spindle_speed = 0.0; + spindle_set_state(SPINDLE_DISABLE,0.0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + + // Execute fast parking retract motion to parking target location. + if (parking_target[PARKING_AXIS] < PARKING_TARGET) { + parking_target[PARKING_AXIS] = PARKING_TARGET; + pl_data->feed_rate = PARKING_RATE; + mc_parking_motion(parking_target, pl_data); + } + + } else { + + // Parking motion not possible. Just disable the spindle and coolant. + // NOTE: Laser mode does not start a parking motion to ensure the laser stops immediately. + spindle_set_state(SPINDLE_DISABLE,0.0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + + } + + #endif + + sys.suspend &= ~(SUSPEND_RESTART_RETRACT); + sys.suspend |= SUSPEND_RETRACT_COMPLETE; + + } else { + + + if (sys.state == STATE_SLEEP) { + report_feedback_message(MESSAGE_SLEEP_MODE); + // Spindle and coolant should already be stopped, but do it again just to be sure. + spindle_set_state(SPINDLE_DISABLE,0.0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + st_go_idle(); // Disable steppers + while (!(sys.abort)) { protocol_exec_rt_system(); } // Do nothing until reset. + return; // Abort received. Return to re-initialize. + } + + // Allows resuming from parking/safety door. Actively checks if safety door is closed and ready to resume. + if (sys.state == STATE_SAFETY_DOOR) { + if (!(system_check_safety_door_ajar())) { + sys.suspend &= ~(SUSPEND_SAFETY_DOOR_AJAR); // Reset door ajar flag to denote ready to resume. + } + } + + // Handles parking restore and safety door resume. + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { + + #ifdef PARKING_ENABLE + // Execute fast restore motion to the pull-out position. Parking requires homing enabled. + // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. + #ifdef ENABLE_PARKING_OVERRIDE_CONTROL + if (((settings.flags & (BITFLAG_HOMING_ENABLE|BITFLAG_LASER_MODE)) == BITFLAG_HOMING_ENABLE) && + (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { + #else + if ((settings.flags & (BITFLAG_HOMING_ENABLE|BITFLAG_LASER_MODE)) == BITFLAG_HOMING_ENABLE) { + #endif + // Check to ensure the motion doesn't move below pull-out position. + if (parking_target[PARKING_AXIS] <= PARKING_TARGET) { + parking_target[PARKING_AXIS] = retract_waypoint; + pl_data->feed_rate = PARKING_RATE; + mc_parking_motion(parking_target, pl_data); + } + } + #endif + + // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + if (bit_istrue(settings.flags,BITFLAG_LASER_MODE)) { + // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. + bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM); + } else { + spindle_set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), restore_spindle_speed); + delay_sec(SAFETY_DOOR_SPINDLE_DELAY, DELAY_MODE_SYS_SUSPEND); + } + } + } + if (gc_state.modal.coolant != COOLANT_DISABLE) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + // NOTE: Laser mode will honor this delay. An exhaust system is often controlled by this pin. + coolant_set_state((restore_condition & (PL_COND_FLAG_COOLANT_FLOOD | PL_COND_FLAG_COOLANT_FLOOD))); + delay_sec(SAFETY_DOOR_COOLANT_DELAY, DELAY_MODE_SYS_SUSPEND); + } + } + + #ifdef PARKING_ENABLE + // Execute slow plunge motion from pull-out position to resume position. + #ifdef ENABLE_PARKING_OVERRIDE_CONTROL + if (((settings.flags & (BITFLAG_HOMING_ENABLE|BITFLAG_LASER_MODE)) == BITFLAG_HOMING_ENABLE) && + (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { + #else + if ((settings.flags & (BITFLAG_HOMING_ENABLE|BITFLAG_LASER_MODE)) == BITFLAG_HOMING_ENABLE) { + #endif + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + // Regardless if the retract parking motion was a valid/safe motion or not, the + // restore parking motion should logically be valid, either by returning to the + // original position through valid machine space or by not moving at all. + pl_data->feed_rate = PARKING_PULLOUT_RATE; + pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Restore accessory state + pl_data->spindle_speed = restore_spindle_speed; + mc_parking_motion(restore_target, pl_data); + } + } + #endif + + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + sys.suspend |= SUSPEND_RESTORE_COMPLETE; + system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. + } + } + + } + + + } else { + + // Feed hold manager. Controls spindle stop override states. + // NOTE: Hold ensured as completed by condition check at the beginning of suspend routine. + if (sys.spindle_stop_ovr) { + // Handles beginning of spindle stop + if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_INITIATE) { + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + spindle_set_state(SPINDLE_DISABLE,0.0); // De-energize + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_ENABLED; // Set stop override state to enabled, if de-energized. + } else { + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state + } + // Handles restoring of spindle state + } else if (sys.spindle_stop_ovr & (SPINDLE_STOP_OVR_RESTORE | SPINDLE_STOP_OVR_RESTORE_CYCLE)) { + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + report_feedback_message(MESSAGE_SPINDLE_RESTORE); + if (bit_istrue(settings.flags,BITFLAG_LASER_MODE)) { + // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. + bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM); + } else { + spindle_set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), restore_spindle_speed); + } + } + if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_RESTORE_CYCLE) { + system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. + } + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state + } + } else { + // Handles spindle state during hold. NOTE: Spindle speed overrides may be altered during hold state. + // NOTE: STEP_CONTROL_UPDATE_SPINDLE_PWM is automatically reset upon resume in step generator. + if (bit_istrue(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM)) { + spindle_set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), restore_spindle_speed); + bit_false(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM); + } + } + + } + } + + protocol_exec_rt_system(); + + } +} + diff --git a/Grbl_Esp32-master/Grbl_Esp32/protocol.h b/Grbl_Esp32-master/Grbl_Esp32/protocol.h new file mode 100644 index 0000000..d32b23d --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/protocol.h @@ -0,0 +1,56 @@ +/* + protocol.h - controls Grbl execution protocol and procedures + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef protocol_h +#define protocol_h + +// Line buffer size from the serial input stream to be executed. +// NOTE: Not a problem except for extreme cases, but the line buffer size can be too small +// and g-code blocks can get truncated. Officially, the g-code standards support up to 256 +// characters. In future versions, this will be increased, when we know how much extra +// memory space we can invest into here or we re-write the g-code parser not to have this +// buffer. +#ifndef LINE_BUFFER_SIZE + #define LINE_BUFFER_SIZE 80 +#endif + +// Starts Grbl main loop. It handles all incoming characters from the serial port and executes +// them as they complete. It is also responsible for finishing the initialization procedures. +void protocol_main_loop(); + +// Checks and executes a realtime command at various stop points in main program +void protocol_execute_realtime(); +void protocol_exec_rt_system(); + +// Executes the auto cycle feature, if enabled. +void protocol_auto_cycle_start(); + +// Block until all buffered steps are executed +void protocol_buffer_synchronize(); + +// Executes the auto cycle feature, if enabled. +void protocol_auto_cycle_start(); + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/report.cpp b/Grbl_Esp32-master/Grbl_Esp32/report.cpp new file mode 100644 index 0000000..73a85b5 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/report.cpp @@ -0,0 +1,874 @@ +/* + report.c - reporting and messaging methods + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +/* + This file functions as the primary feedback interface for Grbl. Any outgoing data, such + as the protocol status messages, feedback messages, and status reports, are stored here. + For the most part, these functions primarily are called from protocol.c methods. If a + different style feedback is desired (i.e. JSON), then a user can change these following + methods to accommodate their needs. + + + ESP32 Notes: + + Major rewrite to fix issues with BlueTooth. As described here there is a + when you try to send data a single byte at a time using SerialBT.write(...). + https://github.com/espressif/arduino-esp32/issues/1537 + + A solution is to send messages as a string using SerialBT.print(...). Use + a short delay after each send. Therefore this file needed to be rewritten + to work that way. AVR Grbl was written to be super efficient to give it + good performance. This is far less efficient, but the ESP32 can handle it. + Do not use this version of the file with AVR Grbl. + + ESP32 discussion here ... https://github.com/bdring/Grbl_Esp32/issues/3 + + +*/ + +#include "grbl.h" + +#define DEFAULTBUFFERSIZE 64 + +// this is a generic send function that everything should use, so interfaces could be added (Bluetooth, etc) +void grbl_send(uint8_t client, const char *text) +{ + if (client == CLIENT_INPUT) return; +#ifdef ENABLE_BLUETOOTH + if (SerialBT.hasClient() && ( client == CLIENT_BT || client == CLIENT_ALL ) ) + { + + SerialBT.print(text); + //delay(10); // possible fix for dropped characters + } +#endif + +#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_OUT) + if ( client == CLIENT_WEBUI || client == CLIENT_ALL ) + Serial2Socket.write((const uint8_t*)text, strlen(text)); +#endif + +#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) + if ( client == CLIENT_TELNET || client == CLIENT_ALL ){ + telnet_server.write((const uint8_t*)text, strlen(text)); + } +#endif + + if ( client == CLIENT_SERIAL || client == CLIENT_ALL ) + Serial.print(text); +} + +// This is a formating version of the grbl_send(CLIENT_ALL,...) function that work like printf +void grbl_sendf(uint8_t client, const char *format, ...) +{ + if (client == CLIENT_INPUT) return; + char loc_buf[64]; + char * temp = loc_buf; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + size_t len = vsnprintf(NULL, 0, format, arg); + va_end(copy); + if(len >= sizeof(loc_buf)){ + temp = new char[len+1]; + if(temp == NULL) { + return; + } + } + len = vsnprintf(temp, len+1, format, arg); + grbl_send(client, temp); + va_end(arg); + if(len > 64){ + delete[] temp; + } +} + +//function to notify +void grbl_notify(const char *title, const char *msg){ +#ifdef ENABLE_NOTIFICATIONS + notificationsservice.sendMSG(title, msg); +#endif +} + +void grbl_notifyf(const char *title, const char *format, ...){ +char loc_buf[64]; + char * temp = loc_buf; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + size_t len = vsnprintf(NULL, 0, format, arg); + va_end(copy); + if(len >= sizeof(loc_buf)){ + temp = new char[len+1]; + if(temp == NULL) { + return; + } + } + len = vsnprintf(temp, len+1, format, arg); + grbl_notify(title, temp); + va_end(arg); + if(len > 64){ + delete[] temp; + } +} + +// formats axis values into a string and returns that string in rpt +static void report_util_axis_values(float *axis_value, char *rpt) { + uint8_t idx; + char axisVal[10]; + float unit_conv = 1.0; // unit conversion multiplier..default is mm + + rpt[0] = '\0'; + + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) + unit_conv = 1.0 / MM_PER_INCH; + + for (idx=0; idx= MOTION_MODE_PROBE_TOWARD) { + sprintf(temp, "38.%d", gc_state.modal.motion - (MOTION_MODE_PROBE_TOWARD-2)); + } else { + sprintf(temp, "%d", gc_state.modal.motion); + } + strcat(modes_rpt, temp); + + sprintf(temp, " G%d", gc_state.modal.coord_select+54); + strcat(modes_rpt, temp); + + sprintf(temp, " G%d", gc_state.modal.plane_select+17); + strcat(modes_rpt, temp); + + sprintf(temp, " G%d", 21-gc_state.modal.units); + strcat(modes_rpt, temp); + + sprintf(temp, " G%d", gc_state.modal.distance+90); + strcat(modes_rpt, temp); + + sprintf(temp, " G%d", 94-gc_state.modal.feed_rate); + strcat(modes_rpt, temp); + + + if (gc_state.modal.program_flow) { + //report_util_gcode_modes_M(); + switch (gc_state.modal.program_flow) { + case PROGRAM_FLOW_PAUSED : strcat(modes_rpt, " M0"); //serial_write('0'); break; + // case PROGRAM_FLOW_OPTIONAL_STOP : serial_write('1'); break; // M1 is ignored and not supported. + case PROGRAM_FLOW_COMPLETED_M2 : + case PROGRAM_FLOW_COMPLETED_M30 : + sprintf(temp, " M%d", gc_state.modal.program_flow); + strcat(modes_rpt, temp); + break; + } + } + + + switch (gc_state.modal.spindle) { + case SPINDLE_ENABLE_CW : strcat(modes_rpt, " M3"); break; + case SPINDLE_ENABLE_CCW : strcat(modes_rpt, " M4"); break; + case SPINDLE_DISABLE : strcat(modes_rpt, " M5"); break; + } + + //report_util_gcode_modes_M(); // optional M7 and M8 should have been dealt with by here + if (gc_state.modal.coolant) { // Note: Multiple coolant states may be active at the same time. + if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_MIST) { strcat(modes_rpt, " M7"); } + if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_FLOOD) { strcat(modes_rpt, " M8"); } + } + else { + strcat(modes_rpt, " M9"); + } + + sprintf(temp, " T%d", gc_state.tool); + strcat(modes_rpt, temp); + + + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { + sprintf(temp, " F%.1f", gc_state.feed_rate); + } else { + sprintf(temp, " F%.0f", gc_state.feed_rate); + } + strcat(modes_rpt, temp); + + #ifdef VARIABLE_SPINDLE + sprintf(temp, " S%4.3f", gc_state.spindle_speed); + strcat(modes_rpt, temp); + #endif + + strcat(modes_rpt, "]\r\n"); + + grbl_send(client, modes_rpt); +} + + + +// Prints specified startup line +void report_startup_line(uint8_t n, char *line, uint8_t client) +{ + grbl_sendf(client, "$N%d=%s\r\n", n, line); // OK to send to all +} + +void report_execute_startup_message(char *line, uint8_t status_code, uint8_t client) +{ + grbl_sendf(client, ">%s:", line); // OK to send to all + report_status_message(status_code, client); +} + +// Prints build info line +void report_build_info(char *line, uint8_t client) +{ + char build_info[50]; + + strcpy(build_info, "[VER:" GRBL_VERSION "." GRBL_VERSION_BUILD ":"); + strcat(build_info, line); + strcat(build_info, "]\r\n[OPT:"); + + #ifdef VARIABLE_SPINDLE + strcat(build_info,"V"); + #endif + #ifdef USE_LINE_NUMBERS + strcat(build_info,"N"); + #endif + #ifdef COOLANT_MIST_PIN + strcat(build_info,"M"); // TODO Need to deal with M8...it could be disabled + #endif + #ifdef COREXY + strcat(build_info,"C"); + #endif + #ifdef PARKING_ENABLE + strcat(build_info,"P"); + #endif + #if (defined(HOMING_FORCE_SET_ORIGIN) || defined(HOMING_FORCE_POSITIVE_SPACE)) + strcat(build_info,"Z"); // homing MPOS bahavior is not the default behavior + #endif + #ifdef HOMING_SINGLE_AXIS_COMMANDS + strcat(build_info,"H"); + #endif + #ifdef LIMITS_TWO_SWITCHES_ON_AXES + strcat(build_info,"L"); + #endif + #ifdef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES + strcat(build_info,"A"); + #endif + #ifdef ENABLE_BLUETOOTH + strcat(build_info,"B"); + #endif + #ifdef ENABLE_SD_CARD + strcat(build_info,"S"); + #endif + #if defined (ENABLE_WIFI) + strcat(build_info,"W"); + #endif + #ifndef ENABLE_RESTORE_EEPROM_WIPE_ALL // NOTE: Shown when disabled. + strcat(build_info,"*"); + #endif + #ifndef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // NOTE: Shown when disabled. + strcat(build_info,"$"); + #endif + #ifndef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // NOTE: Shown when disabled. + strcat(build_info,"#"); + #endif + #ifndef ENABLE_BUILD_INFO_WRITE_COMMAND // NOTE: Shown when disabled. + strcat(build_info,"I"); + #endif + #ifndef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // NOTE: Shown when disabled. + strcat(build_info,"E"); + #endif + #ifndef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // NOTE: Shown when disabled. + strcat(build_info,"W"); + #endif + // NOTE: Compiled values, like override increments/max/min values, may be added at some point later. + // These will likely have a comma delimiter to separate them. + + strcat(build_info,"]\r\n"); + grbl_send(client, build_info); // ok to send to all + #if defined (ENABLE_WIFI) + grbl_send(client, (char *)wifi_config.info()); + #endif + #if defined (ENABLE_BLUETOOTH) + grbl_send(client, (char *)bt_config.info()); + #endif +} + + + + +// Prints the character string line Grbl has received from the user, which has been pre-parsed, +// and has been sent into protocol_execute_line() routine to be executed by Grbl. +void report_echo_line_received(char *line, uint8_t client) +{ + grbl_sendf(client, "[echo: %s]\r\n", line); +} + + // Prints real-time data. This function grabs a real-time snapshot of the stepper subprogram + // and the actual location of the CNC machine. Users may change the following function to their + // specific needs, but the desired real-time data report must be as short as possible. This is + // requires as it minimizes the computational overhead and allows grbl to keep running smoothly, + // especially during g-code programs with fast, short line segments and high frequency reports (5-20Hz). +void report_realtime_status(uint8_t client) +{ + uint8_t idx; + int32_t current_position[N_AXIS]; // Copy current state of the system position variable + memcpy(current_position,sys_position,sizeof(sys_position)); + float print_position[N_AXIS]; + + char status[200]; + char temp[80]; + + system_convert_array_steps_to_mpos(print_position,current_position); + + // Report current machine state and sub-states + strcpy(status, "<"); + switch (sys.state) { + case STATE_IDLE: strcat(status, "Idle"); break; + case STATE_CYCLE: strcat(status, "Run"); break; + case STATE_HOLD: + + if (!(sys.suspend & SUSPEND_JOG_CANCEL)) { + strcat(status, "Hold:"); + if (sys.suspend & SUSPEND_HOLD_COMPLETE) { strcat(status, "0"); } // Ready to resume + else { strcat(status, "1"); } // Actively holding + break; + } // Continues to print jog state during jog cancel. + case STATE_JOG: strcat(status, "Jog"); break; + case STATE_HOMING: strcat(status, "Home"); break; + case STATE_ALARM: strcat(status, "Alarm"); break; + case STATE_CHECK_MODE: strcat(status, "Check"); break; + case STATE_SAFETY_DOOR: + strcat(status, "Door:"); + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { + strcat(status, "3"); // Restoring + } else { + if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { + strcat(status, "1"); // Door ajar + } else { + strcat(status, "0"); + } // Door closed and ready to resume + } else { + strcat(status, "2"); // Retracting + } + } + break; + case STATE_SLEEP: strcat(status, "Sleep"); break; + } + + float wco[N_AXIS]; + if (bit_isfalse(settings.status_report_mask,BITFLAG_RT_STATUS_POSITION_TYPE) || + (sys.report_wco_counter == 0) ) { + for (idx=0; idx< N_AXIS; idx++) { + // Apply work coordinate offsets and tool length offset to current position. + wco[idx] = gc_state.coord_system[idx]+gc_state.coord_offset[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) { wco[idx] += gc_state.tool_length_offset; } + if (bit_isfalse(settings.status_report_mask,BITFLAG_RT_STATUS_POSITION_TYPE)) { + print_position[idx] -= wco[idx]; + } + } + } + + // Report machine position + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_POSITION_TYPE)) { + strcat(status, "|MPos:"); + } else { + #ifdef FWD_KINEMATICS_REPORTING + forward_kinematics(print_position); + #endif + strcat(status, "|WPos:"); + } + + + report_util_axis_values(print_position, temp); + strcat(status, temp); + + // Returns planner and serial read buffer states. +#ifdef REPORT_FIELD_BUFFER_STATE + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_BUFFER_STATE)) { + int bufsize = DEFAULTBUFFERSIZE; +#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) + if (client == CLIENT_TELNET){ + bufsize = telnet_server.get_rx_buffer_available(); + } +#endif //ENABLE_WIFI && ENABLE_TELNET +#if defined(ENABLE_BLUETOOTH) + if (client == CLIENT_BT){ + //TODO FIXME + bufsize = 512 - SerialBT.available(); + } +#endif //ENABLE_BLUETOOTH + if (client == CLIENT_SERIAL){ + bufsize = serial_get_rx_buffer_available(CLIENT_SERIAL); + } + sprintf(temp, "|Bf:%d,%d", plan_get_block_buffer_available(), bufsize); + strcat(status, temp); + } +#endif + + #ifdef USE_LINE_NUMBERS + #ifdef REPORT_FIELD_LINE_NUMBERS + // Report current line number + plan_block_t * cur_block = plan_get_current_block(); + if (cur_block != NULL) { + uint32_t ln = cur_block->line_number; + if (ln > 0) { + sprintf(temp, "|Ln:%d", ln); + strcat(status, temp); + } + } + #endif + #endif + + // Report realtime feed speed + #ifdef REPORT_FIELD_CURRENT_FEED_SPEED + #ifdef VARIABLE_SPINDLE + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { + sprintf(temp, "|FS:%.1f,%.0f", st_get_realtime_rate(), sys.spindle_speed / MM_PER_INCH); + } else { + sprintf(temp, "|FS:%.0f,%.0f", st_get_realtime_rate(), sys.spindle_speed); + } + strcat(status, temp); + #else + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { + sprintf(temp, "|F:%.1f", st_get_realtime_rate() / MM_PER_INCH); + } else { + sprintf(temp, "|F:%.0f", st_get_realtime_rate()); + } + strcat(status, temp); + #endif + #endif + + #ifdef REPORT_FIELD_PIN_STATE + uint8_t lim_pin_state = limits_get_state(); + uint8_t ctrl_pin_state = system_control_get_state(); + uint8_t prb_pin_state = probe_get_state(); + if (lim_pin_state | ctrl_pin_state | prb_pin_state) { + strcat(status, "|Pn:"); + if (prb_pin_state) { strcat(status, "P"); } + if (lim_pin_state) { + if (bit_istrue(lim_pin_state,bit(X_AXIS))) { strcat(status, "X"); } + if (bit_istrue(lim_pin_state,bit(Y_AXIS))) { strcat(status, "Y"); } + if (bit_istrue(lim_pin_state,bit(Z_AXIS))) { strcat(status, "Z"); } + #if (N_AXIS > A_AXIS) + if (bit_istrue(lim_pin_state,bit(A_AXIS))) { strcat(status, "A"); } + #endif + #if (N_AXIS > B_AXIS) + if (bit_istrue(lim_pin_state,bit(B_AXIS))) { strcat(status, "B"); } + #endif + #if (N_AXIS > C_AXIS) + if (bit_istrue(lim_pin_state,bit(C_AXIS))) { strcat(status, "C"); } + #endif + } + if (ctrl_pin_state) { + #ifdef ENABLE_SAFETY_DOOR_INPUT_PIN + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_SAFETY_DOOR)) { strcat(status, "D"); } + #endif + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_RESET)) { strcat(status, "R"); } + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_FEED_HOLD)) { strcat(status, "H"); } + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_CYCLE_START)) { strcat(status, "S"); } + } + } + #endif + + #ifdef REPORT_FIELD_WORK_COORD_OFFSET + if (sys.report_wco_counter > 0) { sys.report_wco_counter--; } + else { + if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { + sys.report_wco_counter = (REPORT_WCO_REFRESH_BUSY_COUNT-1); // Reset counter for slow refresh + } else { sys.report_wco_counter = (REPORT_WCO_REFRESH_IDLE_COUNT-1); } + if (sys.report_ovr_counter == 0) { sys.report_ovr_counter = 1; } // Set override on next report. + strcat(status, "|WCO:"); + report_util_axis_values(wco, temp); + strcat(status, temp); + } + #endif + + #ifdef REPORT_FIELD_OVERRIDES + if (sys.report_ovr_counter > 0) { sys.report_ovr_counter--; } + else { + if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { + sys.report_ovr_counter = (REPORT_OVR_REFRESH_BUSY_COUNT-1); // Reset counter for slow refresh + } else { sys.report_ovr_counter = (REPORT_OVR_REFRESH_IDLE_COUNT-1); } + + sprintf(temp, "|Ov:%d,%d,%d", sys.f_override, sys.r_override, sys.spindle_speed_ovr); + strcat(status, temp); + + uint8_t sp_state = spindle_get_state(); + uint8_t cl_state = coolant_get_state(); + if (sp_state || cl_state) { + strcat(status, "|A:"); + if (sp_state) { // != SPINDLE_STATE_DISABLE + if (sp_state == SPINDLE_STATE_CW) { strcat(status, "S"); } // CW + else { strcat(status, "C"); } // CCW + } + if (cl_state & COOLANT_STATE_FLOOD) { strcat(status, "F"); } + #ifdef COOLANT_MIST_PIN // TODO Deal with M8 - Flood + if (cl_state & COOLANT_STATE_MIST) { strcat(status, "M"); } + #endif + } + } + #endif + + #ifdef ENABLE_SD_CARD + if (get_sd_state(false) == SDCARD_BUSY_PRINTING) { + sprintf(temp, "|SD:%4.2f,", sd_report_perc_complete()); + strcat(status, temp); + + sd_get_current_filename(temp); + strcat(status, temp); + } + #endif + + strcat(status, ">\r\n"); + + grbl_send(client, status); +} + +void report_realtime_steps() +{ + uint8_t idx; + for (idx=0; idx< N_AXIS; idx++) { + grbl_sendf(CLIENT_ALL, "%ld\n", sys_position[idx]); // OK to send to all ... debug stuff + } +} + +void report_gcode_comment(char *comment) { + char msg[80]; + const uint8_t offset = 4; // ignore "MSG_" part of comment + uint8_t index = offset; + + if (strstr(comment, "MSG")) { + while(index < strlen(comment)) { + msg[index-offset] = comment[index]; + index++; + } + msg[index-offset] = 0; // null terminate + + grbl_sendf(CLIENT_ALL, "[MSG:GCode Comment %s]\r\n",msg); + } +} \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/report.h b/Grbl_Esp32-master/Grbl_Esp32/report.h new file mode 100644 index 0000000..55f2a42 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/report.h @@ -0,0 +1,167 @@ +/* + report.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef report_h +#define report_h + +#include "grbl.h" + +// Define Grbl status codes. Valid values (0-255) +#define STATUS_OK 0 +#define STATUS_EXPECTED_COMMAND_LETTER 1 +#define STATUS_BAD_NUMBER_FORMAT 2 +#define STATUS_INVALID_STATEMENT 3 +#define STATUS_NEGATIVE_VALUE 4 +#define STATUS_SETTING_DISABLED 5 +#define STATUS_SETTING_STEP_PULSE_MIN 6 +#define STATUS_SETTING_READ_FAIL 7 +#define STATUS_IDLE_ERROR 8 +#define STATUS_SYSTEM_GC_LOCK 9 +#define STATUS_SOFT_LIMIT_ERROR 10 +#define STATUS_OVERFLOW 11 +#define STATUS_MAX_STEP_RATE_EXCEEDED 12 +#define STATUS_CHECK_DOOR 13 +#define STATUS_LINE_LENGTH_EXCEEDED 14 +#define STATUS_TRAVEL_EXCEEDED 15 +#define STATUS_INVALID_JOG_COMMAND 16 +#define STATUS_SETTING_DISABLED_LASER 17 + +#define STATUS_GCODE_UNSUPPORTED_COMMAND 20 +#define STATUS_GCODE_MODAL_GROUP_VIOLATION 21 +#define STATUS_GCODE_UNDEFINED_FEED_RATE 22 +#define STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER 23 +#define STATUS_GCODE_AXIS_COMMAND_CONFLICT 24 +#define STATUS_GCODE_WORD_REPEATED 25 +#define STATUS_GCODE_NO_AXIS_WORDS 26 +#define STATUS_GCODE_INVALID_LINE_NUMBER 27 +#define STATUS_GCODE_VALUE_WORD_MISSING 28 +#define STATUS_GCODE_UNSUPPORTED_COORD_SYS 29 +#define STATUS_GCODE_G53_INVALID_MOTION_MODE 30 +#define STATUS_GCODE_AXIS_WORDS_EXIST 31 +#define STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE 32 +#define STATUS_GCODE_INVALID_TARGET 33 +#define STATUS_GCODE_ARC_RADIUS_ERROR 34 +#define STATUS_GCODE_NO_OFFSETS_IN_PLANE 35 +#define STATUS_GCODE_UNUSED_WORDS 36 +#define STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR 37 +#define STATUS_GCODE_MAX_VALUE_EXCEEDED 38 +#define STATUS_P_PARAM_MAX_EXCEEDED 39 + +#define STATUS_SD_FAILED_MOUNT 60 // SD Failed to mount +#define STATUS_SD_FAILED_READ 61 // SD Failed to read file +#define STATUS_SD_FAILED_OPEN_DIR 62 // SD card failed to open directory +#define STATUS_SD_DIR_NOT_FOUND 63 // SD Card directory not found +#define STATUS_SD_FILE_EMPTY 64 // SD Card directory not found + +#define STATUS_BT_FAIL_BEGIN 70 // Bluetooth failed to start + + + +// Define Grbl alarm codes. Valid values (1-255). 0 is reserved. +#define ALARM_HARD_LIMIT_ERROR EXEC_ALARM_HARD_LIMIT +#define ALARM_SOFT_LIMIT_ERROR EXEC_ALARM_SOFT_LIMIT +#define ALARM_ABORT_CYCLE EXEC_ALARM_ABORT_CYCLE +#define ALARM_PROBE_FAIL_INITIAL EXEC_ALARM_PROBE_FAIL_INITIAL +#define ALARM_PROBE_FAIL_CONTACT EXEC_ALARM_PROBE_FAIL_CONTACT +#define ALARM_HOMING_FAIL_RESET EXEC_ALARM_HOMING_FAIL_RESET +#define ALARM_HOMING_FAIL_DOOR EXEC_ALARM_HOMING_FAIL_DOOR +#define ALARM_HOMING_FAIL_PULLOFF EXEC_ALARM_HOMING_FAIL_PULLOFF +#define ALARM_HOMING_FAIL_APPROACH EXEC_ALARM_HOMING_FAIL_APPROACH + +// Define Grbl feedback message codes. Valid values (0-255). +#define MESSAGE_CRITICAL_EVENT 1 +#define MESSAGE_ALARM_LOCK 2 +#define MESSAGE_ALARM_UNLOCK 3 +#define MESSAGE_ENABLED 4 +#define MESSAGE_DISABLED 5 +#define MESSAGE_SAFETY_DOOR_AJAR 6 +#define MESSAGE_CHECK_LIMITS 7 +#define MESSAGE_PROGRAM_END 8 +#define MESSAGE_RESTORE_DEFAULTS 9 +#define MESSAGE_SPINDLE_RESTORE 10 +#define MESSAGE_SLEEP_MODE 11 +#define MESSAGE_SD_FILE_QUIT 60 // mc_reset was called during an SD job + +#define CLIENT_SERIAL 1 +#define CLIENT_BT 2 +#define CLIENT_WEBUI 3 +#define CLIENT_TELNET 4 +#define CLIENT_INPUT 5 +#define CLIENT_ALL 0xFF +#define CLIENT_COUNT 5 // total number of client types regardless if they are used + +// functions to send data to the user. +void grbl_send(uint8_t client, const char *text); +void grbl_sendf(uint8_t client, const char *format, ...); + +//function to notify +void grbl_notify(const char *title, const char *msg); +void grbl_notifyf(const char *title, const char *format, ...); + +// Prints system status messages. +void report_status_message(uint8_t status_code, uint8_t client); +void report_realtime_steps(); + +// Prints system alarm messages. +void report_alarm_message(uint8_t alarm_code); + +// Prints miscellaneous feedback messages. +void report_feedback_message(uint8_t message_code); + +// Prints welcome message +void report_init_message(uint8_t client); + +// Prints Grbl help and current global settings +void report_grbl_help(uint8_t client); + +// Prints Grbl global settings +void report_grbl_settings(uint8_t client); + +// Prints an echo of the pre-parsed line received right before execution. +void report_echo_line_received(char *line, uint8_t client); + +// Prints realtime status report +void report_realtime_status(uint8_t client); + +// Prints recorded probe position +void report_probe_parameters(uint8_t client); + +// Prints Grbl NGC parameters (coordinate offsets, probe) +void report_ngc_parameters(uint8_t client); + +// Prints current g-code parser mode state +void report_gcode_modes(uint8_t client); + +// Prints startup line when requested and executed. +void report_startup_line(uint8_t n, char *line, uint8_t client); +void report_execute_startup_message(char *line, uint8_t status_code, uint8_t client); + +// Prints build info and user info +void report_build_info(char *line, uint8_t client); + +void report_gcode_comment(char *comment); + +#ifdef DEBUG + void report_realtime_debug(); +#endif + + + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/serial.cpp b/Grbl_Esp32-master/Grbl_Esp32/serial.cpp new file mode 100644 index 0000000..5a00714 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/serial.cpp @@ -0,0 +1,348 @@ +/* + serial.cpp - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" +#include "commands.h" + +#define RX_RING_BUFFER (RX_BUFFER_SIZE+1) +#define TX_RING_BUFFER (TX_BUFFER_SIZE+1) + +portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; + +uint8_t serial_rx_buffer[CLIENT_COUNT][RX_RING_BUFFER]; +uint8_t serial_rx_buffer_head[CLIENT_COUNT] = {0}; +volatile uint8_t serial_rx_buffer_tail[CLIENT_COUNT] = {0}; +static TaskHandle_t serialCheckTaskHandle = 0; + +// Returns the number of bytes available in the RX serial buffer. +uint8_t serial_get_rx_buffer_available(uint8_t client) +{ + uint8_t client_idx = client - 1; + + uint8_t rtail = serial_rx_buffer_tail[client_idx]; // Copy to limit multiple calls to volatile + if (serial_rx_buffer_head[client_idx] >= rtail) { return(RX_BUFFER_SIZE - (serial_rx_buffer_head[client_idx]-rtail)); } + return((rtail-serial_rx_buffer_head[client_idx]-1)); +} + +void serial_init() +{ + Serial.begin(BAUD_RATE); + grbl_send(CLIENT_SERIAL,"\r\n"); // create some white space after ESP32 boot info + serialCheckTaskHandle = 0; + // create a task to check for incoming data + xTaskCreatePinnedToCore( serialCheckTask, // task + "serialCheckTask", // name for task + 8192, // size of task stack + NULL, // parameters + 1, // priority + &serialCheckTaskHandle, + 0 // core + ); + +} + + +// this task runs and checks for data on all interfaces +// REaltime stuff is acted upon, then characters are added to the appropriate buffer +void serialCheckTask(void *pvParameters) +{ + uint8_t data = 0; + uint8_t next_head; + uint8_t client = CLIENT_ALL; // who send the data + + uint8_t client_idx = 0; // index of data buffer + + while(true) // run continuously + { + while (Serial.available() || inputBuffer.available() + #ifdef ENABLE_BLUETOOTH + || (SerialBT.hasClient() && SerialBT.available()) + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + || Serial2Socket.available() + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) + || telnet_server.available() + #endif + ) + { + if (Serial.available()) + { + client = CLIENT_SERIAL; + data = Serial.read(); + } + else if (inputBuffer.available()){ + client = CLIENT_INPUT; + data = inputBuffer.read(); + } + else + { //currently is wifi or BT but better to prepare both can be live + #ifdef ENABLE_BLUETOOTH + if(SerialBT.hasClient() && SerialBT.available()){ + client = CLIENT_BT; + data = SerialBT.read(); + //Serial.write(data); // echo all data to serial + } else { + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + if (Serial2Socket.available()) { + client = CLIENT_WEBUI; + data = Serial2Socket.read(); + } + else + { + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) + if(telnet_server.available()){ + client = CLIENT_TELNET; + data = telnet_server.read(); + } + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + } + #endif + #ifdef ENABLE_BLUETOOTH + } + #endif + } + + client_idx = client - 1; // for zero based array + + // Pick off realtime command characters directly from the serial stream. These characters are + // not passed into the main buffer, but these set system state flag bits for realtime execution. + switch (data) { + case CMD_RESET: + mc_reset(); // Call motion control reset routine. + //report_init_message(client); // fool senders into thinking a reset happened. + break; + case CMD_STATUS_REPORT: + report_realtime_status(client); + break; // direct call instead of setting flag + case CMD_CYCLE_START: system_set_exec_state_flag(EXEC_CYCLE_START); break; // Set as true + case CMD_FEED_HOLD: system_set_exec_state_flag(EXEC_FEED_HOLD); break; // Set as true + default : + if (data > 0x7F) { // Real-time control characters are extended ACSII only. + switch(data) { + case CMD_SAFETY_DOOR: system_set_exec_state_flag(EXEC_SAFETY_DOOR); break; // Set as true + case CMD_JOG_CANCEL: + if (sys.state & STATE_JOG) { // Block all other states from invoking motion cancel. + system_set_exec_state_flag(EXEC_MOTION_CANCEL); + } + break; + #ifdef DEBUG + case CMD_DEBUG_REPORT: {uint8_t sreg = SREG; cli(); bit_true(sys_rt_exec_debug,EXEC_DEBUG_REPORT); SREG = sreg;} break; + #endif + case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break; + case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break; + case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break; + case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break; + case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break; + case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break; + case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break; + case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break; + case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break; + case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break; + case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break; + case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break; + case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break; + case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break; + #ifdef COOLANT_FLOOD_PIN + case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break; + #endif + #ifdef COOLANT_MIST_PIN + case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break; + #endif + } + // Throw away any unfound extended-ASCII character by not passing it to the serial buffer. + } else { // Write character to buffer + + vTaskEnterCritical(&myMutex); + next_head = serial_rx_buffer_head[client_idx] + 1; + if (next_head == RX_RING_BUFFER) { next_head = 0; } + + // Write data to buffer unless it is full. + if (next_head != serial_rx_buffer_tail[client_idx]) { + serial_rx_buffer[client_idx][serial_rx_buffer_head[client_idx]] = data; + serial_rx_buffer_head[client_idx] = next_head; + } + vTaskExitCritical(&myMutex); + } + } // switch data + } // if something available + COMMANDS::handle(); +#ifdef ENABLE_WIFI + wifi_config.handle(); +#endif +#ifdef ENABLE_BLUETOOTH + bt_config.handle(); +#endif +#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + Serial2Socket.handle_flush(); +#endif + vTaskDelay(1 / portTICK_RATE_MS); // Yield to other tasks + } // while(true) +} + +// ==================== call this in main protocol loop if you want it in the main task ========= +// be sure to stop task. +// Realtime stuff is acted upon, then characters are added to the appropriate buffer +void serialCheck() +{ + uint8_t data = 0; + uint8_t next_head; + uint8_t client = CLIENT_SERIAL; // who send the data + + uint8_t client_idx = 0; // index of data buffer + + + while (Serial.available() || inputBuffer.available() + #ifdef ENABLE_BLUETOOTH + || (SerialBT.hasClient() && SerialBT.available()) + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + || Serial2Socket.available() + #endif + ) + { + if (Serial.available()) + { + client = CLIENT_SERIAL; + data = Serial.read(); + } + else if (inputBuffer.available()) + { + client = CLIENT_INPUT; + data = inputBuffer.read(); + } +#if defined (ENABLE_BLUETOOTH) || (defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN)) + else + { //currently is wifi or BT but better to prepare both can be live + #ifdef ENABLE_BLUETOOTH + if(SerialBT.hasClient() && SerialBT.available()){ + client = CLIENT_BT; + data = SerialBT.read(); + } else { + #endif + #if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) + client = CLIENT_WEBUI; + data = Serial2Socket.read(); + #endif + #ifdef ENABLE_BLUETOOTH + } + #endif + } +#endif + + client_idx = client - 1; // for zero based array + + // Pick off realtime command characters directly from the serial stream. These characters are + // not passed into the main buffer, but these set system state flag bits for realtime execution. + switch (data) { + case CMD_RESET: mc_reset(); break; // Call motion control reset routine. + case CMD_STATUS_REPORT: + report_realtime_status(client); + break; // direct call instead of setting flag + case CMD_CYCLE_START: system_set_exec_state_flag(EXEC_CYCLE_START); break; // Set as true + case CMD_FEED_HOLD: system_set_exec_state_flag(EXEC_FEED_HOLD); break; // Set as true + default : + if (data > 0x7F) { // Real-time control characters are extended ACSII only. + switch(data) { + case CMD_SAFETY_DOOR: system_set_exec_state_flag(EXEC_SAFETY_DOOR); break; // Set as true + case CMD_JOG_CANCEL: + if (sys.state & STATE_JOG) { // Block all other states from invoking motion cancel. + system_set_exec_state_flag(EXEC_MOTION_CANCEL); + } + break; + #ifdef DEBUG + case CMD_DEBUG_REPORT: {uint8_t sreg = SREG; cli(); bit_true(sys_rt_exec_debug,EXEC_DEBUG_REPORT); SREG = sreg;} break; + #endif + case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break; + case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break; + case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break; + case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break; + case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break; + case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break; + case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break; + case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break; + case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break; + case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break; + case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break; + case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break; + case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break; + case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break; + #ifdef COOLANT_FLOOD_PIN + case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break; + #endif + #ifdef COOLANT_MIST_PIN + case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break; + #endif + } + // Throw away any unfound extended-ASCII character by not passing it to the serial buffer. + } else { // Write character to buffer + + + next_head = serial_rx_buffer_head[client_idx] + 1; + if (next_head == RX_RING_BUFFER) { next_head = 0; } + + // Write data to buffer unless it is full. + if (next_head != serial_rx_buffer_tail[client_idx]) { + serial_rx_buffer[client_idx][serial_rx_buffer_head[client_idx]] = data; + serial_rx_buffer_head[client_idx] = next_head; + } + } + } // switch data + } // if something available +} + +void serial_reset_read_buffer(uint8_t client) +{ + for (uint8_t client_num = 0; client_num <= CLIENT_COUNT; client_num++) + { + if (client == client_num || client == CLIENT_ALL) + { + serial_rx_buffer_tail[client_num-1] = serial_rx_buffer_head[client_num-1]; + } + } +} + +// Writes one byte to the TX serial buffer. Called by main program. +void serial_write(uint8_t data) { + Serial.write((char)data); +} +// Fetches the first byte in the serial read buffer. Called by main program. +uint8_t serial_read(uint8_t client) +{ + uint8_t client_idx = client - 1; + + uint8_t tail = serial_rx_buffer_tail[client_idx]; // Temporary serial_rx_buffer_tail (to optimize for volatile) + if (serial_rx_buffer_head[client_idx] == tail) { + return SERIAL_NO_DATA; + } else { + vTaskEnterCritical(&myMutex); // make sure buffer is not modified while reading by newly read chars from the serial when we are here + uint8_t data = serial_rx_buffer[client_idx][tail]; + + tail++; + if (tail == RX_RING_BUFFER) { tail = 0; } + serial_rx_buffer_tail[client_idx] = tail; + vTaskExitCritical(&myMutex); + return data; + } +} + diff --git a/Grbl_Esp32-master/Grbl_Esp32/serial.h b/Grbl_Esp32-master/Grbl_Esp32/serial.h new file mode 100644 index 0000000..3d88a8a --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/serial.h @@ -0,0 +1,57 @@ +/* + serial.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef serial_h +#define serial_h + +#include "grbl.h" + +#ifndef RX_BUFFER_SIZE + #define RX_BUFFER_SIZE 128 +#endif +#ifndef TX_BUFFER_SIZE + #ifdef USE_LINE_NUMBERS + #define TX_BUFFER_SIZE 112 + #else + #define TX_BUFFER_SIZE 104 + #endif +#endif + +#define SERIAL_NO_DATA 0xff + +// a task to read for incoming data from serial port +void serialCheckTask(void *pvParameters); + +void serialCheck(); + +void serial_write(uint8_t data); +// Fetches the first byte in the serial read buffer. Called by main program. +uint8_t serial_read(uint8_t client); + +// See if the character is an action command like feedhold or jogging. If so, do the action and return true +uint8_t check_action_command(uint8_t data); + +void serial_init(); +void serial_reset_read_buffer(uint8_t client); + +// Returns the number of bytes available in the RX serial buffer. +uint8_t serial_get_rx_buffer_available(uint8_t client); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/serial2socket.cpp b/Grbl_Esp32-master/Grbl_Esp32/serial2socket.cpp new file mode 100644 index 0000000..fcc74e1 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/serial2socket.cpp @@ -0,0 +1,181 @@ +/* + serial2socket.cpp - serial 2 socket functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifdef ARDUINO_ARCH_ESP32 + +//#include "grbl.h" +#include "config.h" + +#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) + + +#include "serial2socket.h" +#include "web_server.h" +#include +#include +Serial_2_Socket Serial2Socket; + + +Serial_2_Socket::Serial_2_Socket(){ + _web_socket = NULL; + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} +Serial_2_Socket::~Serial_2_Socket(){ + if (_web_socket) detachWS(); + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} +void Serial_2_Socket::begin(long speed){ + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +void Serial_2_Socket::end(){ + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +long Serial_2_Socket::baudRate(){ + return 0; +} + +bool Serial_2_Socket::attachWS(void * web_socket){ + if (web_socket) { + _web_socket = web_socket; + _TXbufferSize=0; + return true; + } + return false; +} + +bool Serial_2_Socket::detachWS(){ + _web_socket = NULL; + return true; +} + +Serial_2_Socket::operator bool() const +{ + return true; +} +int Serial_2_Socket::available(){ + return _RXbufferSize; +} + + +size_t Serial_2_Socket::write(uint8_t c) +{ + if(!_web_socket) return 0; + write(&c,1); + return 1; +} + +size_t Serial_2_Socket::write(const uint8_t *buffer, size_t size) +{ + if((buffer == NULL) ||(!_web_socket)) { + if(buffer == NULL){ + log_i("[SOCKET]No buffer"); + } + if(!_web_socket){ + log_i("[SOCKET]No socket"); + } + return 0; + } +#if defined(ENABLE_SERIAL2SOCKET_OUT) + if (_TXbufferSize==0)_lastflush = millis(); + //send full line + if (_TXbufferSize + size > TXBUFFERSIZE) flush(); + //need periodic check to force to flush in case of no end + for (int i = 0; i < size;i++){ + _TXbuffer[_TXbufferSize] = buffer[i]; + _TXbufferSize++; + } + log_i("[SOCKET]buffer size %d",_TXbufferSize); + handle_flush(); +#endif + return size; +} + +int Serial_2_Socket::peek(void){ + if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; + else return -1; +} + +bool Serial_2_Socket::push (const char * data){ +#if defined(ENABLE_SERIAL2SOCKET_IN) + int data_size = strlen(data); + if ((data_size + _RXbufferSize) <= RXBUFFERSIZE){ + int current = _RXbufferpos + _RXbufferSize; + if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE; + for (int i = 0; i < data_size; i++){ + if (current > (RXBUFFERSIZE-1)) current = 0; + _RXbuffer[current] = data[i]; + current ++; + } + _RXbufferSize+=strlen(data); + return true; + } + return false; +#else + return true; +#endif +} + +int Serial_2_Socket::read(void){ + if (_RXbufferSize > 0) { + int v = _RXbuffer[_RXbufferpos]; + _RXbufferpos++; + if (_RXbufferpos > (RXBUFFERSIZE-1))_RXbufferpos = 0; + _RXbufferSize--; + return v; + } else return -1; +} + +void Serial_2_Socket::handle_flush() { + if (_TXbufferSize > 0) { + if ((_TXbufferSize>=TXBUFFERSIZE) || ((millis()- _lastflush) > FLUSHTIMEOUT)) { + log_i("[SOCKET]need flush, buffer size %d",_TXbufferSize); + flush(); + } + } +} +void Serial_2_Socket::flush(void){ + if (_TXbufferSize > 0){ + //if ((((AsyncWebSocket *)_web_socket)->count() > 0) && (((AsyncWebSocket *)_web_socket)->availableForWriteAll())) { + log_i("[SOCKET]flush data, buffer size %d",_TXbufferSize); + ((WebSocketsServer *)_web_socket)->broadcastBIN(_TXbuffer,_TXbufferSize); + // } else { + // log_i("[SOCKET]Cannot flush, buffer size %d",_TXbufferSize); + // } + //refresh timout + _lastflush = millis(); + //reset buffer + _TXbufferSize = 0; + } +} + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/serial2socket.h b/Grbl_Esp32-master/Grbl_Esp32/serial2socket.h new file mode 100644 index 0000000..30a2cb0 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/serial2socket.h @@ -0,0 +1,81 @@ +/* + serial2socket.h - serial 2 socket functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _SERIAL_2_SOCKET_H_ +#define _SERIAL_2_SOCKET_H_ + +#include "Print.h" +#define TXBUFFERSIZE 1200 +#define RXBUFFERSIZE 128 +#define FLUSHTIMEOUT 500 +class Serial_2_Socket: public Print{ + public: + Serial_2_Socket(); + ~Serial_2_Socket(); + size_t write(uint8_t c); + size_t write(const uint8_t *buffer, size_t size); + + inline size_t write(const char * s) + { + return write((uint8_t*) s, strlen(s)); + } + inline size_t write(unsigned long n) + { + return write((uint8_t) n); + } + inline size_t write(long n) + { + return write((uint8_t) n); + } + inline size_t write(unsigned int n) + { + return write((uint8_t) n); + } + inline size_t write(int n) + { + return write((uint8_t) n); + } + long baudRate(); + void begin(long speed); + void end(); + int available(); + int peek(void); + int read(void); + bool push (const char * data); + void flush(void); + void handle_flush(); + operator bool() const; + bool attachWS(void * web_socket); + bool detachWS(); + private: + uint32_t _lastflush; + void * _web_socket; + uint8_t _TXbuffer[TXBUFFERSIZE]; + uint16_t _TXbufferSize; + uint8_t _RXbuffer[RXBUFFERSIZE]; + uint16_t _RXbufferSize; + uint16_t _RXbufferpos; +}; + + +extern Serial_2_Socket Serial2Socket; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/servo_axis.cpp b/Grbl_Esp32-master/Grbl_Esp32/servo_axis.cpp new file mode 100644 index 0000000..3759180 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/servo_axis.cpp @@ -0,0 +1,362 @@ +/* + servo_axis.cpp + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring. This file was intended for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + See servo_axis.h for more details + +*/ + +#include "grbl.h" + +#ifdef USE_SERVO_AXES + +static TaskHandle_t servosSyncTaskHandle = 0; + +#ifdef SERVO_X_PIN + ServoAxis X_Servo_Axis(X_AXIS, SERVO_X_PIN, SERVO_X_CHANNEL_NUM); +#endif +#ifdef SERVO_Y_PIN + ServoAxis Y_Servo_Axis(Y_AXIS, SERVO_Y_PIN, SERVO_Y_CHANNEL_NUM); +#endif +#ifdef SERVO_Z_PIN + ServoAxis Z_Servo_Axis(Z_AXIS, SERVO_Z_PIN, SERVO_Z_CHANNEL_NUM); +#endif + +#ifdef SERVO_A_PIN + ServoAxis A_Servo_Axis(A_AXIS, SERVO_A_PIN, SERVO_A_CHANNEL_NUM); +#endif +#ifdef SERVO_B_PIN + ServoAxis B_Servo_Axis(B_AXIS, SERVO_B_PIN, SERVO_B_CHANNEL_NUM); +#endif +#ifdef SERVO_C_PIN + ServoAxis C_Servo_Axis(C_AXIS, SERVO_C_PIN, SERVO_C_CHANNEL_NUM); +#endif + +void init_servos() +{ + //grbl_send(CLIENT_SERIAL, "[MSG: Init Servos]\r\n"); + #ifdef SERVO_X_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:X Servo range %4.3f to %4.3f]\r\n", SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX); + X_Servo_Axis.init(); + X_Servo_Axis.set_range(SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX); + X_Servo_Axis.set_homing_type(SERVO_HOMING_OFF); + X_Servo_Axis.set_disable_on_alarm(false); + X_Servo_Axis.set_disable_with_steppers(false); + #endif + #ifdef SERVO_Y_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:Y Servo range %4.3f to %4.3f]\r\n", SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX); + Y_Servo_Axis.init(); + Y_Servo_Axis.set_range(SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX); + #endif + #ifdef SERVO_Z_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:Z Servo range %4.3f to %4.3f]\r\n", SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX); + Z_Servo_Axis.init(); + Z_Servo_Axis.set_range(SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX); + #ifdef SERVO_Z_HOMING_TYPE + Z_Servo_Axis.set_homing_type(SERVO_Z_HOMING_TYPE); + #endif + #ifdef SERVO_Z_HOME_POS + Z_Servo_Axis.set_homing_position(SERVO_Z_HOME_POS); + #endif + #ifdef SERVO_Z_MPOS // value should be true or false + Z_Servo_Axis.set_use_mpos(SERVO_Z_MPOS); + #endif + #endif + + #ifdef SERVO_A_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:A Servo range %4.3f to %4.3f]\r\n", SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX); + A_Servo_Axis.init(); + A_Servo_Axis.set_range(SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX); + A_Servo_Axis.set_homing_type(SERVO_HOMING_OFF); + A_Servo_Axis.set_disable_on_alarm(false); + A_Servo_Axis.set_disable_with_steppers(false); + #endif + #ifdef SERVO_B_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:B Servo range %4.3f to %4.3f]\r\n", SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX); + B_Servo_Axis.init(); + B_Servo_Axis.set_range(SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX); + #endif + #ifdef SERVO_C_PIN + grbl_sendf(CLIENT_SERIAL, "[MSG:C Servo range %4.3f to %4.3f]\r\n", SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX); + C_Servo_Axis.init(); + C_Servo_Axis.set_range(SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX); + //C_Servo_Axis.set_homing_type(SERVO_HOMING_TARGET); + //C_Servo_Axis.set_homing_position(SERVO_C_RANGE_MAX); + #ifdef SERVO_C_HOMING_TYPE + C_Servo_Axis.set_homing_type(SERVO_C_HOMING_TYPE); + #endif + #ifdef SERVO_C_HOME_POS + C_Servo_Axis.set_homing_position(SERVO_C_HOME_POS); + #endif + #ifdef SERVO_C_MPOS // value should be true or false + C_Servo_Axis.set_use_mpos(SERVO_C_MPOS); + #endif + #endif + + + // setup a task that will calculate the determine and set the servo positions + xTaskCreatePinnedToCore( servosSyncTask, // task + "servosSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &servosSyncTaskHandle, + 0 // core + ); +} + + +// this is the task +void servosSyncTask(void *pvParameters) +{ + TickType_t xLastWakeTime; + const TickType_t xServoFrequency = SERVO_TIMER_INT_FREQ; // in ticks (typically ms) + + + + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while(true) { // don't ever return from this or the task dies + #ifdef SERVO_X_PIN + X_Servo_Axis.set_location(); + #endif + #ifdef SERVO_Y_PIN + Y_Servo_Axis.set_location(); + #endif + #ifdef SERVO_Z_PIN + Z_Servo_Axis.set_location(); + #endif + #ifdef SERVO_A_PIN + A_Servo_Axis.set_location(); + #endif + #ifdef SERVO_B_PIN + B_Servo_Axis.set_location(); + #endif + #ifdef SERVO_C_PIN + C_Servo_Axis.set_location(); + #endif + + vTaskDelayUntil(&xLastWakeTime, xServoFrequency); + } +} + +// =============================== Class Stuff ================================= // + +ServoAxis::ServoAxis(uint8_t axis, uint8_t pin_num, uint8_t channel_num) // constructor +{ + _axis = axis; + _pin_num = pin_num; + _channel_num = channel_num; + _showError = true; // this will be used to show calibration error only once + _use_mpos = true; // default is to use the machine position rather than work position +} + +void ServoAxis::init() +{ + _cal_is_valid(); + ledcSetup(_channel_num, _pwm_freq, _pwm_resolution_bits); + ledcAttachPin(_pin_num, _channel_num); + disable(); +} + +void ServoAxis::set_location() +{ + // These are the pulse lengths for the minimum and maximum positions + // Note: Some machines will have the physical max/min inverted with pulse length max/min due to invert setting $3=... + float servo_pulse_min, servo_pulse_max; + float min_pulse_cal, max_pulse_cal; // calibration values in percent 110% = 1.1 + uint32_t servo_pulse_len; + float servo_pos, mpos, offset; + + + + // skip location if we are in alarm mode + if (_disable_on_alarm && (sys.state == STATE_ALARM)) { + disable(); + return; + } + + // track the disable status of the steppers if desired. + if (_disable_with_steppers && get_stepper_disable()) { + disable(); + return; + } + + + if ( (_homing_type == SERVO_HOMING_TARGET) && (sys.state == STATE_HOMING) ) { + servo_pos = _homing_position; // go to servos home position + } + else { + mpos = system_convert_axis_steps_to_mpos(sys_position, _axis); // get the axis machine position in mm + if (_use_mpos) { + servo_pos = mpos; + } + else { + offset = gc_state.coord_system[_axis] + gc_state.coord_offset[_axis]; // get the current axis work offset + servo_pos = mpos - offset; // determine the current work position + + } + } + + + // 1. Get the pulse ranges of the servos + // 2. Invert if selected in the settings + // 3. Get the calibration values from the settings + // 4. Adjust the calibration offset direction of the cal based on the direction + // 5. Apply the calibrarion + + + servo_pulse_min = SERVO_MIN_PULSE; + servo_pulse_max = SERVO_MAX_PULSE; + + if (bit_istrue(settings.dir_invert_mask,bit(_axis))) { // this allows the user to change the direction via settings + swap(servo_pulse_min, servo_pulse_max); + } + + // get the calibration values + if (_cal_is_valid()) { // if calibration settings are OK then apply them + // apply a calibration + // the cals apply differently if the direction is reverse (i.e. longer pulse is lower position) + if (bit_isfalse(settings.dir_invert_mask,bit(_axis))) { // normal direction + min_pulse_cal = 2.0 - (settings.steps_per_mm[_axis] / 100.0); + max_pulse_cal = (settings.max_travel[_axis] / -100.0); + } + else { // inverted direction + min_pulse_cal = (settings.steps_per_mm[_axis] / 100.0); + max_pulse_cal = 2.0 - (settings.max_travel[_axis] / -100.0); + } + } + else { // settings are not valid so don't apply any calibration + min_pulse_cal = 1.0; + max_pulse_cal = 1.0; + } + + // apply the calibrations + servo_pulse_min *= min_pulse_cal; + servo_pulse_max *= max_pulse_cal; + + // determine the pulse length + servo_pulse_len = (uint32_t)mapConstrain(servo_pos, _position_min, _position_max, servo_pulse_min, servo_pulse_max ); + _write_pwm(servo_pulse_len); +} + +void ServoAxis::_write_pwm(uint32_t duty) +{ + if (ledcRead(_channel_num) != duty) { // only write if it is changing + ledcWrite(_channel_num, duty); + } +} + +// sets the PWM to zero. This allows most servos to be manually moved +void ServoAxis::disable() +{ + _write_pwm(0); +} + +// checks to see if calibration values are in an acceptable range +// vebose = true if you want an error sent to serial port +bool ServoAxis::_cal_is_valid() +{ + bool settingsOK = true; + + if ( (settings.steps_per_mm[_axis] < SERVO_CAL_MIN) || (settings.steps_per_mm[_axis] > SERVO_CAL_MAX) ) { + if (_showError) { + grbl_sendf(CLIENT_SERIAL, "[MSG:Servo calibration ($10%d) value error. Reset to 100]\r\n", _axis); + settings.steps_per_mm[_axis] = 100; + write_global_settings(); + } + settingsOK = false; + } + + // Note: Max travel is set positive via $$, but stored as a negative number + if ( (settings.max_travel[_axis] < -SERVO_CAL_MAX) || (settings.max_travel[_axis] > -SERVO_CAL_MIN) ) { + if (_showError) { + grbl_sendf(CLIENT_SERIAL, "[MSG:Servo calibration ($13%d) value error. Reset to 100]\r\n", _axis); + settings.max_travel[_axis] = -100; + write_global_settings(); + } + settingsOK = false; + } + + _showError = false; // to show error once + + if (! settingsOK) { + write_global_settings(); // they were changed so write them to + } + + return settingsOK; +} + +/* + Use this to set the max and min position in mm of the servo + This is used when mapping pulse length to the position +*/ +void ServoAxis::set_range(float min, float max) { + if (min < max) { + _position_min = min; + _position_max = max; + } + else { + grbl_send(CLIENT_SERIAL, "[MSG:Error setting range. Min not smaller than max]\r\n"); + } +} + +/* + Sets the mode the servo will be in during homing + See servo_axis.h for SERVO_HOMING_xxxxx types +*/ +void ServoAxis::set_homing_type(uint8_t homing_type) +{ + if (homing_type <= SERVO_HOMING_TARGET) + _homing_type = homing_type; +} + +/* + Use this to set the homing position the servo will be commanded to go if + the current homing mode is SERVO_HOMING_TARGET +*/ +void ServoAxis::set_homing_position(float homing_position) +{ + _homing_position = homing_position; +} + +/* + Use this to set the disable on alarm feature. If true, then hobby servo PWM + will be disable in Grbl alarm mode (like before homing). Typical hobby servo + can be moved by hand in this mode +*/ +void ServoAxis::set_disable_on_alarm (bool disable_on_alarm) +{ + _disable_on_alarm = disable_on_alarm; +} + +void ServoAxis::set_disable_with_steppers(bool disable_with_steppers) { + _disable_with_steppers = disable_with_steppers; +} + +/* + If true, servo position will alway be calculated in machine position + Offsets will not be applied +*/ +void ServoAxis::set_use_mpos(bool use_mpos) { + _use_mpos = use_mpos; +} + + + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/servo_axis.h b/Grbl_Esp32-master/Grbl_Esp32/servo_axis.h new file mode 100644 index 0000000..802ab93 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/servo_axis.h @@ -0,0 +1,133 @@ +/* + solenoid_pen.h + Part of Grbl_ESP32 + + copyright (c) 2019 - Bart Dring. This file was intended for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + Servo Axis Class + + The Servo axis feature allows you to use a hobby servo on any axis. + This is done using a repeating RTOS task. Grbl continues to calculate + the position of the axis in real time. The task looks at the current position of + the axis and calculates the required PWM value to go to that location. You define the travel + of the servo in millimeters. + + Grbl still uses the acceleration and speed values you have in the settings, so it + will coordinate servo axes with stepper motor axes. This assumes these values are within the + capabilities of the servo + + Usage + + 1. In config.h un-comment #define USE_SERVO_AXES + + 2. In a cpu_map.h section, define servo pins and PWM channels like this .... + #define SERVO_Y_PIN GPIO_NUM_14 + #define SERVO_Y_CHANNEL_NUM 6 + + undefine any step and direction pins associated with that axis + + 3. In servo_axis.cpp init_servos() function, configure servos like this .... + X_Servo_Axis.set_range(0.0, 20.0); // millimeter + X_Servo_Axis.set_homing_type(SERVO_HOMING_OFF); + X_Servo_Axis.set_disable_on_alarm(true); + + + The positions can be calibrated using the settings. $10x (resolution) settings adjust the minimum + position and $13x (max travel) settings adjust the maximum position. If the servo is traveling + backwards from what you want, you can use the $3 direction setting to compensate. + +*/ + +#ifndef servo_axis_h + #define servo_axis_h + + + +// this is the pulse range of a the servo. Typical servos are 0.001 to 0.002 seconds +// some servos have a wider range. You can adjust this here or in the calibration feature +#define SERVO_MIN_PULSE_SEC 0.001 // min pulse in seconds +#define SERVO_MAX_PULSE_SEC 0.002 // max pulse in seconds + +#define SERVO_POSITION_MIN_DEFAULT 0.0 // mm +#define SERVO_POSITION_MAX_DEFAULT 20.0 // mm + +#define SERVO_PULSE_FREQ 50 // 50Hz ...This is a standard analog servo value. Digital ones can repeat faster + +#define SERVO_PULSE_RES_BITS 16 // bits of resolution of PWM (16 is max) +#define SERVO_PULSE_RES_COUNT 65535 // see above TODO...do the math here 2^SERVO_PULSE_RES_BITS + +#define SERVO_TIME_PER_BIT ((1.0 / (float)SERVO_PULSE_FREQ) / ((float)SERVO_PULSE_RES_COUNT) ) // seconds + +#define SERVO_MIN_PULSE (uint16_t)(SERVO_MIN_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts +#define SERVO_MAX_PULSE (uint16_t)(SERVO_MAX_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts + +#define SERVO_PULSE_RANGE (SERVO_MAX_PULSE-SERVO_MIN_PULSE) + +#define SERVO_CAL_MIN 20.0 // Percent: the minimum allowable calibration value +#define SERVO_CAL_MAX 180.0 // Percent: the maximum allowable calibration value + +#define SERVO_TIMER_INT_FREQ 20 // Hz This is the task frequency + +#define SERVO_HOMING_OFF 0 // servo is off during homing +#define SERVO_HOMING_TARGET 1 // servo is send to a location during homing + +extern float my_location; + +void init_servos(); +void servosSyncTask(void *pvParameters); + + +class ServoAxis{ + public: + ServoAxis(uint8_t axis, uint8_t pin_num, uint8_t channel_num); // constructor + void init(); + void set_location(); + void disable(); // sets PWM to 0% duty cycle. Most servos can be manually moved in this state + void set_range(float min, float max); + void set_homing_type(uint8_t homing_type); + void set_homing_position(float homing_position); + void set_disable_on_alarm (bool disable_on_alarm); + void set_disable_with_steppers(bool disable_with_steppers); + void set_use_mpos(bool use_mpos); + + private: + int _axis; // these should be assign in constructor using Grbl X_AXIS type values + int _pin_num; // The GPIO pin being used + int _channel_num; // The PWM channel + bool _showError; + + uint32_t _pwm_freq = SERVO_PULSE_FREQ; + uint32_t _pwm_resolution_bits = SERVO_PULSE_RES_BITS; + float _pulse_min = SERVO_MIN_PULSE; // in pwm counts + float _pulse_max = SERVO_MAX_PULSE; // in pwm counts + float _position_min = SERVO_POSITION_MIN_DEFAULT; // position in millimeters + float _position_max = SERVO_POSITION_MAX_DEFAULT; // position in millimeters + + + uint8_t _homing_type = SERVO_HOMING_OFF; + float _homing_position = SERVO_POSITION_MAX_DEFAULT; + bool _disable_on_alarm = true; + bool _disable_with_steppers = false; + bool _use_mpos = true; + + bool _validate_cal_settings(); + void _write_pwm(uint32_t duty); + bool _cal_is_valid(); // checks to see if calibration values are in acceptable range + +}; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/servo_pen.cpp b/Grbl_Esp32-master/Grbl_Esp32/servo_pen.cpp new file mode 100644 index 0000000..cc45dae --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/servo_pen.cpp @@ -0,0 +1,176 @@ +/* + servo_pen.cpp + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + +*/ +#include "grbl.h" + +#ifdef USE_PEN_SERVO + +static TaskHandle_t servoSyncTaskHandle = 0; + +// used to delay turn on +bool servo_pen_enable = false; + +void servo_init() +{ + grbl_send(CLIENT_SERIAL, "[MSG:Servo Pen Mode]\r\n"); // startup message + //validate_servo_settings(true); // display any calibration errors + + // Debug stuff + //grbl_sendf(CLIENT_SERIAL, "[MSG:Servo max,min pulse times %.4f sec,%.4f sec]\r\n", SERVO_MAX_PULSE_SEC, SERVO_MIN_PULSE_SEC); + //grbl_sendf(CLIENT_SERIAL, "[MSG:Servo max,min pulse counts %d,%d]\r\n", SERVO_MAX_PULSE, SERVO_MIN_PULSE); + validate_servo_settings(true); // will print errors + // debug stuff + + servo_pen_enable = false; // start delay has not completed yet. + + // setup PWM channel + ledcSetup(SERVO_PEN_CHANNEL_NUM, SERVO_PULSE_FREQ, SERVO_PULSE_RES_BITS); + ledcAttachPin(SERVO_PEN_PIN, SERVO_PEN_CHANNEL_NUM); + + servo_disable(); // start it it off + + // setup a task that will calculate the determine and set the servo position + xTaskCreatePinnedToCore( servoSyncTask, // task + "servoSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &servoSyncTaskHandle, + 0 // core + ); +} + +// turn off the PWM (0 duty) to prevent servo jitter when not in use. +void servo_disable() +{ + ledcWrite(SERVO_PEN_CHANNEL_NUM, 0); +} + +// Grbl settings are used to calibrate the servo positions +// They work on a percentage, so a value of 100 (100%) applies no calibration +// Values outside a reasonable range can cause errors, so this function checks +// that they are within a reasonable range +bool validate_servo_settings(bool verbose) // make sure the settings are reasonable..otherwise reset the settings to default +{ + bool settingsOK = true; + + if ( (settings.steps_per_mm[Z_AXIS] < SERVO_CAL_MIN) || (settings.steps_per_mm[Z_AXIS] > SERVO_CAL_MAX) ) { + if (verbose) { + grbl_sendf(CLIENT_SERIAL, "[MSG:Servo cal ($102) Error: %4.4f s/b between %.2f and %.2f]\r\n", settings.steps_per_mm[Z_AXIS], SERVO_CAL_MIN, SERVO_CAL_MAX); + } + + settingsOK = false; + } + + // Note: Max travel is set positive via $$, but stored as a negative number + if ( (settings.max_travel[Z_AXIS] < -SERVO_CAL_MAX) || (settings.max_travel[Z_AXIS] > -SERVO_CAL_MIN) ) { + if (verbose) { + grbl_sendf(CLIENT_SERIAL, "[MSG:Servo cal ($132) Error: %4.4f s/b between %.2f and %.2f]\r\n", -settings.max_travel[Z_AXIS], SERVO_CAL_MIN, SERVO_CAL_MAX); + } + + settingsOK = false; + } + + return settingsOK; +} + +// this is the task +void servoSyncTask(void *pvParameters) +{ + //int32_t current_position[N_AXIS]; // copy of current location + //float m_pos[N_AXIS]; // machine position in mm + TickType_t xLastWakeTime; + const TickType_t xServoFrequency = SERVO_TIMER_INT_FREQ; // in ticks (typically ms) + uint16_t servo_delay_counter = 0; + + float mpos_z, wpos_z; + float z_offset; + + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while(true) { // don't ever return from this or the task dies + if (sys.state != STATE_ALARM) { // don't move until alarm is cleared...typically homing + if (!servo_pen_enable ) { + servo_delay_counter++; + servo_pen_enable = (servo_delay_counter > SERVO_TURNON_DELAY); + } else { + mpos_z = system_convert_axis_steps_to_mpos(sys_position, Z_AXIS); // get the machine Z in mm + z_offset = gc_state.coord_system[Z_AXIS]+gc_state.coord_offset[Z_AXIS]; // get the current z work offset + wpos_z = mpos_z - z_offset; // determine the current work Z + + calc_pen_servo(wpos_z); // calculate kinematics and move the servos + } + } + vTaskDelayUntil(&xLastWakeTime, xServoFrequency); + } +} + +// calculate and set the PWM value for the servo +void calc_pen_servo(float penZ) +{ + uint32_t servo_pen_pulse_len; + float servo_pen_pulse_min, servo_pen_pulse_max; + + if (!servo_pen_enable) { // only proceed if startup delay as expired + return; + } + + if (validate_servo_settings(false)) { // if calibration settings are OK then apply them + if (bit_istrue(settings.dir_invert_mask,bit(Z_AXIS))) { // this allows the user to change the direction via settings + // Apply a calibration to the minimum position + servo_pen_pulse_max = SERVO_MIN_PULSE * (settings.steps_per_mm[Z_AXIS] / 100.0); + // Apply a calibration to the maximum position + servo_pen_pulse_min = SERVO_MAX_PULSE * (settings.max_travel[Z_AXIS] / -100.0); + } + else { + // Apply a calibration to the minimum position + servo_pen_pulse_min = SERVO_MIN_PULSE * (settings.steps_per_mm[Z_AXIS] / 100.0); + // Apply a calibration to the maximum position + servo_pen_pulse_max = SERVO_MAX_PULSE * (settings.max_travel[Z_AXIS] / -100.0); + } + + } else { // use the defaults + if (bit_istrue(settings.dir_invert_mask,bit(Z_AXIS))) { // this allows the user to change the direction via settings + servo_pen_pulse_min = SERVO_MAX_PULSE; + servo_pen_pulse_max = SERVO_MIN_PULSE; + } + else { + servo_pen_pulse_min = SERVO_MIN_PULSE; + servo_pen_pulse_max = SERVO_MAX_PULSE; + } + + } + + // determine the pulse length + servo_pen_pulse_len = (uint32_t)mapConstrain(penZ, SERVO_PEN_RANGE_MIN_MM, SERVO_PEN_RANGE_MAX_MM, servo_pen_pulse_min, servo_pen_pulse_max ); + + // skip setting value if it is unchanged + if (ledcRead(SERVO_PEN_CHANNEL_NUM) == servo_pen_pulse_len) + return; + + // update the PWM value + // ledcWrite appears to have issues with interrupts, so make this a critical section + portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&myMutex); + ledcWrite(SERVO_PEN_CHANNEL_NUM, servo_pen_pulse_len); + portEXIT_CRITICAL(&myMutex); +} + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/servo_pen.h b/Grbl_Esp32-master/Grbl_Esp32/servo_pen.h new file mode 100644 index 0000000..ab58bbc --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/servo_pen.h @@ -0,0 +1,76 @@ +/* + servo.h + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + To use this, uncomment #define USE_PEN_SERVO in config.h + + That should be the only change you need at the top level + Everything occurs as a low priority task that syncs the servo with the + current machine position. + +*/ + +// ==== Begin: Things you are likely to change ==================== +//#define SERVO_PEN_PIN GPIO_NUM_27 // FYI...you can disable the Z stepper pins (step & dir) + +// the pulse lengths for the min and max travel .. (Note: Servo brands vary) +// If the servo goes backward from what you want, flip the values +// Note: this is not necessarily the servos limits (just the travel you want) +#define SERVO_MIN_PULSE_SEC 0.001 // min pulse in seconds +#define SERVO_MAX_PULSE_SEC 0.002 // max pulse in seconds + +// Pulse repeat rate (PWM Frequency) +#define SERVO_PULSE_FREQ 50 // 50Hz ...This is a standard analog servo value. Digital ones can repeat faster + +// the range of the servo is constrained +// values above or below these will be limited to the min or max +#define SERVO_PEN_RANGE_MIN_MM 0.0 // the minimum z position in mm +#define SERVO_PEN_RANGE_MAX_MM 5.0 // the minimum z position in mm +// ==== End: Things you are likely to change ======================= + +// Begin: Advanced settings + +#define SERVO_TIMER_NUM 1 +#define SERVO_TIMER_INT_FREQ 20 // Hz This is the task frequency +#define SERVO_PEN_CHANNEL_NUM 5 + +#define SERVO_PULSE_RES_BITS 16 // bits of resolution of PWM (16 is max) +#define SERVO_PULSE_RES_COUNT 65535 // see above TODO...do the math here 2^SERVO_PULSE_RES_BITS + +// A way to reduce the turn on current +#define SERVO_TURNON_DELAY SERVO_TIMER_INT_FREQ*3 // Wait this many task counts to turn on servo + +#define SERVO_TIME_PER_BIT ((1.0 / (float)SERVO_PULSE_FREQ) / ((float)SERVO_PULSE_RES_COUNT) ) // seconds + +#define SERVO_MIN_PULSE (uint16_t)(SERVO_MIN_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts +#define SERVO_MAX_PULSE (uint16_t)(SERVO_MAX_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts + +#define SERVO_CAL_MIN 20.0 // Percent: the minimum allowable calibration value +#define SERVO_CAL_MAX 180.0 // Percent: the maximum allowable calibration value + +#ifndef servo_h +#define servo_h + +void servo_init(); +void servo_disable(); +bool validate_servo_settings(bool verbose); +void servoSyncTask(void *pvParameters); +void calc_pen_servo(float penZ); + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/settings.cpp b/Grbl_Esp32-master/Grbl_Esp32/settings.cpp new file mode 100644 index 0000000..5be2f57 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/settings.cpp @@ -0,0 +1,452 @@ +/* + settings.c - eeprom configuration handling + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +settings_t settings; + +// Method to store startup lines into EEPROM +void settings_store_startup_line(uint8_t n, char *line) +{ + #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE + protocol_buffer_synchronize(); // A startup line may contain a motion and be executing. + #endif + uint32_t addr = n*(LINE_BUFFER_SIZE+1)+EEPROM_ADDR_STARTUP_BLOCK; + memcpy_to_eeprom_with_checksum(addr,(char*)line, LINE_BUFFER_SIZE); +} + +void settings_init() +{ + EEPROM.begin(EEPROM_SIZE); + + if(!read_global_settings()) { + report_status_message(STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); + settings_restore(SETTINGS_RESTORE_ALL); // Force restore all EEPROM data. + report_grbl_settings(CLIENT_SERIAL); // only the serial could be working at this point + } +} + +// Method to restore EEPROM-saved Grbl global settings back to defaults. +void settings_restore(uint8_t restore_flag) { +#if defined(ENABLE_BLUETOOTH) || defined(ENABLE_WIFI) + if (restore_flag & SETTINGS_RESTORE_WIFI_SETTINGS){ +#ifdef ENABLE_WIFI + wifi_config.reset_settings(); +#endif +#ifdef ENABLE_BLUETOOTH + bt_config.reset_settings(); +#endif + } +#endif + if (restore_flag & SETTINGS_RESTORE_DEFAULTS) { + settings.pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS; + settings.stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME; + settings.step_invert_mask = DEFAULT_STEPPING_INVERT_MASK; + settings.dir_invert_mask = DEFAULT_DIRECTION_INVERT_MASK; + settings.status_report_mask = DEFAULT_STATUS_REPORT_MASK; + settings.junction_deviation = DEFAULT_JUNCTION_DEVIATION; + + settings.arc_tolerance = DEFAULT_ARC_TOLERANCE; + + settings.spindle_pwm_freq = DEFAULT_SPINDLE_FREQ; // $33 Hz (extended set) + settings.spindle_pwm_off_value = DEFAULT_SPINDLE_OFF_VALUE; // $34 Percent (extended set) + settings.spindle_pwm_min_value = DEFAULT_SPINDLE_MIN_VALUE; // $35 Percent (extended set) + settings.spindle_pwm_max_value = DEFAULT_SPINDLE_MAX_VALUE; // $36 Percent (extended set) + + settings.rpm_max = DEFAULT_SPINDLE_RPM_MAX; + settings.rpm_min = DEFAULT_SPINDLE_RPM_MIN; + + settings.homing_dir_mask = DEFAULT_HOMING_DIR_MASK; + settings.homing_feed_rate = DEFAULT_HOMING_FEED_RATE; + settings.homing_seek_rate = DEFAULT_HOMING_SEEK_RATE; + settings.homing_debounce_delay = DEFAULT_HOMING_DEBOUNCE_DELAY; + settings.homing_pulloff = DEFAULT_HOMING_PULLOFF; + + settings.flags = 0; + if (DEFAULT_REPORT_INCHES) { settings.flags |= BITFLAG_REPORT_INCHES; } + if (DEFAULT_LASER_MODE) { settings.flags |= BITFLAG_LASER_MODE; } + if (DEFAULT_INVERT_ST_ENABLE) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } + if (DEFAULT_HARD_LIMIT_ENABLE) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } + if (DEFAULT_HOMING_ENABLE) { settings.flags |= BITFLAG_HOMING_ENABLE; } + if (DEFAULT_SOFT_LIMIT_ENABLE) { settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; } + if (DEFAULT_INVERT_LIMIT_PINS) { settings.flags |= BITFLAG_INVERT_LIMIT_PINS; } + if (DEFAULT_INVERT_PROBE_PIN) { settings.flags |= BITFLAG_INVERT_PROBE_PIN; } + + settings.steps_per_mm[X_AXIS] = DEFAULT_X_STEPS_PER_MM; + settings.steps_per_mm[Y_AXIS] = DEFAULT_Y_STEPS_PER_MM; + settings.steps_per_mm[Z_AXIS] = DEFAULT_Z_STEPS_PER_MM; + + settings.max_rate[X_AXIS] = DEFAULT_X_MAX_RATE; + settings.max_rate[Y_AXIS] = DEFAULT_Y_MAX_RATE; + settings.max_rate[Z_AXIS] = DEFAULT_Z_MAX_RATE; + + settings.acceleration[X_AXIS] = DEFAULT_X_ACCELERATION; + settings.acceleration[Y_AXIS] = DEFAULT_Y_ACCELERATION; + settings.acceleration[Z_AXIS] = DEFAULT_Z_ACCELERATION; + + settings.max_travel[X_AXIS] = (-DEFAULT_X_MAX_TRAVEL); + settings.max_travel[Y_AXIS] = (-DEFAULT_Y_MAX_TRAVEL); + settings.max_travel[Z_AXIS] = (-DEFAULT_Z_MAX_TRAVEL); + + settings.current[X_AXIS] = DEFAULT_X_CURRENT; + settings.current[Y_AXIS] = DEFAULT_Y_CURRENT; + settings.current[Z_AXIS] = DEFAULT_Z_CURRENT; + + settings.hold_current[X_AXIS] = DEFAULT_X_HOLD_CURRENT; + settings.hold_current[Y_AXIS] = DEFAULT_Y_HOLD_CURRENT; + settings.hold_current[Z_AXIS] = DEFAULT_Z_HOLD_CURRENT; + + settings.microsteps[X_AXIS] = DEFAULT_X_MICROSTEPS; + settings.microsteps[Y_AXIS] = DEFAULT_Y_MICROSTEPS; + settings.microsteps[Z_AXIS] = DEFAULT_Z_MICROSTEPS; + + settings.stallguard[X_AXIS] = DEFAULT_X_STALLGUARD; + settings.stallguard[Y_AXIS] = DEFAULT_Y_STALLGUARD; + settings.stallguard[Z_AXIS] = DEFAULT_Z_STALLGUARD; + + #if (N_AXIS > A_AXIS) + settings.steps_per_mm[A_AXIS] = DEFAULT_A_STEPS_PER_MM; + settings.max_rate[A_AXIS] = DEFAULT_A_MAX_RATE; + settings.acceleration[A_AXIS] = DEFAULT_A_ACCELERATION; + settings.max_travel[A_AXIS] = (-DEFAULT_A_MAX_TRAVEL); + settings.current[A_AXIS] = DEFAULT_A_CURRENT; + settings.hold_current[A_AXIS] = DEFAULT_A_HOLD_CURRENT; + settings.microsteps[A_AXIS] = DEFAULT_A_MICROSTEPS; + settings.stallguard[A_AXIS] = DEFAULT_Z_STALLGUARD; + #endif + + #if (N_AXIS > B_AXIS) + settings.steps_per_mm[B_AXIS] = DEFAULT_B_STEPS_PER_MM; + settings.max_rate[B_AXIS] = DEFAULT_B_MAX_RATE; + settings.acceleration[B_AXIS] = DEFAULT_B_ACCELERATION; + settings.max_travel[B_AXIS] = (-DEFAULT_B_MAX_TRAVEL); + settings.current[B_AXIS] = DEFAULT_B_CURRENT; + settings.hold_current[B_AXIS] = DEFAULT_B_HOLD_CURRENT; + settings.microsteps[B_AXIS] = DEFAULT_B_MICROSTEPS; + settings.stallguard[B_AXIS] = DEFAULT_Z_STALLGUARD; + #endif + + #if (N_AXIS > C_AXIS) + settings.steps_per_mm[C_AXIS] = DEFAULT_C_STEPS_PER_MM; + settings.max_rate[C_AXIS] = DEFAULT_C_MAX_RATE; + settings.acceleration[C_AXIS] = DEFAULT_C_ACCELERATION; + settings.max_travel[C_AXIS] = (-DEFAULT_C_MAX_TRAVEL); + settings.current[C_AXIS] = DEFAULT_C_CURRENT; + settings.hold_current[C_AXIS] = DEFAULT_C_HOLD_CURRENT; + settings.microsteps[C_AXIS] = DEFAULT_C_MICROSTEPS; + settings.stallguard[C_AXIS] = DEFAULT_Z_STALLGUARD; + #endif + + // TODO figure out a clean way to add actual default values + for (uint8_t index = 0; index 0 + EEPROM.write(EEPROM_ADDR_STARTUP_BLOCK, 0); + EEPROM.write(EEPROM_ADDR_STARTUP_BLOCK+1, 0); // Checksum + EEPROM.commit(); + #endif + #if N_STARTUP_LINE > 1 + EEPROM.write(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+1), 0); + EEPROM.write(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+2), 0); // Checksum + EEPROM.commit(); + #endif + } + + if (restore_flag & SETTINGS_RESTORE_BUILD_INFO) { + EEPROM.write(EEPROM_ADDR_BUILD_INFO , 0); + EEPROM.write(EEPROM_ADDR_BUILD_INFO+1 , 0); // Checksum + EEPROM.commit(); + } + + +} + +// Reads Grbl global settings struct from EEPROM. +uint8_t read_global_settings() { + // Check version-byte of eeprom + uint8_t version = EEPROM.read(0); + if (version == SETTINGS_VERSION) { + // Read settings-record and check checksum + if (!(memcpy_from_eeprom_with_checksum((char*)&settings, EEPROM_ADDR_GLOBAL, sizeof(settings_t)))) { + return(false); + } + } else { + return(false); + } + return(true); +} + +// Method to store Grbl global settings struct and version number into EEPROM +// NOTE: This function can only be called in IDLE state. +void write_global_settings() +{ + EEPROM.write(0, SETTINGS_VERSION); + memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL, (char*)&settings, sizeof(settings_t)); + +} + +// Read selected coordinate data from EEPROM. Updates pointed coord_data value. +uint8_t settings_read_coord_data(uint8_t coord_select, float *coord_data) +{ + uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS; + if (!(memcpy_from_eeprom_with_checksum((char*)coord_data, addr, sizeof(float)*N_AXIS))) { + // Reset with default zero vector + clear_vector_float(coord_data); + settings_write_coord_data(coord_select,coord_data); + return(false); + } + return(true); +} + +// Method to store coord data parameters into EEPROM +void settings_write_coord_data(uint8_t coord_select, float *coord_data) +{ + #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE + protocol_buffer_synchronize(); + #endif + uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS; + memcpy_to_eeprom_with_checksum(addr,(char*)coord_data, sizeof(float)*N_AXIS); +} + +// Method to store build info into EEPROM +// NOTE: This function can only be called in IDLE state. +void settings_store_build_info(char *line) +{ + // Build info can only be stored when state is IDLE. + memcpy_to_eeprom_with_checksum(EEPROM_ADDR_BUILD_INFO,(char*)line, LINE_BUFFER_SIZE); +} + +// Reads startup line from EEPROM. Updated pointed line string data. +uint8_t settings_read_build_info(char *line) +{ + if (!(memcpy_from_eeprom_with_checksum((char*)line, EEPROM_ADDR_BUILD_INFO, LINE_BUFFER_SIZE))) { + // Reset line with default value + line[0] = 0; // Empty line + settings_store_build_info(line); + return(false); + } + return(true); +} + + + +// Reads startup line from EEPROM. Updated pointed line string data. +uint8_t settings_read_startup_line(uint8_t n, char *line) +{ + uint32_t addr = n*(LINE_BUFFER_SIZE+1)+EEPROM_ADDR_STARTUP_BLOCK; + if (!(memcpy_from_eeprom_with_checksum((char*)line, addr, LINE_BUFFER_SIZE))) { + // Reset line with default value + line[0] = 0; // Empty line + settings_store_startup_line(n, line); + return(false); + } + return(true); +} + +// A helper method to set settings from command line +uint8_t settings_store_global_setting(uint8_t parameter, float value) { + if (value < 0.0) { return(STATUS_NEGATIVE_VALUE); } + uint8_t int_value = trunc(value); // integer version + if (parameter >= AXIS_SETTINGS_START_VAL) { + // Store axis configuration. Axis numbering sequence set by AXIS_SETTING defines. + // NOTE: Ensure the setting index corresponds to the report.c settings printout. + parameter -= AXIS_SETTINGS_START_VAL; + uint8_t set_idx = 0; + while (set_idx < AXIS_N_SETTINGS) { + if (parameter < N_AXIS) { + // Valid axis setting found. + switch (set_idx) { + case 0: + #ifdef MAX_STEP_RATE_HZ + if (value*settings.max_rate[parameter] > (MAX_STEP_RATE_HZ*60.0)) { return(STATUS_MAX_STEP_RATE_EXCEEDED); } + #endif + settings.steps_per_mm[parameter] = value; + break; + case 1: + #ifdef MAX_STEP_RATE_HZ + if (value*settings.steps_per_mm[parameter] > (MAX_STEP_RATE_HZ*60.0)) { return(STATUS_MAX_STEP_RATE_EXCEEDED); } + #endif + settings.max_rate[parameter] = value; + break; + case 2: settings.acceleration[parameter] = value*60*60; break; // Convert to mm/min^2 for grbl internal use. + case 3: settings.max_travel[parameter] = -value; break; // Store as negative for grbl internal use. + case 4: // run current + settings.current[parameter] = value; + settings_spi_driver_init(); + break; + case 5: // hold current + settings.hold_current[parameter] = value; + settings_spi_driver_init(); + break; + case 6: // microstepping + settings.microsteps[parameter] = int_value; + settings_spi_driver_init(); + break; + case 7: // stallguard + settings.stallguard[parameter] = int_value; + settings_spi_driver_init(); + break; + } + break; // Exit while-loop after setting has been configured and proceed to the EEPROM write call. + } else { + set_idx++; + // If axis index greater than N_AXIS or setting index greater than number of axis settings, error out. + if ((parameter < AXIS_SETTINGS_INCREMENT) || (set_idx == AXIS_N_SETTINGS)) { return(STATUS_INVALID_STATEMENT); } + parameter -= AXIS_SETTINGS_INCREMENT; + } + } + } else { + // Store non-axis Grbl settings + + switch(parameter) { + case 0: + if (int_value < 3) { return(STATUS_SETTING_STEP_PULSE_MIN); } + settings.pulse_microseconds = int_value; break; + case 1: settings.stepper_idle_lock_time = int_value; break; + case 2: + settings.step_invert_mask = int_value; + st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks. + break; + case 3: + settings.dir_invert_mask = int_value; + st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks. + break; + case 4: // Reset to ensure change. Immediate re-init may cause problems. + if (int_value) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } + else { settings.flags &= ~BITFLAG_INVERT_ST_ENABLE; } + break; + case 5: // Reset to ensure change. Immediate re-init may cause problems. + if (int_value) { settings.flags |= BITFLAG_INVERT_LIMIT_PINS; } + else { settings.flags &= ~BITFLAG_INVERT_LIMIT_PINS; } + break; + case 6: // Reset to ensure change. Immediate re-init may cause problems. + if (int_value) { settings.flags |= BITFLAG_INVERT_PROBE_PIN; } + else { settings.flags &= ~BITFLAG_INVERT_PROBE_PIN; } + probe_configure_invert_mask(false); + break; + case 10: settings.status_report_mask = int_value; break; + case 11: settings.junction_deviation = value; break; + case 12: settings.arc_tolerance = value; break; + case 13: + if (int_value) { settings.flags |= BITFLAG_REPORT_INCHES; } + else { settings.flags &= ~BITFLAG_REPORT_INCHES; } + system_flag_wco_change(); // Make sure WCO is immediately updated. + break; + case 20: + if (int_value) { + if (bit_isfalse(settings.flags, BITFLAG_HOMING_ENABLE)) { return(STATUS_SOFT_LIMIT_ERROR); } + settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; + } else { settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; } + break; + case 21: + if (int_value) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } + else { settings.flags &= ~BITFLAG_HARD_LIMIT_ENABLE; } + limits_init(); // Re-init to immediately change. NOTE: Nice to have but could be problematic later. + break; + case 22: + if (int_value) { settings.flags |= BITFLAG_HOMING_ENABLE; } + else { + settings.flags &= ~BITFLAG_HOMING_ENABLE; + settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; // Force disable soft-limits. + } + break; + case 23: settings.homing_dir_mask = int_value; break; + case 24: settings.homing_feed_rate = value; break; + case 25: settings.homing_seek_rate = value; break; + case 26: settings.homing_debounce_delay = int_value; break; + case 27: settings.homing_pulloff = value; break; + case 30: settings.rpm_max = value; spindle_init(); break; // Re-initialize spindle rpm calibration + case 31: settings.rpm_min = value; spindle_init(); break; // Re-initialize spindle rpm calibration + case 32: + #ifdef VARIABLE_SPINDLE + if (int_value) { settings.flags |= BITFLAG_LASER_MODE; } + else { settings.flags &= ~BITFLAG_LASER_MODE; } + #else + return(STATUS_SETTING_DISABLED_LASER); + #endif + break; + case 33: settings.spindle_pwm_freq = value; spindle_init(); break; // Re-initialize spindle pwm calibration + case 34: settings.spindle_pwm_off_value = value; spindle_init(); break; // Re-initialize spindle pwm calibration + case 35: settings.spindle_pwm_min_value = value; spindle_init(); break; // Re-initialize spindle pwm calibration + case 36: settings.spindle_pwm_max_value = value; spindle_init(); break; // Re-initialize spindle pwm calibration + + case 80: + case 81: + case 82: + case 83: + case 84: + settings.machine_int16[parameter - 80] = int_value; + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + settings.machine_float[parameter - 90] = value; + break; + default: + return(STATUS_INVALID_STATEMENT); + } + } + write_global_settings(); + return(STATUS_OK); +} + +// Returns step pin mask according to Grbl internal axis indexing. +uint8_t get_step_pin_mask(uint8_t axis_idx) +{ + // todo clean this up further up stream + return(1<. +*/ + +#ifndef settings_h +#define settings_h + +#include "grbl.h" + + +// Version of the EEPROM data. Will be used to migrate existing data from older versions of Grbl +// when firmware is upgraded. Always stored in byte 0 of eeprom +#define SETTINGS_VERSION 12 // NOTE: Check settings_reset() when moving to next version. + +// Define bit flag masks for the boolean settings in settings.flag. +#define BITFLAG_REPORT_INCHES bit(0) +#define BITFLAG_LASER_MODE bit(1) +#define BITFLAG_INVERT_ST_ENABLE bit(2) +#define BITFLAG_HARD_LIMIT_ENABLE bit(3) +#define BITFLAG_HOMING_ENABLE bit(4) +#define BITFLAG_SOFT_LIMIT_ENABLE bit(5) +#define BITFLAG_INVERT_LIMIT_PINS bit(6) +#define BITFLAG_INVERT_PROBE_PIN bit(7) + +// Define status reporting boolean enable bit flags in settings.status_report_mask +#define BITFLAG_RT_STATUS_POSITION_TYPE bit(0) +#define BITFLAG_RT_STATUS_BUFFER_STATE bit(1) + +// Define settings restore bitflags. +#define SETTINGS_RESTORE_DEFAULTS bit(0) +#define SETTINGS_RESTORE_PARAMETERS bit(1) +#define SETTINGS_RESTORE_STARTUP_LINES bit(2) +#define SETTINGS_RESTORE_BUILD_INFO bit(3) +#define SETTINGS_RESTORE_WIFI_SETTINGS bit(4) +#ifndef SETTINGS_RESTORE_ALL + #define SETTINGS_RESTORE_ALL 0xFF // All bitflags +#endif + +// Define EEPROM memory address location values for Grbl settings and parameters +// NOTE: The Atmega328p has 1KB EEPROM. The upper half is reserved for parameters and +// the startup script. The lower half contains the global settings and space for future +// developments. +#define EEPROM_SIZE 1024U +#define EEPROM_ADDR_GLOBAL 1U +#define EEPROM_ADDR_PARAMETERS 512U +#define EEPROM_ADDR_STARTUP_BLOCK 768U +#define EEPROM_ADDR_BUILD_INFO 942U + +// Define EEPROM address indexing for coordinate parameters +#define N_COORDINATE_SYSTEM 6 // Number of supported work coordinate systems (from index 1) +#define SETTING_INDEX_NCOORD N_COORDINATE_SYSTEM+1 // Total number of system stored (from index 0) +// NOTE: Work coordinate indices are (0=G54, 1=G55, ... , 6=G59) +#define SETTING_INDEX_G28 N_COORDINATE_SYSTEM // Home position 1 +#define SETTING_INDEX_G30 N_COORDINATE_SYSTEM+1 // Home position 2 +// #define SETTING_INDEX_G92 N_COORDINATE_SYSTEM+2 // Coordinate offset (G92.2,G92.3 not supported) + +// Define Grbl axis settings numbering scheme. Starts at START_VAL, every INCREMENT, over N_SETTINGS. +#ifndef SHOW_EXTENDED_SETTINGS + #define AXIS_N_SETTINGS 4 +#else + #define AXIS_N_SETTINGS 8 +#endif +#define AXIS_SETTINGS_START_VAL 100 // NOTE: Reserving settings values >= 100 for axis settings. Up to 255. +#define AXIS_SETTINGS_INCREMENT 10 // Must be greater than the number of axis settings + +#define USER_SETTING_COUNT 5 // for user to define for their machine + +// Global persistent settings (Stored from byte EEPROM_ADDR_GLOBAL onwards) +typedef struct { + // Axis settings + float steps_per_mm[N_AXIS]; + float max_rate[N_AXIS]; + float acceleration[N_AXIS]; + float max_travel[N_AXIS]; + float current[N_AXIS]; // $140... run current (extended set) + float hold_current[N_AXIS]; // $150 percent of run current (extended set) + uint16_t microsteps[N_AXIS]; // $160... (extended set) + uint8_t stallguard[N_AXIS]; // $170... (extended set) + + // Remaining Grbl settings + uint8_t pulse_microseconds; + uint8_t step_invert_mask; + uint8_t dir_invert_mask; + uint8_t stepper_idle_lock_time; // If max value 255, steppers do not disable. + uint8_t status_report_mask; // Mask to indicate desired report data. + float junction_deviation; + float arc_tolerance; + + float spindle_pwm_freq; // $33 Hz (extended set) + float spindle_pwm_off_value; // $34 Percent (extended set) + float spindle_pwm_min_value; // $35 Percent (extended set) + float spindle_pwm_max_value; // $36 Percent (extended set) + + float rpm_max; + float rpm_min; + + uint8_t flags; // Contains default boolean settings + + uint8_t homing_dir_mask; + float homing_feed_rate; + float homing_seek_rate; + uint16_t homing_debounce_delay; + float homing_pulloff; + + int16_t machine_int16[USER_SETTING_COUNT]; // settings starting at 80 to be defined by the user + float machine_float[USER_SETTING_COUNT]; // settings starting at 80 to be defined by the user + +} settings_t; +extern settings_t settings; + +// Initialize the configuration subsystem (load settings from EEPROM) +void settings_init(); +void settings_restore(uint8_t restore_flag); +void write_global_settings(); +uint8_t read_global_settings(); + +uint8_t settings_read_startup_line(uint8_t n, char *line); +void settings_store_startup_line(uint8_t n, char *line); + +uint8_t settings_read_build_info(char *line); +void settings_store_build_info(char *line); + +uint8_t settings_store_global_setting(uint8_t parameter, float value); + +// Writes selected coordinate data to EEPROM +void settings_write_coord_data(uint8_t coord_select, float *coord_data); + +// Reads selected coordinate data from EEPROM +uint8_t settings_read_coord_data(uint8_t coord_select, float *coord_data); + +// Returns the step pin mask according to Grbl's internal axis numbering +uint8_t get_step_pin_mask(uint8_t i); + +// Returns the direction pin mask according to Grbl's internal axis numbering +uint8_t get_direction_pin_mask(uint8_t i); + +void settings_spi_driver_init(); +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.cpp b/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.cpp new file mode 100644 index 0000000..2c75bec --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.cpp @@ -0,0 +1,123 @@ +/* + solenoid_pen.cpp + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + +*/ +#include "grbl.h" + +#ifdef USE_PEN_SOLENOID + +static TaskHandle_t solenoidSyncTaskHandle = 0; + +// used to delay turn on +bool solenoid_pen_enable; +uint16_t solenoide_hold_count; + +void solenoid_init() +{ + grbl_send(CLIENT_SERIAL, "[MSG:Solenoid Mode]\r\n"); // startup message + //validate_servo_settings(true); // display any calibration errors + + solenoid_pen_enable = false; // start delay has not completed yet. + solenoide_hold_count = 0; // initialize + + // setup PWM channel + ledcSetup(SOLENOID_CHANNEL_NUM, SOLENOID_PWM_FREQ, SOLENOID_PWM_RES_BITS); + ledcAttachPin(SOLENOID_PEN_PIN, SOLENOID_CHANNEL_NUM); + + solenoid_disable(); // start it it off + + // setup a task that will calculate the determine and set the servo position + xTaskCreatePinnedToCore( solenoidSyncTask, // task + "solenoidSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &solenoidSyncTaskHandle, + 0 // core + ); +} + +// turn off the PWM (0 duty) +void solenoid_disable() +{ + ledcWrite(SOLENOID_CHANNEL_NUM, 0); +} + +// this is the task +void solenoidSyncTask(void *pvParameters) +{ + int32_t current_position[N_AXIS]; // copy of current location + float m_pos[N_AXIS]; // machine position in mm + TickType_t xLastWakeTime; + const TickType_t xSolenoidFrequency = SOLENOID_TIMER_INT_FREQ; // in ticks (typically ms) + uint16_t solenoid_delay_counter = 0; + + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while(true) { // don't ever return from this or the task dies + if (!solenoid_pen_enable) { + solenoid_delay_counter++; + solenoid_pen_enable = (solenoid_delay_counter > SOLENOID_TURNON_DELAY); + } + else { + memcpy(current_position,sys_position,sizeof(sys_position)); // get current position in step + system_convert_array_steps_to_mpos(m_pos,current_position); // convert to millimeters + calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos + } + vTaskDelayUntil(&xLastWakeTime, xSolenoidFrequency); + } +} + +// calculate and set the PWM value for the servo +void calc_solenoid(float penZ) +{ + uint32_t solenoid_pen_pulse_len; + + if (!solenoid_pen_enable) // only proceed if startup delay as expired + return; + + if (penZ < 0 && (sys.state != STATE_ALARM)) { // alarm also makes it go up + solenoide_hold_count = 0; // reset this count + solenoid_pen_pulse_len = 0; // + } + else { + if (solenoide_hold_count < SOLENOID_PULSE_LEN_HOLD) { + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_UP; + solenoide_hold_count++; + } + else { + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_HOLD; + } + + } + + // skip setting value if it is unchanged + if (ledcRead(SOLENOID_CHANNEL_NUM) == solenoid_pen_pulse_len) + return; + + // update the PWM value + // ledcWrite appears to have issues with interrupts, so make this a critical section + portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&myMutex); + ledcWrite(SOLENOID_CHANNEL_NUM, solenoid_pen_pulse_len); + portEXIT_CRITICAL(&myMutex); +} + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.h b/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.h new file mode 100644 index 0000000..e6495a3 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/solenoid_pen.h @@ -0,0 +1,53 @@ +/* + solenoid_pen.h + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + Usage notes: + This is designed to use a solenoid to lift a pen. + When the current Z location is below zero the pen is down + If the Z goes to zero or above the pen goes up. + There are two power levels, the initial pull up strength, then the hold strength + + + Note: There is a still a virtual Z axis that has a finite speed. + If your gcode is commanding long travels in Z, there will be delays + between solenoid states as the Z "travels" to the location that will + change the state. + +*/ + +#define SOLENOID_PWM_FREQ 5000 +#define SOLENOID_PWM_RES_BITS 8 + +#define SOLENOID_TURNON_DELAY (SOLENOID_TIMER_INT_FREQ/2) +#define SOLENOID_PULSE_LEN_UP 255 +#define SOLENOID_HOLD_DELAY (SOLENOID_TIMER_INT_FREQ/2) // in task counts...after this delay power will change to hold level +#define SOLENOID_PULSE_LEN_HOLD 80 // solenoid hold level ... typically a lower value to prevent overheating + +#define SOLENOID_TIMER_INT_FREQ 50 + +#ifndef solenoid_h +#define solenoid_h + +void solenoid_init(); +void solenoid_disable(); +void solenoidSyncTask(void *pvParameters); +void calc_solenoid(float penZ); + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/spindle_control.cpp b/Grbl_Esp32-master/Grbl_Esp32/spindle_control.cpp new file mode 100644 index 0000000..8877bf3 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/spindle_control.cpp @@ -0,0 +1,257 @@ +/* + spindle_control.cpp - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" + +#ifdef SPINDLE_PWM_PIN +static float pwm_gradient; // Precalulated value to speed up rpm to PWM conversions. +float spindle_pwm_period; +float spindle_pwm_off_value; +float spindle_pwm_min_value; +float spindle_pwm_max_value; +#endif + +void spindle_init() +{ + + #ifdef SPINDLE_PWM_PIN + + #ifdef INVERT_SPINDLE_PWM + grbl_send(CLIENT_SERIAL, "[MSG: INVERT_SPINDLE_PWM]\r\n"); + #endif + + #ifdef INVERT_SPINDLE_ENABLE_PIN + grbl_send(CLIENT_SERIAL, "[MSG: INVERT_SPINDLE_ENABLE_PIN]\r\n"); + #endif + + spindle_pwm_period = SPINDLE_PULSE_RES_COUNT; + + + spindle_pwm_off_value = (spindle_pwm_period * settings.spindle_pwm_off_value / 100); + spindle_pwm_min_value = (spindle_pwm_period * settings.spindle_pwm_min_value / 100); + spindle_pwm_max_value = (spindle_pwm_period * settings.spindle_pwm_max_value / 100); + + //pwm_gradient = (settings.spindle_pwm_max_value - settings.spindle_pwm_min_value)/(settings.rpm_max-settings.rpm_min); + pwm_gradient = (spindle_pwm_max_value-spindle_pwm_min_value)/(settings.rpm_max-settings.rpm_min); + + + if ( (F_TIMERS / (uint32_t)settings.spindle_pwm_freq) < spindle_pwm_max_value) { + /* + PWM Generator is based on 80,000,000 Hz counter + Therefor the freq determines the resolution 80,000,000 / freq = max resolution + For 5000 that is 80,000,000 / 5000 = 16000 + Round down to nearest bit count for SPINDLE_PWM_MAX_VALUE = 13bits (8192) + */ + grbl_sendf(CLIENT_SERIAL, "[MSG: Warning! Spindle freq %5.0f too high for requested PWM max %5.2f%% (%5.0f)]\r\n", settings.spindle_pwm_freq, settings.spindle_pwm_max_value, spindle_pwm_max_value); + } + + // Use DIR and Enable if pins are defined + #ifdef SPINDLE_ENABLE_PIN + pinMode(SPINDLE_ENABLE_PIN, OUTPUT); + #endif + + #ifdef SPINDLE_DIR_PIN + pinMode(SPINDLE_DIR_PIN, OUTPUT); + #endif + + // use the LED control feature to setup PWM https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html + ledcSetup(SPINDLE_PWM_CHANNEL, (double)settings.spindle_pwm_freq, SPINDLE_PWM_BIT_PRECISION); // setup the channel + ledcAttachPin(SPINDLE_PWM_PIN, SPINDLE_PWM_CHANNEL); // attach the PWM to the pin + + // Start with spindle off off + spindle_stop(); + #endif +} + +void spindle_stop() +{ + spindle_set_enable(false); + + #ifdef SPINDLE_PWM_PIN + #ifndef INVERT_SPINDLE_PWM + grbl_analogWrite(SPINDLE_PWM_CHANNEL, spindle_pwm_off_value); + #else + grbl_analogWrite(SPINDLE_PWM_CHANNEL, (1<= settings.rpm_max) || (rpm >= settings.rpm_max)) { + // No PWM range possible. Set simple on/off spindle control pin state. + sys.spindle_speed = settings.rpm_max; + pwm_value = spindle_pwm_max_value; + } else if (rpm <= settings.rpm_min) { + if (rpm == 0.0) { // S0 disables spindle + sys.spindle_speed = 0.0; + pwm_value = spindle_pwm_off_value; + } else { // Set minimum PWM output + sys.spindle_speed = settings.rpm_min; + pwm_value = spindle_pwm_min_value; + } + } else { + // Compute intermediate PWM value with linear spindle speed model. + // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight. + sys.spindle_speed = rpm; + #ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE + pwm_value = piecewise_linear_fit(rpm); + #else + pwm_value = floor((rpm - settings.rpm_min)*pwm_gradient) + settings.spindle_pwm_min_value; + #endif + } + return(pwm_value); + #else + return(0); // no SPINDLE_PWM_PIN + #endif +} + + +// Called by spindle_set_state() and step segment generator. Keep routine small and efficient. +void spindle_set_state(uint8_t state, float rpm) +{ + #ifdef SPINDLE_PWM_PIN + if (sys.abort) { return; } // Block during abort. + if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm. + sys.spindle_speed = 0.0; + spindle_stop(); + } else { + + // TODO ESP32 Enable and direction control + #ifdef SPINDLE_DIR_PIN + digitalWrite(SPINDLE_DIR_PIN, state == SPINDLE_ENABLE_CW); + #endif + + // NOTE: Assumes all calls to this function is when Grbl is not moving or must remain off. + if (settings.flags & BITFLAG_LASER_MODE) { + if (state == SPINDLE_ENABLE_CCW) { rpm = 0.0; } // TODO: May need to be rpm_min*(100/MAX_SPINDLE_SPEED_OVERRIDE); + } + + spindle_set_speed(spindle_compute_pwm_value(rpm)); + } + sys.report_ovr_counter = 0; // Set to report change immediately + #endif +} + + +void spindle_sync(uint8_t state, float rpm) +{ + if (sys.state == STATE_CHECK_MODE) { + return; + } + + protocol_buffer_synchronize(); // Empty planner buffer to ensure spindle is set when programmed. + spindle_set_state(state,rpm); +} + + +void grbl_analogWrite(uint8_t chan, uint32_t duty) +{ + if (ledcRead(chan) != duty) // reduce unnecessary calls to ledcWrite() + { + // Useful for debug, but too many messages in laser mode + // grbl_sendf(CLIENT_SERIAL, "[MSG: grbl_analogWrite %d]\r\n", duty); + ledcWrite(chan, duty); + } +} + +void spindle_set_enable(bool enable) +{ + #ifdef SPINDLE_ENABLE_PIN + #ifndef INVERT_SPINDLE_ENABLE_PIN + digitalWrite(SPINDLE_ENABLE_PIN, enable); // turn off (low) with zero speed + #else + digitalWrite(SPINDLE_ENABLE_PIN, !enable); // turn off (high) with zero speed + #endif + #endif +} + +uint32_t piecewise_linear_fit(float rpm) { + uint32_t pwm_value; + + #if (N_PIECES > 3) + if (rpm > RPM_POINT34) { + pwm_value = floor(RPM_LINE_A4*rpm - RPM_LINE_B4); + } else + #endif + #if (N_PIECES > 2) + if (rpm > RPM_POINT23) { + pwm_value = floor(RPM_LINE_A3*rpm - RPM_LINE_B3); + } else + #endif + #if (N_PIECES > 1) + if (rpm > RPM_POINT12) { + pwm_value = floor(RPM_LINE_A2*rpm - RPM_LINE_B2); + } else + #endif + { + pwm_value = floor(RPM_LINE_A1*rpm - RPM_LINE_B1); + } + return pwm_value; +} + + + diff --git a/Grbl_Esp32-master/Grbl_Esp32/spindle_control.h b/Grbl_Esp32-master/Grbl_Esp32/spindle_control.h new file mode 100644 index 0000000..fd4147d --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/spindle_control.h @@ -0,0 +1,47 @@ +/* + spindle.h - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef spindle_control_h + #define spindle_control_h + + #include "grbl.h" + + +#define SPINDLE_NO_SYNC false +#define SPINDLE_FORCE_SYNC true + +#define SPINDLE_STATE_DISABLE 0 // Must be zero. +#define SPINDLE_STATE_CW bit(0) +#define SPINDLE_STATE_CCW bit(1) + +#define SPINDLE_PULSE_RES_COUNT ((1<. +*/ + +#include "grbl.h" + +// Stores the planner block Bresenham algorithm execution data for the segments in the segment +// buffer. Normally, this buffer is partially in-use, but, for the worst case scenario, it will +// never exceed the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1). +// NOTE: This data is copied from the prepped planner blocks so that the planner blocks may be +// discarded when entirely consumed and completed by the segment buffer. Also, AMASS alters this +// data for its own use. +typedef struct { + uint32_t steps[N_AXIS]; + uint32_t step_event_count; + uint8_t direction_bits; +#ifdef VARIABLE_SPINDLE + uint8_t is_pwm_rate_adjusted; // Tracks motions that require constant laser power/rate +#endif +} st_block_t; +static st_block_t st_block_buffer[SEGMENT_BUFFER_SIZE-1]; + +// Primary stepper segment ring buffer. Contains small, short line segments for the stepper +// algorithm to execute, which are "checked-out" incrementally from the first block in the +// planner buffer. Once "checked-out", the steps in the segments buffer cannot be modified by +// the planner, where the remaining planner block steps still can. +typedef struct { + uint16_t n_step; // Number of step events to be executed for this segment + uint16_t cycles_per_tick; // Step distance traveled per ISR tick, aka step rate. + uint8_t st_block_index; // Stepper block data index. Uses this information to execute this segment. +#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + uint8_t amass_level; // Indicates AMASS level for the ISR to execute this segment +#else + uint8_t prescaler; // Without AMASS, a prescaler is required to adjust for slow timing. +#endif +#ifdef VARIABLE_SPINDLE + uint16_t spindle_pwm; +#endif +} segment_t; +static segment_t segment_buffer[SEGMENT_BUFFER_SIZE]; + +// Stepper ISR data struct. Contains the running data for the main stepper ISR. +typedef struct { + // Used by the bresenham line algorithm + uint32_t counter_x, // Counter variables for the bresenham line tracer + counter_y, + counter_z + #if (N_AXIS > A_AXIS) + , counter_a + #endif + #if (N_AXIS > B_AXIS) + , counter_b + #endif + #if (N_AXIS > C_AXIS) + , counter_c + #endif + ; +#ifdef STEP_PULSE_DELAY + uint8_t step_bits; // Stores out_bits output to complete the step pulse delay +#endif + + uint8_t execute_step; // Flags step execution for each interrupt. + uint8_t step_pulse_time; // Step pulse reset time after step rise + uint8_t step_outbits; // The next stepping-bits to be output + uint8_t dir_outbits; +#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + uint32_t steps[N_AXIS]; +#endif + + uint16_t step_count; // Steps remaining in line segment motion + uint8_t exec_block_index; // Tracks the current st_block index. Change indicates new block. + st_block_t *exec_block; // Pointer to the block data for the segment being executed + segment_t *exec_segment; // Pointer to the segment being executed +} stepper_t; +static stepper_t st; + +// Step segment ring buffer indices +static volatile uint8_t segment_buffer_tail; +static uint8_t segment_buffer_head; +static uint8_t segment_next_head; + +// Step and direction port invert masks. +static uint8_t step_port_invert_mask; +static uint8_t dir_port_invert_mask; + +// Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though. +static volatile uint8_t busy; + +// Pointers for the step segment being prepped from the planner buffer. Accessed only by the +// main program. Pointers may be planning segments or planner blocks ahead of what being executed. +static plan_block_t *pl_block; // Pointer to the planner block being prepped +static st_block_t *st_prep_block; // Pointer to the stepper block data being prepped + +// esp32 work around for diable in main loop +uint64_t stepper_idle_counter; // used to count down until time to disable stepper drivers +bool stepper_idle; + +// Segment preparation data struct. Contains all the necessary information to compute new segments +// based on the current executing planner block. +typedef struct { + uint8_t st_block_index; // Index of stepper common data block being prepped + uint8_t recalculate_flag; + + float dt_remainder; + float steps_remaining; + float step_per_mm; + float req_mm_increment; + +#ifdef PARKING_ENABLE + uint8_t last_st_block_index; + float last_steps_remaining; + float last_step_per_mm; + float last_dt_remainder; +#endif + + uint8_t ramp_type; // Current segment ramp state + float mm_complete; // End of velocity profile from end of current planner block in (mm). + // NOTE: This value must coincide with a step(no mantissa) when converted. + float current_speed; // Current speed at the end of the segment buffer (mm/min) + float maximum_speed; // Maximum speed of executing block. Not always nominal speed. (mm/min) + float exit_speed; // Exit speed of executing block (mm/min) + float accelerate_until; // Acceleration ramp end measured from end of block (mm) + float decelerate_after; // Deceleration ramp start measured from end of block (mm) + +#ifdef VARIABLE_SPINDLE + float inv_rate; // Used by PWM laser mode to speed up segment calculations. + uint16_t current_spindle_pwm; +#endif +} st_prep_t; +static st_prep_t prep; + + +/* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. Grbl employs + the venerable Bresenham line algorithm to manage and exactly synchronize multi-axis moves. + Unlike the popular DDA algorithm, the Bresenham algorithm is not susceptible to numerical + round-off errors and only requires fast integer counters, meaning low computational overhead + and maximizing the Arduino's capabilities. However, the downside of the Bresenham algorithm + is, for certain multi-axis motions, the non-dominant axes may suffer from un-smooth step + pulse trains, or aliasing, which can lead to strange audible noises or shaking. This is + particularly noticeable or may cause motion issues at low step frequencies (0-5kHz), but + is usually not a physical problem at higher frequencies, although audible. + To improve Bresenham multi-axis performance, Grbl uses what we call an Adaptive Multi-Axis + Step Smoothing (AMASS) algorithm, which does what the name implies. At lower step frequencies, + AMASS artificially increases the Bresenham resolution without effecting the algorithm's + innate exactness. AMASS adapts its resolution levels automatically depending on the step + frequency to be executed, meaning that for even lower step frequencies the step smoothing + level increases. Algorithmically, AMASS is acheived by a simple bit-shifting of the Bresenham + step count for each AMASS level. For example, for a Level 1 step smoothing, we bit shift + the Bresenham step event count, effectively multiplying it by 2, while the axis step counts + remain the same, and then double the stepper ISR frequency. In effect, we are allowing the + non-dominant Bresenham axes step in the intermediate ISR tick, while the dominant axis is + stepping every two ISR ticks, rather than every ISR tick in the traditional sense. At AMASS + Level 2, we simply bit-shift again, so the non-dominant Bresenham axes can step within any + of the four ISR ticks, the dominant axis steps every four ISR ticks, and quadruple the + stepper ISR frequency. And so on. This, in effect, virtually eliminates multi-axis aliasing + issues with the Bresenham algorithm and does not significantly alter Grbl's performance, but + in fact, more efficiently utilizes unused CPU cycles overall throughout all configurations. + AMASS retains the Bresenham algorithm exactness by requiring that it always executes a full + Bresenham step, regardless of AMASS Level. Meaning that for an AMASS Level 2, all four + intermediate steps must be completed such that baseline Bresenham (Level 0) count is always + retained. Similarly, AMASS Level 3 means all eight intermediate steps must be executed. + Although the AMASS Levels are in reality arbitrary, where the baseline Bresenham counts can + be multiplied by any integer value, multiplication by powers of two are simply used to ease + CPU overhead with bitshift integer operations. + This interrupt is simple and dumb by design. All the computational heavy-lifting, as in + determining accelerations, is performed elsewhere. This interrupt pops pre-computed segments, + defined as constant velocity over n number of steps, from the step segment buffer and then + executes them by pulsing the stepper pins appropriately via the Bresenham algorithm. This + ISR is supported by The Stepper Port Reset Interrupt which it uses to reset the stepper port + after each pulse. The bresenham line tracer algorithm controls all stepper outputs + simultaneously with these two interrupts. + + NOTE: This interrupt must be as efficient as possible and complete before the next ISR tick, + which for ESP32 Grbl must be less than xx.xusec (TBD). Oscilloscope measured time in + ISR is 5usec typical and 25usec maximum, well below requirement. + NOTE: This ISR expects at least one step to be executed per segment. + + The complete step timing should look this... + Direction pin is set + An optional (via STEP_PULSE_DELAY in config.h) is put after this + The step pin is started + A pulse length is determine (via option $0 ... settings.pulse_microseconds) + The pulse is ended + Direction will remain the same until another step occurs with a change in direction. + + +*/ +#ifdef USE_RMT_STEPS + inline IRAM_ATTR static void stepperRMT_Outputs(); +#endif +// TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller +// int8 variables and update position counters only when a segment completes. This can get complicated +// with probing and homing cycles that require true real-time positions. +void IRAM_ATTR onStepperDriverTimer(void *para) // ISR It is time to take a step ======================================================================================= +{ + #ifndef USE_RMT_STEPS + uint64_t step_pulse_off_time; + #endif + //const int timer_idx = (int)para; // get the timer index + + TIMERG0.int_clr_timers.t0 = 1; + + if (busy) { + return; // The busy-flag is used to avoid reentering this interrupt + } + + set_direction_pins_on(st.dir_outbits); + + #ifdef USE_RMT_STEPS + stepperRMT_Outputs(); + #else + set_stepper_pins_on(st.step_outbits); + step_pulse_off_time = esp_timer_get_time() + (settings.pulse_microseconds); // determine when to turn off pulse + #endif + + #ifdef USE_UNIPOLAR + unipolar_step(st.step_outbits, st.dir_outbits); + #endif + + busy = true; + // If there is no step segment, attempt to pop one from the stepper buffer + if (st.exec_segment == NULL) { + // Anything in the buffer? If so, load and initialize next step segment. + if (segment_buffer_head != segment_buffer_tail) { + // Initialize new step segment and load number of steps to execute + st.exec_segment = &segment_buffer[segment_buffer_tail]; + + // Initialize step segment timing per step and load number of steps to execute. + Stepper_Timer_WritePeriod(st.exec_segment->cycles_per_tick); + + st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. + // If the new segment starts a new planner block, initialize stepper variables and counters. + // NOTE: When the segment data index changes, this indicates a new planner block. + if ( st.exec_block_index != st.exec_segment->st_block_index ) { + st.exec_block_index = st.exec_segment->st_block_index; + st.exec_block = &st_block_buffer[st.exec_block_index]; + + // Initialize Bresenham line and distance counters + st.counter_x = st.counter_y = st.counter_z = (st.exec_block->step_event_count >> 1); + // TODO ABC + } + st.dir_outbits = st.exec_block->direction_bits ^ settings.dir_invert_mask; + +#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + // With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level. + st.steps[X_AXIS] = st.exec_block->steps[X_AXIS] >> st.exec_segment->amass_level; + st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level; + st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level; + + #if (N_AXIS > A_AXIS) + st.steps[A_AXIS] = st.exec_block->steps[A_AXIS] >> st.exec_segment->amass_level; + #endif + #if (N_AXIS > B_AXIS) + st.steps[B_AXIS] = st.exec_block->steps[B_AXIS] >> st.exec_segment->amass_level; + #endif + #if (N_AXIS > C_AXIS) + st.steps[C_AXIS] = st.exec_block->steps[C_AXIS] >> st.exec_segment->amass_level; + #endif + +#endif + +#ifdef VARIABLE_SPINDLE + // Set real-time spindle output as segment is loaded, just prior to the first step. + spindle_set_speed(st.exec_segment->spindle_pwm); +#endif + + } else { + // Segment buffer empty. Shutdown. + st_go_idle(); +#if ( (defined VARIABLE_SPINDLE) && (defined SPINDLE_PWM_PIN) ) + if (!(sys.state & STATE_JOG)) { // added to prevent ... jog after probing crash + // Ensure pwm is set properly upon completion of rate-controlled motion. + if (st.exec_block->is_pwm_rate_adjusted) { + spindle_set_speed(settings.spindle_pwm_off_value); + } + } + +#endif + system_set_exec_state_flag(EXEC_CYCLE_STOP); // Flag main program for cycle end + return; // Nothing to do but exit. + } + } + + + // Check probing state. + if (sys_probe_state == PROBE_ACTIVE) { + probe_state_monitor(); + } + + // Reset step out bits. + st.step_outbits = 0; + + // Execute step displacement profile by Bresenham line algorithm +#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + st.counter_x += st.steps[X_AXIS]; +#else + st.counter_x += st.exec_block->steps[X_AXIS]; +#endif + if (st.counter_x > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1<steps[Y_AXIS]; +#endif + if (st.counter_y > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1<steps[Z_AXIS]; +#endif + if (st.counter_z > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1< A_AXIS) + #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + st.counter_a += st.steps[A_AXIS]; + #else + st.counter_a += st.exec_block->steps[A_AXIS]; + #endif + if (st.counter_a > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1< B_AXIS) + #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + st.counter_b += st.steps[B_AXIS]; + #else + st.counter_b += st.exec_block->steps[B_AXIS]; + #endif + if (st.counter_b > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1< C_AXIS) + #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + st.counter_c += st.steps[C_AXIS]; + #else + st.counter_c += st.exec_block->steps[C_AXIS]; + #endif + if (st.counter_c > st.exec_block->step_event_count) { + st.step_outbits |= (1<step_event_count; + if (st.exec_block->direction_bits & (1<> 3); +#endif + + // Enable Stepper Driver Interrupt + Stepper_Timer_Start(); +} + +// Reset and clear stepper subsystem variables +void st_reset() +{ + +#ifdef ESP_DEBUG + //Serial.println("st_reset()"); +#endif + // Initialize stepper driver idle state. + st_go_idle(); + + // Initialize stepper algorithm variables. + memset(&prep, 0, sizeof(st_prep_t)); + memset(&st, 0, sizeof(stepper_t)); + st.exec_segment = NULL; + pl_block = NULL; // Planner block pointer used by segment buffer + segment_buffer_tail = 0; + segment_buffer_head = 0; // empty = tail + segment_next_head = 1; + busy = false; + + st_generate_step_dir_invert_masks(); + st.dir_outbits = dir_port_invert_mask; // Initialize direction bits to default. + + // TODO do we need to turn step pins off? + +} + + + + + +void set_direction_pins_on(uint8_t onMask) +{ + // inverts are applied in step generation +#ifdef X_DIRECTION_PIN + digitalWrite(X_DIRECTION_PIN, (onMask & (1<entry_speed_sqr = prep.current_speed*prep.current_speed; // Update entry speed. + pl_block = NULL; // Flag st_prep_segment() to load and check active velocity profile. + } +} + +#ifdef PARKING_ENABLE +// Changes the run state of the step segment buffer to execute the special parking motion. +void st_parking_setup_buffer() +{ + // Store step execution data of partially completed block, if necessary. + if (prep.recalculate_flag & PREP_FLAG_HOLD_PARTIAL_BLOCK) { + prep.last_st_block_index = prep.st_block_index; + prep.last_steps_remaining = prep.steps_remaining; + prep.last_dt_remainder = prep.dt_remainder; + prep.last_step_per_mm = prep.step_per_mm; + } + // Set flags to execute a parking motion + prep.recalculate_flag |= PREP_FLAG_PARKING; + prep.recalculate_flag &= ~(PREP_FLAG_RECALCULATE); + pl_block = NULL; // Always reset parking motion to reload new block. +} + + +// Restores the step segment buffer to the normal run state after a parking motion. +void st_parking_restore_buffer() +{ + // Restore step execution data and flags of partially completed block, if necessary. + if (prep.recalculate_flag & PREP_FLAG_HOLD_PARTIAL_BLOCK) { + st_prep_block = &st_block_buffer[prep.last_st_block_index]; + prep.st_block_index = prep.last_st_block_index; + prep.steps_remaining = prep.last_steps_remaining; + prep.dt_remainder = prep.last_dt_remainder; + prep.step_per_mm = prep.last_step_per_mm; + prep.recalculate_flag = (PREP_FLAG_HOLD_PARTIAL_BLOCK | PREP_FLAG_RECALCULATE); + prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR/prep.step_per_mm; // Recompute this value. + } else { + prep.recalculate_flag = false; + } + pl_block = NULL; // Set to reload next block. +} +#endif + +// Generates the step and direction port invert masks used in the Stepper Interrupt Driver. +void st_generate_step_dir_invert_masks() +{ + /* + uint8_t idx; + step_port_invert_mask = 0; + dir_port_invert_mask = 0; + for (idx=0; idxdirection_bits = pl_block->direction_bits; + uint8_t idx; +#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + for (idx=0; idxsteps[idx] = pl_block->steps[idx]; + } + st_prep_block->step_event_count = pl_block->step_event_count; +#else + // With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS + // level, such that we never divide beyond the original data anywhere in the algorithm. + // If the original data is divided, we can lose a step from integer roundoff. + for (idx=0; idxsteps[idx] = pl_block->steps[idx] << MAX_AMASS_LEVEL; + } + st_prep_block->step_event_count = pl_block->step_event_count << MAX_AMASS_LEVEL; +#endif + + // Initialize segment buffer data for generating the segments. + prep.steps_remaining = (float)pl_block->step_event_count; + prep.step_per_mm = prep.steps_remaining/pl_block->millimeters; + prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR/prep.step_per_mm; + prep.dt_remainder = 0.0; // Reset for new segment block + + if ((sys.step_control & STEP_CONTROL_EXECUTE_HOLD) || (prep.recalculate_flag & PREP_FLAG_DECEL_OVERRIDE)) { + // New block loaded mid-hold. Override planner block entry speed to enforce deceleration. + prep.current_speed = prep.exit_speed; + pl_block->entry_speed_sqr = prep.exit_speed*prep.exit_speed; + prep.recalculate_flag &= ~(PREP_FLAG_DECEL_OVERRIDE); + } else { + prep.current_speed = sqrt(pl_block->entry_speed_sqr); + } + +#ifdef VARIABLE_SPINDLE + // Setup laser mode variables. PWM rate adjusted motions will always complete a motion with the + // spindle off. + st_prep_block->is_pwm_rate_adjusted = false; + if (settings.flags & BITFLAG_LASER_MODE) { + if (pl_block->condition & PL_COND_FLAG_SPINDLE_CCW) { + // Pre-compute inverse programmed rate to speed up PWM updating per step segment. + prep.inv_rate = 1.0/pl_block->programmed_rate; + st_prep_block->is_pwm_rate_adjusted = true; + } + } +#endif + } + + /* --------------------------------------------------------------------------------- + Compute the velocity profile of a new planner block based on its entry and exit + speeds, or recompute the profile of a partially-completed planner block if the + planner has updated it. For a commanded forced-deceleration, such as from a feed + hold, override the planner velocities and decelerate to the target exit speed. + */ + prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block. + float inv_2_accel = 0.5/pl_block->acceleration; + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { // [Forced Deceleration to Zero Velocity] + // Compute velocity profile parameters for a feed hold in-progress. This profile overrides + // the planner block profile, enforcing a deceleration to zero speed. + prep.ramp_type = RAMP_DECEL; + // Compute decelerate distance relative to end of block. + float decel_dist = pl_block->millimeters - inv_2_accel*pl_block->entry_speed_sqr; + if (decel_dist < 0.0) { + // Deceleration through entire planner block. End of feed hold is not in this block. + prep.exit_speed = sqrt(pl_block->entry_speed_sqr-2*pl_block->acceleration*pl_block->millimeters); + } else { + prep.mm_complete = decel_dist; // End of feed hold. + prep.exit_speed = 0.0; + } + } else { // [Normal Operation] + // Compute or recompute velocity profile parameters of the prepped planner block. + prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp. + prep.accelerate_until = pl_block->millimeters; + + float exit_speed_sqr; + float nominal_speed; + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + prep.exit_speed = exit_speed_sqr = 0.0; // Enforce stop at end of system motion. + } else { + exit_speed_sqr = plan_get_exec_block_exit_speed_sqr(); + prep.exit_speed = sqrt(exit_speed_sqr); + } + + nominal_speed = plan_compute_profile_nominal_speed(pl_block); + float nominal_speed_sqr = nominal_speed*nominal_speed; + float intersect_distance = + 0.5*(pl_block->millimeters+inv_2_accel*(pl_block->entry_speed_sqr-exit_speed_sqr)); + + if (pl_block->entry_speed_sqr > nominal_speed_sqr) { // Only occurs during override reductions. + prep.accelerate_until = pl_block->millimeters - inv_2_accel*(pl_block->entry_speed_sqr-nominal_speed_sqr); + if (prep.accelerate_until <= 0.0) { // Deceleration-only. + prep.ramp_type = RAMP_DECEL; + // prep.decelerate_after = pl_block->millimeters; + // prep.maximum_speed = prep.current_speed; + + // Compute override block exit speed since it doesn't match the planner exit speed. + prep.exit_speed = sqrt(pl_block->entry_speed_sqr - 2*pl_block->acceleration*pl_block->millimeters); + prep.recalculate_flag |= PREP_FLAG_DECEL_OVERRIDE; // Flag to load next block as deceleration override. + + // TODO: Determine correct handling of parameters in deceleration-only. + // Can be tricky since entry speed will be current speed, as in feed holds. + // Also, look into near-zero speed handling issues with this. + + } else { + // Decelerate to cruise or cruise-decelerate types. Guaranteed to intersect updated plan. + prep.decelerate_after = inv_2_accel*(nominal_speed_sqr-exit_speed_sqr); + prep.maximum_speed = nominal_speed; + prep.ramp_type = RAMP_DECEL_OVERRIDE; + } + } else if (intersect_distance > 0.0) { + if (intersect_distance < pl_block->millimeters) { // Either trapezoid or triangle types + // NOTE: For acceleration-cruise and cruise-only types, following calculation will be 0.0. + prep.decelerate_after = inv_2_accel*(nominal_speed_sqr-exit_speed_sqr); + if (prep.decelerate_after < intersect_distance) { // Trapezoid type + prep.maximum_speed = nominal_speed; + if (pl_block->entry_speed_sqr == nominal_speed_sqr) { + // Cruise-deceleration or cruise-only type. + prep.ramp_type = RAMP_CRUISE; + } else { + // Full-trapezoid or acceleration-cruise types + prep.accelerate_until -= inv_2_accel*(nominal_speed_sqr-pl_block->entry_speed_sqr); + } + } else { // Triangle type + prep.accelerate_until = intersect_distance; + prep.decelerate_after = intersect_distance; + prep.maximum_speed = sqrt(2.0*pl_block->acceleration*intersect_distance+exit_speed_sqr); + } + } else { // Deceleration-only type + prep.ramp_type = RAMP_DECEL; + // prep.decelerate_after = pl_block->millimeters; + // prep.maximum_speed = prep.current_speed; + } + } else { // Acceleration-only type + prep.accelerate_until = 0.0; + // prep.decelerate_after = 0.0; + prep.maximum_speed = prep.exit_speed; + } + } + +#ifdef VARIABLE_SPINDLE + bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_PWM); // Force update whenever updating block. +#endif + } + + // Initialize new segment + segment_t *prep_segment = &segment_buffer[segment_buffer_head]; + + // Set new segment to point to the current segment data block. + prep_segment->st_block_index = prep.st_block_index; + + /*------------------------------------------------------------------------------------ + Compute the average velocity of this new segment by determining the total distance + traveled over the segment time DT_SEGMENT. The following code first attempts to create + a full segment based on the current ramp conditions. If the segment time is incomplete + when terminating at a ramp state change, the code will continue to loop through the + progressing ramp states to fill the remaining segment execution time. However, if + an incomplete segment terminates at the end of the velocity profile, the segment is + considered completed despite having a truncated execution time less than DT_SEGMENT. + The velocity profile is always assumed to progress through the ramp sequence: + acceleration ramp, cruising state, and deceleration ramp. Each ramp's travel distance + may range from zero to the length of the block. Velocity profiles can end either at + the end of planner block (typical) or mid-block at the end of a forced deceleration, + such as from a feed hold. + */ + float dt_max = DT_SEGMENT; // Maximum segment time + float dt = 0.0; // Initialize segment time + float time_var = dt_max; // Time worker variable + float mm_var; // mm-Distance worker variable + float speed_var; // Speed worker variable + float mm_remaining = pl_block->millimeters; // New segment distance from end of block. + float minimum_mm = mm_remaining-prep.req_mm_increment; // Guarantee at least one step. + if (minimum_mm < 0.0) { + minimum_mm = 0.0; + } + + do { + switch (prep.ramp_type) { + case RAMP_DECEL_OVERRIDE: + speed_var = pl_block->acceleration*time_var; + mm_var = time_var*(prep.current_speed - 0.5*speed_var); + mm_remaining -= mm_var; + if ((mm_remaining < prep.accelerate_until) || (mm_var <= 0)) { + // Cruise or cruise-deceleration types only for deceleration override. + mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB + time_var = 2.0*(pl_block->millimeters-mm_remaining)/(prep.current_speed+prep.maximum_speed); + prep.ramp_type = RAMP_CRUISE; + prep.current_speed = prep.maximum_speed; + } else { // Mid-deceleration override ramp. + prep.current_speed -= speed_var; + } + break; + case RAMP_ACCEL: + // NOTE: Acceleration ramp only computes during first do-while loop. + speed_var = pl_block->acceleration*time_var; + mm_remaining -= time_var*(prep.current_speed + 0.5*speed_var); + if (mm_remaining < prep.accelerate_until) { // End of acceleration ramp. + // Acceleration-cruise, acceleration-deceleration ramp junction, or end of block. + mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB + time_var = 2.0*(pl_block->millimeters-mm_remaining)/(prep.current_speed+prep.maximum_speed); + if (mm_remaining == prep.decelerate_after) { + prep.ramp_type = RAMP_DECEL; + } else { + prep.ramp_type = RAMP_CRUISE; + } + prep.current_speed = prep.maximum_speed; + } else { // Acceleration only. + prep.current_speed += speed_var; + } + break; + case RAMP_CRUISE: + // NOTE: mm_var used to retain the last mm_remaining for incomplete segment time_var calculations. + // NOTE: If maximum_speed*time_var value is too low, round-off can cause mm_var to not change. To + // prevent this, simply enforce a minimum speed threshold in the planner. + mm_var = mm_remaining - prep.maximum_speed*time_var; + if (mm_var < prep.decelerate_after) { // End of cruise. + // Cruise-deceleration junction or end of block. + time_var = (mm_remaining - prep.decelerate_after)/prep.maximum_speed; + mm_remaining = prep.decelerate_after; // NOTE: 0.0 at EOB + prep.ramp_type = RAMP_DECEL; + } else { // Cruising only. + mm_remaining = mm_var; + } + break; + default: // case RAMP_DECEL: + // NOTE: mm_var used as a misc worker variable to prevent errors when near zero speed. + speed_var = pl_block->acceleration*time_var; // Used as delta speed (mm/min) + if (prep.current_speed > speed_var) { // Check if at or below zero speed. + // Compute distance from end of segment to end of block. + mm_var = mm_remaining - time_var*(prep.current_speed - 0.5*speed_var); // (mm) + if (mm_var > prep.mm_complete) { // Typical case. In deceleration ramp. + mm_remaining = mm_var; + prep.current_speed -= speed_var; + break; // Segment complete. Exit switch-case statement. Continue do-while loop. + } + } + // Otherwise, at end of block or end of forced-deceleration. + time_var = 2.0*(mm_remaining-prep.mm_complete)/(prep.current_speed+prep.exit_speed); + mm_remaining = prep.mm_complete; + prep.current_speed = prep.exit_speed; + } + dt += time_var; // Add computed ramp time to total segment time. + if (dt < dt_max) { + time_var = dt_max - dt; // **Incomplete** At ramp junction. + } else { + if (mm_remaining > minimum_mm) { // Check for very slow segments with zero steps. + // Increase segment time to ensure at least one step in segment. Override and loop + // through distance calculations until minimum_mm or mm_complete. + dt_max += DT_SEGMENT; + time_var = dt_max - dt; + } else { + break; // **Complete** Exit loop. Segment execution time maxed. + } + } + } while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete. + +#ifdef VARIABLE_SPINDLE + /* ----------------------------------------------------------------------------------- + Compute spindle speed PWM output for step segment + */ + + if (st_prep_block->is_pwm_rate_adjusted || (sys.step_control & STEP_CONTROL_UPDATE_SPINDLE_PWM)) { + if (pl_block->condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)) { + float rpm = pl_block->spindle_speed; + // NOTE: Feed and rapid overrides are independent of PWM value and do not alter laser power/rate. + if (st_prep_block->is_pwm_rate_adjusted) { + rpm *= (prep.current_speed * prep.inv_rate); + } + // If current_speed is zero, then may need to be rpm_min*(100/MAX_SPINDLE_SPEED_OVERRIDE) + // but this would be instantaneous only and during a motion. May not matter at all. + prep.current_spindle_pwm = spindle_compute_pwm_value(rpm); + } else { + sys.spindle_speed = 0.0; + #if ( (defined VARIABLE_SPINDLE) && (defined SPINDLE_PWM_PIN) ) + prep.current_spindle_pwm = settings.spindle_pwm_off_value; + #endif + + } + bit_false(sys.step_control,STEP_CONTROL_UPDATE_SPINDLE_PWM); + } + prep_segment->spindle_pwm = prep.current_spindle_pwm; // Reload segment PWM value +#endif + + /* ----------------------------------------------------------------------------------- + Compute segment step rate, steps to execute, and apply necessary rate corrections. + NOTE: Steps are computed by direct scalar conversion of the millimeter distance + remaining in the block, rather than incrementally tallying the steps executed per + segment. This helps in removing floating point round-off issues of several additions. + However, since floats have only 7.2 significant digits, long moves with extremely + high step counts can exceed the precision of floats, which can lead to lost steps. + Fortunately, this scenario is highly unlikely and unrealistic in CNC machines + supported by Grbl (i.e. exceeding 10 meters axis travel at 200 step/mm). + */ + float step_dist_remaining = prep.step_per_mm*mm_remaining; // Convert mm_remaining to steps + float n_steps_remaining = ceil(step_dist_remaining); // Round-up current steps remaining + float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining + prep_segment->n_step = last_n_steps_remaining-n_steps_remaining; // Compute number of steps to execute. + + // Bail if we are at the end of a feed hold and don't have a step to execute. + if (prep_segment->n_step == 0) { + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { + // Less than one step to decelerate to zero speed, but already very close. AMASS + // requires full steps to execute. So, just bail. + bit_true(sys.step_control,STEP_CONTROL_END_MOTION); +#ifdef PARKING_ENABLE + if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) { + prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; + } +#endif + return; // Segment not generated, but current step data still retained. + } + } + + // Compute segment step rate. Since steps are integers and mm distances traveled are not, + // the end of every segment can have a partial step of varying magnitudes that are not + // executed, because the stepper ISR requires whole steps due to the AMASS algorithm. To + // compensate, we track the time to execute the previous segment's partial step and simply + // apply it with the partial step distance to the current segment, so that it minutely + // adjusts the whole segment rate to keep step output exact. These rate adjustments are + // typically very small and do not adversely effect performance, but ensures that Grbl + // outputs the exact acceleration and velocity profiles as computed by the planner. + dt += prep.dt_remainder; // Apply previous segment partial step execute time + float inv_rate = dt/(last_n_steps_remaining - step_dist_remaining); // Compute adjusted step rate inverse + + // Compute CPU cycles per step for the prepped segment. + uint32_t cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step) + +#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + // Compute step timing and multi-axis smoothing level. + // NOTE: AMASS overdrives the timer with each level, so only one prescalar is required. + if (cycles < AMASS_LEVEL1) { + prep_segment->amass_level = 0; + } else { + if (cycles < AMASS_LEVEL2) { + prep_segment->amass_level = 1; + } else if (cycles < AMASS_LEVEL3) { + prep_segment->amass_level = 2; + } else { + prep_segment->amass_level = 3; + } + cycles >>= prep_segment->amass_level; + prep_segment->n_step <<= prep_segment->amass_level; + } + if (cycles < (1UL << 16)) { + prep_segment->cycles_per_tick = cycles; // < 65536 (4.1ms @ 16MHz) + } else { + prep_segment->cycles_per_tick = 0xffff; // Just set the slowest speed possible. + } +#else + // Compute step timing and timer prescalar for normal step generation. + if (cycles < (1UL << 16)) { // < 65536 (4.1ms @ 16MHz) + prep_segment->prescaler = 1; // prescaler: 0 + prep_segment->cycles_per_tick = cycles; + } else if (cycles < (1UL << 19)) { // < 524288 (32.8ms@16MHz) + prep_segment->prescaler = 2; // prescaler: 8 + prep_segment->cycles_per_tick = cycles >> 3; + } else { + prep_segment->prescaler = 3; // prescaler: 64 + if (cycles < (1UL << 22)) { // < 4194304 (262ms@16MHz) + prep_segment->cycles_per_tick = cycles >> 6; + } else { // Just set the slowest speed possible. (Around 4 step/sec.) + prep_segment->cycles_per_tick = 0xffff; + } + } +#endif + + // Segment complete! Increment segment buffer indices, so stepper ISR can immediately execute it. + segment_buffer_head = segment_next_head; + if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { + segment_next_head = 0; + } + + // Update the appropriate planner and segment data. + pl_block->millimeters = mm_remaining; + prep.steps_remaining = n_steps_remaining; + prep.dt_remainder = (n_steps_remaining - step_dist_remaining)*inv_rate; + + // Check for exit conditions and flag to load next planner block. + if (mm_remaining == prep.mm_complete) { + // End of planner block or forced-termination. No more distance to be executed. + if (mm_remaining > 0.0) { // At end of forced-termination. + // Reset prep parameters for resuming and then bail. Allow the stepper ISR to complete + // the segment queue, where realtime protocol will set new state upon receiving the + // cycle stop flag from the ISR. Prep_segment is blocked until then. + bit_true(sys.step_control,STEP_CONTROL_END_MOTION); +#ifdef PARKING_ENABLE + if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) { + prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; + } +#endif + return; // Bail! + } else { // End of planner block + // The planner block is complete. All steps are set to be executed in the segment buffer. + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + bit_true(sys.step_control,STEP_CONTROL_END_MOTION); + return; + } + pl_block = NULL; // Set pointer to indicate check and load next planner block. + plan_discard_current_block(); + } + } + + } +} + + + +// Called by realtime status reporting to fetch the current speed being executed. This value +// however is not exactly the current speed, but the speed computed in the last step segment +// in the segment buffer. It will always be behind by up to the number of segment blocks (-1) +// divided by the ACCELERATION TICKS PER SECOND in seconds. +float st_get_realtime_rate() +{ + if (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { + return prep.current_speed; + } + return 0.0f; +} + +void IRAM_ATTR Stepper_Timer_WritePeriod(uint64_t alarm_val) +{ + timer_set_alarm_value(STEP_TIMER_GROUP, STEP_TIMER_INDEX, alarm_val); +} + +void IRAM_ATTR Stepper_Timer_Start() +{ +#ifdef ESP_DEBUG + //Serial.println("ST Start"); +#endif + + timer_set_counter_value(STEP_TIMER_GROUP, STEP_TIMER_INDEX, 0x00000000ULL); + + timer_start(STEP_TIMER_GROUP, STEP_TIMER_INDEX); + TIMERG0.hw_timer[STEP_TIMER_INDEX].config.alarm_en = TIMER_ALARM_EN; + +} + +void IRAM_ATTR Stepper_Timer_Stop() +{ +#ifdef ESP_DEBUG + //Serial.println("ST Stop"); +#endif + + timer_pause(STEP_TIMER_GROUP, STEP_TIMER_INDEX); + +} + + +void set_stepper_disable(uint8_t isOn) // isOn = true // to disable +{ + #ifdef TRINAMIC + return; + #endif + + if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { + isOn = !isOn; // Apply pin invert. + } + + #ifdef USE_UNIPOLAR + unipolar_disable(isOn); + #endif + +#ifdef STEPPERS_DISABLE_PIN + digitalWrite(STEPPERS_DISABLE_PIN, isOn ); +#endif +} + +bool get_stepper_disable() // returns true if steppers are disabled +{ + bool disabled = false; + +#ifdef STEPPERS_DISABLE_PIN + disabled = digitalRead(STEPPERS_DISABLE_PIN); +#else + return false; // thery are never disabled if there is no pin defined +#endif + + if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { + disabled = !disabled; // Apply pin invert. + } + + return disabled; + +} + + + diff --git a/Grbl_Esp32-master/Grbl_Esp32/stepper.h b/Grbl_Esp32-master/Grbl_Esp32/stepper.h new file mode 100644 index 0000000..d2dd6bb --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/stepper.h @@ -0,0 +1,131 @@ +/* + stepper.h - stepper motor driver: executes motion plans of planner.c using the stepper motors + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#ifndef stepper_h +#define stepper_h + +#ifndef SEGMENT_BUFFER_SIZE + #define SEGMENT_BUFFER_SIZE 6 +#endif + + + +#include "grbl.h" +#include "config.h" + +// Some useful constants. +#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment +#define REQ_MM_INCREMENT_SCALAR 1.25 +#define RAMP_ACCEL 0 +#define RAMP_CRUISE 1 +#define RAMP_DECEL 2 +#define RAMP_DECEL_OVERRIDE 3 + +#define PREP_FLAG_RECALCULATE bit(0) +#define PREP_FLAG_HOLD_PARTIAL_BLOCK bit(1) +#define PREP_FLAG_PARKING bit(2) +#define PREP_FLAG_DECEL_OVERRIDE bit(3) + +// Define Adaptive Multi-Axis Step-Smoothing(AMASS) levels and cutoff frequencies. The highest level +// frequency bin starts at 0Hz and ends at its cutoff frequency. The next lower level frequency bin +// starts at the next higher cutoff frequency, and so on. The cutoff frequencies for each level must +// be considered carefully against how much it over-drives the stepper ISR, the accuracy of the 16-bit +// timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the +// Level 1 cutoff frequency and up to as fast as the CPU allows (over 30kHz in limited testing). +// NOTE: AMASS cutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency. +// NOTE: Current settings are set to overdrive the ISR to no more than 16kHz, balancing CPU overhead +// and timer accuracy. Do not alter these settings unless you know what you are doing. +///#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + #define MAX_AMASS_LEVEL 3 + // AMASS_LEVEL0: Normal operation. No AMASS. No upper cutoff frequency. Starts at LEVEL1 cutoff frequency. + // Note ESP32 use F_STEPPER_TIMER rather than the AVR F_CPU + #define AMASS_LEVEL1 (F_STEPPER_TIMER/8000) // Over-drives ISR (x2). Defined as F_CPU/(Cutoff frequency in Hz) + #define AMASS_LEVEL2 (F_STEPPER_TIMER/4000) // Over-drives ISR (x4) + #define AMASS_LEVEL3 (F_STEPPER_TIMER/2000) // Over-drives ISR (x8) + + #if MAX_AMASS_LEVEL <= 0 + error "AMASS must have 1 or more levels to operate correctly." + #endif +//#endif + +#define STEP_TIMER_GROUP TIMER_GROUP_0 +#define STEP_TIMER_INDEX TIMER_0 + +// esp32 work around for diable in main loop +extern uint64_t stepper_idle_counter; +extern bool stepper_idle; + +extern uint8_t ganged_mode; + +// -- Task handles for use in the notifications +void IRAM_ATTR onSteppertimer(); +void IRAM_ATTR onStepperOffTimer(); + +#ifdef USE_RMT_STEPS + void initRMT(); +#endif + +void stepper_init(); + +// Enable steppers, but cycle does not start unless called by motion control or realtime command. +void st_wake_up(); + +// Immediately disables steppers +void st_go_idle(); + +// Generate the step and direction port invert masks. +void st_generate_step_dir_invert_masks(); + +// Reset the stepper subsystem variables +void st_reset(); + +// Changes the run state of the step segment buffer to execute the special parking motion. +void st_parking_setup_buffer(); + +// Restores the step segment buffer to the normal run state after a parking motion. +void st_parking_restore_buffer(); + +// Reloads step segment buffer. Called continuously by realtime execution system. +void st_prep_buffer(); + +// Called by planner_recalculate() when the executing block is updated by the new plan. +void st_update_plan_block_parameters(); + +// Called by realtime status reporting if realtime rate reporting is enabled in config.h. +float st_get_realtime_rate(); + +// disable (or enable) steppers via STEPPERS_DISABLE_PIN +void set_stepper_disable(uint8_t disable); +bool get_stepper_disable(); // returns the state of the pin + +void set_step_pin_on(uint8_t axis, uint8_t isOn); +void set_direction_pin_on(uint8_t axis, uint8_t isOn); +void set_stepper_pins_on(uint8_t onMask); +void set_direction_pins_on(uint8_t onMask); + +void Stepper_Timer_WritePeriod(uint64_t alarm_val); +void Stepper_Timer_Start(); +void Stepper_Timer_Stop(); + +#endif \ No newline at end of file diff --git a/Grbl_Esp32-master/Grbl_Esp32/system.cpp b/Grbl_Esp32-master/Grbl_Esp32/system.cpp new file mode 100644 index 0000000..fbfd226 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/system.cpp @@ -0,0 +1,614 @@ +/* + system.cpp - Header for system level commands and real-time processes + Part of Grbl + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "grbl.h" +#include "config.h" + +xQueueHandle control_sw_queue; // used by control switch debouncing +bool debouncing = false; // debouncing in process + +void system_ini() // Renamed from system_init() due to conflict with esp32 files +{ + // setup control inputs + #ifndef IGNORE_CONTROL_PINS + + #ifdef CONTROL_SAFETY_DOOR_PIN + pinMode(CONTROL_SAFETY_DOOR_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(CONTROL_SAFETY_DOOR_PIN), isr_control_inputs, CHANGE); + #endif + #ifdef CONTROL_RESET_PIN + pinMode(CONTROL_RESET_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(CONTROL_RESET_PIN), isr_control_inputs, CHANGE); + #endif + #ifdef CONTROL_FEED_HOLD_PIN + pinMode(CONTROL_FEED_HOLD_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(CONTROL_FEED_HOLD_PIN), isr_control_inputs, CHANGE); + #endif + #ifdef CONTROL_CYCLE_START_PIN + pinMode(CONTROL_CYCLE_START_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(CONTROL_CYCLE_START_PIN), isr_control_inputs, CHANGE); + #endif + + #ifdef MACRO_BUTTON_0_PIN + grbl_send(CLIENT_SERIAL, "[MSG:Macro Pin 0]\r\n"); + pinMode(MACRO_BUTTON_0_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(MACRO_BUTTON_0_PIN), isr_control_inputs, CHANGE); + #endif + + #ifdef MACRO_BUTTON_1_PIN + grbl_send(CLIENT_SERIAL, "[MSG:Macro Pin 1]\r\n"); + pinMode(MACRO_BUTTON_1_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(MACRO_BUTTON_1_PIN), isr_control_inputs, CHANGE); + #endif + + #ifdef MACRO_BUTTON_2_PIN + grbl_send(CLIENT_SERIAL, "[MSG:Macro Pin 2]\r\n"); + pinMode(MACRO_BUTTON_2_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(MACRO_BUTTON_2_PIN), isr_control_inputs, CHANGE); + #endif + + #ifdef MACRO_BUTTON_3_PIN + grbl_send(CLIENT_SERIAL, "[MSG:Macro Pin 3]\r\n"); + pinMode(MACRO_BUTTON_3_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(MACRO_BUTTON_3_PIN), isr_control_inputs, CHANGE); + #endif + + #ifdef ENABLE_CONTROL_SW_DEBOUNCE + // setup task used for debouncing + control_sw_queue = xQueueCreate(10, sizeof( int )); + + xTaskCreate(controlCheckTask, + "controlCheckTask", + 2048, + NULL, + 5, // priority + NULL); + #endif + + #endif + + //customize pin definition if needed + #if (GRBL_SPI_SS != -1) || (GRBL_SPI_MISO != -1) || (GRBL_SPI_MOSI != -1) || (GRBL_SPI_SCK != -1) + SPI.begin(GRBL_SPI_SCK, GRBL_SPI_MISO, GRBL_SPI_MOSI, GRBL_SPI_SS); + #endif + + // Setup USER_DIGITAL_PINs controlled by M62 and M63 + #ifdef USER_DIGITAL_PIN_1 + pinMode(USER_DIGITAL_PIN_1, OUTPUT); + sys_io_control(1<<1, false); // turn off + #endif + + #ifdef USER_DIGITAL_PIN_2 + pinMode(USER_DIGITAL_PIN_2, OUTPUT); + sys_io_control(1<<2, false); // turn off + #endif + + #ifdef USER_DIGITAL_PIN_3 + pinMode(USER_DIGITAL_PIN_3, OUTPUT); + sys_io_control(1<<3, false); // turn off + #endif + + #ifdef USER_DIGITAL_PIN_4 + pinMode(USER_DIGITAL_PIN_4, OUTPUT); + sys_io_control(1<<4, false); // turn off + #endif +} + +#ifdef ENABLE_CONTROL_SW_DEBOUNCE +// this is the debounce task +void controlCheckTask(void *pvParameters) +{ + while(true) { + int evt; + xQueueReceive(control_sw_queue, &evt, portMAX_DELAY); // block until receive queue + vTaskDelay(CONTROL_SW_DEBOUNCE_PERIOD); // delay a while + + uint8_t pin = system_control_get_state(); + if (pin) { + system_exec_control_pin(pin); + } + debouncing = false; + } +} +#endif + +void IRAM_ATTR isr_control_inputs() +{ + #ifdef ENABLE_CONTROL_SW_DEBOUNCE + // we will start a task that will recheck the switches after a small delay + int evt; + if (!debouncing) { // prevent resending until debounce is done + debouncing = true; + xQueueSendFromISR(control_sw_queue, &evt, NULL); + } + #else + uint8_t pin = system_control_get_state(); + system_exec_control_pin(pin); + #endif +} + +// Executes user startup script, if stored. +void system_execute_startup(char *line) +{ + uint8_t n; + for (n=0; n < N_STARTUP_LINE; n++) { + if (!(settings_read_startup_line(n, line))) { + line[0] = 0; + report_execute_startup_message(line,STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); + } else { + if (line[0] != 0) { + uint8_t status_code = gc_execute_line(line, CLIENT_SERIAL); + report_execute_startup_message(line,status_code, CLIENT_SERIAL); + } + } + } +} + +// Directs and executes one line of formatted input from protocol_process. While mostly +// incoming streaming g-code blocks, this also executes Grbl internal commands, such as +// settings, initiating the homing cycle, and toggling switch states. This differs from +// the realtime command module by being susceptible to when Grbl is ready to execute the +// next line during a cycle, so for switches like block delete, the switch only effects +// the lines that are processed afterward, not necessarily real-time during a cycle, +// since there are motions already stored in the buffer. However, this 'lag' should not +// be an issue, since these commands are not typically used during a cycle. +uint8_t system_execute_line(char *line, uint8_t client) +{ + uint8_t char_counter = 1; + uint8_t helper_var = 0; // Helper variable + float parameter, value; + + switch( line[char_counter] ) { + case 0 : report_grbl_help(client); break; + case 'J' : // Jogging + // Execute only if in IDLE or JOG states. + if (sys.state != STATE_IDLE && sys.state != STATE_JOG) { return(STATUS_IDLE_ERROR); } + if(line[2] != '=') { return(STATUS_INVALID_STATEMENT); } + return(gc_execute_line(line, client)); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. + break; + case '$': case 'G': case 'C': case 'X': + if ( line[2] != 0 ) { return(STATUS_INVALID_STATEMENT); } + switch( line[1] ) { + case '$' : // Prints Grbl settings + if ( sys.state & (STATE_CYCLE | STATE_HOLD) ) { return(STATUS_IDLE_ERROR); } // Block during cycle. Takes too long to print. + else { report_grbl_settings(client); } + break; + case 'G' : // Prints gcode parser state + // TODO: Move this to realtime commands for GUIs to request this data during suspend-state. + report_gcode_modes(client); + break; + case 'C' : // Set check g-code mode [IDLE/CHECK] + // Perform reset when toggling off. Check g-code mode should only work if Grbl + // is idle and ready, regardless of alarm locks. This is mainly to keep things + // simple and consistent. + if ( sys.state == STATE_CHECK_MODE ) { + mc_reset(); + report_feedback_message(MESSAGE_DISABLED); + } else { + if (sys.state) { return(STATUS_IDLE_ERROR); } // Requires no alarm mode. + sys.state = STATE_CHECK_MODE; + report_feedback_message(MESSAGE_ENABLED); + } + break; + case 'X' : // Disable alarm lock [ALARM] + if (sys.state == STATE_ALARM) { + // Block if safety door is ajar. + if (system_check_safety_door_ajar()) { return(STATUS_CHECK_DOOR); } + report_feedback_message(MESSAGE_ALARM_UNLOCK); + sys.state = STATE_IDLE; + // Don't run startup script. Prevents stored moves in startup from causing accidents. + } // Otherwise, no effect. + break; + } + break; + default : + // Block any system command that requires the state as IDLE/ALARM. (i.e. EEPROM, homing) + if ( !(sys.state == STATE_IDLE || sys.state == STATE_ALARM) ) { return(STATUS_IDLE_ERROR); } + switch( line[1] ) { + case '#' : // Print Grbl NGC parameters + if ( line[2] != 0 ) { return(STATUS_INVALID_STATEMENT); } + else { report_ngc_parameters(client); } + break; + case 'H' : // Perform homing cycle [IDLE/ALARM] $H + if (bit_isfalse(settings.flags,BITFLAG_HOMING_ENABLE)) {return(STATUS_SETTING_DISABLED); } + if (system_check_safety_door_ajar()) { return(STATUS_CHECK_DOOR); } // Block if safety door is ajar. + sys.state = STATE_HOMING; // Set system state variable + if (line[2] == 0) { + mc_homing_cycle(HOMING_CYCLE_ALL); + #ifdef HOMING_SINGLE_AXIS_COMMANDS + } else if (line[3] == 0) { + switch (line[2]) { + case 'X': mc_homing_cycle(HOMING_CYCLE_X); break; + case 'Y': mc_homing_cycle(HOMING_CYCLE_Y); break; + case 'Z': mc_homing_cycle(HOMING_CYCLE_Z); break; + case 'A': mc_homing_cycle(HOMING_CYCLE_A); break; + case 'B': mc_homing_cycle(HOMING_CYCLE_B); break; + case 'C': mc_homing_cycle(HOMING_CYCLE_C); break; + default: return(STATUS_INVALID_STATEMENT); + } + #endif + } else { return(STATUS_INVALID_STATEMENT); } + if (!sys.abort) { // Execute startup scripts after successful homing. + sys.state = STATE_IDLE; // Set to IDLE when complete. + st_go_idle(); // Set steppers to the settings idle state before returning. + if (line[2] == 0) { system_execute_startup(line); } + } + break; + case 'S' : // Puts Grbl to sleep [IDLE/ALARM] + if ((line[2] != 'L') || (line[3] != 'P') || (line[4] != 0)) { return(STATUS_INVALID_STATEMENT); } + system_set_exec_state_flag(EXEC_SLEEP); // Set to execute sleep mode immediately + break; + case 'I' : // Print or store build info. [IDLE/ALARM] + if ( line[++char_counter] == 0 ) { + settings_read_build_info(line); + report_build_info(line, client); + #ifdef ENABLE_BUILD_INFO_WRITE_COMMAND + } else { // Store startup line [IDLE/ALARM] + if(line[char_counter++] != '=') { return(STATUS_INVALID_STATEMENT); } + helper_var = char_counter; // Set helper variable as counter to start of user info line. + do { + line[char_counter-helper_var] = line[char_counter]; + } while (line[char_counter++] != 0); + settings_store_build_info(line); + #endif + } + break; + case 'R' : // Restore defaults [IDLE/ALARM] + if ((line[2] != 'S') || (line[3] != 'T') || (line[4] != '=') || (line[6] != 0)) { return(STATUS_INVALID_STATEMENT); } + switch (line[5]) { + #ifdef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS + case '$': settings_restore(SETTINGS_RESTORE_DEFAULTS); break; + #endif + #ifdef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS + case '#': settings_restore(SETTINGS_RESTORE_PARAMETERS); break; + #endif + #ifdef ENABLE_RESTORE_EEPROM_WIPE_ALL + case '*': settings_restore(SETTINGS_RESTORE_ALL); break; + #endif + #if defined(ENABLE_BLUETOOTH) || defined(ENABLE_WIFI) + case '@': settings_restore(SETTINGS_RESTORE_WIFI_SETTINGS); break; + #endif + default: return(STATUS_INVALID_STATEMENT); + } + report_feedback_message(MESSAGE_RESTORE_DEFAULTS); + mc_reset(); // Force reset to ensure settings are initialized correctly. + break; + case 'N' : // Startup lines. [IDLE/ALARM] + if ( line[++char_counter] == 0 ) { // Print startup lines + for (helper_var=0; helper_var < N_STARTUP_LINE; helper_var++) { + if (!(settings_read_startup_line(helper_var, line))) { + report_status_message(STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); + } else { + report_startup_line(helper_var,line, client); + } + } + break; + } else { // Store startup line [IDLE Only] Prevents motion during ALARM. + if (sys.state != STATE_IDLE) { return(STATUS_IDLE_ERROR); } // Store only when idle. + helper_var = true; // Set helper_var to flag storing method. + // No break. Continues into default: to read remaining command characters. + } + + default : // Storing setting methods [IDLE/ALARM] + if(!read_float(line, &char_counter, ¶meter)) { return(STATUS_BAD_NUMBER_FORMAT); } + if(line[char_counter++] != '=') { return(STATUS_INVALID_STATEMENT); } + if (helper_var) { // Store startup line + // Prepare sending gcode block to gcode parser by shifting all characters + helper_var = char_counter; // Set helper variable as counter to start of gcode block + do { + line[char_counter-helper_var] = line[char_counter]; + } while (line[char_counter++] != 0); + // Execute gcode block to ensure block is valid. + helper_var = gc_execute_line(line, CLIENT_SERIAL); // Set helper_var to returned status code. + if (helper_var) { return(helper_var); } + else { + helper_var = trunc(parameter); // Set helper_var to int value of parameter + settings_store_startup_line(helper_var,line); + } + } else { // Store global setting. + if(!read_float(line, &char_counter, &value)) { return(STATUS_BAD_NUMBER_FORMAT); } + if((line[char_counter] != 0) || (parameter > 255)) { return(STATUS_INVALID_STATEMENT); } + return(settings_store_global_setting((uint8_t)parameter, value)); + } + } + } + return(STATUS_OK); // If '$' command makes it to here, then everything's ok. +} + + +// Returns if safety door is ajar(T) or closed(F), based on pin state. +uint8_t system_check_safety_door_ajar() +{ + #ifdef ENABLE_SAFETY_DOOR_INPUT_PIN + return(system_control_get_state() & CONTROL_PIN_INDEX_SAFETY_DOOR); + #else + return(false); // Input pin not enabled, so just return that it's closed. + #endif +} + +// Special handlers for setting and clearing Grbl's real-time execution flags. +void system_set_exec_state_flag(uint8_t mask) { + // TODO uint8_t sreg = SREG; + // TODO cli(); + sys_rt_exec_state |= (mask); + // TODO SREG = sreg; +} + +void system_clear_exec_state_flag(uint8_t mask) { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_state &= ~(mask); + //SREG = sreg; +} + +void system_set_exec_alarm(uint8_t code) { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_alarm = code; + //SREG = sreg; +} + +void system_clear_exec_alarm() { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_alarm = 0; + //SREG = sreg; +} + +void system_set_exec_motion_override_flag(uint8_t mask) { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_motion_override |= (mask); + //SREG = sreg; +} + +void system_set_exec_accessory_override_flag(uint8_t mask) { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_accessory_override |= (mask); + //SREG = sreg; +} + +void system_clear_exec_motion_overrides() { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_motion_override = 0; + //SREG = sreg; +} + +void system_clear_exec_accessory_overrides() { + //uint8_t sreg = SREG; + //cli(); + sys_rt_exec_accessory_override = 0; + //SREG = sreg; +} + + +void system_flag_wco_change() +{ + #ifdef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE + protocol_buffer_synchronize(); + #endif + sys.report_wco_counter = 0; +} + + +// Returns machine position of axis 'idx'. Must be sent a 'step' array. +// NOTE: If motor steps and machine position are not in the same coordinate frame, this function +// serves as a central place to compute the transformation. +float system_convert_axis_steps_to_mpos(int32_t *steps, uint8_t idx) +{ + float pos; + #ifdef COREXY + if (idx==X_AXIS) { + pos = (float)system_convert_corexy_to_x_axis_steps(steps) / settings.steps_per_mm[idx]; + } else if (idx==Y_AXIS) { + pos = (float)system_convert_corexy_to_y_axis_steps(steps) / settings.steps_per_mm[idx]; + } else { + pos = steps[idx]/settings.steps_per_mm[idx]; + } + #else + pos = steps[idx]/settings.steps_per_mm[idx]; + #endif + return(pos); +} + +void system_convert_array_steps_to_mpos(float *position, int32_t *steps) +{ + uint8_t idx; + for (idx=0; idx -settings.max_travel[idx]) { return(true); } + } else { + if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { return(true); } + } + #else + // NOTE: max_travel is stored as negative + #ifdef HOMING_FORCE_POSITIVE_SPACE + if (target[idx] < 0 || target[idx] > -settings.max_travel[idx]) { return(true); } + #else + if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { return(true); } + #endif + #endif + } + return(false); +} + +// Returns control pin state as a uint8 bitfield. Each bit indicates the input pin state, where +// triggered is 1 and not triggered is 0. Invert mask is applied. Bitfield organization is +// defined by the CONTROL_PIN_INDEX in the header file. +uint8_t system_control_get_state() +{ + uint8_t defined_pin_mask = 0; // a mask of defined pins + + #ifdef IGNORE_CONTROL_PINS + return 0; + #endif + + uint8_t control_state = 0; + #ifdef CONTROL_SAFETY_DOOR_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_SAFETY_DOOR; + if (digitalRead(CONTROL_SAFETY_DOOR_PIN)) { control_state |= CONTROL_PIN_INDEX_SAFETY_DOOR; } + #endif + #ifdef CONTROL_RESET_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_RESET; + if (digitalRead(CONTROL_RESET_PIN)) { control_state |= CONTROL_PIN_INDEX_RESET; } + #endif + #ifdef CONTROL_FEED_HOLD_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_FEED_HOLD; + if (digitalRead(CONTROL_FEED_HOLD_PIN)) { control_state |= CONTROL_PIN_INDEX_FEED_HOLD; } + #endif + #ifdef CONTROL_CYCLE_START_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_CYCLE_START; + if (digitalRead(CONTROL_CYCLE_START_PIN)) { control_state |= CONTROL_PIN_INDEX_CYCLE_START; } + #endif + + #ifdef MACRO_BUTTON_0_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_0; + if (digitalRead(MACRO_BUTTON_0_PIN)) { control_state |= CONTROL_PIN_INDEX_MACRO_0; } + #endif + + #ifdef MACRO_BUTTON_1_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_1; + if (digitalRead(MACRO_BUTTON_1_PIN)) { control_state |= CONTROL_PIN_INDEX_MACRO_1; } + #endif + + #ifdef MACRO_BUTTON_2_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_2; + if (digitalRead(MACRO_BUTTON_2_PIN)) { control_state |= CONTROL_PIN_INDEX_MACRO_2; } + #endif + + #ifdef MACRO_BUTTON_3_PIN + defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_3; + if (digitalRead(MACRO_BUTTON_3_PIN)) { control_state |= CONTROL_PIN_INDEX_MACRO_3; } + #endif + + + #ifdef INVERT_CONTROL_PIN_MASK + control_state ^= (INVERT_CONTROL_PIN_MASK & defined_pin_mask); + #endif + + return(control_state); +} + +// Returns limit pin mask according to Grbl internal axis indexing. +uint8_t get_limit_pin_mask(uint8_t axis_idx) +{ + if ( axis_idx == X_AXIS ) { return((1<. +*/ + +#ifndef system_h + #define system_h + #include "grbl.h" + #include "tdef.h" + +// Define global system variables +typedef struct { + uint8_t state; // Tracks the current system state of Grbl. + uint8_t abort; // System abort flag. Forces exit back to main loop for reset. + uint8_t suspend; // System suspend bitflag variable that manages holds, cancels, and safety door. + uint8_t soft_limit; // Tracks soft limit errors for the state machine. (boolean) + uint8_t step_control; // Governs the step segment generator depending on system state. + uint8_t probe_succeeded; // Tracks if last probing cycle was successful. + uint8_t homing_axis_lock; // Locks axes when limits engage. Used as an axis motion mask in the stepper ISR. + uint8_t f_override; // Feed rate override value in percent + uint8_t r_override; // Rapids override value in percent + uint8_t spindle_speed_ovr; // Spindle speed value in percent + uint8_t spindle_stop_ovr; // Tracks spindle stop override states + uint8_t report_ovr_counter; // Tracks when to add override data to status reports. + uint8_t report_wco_counter; // Tracks when to add work coordinate offset data to status reports. + #ifdef ENABLE_PARKING_OVERRIDE_CONTROL + uint8_t override_ctrl; // Tracks override control states. + #endif + + float spindle_speed; + +} system_t; +extern system_t sys; + + + + +// Define system executor bit map. Used internally by realtime protocol as realtime command flags, +// which notifies the main program to execute the specified realtime command asynchronously. +// NOTE: The system executor uses an unsigned 8-bit volatile variable (8 flag limit.) The default +// flags are always false, so the realtime protocol only needs to check for a non-zero value to +// know when there is a realtime command to execute. +#define EXEC_STATUS_REPORT bit(0) // bitmask 00000001 +#define EXEC_CYCLE_START bit(1) // bitmask 00000010 +#define EXEC_CYCLE_STOP bit(2) // bitmask 00000100 +#define EXEC_FEED_HOLD bit(3) // bitmask 00001000 +#define EXEC_RESET bit(4) // bitmask 00010000 +#define EXEC_SAFETY_DOOR bit(5) // bitmask 00100000 +#define EXEC_MOTION_CANCEL bit(6) // bitmask 01000000 +#define EXEC_SLEEP bit(7) // bitmask 10000000 + +// Alarm executor codes. Valid values (1-255). Zero is reserved. +#define EXEC_ALARM_HARD_LIMIT 1 +#define EXEC_ALARM_SOFT_LIMIT 2 +#define EXEC_ALARM_ABORT_CYCLE 3 +#define EXEC_ALARM_PROBE_FAIL_INITIAL 4 +#define EXEC_ALARM_PROBE_FAIL_CONTACT 5 +#define EXEC_ALARM_HOMING_FAIL_RESET 6 +#define EXEC_ALARM_HOMING_FAIL_DOOR 7 +#define EXEC_ALARM_HOMING_FAIL_PULLOFF 8 +#define EXEC_ALARM_HOMING_FAIL_APPROACH 9 + +// Override bit maps. Realtime bitflags to control feed, rapid, spindle, and coolant overrides. +// Spindle/coolant and feed/rapids are separated into two controlling flag variables. +#define EXEC_FEED_OVR_RESET bit(0) +#define EXEC_FEED_OVR_COARSE_PLUS bit(1) +#define EXEC_FEED_OVR_COARSE_MINUS bit(2) +#define EXEC_FEED_OVR_FINE_PLUS bit(3) +#define EXEC_FEED_OVR_FINE_MINUS bit(4) +#define EXEC_RAPID_OVR_RESET bit(5) +#define EXEC_RAPID_OVR_MEDIUM bit(6) +#define EXEC_RAPID_OVR_LOW bit(7) +// #define EXEC_RAPID_OVR_EXTRA_LOW bit(*) // *NOT SUPPORTED* + +#define EXEC_SPINDLE_OVR_RESET bit(0) +#define EXEC_SPINDLE_OVR_COARSE_PLUS bit(1) +#define EXEC_SPINDLE_OVR_COARSE_MINUS bit(2) +#define EXEC_SPINDLE_OVR_FINE_PLUS bit(3) +#define EXEC_SPINDLE_OVR_FINE_MINUS bit(4) +#define EXEC_SPINDLE_OVR_STOP bit(5) +#define EXEC_COOLANT_FLOOD_OVR_TOGGLE bit(6) +#define EXEC_COOLANT_MIST_OVR_TOGGLE bit(7) + +// Define system state bit map. The state variable primarily tracks the individual functions +// of Grbl to manage each without overlapping. It is also used as a messaging flag for +// critical events. +#define STATE_IDLE 0 // Must be zero. No flags. +#define STATE_ALARM bit(0) // In alarm state. Locks out all g-code processes. Allows settings access. +#define STATE_CHECK_MODE bit(1) // G-code check mode. Locks out planner and motion only. +#define STATE_HOMING bit(2) // Performing homing cycle +#define STATE_CYCLE bit(3) // Cycle is running or motions are being executed. +#define STATE_HOLD bit(4) // Active feed hold +#define STATE_JOG bit(5) // Jogging mode. +#define STATE_SAFETY_DOOR bit(6) // Safety door is ajar. Feed holds and de-energizes system. +#define STATE_SLEEP bit(7) // Sleep state. + +// Define system suspend flags. Used in various ways to manage suspend states and procedures. +#define SUSPEND_DISABLE 0 // Must be zero. +#define SUSPEND_HOLD_COMPLETE bit(0) // Indicates initial feed hold is complete. +#define SUSPEND_RESTART_RETRACT bit(1) // Flag to indicate a retract from a restore parking motion. +#define SUSPEND_RETRACT_COMPLETE bit(2) // (Safety door only) Indicates retraction and de-energizing is complete. +#define SUSPEND_INITIATE_RESTORE bit(3) // (Safety door only) Flag to initiate resume procedures from a cycle start. +#define SUSPEND_RESTORE_COMPLETE bit(4) // (Safety door only) Indicates ready to resume normal operation. +#define SUSPEND_SAFETY_DOOR_AJAR bit(5) // Tracks safety door state for resuming. +#define SUSPEND_MOTION_CANCEL bit(6) // Indicates a canceled resume motion. Currently used by probing routine. +#define SUSPEND_JOG_CANCEL bit(7) // Indicates a jog cancel in process and to reset buffers when complete. + +// Define step segment generator state flags. +#define STEP_CONTROL_NORMAL_OP 0 // Must be zero. +#define STEP_CONTROL_END_MOTION bit(0) +#define STEP_CONTROL_EXECUTE_HOLD bit(1) +#define STEP_CONTROL_EXECUTE_SYS_MOTION bit(2) +#define STEP_CONTROL_UPDATE_SPINDLE_PWM bit(3) + +// Define control pin index for Grbl internal use. Pin maps may change, but these values don't. +//#ifdef ENABLE_SAFETY_DOOR_INPUT_PIN + #define N_CONTROL_PIN 4 + #define CONTROL_PIN_INDEX_SAFETY_DOOR bit(0) + #define CONTROL_PIN_INDEX_RESET bit(1) + #define CONTROL_PIN_INDEX_FEED_HOLD bit(2) + #define CONTROL_PIN_INDEX_CYCLE_START bit(3) + #define CONTROL_PIN_INDEX_MACRO_0 bit(4) + #define CONTROL_PIN_INDEX_MACRO_1 bit(5) + #define CONTROL_PIN_INDEX_MACRO_2 bit(6) + #define CONTROL_PIN_INDEX_MACRO_3 bit(7) +//#else + //#define N_CONTROL_PIN 3 + //#define CONTROL_PIN_INDEX_RESET bit(0) + //#define CONTROL_PIN_INDEX_FEED_HOLD bit(1) + //#define CONTROL_PIN_INDEX_CYCLE_START bit(2) +//#endif + +// Define spindle stop override control states. +#define SPINDLE_STOP_OVR_DISABLED 0 // Must be zero. +#define SPINDLE_STOP_OVR_ENABLED bit(0) +#define SPINDLE_STOP_OVR_INITIATE bit(1) +#define SPINDLE_STOP_OVR_RESTORE bit(2) +#define SPINDLE_STOP_OVR_RESTORE_CYCLE bit(3) + + + + +// NOTE: These position variables may need to be declared as volatiles, if problems arise. +extern int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. +extern int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. + +extern volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. +extern volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. +extern volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +extern volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. +extern volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. + +#ifdef DEBUG + #define EXEC_DEBUG_REPORT bit(0) + extern volatile uint8_t sys_rt_exec_debug; +#endif + + +void system_ini(); // Renamed from system_init() due to conflict with esp32 files + +// Returns bitfield of control pin states, organized by CONTROL_PIN_INDEX. (1=triggered, 0=not triggered). +uint8_t system_control_get_state(); + +// Returns if safety door is ajar(T) or closed(F), based on pin state. +uint8_t system_check_safety_door_ajar(); + +void isr_control_inputs(); + +// Special handlers for setting and clearing Grbl's real-time execution flags. +void system_set_exec_state_flag(uint8_t mask); +void system_clear_exec_state_flag(uint8_t mask); +void system_set_exec_alarm(uint8_t code); +void system_clear_exec_alarm(); +void system_set_exec_motion_override_flag(uint8_t mask); +void system_set_exec_accessory_override_flag(uint8_t mask); +void system_clear_exec_motion_overrides(); +void system_clear_exec_accessory_overrides(); + +// Execute the startup script lines stored in EEPROM upon initialization +void system_execute_startup(char *line); +uint8_t system_execute_line(char *line, uint8_t client); + +void system_flag_wco_change(); + +// Returns machine position of axis 'idx'. Must be sent a 'step' array. +float system_convert_axis_steps_to_mpos(int32_t *steps, uint8_t idx); + +// Updates a machine 'position' array based on the 'step' array sent. +void system_convert_array_steps_to_mpos(float *position, int32_t *steps); + +// Checks and reports if target array exceeds machine travel limits. +uint8_t system_check_travel_limits(float *target); +uint8_t get_limit_pin_mask(uint8_t axis_idx); + +// Special handlers for setting and clearing Grbl's real-time execution flags. +void system_set_exec_state_flag(uint8_t mask); +void system_clear_exec_state_flag(uint8_t mask); +void system_set_exec_alarm(uint8_t code); +void system_clear_exec_alarm(); +void system_set_exec_motion_override_flag(uint8_t mask); +void system_set_exec_accessory_override_flag(uint8_t mask); +void system_clear_exec_motion_overrides(); +void system_clear_exec_accessory_overrides(); + + +int32_t system_convert_corexy_to_x_axis_steps(int32_t *steps); +int32_t system_convert_corexy_to_y_axis_steps(int32_t *steps); + +// A task that runs after a control switch interrupt for debouncing. +void controlCheckTask(void *pvParameters); +void system_exec_control_pin(uint8_t pin); + +void sys_io_control(uint8_t io_num_mask, bool turnOn); + + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/tdef.h b/Grbl_Esp32-master/Grbl_Esp32/tdef.h new file mode 100644 index 0000000..5e73f4c --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/tdef.h @@ -0,0 +1,7 @@ +#ifndef tdef_h +#define tdef_h + +#include "grbl.h" + + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/telnet_server.cpp b/Grbl_Esp32-master/Grbl_Esp32/telnet_server.cpp new file mode 100644 index 0000000..06a99a7 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/telnet_server.cpp @@ -0,0 +1,239 @@ +/* + telnet_server.cpp - telnet server functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" + +#if defined (ENABLE_WIFI) && defined (ENABLE_TELNET) + +#include "wifiservices.h" + +#include "grbl.h" + +#include "telnet_server.h" +#include "wificonfig.h" +#include +#include +#include "report.h" +#include "commands.h" + + +Telnet_Server telnet_server; +bool Telnet_Server::_setupdone = false; +uint16_t Telnet_Server::_port = 0; +WiFiServer * Telnet_Server::_telnetserver = NULL; +WiFiClient Telnet_Server::_telnetClients[MAX_TLNT_CLIENTS]; +#ifdef ENABLE_TELNET_WELCOME_MSG +IPAddress Telnet_Server::_telnetClientsIP[MAX_TLNT_CLIENTS]; +#endif + +Telnet_Server::Telnet_Server(){ + _RXbufferSize = 0; + _RXbufferpos = 0; +} +Telnet_Server::~Telnet_Server(){ + end(); +} + + +bool Telnet_Server::begin(){ + + bool no_error = true; + end(); + Preferences prefs; + _RXbufferSize = 0; + _RXbufferpos = 0;; + prefs.begin(NAMESPACE, true); + int8_t penabled = prefs.getChar(TELNET_ENABLE_ENTRY, DEFAULT_TELNET_STATE); + //Get telnet port + _port = prefs.getUShort(TELNET_PORT_ENTRY, DEFAULT_TELNETSERVER_PORT); + prefs.end(); + + if (penabled == 0) return false; + //create instance + _telnetserver= new WiFiServer(_port, MAX_TLNT_CLIENTS); + _telnetserver->setNoDelay(true); + String s = "[MSG:TELNET Started " + String(_port) + "]\r\n"; + grbl_send(CLIENT_ALL,(char *)s.c_str()); + //start telnet server + _telnetserver->begin(); + _setupdone = true; + return no_error; +} + +void Telnet_Server::end(){ + _setupdone = false; + _RXbufferSize = 0; + _RXbufferpos = 0; + if (_telnetserver) { + delete _telnetserver; + _telnetserver = NULL; + } +} + +void Telnet_Server::clearClients(){ + //check if there are any new clients + if (_telnetserver->hasClient()){ + uint8_t i; + for(i = 0; i < MAX_TLNT_CLIENTS; i++){ + //find free/disconnected spot + if (!_telnetClients[i] || !_telnetClients[i].connected()){ +#ifdef ENABLE_TELNET_WELCOME_MSG + _telnetClientsIP[i] = IPAddress(0, 0, 0, 0); +#endif + if(_telnetClients[i]) _telnetClients[i].stop(); + _telnetClients[i] = _telnetserver->available(); + break; + } + } + if (i >= MAX_TLNT_CLIENTS) { + //no free/disconnected spot so reject + _telnetserver->available().stop(); + } + } +} + +size_t Telnet_Server::write(const uint8_t *buffer, size_t size){ + + size_t wsize = 0; + if ( !_setupdone || _telnetserver == NULL) { + log_d("[TELNET out blocked]"); + return 0; + } + clearClients(); + //log_d("[TELNET out]"); + //push UART data to all connected telnet clients + for(uint8_t i = 0; i < MAX_TLNT_CLIENTS; i++){ + if (_telnetClients[i] && _telnetClients[i].connected()){ + //log_d("[TELNET out connected]"); + wsize = _telnetClients[i].write(buffer, size); + COMMANDS::wait(0); + } + } + return wsize; +} + +void Telnet_Server::handle(){ + COMMANDS::wait(0); + //check if can read + if ( !_setupdone || _telnetserver == NULL) { + return; + } + clearClients(); + //check clients for data + //uint8_t c; + for(uint8_t i = 0; i < MAX_TLNT_CLIENTS; i++){ + if (_telnetClients[i] && _telnetClients[i].connected()){ +#ifdef ENABLE_TELNET_WELCOME_MSG + if (_telnetClientsIP[i] != _telnetClients[i].remoteIP()){ + report_init_message(CLIENT_TELNET); + _telnetClientsIP[i] = _telnetClients[i].remoteIP(); + } +#endif + if(_telnetClients[i].available()){ + uint8_t buf[1024]; + COMMANDS::wait(0); + int readlen = _telnetClients[i].available(); + int writelen = TELNETRXBUFFERSIZE - available(); + if (readlen > 1024) readlen = 1024; + if (readlen > writelen) readlen = writelen; + if (readlen > 0) { + _telnetClients[i].read(buf, readlen); + push(buf, readlen); + } + return; + } + } + else { + if (_telnetClients[i]) { +#ifdef ENABLE_TELNET_WELCOME_MSG + _telnetClientsIP[i] = IPAddress(0, 0, 0, 0); +#endif + _telnetClients[i].stop(); + } + } + COMMANDS::wait(0); + } +} + +int Telnet_Server::peek(void){ + if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; + else return -1; +} + +int Telnet_Server::available(){ + return _RXbufferSize; +} + +int Telnet_Server::get_rx_buffer_available(){ + return TELNETRXBUFFERSIZE - _RXbufferSize; +} + +bool Telnet_Server::push (uint8_t data){ + log_i("[TELNET]push %c",data); + if ((1 + _RXbufferSize) <= TELNETRXBUFFERSIZE){ + int current = _RXbufferpos + _RXbufferSize; + if (current > TELNETRXBUFFERSIZE) current = current - TELNETRXBUFFERSIZE; + if (current > (TELNETRXBUFFERSIZE-1)) current = 0; + _RXbuffer[current] = data; + _RXbufferSize++; + log_i("[TELNET]buffer size %d",_RXbufferSize); + return true; + } + return false; +} + +bool Telnet_Server::push (const uint8_t * data, int data_size){ + if ((data_size + _RXbufferSize) <= TELNETRXBUFFERSIZE){ + int data_processed = 0; + int current = _RXbufferpos + _RXbufferSize; + if (current > TELNETRXBUFFERSIZE) current = current - TELNETRXBUFFERSIZE; + for (int i = 0; i < data_size; i++){ + if (current > (TELNETRXBUFFERSIZE-1)) current = 0; + if (char(data[i]) != '\r') { + _RXbuffer[current] = data[i]; + current ++; + data_processed++; + } + COMMANDS::wait(0); + //vTaskDelay(1 / portTICK_RATE_MS); // Yield to other tasks + } + _RXbufferSize+=data_processed; + return true; + } + return false; +} + +int Telnet_Server::read(void){ + + if (_RXbufferSize > 0) { + int v = _RXbuffer[_RXbufferpos]; + //log_d("[TELNET]read %c",char(v)); + _RXbufferpos++; + if (_RXbufferpos > (TELNETRXBUFFERSIZE-1))_RXbufferpos = 0; + _RXbufferSize--; + return v; + } else return -1; +} + +#endif // Enable TELNET && ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/telnet_server.h b/Grbl_Esp32-master/Grbl_Esp32/telnet_server.h new file mode 100644 index 0000000..10d5703 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/telnet_server.h @@ -0,0 +1,68 @@ +/* + telnet_server.h - telnet service functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//how many clients should be able to telnet to this ESP32 +#define MAX_TLNT_CLIENTS 1 + +#ifndef _TELNET_SERVER_H +#define _TELNET_SERVER_H + + +#include "config.h" +class WiFiServer; +class WiFiClient; + +#define TELNETRXBUFFERSIZE 1200 +#define FLUSHTIMEOUT 500 + +class Telnet_Server { + public: + Telnet_Server(); + ~Telnet_Server(); + bool begin(); + void end(); + void handle(); + size_t write(const uint8_t *buffer, size_t size); + int read(void); + int peek(void); + int available(); + int get_rx_buffer_available(); + bool push (uint8_t data); + bool push (const uint8_t * data, int datasize); + static uint16_t port(){return _port;} + private: + static bool _setupdone; + static WiFiServer * _telnetserver; + static WiFiClient _telnetClients[MAX_TLNT_CLIENTS]; +#ifdef ENABLE_TELNET_WELCOME_MSG + static IPAddress _telnetClientsIP[MAX_TLNT_CLIENTS]; +#endif + static uint16_t _port; + void clearClients(); + uint32_t _lastflush; + uint8_t _RXbuffer[TELNETRXBUFFERSIZE]; + uint16_t _RXbufferSize; + uint16_t _RXbufferpos; +}; + +extern Telnet_Server telnet_server; + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/tests/parsetest.nc b/Grbl_Esp32-master/Grbl_Esp32/tests/parsetest.nc new file mode 100644 index 0000000..2ef7bee --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/tests/parsetest.nc @@ -0,0 +1,6 @@ +G21 +G90 (A standard comment) +G1 Z3.810 F228.6 ; a LinuxCNC style comment +G0x0x0 (some lowercase) +G0 X10 (internal comment) Y0 +G0X0 (internal comment; with semi colon) Y0Z3 diff --git a/Grbl_Esp32-master/Grbl_Esp32/web_server.cpp b/Grbl_Esp32-master/Grbl_Esp32/web_server.cpp new file mode 100644 index 0000000..fdc9be7 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/web_server.cpp @@ -0,0 +1,1889 @@ +/* + web_server.cpp - web server functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" + +#if defined (ENABLE_WIFI) && defined (ENABLE_HTTP) + +#include "wifiservices.h" + +#include "grbl.h" + +#include "commands.h" +#include "espresponse.h" +#include "serial2socket.h" +#include "web_server.h" +#include +#include "wificonfig.h" +#include +#include +#include +#ifdef ENABLE_SD_CARD +#include +#include "grbl_sd.h" +#endif +#include +#include "report.h" +#include +#include +#include +#include +#include +#ifdef ENABLE_MDNS +#include +#endif +#ifdef ENABLE_SSDP +#include +#endif +#ifdef ENABLE_CAPTIVE_PORTAL +#include +const byte DNS_PORT = 53; +DNSServer dnsServer; +#endif +#include + +//embedded response file if no files on SPIFFS +#include "nofile.h" + +//Upload status +typedef enum { + UPLOAD_STATUS_NONE = 0, + UPLOAD_STATUS_FAILED = 1, + UPLOAD_STATUS_CANCELLED = 2, + UPLOAD_STATUS_SUCCESSFUL = 3, + UPLOAD_STATUS_ONGOING = 4 +} upload_status_type; + + +//Default 404 +const char PAGE_404 [] = "\n\nRedirecting... \n\n\n
Unknown page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; +const char PAGE_CAPTIVE [] = "\n\nCaptive Portal \n\n\n
Captive Portal page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; + +//error codes fo upload +#define ESP_ERROR_AUTHENTICATION 1 +#define ESP_ERROR_FILE_CREATION 2 +#define ESP_ERROR_FILE_WRITE 3 +#define ESP_ERROR_UPLOAD 4 +#define ESP_ERROR_NOT_ENOUGH_SPACE 5 +#define ESP_ERROR_UPLOAD_CANCELLED 6 +#define ESP_ERROR_FILE_CLOSE 7 +#define ESP_ERROR_NO_SD 8 + +Web_Server web_server; +bool Web_Server::_setupdone = false; +uint16_t Web_Server::_port = 0; +long Web_Server::_id_connection = 0; +uint8_t Web_Server::_upload_status = UPLOAD_STATUS_NONE; +WebServer * Web_Server::_webserver = NULL; +WebSocketsServer * Web_Server::_socket_server = NULL; +#ifdef ENABLE_AUTHENTICATION +auth_ip * Web_Server::_head = NULL; +uint8_t Web_Server::_nb_ip = 0; +#define MAX_AUTH_IP 10 +#endif +Web_Server::Web_Server(){ + +} +Web_Server::~Web_Server(){ + end(); +} + +long Web_Server::get_client_ID() { + return _id_connection; +} + +bool Web_Server::begin(){ + + bool no_error = true; + _setupdone = false; + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t penabled = prefs.getChar(HTTP_ENABLE_ENTRY, DEFAULT_HTTP_STATE); + //Get http port + _port = prefs.getUShort(HTTP_PORT_ENTRY, DEFAULT_WEBSERVER_PORT); + prefs.end(); + if (penabled == 0) return false; + //create instance + _webserver= new WebServer(_port); +#ifdef ENABLE_AUTHENTICATION + //here the list of headers to be recorded + const char * headerkeys[] = {"Cookie"} ; + size_t headerkeyssize = sizeof (headerkeys) / sizeof (char*); + //ask server to track these headers + _webserver->collectHeaders (headerkeys, headerkeyssize ); +#endif + _socket_server = new WebSocketsServer(_port + 1); + _socket_server->begin(); + _socket_server->onEvent(handle_Websocket_Event); + + + //Websocket output + Serial2Socket.attachWS(_socket_server); + + //events functions + //_web_events->onConnect(handle_onevent_connect); + //events management + // _webserver->addHandler(_web_events); + + //Websocket function + //_web_socket->onEvent(handle_Websocket_Event); + //Websocket management + //_webserver->addHandler(_web_socket); + + //Web server handlers + //trick to catch command line on "/" before file being processed + _webserver->on("/",HTTP_ANY, handle_root); + + //Page not found handler + _webserver->onNotFound (handle_not_found); + + //need to be there even no authentication to say to UI no authentication + _webserver->on("/login", HTTP_ANY, handle_login); + + //web commands + _webserver->on ("/command", HTTP_ANY, handle_web_command); + _webserver->on ("/command_silent", HTTP_ANY, handle_web_command_silent); + + //SPIFFS + _webserver->on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload); + + //web update + _webserver->on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload); + +#ifdef ENABLE_SD_CARD + //Direct SD management + _webserver->on("/upload", HTTP_ANY, handle_direct_SDFileList,SDFile_direct_upload); + //_webserver->on("/SD", HTTP_ANY, handle_SDCARD); +#endif + +#ifdef ENABLE_CAPTIVE_PORTAL + if(WiFi.getMode() != WIFI_STA){ + // if DNSServer is started with "*" for domain name, it will reply with + // provided IP to all DNS request + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + grbl_send(CLIENT_ALL,"[MSG:Captive Portal Started]\r\n"); + _webserver->on ("/generate_204", HTTP_ANY, handle_root); + _webserver->on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, handle_root); + //do not forget the / at the end + _webserver->on ("/fwlink/", HTTP_ANY, handle_root); + } +#endif + +#ifdef ENABLE_SSDP + //SSDP service presentation + if(WiFi.getMode() == WIFI_STA){ + _webserver->on ("/description.xml", HTTP_GET, handle_SSDP); + //Add specific for SSDP + SSDP.setSchemaURL ("description.xml"); + SSDP.setHTTPPort (_port); + SSDP.setName (wifi_config.Hostname()); + SSDP.setURL ("/"); + SSDP.setDeviceType ("upnp:rootdevice"); + /*Any customization could be here + SSDP.setModelName (ESP32_MODEL_NAME); + SSDP.setModelURL (ESP32_MODEL_URL); + SSDP.setModelNumber (ESP_MODEL_NUMBER); + SSDP.setManufacturer (ESP_MANUFACTURER_NAME); + SSDP.setManufacturerURL (ESP_MANUFACTURER_URL); + */ + + //Start SSDP + grbl_send(CLIENT_ALL,"[MSG:SSDP Started]\r\n"); + SSDP.begin(); + } +#endif + grbl_send(CLIENT_ALL,"[MSG:HTTP Started]\r\n"); + //start webserver + _webserver->begin(); +#ifdef ENABLE_MDNS + //add mDNS + if(WiFi.getMode() == WIFI_STA){ + MDNS.addService("http","tcp",_port); + } +#endif + _setupdone = true; + return no_error; +} + +void Web_Server::end(){ + _setupdone = false; +#ifdef ENABLE_SSDP + SSDP.end(); +#endif //ENABLE_SSDP +#ifdef ENABLE_MDNS + //remove mDNS + mdns_service_remove("_http", "_tcp"); +#endif + if (_socket_server) { + delete _socket_server; + _socket_server = NULL; + } + if (_webserver) { + delete _webserver; + _webserver = NULL; + } +#ifdef ENABLE_AUTHENTICATION + while (_head) { + auth_ip * current = _head; + _head = _head->_next; + delete current; + } + _nb_ip = 0; +#endif +} + +//Root of Webserver///////////////////////////////////////////////////// + +void Web_Server::handle_root() +{ + String path = "/index.html"; + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + //if have a index.html or gzip version this is default root page + if((SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) && !_webserver->hasArg("forcefallback") && _webserver->arg("forcefallback")!="yes") { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } + //if no lets launch the default content + _webserver->sendHeader("Content-Encoding", "gzip"); + _webserver->send_P(200,"text/html",PAGE_NOFILES,PAGE_NOFILES_SIZE); +} + +//Handle not registred path on SPIFFS neither SD /////////////////////// +void Web_Server:: handle_not_found() +{ + if (is_authenticated() == LEVEL_GUEST) { + _webserver->sendContent_P("HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"); + //_webserver->client().stop(); + return; + } + bool page_not_found = false; + String path = _webserver->urlDecode(_webserver->uri()); + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + +#ifdef ENABLE_SD_CARD + if ((path.substring(0,4) == "/SD/")) { + //remove /SD + path = path.substring(3); + if(SD.exists((char *)pathWithGz.c_str()) || SD.exists((char *)path.c_str())) { + if(SD.exists((char *)pathWithGz.c_str())) { + path = pathWithGz; + } + File datafile = SD.open((char *)path.c_str()); + if (datafile) { + vTaskDelay(1 / portTICK_RATE_MS); + size_t totalFileSize = datafile.size(); + size_t i = 0; + bool done = false; + _webserver->setContentLength(totalFileSize); + _webserver->send(200, contentType, ""); + uint8_t buf[1024]; + while (!done){ + vTaskDelay(1 / portTICK_RATE_MS); + int v = datafile.read(buf,1024); + if ((v == -1) || (v == 0)) { + done = true; + } else { + _webserver->client().write(buf,1024); + i+=v; + } + if (i >= totalFileSize) done = true; + } + datafile.close(); + if ( i != totalFileSize) { + //error: TBD + } + return; + } + } + String content = "cannot find "; + content+=path; + _webserver->send(404,"text/plain",content.c_str()); + return; + } else +#endif + if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } else { + page_not_found = true; + } + + if (page_not_found ) { +#ifdef ENABLE_CAPTIVE_PORTAL + if (WiFi.getMode()!=WIFI_STA ) { + String contentType= PAGE_CAPTIVE; + String stmp = WiFi.softAPIP().toString(); + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if (_port != 80) { + stmp+=":"; + stmp+=String(_port); + } + contentType.replace(KEY_IP,stmp); + contentType.replace(KEY_IP,stmp); + contentType.replace(KEY_QUERY,_webserver->uri()); + _webserver->send(200,"text/html",contentType); + //_webserver->sendContent_P(NOT_AUTH_NF); + //_webserver->client().stop(); + return; + } +#endif + path = "/404.htm"; + contentType = getContentType(path); + pathWithGz = path + ".gz"; + if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + + } else { + //if not template use default page + contentType = PAGE_404; + String stmp; + if (WiFi.getMode()==WIFI_STA ) { + stmp=WiFi.localIP().toString(); + } else { + stmp=WiFi.softAPIP().toString(); + } + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if ( _port != 80) { + stmp+=":"; + stmp+=String(_port); + } + contentType.replace(KEY_IP,stmp); + contentType.replace(KEY_QUERY,_webserver->uri()); + _webserver->send(200,"text/html",contentType); + } + } +} + +#ifdef ENABLE_SSDP +//http SSDP xml presentation +void Web_Server::handle_SSDP () +{ + StreamString sschema ; + if (sschema.reserve (1024) ) { + String templ = "" + "" + "" + "1" + "0" + "" + "http://%s:%u/" + "" + "upnp:rootdevice" + "%s" + "/" + "%s" + "ESP32" + "Marlin" + "http://espressif.com/en/products/hardware/esp-wroom-32/overview" + "Espressif Systems" + "http://espressif.com" + "uuid:%s" + "" + "\r\n" + "\r\n"; + char uuid[37]; + String sip = WiFi.localIP().toString(); + uint32_t chipId = (uint16_t) (ESP.getEfuseMac() >> 32); + sprintf (uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t) ( (chipId >> 16) & 0xff), + (uint16_t) ( (chipId >> 8) & 0xff), + (uint16_t) chipId & 0xff ); + String serialNumber = String (chipId); + sschema.printf (templ.c_str(), + sip.c_str(), + _port, + wifi_config.Hostname().c_str(), + serialNumber.c_str(), + uuid); + _webserver->send (200, "text/xml", (String) sschema); + } else { + _webserver->send (500); + } +} +#endif + +bool Web_Server::is_realtime_cmd(char c){ + if (c == CMD_STATUS_REPORT) return true; + if (c == CMD_CYCLE_START) return true; + if (c == CMD_RESET) return true; + if (c == CMD_FEED_HOLD) return true; + if (c == CMD_SAFETY_DOOR) return true; + if (c == CMD_JOG_CANCEL) return true; + if (c == CMD_DEBUG_REPORT) return true; + if (c == CMD_FEED_OVR_RESET) return true; + if (c == CMD_FEED_OVR_COARSE_PLUS) return true; + if (c == CMD_FEED_OVR_COARSE_MINUS) return true; + if (c == CMD_FEED_OVR_FINE_PLUS) return true; + if (c == CMD_FEED_OVR_FINE_MINUS) return true; + if (c == CMD_RAPID_OVR_RESET) return true; + if (c == CMD_RAPID_OVR_MEDIUM) return true; + if (c == CMD_RAPID_OVR_LOW) return true; + if (c == CMD_SPINDLE_OVR_COARSE_PLUS) return true; + if (c == CMD_SPINDLE_OVR_COARSE_MINUS) return true; + if (c == CMD_SPINDLE_OVR_FINE_PLUS) return true; + if (c == CMD_SPINDLE_OVR_FINE_MINUS) return true; + if (c == CMD_SPINDLE_OVR_STOP) return true; + if (c == CMD_COOLANT_FLOOD_OVR_TOGGLE) return true; + if (c == CMD_COOLANT_MIST_OVR_TOGGLE) return true; + return false; +} + +//Handle web command query and send answer////////////////////////////// +void Web_Server::handle_web_command () +{ + //to save time if already disconnected + //if (_webserver->hasArg ("PAGEID") ) { + // if (_webserver->arg ("PAGEID").length() > 0 ) { + // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { + // _webserver->send (200, "text/plain", "Invalid command"); + // return; + // } + // } + //} + level_authenticate_type auth_level = is_authenticated(); + String cmd = ""; + if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) { + if (_webserver->hasArg ("plain") ) { + cmd = _webserver->arg ("plain"); + } else { + cmd = _webserver->arg ("commandText"); + } + } else { + _webserver->send (200, "text/plain", "Invalid command"); + return; + } + //if it is internal command [ESPXXX] + cmd.trim(); + int ESPpos = cmd.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = cmd.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //only [ESP800] is allowed login free if authentication is enabled + if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //is there space for parameters? + if (ESPpos2 < cmd.length() ) { + cmd_part2 = cmd.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if (cmd_part1.toInt() != 0) { + ESPResponseStream espresponse(_webserver); + //commmand is web only + COMMANDS::execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, &espresponse); + //flush + espresponse.flush(); + } + //if not is not a valid [ESPXXX] command + } + } else { //execute GCODE + if (auth_level == LEVEL_GUEST) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //Instead of send several commands one by one by web / send full set and split here + String scmd; + String res = ""; + uint8_t sindex = 0; + scmd = get_Splited_Value(cmd,'\n', sindex); + while ( scmd != "" ){ + if ((scmd.length() == 2) && (scmd[0] == 0xC2)){ + scmd[0]=scmd[1]; + scmd.remove(1,1); + } + if (scmd.length() > 1)scmd += "\n"; + else if (!is_realtime_cmd(scmd[0]) )scmd += "\n"; + if (!Serial2Socket.push(scmd.c_str()))res = "Error"; + sindex++; + scmd = get_Splited_Value(cmd,'\n', sindex); + } + _webserver->send (200, "text/plain", res.c_str()); + } +} +//Handle web command query and send answer////////////////////////////// +void Web_Server::handle_web_command_silent () +{ + //to save time if already disconnected + //if (_webserver->hasArg ("PAGEID") ) { + // if (_webserver->arg ("PAGEID").length() > 0 ) { + // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { + // _webserver->send (200, "text/plain", "Invalid command"); + // return; + // } + // } + //} + level_authenticate_type auth_level = is_authenticated(); + String cmd = ""; + if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) { + if (_webserver->hasArg ("plain") ) { + cmd = _webserver->arg ("plain"); + } else { + cmd = _webserver->arg ("commandText"); + } + } else { + _webserver->send (200, "text/plain", "Invalid command"); + return; + } + //if it is internal command [ESPXXX] + cmd.trim(); + int ESPpos = cmd.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = cmd.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //only [ESP800] is allowed login free if authentication is enabled + if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //is there space for parameters? + if (ESPpos2 < cmd.length() ) { + cmd_part2 = cmd.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if (cmd_part1.toInt() != 0) { + //commmand is web only + if(COMMANDS::execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, NULL)) _webserver->send (200, "text/plain", "ok"); + else _webserver->send (200, "text/plain", "error"); + } + //if not is not a valid [ESPXXX] command + } + } else { //execute GCODE + if (auth_level == LEVEL_GUEST) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //Instead of send several commands one by one by web / send full set and split here + String scmd; + uint8_t sindex = 0; + scmd = get_Splited_Value(cmd,'\n', sindex); + String res = ""; + while ( scmd != "" ){ + if (scmd.length() > 1)scmd+="\n"; + else if (!is_realtime_cmd(scmd[0]) )scmd+="\n"; + if (!Serial2Socket.push(scmd.c_str()))res = "Error"; + sindex++; + scmd = get_Splited_Value(cmd,'\n', sindex); + } + _webserver->send (200, "text/plain", res.c_str()); + } +} + + +//login status check +void Web_Server::handle_login() +{ +#ifdef ENABLE_AUTHENTICATION + String smsg; + String sUser,sPassword; + String auths; + int code = 200; + bool msg_alert_error=false; + //disconnect can be done anytime no need to check credential + if (_webserver->hasArg("DISCONNECT")) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos!= -1) { + int pos2 = cookie.indexOf(";",pos); + sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); + } + ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str()); + _webserver->sendHeader("Set-Cookie","ESPSESSIONID=0"); + _webserver->sendHeader("Cache-Control","no-cache"); + String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}"; + _webserver->send(code, "application/json", buffer2send); + //_webserver->client().stop(); + return; + } + + level_authenticate_type auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) auths = "guest"; + else if (auth_level == LEVEL_USER) auths = "user"; + else if (auth_level == LEVEL_ADMIN) auths = "admin"; + else auths = "???"; + + //check is it is a submission or a query + if (_webserver->hasArg("SUBMIT")) { + //is there a correct list of query? + if ( _webserver->hasArg("PASSWORD") && _webserver->hasArg("USER")) { + //USER + sUser = _webserver->arg("USER"); + if ( !((sUser == DEFAULT_ADMIN_LOGIN) || (sUser == DEFAULT_USER_LOGIN))) { + msg_alert_error=true; + smsg = "Error : Incorrect User"; + code = 401; + } + if (msg_alert_error == false) { + //Password + sPassword = _webserver->arg("PASSWORD"); + String sadminPassword; + + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_ADMIN_PWD; + sadminPassword = prefs.getString(ADMIN_PWD_ENTRY, defV); + String suserPassword; + defV = DEFAULT_USER_PWD; + suserPassword = prefs.getString(USER_PWD_ENTRY, defV); + prefs.end(); + + if(!(((sUser == DEFAULT_ADMIN_LOGIN) && (strcmp(sPassword.c_str(),sadminPassword.c_str()) == 0)) || + ((sUser == DEFAULT_USER_LOGIN) && (strcmp(sPassword.c_str(),suserPassword.c_str()) == 0)))) { + msg_alert_error=true; + smsg = "Error: Incorrect password"; + code = 401; + } + } + } else { + msg_alert_error=true; + smsg = "Error: Missing data"; + code = 500; + } + //change password + if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER") && _webserver->hasArg("NEWPASSWORD") && (msg_alert_error==false) ) { + String newpassword = _webserver->arg("NEWPASSWORD"); + if (COMMANDS::isLocalPasswordValid(newpassword.c_str())) { + String spos; + if(sUser == DEFAULT_ADMIN_LOGIN) spos = ADMIN_PWD_ENTRY; + else spos = USER_PWD_ENTRY; + + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(spos.c_str(), newpassword) != newpassword.length()) { + msg_alert_error = true; + smsg = "Error: Cannot apply changes"; + code = 500; + } + prefs.end(); + } else { + msg_alert_error=true; + smsg = "Error: Incorrect password"; + code = 500; + } + } + if ((code == 200) || (code == 500)) { + level_authenticate_type current_auth_level; + if(sUser == DEFAULT_ADMIN_LOGIN) { + current_auth_level = LEVEL_ADMIN; + } else if(sUser == DEFAULT_USER_LOGIN){ + current_auth_level = LEVEL_USER; + } else { + current_auth_level = LEVEL_GUEST; + } + //create Session + if ((current_auth_level != auth_level) || (auth_level== LEVEL_GUEST)) { + auth_ip * current_auth = new auth_ip; + current_auth->level = current_auth_level; + current_auth->ip=_webserver->client().remoteIP(); + strcpy(current_auth->sessionID,create_session_ID()); + strcpy(current_auth->userID,sUser.c_str()); + current_auth->last_time=millis(); + if (AddAuthIP(current_auth)) { + String tmps ="ESPSESSIONID="; + tmps+=current_auth->sessionID; + _webserver->sendHeader("Set-Cookie",tmps); + _webserver->sendHeader("Cache-Control","no-cache"); + switch(current_auth->level) { + case LEVEL_ADMIN: + auths = "admin"; + break; + case LEVEL_USER: + auths = "user"; + break; + default: + auths = "guest"; + break; + } + } else { + delete current_auth; + msg_alert_error=true; + code = 500; + smsg = "Error: Too many connections"; + } + } + } + if (code == 200) smsg = "Ok"; + + //build JSON + String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\"}"; + _webserver->send(code, "application/json", buffer2send); + } else { + if (auth_level != LEVEL_GUEST) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos!= -1) { + int pos2 = cookie.indexOf(";",pos); + sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); + auth_ip * current_auth_info = GetAuth(_webserver->client().remoteIP(), sessionID.c_str()); + if (current_auth_info != NULL){ + sUser = current_auth_info->userID; + } + } + } + String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\",\"user\":\""; + buffer2send += sUser; + buffer2send +="\"}"; + _webserver->send(code, "application/json", buffer2send); + } +#else + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); +#endif +} +//SPIFFS +//SPIFFS files list and file commands +void Web_Server::handleFileList () +{ + level_authenticate_type auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + String path ; + String status = "Ok"; + if (_upload_status == UPLOAD_STATUS_FAILED) { + status = "Upload failed"; + _upload_status = UPLOAD_STATUS_NONE; + } + _upload_status = UPLOAD_STATUS_NONE; + //be sure root is correct according authentication + if (auth_level == LEVEL_ADMIN) { + path = "/"; + } else { + path = "/user"; + } + //get current path + if (_webserver->hasArg ("path") ) { + path += _webserver->arg ("path") ; + } + //to have a clean path + path.trim(); + path.replace ("//", "/"); + if (path[path.length() - 1] != '/') { + path += "/"; + } + //check if query need some action + if (_webserver->hasArg ("action") ) { + //delete a file + if (_webserver->arg ("action") == "delete" && _webserver->hasArg ("filename") ) { + String filename; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename = path + _webserver->arg ("filename"); + filename.replace ("//", "/"); + if (!SPIFFS.exists (filename) ) { + status = shortname + " does not exists!"; + } else { + if (SPIFFS.remove (filename) ) { + status = shortname + " deleted"; + //what happen if no "/." and no other subfiles ? + String ptmp = path; + if ( (path != "/") && (path[path.length() - 1] = '/') ) { + ptmp = path.substring (0, path.length() - 1); + } + File dir = SPIFFS.open (ptmp); + File dircontent = dir.openNextFile(); + if (!dircontent) { + //keep directory alive even empty + File r = SPIFFS.open (path + "/.", FILE_WRITE); + if (r) { + r.close(); + } + } + } else { + status = "Cannot deleted " ; + status += shortname ; + } + } + } + //delete a directory + if (_webserver->arg ("action") == "deletedir" && _webserver->hasArg ("filename") ) { + String filename; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename = path + _webserver->arg ("filename"); + filename += "/"; + filename.replace ("//", "/"); + if (filename != "/") { + bool delete_error = false; + File dir = SPIFFS.open (path + shortname); + { + File file2deleted = dir.openNextFile(); + while (file2deleted) { + String fullpath = file2deleted.name(); + if (!SPIFFS.remove (fullpath) ) { + delete_error = true; + status = "Cannot deleted " ; + status += fullpath; + } + file2deleted = dir.openNextFile(); + } + } + if (!delete_error) { + status = shortname ; + status += " deleted"; + } + } + } + //create a directory + if (_webserver->arg ("action") == "createdir" && _webserver->hasArg ("filename") ) { + String filename; + filename = path + _webserver->arg ("filename") + "/."; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename.replace ("//", "/"); + if (SPIFFS.exists (filename) ) { + status = shortname + " already exists!"; + } else { + File r = SPIFFS.open (filename, FILE_WRITE); + if (!r) { + status = "Cannot create "; + status += shortname ; + } else { + r.close(); + status = shortname + " created"; + } + } + } + } + String jsonfile = "{"; + String ptmp = path; + if ( (path != "/") && (path[path.length() - 1] = '/') ) { + ptmp = path.substring (0, path.length() - 1); + } + File dir = SPIFFS.open (ptmp); + jsonfile += "\"files\":["; + bool firstentry = true; + String subdirlist = ""; + File fileparsed = dir.openNextFile(); + while (fileparsed) { + String filename = fileparsed.name(); + String size = ""; + bool addtolist = true; + //remove path from name + filename = filename.substring (path.length(), filename.length() ); + //check if file or subfile + if (filename.indexOf ("/") > -1) { + //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files + //and no need to overload SPIFFS if not necessary to create "/." if no need + //it will reduce SPIFFS available space so limit it to creation + filename = filename.substring (0, filename.indexOf ("/") ); + String tag = "*"; + tag += filename + "*"; + if (subdirlist.indexOf (tag) > -1 || filename.length() == 0) { //already in list + addtolist = false; //no need to add + } else { + size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory + if (subdirlist.length() == 0) { + subdirlist += "*"; + } + subdirlist += filename + "*"; //add to list + } + } else { + //do not add "." file + if (! ( (filename == ".") || (filename == "") ) ) { + size = ESPResponseStream::formatBytes (fileparsed.size() ); + } else { + addtolist = false; + } + } + if (addtolist) { + if (!firstentry) { + jsonfile += ","; + } else { + firstentry = false; + } + jsonfile += "{"; + jsonfile += "\"name\":\""; + jsonfile += filename; + jsonfile += "\",\"size\":\""; + jsonfile += size; + jsonfile += "\""; + jsonfile += "}"; + } + fileparsed = dir.openNextFile(); + } + jsonfile += "],"; + jsonfile += "\"path\":\"" + path + "\","; + jsonfile += "\"status\":\"" + status + "\","; + size_t totalBytes; + size_t usedBytes; + totalBytes = SPIFFS.totalBytes(); + usedBytes = SPIFFS.usedBytes(); + jsonfile += "\"total\":\"" + ESPResponseStream::formatBytes (totalBytes) + "\","; + jsonfile += "\"used\":\"" + ESPResponseStream::formatBytes (usedBytes) + "\","; + jsonfile.concat (F ("\"occupation\":\"") ); + jsonfile += String (100 * usedBytes / totalBytes); + jsonfile += "\""; + jsonfile += "}"; + path = ""; + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + _upload_status = UPLOAD_STATUS_NONE; +} + +//push error code and message to websocket +void Web_Server::pushError(int code, const char * st, bool web_error, uint16_t timeout){ + if (_socket_server && st) { + String s = "ERROR:" + String(code) + ":"; + s+=st; + _socket_server->sendTXT(_id_connection, s); + if (web_error != 0) { + if (_webserver) { + if (_webserver->client().available() > 0) { + _webserver->send (web_error, "text/xml", st); + } + } + } + uint32_t t = millis(); + while (millis() - t < timeout) { + _socket_server->loop(); + delay(10); + } + } +} + +//abort reception of packages +void Web_Server::cancelUpload(){ + if (_webserver) { + if (_webserver->client().available() > 0) { + HTTPUpload& upload = _webserver->upload(); + upload.status = UPLOAD_FILE_ABORTED; + errno = ECONNABORTED; + _webserver->client().stop(); + delay(100); + } + } +} + +//SPIFFS files uploader handle +void Web_Server::SPIFFSFileupload () +{ + static String filename; + static File fsUploadFile = (File)0; + //get authentication status + level_authenticate_type auth_level= is_authenticated(); + //Guest cannot upload - only admin + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload rejected]\r\n"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + HTTPUpload& upload = _webserver->upload(); + if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + _upload_status= UPLOAD_STATUS_ONGOING; + String upload_filename = upload.filename; + if (upload_filename[0] != '/') filename = "/" + upload_filename; + else filename = upload.filename; + //according User or Admin the root is different as user is isolate to /user when admin has full access + if(auth_level != LEVEL_ADMIN) { + upload_filename = filename; + filename = "/user" + upload_filename; + } + + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + if (fsUploadFile ) { + fsUploadFile.close(); + } + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg (sizeargname.c_str()) ) { + uint32_t filesize = _webserver->arg (sizeargname.c_str()).toInt(); + uint32_t freespace = SPIFFS.totalBytes() - SPIFFS.usedBytes(); + if (filesize > freespace) { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } + + } + if (_upload_status != UPLOAD_STATUS_FAILED) { + //create file + fsUploadFile = SPIFFS.open(filename, FILE_WRITE); + //check If creation succeed + if (fsUploadFile) { + //if yes upload is started + _upload_status= UPLOAD_STATUS_ONGOING; + } else { + //if no set cancel flag + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); + } + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + //check if file is available and no error + if(fsUploadFile && _upload_status == UPLOAD_STATUS_ONGOING) { + //no error so write post date + if (upload.currentSize != fsUploadFile.write(upload.buf, upload.currentSize)) { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } else { + //we have a problem set flag UPLOAD_STATUS_FAILED + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + //check if file is still open + if(fsUploadFile) { + //close it + fsUploadFile.close(); + //check size + String sizeargname = upload.filename + "S"; + fsUploadFile = SPIFFS.open (filename, FILE_READ); + uint32_t filesize = fsUploadFile.size(); + fsUploadFile.close(); + if (_webserver->hasArg (sizeargname.c_str()) ) { + if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { + _upload_status = UPLOAD_STATUS_FAILED; + } + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + } else { + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_UPLOAD, "File upload failed"); + } + } else { + //we have a problem set flag UPLOAD_STATUS_FAILED + _upload_status=UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + + } + //Upload cancelled + //************** + } else { + _upload_status = UPLOAD_STATUS_FAILED; + //pushError(ESP_ERROR_UPLOAD, "File upload failed"); + return; + } + } + } + + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + } + COMMANDS::wait(0); +} + +//Web Update handler +void Web_Server::handleUpdate () +{ + level_authenticate_type auth_level = is_authenticated(); + if (auth_level != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send (403, "text/plain", "Not allowed, log in first!\n"); + return; + } + String jsonfile = "{\"status\":\"" ; + jsonfile += String(_upload_status); + jsonfile += "\"}"; + //send status + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + //if success restart + if (_upload_status == UPLOAD_STATUS_SUCCESSFUL) { + COMMANDS::wait(1000); + COMMANDS::restart_ESP(); + } else { + _upload_status = UPLOAD_STATUS_NONE; + } +} + +//File upload for Web update +void Web_Server::WebUpdateUpload () +{ + static size_t last_upload_update; + static uint32_t maxSketchSpace = 0; + //only admin can update FW + if (is_authenticated() != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload rejected]\r\n"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + //get current file ID + HTTPUpload& upload = _webserver->upload(); + if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + grbl_send(CLIENT_ALL,"[MSG:Update Firmware]\r\n"); + _upload_status= UPLOAD_STATUS_ONGOING; + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg (sizeargname.c_str()) ) { + maxSketchSpace = _webserver->arg (sizeargname).toInt(); + } + //check space + size_t flashsize = 0; + if (esp_ota_get_running_partition()) { + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + if (partition) { + flashsize = partition->size; + } + } + if (flashsize < maxSketchSpace) { + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Update cancelled]\r\n"); + } + if (_upload_status != UPLOAD_STATUS_FAILED) { + last_upload_update = 0; + if(!Update.begin()) { //start with max available size + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Update cancelled]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } else { + grbl_send(CLIENT_ALL,"\n[MSG:Update 0%]\r\n"); + } + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + //check if no error + if (_upload_status == UPLOAD_STATUS_ONGOING) { + if ( ((100 * upload.totalSize) / maxSketchSpace) !=last_upload_update) { + if ( maxSketchSpace > 0)last_upload_update = (100 * upload.totalSize) / maxSketchSpace; + else last_upload_update = upload.totalSize; + String s = "Update "; + s+= String(last_upload_update); + s+="%"; + grbl_sendf(CLIENT_ALL,"[MSG:%s]\r\n", s.c_str()); + } + if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Update write failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + if(Update.end(true)) { //true to set the size to the current progress + //Now Reboot + grbl_send(CLIENT_ALL,"[MSG:Update 100%]\r\n"); + _upload_status=UPLOAD_STATUS_SUCCESSFUL; + } else { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Update failed]\r\n"); + pushError(ESP_ERROR_UPLOAD, "Update upload failed"); + } + } else if(upload.status == UPLOAD_FILE_ABORTED) { + grbl_send(CLIENT_ALL,"[MSG:Update failed]\r\n"); + _upload_status=UPLOAD_STATUS_FAILED; + return; + } + } + } + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + Update.end(); + } + COMMANDS::wait(0); +} + + +#ifdef ENABLE_SD_CARD + +//Function to delete not empty directory on SD card +bool Web_Server::deleteRecursive(String path) +{ + bool result = true; + File file = SD.open((char *)path.c_str()); + //failed + if (!file) { + return false; + } + if(!file.isDirectory()) { + file.close(); + //return if success or not + return SD.remove((char *)path.c_str()); + } + file.rewindDirectory(); + while(true) { + File entry = file.openNextFile(); + if (!entry) { + break; + } + String entryPath = entry.name(); + if(entry.isDirectory()) { + entry.close(); + if(!deleteRecursive(entryPath)) { + result = false; + } + } else { + entry.close(); + if (!SD.remove((char *)entryPath.c_str())) { + result = false; + break; + } + } + COMMANDS::wait(0); //wdtFeed + } + file.close(); + if (result) return SD.rmdir((char *)path.c_str()); + else return false; +} + +//direct SD files list////////////////////////////////////////////////// +void Web_Server::handle_direct_SDFileList() +{ + //this is only for admin and user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status=UPLOAD_STATUS_NONE; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + return; + } + + String path="/"; + String sstatus="Ok"; + if ((_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_FAILED)) { + sstatus = "Upload failed"; + _upload_status = UPLOAD_STATUS_NONE; + } + bool list_files = true; + uint64_t totalspace = 0; + uint64_t usedspace = 0; + if (get_sd_state(true) != SDCARD_IDLE) { + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"No SD Card\"}"); + return; + } + set_sd_state(SDCARD_BUSY_PARSING); + //get current path + if(_webserver->hasArg("path")) { + path += _webserver->arg("path") ; + } + //to have a clean path + path.trim(); + path.replace("//","/"); + if (path[path.length()-1] !='/') { + path +="/"; + } + //check if query need some action + if(_webserver->hasArg("action")) { + //delete a file + if(_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/",""); + filename.replace("//","/"); + if(!SD.exists((char *)filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (SD.remove((char *)filename.c_str())) { + sstatus = shortname + " deleted"; + } else { + sstatus = "Cannot deleted " ; + sstatus+=shortname ; + } + } + } + //delete a directory + if( _webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + shortname.replace("/",""); + filename = path + "/" + shortname; + filename.replace("//","/"); + if (filename != "/") { + if(!SD.exists((char *)filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (!deleteRecursive(filename)) { + sstatus ="Error deleting: "; + sstatus += shortname ; + } else { + sstatus = shortname ; + sstatus+=" deleted"; + } + } + } else { + sstatus ="Cannot delete root"; + } + } + //create a directory + if( _webserver->arg("action")=="createdir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/",""); + filename.replace("//","/"); + if(SD.exists((char *)filename.c_str())) { + sstatus = shortname + " already exists!"; + } else { + if (!SD.mkdir((char *)filename.c_str())) { + sstatus = "Cannot create "; + sstatus += shortname ; + } else { + sstatus = shortname + " created"; + } + } + } + } + //check if no need build file list + if( _webserver->hasArg("dontlist")) { + if( _webserver->arg("dontlist") == "yes") { + list_files = false; + } + } + String jsonfile = "{" ; + jsonfile+="\"files\":["; + + if (path!="/")path = path.substring(0,path.length()-1); + if (path!="/" && !SD.exists((char *)path.c_str())) { + + String s = "{\"status\":\" "; + s += path; + s+= " does not exist on SD Card\"}"; + _webserver->send(200, "application/json", s.c_str()); + return; + } + if (list_files) { + File dir = SD.open((char *)path.c_str()); + if (!dir) { + } + if(!dir.isDirectory()) { + dir.close(); + } + dir.rewindDirectory(); + File entry = dir.openNextFile(); + int i = 0; + while(entry) { + COMMANDS::wait (1); + if (i>0) { + jsonfile+=","; + } + jsonfile+="{\"name\":\""; + String tmpname = entry.name(); + int pos = tmpname.lastIndexOf("/"); + tmpname = tmpname.substring(pos+1); + jsonfile+=tmpname; + jsonfile+="\",\"shortname\":\""; //No need here + jsonfile+=tmpname; + jsonfile+="\",\"size\":\""; + if (entry.isDirectory()) { + jsonfile+="-1"; + } else { + // files have sizes, directories do not + jsonfile+=ESPResponseStream::formatBytes(entry.size()); + } + jsonfile+="\",\"datetime\":\""; + //TODO - can be done later + jsonfile+="\"}"; + i++; + entry.close(); + entry = dir.openNextFile(); + } + dir.close(); + } + jsonfile+="],\"path\":\""; + jsonfile+=path + "\","; + jsonfile+="\"total\":\""; + String stotalspace,susedspace; + //SDCard are in GB or MB but no less + totalspace = SD.totalBytes(); + usedspace = SD.usedBytes(); + stotalspace = ESPResponseStream::formatBytes(totalspace); + susedspace = ESPResponseStream::formatBytes(usedspace+1); + + uint32_t occupedspace = 1; + uint32_t usedspace2 = usedspace/(1024*1024); + uint32_t totalspace2 = totalspace/(1024*1024); + occupedspace = (usedspace2 * 100)/totalspace2; + //minimum if even one byte is used is 1% + if ( occupedspace <= 1) { + occupedspace=1; + } + if (totalspace) { + jsonfile+= stotalspace ; + } else { + jsonfile+= "-1"; + } + jsonfile+="\",\"used\":\""; + jsonfile+= susedspace ; + jsonfile+="\",\"occupation\":\""; + if (totalspace) { + jsonfile+= String(occupedspace); + } else { + jsonfile+= "-1"; + } + jsonfile+= "\","; + jsonfile+= "\"mode\":\"direct\","; + jsonfile+= "\"status\":\""; + jsonfile+=sstatus + "\""; + jsonfile+= "}"; + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send (200, "application/json", jsonfile.c_str()); + _upload_status=UPLOAD_STATUS_NONE; + set_sd_state(SDCARD_IDLE); +} + +//SD File upload with direct access to SD/////////////////////////////// +void Web_Server::SDFile_direct_upload() +{ + static String filename ; + static File sdUploadFile; + //this is only for admin and user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status=UPLOAD_STATUS_FAILED; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + //retrieve current file id + HTTPUpload& upload = _webserver->upload(); + if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + _upload_status= UPLOAD_STATUS_ONGOING; + filename= upload.filename; + //on SD need to add / if not present + if (filename[0]!='/') { + filename= "/"+upload.filename; + } + //check if SD Card is available + if ( get_sd_state(true) != SDCARD_IDLE) { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload cancelled]\r\n"); + pushError(ESP_ERROR_UPLOAD_CANCELLED, "Upload cancelled"); + + } else { + set_sd_state(SDCARD_BUSY_UPLOADING); + //delete file on SD Card if already present + if(SD.exists((char *)filename.c_str())) { + SD.remove((char *)filename.c_str()); + } + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg (sizeargname.c_str()) ) { + uint32_t filesize = _webserver->arg (sizeargname.c_str()).toInt(); + uint64_t freespace = SD.totalBytes() - SD.usedBytes(); + if (filesize > freespace) { + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } + + } + if (_upload_status != UPLOAD_STATUS_FAILED){ + //Create file for writing + sdUploadFile = SD.open((char *)filename.c_str(), FILE_WRITE); + //check if creation succeed + if (!sdUploadFile) { + //if creation failed + _upload_status=UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); + } + //if creation succeed set flag UPLOAD_STATUS_ONGOING + else { + _upload_status= UPLOAD_STATUS_ONGOING; + } + } + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + if(sdUploadFile && (_upload_status == UPLOAD_STATUS_ONGOING) && (get_sd_state(false) == SDCARD_BUSY_UPLOADING)) { + //no error write post data + if (upload.currentSize != sdUploadFile.write(upload.buf, upload.currentSize)) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } else { //if error set flag UPLOAD_STATUS_FAILED + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + //if file is open close it + if(sdUploadFile) { + sdUploadFile.close(); + //TODO Check size + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg (sizeargname.c_str()) ) { + uint32_t filesize = 0; + sdUploadFile = SD.open (filename.c_str(), FILE_READ); + filesize = sdUploadFile.size(); + sdUploadFile.close(); + if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { + _upload_status = UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_UPLOAD, "File upload mismatch"); + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + } + } + } else { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + set_sd_state(SDCARD_IDLE); + } else { + _upload_status = UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_UPLOAD, "Upload error"); + } + + } else {//Upload cancelled + _upload_status=UPLOAD_STATUS_FAILED; + set_sd_state(SDCARD_IDLE); + grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); + if(sdUploadFile) { + sdUploadFile.close(); + } + return; + } + } + } + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + if(sdUploadFile) { + sdUploadFile.close(); + } + if(SD.exists((char *)filename.c_str())) { + SD.remove((char *)filename.c_str()); + } + set_sd_state(SDCARD_IDLE); + } + COMMANDS::wait(0); +} +#endif + +void Web_Server::handle(){ +static uint32_t timeout = millis(); + COMMANDS::wait(0); +#ifdef ENABLE_CAPTIVE_PORTAL + if(WiFi.getMode() != WIFI_STA){ + dnsServer.processNextRequest(); + } +#endif + if (_webserver)_webserver->handleClient(); + if (_socket_server && _setupdone)_socket_server->loop(); + if ((millis() - timeout) > 10000) { + if (_socket_server){ + String s = "PING:"; + s+=String(_id_connection); + _socket_server->broadcastTXT(s); + timeout=millis(); + } + } + +} + + +void Web_Server::handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + //USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = _socket_server->remoteIP(num); + //USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + String s = "CURRENT_ID:" + String(num); + // send message to client + _id_connection = num; + _socket_server->sendTXT(_id_connection, s); + s = "ACTIVE_ID:" + String(_id_connection); + _socket_server->broadcastTXT(s); + } + break; + case WStype_TEXT: + //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + //USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + //hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + default: + break; + } + +} + +String Web_Server::get_Splited_Value(String data, char separator, int index) +{ + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = data.length()-1; + + for(int i=0; i<=maxIndex && found<=index; i++){ + if(data.charAt(i)==separator || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + + return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; +} + + +//helper to extract content type from file extension +//Check what is the content tye according extension file +String Web_Server::getContentType (String filename) +{ + String file_name = filename; + file_name.toLowerCase(); + if (filename.endsWith (".htm") ) { + return "text/html"; + } else if (file_name.endsWith (".html") ) { + return "text/html"; + } else if (file_name.endsWith (".css") ) { + return "text/css"; + } else if (file_name.endsWith (".js") ) { + return "application/javascript"; + } else if (file_name.endsWith (".png") ) { + return "image/png"; + } else if (file_name.endsWith (".gif") ) { + return "image/gif"; + } else if (file_name.endsWith (".jpeg") ) { + return "image/jpeg"; + } else if (file_name.endsWith (".jpg") ) { + return "image/jpeg"; + } else if (file_name.endsWith (".ico") ) { + return "image/x-icon"; + } else if (file_name.endsWith (".xml") ) { + return "text/xml"; + } else if (file_name.endsWith (".pdf") ) { + return "application/x-pdf"; + } else if (file_name.endsWith (".zip") ) { + return "application/x-zip"; + } else if (file_name.endsWith (".gz") ) { + return "application/x-gzip"; + } else if (file_name.endsWith (".txt") ) { + return "text/plain"; + } + return "application/octet-stream"; +} + +//check authentification +level_authenticate_type Web_Server::is_authenticated() +{ +#ifdef ENABLE_AUTHENTICATION + if (_webserver->hasHeader ("Cookie") ) { + String cookie = _webserver->header ("Cookie"); + int pos = cookie.indexOf ("ESPSESSIONID="); + if (pos != -1) { + int pos2 = cookie.indexOf (";", pos); + String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2); + IPAddress ip = _webserver->client().remoteIP(); + //check if cookie can be reset and clean table in same time + return ResetAuthIP (ip, sessionID.c_str() ); + } + } + return LEVEL_GUEST; +#else + return LEVEL_ADMIN; +#endif +} + +#ifdef ENABLE_AUTHENTICATION + +//add the information in the linked list if possible +bool Web_Server::AddAuthIP (auth_ip * item) +{ + if (_nb_ip > MAX_AUTH_IP) { + return false; + } + item->_next = _head; + _head = item; + _nb_ip++; + return true; +} + +//Session ID based on IP and time using 16 char +char * Web_Server::create_session_ID() +{ + static char sessionID[17]; +//reset SESSIONID + for (int i = 0; i < 17; i++) { + sessionID[i] = '\0'; + } +//get time + uint32_t now = millis(); +//get remote IP + IPAddress remoteIP = _webserver->client().remoteIP(); +//generate SESSIONID + if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) { + strcpy (sessionID, "NONE"); + } + return sessionID; +} + + +bool Web_Server::ClearAuthIP (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + auth_ip * previous = NULL; + bool done = false; + while (current) { + if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) { + //remove + done = true; + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + previous = current; + current = current->_next; + } + } + return done; +} + +//Get info +auth_ip * Web_Server::GetAuth (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + //auth_ip * previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if (ip == current->ip) { + if (strcmp (sessionID, current->sessionID) == 0) { + //found + return current; + } + } + //previous = current; + current = current->_next; + } + return NULL; +} + +//Review all IP to reset timers +level_authenticate_type Web_Server::ResetAuthIP (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + auth_ip * previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if ( (millis() - current->last_time) > 360000) { + //remove + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + if (ip == current->ip) { + if (strcmp (sessionID, current->sessionID) == 0) { + //reset time + current->last_time = millis(); + return (level_authenticate_type) current->level; + } + } + previous = current; + current = current->_next; + } + } + return LEVEL_GUEST; +} +#endif + +#endif // Enable HTTP && ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/web_server.h b/Grbl_Esp32-master/Grbl_Esp32/web_server.h new file mode 100644 index 0000000..c96930b --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/web_server.h @@ -0,0 +1,97 @@ +/* + web_server.h - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _WEB_SERVER_H +#define _WEB_SERVER_H + + +#include "config.h" +#include "commands.h" +class WebSocketsServer; +class WebServer; + +#ifdef ENABLE_AUTHENTICATION +struct auth_ip { + IPAddress ip; + level_authenticate_type level; + char userID[17]; + char sessionID[17]; + uint32_t last_time; + auth_ip * _next; +}; + +#endif + +class Web_Server { + public: + Web_Server(); + ~Web_Server(); + bool begin(); + void end(); + void handle(); + static long get_client_ID(); + static uint16_t port(){return _port;} + private: + static bool _setupdone; + static WebServer * _webserver; + static long _id_connection; + static WebSocketsServer * _socket_server; + static uint16_t _port; + static uint8_t _upload_status; + static String getContentType (String filename); + static String get_Splited_Value(String data, char separator, int index); + static level_authenticate_type is_authenticated(); +#ifdef ENABLE_AUTHENTICATION + static auth_ip * _head; + static uint8_t _nb_ip; + static bool AddAuthIP (auth_ip * item); + static char * create_session_ID(); + static bool ClearAuthIP (IPAddress ip, const char * sessionID); + static auth_ip * GetAuth (IPAddress ip, const char * sessionID); + static level_authenticate_type ResetAuthIP (IPAddress ip, const char * sessionID); +#endif +#ifdef ENABLE_SSDP + static void handle_SSDP (); +#endif + static void handle_root(); + static void handle_login(); + static void handle_not_found (); + static void handle_web_command (); + static void handle_web_command_silent (); + static void handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length); + static void SPIFFSFileupload (); + static void handleFileList (); + static void handleUpdate (); + static void WebUpdateUpload (); + static bool is_realtime_cmd(char c); + static void pushError(int code, const char * st, bool web_error = 500, uint16_t timeout = 1000); + static void cancelUpload(); +#ifdef ENABLE_SD_CARD + static void handle_direct_SDFileList(); + static void SDFile_direct_upload(); + static bool deleteRecursive(String path); +#endif +}; + +extern Web_Server web_server; + +#endif + diff --git a/Grbl_Esp32-master/Grbl_Esp32/wificonfig.cpp b/Grbl_Esp32-master/Grbl_Esp32/wificonfig.cpp new file mode 100644 index 0000000..eec95e6 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/wificonfig.cpp @@ -0,0 +1,557 @@ +/* + wificonfig.cpp - wifi functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" + +#ifdef ENABLE_WIFI + +#include +#include +#include +#include +#include +#include +#include "wificonfig.h" +#include "wifiservices.h" +#include "commands.h" +#include "report.h" + +WiFiConfig wifi_config; + +String WiFiConfig::_hostname = ""; +bool WiFiConfig::_events_registered = false; +WiFiConfig::WiFiConfig(){ +} + +WiFiConfig::~WiFiConfig(){ + end(); +} + +//just simple helper to convert mac address to string +char * WiFiConfig::mac2str (uint8_t mac [8]) +{ + static char macstr [18]; + if (0 > sprintf (macstr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) ) { + strcpy (macstr, "00:00:00:00:00:00"); + } + return macstr; +} + +const char *WiFiConfig::info(){ + static String result; + String tmp; + result = "[MSG:"; + if((WiFi.getMode() == WIFI_MODE_STA ) || (WiFi.getMode() == WIFI_MODE_APSTA )) { + result += "Mode=STA:SSID="; + result += WiFi.SSID(); + result += ":Status="; + result += (WiFi.status()==WL_CONNECTED)?"Connected":"Not connected"; + result += ":IP="; + result += WiFi.localIP().toString(); + result += ":MAC="; + tmp = WiFi.macAddress(); + tmp.replace(":","-"); + result += tmp; + + } + if((WiFi.getMode() == WIFI_MODE_AP ) || (WiFi.getMode() == WIFI_MODE_APSTA )) { + if(WiFi.getMode() == WIFI_MODE_APSTA ) { + result+= "]\r\n[MSG:"; + } + result+="Mode=AP:SSDI="; + wifi_config_t conf; + esp_wifi_get_config (ESP_IF_WIFI_AP, &conf); + result+= (const char*)conf.ap.ssid; + result+=":IP="; + result+=WiFi.softAPIP().toString(); + result+=":MAC="; + tmp = WiFi.softAPmacAddress(); + tmp.replace(":","-"); + result += tmp; + } + if(WiFi.getMode() == WIFI_MODE_NULL)result+="No Wifi"; + result+= "]\r\n"; + return result.c_str(); +} + +/** + * Helper to convert IP string to int + */ + +uint32_t WiFiConfig::IP_int_from_string(String & s){ + uint32_t ip_int = 0; + IPAddress ipaddr; + if (ipaddr.fromString(s)) ip_int = ipaddr; + return ip_int; +} + +/** + * Helper to convert int to IP string + */ + +String WiFiConfig::IP_string_from_int(uint32_t ip_int){ + IPAddress ipaddr(ip_int); + return ipaddr.toString(); +} + +/** + * Check if Hostname string is valid + */ + +bool WiFiConfig::isHostnameValid (const char * hostname) +{ + //limited size + char c; + if (strlen (hostname) > MAX_HOSTNAME_LENGTH || strlen (hostname) < MIN_HOSTNAME_LENGTH) { + return false; + } + //only letter and digit + for (int i = 0; i < strlen (hostname); i++) { + c = hostname[i]; + if (! (isdigit (c) || isalpha (c) || c == '-') ) { + return false; + } + if (c == ' ') { + return false; + } + } + return true; +} + + +/** + * Check if SSID string is valid + */ + +bool WiFiConfig::isSSIDValid (const char * ssid) +{ + //limited size + //char c; + if (strlen (ssid) > MAX_SSID_LENGTH || strlen (ssid) < MIN_SSID_LENGTH) { + return false; + } + //only printable + for (int i = 0; i < strlen (ssid); i++) { + if (!isPrintable (ssid[i]) ) { + return false; + } + } + return true; +} + +/** + * Check if password string is valid + */ + +bool WiFiConfig::isPasswordValid (const char * password) +{ + if (strlen (password) == 0) return true; //open network + //limited size + if ((strlen (password) > MAX_PASSWORD_LENGTH) || (strlen (password) < MIN_PASSWORD_LENGTH)) { + return false; + } + //no space allowed ? + /* for (int i = 0; i < strlen (password); i++) + if (password[i] == ' ') { + return false; + }*/ + return true; +} + +/** + * Check if IP string is valid + */ +bool WiFiConfig::isValidIP(const char * string){ + IPAddress ip; + return ip.fromString(string); +} + + +/** + * WiFi events + * SYSTEM_EVENT_WIFI_READY < ESP32 WiFi ready + * SYSTEM_EVENT_SCAN_DONE < ESP32 finish scanning AP + * SYSTEM_EVENT_STA_START < ESP32 station start + * SYSTEM_EVENT_STA_STOP < ESP32 station stop + * SYSTEM_EVENT_STA_CONNECTED < ESP32 station connected to AP + * SYSTEM_EVENT_STA_DISCONNECTED < ESP32 station disconnected from AP + * SYSTEM_EVENT_STA_AUTHMODE_CHANGE < the auth mode of AP connected by ESP32 station changed + * SYSTEM_EVENT_STA_GOT_IP < ESP32 station got IP from connected AP + * SYSTEM_EVENT_STA_LOST_IP < ESP32 station lost IP and the IP is reset to 0 + * SYSTEM_EVENT_STA_WPS_ER_SUCCESS < ESP32 station wps succeeds in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_FAILED < ESP32 station wps fails in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_TIMEOUT < ESP32 station wps timeout in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_PIN < ESP32 station wps pin code in enrollee mode + * SYSTEM_EVENT_AP_START < ESP32 soft-AP start + * SYSTEM_EVENT_AP_STOP < ESP32 soft-AP stop + * SYSTEM_EVENT_AP_STACONNECTED < a station connected to ESP32 soft-AP + * SYSTEM_EVENT_AP_STADISCONNECTED < a station disconnected from ESP32 soft-AP + * SYSTEM_EVENT_AP_PROBEREQRECVED < Receive probe request packet in soft-AP interface + * SYSTEM_EVENT_GOT_IP6 < ESP32 station or ap or ethernet interface v6IP addr is preferred + * SYSTEM_EVENT_ETH_START < ESP32 ethernet start + * SYSTEM_EVENT_ETH_STOP < ESP32 ethernet stop + * SYSTEM_EVENT_ETH_CONNECTED < ESP32 ethernet phy link up + * SYSTEM_EVENT_ETH_DISCONNECTED < ESP32 ethernet phy link down + * SYSTEM_EVENT_ETH_GOT_IP < ESP32 ethernet got IP from connected AP + * SYSTEM_EVENT_MAX + */ + +void WiFiConfig::WiFiEvent(WiFiEvent_t event) +{ + switch (event) + { + case SYSTEM_EVENT_STA_GOT_IP: + grbl_sendf(CLIENT_ALL,"[MSG:Connected with %s]\r\n",WiFi.localIP().toString().c_str()); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + grbl_send(CLIENT_ALL,"[MSG:Disconnected]\r\n"); + break; + default: + break; + } +} + +/* + * Get WiFi signal strength + */ +int32_t WiFiConfig::getSignal (int32_t RSSI) +{ + if (RSSI <= -100) { + return 0; + } + if (RSSI >= -50) { + return 100; + } + return (2 * (RSSI + 100) ); +} + +/* + * Connect client to AP + */ + +bool WiFiConfig::ConnectSTA2AP(){ + String msg, msg_out; + uint8_t count = 0; + uint8_t dot = 0; + wl_status_t status = WiFi.status(); + while (status != WL_CONNECTED && count < 40) { + + switch (status) { + case WL_NO_SSID_AVAIL: + msg="No SSID"; + break; + case WL_CONNECT_FAILED: + msg="Connection failed"; + break; + case WL_CONNECTED: + break; + default: + if ((dot>3) || (dot==0) ){ + dot=0; + msg_out = "Connecting"; + } + msg_out+="."; + msg= msg_out; + dot++; + break; + } + grbl_sendf(CLIENT_ALL,"[MSG:%s]\r\n",msg.c_str()); + COMMANDS::wait (500); + count++; + status = WiFi.status(); + } + return (status == WL_CONNECTED); +} + +/* + * Start client mode (Station) + */ + +bool WiFiConfig::StartSTA(){ + String defV; + Preferences prefs; + //stop active service + wifi_services.end(); + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + WiFi.enableAP (false); + WiFi.mode(WIFI_STA); + //Get parameters for STA + prefs.begin(NAMESPACE, true); + defV = DEFAULT_HOSTNAME; + String h = prefs.getString(HOSTNAME_ENTRY, defV); + WiFi.setHostname(h.c_str()); + //SSID + defV = DEFAULT_STA_SSID; + String SSID = prefs.getString(STA_SSID_ENTRY, defV); + if (SSID.length() == 0)SSID = DEFAULT_STA_SSID; + //password + defV = DEFAULT_STA_PWD; + String password = prefs.getString(STA_PWD_ENTRY, defV); + int8_t IP_mode = prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE); + //IP + defV = DEFAULT_STA_IP; + int32_t IP = prefs.getInt(STA_IP_ENTRY, IP_int_from_string(defV)); + //GW + defV = DEFAULT_STA_GW; + int32_t GW = prefs.getInt(STA_GW_ENTRY, IP_int_from_string(defV)); + //MK + defV = DEFAULT_STA_MK; + int32_t MK = prefs.getInt(STA_MK_ENTRY, IP_int_from_string(defV)); + prefs.end(); + //if not DHCP + if (IP_mode != DHCP_MODE) { + IPAddress ip(IP), mask(MK), gateway(GW); + WiFi.config(ip, gateway,mask); + } + if (WiFi.begin(SSID.c_str(), (password.length() > 0)?password.c_str():NULL)){ + grbl_send(CLIENT_ALL,"\n[MSG:Client Started]\r\n"); + grbl_sendf(CLIENT_ALL,"[MSG:Connecting %s]\r\n", SSID.c_str()); + return ConnectSTA2AP(); + } else { + grbl_send(CLIENT_ALL,"[MSG:Starting client failed]\r\n"); + return false; + } +} + +/** + * Setup and start Access point + */ + +bool WiFiConfig::StartAP(){ + String defV; + Preferences prefs; + //stop active services + wifi_services.end(); + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + WiFi.enableSTA (false); + WiFi.mode(WIFI_AP); + //Get parameters for AP + prefs.begin(NAMESPACE, true); + //SSID + defV = DEFAULT_AP_SSID; + String SSID = prefs.getString(AP_SSID_ENTRY, defV); + if (SSID.length() == 0)SSID = DEFAULT_AP_SSID; + //password + defV = DEFAULT_AP_PWD; + String password = prefs.getString(AP_PWD_ENTRY, defV); + //channel + int8_t channel = prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL); + if (channel == 0)channel = DEFAULT_AP_CHANNEL; + //IP + defV = DEFAULT_AP_IP; + int32_t IP = prefs.getInt(AP_IP_ENTRY, IP_int_from_string(defV)); + if (IP==0){ + IP = IP_int_from_string(defV); + } + prefs.end(); + IPAddress ip(IP); + IPAddress mask; + mask.fromString(DEFAULT_AP_MK); + //Set static IP + WiFi.softAPConfig(ip, ip, mask); + //Start AP + if(WiFi.softAP(SSID.c_str(), (password.length() > 0)?password.c_str():NULL, channel)) { + grbl_sendf(CLIENT_ALL,"\n[MSG:Local access point %s started, %s]\r\n", SSID.c_str(), WiFi.softAPIP().toString().c_str()); + return true; + } else { + grbl_send(CLIENT_ALL,"[MSG:Starting AP failed]\r\n"); + return false; + } +} + +/** + * Stop WiFi + */ + +void WiFiConfig::StopWiFi(){ + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(true); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(true); + wifi_services.end(); + WiFi.enableSTA (false); + WiFi.enableAP (false); + WiFi.mode(WIFI_OFF); + grbl_send(CLIENT_ALL,"\n[MSG:WiFi Off]\r\n"); +} + +/** + * begin WiFi setup + */ +void WiFiConfig::begin() { + Preferences prefs; + //stop active services + wifi_services.end(); + //setup events + if (!_events_registered) { + //cumulative function and no remove so only do once + WiFi.onEvent(WiFiConfig::WiFiEvent); + _events_registered = true; + } + //open preferences as read-only + prefs.begin(NAMESPACE, true); + //Get hostname + String defV = DEFAULT_HOSTNAME; + _hostname = prefs.getString(HOSTNAME_ENTRY, defV); + int8_t wifiMode = prefs.getChar(ESP_RADIO_MODE, DEFAULT_RADIO_MODE); + + if (wifiMode == ESP_WIFI_AP) { + StartAP(); + //start services + wifi_services.begin(); + } else if (wifiMode == ESP_WIFI_STA){ + if(!StartSTA()){ + defV = DEFAULT_STA_SSID; + grbl_sendf(CLIENT_ALL,"[MSG:Cannot connect to %s]\r\n", prefs.getString(STA_SSID_ENTRY, defV).c_str()); + StartAP(); + } + //start services + wifi_services.begin(); + }else WiFi.mode(WIFI_OFF); + prefs.end(); +} + +/** + * End WiFi + */ +void WiFiConfig::end() { + StopWiFi(); +} + +/** + * Reset ESP + */ +void WiFiConfig::reset_settings(){ + Preferences prefs; + prefs.begin(NAMESPACE, false); + String sval; + int8_t bbuf; + int16_t ibuf; + bool error = false; + sval = DEFAULT_HOSTNAME; + if (prefs.putString(HOSTNAME_ENTRY, sval) == 0){ + error = true; + } + + bbuf = DEFAULT_NOTIFICATION_TYPE; + if (prefs.putChar(NOTIFICATION_TYPE, bbuf) ==0 ) { + error = true; + } + sval = DEFAULT_TOKEN; + if (prefs.putString(NOTIFICATION_T1, sval) != sval.length()){ + error = true; + } + if (prefs.putString(NOTIFICATION_T2, sval) != sval.length()){ + error = true; + } + if (prefs.putString(NOTIFICATION_TS, sval) != sval.length()){ + error = true; + } + + sval = DEFAULT_STA_SSID; + if (prefs.putString(STA_SSID_ENTRY, sval) == 0){ + error = true; + } + sval = DEFAULT_STA_PWD; + if (prefs.putString(STA_PWD_ENTRY, sval) != sval.length()){ + error = true; + } + sval = DEFAULT_AP_SSID; + if (prefs.putString(AP_SSID_ENTRY, sval) == 0){ + error = true; + } + sval = DEFAULT_AP_PWD; + if (prefs.putString(AP_PWD_ENTRY, sval) != sval.length()){ + error = true; + } + + bbuf = DEFAULT_AP_CHANNEL; + if (prefs.putChar(AP_CHANNEL_ENTRY, bbuf) ==0 ) { + error = true; + } + bbuf = DEFAULT_STA_IP_MODE; + if (prefs.putChar(STA_IP_MODE_ENTRY, bbuf) ==0 ) { + error = true; + } + bbuf = DEFAULT_HTTP_STATE; + if (prefs.putChar(HTTP_ENABLE_ENTRY, bbuf) ==0 ) { + error = true; + } + bbuf = DEFAULT_TELNET_STATE; + if (prefs.putChar(TELNET_ENABLE_ENTRY, bbuf) ==0 ) { + error = true; + } + bbuf = DEFAULT_RADIO_MODE; + if (prefs.putChar(ESP_RADIO_MODE, bbuf) ==0 ) { + error = true; + } + ibuf = DEFAULT_WEBSERVER_PORT; + if (prefs.putUShort(HTTP_PORT_ENTRY, ibuf) == 0) { + error = true; + } + ibuf = DEFAULT_TELNETSERVER_PORT; + if (prefs.putUShort(TELNET_PORT_ENTRY, ibuf) == 0) { + error = true; + } + sval = DEFAULT_STA_IP; + if (prefs.putInt(STA_IP_ENTRY, wifi_config.IP_int_from_string(sval)) == 0) { + error = true; + } + sval = DEFAULT_STA_GW; + if (prefs.putInt(STA_GW_ENTRY, wifi_config.IP_int_from_string(sval)) == 0) { + error = true; + } + sval = DEFAULT_STA_MK; + if (prefs.putInt(STA_MK_ENTRY, wifi_config.IP_int_from_string(sval)) == 0) { + error = true; + } + sval = DEFAULT_AP_IP; + if (prefs.putInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(sval)) == 0) { + error = true; + } + prefs.end(); + if (error) { + grbl_send(CLIENT_ALL,"[MSG:WiFi reset error]\r\n"); + } else { + grbl_send(CLIENT_ALL,"[MSG:WiFi reset done]\r\n"); + } +} +bool WiFiConfig::Is_WiFi_on(){ + return !(WiFi.getMode() == WIFI_MODE_NULL); +} + +/** + * Handle not critical actions that must be done in sync environement + */ +void WiFiConfig::handle() { + //Services + COMMANDS::wait(0); + wifi_services.handle(); +} + + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/wificonfig.h b/Grbl_Esp32-master/Grbl_Esp32/wificonfig.h new file mode 100644 index 0000000..a4e82f7 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/wificonfig.h @@ -0,0 +1,129 @@ +/* + wificonfig.h - wifi functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//Preferences entries +#define HOSTNAME_ENTRY "ESP_HOSTNAME" +#define STA_SSID_ENTRY "STA_SSID" +#define STA_PWD_ENTRY "STA_PWD" +#define STA_IP_ENTRY "STA_IP" +#define STA_GW_ENTRY "STA_GW" +#define STA_MK_ENTRY "STA_MK" +#define AP_SSID_ENTRY "AP_SSID" +#define AP_PWD_ENTRY "AP_PWD" +#define AP_IP_ENTRY "AP_IP" +#define AP_CHANNEL_ENTRY "AP_CHANNEL" +#define HTTP_ENABLE_ENTRY "HTTP_ON" +#define HTTP_PORT_ENTRY "HTTP_PORT" +#define TELNET_ENABLE_ENTRY "TELNET_ON" +#define TELNET_PORT_ENTRY "TELNET_PORT" +#define STA_IP_MODE_ENTRY "STA_IP_MODE" +#define NOTIFICATION_TYPE "NOTIF_TYPE" +#define NOTIFICATION_T1 "NOTIF_T1" +#define NOTIFICATION_T2 "NOTIF_T2" +#define NOTIFICATION_TS "NOTIF_TS" + +//Notifications +#define ESP_PUSHOVER_NOTIFICATION 1 +#define ESP_EMAIL_NOTIFICATION 2 +#define ESP_LINE_NOTIFICATION 3 + +#define DHCP_MODE 0 +#define STATIC_MODE 1 + +//Switch +#define ESP_SAVE_ONLY 0 +#define ESP_APPLY_NOW 1 + +//defaults values +#define DEFAULT_HOSTNAME "grblesp" +#define DEFAULT_STA_SSID "GRBL_ESP" +#define DEFAULT_STA_PWD "12345678" +#define DEFAULT_STA_IP "0.0.0.0" +#define DEFAULT_STA_GW "0.0.0.0" +#define DEFAULT_STA_MK "0.0.0.0" +#define DEFAULT_AP_SSID "GRBL_ESP" +#define DEFAULT_AP_PWD "12345678" +#define DEFAULT_AP_IP "192.168.0.1" +#define DEFAULT_AP_MK "255.255.255.0" +#define DEFAULT_AP_CHANNEL 1 +#define DEFAULT_WEBSERVER_PORT 80 +#define DEFAULT_HTTP_STATE 1 +#define DEFAULT_TELNETSERVER_PORT 23 +#define DEFAULT_TELNET_STATE 1 +#define DEFAULT_STA_IP_MODE DHCP_MODE +#define HIDDEN_PASSWORD "********" +#define DEFAULT_TOKEN "" +#define DEFAULT_NOTIFICATION_TYPE 0 + +//boundaries +#define MAX_SSID_LENGTH 32 +#define MIN_SSID_LENGTH 1 +#define MAX_PASSWORD_LENGTH 64 +//min size of password is 0 or upper than 8 char +//so let set min is 8 +#define MIN_PASSWORD_LENGTH 8 +#define MAX_HOSTNAME_LENGTH 32 +#define MIN_HOSTNAME_LENGTH 1 +#define MAX_HTTP_PORT 65001 +#define MIN_HTTP_PORT 1 +#define MAX_TELNET_PORT 65001 +#define MIN_TELNET_PORT 1 +#define MIN_CHANNEL 1 +#define MAX_CHANNEL 14 +#define MIN_NOTIFICATION_TOKEN_LENGTH 0 +#define MAX_NOTIFICATION_TOKEN_LENGTH 63 +#define MAX_NOTIFICATION_SETTING_LENGTH 127 + +#ifndef _WIFI_CONFIG_H +#define _WIFI_CONFIG_H +#include "WiFi.h" + +class WiFiConfig { +public: + WiFiConfig(); + ~WiFiConfig(); + static const char *info(); + static bool isValidIP(const char * string); + static bool isPasswordValid (const char * password); + static bool isSSIDValid (const char * ssid); + static bool isHostnameValid (const char * hostname); + static uint32_t IP_int_from_string(String & s); + static String IP_string_from_int(uint32_t ip_int); + static String Hostname(){return _hostname;} + static char * mac2str (uint8_t mac [8]); + static bool StartAP(); + static bool StartSTA(); + static void StopWiFi(); + static int32_t getSignal (int32_t RSSI); + static void begin(); + static void end(); + static void handle(); + static void reset_settings(); + static bool Is_WiFi_on(); +private : + static bool ConnectSTA2AP(); + static void WiFiEvent(WiFiEvent_t event); + static String _hostname; + static bool _events_registered; +}; + +extern WiFiConfig wifi_config; + +#endif diff --git a/Grbl_Esp32-master/Grbl_Esp32/wifiservices.cpp b/Grbl_Esp32-master/Grbl_Esp32/wifiservices.cpp new file mode 100644 index 0000000..4255a34 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/wifiservices.cpp @@ -0,0 +1,173 @@ +/* + wifiservices.cpp - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "config.h" + +#ifdef ENABLE_WIFI + +#include +#include +#include +#include +#include "report.h" +#include "wificonfig.h" +#include "wifiservices.h" +#ifdef ENABLE_MDNS +#include +#endif +#ifdef ENABLE_OTA +#include +#endif +#ifdef ENABLE_HTTP +#include "web_server.h" +#endif +#ifdef ENABLE_TELNET +#include "telnet_server.h" +#endif +#ifdef ENABLE_NOTIFICATIONS +#include "notifications_service.h" +#endif +#include "commands.h" + +WiFiServices wifi_services; + +WiFiServices::WiFiServices(){ +} +WiFiServices::~WiFiServices(){ + end(); +} + +bool WiFiServices::begin(){ + bool no_error = true; + //Sanity check + if(WiFi.getMode() == WIFI_OFF) return false; + String h; + Preferences prefs; + //Get hostname + String defV = DEFAULT_HOSTNAME; + prefs.begin(NAMESPACE, true); + h = prefs.getString(HOSTNAME_ENTRY, defV); + prefs.end(); + //Start SPIFFS + SPIFFS.begin(true); + +#ifdef ENABLE_OTA + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else {// U_SPIFFS + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + type = "filesystem"; + SPIFFS.end(); + } + grbl_sendf(CLIENT_ALL,"[MSG:Start OTA updating %s]\r\n", type.c_str()); + }) + .onEnd([]() { + grbl_sendf(CLIENT_ALL,"[MSG:End OTA]\r\n"); + + }) + .onProgress([](unsigned int progress, unsigned int total) { + grbl_sendf(CLIENT_ALL,"[MSG:OTA Progress: %u%%]\r\n", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + grbl_sendf(CLIENT_ALL,"[MSG:OTA Error(%u):]\r\n", error); + if (error == OTA_AUTH_ERROR) grbl_send(CLIENT_ALL,"[MSG:Auth Failed]\r\n"); + else if (error == OTA_BEGIN_ERROR) grbl_send(CLIENT_ALL,"[MSG:Begin Failed]\r\n"); + else if (error == OTA_CONNECT_ERROR) grbl_send(CLIENT_ALL,"[MSG:Connect Failed]\r\n"); + else if (error == OTA_RECEIVE_ERROR) grbl_send(CLIENT_ALL,"[MSG:Receive Failed]\r\n"); + else if (error == OTA_END_ERROR) grbl_send(CLIENT_ALL,"[MSG:End Failed]\r\n"); + }); + ArduinoOTA.begin(); +#endif +#ifdef ENABLE_MDNS + //no need in AP mode + if(WiFi.getMode() == WIFI_STA){ + //start mDns + if (!MDNS.begin(h.c_str())) { + grbl_send(CLIENT_ALL,"[MSG:Cannot start mDNS]\r\n"); + no_error = false; + } else { + grbl_sendf(CLIENT_ALL,"[MSG:Start mDNS with hostname:http://%s.local/]\r\n",h.c_str()); + } + } +#endif +#ifdef ENABLE_HTTP + web_server.begin(); +#endif +#ifdef ENABLE_TELNET + telnet_server.begin(); +#endif +#ifdef ENABLE_NOTIFICATIONS + notificationsservice.begin(); +#endif + //be sure we are not is mixed mode in setup + WiFi.scanNetworks (true); + return no_error; +} +void WiFiServices::end(){ +#ifdef ENABLE_NOTIFICATIONS + notificationsservice.end(); +#endif +#ifdef ENABLE_TELNET + telnet_server.end(); +#endif + +#ifdef ENABLE_HTTP + web_server.end(); +#endif + //stop OTA +#ifdef ENABLE_OTA + ArduinoOTA.end(); +#endif + //Stop SPIFFS + SPIFFS.end(); + +#ifdef ENABLE_MDNS + //Stop mDNS + MDNS.end(); +#endif +} + +void WiFiServices::handle(){ + COMMANDS::wait(0); + //to avoid mixed mode due to scan network + if (WiFi.getMode() == WIFI_AP_STA) { + if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) { + WiFi.enableSTA (false); + } + } +#ifdef ENABLE_OTA + ArduinoOTA.handle(); +#endif +#ifdef ENABLE_HTTP + web_server.handle(); +#endif +#ifdef ENABLE_TELNET + telnet_server.handle(); +#endif +} + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32-master/Grbl_Esp32/wifiservices.h b/Grbl_Esp32-master/Grbl_Esp32/wifiservices.h new file mode 100644 index 0000000..790fd67 --- /dev/null +++ b/Grbl_Esp32-master/Grbl_Esp32/wifiservices.h @@ -0,0 +1,39 @@ +/* + wifiservices.h - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + + +#ifndef _WIFI_SERVICES_H +#define _WIFI_SERVICES_H + + +class WiFiServices { + public: + WiFiServices(); + ~WiFiServices(); + static bool begin(); + static void end(); + static void handle(); +}; + +extern WiFiServices wifi_services; + +#endif + diff --git a/Grbl_Esp32-master/LICENSE b/Grbl_Esp32-master/LICENSE new file mode 100644 index 0000000..9e419e0 --- /dev/null +++ b/Grbl_Esp32-master/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/Grbl_Esp32-master/README.md b/Grbl_Esp32-master/README.md new file mode 100644 index 0000000..460749d --- /dev/null +++ b/Grbl_Esp32-master/README.md @@ -0,0 +1,56 @@ + + +# Grbl (CNC Controller) For ESP32 + +![ESP32](http://www.buildlog.net/blog/wp-content/uploads/2018/10/20181007_153826.jpg) + +### Project Overview + +[![Build Status](https://travis-ci.com/odaki/Grbl_Esp32.svg?branch=master)](https://travis-ci.com/odaki/Grbl_Esp32) + +This is a port of [Grbl](https://github.com/gnea/grbl) for the ESP32. The ESP32 is potentially a great target for Grbl for the following reasons + +- **Faster** - At least 4x the step rates over Grbl +- **Lower Cost** - +- **Small footprint** - +- **More Flash and RAM** - A larger planner buffer could be used and more features could be added. +- **I/O** - It has just about the same number of pins as an Arduino UNO, the original target for Grbl +- **Peripherals** - It has more timers and advanced features than an UNO. These can also be mapped to pins more flexibly. +- **Connectivity** - Bluetooth and WiFi built in. +- **Fast Boot** - Boots almost instantly and does not need to be formally shutdown (unlike Raspberry Pi or Beagle Bone) +- **RTOS (Real Time operating System)** - Custom features can be added without affecting the performance of the motion control system. + + +### Using It + +The code should be compiled using the latest Arduino IDE. [Follow instructions here](https://github.com/espressif/arduino-esp32) on how to setup ESP32 in the IDE. The choice was made to use the Arduino IDE over the ESP-IDF to make the code a little more accessible to novices trying to compile the code. + +I use the ESP32 Dev Module version of the ESP32. I suggest starting with that if you don't have hardware yet. + +For basic instructions on using Grbl use the [gnea/grbl wiki](https://github.com/gnea/grbl/wiki). That is the Arduino version of Grbl, so keep that in mind regarding hardware setup. If you have questions ask via the GitHub issue system for this project. + +### Roadmap + +The roadmap is now [on the wiki](https://github.com/bdring/Grbl_Esp32/wiki/Development-Roadmap). + +### Credits + +The original [Grbl](https://github.com/gnea/grbl) is an awesome project by Sungeon (Sonny) Jeon. I have known him for many years and he is always very helpful. I have used Grbl on many projects. I only ported because of the limitation of the processors it was designed for. The core engine design is virtually unchanged. + +The Wifi and WebUI is based on [this project.](https://github.com/luc-github/ESP3D-WEBUI) + +### Contribute + +![](http://www.buildlog.net/blog/wp-content/uploads/2018/07/slack_hash_128.png) There is a slack channel for the development this project. Ask for an Invite + +### FAQ + +Start asking questions...I'll put the frequent ones here. + + + +### Donation + +This project requires a lot of work and often expensive items for testing. Please consider a safe, secure and highly appreciated donation via the PayPal link below. + +[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TKNJ9Z775VXB2) diff --git a/Grbl_Esp32-master/command.sh b/Grbl_Esp32-master/command.sh new file mode 100644 index 0000000..e366f20 --- /dev/null +++ b/Grbl_Esp32-master/command.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +function build_sketch() +{ + local sketch=$1 + + # buld sketch with arudino ide + echo -e "\n Build $sketch \n" + arduino --verbose --verify $sketch + + # get build result from arduino + local re=$? + + # check result + if [ $re -ne 0 ]; then + echo "Failed to build $sketch" + return $re + fi +} diff --git a/Grbl_Esp32-master/doc/Commands.txt b/Grbl_Esp32-master/doc/Commands.txt new file mode 100644 index 0000000..2071a42 --- /dev/null +++ b/Grbl_Esp32-master/doc/Commands.txt @@ -0,0 +1,153 @@ +This file defines the [ESP...] style commands. They can be used via serial, Bluetooth or Wifi +These are used for basic wireless settings as well as SD card commands + +Some commands need to be authenticated with a password. Authentication +is optional via #define ENABLE_AUTHENTICATION in the config.h file. +The default password is "admin" + +============== Examples (assumes "admin" password): =============== + +Command: [ESP800]pwd=admin +Reply: FW version:1.1f # FW target:grbl-embedded # FW HW:Direct SD # primary sd:/sd # secondary sd:none # authentication:yes # webcommunication: Sync: 81# hostname:grblesp + +Command: [ESP111] +Reply: 192.168.1.11 + +Command: [ESP121]pwd=admin +Reply: 80 + +Command: [ESP121]pwd=admin +Reply: 80 + +Command: [ESP121]81 pwd=admin +Reply: ok + +Command: [ESP121]pwd=admin +Reply: 81 + +=============== Command Reference ======================== + + +* Set/Get STA SSID +[ESP100]pwd= + +* Set STA Password +[ESP101]pwd= + +* Set/Get STA IP mode (DHCP/STATIC) +[ESP102]pwd= + +* Set/Get STA IP/Mask/GW +[ESP103]IP= MSK= GW= pwd= + +* Set/Get AP SSID +[ESP105]pwd= + +* Change AP Password +[ESP106]pwd= + +* Set/Get AP IP +[ESP107]pwd= + +* Set/Get AP channel +[ESP108]pwd= + +* Set/Get radio state which can be STA, AP, BT, OFF +[ESP110]pwd= + +* Get current IP +[ESP111]
+ +* Get/Set hostname +[ESP112] pwd= + +* Get/Set immediate Radio (WiFi/BT) state which can be ON, OFF +[ESP115]pwd= + +* Get/Set HTTP state which can be ON, OFF +[ESP120]pwd= + +* Get/Set HTTP port +[ESP121]pwd= + +* Get/Set Telnet state which can be ON, OFF +[ESP130]pwd= + +* Get/Set Telnet port +[ESP131]pwd= + +* Get/Set btname +[ESP140]< Bluetooth name> pwd= + +* Get SD Card Status +[ESP200] pwd= + +* Get SD Card Content +[ESP210] pwd= + +* Delete SD Card file / directory +[ESP215]pwd= + +* Print SD file +[ESP220] pwd= + +*Get full EEPROM settings content +but do not give any passwords +[ESP400] pwd= + +*Set EEPROM setting +position in EEPROM, type: B(byte), I(integer/long), S(string), A(IP address / mask) +[ESP401]P= T= V= pwd= +Positions: +HOSTNAME_ENTRY "ESP_HOSTNAME" String +STA_SSID_ENTRY "STA_SSID" String +STA_PWD_ENTRY "STA_PWD" String +STA_IP_ENTRY "STA_IP" IP +STA_GW_ENTRY "STA_GW" IP +STA_MK_ENTRY "STA_MK" IP +ESP_WIFI_MODE "WIFI_MODE" Byte (0=OFF, STA=1, AP=2) +AP_SSID_ENTRY "AP_SSID" String +AP_PWD_ENTRY "AP_PWD" String +AP_IP_ENTRY "AP_IP" IP +AP_CHANNEL_ENTRY "AP_CHANNEL" Byte +HTTP_ENABLE_ENTRY "HTTP_ON" Byte (0=Disabled, 1=Enabled) +HTTP_PORT_ENTRY "HTTP_PORT" Integer +TELNET_ENABLE_ENTRY "TELNET_ON" Byte (0=Disabled, 1=Enabled) +TELNET_PORT_ENTRY "TELNET_PORT" Integer +STA_IP_MODE_ENTRY "STA_IP_MODE" Byte (0=DHCP, 1=STATIC) + +*Get available AP list (limited to 30) +output is JSON or plain text according parameter +[ESP410] pwd= + +*Get current settings of ESP3D +output is JSON or plain text according parameter +[ESP420]pwd= + +* Restart ESP +[ESP444]RESTART pwd= + +* Change / Reset user password +[ESP555]pwd= +if no password set it use default one + +* Send Notification +[ESP600]msg [pwd=] + +* Set/Get Notification settings +[ESP610]type= T1= T2= TS= [pwd=] +Get will give type and settings only, not the protected T1/T2 + +* Read SPIFFS file and send each line to serial +[ESP700] pwd= + +* Format SPIFFS +[ESP710]FORMAT pwd= + +* SPIFFS total size and used size +[ESP720]
pwd= + +* Get fw version and basic information +[ESP800]
+ + diff --git a/Grbl_Esp32-master/doc/csv/alarm_codes_en_US.csv b/Grbl_Esp32-master/doc/csv/alarm_codes_en_US.csv new file mode 100644 index 0000000..1ab2b8a --- /dev/null +++ b/Grbl_Esp32-master/doc/csv/alarm_codes_en_US.csv @@ -0,0 +1,10 @@ +"Alarm Code in v1.1+"," Alarm Message in v1.0-"," Alarm Description" +"1","Hard limit","Hard limit has been triggered. Machine position is likely lost due to sudden halt. Re-homing is highly recommended." +"2","Soft limit","Soft limit alarm. G-code motion target exceeds machine travel. Machine position retained. Alarm may be safely unlocked." +"3","Abort during cycle","Reset while in motion. Machine position is likely lost due to sudden halt. Re-homing is highly recommended." +"4","Probe fail","Probe fail. Probe is not in the expected initial state before starting probe cycle when G38.2 and G38.3 is not triggered and G38.4 and G38.5 is triggered." +"5","Probe fail","Probe fail. Probe did not contact the workpiece within the programmed travel for G38.2 and G38.4." +"6","Homing fail","Homing fail. The active homing cycle was reset." +"7","Homing fail","Homing fail. Safety door was opened during homing cycle." +"8","Homing fail","Homing fail. Pull off travel failed to clear limit switch. Try increasing pull-off setting or check wiring." +"9","Homing fail","Homing fail. Could not find limit switch within search distances. Try increasing max travel, decreasing pull-off distance, or check wiring." diff --git a/Grbl_Esp32-master/doc/csv/build_option_codes_en_US.csv b/Grbl_Esp32-master/doc/csv/build_option_codes_en_US.csv new file mode 100644 index 0000000..9a6d0fb --- /dev/null +++ b/Grbl_Esp32-master/doc/csv/build_option_codes_en_US.csv @@ -0,0 +1,22 @@ +OPT: Code, Build-Option Description,State +V,Variable spindle,Enabled +N,Line numbers,Enabled +M,Mist coolant M7,Enabled +C,CoreXY,Enabled +P,Parking motion,Enabled +Z,Homing force origin,Enabled +H,Homing single axis commands,Enabled +T,Two limit switches on axis,Enabled +A,Allow feed rate overrides in probe cycles,Enabled +D,Use spindle direction as enable pin,Enabled +0,Spindle enable off when speed is zero,Enabled +S,Software limit pin debouncing,Enabled +R,Parking override control,Enabled ++,Safety door input pin,Enabled +*,Restore all EEPROM command,Disabled +$,Restore EEPROM `$` settings command,Disabled +#,Restore EEPROM parameter data command,Disabled +I,Build info write user string command,Disabled +E,Force sync upon EEPROM write,Disabled +W,Force sync upon work coordinate offset change,Disabled +L,Homing initialization auto-lock,Disabled \ No newline at end of file diff --git a/Grbl_Esp32-master/doc/csv/error_codes_en_US.csv b/Grbl_Esp32-master/doc/csv/error_codes_en_US.csv new file mode 100644 index 0000000..b64832b --- /dev/null +++ b/Grbl_Esp32-master/doc/csv/error_codes_en_US.csv @@ -0,0 +1,44 @@ +"Error Code in v1.1+","Error Message in v1.0-","Error Description" +"1","Expected command letter","G-code words consist of a letter and a value. Letter was not found." +"2","Bad number format","Missing the expected G-code word value or numeric value format is not valid." +"3","Invalid statement","Grbl '$' system command was not recognized or supported." +"4","Value < 0","Negative value received for an expected positive value." +"5","Setting disabled","Homing cycle failure. Homing is not enabled via settings." +"6","Value < 3 usec","Minimum step pulse time must be greater than 3usec." +"7","EEPROM read fail. Using defaults","An EEPROM read failed. Auto-restoring affected EEPROM to default values." +"8","Not idle","Grbl '$' command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job." +"9","G-code lock","G-code commands are locked out during alarm or jog state." +"10","Homing not enabled","Soft limits cannot be enabled without homing also enabled." +"11","Line overflow","Max characters per line exceeded. Received command line was not executed." +"12","Step rate > 30kHz","Grbl '$' setting value cause the step rate to exceed the maximum supported." +"13","Check Door","Safety door detected as opened and door state initiated." +"14","Line length exceeded","Build info or startup line exceeded EEPROM line length limit. Line not stored." +"15","Travel exceeded","Jog target exceeds machine travel. Jog command has been ignored." +"16","Invalid jog command","Jog command has no '=' or contains prohibited g-code." +"17","Setting disabled","Laser mode requires PWM output." +"20","Unsupported command","Unsupported or invalid g-code command found in block." +"21","Modal group violation","More than one g-code command from same modal group found in block." +"22","Undefined feed rate","Feed rate has not yet been set or is undefined." +"23","Invalid gcode ID:23","G-code command in block requires an integer value." +"24","Invalid gcode ID:24","More than one g-code command that requires axis words found in block." +"25","Invalid gcode ID:25","Repeated g-code word found in block." +"26","Invalid gcode ID:26","No axis words found in block for g-code command or current modal state which requires them." +"27","Invalid gcode ID:27","Line number value is invalid." +"28","Invalid gcode ID:28","G-code command is missing a required value word." +"29","Invalid gcode ID:29","G59.x work coordinate systems are not supported." +"30","Invalid gcode ID:30","G53 only allowed with G0 and G1 motion modes." +"31","Invalid gcode ID:31","Axis words found in block when no command or current modal state uses them." +"32","Invalid gcode ID:32","G2 and G3 arcs require at least one in-plane axis word." +"33","Invalid gcode ID:33","Motion command target is invalid." +"34","Invalid gcode ID:34","Arc radius value is invalid." +"35","Invalid gcode ID:35","G2 and G3 arcs require at least one in-plane offset word." +"36","Invalid gcode ID:36","Unused value words found in block." +"37","Invalid gcode ID:37","G43.1 dynamic tool length offset is not assigned to configured tool length axis." +"38","Invalid gcode ID:38","Tool number greater than max supported value." +"39","Parameter P exceeded max ID:39","Parameter P exceeded max" +"60","SD failed to mount" +"61","SD card failed to open file for reading" +"62","SD card failed to open directory" +"63","SD Card directory not found" +"64","SD Card file empty" +"70","Bluetooth failed to start" diff --git a/Grbl_Esp32-master/doc/csv/setting_codes_en_US.csv b/Grbl_Esp32-master/doc/csv/setting_codes_en_US.csv new file mode 100644 index 0000000..5536262 --- /dev/null +++ b/Grbl_Esp32-master/doc/csv/setting_codes_en_US.csv @@ -0,0 +1,46 @@ +"$-Code"," Setting"," Units"," Setting Description" +"0","Step pulse time","microseconds","Sets time length per step. Minimum 3usec." +"1","Step idle delay","milliseconds","Sets a short hold delay when stopping to let dynamics settle before disabling steppers. Value 255 keeps motors enabled with no delay." +"2","Step pulse invert","mask","Inverts the step signal. Set axis bit to invert (00000ZYX)." +"3","Step direction invert","mask","Inverts the direction signal. Set axis bit to invert (00000ZYX)." +"4","Invert step enable pin","boolean","Inverts the stepper driver enable pin signal." +"5","Invert limit pins","boolean","Inverts the all of the limit input pins." +"6","Invert probe pin","boolean","Inverts the probe input pin signal." +"10","Status report options","mask","Alters data included in status reports." +"11","Junction deviation","millimeters","Sets how fast Grbl travels through consecutive motions. Lower value slows it down." +"12","Arc tolerance","millimeters","Sets the G2 and G3 arc tracing accuracy based on radial error. Beware: A very small value may effect performance." +"13","Report in inches","boolean","Enables inch units when returning any position and rate value that is not a settings value." +"20","Soft limits enable","boolean","Enables soft limits checks within machine travel and sets alarm when exceeded. Requires homing." +"21","Hard limits enable","boolean","Enables hard limits. Immediately halts motion and throws an alarm when switch is triggered." +"22","Homing cycle enable","boolean","Enables homing cycle. Requires limit switches on all axes." +"23","Homing direction invert","mask","Homing searches for a switch in the positive direction. Set axis bit (00000ZYX) to search in negative direction." +"24","Homing locate feed rate","mm/min","Feed rate to slowly engage limit switch to determine its location accurately." +"25","Homing search seek rate","mm/min","Seek rate to quickly find the limit switch before the slower locating phase." +"26","Homing switch debounce delay","milliseconds","Sets a short delay between phases of homing cycle to let a switch debounce." +"27","Homing switch pull-off distance","millimeters","Retract distance after triggering switch to disengage it. Homing will fail if switch isn't cleared." +"30","Maximum spindle speed","RPM","Maximum spindle speed. Sets PWM to 100% duty cycle." +"31","Minimum spindle speed","RPM","Minimum spindle speed. Sets PWM to 0.4% or lowest duty cycle." +"32","Laser-mode enable","boolean","Enables laser mode. Consecutive G1/2/3 commands will not halt when spindle speed is changed." +"33","Spindle PWM Freq","16-bit","Spindle PWM Freq" +"34","Spindle PWM Off Value","16-bit","Spindle PWM Off Value" +"35","Spindle PWM Min Value","16-bit","Spindle PWM Min Value" +"36","Spindle PWM Max Value","16-bit","Spindle PWM Max Value" +"80-84","User integer Values","unsigned 16-bit","Reserved for custom machine use" +"90-94","User Floating point value","float","Reserved for custom machine use" +"100","X-axis travel resolution","step/mm","X-axis travel resolution in steps per millimeter." +"101","Y-axis travel resolution","step/mm","Y-axis travel resolution in steps per millimeter." +"102","Z-axis travel resolution","step/mm","Z-axis travel resolution in steps per millimeter." +"110","X-axis maximum rate","mm/min","X-axis maximum rate. Used as G0 rapid rate." +"111","Y-axis maximum rate","mm/min","Y-axis maximum rate. Used as G0 rapid rate." +"112","Z-axis maximum rate","mm/min","Z-axis maximum rate. Used as G0 rapid rate." +"120","X-axis acceleration","mm/sec^2","X-axis acceleration. Used for motion planning to not exceed motor torque and lose steps." +"121","Y-axis acceleration","mm/sec^2","Y-axis acceleration. Used for motion planning to not exceed motor torque and lose steps." +"122","Z-axis acceleration","mm/sec^2","Z-axis acceleration. Used for motion planning to not exceed motor torque and lose steps." +"130","X-axis maximum travel","millimeters","Maximum X-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances." +"131","Y-axis maximum travel","millimeters","Maximum Y-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances." +"132","Z-axis maximum travel","millimeters","Maximum Z-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances." +"140-145","Motor run current","Amps","Motor run current for SPI (Trinamic) type motors" +"150-155","Motor hold current","Percent","Hold current in percent of run current for SPI (Trinamic) type motors" +"160-165","Motor microstepping","micros/step","Number of microsteps per step for SPI (Trinamic) type motors" +"170-175","Motor Stallguard Value","0-255","Value of Stallguard setting for SPI (Trinamic) type motors" + diff --git a/Grbl_Esp32-master/doc/script/fit_nonlinear_spindle.py b/Grbl_Esp32-master/doc/script/fit_nonlinear_spindle.py new file mode 100644 index 0000000..57ca5f3 --- /dev/null +++ b/Grbl_Esp32-master/doc/script/fit_nonlinear_spindle.py @@ -0,0 +1,373 @@ +""" +--------------------- +The MIT License (MIT) + +Copyright (c) 2017-2018 Sungeun K. Jeon for Gnea Research LLC + +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. +--------------------- +""" + + +""" +This Python script produces a continuous piece-wise line fit of actual spindle speed over +programmed speed/PWM, which must be measured and provided by the user. A plot of the data +and line fit will be auto-generated and saved in the working directory as 'line_fit.png'. + +REQUIREMENTS: + - Python 2.7 or 3.x with SciPy, NumPy, and Matplotlib Python Libraries + + - For the most people, the easiest way to run this script is on the free cloud service + https://repl.it/site/languages/python3. No account necessary. Unlimited runs. + + - Last checked on 4/5/2018. This site has been regularly evolving and becoming more + powerful. Things may not work exactly as shown. Please report any issues. + + - To use, go to the website and start the Python REPL. Copy and paste this script into + the main.py file in the browser editor. Click 'Run' and a solution should appear in + the text output in the REPL console window. You can edit the script directly in the + browser and re-run the script as many times as you need. A free account is only + necessary if you want to save files on their servers. + + - This script will also automatically generate a png image with the plot of the data + with the piece-wise linear fit over it, but this will not show up by default on this + website. To enable this, just click the 'Add File' icon and create a dummy file. + Name it anything, like dummy.py. Leave this file blank. Doing this places the REPL + in multiple file mode and will enable viewing the plot. Click the 'Run' icon. The + solution will be presented again in the console, and the data plot will appear in + the file list called 'line_fit.png'. Click the file to view the plot. + + - For offline Python installs, most Mac and Linux computers have Python pre-installed + with the required libraries. If not, a quick google search will show you how to + install them. For Windows, Python installations are bit more difficult. Anaconda and + Pyzo seem to work well. + +USAGE: + - First, make sure you are using the stock build of Grbl for the 328p processor. Most + importantly, the SPINDLE_PWM_MAX_VALUE and SPINDLE_PWM_MIN_VALUE should be unaltered + from defaults, otherwise change them back to 255.0 and 1.0 respectively for this test. + + - Next, program the max and min rpm Grbl settings to '$30=255' and '$31=1'. This sets + the internal PWM values equal to 'S' spindle speed for the standard Grbl build. + + - Check if your spindle does not turn on at very low voltages by setting 'S' spindle + speed to 'S1'. If it does not turn on or turns at a non-useful rpm, increase 'S' by + one until it does. Write down this 'S' value for later. You'll start the rpm data + collection from this point onward and will need to update the SPINDLE_PWM_MIN_VALUE + in cpu_map.h afterwards. + + - Collect actual spindle speed with a tachometer or similar means over a range of 'S' + and PWM values. Start by setting the spindle 'S' speed to the minimum useful 'S' from + the last step and measure and record actual spindle rpm. Next, increase 'S' spindle + speed over equally sized intervals and repeat the measurement. Increments of 20 rpm + should be more than enough, but decrease increment size, if highly nonlinear. Complete + the data collection the 'S' spindle speed equal to '$30' max rpm, or at the max useful + rpm, and record the actual rpm output. Make sure to collect rpm data all the way + throughout useful output rpm. The actual operating range within this model may be set + later within Grbl with the '$30' and '$31' settings. + + - In some cases, spindle PWM output can have discontinuities or not have a useful rpm + in certain ranges. For example, a known controller board has the spindle rpm drop + completely at voltages above ~4.5V. If you have discontinuities like this at the low + or high range of rpm, simply trim them from the data set. Don't include them. For + Grbl to compensate, you'll need to alter the SPINDLE_PWM_MIN_VALUE and/or + SPINDLE_PWM_MAX_VALUE in cpu_map.h to where your data set ends. This script will + indicate if you need to do that in the solution output. + + - Keep in mind that spindles without control electronics can slow down drastically when + cutting and under load. How much it slows down is dependent on a lot of factors, such + as feed rate, chip load, cutter diameter, flutes, cutter type, lubricant/coolant, + material being cut, etc. Even spindles with controllers can still slow down if the + load is higher than the max current the controller can provide. It's recommended to + frequently re-check and measure actual spindle speed during a job. You can always use + spindle speed overrides to tweak it temporarily to the desired speed. + + - Edit this script and enter the measured rpm values and their corresponding 'S' spindle + speed values in the data arrays below. Set the number of piecewise lines you would + like to use, from one to four lines. For most cases, four lines is perfectly fine. + In certain scenarios (laser engraving), this may significantly degrade performance and + should be reduced if possible. + + - Run the Python script. Visually assess the line fit from the plot. It will not likely + to what you want on the first go. Dial things in by altering the line fit junction + points 'PWM_pointX' in this script to move where the piecewise line junctions are + located along the plot x-axis. It may be desired to tweak the junction points so the + model solution is more accurate in the region that the spindle typically running. + Re-run the script and tweak the junction points until you are satified with the model. + + - Record the solution and enter the RPM_POINT and RPM_LINE values into config.h. Set the + number of piecewise lines used in this model in config.h. Also set the '$30' and '$31' + max and min rpm values to the solution values or in a range between them in Grbl '$' + settings. And finally, alter the SPINDLE_PWM_MIN_VALUE in cpu_map.h, if your spindle + needs to be above a certain voltage to produce a useful low rpm. + + - Once the solution is entered. Recompile and flash Grbl. This solution model is only + valid for this particular set of data. If the machine is altered, you will need to + perform this experiment again and regenerate a new model here. + +OUTPUT: + The solver produces a set of values that define the piecewise fit and can be used by + Grbl to quickly and efficiently compute spindle PWM output voltage for a desired RPM. + + The first two are the RPM_MAX ($30) and RPM_MIN ($31) Grbl settings. These must be + programmed into Grbl manually or setup in defaults.h for new systems. Altering these + values within Grbl after a piece-wise linear model is installed will not change alter + model. It will only alter the range of spindle speed rpm values Grbl output. + + For example, if the solver produces an RPM_MAX of 9000 and Grbl is programmed with + $30=8000, S9000 may be programmed, but Grbl will only produce the output voltage to run + at 8000 rpm. In other words, Grbl will only output voltages the range between + max(RPM_MIN,$31) and min(RPM_MAX,$30). + + The remaining values define the slopes and offsets of the line segments and the junction + points between line segments, like so for n_pieces=3: + + PWM_output = RPM_LINE_A1 * rpm - RPM_LINE_B1 [ RPM_MIN < rpm < RPM_POINT12 ] + PWM_output = RPM_LINE_A2 * rpm - RPM_LINE_B2 [ RPM_POINT12 < rpm < RPM_POINT23 ] + PWM_output = RPM_LINE_A3 * rpm - RPM_LINE_B3 [ RPM_POINT23 < rpm < RPM_MAX ] + + NOTE: The script solves in terms of PWM but the final equations and values are expressed + in terms of rpm in the form 'PWM = a*rpm - b'. + +""" + +from scipy import optimize +import numpy as np + +# ---------------------------------------------------------------------------------------- +# Configure spindle PWM line fit solver + +n_pieces = 4 # Number of line segments used for data fit. Only 1 to 4 line segments supported. + +# Programmed 'S' spindle speed values. Must start with minimum useful PWM or 'S' programmed +# value and end with the maximum useful PWM or 'S' programmed value. Order of the array must +# be synced with the RPM_measured array below. +# NOTE: ** DO NOT USE DATA FROM AN EXISTING PIECEWISE LINE FIT. USE DEFAULT GRBL MODEL ONLY. ** +PWM_set = np.array([2,18,36,55,73,91,109,127,146,164,182,200,218,237,254], dtype=float) + +# Actual RPM measured at the spindle. Must be in the ascending value and equal in length +# as the PWM_set array. Must include the min and max measured rpm output in the first and +# last array entries, respectively. +RPM_measured = np.array([213.,5420,7145,8282,9165,9765,10100,10500,10700,10900,11100,11250,11400,11550,11650], dtype=float) + +# Configure line fit points by 'S' programmed rpm or PWM value. Values must be between +# PWM_max and PWM_min. Typically, alter these values to space the points evenly between +# max and min PWM range. However, they may be tweaked to maximize accuracy in the places +# you normally operate for highly nonlinear curves. Plot to visually assess how well the +# solution fits the data. +PWM_point1 = 20.0 # (S) Point between segments 0 and 1. Used when n_pieces >= 2. +PWM_point2 = 80.0 # (S) Point between segments 1 and 2. Used when n_pieces >= 3. +PWM_point3 = 150.0 # (S) Point between segments 2 and 3. Used when n_pieces = 4. + +# ---------------------------------------------------------------------------------------- + +# Advanced settings + +# The optimizer requires an initial guess of the solution. Change value if solution fails. +slope_i = 100.0; # > 0.0 + +PWM_max = max(PWM_set) # Maximum PWM set in measured range +PWM_min = min(PWM_set) # Minimum PWM set in measured range +plot_figure = True # Set to False, if matplotlib is not available. + +# ---------------------------------------------------------------------------------------- +# DO NOT ALTER ANYTHING BELOW. + +def piecewise_linear_1(x,b,k1): + return np.piecewise(x, [(x>=PWM_min)&(x<=PWM_max)], [lambda x:k1*(x-PWM_min)+b]) + +def piecewise_linear_2(x,b,k1,k2): + c = [b, + b+k1*(PWM_point1-PWM_min)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1]] + conds = [(x=PWM_min), + (x<=PWM_max)&(x>=PWM_point1)] + return np.piecewise(x, conds, funcs) + +def piecewise_linear_3(x,b,k1,k2,k3): + c = [b, + b+k1*(PWM_point1-PWM_min), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1], + lambda x:k3*(x-PWM_point2)+c[2]] + conds = [(x=PWM_min), + (x=PWM_point1), + (x<=PWM_max)&(x>=PWM_point2)] + return np.piecewise(x, conds, funcs) + +def piecewise_linear_4(x,b,k1,k2,k3,k4): + c = [b, + b+k1*(PWM_point1-PWM_min), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)+k3*(PWM_point3-PWM_point2)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1], + lambda x:k3*(x-PWM_point2)+c[2], + lambda x:k4*(x-PWM_point3)+c[3]] + conds = [(x=PWM_min), + (x=PWM_point1), + (x=PWM_point2), + (x<=PWM_max)&(x>=PWM_point3)] + return np.piecewise(x, conds, funcs) + +# ---------------------------------------------------------------------------------------- + +print("\nCONFIG:") +print(" N_pieces: %i" % n_pieces) +print(" PWM_min: %.1f" % PWM_min) +print(" PWM_max: %.1f" % PWM_max) +if n_pieces > 1: + print(" PWM_point1: %.1f" % PWM_point1) +if n_pieces > 2: + print(" PWM_point2: %.1f" % PWM_point2) +if n_pieces > 3: + print(" PWM_point3: %.1f" % PWM_point3) +print(" N_data: %i" % len(RPM_measured)) +print(" PWM_set: ", PWM_set) +print(" RPM_measured: ", RPM_measured) + +if n_pieces == 1: + piece_func = piecewise_linear_1 + p_initial = [RPM_measured[0],slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1]] + b = [ p[0]-p[1]*PWM_min] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min)] + +elif n_pieces == 2: + piece_func = piecewise_linear_2 + p_initial = [RPM_measured[0],slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_max-PWM_point1)] + +elif n_pieces == 3: + piece_func = piecewise_linear_3 + p_initial = [RPM_measured[0],slope_i,slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2],p[3]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_max-PWM_point2) ] + +elif n_pieces == 4: + piece_func = piecewise_linear_4 + p_initial = [RPM_measured[0],slope_i,slope_i,slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2],p[3],p[4]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2)-p[4]*PWM_point3 ] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2)+p[4]*(PWM_max-PWM_point3) ] + +else : + print("ERROR: Unsupported number of pieces. Check and alter n_pieces") + quit() + +print("\nSOLUTION:\n\n[Update these #define values and uncomment]\n[ENABLE_PIECEWISE_LINEAR_SPINDLE in config.h.]") +print("#define N_PIECES %.0f" % n_pieces) +print("#define RPM_MAX %.1f" % rpm[-1]) +print("#define RPM_MIN %.1f" % rpm[0]) + +if n_pieces > 1: + print("#define RPM_POINT12 %.1f" % rpm[1]) +if n_pieces > 2: + print("#define RPM_POINT23 %.1f" %rpm[2]) +if n_pieces > 3: + print("#define RPM_POINT34 %.1f" %rpm[3]) + +print("#define RPM_LINE_A1 %.6e" % (1./a[0])) +print("#define RPM_LINE_B1 %.6e" % (b[0]/a[0])) +if n_pieces > 1: + print("#define RPM_LINE_A2 %.6e" % (1./a[1])) + print("#define RPM_LINE_B2 %.6e" % (b[1]/a[1])) +if n_pieces > 2: + print("#define RPM_LINE_A3 %.6e" % (1./a[2])) + print("#define RPM_LINE_B3 %.6e" % (b[2]/a[2])) +if n_pieces > 3: + print("#define RPM_LINE_A4 %.6e" % (1./a[3])) + print("#define RPM_LINE_B4 %.6e" % (b[3]/a[3])) + +print("\n[To operate over full model range, manually write these]") +print("['$' settings or alter values in defaults.h. Grbl will]") +print("[operate between min($30,RPM_MAX) and max($31,RPM_MIN)]") +print("$30=%.1f (rpm max)" % rpm[-1]) +print("$31=%.1f (rpm min)" % rpm[0]) + +if (PWM_min > 1)|(PWM_max<255): + print("\n[Update the following #define values in cpu_map.h]") + if (PWM_min >1) : + print("#define SPINDLE_PWM_MIN_VALUE %.0f" % PWM_min) + if PWM_max <255: + print("#define SPINDLE_PWM_MAX_VALUE %.0f" % PWM_max) +else: + print("\n[No cpu_map.h changes required.]") +print("\n") + +test_val = (1./a[0])*rpm[0] - (b[0]/a[0]) +if test_val < 0.0 : + print("ERROR: Solution is negative at RPM_MIN. Adjust junction points or increase n_pieces.\n") + +if plot_figure: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + fig = plt.figure() + ax = fig.add_subplot(111) + xd = np.linspace(PWM_min, PWM_max, 10000) + ax.plot(PWM_set, RPM_measured, "o") + ax.plot(xd, piece_func(xd, *p),'g') + plt.xlabel("Programmed PWM") + plt.ylabel("Measured RPM") + + # Check solution by plotting in terms of rpm. +# x = np.linspace(rpm[0], rpm[1], 10000) +# ax.plot((1./a[0])*x-(b[0]/a[0]),x,'r:') +# if n_pieces > 1: +# x = np.linspace(rpm[1], rpm[2], 10000) +# ax.plot((1./a[1])*x-(b[1]/a[1]),x,'r:') +# if n_pieces > 2: +# x = np.linspace(rpm[2], rpm[3], 10000) +# ax.plot((1./a[2])*x-(b[2]/a[2]),x,'r:') +# if n_pieces > 3: +# x = np.linspace(rpm[3], rpm[-1], 10000) +# ax.plot((1./a[3])*x-(b[3]/a[3]),x,'r:') + + fig.savefig("line_fit.png") diff --git a/Grbl_Esp32-master/doc/script/piecewise_example.png b/Grbl_Esp32-master/doc/script/piecewise_example.png new file mode 100644 index 0000000000000000000000000000000000000000..83b24d653abe4dad01bee36376fb236af7e5f57a GIT binary patch literal 38812 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 literal 0 HcmV?d00001 diff --git a/Grbl_Esp32-master/doc/script/simple_stream.py b/Grbl_Esp32-master/doc/script/simple_stream.py new file mode 100644 index 0000000..67c2a2c --- /dev/null +++ b/Grbl_Esp32-master/doc/script/simple_stream.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +"""\ +Simple g-code streaming script for grbl + +Provided as an illustration of the basic communication interface +for grbl. When grbl has finished parsing the g-code block, it will +return an 'ok' or 'error' response. When the planner buffer is full, +grbl will not send a response until the planner buffer clears space. + +G02/03 arcs are special exceptions, where they inject short line +segments directly into the planner. So there may not be a response +from grbl for the duration of the arc. + +--------------------- +The MIT License (MIT) + +Copyright (c) 2012 Sungeun K. Jeon + +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. +--------------------- +""" + +import serial +import time + +# Open grbl serial port +s = serial.Serial('/dev/tty.usbmodem1811',115200) + +# Open g-code file +f = open('grbl.gcode','r'); + +# Wake up grbl +s.write("\r\n\r\n") +time.sleep(2) # Wait for grbl to initialize +s.flushInput() # Flush startup text in serial input + +# Stream g-code to grbl +for line in f: + l = line.strip() # Strip all EOL characters for consistency + print 'Sending: ' + l, + s.write(l + '\n') # Send g-code block to grbl + grbl_out = s.readline() # Wait for grbl response with carriage return + print ' : ' + grbl_out.strip() + +# Wait here until grbl is finished to close serial port and file. +raw_input(" Press to exit and disable grbl.") + +# Close file and serial port +f.close() +s.close() \ No newline at end of file diff --git a/Grbl_Esp32-master/doc/script/stream.py b/Grbl_Esp32-master/doc/script/stream.py new file mode 100644 index 0000000..4a637ab --- /dev/null +++ b/Grbl_Esp32-master/doc/script/stream.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +"""\ + +Stream g-code to grbl controller + +This script differs from the simple_stream.py script by +tracking the number of characters in grbl's serial read +buffer. This allows grbl to fetch the next line directly +from the serial buffer and does not have to wait for a +response from the computer. This effectively adds another +buffer layer to prevent buffer starvation. + +CHANGELOG: +- 20170531: Status report feedback at 1.0 second intervals. + Configurable baudrate and report intervals. Bug fixes. +- 20161212: Added push message feedback for simple streaming +- 20140714: Updated baud rate to 115200. Added a settings + write mode via simple streaming method. MIT-licensed. + +TODO: +- Add realtime control commands during streaming. + +--------------------- +The MIT License (MIT) + +Copyright (c) 2012-2017 Sungeun K. Jeon + +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. +--------------------- +""" + +import serial +import re +import time +import sys +import argparse +import threading + +RX_BUFFER_SIZE = 128 +BAUD_RATE = 115200 +ENABLE_STATUS_REPORTS = True +REPORT_INTERVAL = 1.0 # seconds + +is_run = True # Controls query timer + +# Define command line argument interface +parser = argparse.ArgumentParser(description='Stream g-code file to grbl. (pySerial and argparse libraries required)') +parser.add_argument('gcode_file', type=argparse.FileType('r'), + help='g-code filename to be streamed') +parser.add_argument('device_file', + help='serial device path') +parser.add_argument('-q','--quiet',action='store_true', default=False, + help='suppress output text') +parser.add_argument('-s','--settings',action='store_true', default=False, + help='settings write mode') +parser.add_argument('-c','--check',action='store_true', default=False, + help='stream in check mode') +args = parser.parse_args() + +# Periodic timer to query for status reports +# TODO: Need to track down why this doesn't restart consistently before a release. +def send_status_query(): + s.write('?') + +def periodic_timer() : + while is_run: + send_status_query() + time.sleep(REPORT_INTERVAL) + + +# Initialize +s = serial.Serial(args.device_file,BAUD_RATE) +f = args.gcode_file +verbose = True +if args.quiet : verbose = False +settings_mode = False +if args.settings : settings_mode = True +check_mode = False +if args.check : check_mode = True + +# Wake up grbl +print "Initializing Grbl..." +s.write("\r\n\r\n") + +# Wait for grbl to initialize and flush startup text in serial input +time.sleep(2) +s.flushInput() + +if check_mode : + print "Enabling Grbl Check-Mode: SND: [$C]", + s.write("$C\n") + while 1: + grbl_out = s.readline().strip() # Wait for grbl response with carriage return + if grbl_out.find('error') >= 0 : + print "REC:",grbl_out + print " Failed to set Grbl check-mode. Aborting..." + quit() + elif grbl_out.find('ok') >= 0 : + if verbose: print 'REC:',grbl_out + break + +start_time = time.time(); + +# Start status report periodic timer +if ENABLE_STATUS_REPORTS : + timerThread = threading.Thread(target=periodic_timer) + timerThread.daemon = True + timerThread.start() + +# Stream g-code to grbl +l_count = 0 +error_count = 0 +if settings_mode: + # Send settings file via simple call-response streaming method. Settings must be streamed + # in this manner since the EEPROM accessing cycles shut-off the serial interrupt. + print "SETTINGS MODE: Streaming", args.gcode_file.name, " to ", args.device_file + for line in f: + l_count += 1 # Iterate line counter + # l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize + l_block = line.strip() # Strip all EOL characters for consistency + if verbose: print "SND>"+str(l_count)+": \"" + l_block + "\"" + s.write(l_block + '\n') # Send g-code block to grbl + while 1: + grbl_out = s.readline().strip() # Wait for grbl response with carriage return + if grbl_out.find('ok') >= 0 : + if verbose: print " REC<"+str(l_count)+": \""+grbl_out+"\"" + break + elif grbl_out.find('error') >= 0 : + if verbose: print " REC<"+str(l_count)+": \""+grbl_out+"\"" + error_count += 1 + break + else: + print " MSG: \""+grbl_out+"\"" +else: + # Send g-code program via a more agressive streaming protocol that forces characters into + # Grbl's serial read buffer to ensure Grbl has immediate access to the next g-code command + # rather than wait for the call-response serial protocol to finish. This is done by careful + # counting of the number of characters sent by the streamer to Grbl and tracking Grbl's + # responses, such that we never overflow Grbl's serial read buffer. + g_count = 0 + c_line = [] + for line in f: + l_count += 1 # Iterate line counter + l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize + # l_block = line.strip() + c_line.append(len(l_block)+1) # Track number of characters in grbl serial read buffer + grbl_out = '' + while sum(c_line) >= RX_BUFFER_SIZE-1 | s.inWaiting() : + out_temp = s.readline().strip() # Wait for grbl response + if out_temp.find('ok') < 0 and out_temp.find('error') < 0 : + print " MSG: \""+out_temp+"\"" # Debug response + else : + if out_temp.find('error') >= 0 : error_count += 1 + g_count += 1 # Iterate g-code counter + if verbose: print " REC<"+str(g_count)+": \""+out_temp+"\"" + del c_line[0] # Delete the block character count corresponding to the last 'ok' + s.write(l_block + '\n') # Send g-code block to grbl + if verbose: print "SND>"+str(l_count)+": \"" + l_block + "\"" + # Wait until all responses have been received. + while l_count > g_count : + out_temp = s.readline().strip() # Wait for grbl response + if out_temp.find('ok') < 0 and out_temp.find('error') < 0 : + print " MSG: \""+out_temp+"\"" # Debug response + else : + if out_temp.find('error') >= 0 : error_count += 1 + g_count += 1 # Iterate g-code counter + del c_line[0] # Delete the block character count corresponding to the last 'ok' + if verbose: print " REC<"+str(g_count)+": \""+out_temp + "\"" + +# Wait for user input after streaming is completed +print "\nG-code streaming finished!" +end_time = time.time(); +is_run = False; +print " Time elapsed: ",end_time-start_time,"\n" +if check_mode : + if error_count > 0 : + print "CHECK FAILED:",error_count,"errors found! See output for details.\n" + else : + print "CHECK PASSED: No errors found in g-code program.\n" +else : + print "WARNING: Wait until Grbl completes buffered g-code blocks before exiting." + raw_input(" Press to exit and disable Grbl.") + +# Close file and serial port +f.close() +s.close() diff --git a/Grbl_Esp32-master/embedded/build.bat b/Grbl_Esp32-master/embedded/build.bat new file mode 100644 index 0000000..85b2a5f --- /dev/null +++ b/Grbl_Esp32-master/embedded/build.bat @@ -0,0 +1,16 @@ +cd %~dp0 +cmd.exe /c npm install +cmd.exe /c npm audit fix +cmd.exe /c npm audit +cmd.exe /c gulp package +cmd.exe /c bin2c -o embedded.h -m tool.html.gz +cat header.txt > out.h +cat embedded.h >> out.h +cat footer.txt >> out.h +sed -i "s/tool_html_gz_size/PAGE_NOFILES_SIZE/g" ./out.h +sed -i "s/const unsigned char tool_html_gz/const char PAGE_NOFILES/g" ./out.h +sed -i "s/] = {/] PROGMEM = {/g" ./out.h +cat out.h > ../Grbl_Esp32/nofile.h +rm -f out.h +pause + diff --git a/Grbl_Esp32-master/embedded/embedded.h b/Grbl_Esp32-master/embedded/embedded.h new file mode 100644 index 0000000..d4b125a --- /dev/null +++ b/Grbl_Esp32-master/embedded/embedded.h @@ -0,0 +1,427 @@ +/* Generated by bin2c, do not edit manually */ + +/* Contents of file tool.html.gz */ +#define tool_html_gz_size 6728 +const unsigned char tool_html_gz[6728] = { + 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xED, 0x3C, 0x89, 0x72, 0xDB, 0xC6, + 0x92, 0xBF, 0x82, 0x20, 0x15, 0x93, 0x58, 0x02, 0x24, 0x2E, 0xDE, 0xA2, 0xBC, 0x49, 0x2C, 0x27, + 0xDA, 0xB2, 0x63, 0x97, 0x24, 0xAF, 0xF7, 0x95, 0xE3, 0x52, 0x81, 0xC4, 0x50, 0xC4, 0x1A, 0x04, + 0x28, 0x60, 0x28, 0x4A, 0x96, 0xB9, 0xDF, 0xBE, 0xDD, 0x3D, 0x83, 0x8B, 0x97, 0x8E, 0xE7, 0xB7, + 0x2F, 0x5B, 0xF5, 0xA2, 0x90, 0x00, 0xE6, 0xE8, 0xE9, 0xE9, 0xBB, 0x1B, 0x43, 0x1F, 0xCD, 0xF8, + 0x3C, 0x3C, 0x3E, 0x9A, 0x31, 0xCF, 0x3F, 0x3E, 0x4A, 0xF9, 0x5D, 0xC8, 0x8E, 0xB1, 0xE5, 0x7E, + 0x1A, 0x47, 0xDC, 0x98, 0x7A, 0xF3, 0x20, 0xBC, 0x1B, 0xA4, 0x5E, 0x94, 0x1A, 0x29, 0x4B, 0x82, + 0xE9, 0xD0, 0x98, 0xA7, 0x06, 0x67, 0xB7, 0xDC, 0x48, 0x83, 0xAF, 0xCC, 0xF0, 0xFC, 0xFF, 0x5E, + 0xA6, 0x7C, 0x60, 0x99, 0xE6, 0x4F, 0x43, 0x63, 0xC5, 0xC6, 0x5F, 0x02, 0xBE, 0xA7, 0x97, 0xC0, + 0x61, 0x2B, 0x3C, 0x2E, 0x6E, 0xD7, 0xE3, 0xD8, 0xBF, 0xAB, 0x2C, 0xA1, 0xFE, 0xCE, 0xC2, 0x1B, + 0xC6, 0x83, 0x89, 0xA7, 0xFC, 0xC1, 0x96, 0x4C, 0xD5, 0xF3, 0x67, 0xFD, 0xE7, 0x24, 0xF0, 0x42, + 0xBD, 0x84, 0x43, 0x09, 0x96, 0xBB, 0xB8, 0x1D, 0x86, 0x41, 0xC4, 0x8C, 0x19, 0x0B, 0xAE, 0x66, + 0xB0, 0x56, 0xD3, 0xB5, 0x7B, 0xED, 0xAE, 0xE5, 0x3A, 0xC3, 0x49, 0x1C, 0xC6, 0xC9, 0xE0, 0x47, + 0xC7, 0x71, 0x86, 0x63, 0x6F, 0xF2, 0xE5, 0x2A, 0x89, 0x97, 0x91, 0x6F, 0xC8, 0xD6, 0xE9, 0x74, + 0xBA, 0xE6, 0xDE, 0x38, 0x64, 0xF7, 0xE3, 0x38, 0xF1, 0x59, 0x32, 0x30, 0x87, 0xE2, 0xC6, 0x48, + 0x17, 0xDE, 0x24, 0x88, 0xAE, 0xA0, 0x61, 0xEE, 0xDD, 0x1A, 0xAB, 0xC0, 0xE7, 0x33, 0xDA, 0xC1, + 0x9A, 0xFB, 0xF7, 0xAB, 0x59, 0xC0, 0x19, 0x8D, 0x60, 0x83, 0x28, 0x5E, 0x25, 0xDE, 0x62, 0xB8, + 0xF0, 0x7C, 0x1F, 0x87, 0xDB, 0xF3, 0xF9, 0x9A, 0xCF, 0xEE, 0x69, 0xF3, 0x5E, 0x18, 0x5C, 0x45, + 0x83, 0x90, 0x4D, 0xF9, 0xBA, 0x49, 0x8B, 0x1C, 0x73, 0xDC, 0xEF, 0x31, 0x4F, 0x8E, 0xB9, 0xAF, + 0x6F, 0x35, 0xCD, 0xF2, 0x26, 0x62, 0x42, 0x75, 0x54, 0xDE, 0x34, 0xBB, 0xCF, 0x96, 0xEA, 0xED, + 0xDF, 0xF3, 0x0D, 0x4B, 0x90, 0x64, 0xA1, 0x44, 0x81, 0xC7, 0x8B, 0x6C, 0x5B, 0x70, 0x3B, 0xB0, + 0x16, 0xB7, 0x4A, 0x1A, 0x87, 0x81, 0xAF, 0xFC, 0xE8, 0xFB, 0xBE, 0xC4, 0xCD, 0x48, 0x79, 0x12, + 0x2C, 0x98, 0x9F, 0x23, 0x34, 0x88, 0xF8, 0xCC, 0x88, 0xA7, 0x06, 0xBF, 0x5B, 0xB0, 0x7A, 0xEC, + 0xFB, 0xDA, 0xFD, 0x0E, 0xF2, 0xF5, 0xF1, 0x6F, 0xED, 0xDD, 0x2F, 0xE2, 0x34, 0xE0, 0x41, 0x1C, + 0x0D, 0x12, 0x16, 0x7A, 0x3C, 0xB8, 0x61, 0x43, 0x3F, 0x48, 0x17, 0xA1, 0x77, 0x37, 0x18, 0x87, + 0xF1, 0xE4, 0x4B, 0x4E, 0x1E, 0x64, 0xBA, 0x62, 0xB5, 0x01, 0x73, 0xA2, 0x90, 0xCF, 0x26, 0x71, + 0xE2, 0xD1, 0xC4, 0x28, 0x8E, 0x58, 0xC6, 0xAB, 0xC9, 0x64, 0xB2, 0x6E, 0x7A, 0x13, 0x84, 0x73, + 0x5F, 0x30, 0x6A, 0x07, 0xFB, 0x4C, 0xD3, 0xCC, 0x06, 0x2A, 0x9E, 0xEE, 0x0D, 0xA6, 0xF1, 0x64, + 0x99, 0xC2, 0x75, 0x16, 0x03, 0x05, 0x4A, 0x53, 0xD7, 0xCD, 0x85, 0x17, 0xB1, 0xF0, 0x7E, 0xEE, + 0x25, 0x57, 0x41, 0x64, 0x8C, 0x63, 0xCE, 0xE3, 0xF9, 0xC0, 0x06, 0x64, 0x76, 0xCB, 0x84, 0xA4, + 0xD6, 0x06, 0xA5, 0x32, 0x1A, 0x26, 0x9E, 0x1F, 0x2C, 0xD3, 0x01, 0xCA, 0x5C, 0x26, 0xEC, 0xE3, + 0xF8, 0xD6, 0x48, 0x67, 0x9E, 0x1F, 0xAF, 0x06, 0xA6, 0x82, 0xB3, 0xF0, 0x93, 0x5C, 0x8D, 0xBD, + 0xBA, 0xA9, 0xE3, 0x5F, 0xD3, 0x6C, 0x6B, 0xC3, 0xC7, 0x0C, 0x92, 0x98, 0x1A, 0xA4, 0x18, 0x39, + 0xD5, 0x80, 0x60, 0x59, 0x07, 0x0A, 0x02, 0xB4, 0xDD, 0x6F, 0x53, 0xF4, 0xB0, 0xA0, 0xB7, 0xF1, + 0x2F, 0xDB, 0x81, 0x6C, 0x2C, 0xED, 0x09, 0xE4, 0xC2, 0x48, 0x50, 0x8C, 0xB2, 0xDD, 0x39, 0x48, + 0x9B, 0xA2, 0x0F, 0xA5, 0x78, 0x47, 0x97, 0xA4, 0xE4, 0xA6, 0x44, 0x4D, 0xE3, 0x64, 0x0E, 0x8B, + 0x44, 0x3C, 0x89, 0xC3, 0xFB, 0xAA, 0x24, 0x08, 0x4D, 0xF2, 0x96, 0x3C, 0x1E, 0x4A, 0xB9, 0x75, + 0x90, 0x90, 0xD9, 0x76, 0x3A, 0xB8, 0x1B, 0x1B, 0x1A, 0x9E, 0xA4, 0xDC, 0xED, 0x76, 0x7B, 0x1F, + 0x23, 0x8B, 0xD6, 0x60, 0xEE, 0x5D, 0x31, 0x21, 0x67, 0xDB, 0xEC, 0x05, 0x91, 0x7B, 0x1C, 0x7B, + 0x83, 0x28, 0x65, 0x5C, 0xD9, 0xC3, 0xBF, 0x6E, 0x95, 0xCB, 0x0F, 0x8E, 0x35, 0x62, 0x83, 0x27, + 0x60, 0xD0, 0x84, 0xEE, 0x94, 0x99, 0xA3, 0x30, 0x2F, 0x65, 0x06, 0xC8, 0x6A, 0xBC, 0xE4, 0x4A, + 0xD3, 0x6A, 0xA7, 0x7A, 0x01, 0x77, 0xAB, 0xAF, 0x4A, 0x70, 0xA1, 0x05, 0xF7, 0x55, 0x56, 0x77, + 0x3A, 0xDE, 0x94, 0xF5, 0x87, 0x30, 0x03, 0x29, 0x09, 0x56, 0xED, 0x19, 0x5B, 0xD3, 0x4D, 0xE8, + 0xEC, 0x65, 0x1D, 0x96, 0x69, 0xEB, 0x56, 0xB7, 0xAD, 0xDB, 0x8E, 0xA3, 0x37, 0x3B, 0x9A, 0xC4, + 0x01, 0x69, 0xBD, 0xD8, 0xD0, 0x33, 0x21, 0xBE, 0x63, 0x1E, 0xE5, 0xA2, 0x10, 0x44, 0xC4, 0x4F, + 0x21, 0x11, 0xD5, 0xC1, 0xA6, 0xE0, 0xFC, 0x4A, 0xB0, 0xDA, 0x35, 0xCD, 0x61, 0xC9, 0x96, 0x4E, + 0x58, 0xC4, 0x59, 0xB2, 0x69, 0xDE, 0xE6, 0x81, 0xEF, 0x87, 0x4C, 0xB8, 0xA4, 0x78, 0x39, 0x99, + 0x19, 0x68, 0x11, 0x80, 0x9E, 0x73, 0x2F, 0x0A, 0x16, 0xCB, 0x90, 0xEC, 0xCB, 0x70, 0x7F, 0xCF, + 0x64, 0x99, 0xA4, 0x40, 0xA2, 0x45, 0x1C, 0x10, 0xF0, 0x47, 0x4A, 0x0C, 0xF1, 0x6D, 0xE1, 0x25, + 0x80, 0xD1, 0xF0, 0x80, 0x3F, 0x78, 0xA2, 0x3C, 0xEF, 0x10, 0xC1, 0x79, 0xFC, 0xD5, 0x58, 0xA6, + 0xE8, 0x91, 0x58, 0xC8, 0x26, 0x5C, 0xA0, 0x83, 0x7B, 0xDD, 0x6A, 0xDC, 0x6C, 0x20, 0x9A, 0x1B, + 0x8B, 0x04, 0xB6, 0x91, 0xDC, 0x1D, 0x36, 0xA4, 0x8E, 0xD3, 0xF5, 0xC6, 0xDD, 0x0D, 0xF3, 0x60, + 0xB3, 0x8E, 0xEF, 0xB9, 0x15, 0x28, 0xD2, 0xD8, 0xEA, 0x95, 0x36, 0x61, 0x75, 0x2B, 0x4D, 0x64, + 0x80, 0x2B, 0x4D, 0x83, 0x1D, 0x33, 0x07, 0xDB, 0x33, 0xB7, 0x4C, 0xF7, 0x0E, 0x64, 0xED, 0x5E, + 0xC7, 0xEC, 0x9B, 0x1B, 0xC8, 0x5A, 0xB6, 0x3D, 0x76, 0x4D, 0x42, 0x36, 0x98, 0x5F, 0xDD, 0x4B, + 0xA6, 0xCE, 0xBC, 0x68, 0xD3, 0x6C, 0x77, 0x72, 0xEB, 0x55, 0xD6, 0x7F, 0x72, 0x12, 0x62, 0xAE, + 0x44, 0x61, 0x87, 0x3D, 0x31, 0xF1, 0x6F, 0x63, 0xDD, 0xCE, 0x04, 0xFF, 0x9E, 0xAD, 0x4E, 0x28, + 0x1F, 0x57, 0x09, 0xBB, 0x7B, 0x8A, 0xD9, 0xA8, 0x4C, 0x24, 0xAC, 0x09, 0xCD, 0xC3, 0xDB, 0x76, + 0x4C, 0xA9, 0x84, 0xD9, 0xD8, 0x87, 0xB6, 0xF9, 0xCF, 0xDC, 0x51, 0x08, 0x48, 0x81, 0x86, 0x7C, + 0xD1, 0x8B, 0xDB, 0x41, 0x35, 0x1E, 0x20, 0xCF, 0x5F, 0x74, 0x56, 0xA4, 0x06, 0xFB, 0x82, 0x68, + 0xB1, 0xE4, 0x9F, 0x30, 0x76, 0x19, 0x4D, 0x83, 0x90, 0x7D, 0x1E, 0x0C, 0xB2, 0xFD, 0xE0, 0xA3, + 0xB1, 0x5C, 0x84, 0xB1, 0xE7, 0x1B, 0xE3, 0x25, 0xD8, 0x9C, 0x7F, 0x99, 0xA5, 0xFF, 0x5B, 0xB3, + 0x34, 0x3C, 0xA8, 0xDC, 0xED, 0xF1, 0xC4, 0xF4, 0xD9, 0x86, 0x92, 0xB9, 0x9D, 0x71, 0xCF, 0xF7, + 0x9E, 0xC4, 0x54, 0xE9, 0x05, 0xFF, 0xC5, 0xDA, 0xBF, 0x0E, 0x6B, 0x1D, 0x6B, 0x6C, 0xFA, 0x9B, + 0x31, 0xA8, 0x35, 0xEE, 0xF8, 0xBD, 0xF6, 0xD3, 0x58, 0x2B, 0xB4, 0xFD, 0x5F, 0xAC, 0xFD, 0x8B, + 0xB3, 0xD6, 0xEE, 0xF4, 0xBD, 0xF1, 0x24, 0x4B, 0x5C, 0xA6, 0x71, 0x0C, 0x14, 0x39, 0x90, 0xB7, + 0x58, 0x5D, 0xB3, 0xB7, 0x0B, 0xF6, 0x23, 0x52, 0x97, 0xAD, 0x04, 0xE4, 0x9F, 0xB0, 0xE4, 0x3C, + 0xF6, 0xBD, 0x22, 0xD9, 0x21, 0x92, 0xE5, 0x59, 0xF1, 0x34, 0xB8, 0x65, 0xFE, 0xF0, 0x2B, 0xC4, + 0xEC, 0x3E, 0xBB, 0xC5, 0x32, 0x02, 0x48, 0xA2, 0xC4, 0x4A, 0xC0, 0x32, 0x31, 0x15, 0xC5, 0x1C, + 0x0B, 0x44, 0x16, 0x1B, 0xCC, 0x61, 0x51, 0x71, 0xC8, 0xF2, 0x24, 0xBA, 0x47, 0xC9, 0x9F, 0x86, + 0xE0, 0x52, 0x29, 0x83, 0xDA, 0x99, 0x11, 0x6F, 0xB7, 0x96, 0xDD, 0xAD, 0xAB, 0x49, 0x54, 0x29, + 0x5D, 0x00, 0x81, 0xBB, 0xDF, 0x93, 0xE5, 0x59, 0x66, 0x35, 0x03, 0xAC, 0x64, 0x87, 0xE5, 0x4E, + 0xA1, 0x6B, 0x7B, 0xE7, 0xCA, 0xEE, 0x7D, 0xD3, 0x07, 0x76, 0x41, 0xC7, 0x3C, 0x0A, 0x2D, 0xE5, + 0xC9, 0x98, 0x6F, 0x58, 0x28, 0xF8, 0x66, 0x25, 0x6A, 0xB0, 0xB5, 0xE1, 0x76, 0xCD, 0x41, 0x28, + 0xBF, 0x20, 0x4D, 0xC6, 0xF4, 0x1D, 0xE4, 0xF8, 0x71, 0xCA, 0xF0, 0x2F, 0xA3, 0x03, 0x66, 0xD4, + 0x25, 0x29, 0xB1, 0xE5, 0x82, 0x99, 0x90, 0x50, 0x34, 0xB4, 0x53, 0x48, 0x6C, 0xFC, 0xDB, 0x97, + 0x24, 0x3F, 0x91, 0x7C, 0x95, 0x5C, 0x74, 0x8A, 0x7F, 0x19, 0x7A, 0xD5, 0x4A, 0x80, 0x29, 0xB1, + 0xCB, 0x7A, 0x37, 0x45, 0xBC, 0x93, 0x61, 0x2F, 0x85, 0xC6, 0x6D, 0xB6, 0xD9, 0xFC, 0xE9, 0x5B, + 0xD9, 0x46, 0xE7, 0xEF, 0xE4, 0xF6, 0xBA, 0x39, 0x0B, 0x7C, 0x76, 0x19, 0xF0, 0x8A, 0x86, 0xAC, + 0xFF, 0x7D, 0xCE, 0xFC, 0xC0, 0x53, 0xEA, 0x73, 0xB0, 0xD9, 0x42, 0xE2, 0xBB, 0x1D, 0xE0, 0xB8, + 0x76, 0xBF, 0x21, 0xA3, 0xA2, 0xAF, 0xDD, 0x43, 0x48, 0xD9, 0xA4, 0x74, 0x92, 0x30, 0x16, 0x29, + 0x10, 0xEA, 0xC2, 0xFC, 0xBC, 0x46, 0xD7, 0xED, 0x74, 0xF7, 0xCE, 0xA7, 0xFA, 0xDD, 0xFA, 0xA8, + 0x25, 0xCA, 0x9B, 0x47, 0x3C, 0xE0, 0x70, 0x39, 0x39, 0x7F, 0xEF, 0xBC, 0x52, 0x78, 0x1C, 0x87, + 0xCA, 0x02, 0x2C, 0xF4, 0x51, 0x4B, 0x34, 0x1F, 0xB5, 0x44, 0x29, 0x94, 0xAA, 0x61, 0x47, 0x7E, + 0x70, 0xA3, 0x4C, 0x42, 0x2F, 0x4D, 0x47, 0x2A, 0x99, 0x16, 0x15, 0x66, 0x63, 0xD5, 0x4C, 0x21, + 0xC0, 0x23, 0x15, 0x21, 0x63, 0x5B, 0x02, 0x1F, 0x98, 0xE4, 0x65, 0x83, 0x45, 0x46, 0xA1, 0x2A, + 0xB3, 0x84, 0x4D, 0x47, 0xEA, 0x8C, 0xF3, 0x45, 0x3A, 0x68, 0xB5, 0xAE, 0x02, 0x3E, 0x5B, 0x8E, + 0x9B, 0x93, 0x78, 0xDE, 0x1A, 0xFB, 0x09, 0xF0, 0xAD, 0xF5, 0x5B, 0x32, 0x0E, 0x2F, 0x4F, 0xD2, + 0x85, 0x63, 0xAB, 0x0A, 0x07, 0x29, 0x66, 0x7C, 0xA4, 0x5E, 0x42, 0x78, 0x1B, 0x7D, 0x01, 0xA8, + 0xE9, 0xCD, 0x55, 0xBE, 0x0E, 0x9B, 0x03, 0x30, 0x62, 0xAD, 0x7C, 0xB8, 0x09, 0xD8, 0xEA, 0x97, + 0xF8, 0x76, 0xA4, 0x62, 0x08, 0x6D, 0x39, 0x26, 0x7C, 0xD9, 0xA6, 0x09, 0xB3, 0xAE, 0x84, 0x57, + 0xC1, 0xAC, 0x7C, 0xA4, 0xD2, 0x2D, 0x68, 0x09, 0xAB, 0xB7, 0x4D, 0x1D, 0x07, 0x68, 0x40, 0x3E, + 0x2F, 0x64, 0x75, 0x4B, 0x57, 0x0C, 0x4B, 0x83, 0xE1, 0x0B, 0x8F, 0xCF, 0x14, 0x7F, 0xA4, 0xBE, + 0xED, 0x20, 0x08, 0xAB, 0xEB, 0x5E, 0x3B, 0x0E, 0x40, 0xEC, 0xBA, 0x8A, 0xD1, 0x0E, 0x9D, 0x1E, + 0x8C, 0x6A, 0xDB, 0x61, 0x1B, 0x2E, 0xD7, 0x6E, 0x1F, 0xBE, 0x5D, 0xA5, 0x0F, 0x3D, 0x4E, 0x1F, + 0x9B, 0xEC, 0xD0, 0x72, 0x5C, 0xA5, 0x67, 0x5E, 0x77, 0x2C, 0xC5, 0x70, 0x7B, 0x8A, 0x65, 0x42, + 0x97, 0x65, 0xB6, 0x43, 0xA3, 0x67, 0xC2, 0x8D, 0xE3, 0x86, 0x0E, 0x00, 0xB9, 0xB6, 0x61, 0xA8, + 0xEB, 0x2A, 0x0E, 0x4C, 0xEF, 0x3B, 0x21, 0x0C, 0xED, 0x84, 0x00, 0x13, 0x80, 0xF4, 0xAE, 0xB1, + 0xC7, 0x51, 0xE0, 0xBB, 0xEB, 0x5C, 0xC3, 0x14, 0x07, 0x17, 0x85, 0x07, 0x37, 0x34, 0xE4, 0x08, + 0xB8, 0x81, 0xF1, 0xD7, 0xF0, 0x08, 0x23, 0xFB, 0xB8, 0x30, 0x01, 0x31, 0x10, 0x70, 0x28, 0x57, + 0xB9, 0xC6, 0xB5, 0x0D, 0xC4, 0xA1, 0x40, 0x80, 0x10, 0xB3, 0x42, 0x84, 0xE6, 0x5C, 0xE3, 0xEA, + 0x06, 0x62, 0x21, 0x51, 0x37, 0x08, 0x77, 0x43, 0x6C, 0xCE, 0x52, 0xAE, 0x11, 0x07, 0xB1, 0x2E, + 0xA2, 0x6B, 0xD0, 0xFE, 0xF1, 0xA1, 0x4D, 0x63, 0x60, 0x08, 0xCE, 0xB0, 0xAF, 0x11, 0x01, 0xD8, + 0x3F, 0x42, 0x11, 0x40, 0x1C, 0xB1, 0x8E, 0xD1, 0xB3, 0xAE, 0x8D, 0x8E, 0xA9, 0x20, 0x16, 0x88, + 0x01, 0x22, 0xD0, 0x43, 0x9E, 0xB8, 0x88, 0x27, 0x00, 0x84, 0xA5, 0x5D, 0x44, 0xA4, 0xA7, 0x20, + 0xEA, 0xB6, 0xD2, 0x09, 0x69, 0x5D, 0xD8, 0xBF, 0xD1, 0x51, 0x5C, 0xD8, 0x67, 0x07, 0xC8, 0x0D, + 0xFB, 0x87, 0x85, 0xE1, 0x0E, 0x48, 0x44, 0x9D, 0x21, 0x0C, 0xBC, 0xB6, 0x1C, 0x04, 0x2B, 0x66, + 0x3A, 0x8A, 0xA0, 0x2C, 0x6E, 0xD9, 0xED, 0x2A, 0xB0, 0x61, 0x58, 0x89, 0x56, 0xB3, 0x60, 0x26, + 0xF4, 0x84, 0x88, 0x25, 0xAC, 0x04, 0xEB, 0x09, 0x1C, 0xA1, 0x37, 0xA4, 0x1D, 0x40, 0x33, 0x92, + 0x19, 0xF7, 0xF4, 0x95, 0x18, 0xDD, 0x03, 0x82, 0x5E, 0x1B, 0xBD, 0x3E, 0xEE, 0x94, 0x48, 0xDD, + 0x71, 0x38, 0x7C, 0x88, 0x20, 0xCD, 0x36, 0x2F, 0xEE, 0xB2, 0x4E, 0xBC, 0xC2, 0x05, 0x3A, 0x44, + 0xBB, 0x51, 0xDC, 0x89, 0xAE, 0xAF, 0x20, 0x4B, 0x2D, 0x14, 0x26, 0xB8, 0x5C, 0xC1, 0x07, 0x84, + 0xF7, 0x58, 0x39, 0x82, 0x70, 0x26, 0xCA, 0x75, 0x22, 0xCB, 0xDA, 0xD4, 0xE3, 0xD7, 0x41, 0x32, + 0x5F, 0x41, 0xD8, 0x03, 0xC3, 0x60, 0x00, 0x8C, 0xF6, 0xE0, 0x83, 0x0A, 0xF4, 0x08, 0x25, 0x5A, + 0xAD, 0x56, 0xCD, 0x92, 0x22, 0x85, 0xCB, 0x89, 0x21, 0x1E, 0x5B, 0xA4, 0xD1, 0xC6, 0xC7, 0x93, + 0x5F, 0x3E, 0x9C, 0xB6, 0x38, 0xD8, 0x88, 0x96, 0xDD, 0xB4, 0xFE, 0x1A, 0x6A, 0x65, 0xF6, 0xDD, + 0xEB, 0x9E, 0x8D, 0x10, 0x3B, 0x66, 0x13, 0xA5, 0xCF, 0x46, 0xD2, 0xBA, 0x40, 0xFC, 0x76, 0x9F, + 0x5B, 0x56, 0x07, 0xDB, 0x7A, 0xD8, 0xD6, 0x77, 0xF1, 0xB6, 0x0F, 0x1C, 0xE8, 0xD1, 0xC5, 0xB5, + 0xF3, 0x2E, 0x14, 0xBD, 0x76, 0x97, 0x08, 0x9E, 0xDF, 0xA1, 0xE0, 0x52, 0xA7, 0xD1, 0xE9, 0xC9, + 0x89, 0x46, 0x0E, 0xC2, 0x28, 0x03, 0x36, 0xB2, 0xD5, 0x80, 0x5D, 0xFD, 0x1C, 0x05, 0xF9, 0x60, + 0xE7, 0x23, 0x68, 0x00, 0x4D, 0x13, 0xB3, 0x08, 0x58, 0x3F, 0x83, 0xDF, 0x17, 0x4B, 0x66, 0x00, + 0x15, 0x42, 0x22, 0xBB, 0x12, 0xAA, 0xD4, 0x05, 0xB8, 0xF7, 0xDB, 0x0A, 0xCF, 0xE6, 0x96, 0xE0, + 0xC9, 0x25, 0x04, 0x15, 0x70, 0xD5, 0xAF, 0x6F, 0x7B, 0xBD, 0x1E, 0xF4, 0xF5, 0x49, 0xC5, 0x51, + 0xCB, 0x2D, 0x90, 0x57, 0x9B, 0x13, 0x82, 0x64, 0x39, 0xDA, 0x5D, 0x94, 0x67, 0x40, 0xAA, 0x8F, + 0x16, 0xC2, 0xB2, 0x51, 0xDF, 0x80, 0x36, 0x36, 0x0C, 0xC2, 0x2F, 0x7C, 0x12, 0x37, 0x78, 0x85, + 0x1E, 0xB8, 0xBD, 0xC6, 0x45, 0x14, 0x1B, 0x04, 0xD4, 0x02, 0xB2, 0x2B, 0x56, 0x5F, 0x71, 0x69, + 0x39, 0xC0, 0xB9, 0x8B, 0x5B, 0x87, 0x11, 0x46, 0x17, 0x80, 0x75, 0xD0, 0xA0, 0x75, 0x10, 0x6A, + 0x0F, 0x8C, 0x88, 0x85, 0x32, 0xDF, 0x51, 0x84, 0xA9, 0x31, 0x91, 0x17, 0x70, 0x05, 0x14, 0xAF, + 0x6D, 0xB4, 0x44, 0xA0, 0xA8, 0x5D, 0x30, 0x0A, 0x16, 0xC7, 0x89, 0x3D, 0x9B, 0xF7, 0x05, 0x63, + 0x2C, 0xD8, 0x1D, 0x1A, 0x8F, 0x1E, 0x6E, 0xCE, 0x71, 0x88, 0xB0, 0xB8, 0x98, 0x7C, 0xB0, 0x5D, + 0xEA, 0xA7, 0x6E, 0x9A, 0xD1, 0x43, 0x8D, 0xE9, 0x9A, 0xE2, 0x0A, 0x10, 0xBB, 0xB0, 0xD0, 0xB5, + 0x05, 0x9A, 0x0C, 0x24, 0x53, 0xDC, 0x8C, 0xAE, 0x2E, 0xF4, 0x5E, 0x1B, 0x7D, 0xB2, 0xC7, 0x88, + 0x14, 0xEC, 0xA4, 0xD7, 0xFF, 0xFA, 0xD6, 0x05, 0x53, 0xD0, 0xB5, 0xBB, 0x60, 0x55, 0xD0, 0x9A, + 0x48, 0xAB, 0x48, 0x1F, 0xE2, 0xA8, 0x83, 0xAB, 0x10, 0xF3, 0x69, 0xBE, 0x03, 0x53, 0x91, 0x13, + 0xB8, 0x2D, 0x0B, 0x2C, 0x09, 0x6E, 0xCD, 0x51, 0x1C, 0x92, 0x13, 0xCB, 0xE2, 0x0E, 0x32, 0xC5, + 0xEA, 0x86, 0x00, 0x0B, 0xEC, 0x09, 0x2C, 0x8A, 0xF4, 0x47, 0x14, 0x11, 0x71, 0xC0, 0xA2, 0x23, + 0x6F, 0xC9, 0x7A, 0xA2, 0x01, 0x05, 0x63, 0x01, 0xE8, 0xC0, 0xA2, 0x84, 0xAD, 0x61, 0x03, 0xA9, + 0x4D, 0x6E, 0x38, 0x36, 0xD2, 0xF3, 0x49, 0xCA, 0x7F, 0x8A, 0x19, 0xD2, 0x14, 0xD2, 0x9C, 0xE7, + 0x68, 0xFF, 0x21, 0x17, 0xDA, 0x5A, 0x05, 0x5F, 0x82, 0xBF, 0x86, 0xC2, 0x5B, 0xDD, 0xEE, 0x35, + 0x32, 0xCF, 0x04, 0xB1, 0x03, 0xDA, 0xB9, 0x6D, 0x94, 0x8F, 0x9E, 0x2B, 0xA4, 0x0F, 0xAC, 0xA9, + 0xED, 0x90, 0xD4, 0x21, 0xC3, 0xDA, 0x42, 0x1B, 0x5D, 0x6E, 0x94, 0x6E, 0x4B, 0x03, 0x8C, 0xD2, + 0x3C, 0xA3, 0x80, 0x46, 0xB7, 0xE2, 0x4E, 0x0C, 0xA0, 0x7E, 0x9C, 0x27, 0xA7, 0x11, 0x34, 0x04, + 0x96, 0xDF, 0x14, 0x9D, 0xC5, 0x8C, 0x0C, 0xCA, 0xD7, 0xB7, 0x6D, 0x50, 0x9E, 0xBE, 0x0B, 0xAE, + 0xCC, 0x26, 0xAF, 0x00, 0x1A, 0x64, 0xB4, 0xA5, 0xA1, 0x37, 0x6C, 0xD4, 0x07, 0x90, 0x72, 0x29, + 0x64, 0x24, 0x60, 0xC2, 0x67, 0x48, 0x13, 0x83, 0x02, 0x88, 0xFA, 0x09, 0xAA, 0x6A, 0xD3, 0x65, + 0x66, 0x39, 0xD6, 0xB5, 0x83, 0x70, 0x14, 0x90, 0x30, 0xCB, 0xBA, 0xEE, 0x60, 0x87, 0x4D, 0x7A, + 0xDF, 0x13, 0x28, 0xF5, 0xAE, 0x6D, 0xA4, 0xB9, 0x43, 0xB0, 0x2C, 0x5C, 0xC1, 0xA2, 0x5B, 0x1B, + 0x96, 0x20, 0x58, 0xB0, 0x70, 0x17, 0x83, 0x02, 0xD8, 0x2B, 0x0A, 0x2F, 0xB8, 0x4B, 0x8B, 0xFC, + 0x15, 0xA9, 0x19, 0x12, 0x89, 0xC4, 0x9E, 0x0C, 0x21, 0x2E, 0x4A, 0x20, 0x0C, 0xD4, 0x56, 0xAB, + 0x8B, 0x44, 0x11, 0xCA, 0x88, 0x36, 0x8E, 0xB4, 0x03, 0xFA, 0x00, 0x7F, 0x17, 0xD5, 0x06, 0x10, + 0x56, 0xA8, 0x11, 0xB1, 0xE7, 0x84, 0x94, 0x01, 0xC3, 0x67, 0x16, 0x84, 0x21, 0x82, 0x67, 0x4A, + 0x8F, 0x3B, 0x84, 0xA9, 0x83, 0x16, 0xC3, 0xED, 0x71, 0x1B, 0xD7, 0xEA, 0x22, 0x0D, 0xC1, 0x9A, + 0x9B, 0x80, 0x1E, 0x9A, 0x03, 0x60, 0x70, 0xCF, 0x51, 0x38, 0x99, 0x09, 0x30, 0x82, 0x40, 0x21, + 0x1C, 0xE5, 0x10, 0xF9, 0x3B, 0xA0, 0x51, 0x3D, 0x6C, 0x41, 0xE3, 0x03, 0x4E, 0xB7, 0x0B, 0x60, + 0x4C, 0x73, 0x06, 0xD8, 0x98, 0x80, 0x81, 0x49, 0x1B, 0xE9, 0xE6, 0xF8, 0x0B, 0xBB, 0x04, 0xDF, + 0x37, 0x34, 0x80, 0x76, 0xA3, 0xE4, 0x8D, 0x3C, 0x1F, 0x39, 0xC3, 0x5E, 0x9A, 0x4D, 0x4D, 0xD8, + 0xD7, 0x25, 0x3D, 0x86, 0x99, 0x62, 0xA2, 0x65, 0xD2, 0x40, 0x6A, 0x92, 0x06, 0x0F, 0x3E, 0x4F, + 0x52, 0xD0, 0xDF, 0x59, 0xB8, 0xD8, 0xA1, 0x9B, 0x0A, 0x85, 0xCF, 0x23, 0xB5, 0x88, 0xA9, 0xD5, + 0xA2, 0x2F, 0x8E, 0x26, 0x61, 0x30, 0xF9, 0x32, 0x52, 0xCF, 0xDE, 0xD4, 0xB5, 0xA1, 0xAA, 0x04, + 0xA0, 0x0A, 0x61, 0x0C, 0x59, 0x59, 0x00, 0xA1, 0xB8, 0xBA, 0xAD, 0xD6, 0x55, 0xAD, 0x6C, 0x3A, + 0x15, 0xBD, 0x6C, 0xDA, 0xDF, 0x5D, 0x33, 0xA7, 0x41, 0x18, 0xCA, 0x4D, 0xAA, 0xA4, 0xA6, 0x7D, + 0x8C, 0x82, 0x4C, 0xF3, 0xC6, 0x26, 0x6E, 0xF6, 0xA4, 0x11, 0x87, 0x00, 0x4F, 0xC4, 0x34, 0x24, + 0xE1, 0xD8, 0x32, 0x33, 0x60, 0x65, 0x08, 0x95, 0x6C, 0xE2, 0x98, 0x6B, 0x09, 0x27, 0xDA, 0xA6, + 0x58, 0xD8, 0xBA, 0x01, 0xC1, 0x43, 0x9E, 0xE2, 0x08, 0x97, 0x84, 0xB3, 0x2B, 0xFC, 0x7B, 0x9F, + 0x44, 0x51, 0xC8, 0xA7, 0x89, 0x5C, 0xED, 0xD0, 0x32, 0x38, 0xA8, 0x68, 0xE5, 0xC5, 0xE0, 0x19, + 0xA0, 0x73, 0x4D, 0x10, 0xA8, 0x8D, 0xE6, 0x0B, 0x91, 0xC3, 0xD9, 0x62, 0x32, 0xAE, 0x9D, 0xB7, + 0x71, 0x23, 0x1F, 0x48, 0xEB, 0x83, 0x77, 0x90, 0x1B, 0x12, 0x62, 0x60, 0x5B, 0x68, 0xD0, 0x1D, + 0xD4, 0x60, 0xD4, 0x3F, 0x90, 0xC1, 0x19, 0xA0, 0xAA, 0x48, 0x95, 0x43, 0xB9, 0x22, 0x3B, 0x40, + 0x8A, 0x21, 0xA5, 0x8E, 0x76, 0xFA, 0x55, 0x6D, 0x95, 0x44, 0x64, 0x17, 0xFB, 0x4B, 0x95, 0x34, + 0xCA, 0xF6, 0x90, 0x95, 0x28, 0x47, 0xC8, 0xF1, 0xD7, 0x1F, 0xFF, 0xF3, 0xE4, 0xEC, 0xFC, 0xF4, + 0xDD, 0x1F, 0xEA, 0x0E, 0xB1, 0xCA, 0x45, 0x0A, 0xE1, 0xB5, 0x30, 0x65, 0x6A, 0x89, 0x43, 0x0E, + 0x47, 0x2D, 0x48, 0xB3, 0x76, 0xE6, 0x5A, 0xA2, 0x54, 0x77, 0x7C, 0x34, 0xB3, 0x09, 0xFC, 0xDB, + 0xF3, 0xDF, 0x10, 0xCC, 0xCC, 0x86, 0xAF, 0xAC, 0x6B, 0xF7, 0x5C, 0x45, 0x66, 0x9D, 0x42, 0x10, + 0x5F, 0x9F, 0xBE, 0x39, 0x39, 0xFF, 0xDB, 0xF9, 0xC5, 0xC9, 0x5B, 0x75, 0x7B, 0x68, 0xF6, 0x66, + 0x1D, 0xA2, 0x52, 0x68, 0x9D, 0x29, 0xAF, 0x83, 0x90, 0xA5, 0x77, 0x29, 0x67, 0xF3, 0x3D, 0xB0, + 0x29, 0x33, 0x07, 0x40, 0x54, 0xBA, 0x54, 0xA8, 0x74, 0xA9, 0x62, 0xB1, 0x52, 0xAC, 0x45, 0x65, + 0x4B, 0x51, 0x3F, 0x53, 0x95, 0xC8, 0x9B, 0x43, 0xE7, 0xFC, 0x0E, 0x1B, 0xD3, 0x4F, 0x9F, 0x55, + 0x65, 0xBE, 0x0C, 0x79, 0xB0, 0x40, 0x32, 0x66, 0x77, 0x2A, 0xE8, 0xA1, 0x80, 0x54, 0x28, 0x88, + 0x52, 0x7A, 0x31, 0xA6, 0xCA, 0x15, 0x44, 0x09, 0x54, 0xAC, 0x51, 0xA9, 0x8A, 0xAA, 0x85, 0xEE, + 0x9D, 0xB3, 0xC8, 0xC7, 0xA5, 0x48, 0x03, 0x6F, 0xBC, 0x70, 0x09, 0xF3, 0x3E, 0xD0, 0x58, 0xF5, + 0xF8, 0x45, 0x34, 0x4E, 0x17, 0x43, 0xF1, 0x7D, 0xB4, 0x48, 0xE2, 0xAB, 0x84, 0xA5, 0x69, 0xC6, + 0xD3, 0x9B, 0x20, 0x0D, 0xC6, 0x41, 0x18, 0xF0, 0xBB, 0x01, 0x10, 0xCE, 0x67, 0x51, 0x86, 0xFA, + 0x22, 0xB9, 0x12, 0x4B, 0xD2, 0x0D, 0x64, 0xDB, 0x94, 0xF2, 0x92, 0x31, 0x91, 0x20, 0x20, 0x53, + 0x4E, 0xC4, 0x67, 0x07, 0xFF, 0xF6, 0x91, 0x4E, 0xF2, 0x5D, 0xA4, 0xCD, 0x99, 0x15, 0x20, 0x7B, + 0xF2, 0x14, 0x52, 0x54, 0xF6, 0xFD, 0x6B, 0x3C, 0x9F, 0x7B, 0x91, 0x5F, 0xAF, 0x85, 0x41, 0xCA, + 0x6B, 0x7A, 0xCD, 0x0B, 0xC3, 0x5A, 0x89, 0x0C, 0x67, 0x6C, 0x0A, 0xD8, 0xCE, 0x4A, 0x16, 0xAB, + 0xBC, 0x2A, 0xE2, 0x99, 0x43, 0xFB, 0x35, 0x61, 0x60, 0x4E, 0xFC, 0x20, 0xA9, 0x6B, 0xEA, 0x21, + 0xAB, 0xE5, 0x9A, 0x85, 0xC9, 0xC2, 0xFB, 0x8A, 0xBD, 0x72, 0xF1, 0x7F, 0x18, 0x9F, 0x80, 0x1C, + 0x28, 0xD0, 0xD6, 0x56, 0x95, 0x3B, 0xA4, 0x9D, 0x9A, 0xCD, 0x76, 0x4A, 0xB3, 0x6D, 0xB8, 0x4F, + 0x60, 0x90, 0x0D, 0x97, 0x3B, 0xBA, 0x08, 0x73, 0x25, 0xCB, 0xAB, 0xA8, 0x92, 0x19, 0x1C, 0x1C, + 0x7A, 0x47, 0xE0, 0x32, 0xDB, 0xD9, 0x2E, 0x19, 0xCE, 0xF6, 0x83, 0x70, 0x50, 0x7B, 0x11, 0x8E, + 0x25, 0x10, 0xB2, 0xE1, 0x92, 0x17, 0x95, 0xA1, 0xB5, 0x27, 0x1F, 0x57, 0x12, 0x22, 0x18, 0x94, + 0x0C, 0x08, 0xD5, 0xA7, 0xD5, 0xE3, 0x06, 0x10, 0x10, 0x60, 0xE4, 0x06, 0x82, 0x54, 0x64, 0x83, + 0xA6, 0xD2, 0x37, 0x20, 0x55, 0x49, 0x76, 0xC0, 0x00, 0xE7, 0x94, 0x0C, 0xA2, 0x69, 0x9C, 0x49, + 0x63, 0x79, 0x76, 0xC5, 0x20, 0x88, 0x1A, 0x8B, 0x9C, 0x21, 0x1E, 0x2A, 0x87, 0x95, 0xD4, 0x4C, + 0x70, 0x8B, 0x8A, 0x3A, 0x4A, 0x95, 0xA8, 0xDC, 0x90, 0x54, 0xCD, 0xCA, 0xFC, 0xBD, 0x00, 0xB1, + 0x01, 0xD8, 0x33, 0x6C, 0x3F, 0xFE, 0x03, 0x04, 0x3B, 0x7F, 0x38, 0x87, 0x6D, 0x67, 0x0F, 0xC2, + 0x54, 0x9C, 0x5F, 0xF2, 0x60, 0x0E, 0xFB, 0xBC, 0x08, 0x8A, 0x61, 0x15, 0x59, 0xD9, 0x68, 0xCB, + 0xFD, 0xE0, 0x2C, 0xDF, 0x83, 0x44, 0x03, 0x65, 0x3D, 0x37, 0x09, 0x97, 0x28, 0x96, 0x34, 0x4E, + 0x14, 0x96, 0x0E, 0x9B, 0x3E, 0x59, 0xDE, 0x13, 0xAA, 0x97, 0x72, 0x8F, 0x2F, 0x53, 0x35, 0xA7, + 0xF5, 0xD6, 0xF7, 0x03, 0xC6, 0xEF, 0xE3, 0x87, 0xF7, 0xAF, 0x7E, 0xBE, 0x38, 0x39, 0x6C, 0xFA, + 0x64, 0x42, 0xAE, 0x7C, 0x58, 0xF8, 0x20, 0xFC, 0x0F, 0x58, 0xBE, 0x8A, 0xFA, 0xEE, 0x35, 0x84, + 0xAB, 0xBD, 0x66, 0xB0, 0x14, 0xEE, 0x3F, 0xD9, 0xF4, 0xC1, 0x43, 0x49, 0xF3, 0x85, 0x75, 0xDB, + 0xB6, 0x79, 0xB8, 0x89, 0xF2, 0x32, 0x4F, 0x31, 0x78, 0xD3, 0x55, 0x6E, 0xF2, 0xF0, 0x76, 0xB7, + 0xD1, 0xCB, 0x21, 0xE7, 0xBE, 0x6F, 0x9E, 0x5E, 0xA9, 0xFB, 0xC1, 0x1F, 0x9F, 0x31, 0xE0, 0x63, + 0xC2, 0x81, 0xDA, 0xBA, 0x02, 0x66, 0xDF, 0x4B, 0x99, 0xB2, 0xF2, 0x02, 0xDE, 0x84, 0xFF, 0x32, + 0xC7, 0x98, 0x83, 0x9A, 0xC4, 0x4B, 0x74, 0x6E, 0x0F, 0xBB, 0xCC, 0x82, 0x4D, 0x79, 0xBC, 0x85, + 0xB5, 0xCC, 0x5C, 0xD9, 0xA8, 0x18, 0x5A, 0x65, 0x7C, 0xA5, 0x3E, 0xBA, 0xAB, 0x4B, 0x94, 0xC5, + 0xA1, 0x67, 0xE6, 0x1C, 0x9F, 0x02, 0xEA, 0x3C, 0x98, 0x06, 0x13, 0x7A, 0xC9, 0x05, 0x9E, 0xD7, + 0xD9, 0x21, 0x73, 0x45, 0xB9, 0x5A, 0x86, 0x02, 0xC7, 0x95, 0xC0, 0x52, 0x74, 0xA3, 0xCD, 0x50, + 0x95, 0x3C, 0x58, 0x3B, 0xFE, 0x90, 0x82, 0xDA, 0xCA, 0xED, 0x6D, 0x38, 0xC0, 0xF2, 0x99, 0xA6, + 0x4C, 0x04, 0xC4, 0x74, 0xDA, 0x24, 0xF2, 0xBF, 0x12, 0x89, 0x62, 0xD9, 0xBF, 0x20, 0xD5, 0x2C, + 0x79, 0x3C, 0x12, 0xEF, 0xA1, 0x6F, 0x05, 0x16, 0xE4, 0x09, 0x88, 0x2C, 0xE4, 0x14, 0x89, 0xCC, + 0xE2, 0x30, 0x32, 0xE3, 0x5D, 0x01, 0x4A, 0xB9, 0x80, 0xBF, 0x11, 0x46, 0x64, 0x92, 0xBE, 0x4F, + 0x1F, 0x0A, 0x8F, 0xF7, 0xE6, 0x0C, 0xBD, 0x93, 0x94, 0xF7, 0xF3, 0xE5, 0x78, 0x1E, 0xF0, 0x9D, + 0x16, 0x22, 0x9D, 0x80, 0xC1, 0xE4, 0xC7, 0x37, 0x5E, 0xA2, 0xAC, 0xD2, 0xCB, 0x34, 0x5E, 0x26, + 0x13, 0xA6, 0xDF, 0xCE, 0x43, 0xCC, 0xA7, 0x45, 0x18, 0xA1, 0x4F, 0x96, 0x09, 0xBE, 0x80, 0x44, + 0x2B, 0x3D, 0x52, 0x5B, 0xAA, 0x0E, 0x5B, 0x98, 0x21, 0xDB, 0x05, 0xD3, 0x47, 0x3F, 0x58, 0xFA, + 0x8A, 0x8D, 0xD3, 0x78, 0xF2, 0x85, 0xF1, 0xCB, 0x45, 0x9C, 0xF0, 0x91, 0x59, 0x6A, 0x38, 0x7D, + 0x3F, 0x52, 0x61, 0x4A, 0x7A, 0x17, 0x4D, 0x2E, 0xA1, 0x15, 0xF2, 0xF2, 0xF9, 0x32, 0x2A, 0x4D, + 0x45, 0x71, 0xBC, 0x44, 0x52, 0xA9, 0x3A, 0x88, 0xE7, 0x65, 0x3C, 0x9D, 0x56, 0x01, 0x92, 0x52, + 0x30, 0x1F, 0x1B, 0x59, 0xBA, 0xB8, 0x64, 0x49, 0x12, 0x27, 0x97, 0x73, 0x50, 0x31, 0x98, 0x87, + 0x93, 0x8A, 0xC6, 0x49, 0xEC, 0x33, 0x58, 0x1A, 0x09, 0x25, 0x10, 0x1F, 0x99, 0xC3, 0xE9, 0x32, + 0xA2, 0x77, 0xB1, 0xA0, 0xBC, 0x37, 0x63, 0x0F, 0x1C, 0xF6, 0x3D, 0x6E, 0x14, 0x26, 0x96, 0x4C, + 0x94, 0xAA, 0xF3, 0x51, 0x69, 0x8B, 0xCD, 0x74, 0x01, 0x7A, 0x59, 0x87, 0x8D, 0x6A, 0x7A, 0x44, + 0xFB, 0x0D, 0x46, 0xD6, 0x10, 0x58, 0x5D, 0x67, 0x0D, 0x9C, 0xE7, 0x4B, 0xE2, 0xD7, 0x84, 0xCB, + 0xAF, 0x29, 0x39, 0xD1, 0xFF, 0x54, 0xCB, 0x94, 0xAA, 0xB5, 0x6A, 0x43, 0x65, 0x7F, 0xE0, 0xF1, + 0xA7, 0x7A, 0xDC, 0x22, 0xB5, 0x55, 0x87, 0xC1, 0x11, 0x6F, 0x86, 0x2C, 0xBA, 0xE2, 0x10, 0xA8, + 0x0F, 0xB5, 0x3D, 0xAB, 0xEC, 0x59, 0x44, 0x6D, 0xD4, 0xA3, 0xC6, 0x88, 0x7F, 0x0A, 0x3E, 0x37, + 0x10, 0xE3, 0x86, 0xFA, 0xD0, 0xA2, 0x6A, 0x43, 0x0C, 0xCE, 0x0D, 0x94, 0xC4, 0x42, 0x0F, 0x1A, + 0x8D, 0x61, 0xC2, 0xF8, 0x32, 0x89, 0x14, 0x42, 0xA1, 0x6C, 0x4D, 0xD4, 0x75, 0x4E, 0x48, 0x50, + 0x8E, 0x74, 0x76, 0x89, 0x49, 0x1B, 0x10, 0x53, 0x8C, 0x57, 0xB3, 0xE0, 0xA3, 0xD6, 0xB6, 0x6B, + 0x10, 0x34, 0xD4, 0x2C, 0xB8, 0x40, 0x98, 0x51, 0xEB, 0xD4, 0x30, 0xCC, 0xC0, 0x8B, 0xF0, 0x85, + 0x35, 0xBB, 0x5D, 0xCB, 0x62, 0x91, 0x5A, 0xB7, 0x26, 0x15, 0xA3, 0x86, 0xE1, 0xC3, 0x20, 0x61, + 0xFE, 0xB0, 0xA6, 0xB4, 0x00, 0x91, 0x6D, 0x70, 0xBB, 0x01, 0xD8, 0x55, 0x00, 0x14, 0x7E, 0x6C, + 0x81, 0x70, 0x4C, 0x01, 0xA2, 0xB7, 0x07, 0xA3, 0x4E, 0xB7, 0x00, 0x08, 0x36, 0xFC, 0x61, 0x9C, + 0xEC, 0x2A, 0x40, 0xCB, 0x14, 0x10, 0xF1, 0x2A, 0x41, 0xF6, 0xCA, 0x20, 0xDD, 0x47, 0x43, 0xB4, + 0xFB, 0x3B, 0x21, 0x38, 0x8F, 0xD9, 0xA5, 0x2B, 0x40, 0xB8, 0x8E, 0x40, 0xAA, 0x2B, 0x70, 0xEA, + 0xE6, 0x00, 0x4B, 0xF0, 0x3A, 0x8F, 0x02, 0xD8, 0xF9, 0xDE, 0x00, 0x7B, 0xDF, 0x03, 0xA0, 0x08, + 0x29, 0x11, 0x6C, 0x11, 0x65, 0xD7, 0x6C, 0xB7, 0x24, 0x12, 0x70, 0x9F, 0x45, 0xD9, 0x35, 0xAA, + 0x0A, 0xD8, 0x58, 0x2D, 0xEE, 0xD5, 0x8E, 0xBF, 0xA7, 0x88, 0xFE, 0xBD, 0xF2, 0xF9, 0x7D, 0x85, + 0xF3, 0x3B, 0x4B, 0xE6, 0xDF, 0x2B, 0x96, 0xDF, 0x57, 0x26, 0xBF, 0xAF, 0x40, 0xFE, 0x43, 0xA4, + 0xB1, 0x30, 0x8D, 0xF8, 0xFA, 0x7C, 0xD3, 0x32, 0x3E, 0x56, 0x50, 0x6D, 0x17, 0xFE, 0xAF, 0xE5, + 0x55, 0xE2, 0xDA, 0xDB, 0xAE, 0xEE, 0x28, 0x6F, 0x6C, 0xBD, 0xA7, 0xBC, 0xE9, 0xEA, 0x96, 0x43, + 0xDF, 0xA6, 0xF2, 0xC6, 0x92, 0x97, 0x9E, 0x6E, 0x59, 0xE2, 0xD2, 0x16, 0x8D, 0x1D, 0xB8, 0x98, + 0x74, 0xE9, 0xEB, 0x56, 0x97, 0xBE, 0xFB, 0xD4, 0x64, 0xC3, 0x70, 0x5B, 0x5E, 0x6C, 0xDD, 0xEA, + 0xD1, 0xA5, 0x47, 0x6D, 0x1D, 0x84, 0xDA, 0x51, 0xBE, 0xE2, 0x06, 0x93, 0xF8, 0x0B, 0xEC, 0x90, + 0x8A, 0x31, 0x35, 0x91, 0xCF, 0xD5, 0x68, 0xA7, 0x3B, 0x37, 0x2A, 0xC2, 0xF6, 0x4B, 0xCC, 0x80, + 0x99, 0x76, 0x5F, 0xF2, 0x47, 0x8D, 0x11, 0x43, 0x37, 0xA4, 0x97, 0x3D, 0x90, 0x4A, 0x89, 0x8D, + 0xAE, 0x82, 0x07, 0x52, 0xB5, 0x02, 0x06, 0x04, 0x02, 0x78, 0xAE, 0xE9, 0x9C, 0x63, 0x8D, 0x3E, + 0xAD, 0x33, 0x9D, 0x67, 0x44, 0xAB, 0xB3, 0x11, 0x6B, 0xF2, 0xF8, 0x4D, 0xBC, 0x62, 0xC9, 0xAF, + 0x10, 0x06, 0xD7, 0x35, 0xED, 0xA8, 0xCE, 0x47, 0x7C, 0xA3, 0xED, 0xA5, 0x61, 0x0D, 0xF8, 0x11, + 0x7B, 0x69, 0x0D, 0xCC, 0x02, 0x2A, 0x9E, 0x21, 0xF0, 0xF8, 0x64, 0x46, 0x89, 0x04, 0xA5, 0x45, + 0x88, 0x21, 0xFA, 0x7C, 0x8E, 0xC1, 0x02, 0x46, 0x1C, 0xC3, 0x60, 0x0A, 0xD0, 0xD4, 0x72, 0xB9, + 0xE3, 0x9C, 0x46, 0x0E, 0x14, 0xB5, 0xC1, 0x9A, 0x62, 0x96, 0xCE, 0x1B, 0xD5, 0x21, 0xDF, 0xCA, + 0x0F, 0x17, 0x31, 0xF7, 0x42, 0x45, 0x9C, 0xC4, 0xA2, 0x49, 0x1C, 0x1B, 0x0E, 0xCF, 0x81, 0x78, + 0xD6, 0x2F, 0x4F, 0x59, 0xC2, 0xF3, 0xE1, 0x19, 0xEF, 0x26, 0x93, 0xE5, 0x42, 0xFC, 0x06, 0x46, + 0x51, 0x69, 0xE8, 0xD1, 0x9C, 0x41, 0x48, 0xA8, 0xCC, 0x83, 0x08, 0x84, 0xA6, 0x46, 0x89, 0x87, + 0xB0, 0x0B, 0x33, 0x90, 0xAA, 0x51, 0xAD, 0x0F, 0x77, 0x22, 0xE0, 0xAB, 0xE1, 0x0A, 0x71, 0x3E, + 0x1F, 0xA2, 0x02, 0x60, 0x22, 0x4D, 0x96, 0x89, 0xF5, 0x66, 0xFF, 0x4F, 0xAA, 0xEE, 0xC7, 0x93, + 0xE5, 0x1C, 0xF8, 0xD8, 0xBC, 0x62, 0xFC, 0x24, 0x64, 0x78, 0xFB, 0xCB, 0xDD, 0x29, 0xF0, 0x4F, + 0xA6, 0x97, 0x5A, 0x33, 0x88, 0x22, 0x96, 0xFC, 0x7E, 0xF1, 0xF6, 0xCD, 0x88, 0xEB, 0x44, 0x4E, + 0x60, 0xF5, 0x0F, 0xE5, 0xD0, 0x49, 0x50, 0x3A, 0xA8, 0x44, 0x53, 0x10, 0xC5, 0xF0, 0x53, 0x3C, + 0xDD, 0xF4, 0x6E, 0x8A, 0x31, 0x95, 0x5E, 0xE9, 0x13, 0x41, 0x8F, 0xAD, 0x0D, 0x69, 0x77, 0x3C, + 0xC9, 0x34, 0xAD, 0x7C, 0x5A, 0xF9, 0x40, 0xE8, 0x53, 0x89, 0xDA, 0x60, 0x08, 0xAB, 0x9B, 0x10, + 0xC6, 0x58, 0x8F, 0x88, 0x82, 0x30, 0xC0, 0x82, 0x50, 0xA8, 0xA4, 0xB1, 0x45, 0x44, 0x04, 0xA2, + 0x19, 0x62, 0x74, 0x0E, 0xF6, 0xA1, 0x76, 0x0C, 0xF9, 0x2E, 0xE6, 0x5F, 0x59, 0x7A, 0xA5, 0xAE, + 0x59, 0x93, 0x64, 0xAB, 0x09, 0xF8, 0xF1, 0x7A, 0x26, 0x77, 0x65, 0xF1, 0xDD, 0x92, 0xEC, 0x26, + 0x26, 0x8E, 0x3A, 0xA7, 0x8B, 0xB6, 0xD6, 0x90, 0x6A, 0xA3, 0x32, 0x89, 0x5E, 0xBC, 0xA8, 0x83, + 0x5C, 0x9A, 0x1A, 0xC5, 0x98, 0x48, 0xC0, 0x10, 0x83, 0xDD, 0x18, 0xA2, 0xD7, 0xF8, 0x28, 0x5B, + 0x4D, 0x50, 0x6A, 0x18, 0x37, 0x1A, 0x9A, 0x6A, 0x58, 0x40, 0x75, 0x01, 0xBD, 0x2E, 0xFB, 0x3F, + 0xC5, 0x9F, 0x9B, 0x58, 0xA0, 0xD1, 0x00, 0x16, 0x91, 0xF2, 0xE2, 0xEC, 0x58, 0x8A, 0x0C, 0xA5, + 0xA0, 0x60, 0x84, 0xCA, 0x96, 0xA7, 0x64, 0x90, 0x76, 0x18, 0x21, 0xE5, 0x58, 0x81, 0xFF, 0x0A, + 0x4B, 0x64, 0xE9, 0x36, 0x58, 0x12, 0xDD, 0xB6, 0xD0, 0x1E, 0xD9, 0x78, 0xDF, 0x11, 0x97, 0x2E, + 0xB5, 0x59, 0x68, 0x43, 0xDE, 0x58, 0xB6, 0xFC, 0xB6, 0x14, 0x1C, 0x66, 0x3D, 0xC2, 0xAA, 0xE0, + 0xE1, 0x44, 0xE5, 0xD6, 0x12, 0xBE, 0xF8, 0x0E, 0xAF, 0x35, 0xE5, 0xD6, 0x86, 0x0B, 0x58, 0xDF, + 0x3B, 0x9B, 0xFC, 0xE0, 0x06, 0x04, 0xF1, 0x68, 0x48, 0xF4, 0xAD, 0x5A, 0x2B, 0xDB, 0xA4, 0x2C, + 0x35, 0x51, 0x1C, 0x0B, 0x0D, 0xB5, 0xA3, 0x8B, 0x57, 0x32, 0x7E, 0xFE, 0x53, 0x06, 0xD0, 0x7F, + 0x66, 0x56, 0x5C, 0xCD, 0xCF, 0x7F, 0x2D, 0x6E, 0x87, 0xF4, 0xDE, 0x41, 0xBC, 0x3F, 0xAC, 0x81, + 0x46, 0x90, 0x09, 0x2B, 0x91, 0x14, 0x39, 0xD6, 0xA8, 0xE5, 0x2F, 0x0C, 0xC5, 0xFB, 0xC2, 0x4A, + 0xAA, 0x56, 0xAA, 0x5E, 0xD7, 0x70, 0xE5, 0x8D, 0xC9, 0xBA, 0x4A, 0x07, 0xFB, 0x9A, 0xF8, 0x83, + 0xC9, 0xE6, 0xD5, 0x57, 0xE0, 0xDB, 0xC6, 0x80, 0x17, 0x2F, 0x4A, 0x23, 0xB6, 0xBB, 0xBF, 0x7D, + 0x43, 0xD1, 0xB0, 0x34, 0xB9, 0x4B, 0x91, 0xB6, 0x61, 0xC5, 0xFD, 0xE2, 0xD5, 0x31, 0xEC, 0x51, + 0xEC, 0x76, 0x43, 0x06, 0xE4, 0x58, 0xEA, 0x2D, 0x75, 0xCD, 0xBC, 0xF4, 0xDD, 0x2A, 0x7A, 0x9F, + 0xC4, 0x0B, 0x96, 0xF0, 0xBB, 0xBA, 0x4A, 0x45, 0x2C, 0xED, 0x65, 0x1D, 0x84, 0xCD, 0x14, 0x53, + 0x76, 0xC0, 0xC3, 0x41, 0x25, 0x78, 0xDA, 0x20, 0x1B, 0x28, 0xE1, 0xCB, 0xC7, 0x4C, 0xA0, 0xCC, + 0x9F, 0x6A, 0x65, 0xEA, 0xFC, 0x29, 0xAB, 0xA2, 0x7F, 0xAA, 0x25, 0x15, 0x7E, 0x05, 0xBE, 0x83, + 0xB3, 0x3A, 0x59, 0xA8, 0x2A, 0xA1, 0xD5, 0x9A, 0x86, 0xF9, 0x09, 0x02, 0x2D, 0x27, 0x19, 0x95, + 0xBD, 0xE3, 0xD2, 0x42, 0x17, 0xF1, 0x0B, 0xA5, 0xBC, 0x50, 0x9B, 0x04, 0x34, 0x26, 0xD9, 0xD4, + 0x98, 0x44, 0x6A, 0xCC, 0x68, 0x53, 0x63, 0x92, 0x6D, 0x8D, 0x79, 0x96, 0xAE, 0x94, 0xF4, 0xA4, + 0x2F, 0x9C, 0x72, 0x1F, 0xDD, 0x2B, 0xB8, 0x66, 0xF0, 0xC2, 0xF2, 0xAB, 0x8D, 0x8E, 0xD6, 0x45, + 0xBD, 0x70, 0x51, 0x93, 0xDA, 0xA4, 0x4E, 0x36, 0x0D, 0xC5, 0x0B, 0xBA, 0x67, 0x54, 0x2E, 0x87, + 0xE6, 0xB7, 0xE9, 0xDB, 0x16, 0xBA, 0x05, 0xFD, 0x8F, 0xF3, 0xCE, 0x85, 0xF0, 0x13, 0x43, 0xAA, + 0xD9, 0xA3, 0x92, 0x8B, 0x69, 0x1E, 0xCB, 0x6C, 0x1F, 0x7D, 0x2D, 0xDB, 0xD9, 0x92, 0x83, 0x2F, + 0x31, 0x2A, 0x29, 0x18, 0x35, 0xCC, 0x38, 0xB5, 0xD1, 0x57, 0x08, 0x4B, 0x2E, 0x25, 0x25, 0x71, + 0x29, 0x0D, 0xDE, 0x2D, 0x8E, 0x7A, 0x59, 0x1A, 0x9F, 0x2E, 0x55, 0xFB, 0xF1, 0x7D, 0x96, 0x60, + 0xED, 0xF5, 0x88, 0x59, 0x0D, 0xB8, 0xEC, 0x12, 0xC3, 0x97, 0xAA, 0x3A, 0x50, 0xB1, 0x28, 0x7C, + 0xC0, 0x95, 0xE2, 0xFB, 0xA9, 0xF2, 0xA4, 0xE8, 0xA5, 0x8A, 0x6F, 0x93, 0x94, 0x8A, 0x95, 0x50, + 0x82, 0x14, 0x5C, 0x7C, 0x9A, 0x96, 0x2B, 0x81, 0xA2, 0xA0, 0xA1, 0x04, 0x1C, 0xD6, 0xC8, 0x4C, + 0x56, 0xAD, 0x55, 0x93, 0x24, 0x51, 0x6A, 0x1B, 0x85, 0xA0, 0xDA, 0xF1, 0x6F, 0xB1, 0xC2, 0x63, + 0x45, 0x1C, 0x61, 0x0C, 0x8A, 0xF3, 0x14, 0xDE, 0xF1, 0x01, 0xEC, 0x8A, 0x32, 0x74, 0xD5, 0xD7, + 0xEF, 0x9D, 0x40, 0x75, 0xFB, 0xCA, 0x7E, 0x64, 0x95, 0xA5, 0x08, 0xC8, 0xA4, 0xBA, 0x63, 0x98, + 0x18, 0x47, 0xD3, 0x20, 0x99, 0xD7, 0xD5, 0x5F, 0xC5, 0x8D, 0xE2, 0x63, 0x17, 0x8E, 0x89, 0xA7, + 0x28, 0xD3, 0x22, 0x2E, 0x02, 0x7D, 0xAC, 0x04, 0x8F, 0x34, 0x08, 0x48, 0xCA, 0xB6, 0x60, 0x66, + 0xD1, 0xE7, 0x21, 0xB0, 0x30, 0x06, 0xE4, 0x38, 0x4E, 0xEE, 0x0E, 0xC0, 0x86, 0x31, 0x55, 0xF0, + 0xA5, 0xD7, 0x3B, 0xB2, 0x5A, 0xB4, 0x48, 0xC0, 0xA5, 0xF3, 0xBA, 0xFA, 0x2A, 0x03, 0x47, 0xB5, + 0x60, 0x88, 0x7F, 0x40, 0x48, 0xA2, 0x65, 0x18, 0x82, 0xDD, 0xDE, 0x00, 0x3D, 0xC9, 0x60, 0xA0, + 0xD0, 0x83, 0xE1, 0x99, 0x43, 0xC0, 0x5A, 0xAC, 0x50, 0x1E, 0x4A, 0x91, 0x03, 0x2E, 0x13, 0x8D, + 0x22, 0xB6, 0x52, 0xFE, 0xEB, 0xED, 0x9B, 0xDF, 0x39, 0x5F, 0x9C, 0xB1, 0xEB, 0x25, 0x04, 0xB0, + 0x7A, 0x30, 0x52, 0x5B, 0x24, 0xCC, 0x2F, 0xC5, 0x6F, 0x0A, 0x46, 0xB0, 0x8D, 0xFD, 0x72, 0xB9, + 0x29, 0x5E, 0x48, 0x93, 0x08, 0x30, 0x06, 0x49, 0x6A, 0x36, 0x9B, 0x58, 0xE2, 0x81, 0x70, 0x13, + 0xC1, 0x89, 0x52, 0x76, 0x83, 0x45, 0x58, 0x33, 0xFB, 0x70, 0x76, 0x5A, 0xE7, 0x9A, 0xE8, 0x14, + 0x35, 0xBE, 0x52, 0x47, 0x39, 0xBA, 0xD3, 0xA3, 0x66, 0x1C, 0xC1, 0xC6, 0xFC, 0x3B, 0x0C, 0x09, + 0xD9, 0x04, 0xC2, 0xB3, 0x2B, 0x36, 0xCA, 0x63, 0x20, 0xED, 0xDE, 0x1D, 0x8D, 0xA2, 0x26, 0x0D, + 0xC0, 0x88, 0x1A, 0x68, 0x52, 0xB7, 0x4D, 0x13, 0xDB, 0x44, 0x08, 0xF9, 0x72, 0x47, 0x74, 0xFE, + 0x1F, 0xE7, 0xEF, 0xFE, 0x00, 0xBF, 0x9B, 0x40, 0x48, 0x8F, 0x53, 0xD3, 0x45, 0x1C, 0xA5, 0xEC, + 0x82, 0xDD, 0x72, 0x4D, 0x1B, 0xB8, 0xA6, 0x55, 0x9A, 0x8C, 0xE7, 0x06, 0x06, 0x75, 0x60, 0x77, + 0x1A, 0x87, 0xAC, 0x19, 0xC6, 0x57, 0xF5, 0xAC, 0x4B, 0xD3, 0x5F, 0x7F, 0x3C, 0xC1, 0x12, 0x20, + 0x10, 0x59, 0x5B, 0x23, 0x96, 0x0B, 0x16, 0xD5, 0xD5, 0xDF, 0x4E, 0x2E, 0x60, 0xCB, 0x3A, 0x44, + 0x56, 0xD0, 0x94, 0x02, 0xC9, 0xEB, 0x1B, 0x2C, 0x10, 0x6F, 0x05, 0x24, 0x8F, 0x0F, 0x6A, 0x45, + 0xF6, 0xA2, 0x42, 0x13, 0x96, 0x05, 0x53, 0x08, 0x13, 0x9D, 0xB5, 0x70, 0x32, 0xDA, 0xFD, 0xDE, + 0xC9, 0xD5, 0x17, 0xB1, 0x5A, 0xB3, 0xF2, 0xCE, 0x35, 0x63, 0xCB, 0x7E, 0xFD, 0x4A, 0xAE, 0x60, + 0x0E, 0x59, 0xEC, 0x66, 0xF1, 0xC6, 0x40, 0xBE, 0x3D, 0x08, 0x99, 0x3A, 0x14, 0x89, 0x0D, 0xCA, + 0xCD, 0xEB, 0x38, 0x99, 0xBF, 0xF2, 0xB8, 0x37, 0xE4, 0x4D, 0x6F, 0xB1, 0xC0, 0xCD, 0x0A, 0xED, + 0x2C, 0xC7, 0xDB, 0x85, 0xAB, 0x8C, 0xC0, 0x55, 0x46, 0x47, 0x19, 0xFE, 0xC3, 0x08, 0x9C, 0xA4, + 0x0C, 0xDD, 0xD9, 0xA7, 0xE8, 0x33, 0x58, 0xE1, 0x72, 0x4E, 0x17, 0x48, 0x2B, 0x7A, 0xAE, 0x16, + 0xC0, 0x43, 0x3D, 0x10, 0xEE, 0x53, 0x2F, 0xD6, 0x2B, 0x5E, 0xDF, 0x00, 0xD5, 0xB7, 0x01, 0x68, + 0xEB, 0x7A, 0xA5, 0xAC, 0xBC, 0x43, 0xDC, 0x35, 0xC9, 0xB9, 0xF7, 0xEF, 0xCE, 0x2F, 0x30, 0xBB, + 0x20, 0x78, 0x2A, 0x71, 0xB0, 0x32, 0xB5, 0x29, 0x2F, 0xE0, 0xC3, 0x4E, 0x6E, 0x60, 0x95, 0x37, + 0x60, 0xB3, 0x18, 0x08, 0x3D, 0x52, 0x4C, 0xBC, 0x7F, 0x51, 0xF5, 0x22, 0x34, 0xD7, 0xEE, 0x81, + 0x5D, 0xD9, 0x5E, 0x41, 0xED, 0x16, 0x4B, 0x2A, 0x6A, 0x66, 0x69, 0x21, 0xCA, 0x92, 0xE7, 0x33, + 0xBF, 0x25, 0xD3, 0xB8, 0x7F, 0x83, 0x8C, 0x6A, 0xBF, 0x8A, 0x09, 0x96, 0x08, 0x36, 0x1E, 0xB0, + 0x8C, 0x0F, 0xF1, 0x5D, 0x41, 0xC6, 0x37, 0x30, 0xA5, 0x7D, 0x8D, 0xBF, 0x00, 0xA9, 0x9B, 0x1A, + 0xE6, 0x60, 0xEB, 0xB5, 0x4E, 0xC1, 0x5E, 0x51, 0xC1, 0xB6, 0x36, 0x36, 0x1E, 0x47, 0xD4, 0x5C, + 0xD2, 0x39, 0xD2, 0xB0, 0x51, 0x75, 0x94, 0xD4, 0x98, 0xFA, 0xF3, 0xD0, 0x7B, 0x86, 0x40, 0xCA, + 0x77, 0x58, 0x87, 0x5D, 0x4B, 0xA1, 0x44, 0x72, 0x39, 0x18, 0x7F, 0xD0, 0x20, 0x54, 0x37, 0x55, + 0x35, 0x0E, 0xDA, 0x40, 0x34, 0x4B, 0xBD, 0x5F, 0x6F, 0xD0, 0x89, 0xD4, 0x9D, 0x6B, 0xEB, 0x42, + 0xE1, 0x21, 0xE0, 0xF9, 0x63, 0x39, 0x1F, 0x83, 0x90, 0x90, 0xC5, 0x2D, 0x34, 0x01, 0xD9, 0x2E, + 0x63, 0x43, 0xB0, 0xE6, 0x52, 0x4C, 0x8E, 0xF8, 0x50, 0x03, 0x63, 0x6B, 0xAA, 0x8D, 0x28, 0x2B, + 0x8A, 0x47, 0x05, 0x2C, 0xD8, 0xDD, 0xFB, 0x5F, 0xD1, 0xD5, 0xE7, 0xD6, 0x03, 0x65, 0x19, 0xD4, + 0x8F, 0xE5, 0x15, 0x74, 0xA4, 0xC0, 0x6B, 0x70, 0x0D, 0x7F, 0x63, 0xE8, 0x0F, 0x1B, 0xAA, 0xA1, + 0x36, 0x4A, 0x18, 0x60, 0xEF, 0xDB, 0x38, 0xE2, 0x33, 0xE8, 0x82, 0x18, 0x6F, 0x67, 0x3F, 0x82, + 0x83, 0x20, 0x65, 0x77, 0xE7, 0xEF, 0x31, 0x64, 0xC8, 0x7B, 0x7B, 0xDF, 0x06, 0xD1, 0x92, 0xB3, + 0xFD, 0xFD, 0xE7, 0x0C, 0xCC, 0xA8, 0x2F, 0xFA, 0x8B, 0x5D, 0xFD, 0x1E, 0xF8, 0xEC, 0xE7, 0x30, + 0x44, 0x85, 0xC9, 0xDF, 0xC4, 0x98, 0xDB, 0x6F, 0x62, 0x5E, 0xBC, 0xC8, 0xDF, 0x13, 0x35, 0x27, + 0x61, 0x8C, 0xB5, 0x98, 0x82, 0xEF, 0xF4, 0x13, 0x89, 0x51, 0xF5, 0xB1, 0xA1, 0xD6, 0x81, 0xCF, + 0x13, 0xE1, 0x8A, 0x98, 0xAF, 0x3D, 0x21, 0x3E, 0x62, 0xFB, 0x87, 0x96, 0x8E, 0xEC, 0x64, 0x12, + 0x29, 0x7F, 0x40, 0x32, 0x52, 0xF1, 0x17, 0x24, 0x07, 0x56, 0xC9, 0x5F, 0x78, 0xEF, 0x9E, 0x58, + 0x50, 0x24, 0x77, 0x2B, 0xF7, 0x19, 0x6D, 0xD4, 0xD7, 0x1E, 0x88, 0xAA, 0x8F, 0xB1, 0x55, 0xF1, + 0x26, 0x0B, 0x7F, 0xEC, 0x01, 0x39, 0xC0, 0xEB, 0x8F, 0x3F, 0x94, 0x0B, 0x5C, 0xAF, 0x3F, 0xBE, + 0xFB, 0x52, 0x3F, 0xE0, 0x1A, 0xF6, 0x39, 0x6B, 0x76, 0x48, 0x01, 0x0F, 0xED, 0x9A, 0x7E, 0xE7, + 0xF8, 0xAC, 0x6D, 0x8B, 0x99, 0x05, 0xEA, 0xA7, 0x51, 0xC0, 0x3F, 0x9C, 0x4A, 0xE1, 0x8E, 0x77, + 0xC5, 0x25, 0xA0, 0xBD, 0xAD, 0x89, 0x88, 0x60, 0x5E, 0xCA, 0x2B, 0xEA, 0x65, 0x25, 0x74, 0x50, + 0x3F, 0x41, 0xF8, 0xD9, 0x33, 0xCD, 0xCF, 0x10, 0x25, 0xED, 0x79, 0xFF, 0xB7, 0xFD, 0x26, 0xB1, + 0xEC, 0xDF, 0x55, 0xC4, 0x43, 0xF9, 0x70, 0x0A, 0x79, 0x41, 0xFC, 0x40, 0xE4, 0x01, 0x16, 0x1E, + 0x82, 0x8F, 0xB8, 0x14, 0x7C, 0x64, 0x9A, 0x29, 0x0A, 0x7E, 0x64, 0x25, 0xE3, 0x2C, 0x5E, 0x90, + 0x96, 0xDF, 0xD4, 0x23, 0x9A, 0x52, 0xD8, 0x95, 0xEC, 0xDD, 0xDF, 0x8F, 0x80, 0x32, 0xCC, 0x2A, + 0x23, 0x53, 0x1D, 0x88, 0x21, 0x85, 0xB4, 0x14, 0x8E, 0x06, 0x8B, 0x98, 0x43, 0x16, 0xA6, 0x2C, + 0xB7, 0x2A, 0x01, 0xF8, 0xD7, 0xE0, 0x28, 0x1B, 0x32, 0x0C, 0x32, 0xFF, 0x1A, 0x8E, 0xA2, 0x4F, + 0xC1, 0xE7, 0x6C, 0x95, 0x01, 0xAC, 0x02, 0x5C, 0x51, 0x6E, 0x58, 0x92, 0xC2, 0x36, 0x20, 0x47, + 0x0D, 0x3F, 0x99, 0x9F, 0x65, 0xB8, 0x08, 0xC1, 0xD3, 0x01, 0x4E, 0x66, 0xA7, 0xE8, 0x2A, 0x02, + 0x74, 0xA3, 0x36, 0xC2, 0x4F, 0xD6, 0x67, 0xC8, 0x6B, 0x1A, 0x9A, 0xAE, 0x56, 0x69, 0xBB, 0x05, + 0x1C, 0x64, 0x9D, 0xDA, 0xAC, 0xAC, 0xED, 0x65, 0x7D, 0x9B, 0x1B, 0x7B, 0x31, 0x28, 0x4E, 0x6E, + 0x1E, 0x70, 0x07, 0x10, 0xAE, 0x6D, 0x82, 0x34, 0x9F, 0x0B, 0x32, 0x0B, 0x79, 0x34, 0xB9, 0xBB, + 0x4D, 0x51, 0xDA, 0xDA, 0xDF, 0xC6, 0x2B, 0xE9, 0xF0, 0x93, 0x9D, 0x75, 0x56, 0x5F, 0x4E, 0x87, + 0x9F, 0x9C, 0xBC, 0x83, 0x2C, 0xDC, 0x39, 0x75, 0xD5, 0xB3, 0x85, 0x66, 0x71, 0xCA, 0x29, 0xDA, + 0xDF, 0xCB, 0x1D, 0x61, 0xF3, 0x4A, 0x94, 0xA4, 0x99, 0x6B, 0x90, 0x47, 0xFE, 0xB2, 0xBE, 0xB7, + 0x12, 0xAE, 0x0B, 0xFB, 0xA0, 0x0D, 0x50, 0x7A, 0xD6, 0x6B, 0x14, 0x1F, 0x85, 0x02, 0xDE, 0xB8, + 0x1A, 0xF0, 0x62, 0xB7, 0x5E, 0x15, 0x44, 0x29, 0xC5, 0xDA, 0x10, 0x22, 0xEC, 0xDC, 0x44, 0x41, + 0xEC, 0x10, 0x97, 0x03, 0x5F, 0x46, 0x61, 0x53, 0xBC, 0x15, 0xF8, 0x56, 0xF6, 0x78, 0xFF, 0x28, + 0xEB, 0x5E, 0xCF, 0x9B, 0x46, 0xBB, 0xF5, 0xF8, 0x25, 0x5A, 0x88, 0x8F, 0x6C, 0x2C, 0xC1, 0xAA, + 0x2B, 0x3C, 0x88, 0xAF, 0x36, 0xCA, 0x74, 0x6E, 0x80, 0xB4, 0x37, 0xAA, 0x3C, 0x69, 0xA8, 0xAD, + 0x15, 0x04, 0x6A, 0x9F, 0x54, 0x2F, 0xF1, 0x97, 0x01, 0x88, 0xE3, 0x67, 0x6D, 0xF0, 0x2C, 0x40, + 0x15, 0x10, 0x5A, 0x73, 0x1C, 0x44, 0x90, 0x02, 0x5F, 0xD0, 0x79, 0x09, 0x2F, 0x49, 0xBC, 0xBB, + 0xF1, 0x72, 0x3A, 0x65, 0x90, 0x89, 0x15, 0x3B, 0x8B, 0x23, 0xA4, 0xD4, 0xA8, 0x1C, 0x23, 0x56, + 0x0C, 0xCF, 0xC7, 0x73, 0x55, 0xDB, 0x75, 0x06, 0xC1, 0x5C, 0x57, 0x80, 0x10, 0x81, 0x2A, 0x50, + 0x76, 0x9E, 0x5B, 0xA8, 0x80, 0xFE, 0x1F, 0x82, 0x2D, 0x1D, 0xEC, 0xB7, 0x6F, 0x29, 0xE3, 0x18, + 0x48, 0xC4, 0x4B, 0x5E, 0x2F, 0x71, 0x46, 0x77, 0x98, 0xA3, 0x55, 0xD7, 0xA2, 0x33, 0x0E, 0x07, + 0x31, 0xC6, 0x24, 0xB6, 0x32, 0x25, 0x3B, 0x25, 0xB1, 0x11, 0x0A, 0xFF, 0x00, 0x41, 0x80, 0x0F, + 0xA9, 0x82, 0x12, 0x44, 0xB0, 0x64, 0x34, 0x61, 0x90, 0x28, 0xFF, 0x8C, 0x74, 0xFA, 0x85, 0xE8, + 0xA4, 0x15, 0x81, 0x31, 0x8E, 0x2A, 0x1B, 0x2B, 0xFB, 0x68, 0x94, 0x9D, 0x54, 0x40, 0x0B, 0x22, + 0x63, 0xFC, 0xD3, 0x57, 0xA0, 0x1B, 0x1C, 0x74, 0x03, 0xDA, 0xB2, 0xD3, 0x1C, 0x1C, 0xED, 0x50, + 0xD5, 0x96, 0xBF, 0x52, 0x30, 0x0E, 0xA1, 0x6E, 0x0D, 0xCD, 0x13, 0xFD, 0xAB, 0x1D, 0xA5, 0xB9, + 0xB2, 0xEF, 0x07, 0x9A, 0xFB, 0xE2, 0x45, 0xEE, 0x71, 0x4F, 0xB9, 0x92, 0x32, 0x36, 0x4F, 0x95, + 0xBB, 0x78, 0xA9, 0xE0, 0x09, 0x34, 0x19, 0x49, 0x28, 0x53, 0xC8, 0xCA, 0x15, 0x2F, 0x8A, 0xC1, + 0xC2, 0x80, 0x65, 0x8D, 0x85, 0x28, 0xEA, 0x38, 0x2C, 0xA1, 0x71, 0x51, 0xBC, 0x52, 0xCA, 0x81, + 0x07, 0x90, 0x5D, 0x3D, 0x39, 0x3B, 0x7B, 0x77, 0x56, 0xA0, 0xBB, 0x7D, 0xA2, 0x84, 0x83, 0x9D, + 0xD8, 0x3C, 0x53, 0xB2, 0xB5, 0x19, 0x1C, 0xD4, 0x50, 0x15, 0xEC, 0x1C, 0xE0, 0x79, 0x0A, 0xEB, + 0xB3, 0xA6, 0x57, 0xC2, 0xD1, 0x8D, 0x68, 0xD4, 0x1B, 0xE3, 0xCB, 0x02, 0x4C, 0x4E, 0x4B, 0x01, + 0x69, 0x65, 0xC2, 0x3D, 0xA6, 0x93, 0x95, 0x55, 0xC1, 0x1C, 0x87, 0x0C, 0x66, 0xC9, 0xB3, 0x6A, + 0xCA, 0x94, 0x02, 0x8F, 0x3A, 0x38, 0xD8, 0xCA, 0xB0, 0x06, 0x58, 0x59, 0xA5, 0xDC, 0x28, 0x37, + 0xA2, 0x6D, 0x1D, 0x8C, 0xD1, 0x06, 0xBB, 0x00, 0x42, 0xC8, 0xA2, 0x83, 0xD1, 0x29, 0x52, 0x8E, + 0xFF, 0x07, 0x59, 0xC3, 0x5E, 0x9B, 0xAA, 0x0D, 0x32, 0x31, 0x00, 0x67, 0x8D, 0x48, 0x95, 0x0D, + 0x5F, 0xF9, 0x24, 0xE0, 0xBD, 0x70, 0xEE, 0xD5, 0xC2, 0xD1, 0xC6, 0x11, 0x47, 0xE5, 0xA5, 0xAA, + 0x3D, 0x5C, 0x1C, 0x58, 0x3D, 0xBF, 0x34, 0x80, 0x07, 0x15, 0x9F, 0x47, 0x96, 0xD2, 0xA2, 0xCF, + 0x98, 0x8E, 0xE7, 0x10, 0x0F, 0xB9, 0xD8, 0x87, 0x66, 0x96, 0x22, 0x8E, 0x67, 0x46, 0xAA, 0x0F, + 0xC4, 0xE7, 0xE2, 0x5C, 0xE5, 0xC1, 0xC2, 0x47, 0x16, 0x68, 0x55, 0x8B, 0x1F, 0xFA, 0x83, 0x75, + 0x0D, 0x15, 0xBC, 0xC9, 0xD3, 0xEA, 0x19, 0xA2, 0x9C, 0x51, 0xCC, 0xD3, 0xD6, 0xE5, 0x03, 0x66, + 0xFA, 0x93, 0x6B, 0x1A, 0x4B, 0x12, 0x2E, 0xD8, 0xDF, 0x8E, 0xB2, 0xC6, 0x3F, 0xA7, 0x9E, 0x41, + 0xB4, 0x7E, 0xB0, 0xA2, 0xB1, 0xC5, 0xFB, 0xC7, 0xD5, 0x32, 0x1E, 0x2A, 0x5F, 0x64, 0xB1, 0xF9, + 0xCE, 0x0A, 0xC6, 0xC3, 0xDA, 0xF3, 0x58, 0x13, 0xB4, 0x85, 0xFD, 0x81, 0xD3, 0xB6, 0x07, 0xC0, + 0x64, 0xE7, 0x6E, 0x9F, 0xA7, 0x3E, 0xCF, 0xD7, 0xF8, 0xCA, 0xCC, 0xEC, 0xAC, 0xF9, 0xE2, 0xF6, + 0x91, 0x36, 0xA2, 0x28, 0xB7, 0xFC, 0x83, 0x2C, 0xCA, 0xF6, 0xF4, 0x12, 0x8E, 0x43, 0x61, 0x42, + 0x1F, 0x57, 0xD7, 0x41, 0xF3, 0xA9, 0x5A, 0xF4, 0x22, 0x54, 0x88, 0xC0, 0x8B, 0x17, 0xAA, 0x5B, + 0x7D, 0x2C, 0xF7, 0x7E, 0xFB, 0x56, 0x75, 0xBB, 0xAA, 0x0D, 0xBE, 0x3D, 0xEB, 0xD4, 0xAA, 0xFE, + 0x6E, 0x82, 0xA1, 0x8E, 0xF0, 0x78, 0x94, 0xAC, 0x29, 0xB8, 0x94, 0x53, 0x1E, 0x2F, 0x14, 0x88, + 0x8C, 0xC8, 0x83, 0xFA, 0x82, 0x67, 0x2F, 0x5C, 0x53, 0xE7, 0x23, 0x08, 0xE0, 0xE8, 0xF7, 0xAA, + 0x40, 0xE4, 0x7A, 0x49, 0xAE, 0xA3, 0xC6, 0xE8, 0x40, 0xFA, 0x54, 0xD5, 0xBA, 0xE8, 0x31, 0x02, + 0x57, 0xC8, 0xAE, 0x6B, 0x19, 0x91, 0xEE, 0x9A, 0x47, 0x11, 0x04, 0x30, 0x13, 0x10, 0xDD, 0x24, + 0x5F, 0x9F, 0x63, 0x60, 0xB9, 0xE1, 0x00, 0x21, 0x26, 0xB4, 0x30, 0x98, 0xA4, 0x2D, 0x57, 0x6B, + 0x66, 0x3B, 0x9A, 0xF6, 0x95, 0xD1, 0x0A, 0x37, 0x8A, 0x59, 0xC9, 0x7E, 0xB5, 0x2C, 0x4E, 0x98, + 0x3F, 0x58, 0x64, 0xA0, 0x13, 0xCA, 0xCF, 0x82, 0x44, 0xEE, 0x63, 0xF8, 0x80, 0x67, 0x0E, 0x0B, + 0xFB, 0x90, 0xA7, 0x65, 0x07, 0x46, 0x2F, 0xB6, 0x46, 0xE3, 0xD9, 0x5F, 0x42, 0xE2, 0xE5, 0x87, + 0xF3, 0x93, 0xB3, 0x72, 0x65, 0x03, 0x6D, 0x2E, 0xA0, 0x10, 0x71, 0xB0, 0xC5, 0x0D, 0xF5, 0xC5, + 0xFB, 0x9F, 0xCF, 0xCF, 0x3F, 0xBE, 0x3B, 0x7B, 0xB5, 0x7B, 0x08, 0xC7, 0x21, 0xE7, 0x1F, 0x7E, + 0x79, 0x7B, 0x7A, 0x31, 0xBA, 0xC3, 0x6A, 0x76, 0xB0, 0xC3, 0x49, 0x0C, 0x83, 0x87, 0xDF, 0xAC, + 0x04, 0x5B, 0x6F, 0x56, 0x7E, 0x80, 0x36, 0x99, 0x2B, 0x52, 0xE6, 0x18, 0x54, 0x33, 0xC7, 0x3C, + 0x35, 0xAC, 0xC4, 0xAE, 0x41, 0x9E, 0x3E, 0x0E, 0xB2, 0x32, 0x0F, 0xB0, 0x3D, 0x28, 0xA7, 0x8E, + 0x11, 0xB9, 0xA6, 0x20, 0x4B, 0x1D, 0x57, 0x41, 0xE4, 0xC7, 0xAB, 0x1D, 0xB6, 0x3B, 0x9B, 0xBF, + 0x1E, 0x1E, 0xB5, 0xE4, 0x31, 0xF2, 0xA3, 0x96, 0xFC, 0xF5, 0x0A, 0xFD, 0xA3, 0xD1, 0xFF, 0x0B, + 0xB4, 0xEA, 0x2E, 0x52, 0x3B, 0x5A, 0x00, 0x00 +}; diff --git a/Grbl_Esp32-master/embedded/footer.txt b/Grbl_Esp32-master/embedded/footer.txt new file mode 100644 index 0000000..327a2c0 --- /dev/null +++ b/Grbl_Esp32-master/embedded/footer.txt @@ -0,0 +1 @@ +#endif //__nofile_h diff --git a/Grbl_Esp32-master/embedded/gulpfile.js b/Grbl_Esp32-master/embedded/gulpfile.js new file mode 100644 index 0000000..8deb4a7 --- /dev/null +++ b/Grbl_Esp32-master/embedded/gulpfile.js @@ -0,0 +1,125 @@ +var gulp = require('gulp'), + jshint = require('gulp-jshint'), + gulpif = require('gulp-if'), + concat = require('gulp-concat'), + uglify = require('gulp-uglify'), + cleanCSS = require('gulp-clean-css'), + removeCode = require('gulp-remove-code'), + merge = require('merge-stream'), + del = require('del'), + zip = require('gulp-zip'), + gzip = require('gulp-gzip'), + htmlmin = require('gulp-htmlmin'), + replace = require('gulp-replace'), + fs = require('fs'), + smoosher = require('gulp-smoosher'); + +var demoMode = false; +var testMode = false; + +function clean() { + return del(['dist']); +} + +function clean2() { + return del(['dist/js', 'dist/css']); +} +function lint() { + return gulp.src('www/js/**/script.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')); +} + +function Copytest() { + return merge( + gulp.src(['www/tool.html']) + .pipe(removeCode({production: false})) + .pipe(gulp.dest('dist')), + gulp.src(['www/images/**/*.*']) + .pipe(gulp.dest('dist/images')) + ) +} + +function Copy() { + return merge( + gulp.src(['www/tool.html']) + .pipe(removeCode({production: true})) + .pipe(gulp.dest('dist')), + gulp.src(['www/images/**/*.*']) + .pipe(gulp.dest('dist/images')) + ) +} + +function concatApptest() { + return merge( + gulp.src([ 'www/js/**/*.js']) + .pipe(concat('script.js')) + .pipe(removeCode({production: false})) + .pipe(gulp.dest('./dist/js')), + + gulp.src([ 'www/css/**/*.css']) + .pipe(concat('style.css')) + .pipe(gulp.dest('./dist/css/')) + ) +} + +function concatApp() { + return merge( + gulp.src([ 'www/js/**/*.js']) + .pipe(concat('script.js')) + .pipe(removeCode({production: true})) + .pipe(gulp.dest('./dist/js')), + + gulp.src([ 'www/css/**/*.css']) + .pipe(concat('style.css')) + .pipe(gulp.dest('./dist/css/')) + ) +} + +function minifyApp() { + return merge( + gulp.src(['dist/js/script.js']) + .pipe(uglify({mangle: true})) + .pipe(gulp.dest('./dist/js/')), + + gulp.src('dist/css/style.css') + .pipe(cleanCSS({debug: true}, function(details) { + console.log(details.name + ': ' + details.stats.originalSize); + console.log(details.name + ': ' + details.stats.minifiedSize); + })) + .pipe(gulp.dest('./dist/css/')), + + gulp.src('dist/tool.html') + .pipe(htmlmin({collapseWhitespace: true, minifyCSS: true})) + .pipe(gulp.dest('dist')) + ) +} + +function smoosh() { + return gulp.src('dist/tool.html') + .pipe(smoosher()) + .pipe(gulp.dest('dist')) +} + +function compress() { + return gulp.src('dist/tool.html') + .pipe(gzip()) + .pipe(gulp.dest('.')); +} + +gulp.task(clean); +gulp.task(lint); +gulp.task(Copy); +gulp.task(Copytest); +gulp.task(concatApp); +gulp.task(concatApptest); +gulp.task(minifyApp); +gulp.task(smoosh); +gulp.task(clean2); + +var defaultSeries = gulp.series(clean, lint, Copy, concatApp, smoosh); +var packageSeries = gulp.series(clean, lint, Copy, concatApp,minifyApp, smoosh, compress, clean2); + +gulp.task('default', defaultSeries); +gulp.task('package', packageSeries); + diff --git a/Grbl_Esp32-master/embedded/header.txt b/Grbl_Esp32-master/embedded/header.txt new file mode 100644 index 0000000..85c9dfe --- /dev/null +++ b/Grbl_Esp32-master/embedded/header.txt @@ -0,0 +1,24 @@ +/* + nofile.h - ESP3D data file + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//data generated by https://github.com/AraHaan/bin2c +//bin2c Conversion Tool v0.14.0 - Windows - [FINAL]. +#ifndef __nofile_h +#define __nofile_h diff --git a/Grbl_Esp32-master/embedded/package-lock.json b/Grbl_Esp32-master/embedded/package-lock.json new file mode 100644 index 0000000..156dfe5 --- /dev/null +++ b/Grbl_Esp32-master/embedded/package-lock.json @@ -0,0 +1,6263 @@ +{ + "name": "embedded4ESP3D", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@nodelib/fs.scandir": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz", + "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.2", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz", + "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz", + "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.2", + "fastq": "^1.6.0" + } + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.3.tgz", + "integrity": "sha512-3SiLAIBkDWDg6vFo0+5YJyHPWU9uwu40Qe+v+0MH8wRKYBimHvvAOyk3EzMrD/TrIlLYfXrqDqrg913PynrMJQ==", + "dev": true + }, + "CSSselect": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/CSSselect/-/CSSselect-0.4.1.tgz", + "integrity": "sha1-+Kt+H4QYzmPNput713ioXX7EkrI=", + "dev": true, + "requires": { + "CSSwhat": "0.4", + "domutils": "1.4" + } + }, + "CSSwhat": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/CSSwhat/-/CSSwhat-0.4.7.tgz", + "integrity": "sha1-hn2g/zn3eGEyQsRM/qg/CqTr35s=", + "dev": true + }, + "acorn": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", + "integrity": "sha1-6x9FtKQ/ox0DcBpexG87Umc+kO4=", + "dev": true + }, + "aggregate-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz", + "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^3.2.0" + } + }, + "alter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", + "integrity": "sha1-x1iICGF1cgNKrmJICvJrHU0cs80=", + "dev": true, + "requires": { + "stable": "~0.1.3" + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-replace/-/async-replace-1.0.1.tgz", + "integrity": "sha1-0/CFfM0C8elOsUnLX4nVisTwIdY=", + "dev": true, + "requires": { + "async": "^1.4.2" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "binaryextensions": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.2.tgz", + "integrity": "sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "bufferstreams": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.1.3.tgz", + "integrity": "sha512-HaJnVuslRF4g2kSDeyl++AaVizoitCpL9PglzCYwy0uHHyvWerfvEb8jWmYbF1z4kiVFolGomnxSGl+GUQp2jg==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "cdnizer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cdnizer/-/cdnizer-3.0.2.tgz", + "integrity": "sha512-XTBGCv61pn9r8o/HDkUlPLv9hQ1bx0maWj07UdIU1VV/2P5KAswN9OEHPu6Tz7C5zMXIywdERtpCw0osQYm1ZA==", + "dev": true, + "requires": { + "cdnjs-cdn-data": "^0.1.1", + "google-cdn-data": "^0.1.6", + "jsdelivr-cdn-data": "git://github.com/shahata/jsdelivr-cdn-data.git#d014a2ad1bdfb4c6e3d3cefc7f264435281b91e0", + "lodash": "^4.17.11", + "minimatch": "^3.0.2" + } + }, + "cdnjs-cdn-data": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cdnjs-cdn-data/-/cdnjs-cdn-data-0.1.2.tgz", + "integrity": "sha1-hl00uk5I3Rtz/WaOJKYaWt+biyE=", + "dev": true, + "requires": { + "semver": "~5.0.1" + }, + "dependencies": { + "semver": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", + "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", + "dev": true + } + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cheerio": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.13.1.tgz", + "integrity": "sha1-SK8RNFYbNSf4PZFWxPmo69grBuw=", + "dev": true, + "requires": { + "CSSselect": "~0.4.0", + "entities": "0.x", + "htmlparser2": "~3.4.0", + "underscore": "~1.5" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "dev": true, + "requires": { + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" + } + }, + "deprecated": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.2.tgz", + "integrity": "sha1-vJ3Pm86RdPz5CQzxKVExxDnGgv0=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dom-serializer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.2.1.tgz", + "integrity": "sha1-Wd+dzSJ+gIs2Wuc+H2aErD2Ub8I=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "editions": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", + "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "entities": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-0.5.0.tgz", + "integrity": "sha1-9hHLWuIhBQ4AEsZpeVA/164ZzEk=", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", + "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.1", + "@nodelib/fs.walk": "^1.2.1", + "glob-parent": "^5.0.0", + "is-glob": "^4.0.1", + "merge2": "^1.2.3", + "micromatch": "^4.0.2" + } + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, + "filesize": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.1.6.tgz", + "integrity": "sha1-WISSTvyBpkTjcJqsQDIWGDw9eYo=", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", + "dev": true + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "google-cdn-data": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/google-cdn-data/-/google-cdn-data-0.1.25.tgz", + "integrity": "sha1-nDwxSasYp8LV7V8PC07ovEWZK3E=", + "dev": true + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "gulp-cli": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", + "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.1.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.0.1", + "yargs": "^7.1.0" + } + } + } + }, + "gulp-bytediff": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-bytediff/-/gulp-bytediff-1.0.0.tgz", + "integrity": "sha1-VXPidyiwsW1cqIaU/NNhYzj5xS0=", + "dev": true, + "requires": { + "filesize": "~3.1.3", + "gulp-util": "~3.0.6", + "map-stream": "~0.0.6" + } + }, + "gulp-cdnizer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gulp-cdnizer/-/gulp-cdnizer-2.0.2.tgz", + "integrity": "sha512-kXTdxYMiUoQXvRUaqwm5+uGR2LqUy/7GHde2EtRBK1qY0BUKnbV2mInLBvdsITFQpZ/F6zX2FVFIpLa0qz/8Bw==", + "dev": true, + "requires": { + "cdnizer": "^3.0.2", + "gulp-util": "^3.0.0", + "through2": "^0.5.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-clean-css": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", + "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", + "dev": true, + "requires": { + "clean-css": "4.2.1", + "plugin-error": "1.0.1", + "through2": "3.0.1", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + } + }, + "gulp-gzip": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gulp-gzip/-/gulp-gzip-1.4.2.tgz", + "integrity": "sha512-ZIxfkUwk2XmZPTT9pPHrHUQlZMyp9nPhg2sfoeN27mBGpi7OaHnOD+WCN41NXjfJQ69lV1nQ9LLm1hYxx4h3UQ==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "bytes": "^3.0.0", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.0", + "stream-to-array": "^2.3.0", + "through2": "^2.0.3" + } + }, + "gulp-htmlmin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-htmlmin/-/gulp-htmlmin-5.0.1.tgz", + "integrity": "sha512-ASlyDPZOSKjHYUifYV0rf9JPDflN9IRIb8lw2vRqtYMC4ljU3zAmnnaVXwFQ3H+CfXxZSUesZ2x7jrnPJu93jA==", + "dev": true, + "requires": { + "html-minifier": "^3.5.20", + "plugin-error": "^1.0.1", + "through2": "^2.0.3" + } + }, + "gulp-if": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", + "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", + "dev": true, + "requires": { + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" + }, + "dependencies": { + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulp-jshint": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-jshint/-/gulp-jshint-2.1.0.tgz", + "integrity": "sha512-sP3NK8Y/1e58O0PH9t6s7DAr/lKDSUbIY207oWSeufM6/VclB7jJrIBcPCsyhrFTCDUl9DauePbt6VqP2vPM5w==", + "dev": true, + "requires": { + "lodash": "^4.12.0", + "minimatch": "^3.0.3", + "plugin-error": "^0.1.2", + "rcloader": "^0.2.2", + "through2": "^2.0.0" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + } + } + }, + "gulp-match": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", + "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.3" + } + }, + "gulp-ng-annotate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-ng-annotate/-/gulp-ng-annotate-2.1.0.tgz", + "integrity": "sha512-wjazOa5qE83akCih+lK2a0LFvkLbIMeblxr54ofmc3WKJ3Ipx/BM98ZCtCDfQW/008EVUSRqwfEjFKEEGI0QbA==", + "dev": true, + "requires": { + "bufferstreams": "^1.1.0", + "merge": "^1.2.0", + "ng-annotate": "^1.2.1", + "plugin-error": "^0.1.2", + "through2": "^2.0.1", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + } + } + }, + "gulp-remove-code": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/gulp-remove-code/-/gulp-remove-code-3.0.4.tgz", + "integrity": "sha512-nfGXuE2ra/o008t+XPzd3/dbkgmO4XNLEUibCFlv4KS5+V2cLGU0m9Rmdd4L9ZkduwC1+/AuSEyySt7CZhcLzw==", + "dev": true, + "requires": { + "bufferstreams": "^2.0.1", + "escape-string-regexp": "^1.0.5", + "object.entries": "^1.0.4", + "plugin-error": "^1.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "bufferstreams": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-2.0.1.tgz", + "integrity": "sha512-ZswyIoBfFb3cVDsnZLLj2IDJ/0ppYdil/v2EGlZXvoefO689FokEmFEldhN5dV7R2QBxFneqTJOMIpfqhj+n0g==", + "dev": true, + "requires": { + "readable-stream": "^2.3.6" + } + } + } + }, + "gulp-replace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz", + "integrity": "sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw==", + "dev": true, + "requires": { + "istextorbinary": "2.2.1", + "readable-stream": "^2.0.1", + "replacestream": "^4.0.0" + } + }, + "gulp-smoosher": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/gulp-smoosher/-/gulp-smoosher-0.0.9.tgz", + "integrity": "sha1-IiqiHu5TEzzvKL8ki1xJdbid2x8=", + "dev": true, + "requires": { + "async": "^0.9.0", + "async-replace": "^1.0.0", + "cheerio": "~0.13.1", + "gulp-util": "~2.2.14", + "lodash": "^3.10.1", + "through2": "~0.4.1" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "^0.5.0", + "dateformat": "^1.0.7-1.2.3", + "lodash._reinterpolate": "^2.4.1", + "lodash.template": "^2.4.1", + "minimist": "^0.2.0", + "multipipe": "^0.1.0", + "through2": "^0.5.0", + "vinyl": "^0.2.1" + }, + "dependencies": { + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "requires": { + "lodash._escapehtmlchar": "~2.4.1", + "lodash._reunescapedhtml": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "requires": { + "lodash._escapestringchar": "~2.4.1", + "lodash._reinterpolate": "~2.4.1", + "lodash.defaults": "~2.4.1", + "lodash.escape": "~2.4.1", + "lodash.keys": "~2.4.1", + "lodash.templatesettings": "~2.4.1", + "lodash.values": "~2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~2.4.1", + "lodash.escape": "~2.4.1" + } + }, + "minimist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "~0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-zip": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-5.0.0.tgz", + "integrity": "sha512-oR3t8kn+ccHkSyRcBV5kBLPXrhqTh5d6wBAR7r7wqjNQNBhYvOwPedCwlAaGcNl1qSeXNDn6qOk1Qyxvx9Wrow==", + "dev": true, + "requires": { + "get-stream": "^5.1.0", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "vinyl": "^2.1.0", + "yazl": "^2.5.1" + }, + "dependencies": { + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "htmlparser2": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.4.0.tgz", + "integrity": "sha1-oc1l9YI60oXhnWOwha1yLQpR6uc=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.2", + "domutils": "1.3", + "readable-stream": "1.1" + }, + "dependencies": { + "domutils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.3.0.tgz", + "integrity": "sha1-mtTVm1r2ymhMYv5tdo7xcOcN8ZI=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.1.tgz", + "integrity": "sha512-CKstxrctq1kUesU6WhtZDbYKzzYBuRH0UYInAVrkc/EYdB9ltbfE0gOoayG9nhohG6447sOOVGhHqsdmBvkbNg==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "istextorbinary": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", + "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", + "dev": true, + "requires": { + "binaryextensions": "2", + "editions": "^1.3.3", + "textextensions": "2" + } + }, + "jsdelivr-cdn-data": { + "version": "git://github.com/shahata/jsdelivr-cdn-data.git#d014a2ad1bdfb4c6e3d3cefc7f264435281b91e0", + "from": "git://github.com/shahata/jsdelivr-cdn-data.git#d014a2ad1bdfb4c6e3d3cefc7f264435281b91e0", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "jshint": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", + "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.11", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" + }, + "dependencies": { + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "requires": { + "lodash._htmlescapes": "~2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "requires": { + "lodash._htmlescapes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "requires": { + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "^1.2.0" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.4.tgz", + "integrity": "sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "ng-annotate": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ng-annotate/-/ng-annotate-1.2.2.tgz", + "integrity": "sha1-3D/FG6Cy+LOF2+BH9NoG9YCh/WE=", + "dev": true, + "requires": { + "acorn": "~2.6.4", + "alter": "~0.2.0", + "convert-source-map": "~1.1.2", + "optimist": "~0.6.1", + "ordered-ast-traverse": "~1.1.1", + "simple-fmt": "~0.1.0", + "simple-is": "~0.2.0", + "source-map": "~0.5.3", + "stable": "~0.1.5", + "stringmap": "~0.2.2", + "stringset": "~0.2.1", + "tryor": "~0.1.2" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "ordered-ast-traverse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz", + "integrity": "sha1-aEOhcLwO7otSDMjdwd3TqjD6BXw=", + "dev": true, + "requires": { + "ordered-esprima-props": "~1.1.0" + } + }, + "ordered-esprima-props": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ordered-esprima-props/-/ordered-esprima-props-1.1.0.tgz", + "integrity": "sha1-qYJwht9fAQqmDpvQK24DNc6i/8s=", + "dev": true + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "rcfinder": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/rcfinder/-/rcfinder-0.1.9.tgz", + "integrity": "sha1-8+gPOH3fmugK4wpBADKWQuroERU=", + "dev": true, + "requires": { + "lodash.clonedeep": "^4.3.2" + } + }, + "rcloader": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/rcloader/-/rcloader-0.2.2.tgz", + "integrity": "sha1-WNIpi0YtC5v9ITPSoex0+9cFxxc=", + "dev": true, + "requires": { + "lodash.assign": "^4.2.0", + "lodash.isobject": "^3.0.2", + "lodash.merge": "^4.6.0", + "rcfinder": "^0.1.6" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "replacestream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", + "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-fmt": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", + "integrity": "sha1-GRv1ZqWeZTBILLJatTtKjchcOms=", + "dev": true + }, + "simple-is": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", + "integrity": "sha1-Krt1qt453rXMgVzhDmGRFkhQuvA=", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringmap": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", + "integrity": "sha1-VWwTeyWPlCuHdvWy71gqoGnX0bE=", + "dev": true + }, + "stringset": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", + "integrity": "sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "ternary-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", + "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", + "dev": true, + "requires": { + "duplexify": "^4.1.1", + "fork-stream": "^0.0.4", + "merge-stream": "^2.0.0", + "through2": "^3.0.1" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "dev": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "textextensions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.5.0.tgz", + "integrity": "sha512-1IkVr355eHcomgK7fgj1Xsokturx6L5S2JRT5WcRdA6v5shk9sxWuO/w/VbpQexwkXJMQIa/j1dBi3oo7+HhcA==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tryor": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz", + "integrity": "sha1-gUXkynyv9ArN48z5Rui4u3W0Fys=", + "dev": true + }, + "type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", + "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", + "integrity": "sha1-EzXF5PXm0zu7SwBrqMhqAPVW3gg=", + "dev": true + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8flags": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + } + } +} diff --git a/Grbl_Esp32-master/embedded/package.json b/Grbl_Esp32-master/embedded/package.json new file mode 100644 index 0000000..b7a2d06 --- /dev/null +++ b/Grbl_Esp32-master/embedded/package.json @@ -0,0 +1,29 @@ +{ + "name": "embedded4ESP3D", + "description": "Embedded files for ESP3D", + "devDependencies": { + "del": "latest", + "deprecated": "latest", + "fs": "latest", + "gulp": "^4.0.0", + "gulp-bytediff": "latest", + "gulp-cdnizer": "latest", + "gulp-clean-css": "latest", + "gulp-concat": "latest", + "gulp-gzip": "latest", + "gulp-htmlmin": "latest", + "gulp-if": "latest", + "gulp-jshint": "latest", + "gulp-ng-annotate": "latest", + "gulp-remove-code": "latest", + "gulp-replace": "latest", + "gulp-smoosher": "latest", + "gulp-uglify": "latest", + "gulp-zip": "latest", + "jshint": "latest", + "merge-stream": "latest" + }, + "repository": "https://github.com/luc-github/ESP3D", + "author": "Luc LEBOSSE", + "license": "(ISC OR GPL-3.0)" +} diff --git a/Grbl_Esp32-master/embedded/tool.html.gz b/Grbl_Esp32-master/embedded/tool.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..1fb223f4fa07a2d2331c0bdb140766f3aab3ada6 GIT binary patch literal 6728 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 literal 0 HcmV?d00001 diff --git a/Grbl_Esp32-master/embedded/www/css/style.css b/Grbl_Esp32-master/embedded/www/css/style.css new file mode 100644 index 0000000..550ff9e --- /dev/null +++ b/Grbl_Esp32-master/embedded/www/css/style.css @@ -0,0 +1,105 @@ +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%; font-size:10px;} +body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333333;background-color:#ffffff;} +table{border:0px;border-spacing:0;max-width:100%;} +td{white-space:nowrap; padding:2mm;} +th{text-align:left;} +.table>thead>tr>th,.table>tbody>tr>th,.table>thead>tr>td,.table>tbody>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #dddddd;} +.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9;} +a{position:relative;display:block;padding:10px 15px;text-decoration:none;color:#cccccc;} +.active{color:#ffffff;background-color:#000000;} +.active a,a:hover,a:focus{color:#FFFFFF;} +.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05);} +.panel-body{padding:15px;} +.panel-heading{padding:10px 15px;color:#333333;background-color:#f5f5f5;border-color:#dddddd;border-top-right-radius:3px;border-top-left-radius:3px;border-bottom:1px solid #dddddd;} +.form-control{display:block;width:auto;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff +;background-image:none;border:1px solid #cccccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075); +* -webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s; +* transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6); +* box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6);} +.form-group{margin-bottom:15px;} +.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation; touch-action:manipulation;cursor:pointer; +background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px; +* -webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;} +.btn-primary{color:#ffffff;background-color:#337ab7;border-color:#2e6da4;} +.btn-primary:focus,.btn-primary:active,.btn-primary:hover,.btn-primary.focus,.btn-primary.active,.btn-primary.hover{color:#ffffff;background-color:#286090;border-color:#122b40;} +.btnimg {cursor:hand; border-radius:6px ;border:1px solid #FFFFFF;} +.btnimg:hover{background-color:#F0F0F0;border-color:#6c6c6c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #808080;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #808080;} +.btnroundimg {cursor:hand; border-radius:30px;} +.btnroundimg:hover{background-color:#F0F0F0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #808080;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #808080;} +.blacklink, .blacklink:active {color:#000000;} +.blacklink:hover{color:#000000;} +input[type="file"]::-webkit-file-upload-button{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation; touch-action:manipulation;cursor:pointer; +background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px; +* -webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none; color: #ffffff;background-color: #5bc0de;border-color: #46b8da;} +input[type="file"]::-webkit-file-upload-button:focus{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation; touch-action:manipulation;cursor:pointer; +background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px; +* -webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none; color: #ffffff;background-color: #31b0d5;border-color: #1b6d85;} +input[type="file"]::-webkit-file-upload-button:hover{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation; touch-action:manipulation;cursor:pointer; +background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px; +* -webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none; color: #ffffff;background-color: #31b0d5;border-color: #269abc;} +.panel-footer{padding:10px 15px;color:#31708f;background-color:#f5f5f5;border-color:#dddddd;border-top:1px solid #dddddd;} +.panel-footer{padding:10px 15px;color:#31708f;background-color:#f5f5f5;border-color:#dddddd;border-top:1px solid #dddddd;} + +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 10000; /* Sit on top */ + padding-top: 100px; /* Location of the box */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +/* Modal Content */ +.modal-content { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + border: 2px solid #337AB7; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + position: relative; + margin: auto; + padding: 0; + background-color: #fefefe; +} + +.modal-header { + padding: 2px 16px; + color: #0f0f0f; + background-color: #f2f2f2; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom: 1px solid #cfcfcf; +} + +.modal-body {padding: 10px 16px;} + +.modal-footer { + padding: 16px 16px; + height: 4.5em; + color: #0f0f0f; + background-color: #f2f2f2; + border-top: 1px solid #cfcfcf; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} +.hide_it { + display: none; +} + +@media (min-width: 768px) { + .modal-content { + width: 580px; + } +} + +@media screen and (max-width: 767px) { + .modal-content { + width: 100%; + } +} diff --git a/Grbl_Esp32-master/embedded/www/js/script.js b/Grbl_Esp32-master/embedded/www/js/script.js new file mode 100644 index 0000000..ab226d6 --- /dev/null +++ b/Grbl_Esp32-master/embedded/www/js/script.js @@ -0,0 +1,505 @@ +var currentpath = "/"; +var authentication = false; +var websocket_port = 0; +var websocket_IP = ""; +var async_webcommunication = false; +var page_id = ""; +var ws_source; +var log_off =false; +var websocket_started =false; +var esp_error_message =""; +var esp_error_code = 0; +var xmlhttpupload; +var typeupload = 0; +function navbar(){ + var content=""; + var tlist = currentpath.split("/"); + var path="/"; + var nb = 1; + content+=""; + while (nb < (tlist.length-1)) + { + path+=tlist[nb] + "/"; + content+=""; + nb++; + } + content+="
/"+tlist[nb] +"/
"; + return content; +} + +function trash_icon(){ + var content =""; + content +=""; + content +=""; + content +=""; + content +=""; + content +=""; + content +=""; + content +=""; + content +=""; + return content; +} + +function back_icon(){ + var content =""; + return content; +} + +function select_dir(directoryname){ + currentpath+=directoryname + "/"; + SendCommand('list','all'); +} + +function compareStrings(a, b) { + // case-insensitive comparison + a = a.toLowerCase(); + b = b.toLowerCase(); + return (a < b) ? -1 : (a > b) ? 1 : 0; +} + +function dispatchfilestatus(jsonresponse) +{ +var content =""; +var display_message = false; +content ="  Status: "+jsonresponse.status; +content +="  |  Total space: "+jsonresponse.total; +content +="  |  Used space: "+jsonresponse.used; +content +="  |  Occupation: "; +content +=" "+jsonresponse.occupation +"%"; +document.getElementById('status').innerHTML=content; +content =""; +if (currentpath!="/") + { + var pos = currentpath.lastIndexOf("/",currentpath.length-2); + var previouspath = currentpath.slice(0,pos+1); + content +=""+back_icon()+" Up.."; + } +jsonresponse.files.sort(function(a, b) { + return compareStrings(a.name, b.name); +}); +if (currentpath=="/") { + display_message = true; +} +var display_time =false; +for (var i1=0;i1
"; + content +=jsonresponse.files[i1].name; + if ((jsonresponse.files[i1].name == "index.html.gz")||(jsonresponse.files[i1].name == "index.html")){ + display_message = false; + } + content +="
"; + content +=jsonresponse.files[i1].size; + content +=""; + if (jsonresponse.files[i1].hasOwnProperty('time')){ + display_time = true; + content +=""; + content += jsonresponse.files[i1].time; + content +=""; + } else { + content +=""; + } + content +="
"; + content +=trash_icon(); + content +="
"; + } +} +//then display directories +for (var i2=0;i2 "; + content +=jsonresponse.files[i2].name; + content +=""; + if (typeof jsonresponse.files[i2].hasOwnProperty('time')){ + display_time = true; + } + content +="
"; + content +=trash_icon(); + content +="
"; + } +} +if(display_time){ + document.getElementById('FS_time').innerHTML = ""; +} else { + document.getElementById('FS_time').innerHTML = "Time"; +} + if (display_message) { + + document.getElementById('MSG').innerHTML = "File index.html.gz is missing, please upload it"; + } else { + document.getElementById('MSG').innerHTML = "Go to ESP3D interface"; + } + document.getElementById('file_list').innerHTML=content; + document.getElementById('path').innerHTML=navbar();} + +function Delete(filename){ +if (confirm("Confirm deletion of file: " + filename))SendCommand("delete",filename); +} + +function Deletedir(filename){ +if (confirm("Confirm deletion of directory: " + filename))SendCommand("deletedir",filename); +} + +function Createdir(){ +var filename = prompt("Directory name", ""); +if (filename != null) { + SendCommand("createdir",filename.trim()); + } +} + +function SendCommand(action,filename){ +var xmlhttp = new XMLHttpRequest(); +var url = "/files?action="+action; +document.getElementById('MSG').innerHTML = "Connecting..."; +url += "&filename="+encodeURI(filename); +url += "&path="+encodeURI(currentpath); +xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState == 4 ) { + if(xmlhttp.status == 200) { + var jsonresponse = JSON.parse(xmlhttp.responseText); + dispatchfilestatus(jsonresponse); + } else { + if(xmlhttp.status == 401) { + RL (); + } else { + console.log(xmlhttp.status); + FWError(); + } + } + } +}; +xmlhttp.open("GET", url, true); +xmlhttp.send(); +} + +function Sendfile(){ +var files = document.getElementById('file-select').files; +if (files.length==0)return; +document.getElementById('upload-button').value = "Uploading..."; +document.getElementById('prg').style.visibility = "visible"; +var formData = new FormData(); +formData.append('path', currentpath); +for (var i3 = 0; i3 < files.length; i3++) { +var file = files[i3]; +var arg = currentpath + file.name + "S"; + //append file size first to check updload is complete + formData.append(arg, file.size); + formData.append('myfiles[]', file, currentpath+file.name);} +xmlhttpupload = new XMLHttpRequest(); +xmlhttpupload.open('POST', '/files', true); +//progress upload event +xmlhttpupload.upload.addEventListener("progress", updateProgress, false); +//progress function +function updateProgress (oEvent) { + if (oEvent.lengthComputable) { + var percentComplete = (oEvent.loaded / oEvent.total)*100; + document.getElementById('prg').value=percentComplete; + document.getElementById('upload-button').value = "Uploading ..." + percentComplete.toFixed(0)+"%" ; + } else { + // Impossible because size is unknown + } +} +typeupload = 1; +xmlhttpupload.onload = function () { + if (xmlhttpupload.status === 200) { +document.getElementById('upload-button').value = 'Upload'; +document.getElementById('prg').style.visibility = "hidden"; +document.getElementById('file-select').value=""; +var jsonresponse = JSON.parse(xmlhttpupload.responseText); +dispatchfilestatus(jsonresponse); + } else uploadError(); +}; + +xmlhttpupload.send(formData); +} + +function padNumber(num, size) { + var s = num.toString(); + while (s.length < size) s = "0" + s; + return s; +} +function getPCTime(){ + var d = new Date(); + return d.getFullYear() + "-" + padNumber(d.getMonth() + 1 ,2) + "-" + padNumber(d.getDate(),2) + "-" + padNumber(d.getHours(),2) + "-" + padNumber(d.getMinutes(),2) + "-" + padNumber(d.getSeconds(),2); +} + +function HideAll(msg){ + //console.log("Hide all:" + msg); + log_off = true; + if(websocket_started){ + ws_source.close(); + } + document.title = document.title + "(disconnected)"; + document.getElementById('MSG').innerHTML = msg; + document.getElementById('FILESYSTEM').style.display = "none"; + document.getElementById('FWUPDATE').style.display = "none"; +} + +function FWError(){ + HideAll("Failed to communicate with FW!"); +} + +function FWOk(){ + document.getElementById('MSG').innerHTML = "Connected"; + document.getElementById('FILESYSTEM').style.display = "block"; + document.getElementById('FWUPDATE').style.display = "block"; +} + +function InitUI(){ +var xmlhttp = new XMLHttpRequest(); +var url = "/command?commandText="+encodeURI("[ESP800]"); +authentication = false; +async_webcommunication = false; +console.log("Init UI"); +xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState == 4 ) { + var error = false; + if(xmlhttp.status == 200) { + var response = xmlhttp.responseText; + var nbitem = 0; + var tresp = response.split("#"); + console.log(xmlhttp.responseText); + if (tresp.length < 3) { + error = true; + } else { + //FW version:1.1f # FW target:grbl-embedded # FW HW:Direct SD # primary sd:/sd # secondary sd:none # authentication:no # webcommunication: Sync: 81:192.168.0.1 # hostname:grblesp # axis:3 + for (var p=0; p < tresp.length; p++){ + var sublist = tresp[p].split(":"); + if (sublist[0].trim() == "FW version"){ + document.getElementById('FWVERSION').innerHTML = "v"+sublist[1]; + nbitem++; + } + if (sublist[0].trim() == "authentication"){ + if (sublist[1].trim() == "no") { + authentication = false; + document.getElementById('loginicon').style.visibility = "hidden"; + } + else { + authentication = true; + document.getElementById('loginicon').style.visibility = "visible"; + } + nbitem++; + } + if (sublist[0].trim() == "webcommunication"){ + websocket_port = sublist[2].trim(); + websocket_IP = sublist[3].trim(); + startSocket(); + nbitem++; + } + if (sublist[0].trim() == "hostname"){ + document.title = sublist[1].trim(); + nbitem++ + } + } + if (nbitem == 4) { + SendCommand('list','all'); + FWOk(); + } else { + error = true; + + } + } + + + + } else if (xmlhttp.status == 401){ + RL(); + } else { + error = true; + console.log( xmlhttp.status); + } + if (error) { + FWError(); + } + } +}; +xmlhttp.open("GET", url, true); +xmlhttp.send(); +} + +function startSocket(){ + if (websocket_started){ + ws_source.close(); + } + if(async_webcommunication){ + ws_source = new WebSocket('ws://'+websocket_IP + ':' + websocket_port +'/ws',['arduino']); + } + else { + //console.log("Socket port is :" + websocket_port); + ws_source = new WebSocket('ws://'+websocket_IP + ':' + websocket_port,['arduino']); + } + ws_source.binaryType = "arraybuffer"; + ws_source.onopen = function(e){ + console.log("WS"); + websocket_started = true; + }; + ws_source.onclose = function(e){ + websocket_started = false; + console.log("~WS"); + //seems sometimes it disconnect so wait 3s and reconnect + //if it is not a log off + if(!log_off) setTimeout(startSocket, 3000); + }; + ws_source.onerror = function(e){ + console.log("WS", e); + }; + ws_source.onmessage = function(e){ + var msg = ""; + //bin + if(!(e.data instanceof ArrayBuffer)){ + msg = e.data; + var tval = msg.split(":"); + if (tval.length >= 2) { + if (tval[0] == 'currentID') { + page_id = tval[1]; + console.log("ID " + page_id); + } + if (tval[0] == 'activeID') { + if(page_id != tval[1]) { + HideAll("It seems you are connect from another location, your are now disconnected"); + } + } + if (tval[0] == 'ERROR') { + esp_error_message = tval[2]; + esp_error_code = tval[1]; + console.log(tval[2] + " code:" + tval[1]); + uploadError(); + xmlhttpupload.abort(); + } + } + + } + //console.log(msg); + + }; +} + +window.onload = function() { +InitUI(); +}; + +function uploadError() +{ + if (esp_error_code != 0) { + alert('Update failed(' + esp_error_code + '): ' + esp_error_message); + esp_error_code = 0; + } else { + alert('Update failed!'); + } + + if (typeupload == 1) { + //location.reload(); + document.getElementById('upload-button').value = 'Upload'; + document.getElementById('prg').style.visibility = "hidden"; + document.getElementById('file-select').value=""; + SendCommand('list', 'all'); + } else { + location.reload(); + } +} + +function Uploadfile(){ +if (!confirm("Confirm Firmware Update ?"))return; +var files = document.getElementById('fw-select').files; +if (files.length==0)return; +document.getElementById('ubut').style.visibility = 'hidden'; +document.getElementById('fw-select').style.visibility = 'hidden'; +document.getElementById('msg').style.visibility = "visible"; +document.getElementById('msg').innerHTML=""; +document.getElementById('FILESYSTEM').style.display = "none"; +document.getElementById('prgfw').style.visibility = "visible"; +var formData = new FormData(); +for (var i4 = 0; i4 < files.length; i4++) { +var file = files[i4]; +var arg = "/" + file.name + "S"; + //append file size first to check updload is complete + formData.append(arg, file.size); + formData.append('myfile[]', file, "/"+file.name);} +typeupload = 0; +xmlhttpupload = new XMLHttpRequest(); +xmlhttpupload.open('POST', '/updatefw', true); +//progress upload event +xmlhttpupload.addEventListener("progress", updateProgress, false); +//progress function +function updateProgress (oEvent) { + if (oEvent.lengthComputable) { + var percentComplete = (oEvent.loaded / oEvent.total)*100; + document.getElementById('prgfw').value=percentComplete; + document.getElementById('msg').innerHTML = "Uploading ..." + percentComplete.toFixed(0)+"%" ; + } else { + // Impossible because size is unknown + } +} +xmlhttpupload.onload = function () { + if (xmlhttpupload.status === 200) { +document.getElementById('ubut').value = 'Upload'; +document.getElementById('msg').innerHTML="Restarting, please wait...."; +document.getElementById('counter').style.visibility = "visible"; +document.getElementById('ubut').style.visibility = 'hidden'; +document.getElementById('ubut').style.width = '0px'; +document.getElementById('fw-select').value=""; +document.getElementById('fw-select').style.visibility = 'hidden'; +document.getElementById('fw-select').style.width = '0px'; + +var jsonresponse = JSON.parse(xmlhttpupload.responseText); +if (jsonresponse.status=='1' || jsonresponse.status=='4' || jsonresponse.status=='1')uploadError(); +if (jsonresponse.status=='2')alert('Update canceled!'); +else if (jsonresponse.status=='3') +{ + var i5 = 0; + var interval; + var x = document.getElementById("prgfw"); + x.max=40; + interval = setInterval(function(){ + i5=i5+1; + var x = document.getElementById("prgfw"); + x.value=i5; + document.getElementById('counter').innerHTML=41-i5; + if (i5>40) + { + clearInterval(interval); + location.reload(); + } + },1000); +} +else uploadError() + } else uploadError() +}; +xmlhttpupload.send(formData); +} + + +function RL(){ + document.getElementById('loginpage').style.display='block'; +} + +function SLR (){ + document.getElementById('loginpage').style.display='none'; + var user = document.getElementById('lut').value.trim(); + var password = document.getElementById('lpt').value.trim(); + var url = "/login?USER="+encodeURIComponent(user) + "&PASSWORD=" + encodeURIComponent(password) + "&SUBMIT=yes" ; + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState == 4){ + if (xmlhttp.status != 200) { + if (xmlhttp.status == 401) { + RL(); + } else { + FWError(); + console.log(xmlhttp.status); + } + } else { + InitUI(); + } + } + }; +xmlhttp.open("GET", url, true); +xmlhttp.send(); +} diff --git a/Grbl_Esp32-master/embedded/www/tool.html b/Grbl_Esp32-master/embedded/www/tool.html new file mode 100644 index 0000000..fea90df --- /dev/null +++ b/Grbl_Esp32-master/embedded/www/tool.html @@ -0,0 +1,142 @@ + + + + + + ESP3D tool page + + + +
+

+
+
+
Flash Filesystem
+
+ +    +

+
+
+ + + + + + +
+ + +
+ + +
+
+
 
+
+ + + + + + + + + + + + +
TypeNameSizeTime
+
+ +
+
+
+
+
Firmware Update
+
+ + + + + + + +
+
+
+ + + + + + + + diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.cpp b/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.cpp new file mode 100644 index 0000000..59debf5 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.cpp @@ -0,0 +1,464 @@ +/* +ESP32 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (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 +#include "ESP32SSDP.h" +#include "WiFiUdp.h" +#include + +//#define DEBUG_SSDP Serial + +#define SSDP_INTERVAL 1200 +#define SSDP_PORT 1900 +#define SSDP_METHOD_SIZE 10 +#define SSDP_URI_SIZE 2 +#define SSDP_BUFFER_SIZE 64 +#define SSDP_MULTICAST_TTL 2 +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + + + +static const char _ssdp_response_template[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n"; + +static const char _ssdp_notify_template[] PROGMEM = + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NTS: ssdp:alive\r\n"; + +static const char _ssdp_packet_template[] PROGMEM = + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber + "USN: uuid:%s\r\n" // _uuid + "%s: %s\r\n" // "NT" or "ST", _deviceType + "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL + "\r\n"; + +static const char _ssdp_schema_template[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "\r\n" + "" + "" + "" + "1" + "0" + "" + "http://%u.%u.%u.%u:%u/" // WiFi.localIP(), _port + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "uuid:%s" + "" +// "" +// "" +// "image/png" +// "48" +// "48" +// "24" +// "icon48.png" +// "" +// "" +// "image/png" +// "120" +// "120" +// "24" +// "icon120.png" +// "" +// "" + "\r\n" + "\r\n"; + +struct SSDPTimer { + ETSTimer timer; +}; + +SSDPClass::SSDPClass() : +_server(0), +_timer(0), +_port(80), +_ttl(SSDP_MULTICAST_TTL), +_respondToPort(0), +_pending(false), +_delay(0), +_process_time(0), +_notify_time(0) +{ + _uuid[0] = '\0'; + _modelNumber[0] = '\0'; + sprintf(_deviceType, "urn:schemas-upnp-org:device:Basic:1"); + _friendlyName[0] = '\0'; + _presentationURL[0] = '\0'; + _serialNumber[0] = '\0'; + _modelName[0] = '\0'; + _modelURL[0] = '\0'; + _manufacturer[0] = '\0'; + _manufacturerURL[0] = '\0'; + sprintf(_schemaURL, "ssdp/schema.xml"); +} + +SSDPClass::~SSDPClass(){ + end(); +} + +void SSDPClass::end(){ + if(!_server) { + return; + } +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf_P(PSTR("SSDP end ... ")); +#endif + // undo all initializations done in begin(), in reverse order + _stopTimer(); + _server->stop(); + delete (_server); + _server = 0; +} + + +bool SSDPClass::begin(){ + _pending = false; + end(); + uint32_t chipId = ((uint16_t) (ESP.getEfuseMac() >> 32)); + sprintf(_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t) ((chipId >> 16) & 0xff), + (uint16_t) ((chipId >> 8) & 0xff), + (uint16_t) chipId & 0xff ); + assert(nullptr == _server); + _server = new WiFiUDP; +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid); +#endif + + + _server = new WiFiUDP; + if (!(_server->beginMulticast(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT))) { +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Error begin"); +#endif + return false; + } + + _startTimer(); + + return true; +} + +void SSDPClass::_send(ssdp_method_t method){ + char buffer[1460]; + IPAddress ip = WiFi.localIP(); + + char valueBuffer[strlen_P(_ssdp_notify_template)+1]; + strcpy_P(valueBuffer, (method == NONE)?_ssdp_response_template:_ssdp_notify_template); + + int len = snprintf_P(buffer, sizeof(buffer), + _ssdp_packet_template, + valueBuffer, + SSDP_INTERVAL, + _modelName, _modelNumber, + _uuid, + (method == NONE)?"ST":"NT", + _deviceType, + ip[0], ip[1], ip[2], ip[3], _port, _schemaURL + ); + if(len < 0) return; + IPAddress remoteAddr; + uint16_t remotePort; + if(method == NONE) { + remoteAddr = _respondToAddr; + remotePort = _respondToPort; +#ifdef DEBUG_SSDP + DEBUG_SSDP.print("Sending Response to "); +#endif + } else { + remoteAddr = IPAddress(SSDP_MULTICAST_ADDR); + remotePort = SSDP_PORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Sending Notify to "); +#endif + } +#ifdef DEBUG_SSDP + DEBUG_SSDP.print(remoteAddr); + DEBUG_SSDP.print(":"); + DEBUG_SSDP.println(remotePort); +#endif + _server->beginPacket(remoteAddr, remotePort); + _server->println(buffer); + _server->endPacket(); +} + +void SSDPClass::schema(WiFiClient client){ + IPAddress ip = WiFi.localIP(); + char buffer[strlen_P(_ssdp_schema_template)+1]; + strcpy_P(buffer, _ssdp_schema_template); + client.printf(buffer, + ip[0], ip[1], ip[2], ip[3], _port, + _deviceType, + _friendlyName, + _presentationURL, + _serialNumber, + _modelName, + _modelNumber, + _modelURL, + _manufacturer, + _manufacturerURL, + _uuid + ); +} + +void SSDPClass::_update(){ + int nbBytes =0; + char * packetBuffer = nullptr; + + if(!_pending && _server) { + ssdp_method_t method = NONE; + nbBytes= _server->parsePacket(); + typedef enum {METHOD, URI, PROTO, KEY, VALUE, ABORT} states; + states state = METHOD; + typedef enum {START, MAN, ST, MX} headers; + headers header = START; + + uint8_t cursor = 0; + uint8_t cr = 0; + + char buffer[SSDP_BUFFER_SIZE] = {0}; + packetBuffer = new char[nbBytes +1]; + int message_size=_server->read(packetBuffer,nbBytes); + int process_pos = 0; + packetBuffer[message_size]='\0'; + _respondToAddr = _server->remoteIP(); + _respondToPort = _server->remotePort(); +#ifdef DEBUG_SSDP + if (message_size) { + DEBUG_SSDP.println("****************************************************"); + DEBUG_SSDP.println(_server->remoteIP()); + DEBUG_SSDP.println(packetBuffer); + DEBUG_SSDP.println("****************************************************"); + } +#endif + while(process_pos < message_size){ + + char c = packetBuffer[process_pos]; + process_pos++; + (c == '\r' || c == '\n') ? cr++ : cr = 0; +#ifdef DEBUG_SSDP + if ((c == '\r' || c == '\n') && (cr < 2)) DEBUG_SSDP.println(buffer); +#endif + switch(state){ + case METHOD: + if(c == ' '){ + if(strcmp(buffer, "M-SEARCH") == 0) method = SEARCH; + + if(method == NONE) state = ABORT; + else state = URI; + cursor = 0; + + } else if(cursor < SSDP_METHOD_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case URI: + if(c == ' '){ + if(strcmp(buffer, "*")) state = ABORT; + else state = PROTO; + cursor = 0; + } else if(cursor < SSDP_URI_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case PROTO: + if(cr == 2){ state = KEY; cursor = 0; } + break; + case KEY: + if(cr == 4){ _pending = true; _process_time = millis(); } + else if(c == ' '){ cursor = 0; state = VALUE; } + else if(c != '\r' && c != '\n' && c != ':' && cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case VALUE: + if(cr == 2){ + switch(header){ + case START: + break; + case MAN: +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("MAN: %s\n", (char *)buffer); +#endif + break; + case ST: + if(strcmp(buffer, "ssdp:all")){ + state = ABORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("REJECT: %s\n", (char *)buffer); +#endif + } + // if the search type matches our type, we should respond instead of ABORT + if(strcasecmp(buffer, _deviceType) == 0){ + _pending = true; + _process_time = 0; +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("the search type matches our type"); +#endif + state = KEY; + } + break; + case MX: + _delay = random(0, atoi(buffer)) * 1000L; + break; + } + + if(state != ABORT){ state = KEY; header = START; cursor = 0; } + } else if(c != '\r' && c != '\n'){ + if(header == START){ + if(strncmp(buffer, "MA", 2) == 0) header = MAN; + else if(strcmp(buffer, "ST") == 0) header = ST; + else if(strcmp(buffer, "MX") == 0) header = MX; + } + + if(cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + } + break; + case ABORT: + _pending = false; _delay = 0; + break; + } + } + } + if(packetBuffer) delete packetBuffer; + if(_pending && (millis() - _process_time) > _delay){ + _pending = false; _delay = 0; +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Send None"); +#endif + _send(NONE); + } else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ + _notify_time = millis(); + #ifdef DEBUG_SSDP + DEBUG_SSDP.println("Send Notify"); +#endif + _send(NOTIFY); + } else { +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Do not sent"); +#endif + } + + if (_pending) { + _server->flush(); + } + +} + +void SSDPClass::setSchemaURL(const char *url){ + strlcpy(_schemaURL, url, sizeof(_schemaURL)); +} + +void SSDPClass::setHTTPPort(uint16_t port){ + _port = port; +} + +void SSDPClass::setDeviceType(const char *deviceType){ + strlcpy(_deviceType, deviceType, sizeof(_deviceType)); +} + +void SSDPClass::setName(const char *name){ + strlcpy(_friendlyName, name, sizeof(_friendlyName)); +} + +void SSDPClass::setURL(const char *url){ + strlcpy(_presentationURL, url, sizeof(_presentationURL)); +} + +void SSDPClass::setSerialNumber(const char *serialNumber){ + strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber)); +} + +void SSDPClass::setSerialNumber(const uint32_t serialNumber){ + snprintf(_serialNumber, sizeof(uint32_t)*2+1, "%08X", serialNumber); +} + +void SSDPClass::setModelName(const char *name){ + strlcpy(_modelName, name, sizeof(_modelName)); +} + +void SSDPClass::setModelNumber(const char *num){ + strlcpy(_modelNumber, num, sizeof(_modelNumber)); +} + +void SSDPClass::setModelURL(const char *url){ + strlcpy(_modelURL, url, sizeof(_modelURL)); +} + +void SSDPClass::setManufacturer(const char *name){ + strlcpy(_manufacturer, name, sizeof(_manufacturer)); +} + +void SSDPClass::setManufacturerURL(const char *url){ + strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL)); +} + +void SSDPClass::setTTL(const uint8_t ttl){ + _ttl = ttl; +} + +void SSDPClass::_onTimerStatic(SSDPClass* self) { +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Update"); +#endif + self->_update(); +} + +void SSDPClass::_startTimer() { + _stopTimer(); + _timer= new SSDPTimer(); + ETSTimer* tm = &(_timer->timer); + const int interval = 1000; + ets_timer_disarm(tm); + ets_timer_setfn(tm, reinterpret_cast(&SSDPClass::_onTimerStatic), reinterpret_cast(this)); + ets_timer_arm(tm, interval, 1 /* repeat */); +} + +void SSDPClass::_stopTimer() { + if(!_timer){ + return; + } + ETSTimer* tm = &(_timer->timer); + ets_timer_disarm(tm); + delete _timer; + _timer = nullptr; +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP) +SSDPClass SSDP; +#endif diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.h b/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.h new file mode 100644 index 0000000..bf3b2e2 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/ESP32SSDP.h @@ -0,0 +1,128 @@ +/* +ESP32 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (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. + +*/ + +#ifndef ESP32SSDP_H +#define ESP32SSDP_H + +#include +#include +#include + +#define SSDP_UUID_SIZE 37 +#define SSDP_SCHEMA_URL_SIZE 64 +#define SSDP_DEVICE_TYPE_SIZE 64 +#define SSDP_FRIENDLY_NAME_SIZE 64 +#define SSDP_SERIAL_NUMBER_SIZE 32 +#define SSDP_PRESENTATION_URL_SIZE 128 +#define SSDP_MODEL_NAME_SIZE 64 +#define SSDP_MODEL_URL_SIZE 128 +#define SSDP_MODEL_VERSION_SIZE 32 +#define SSDP_MANUFACTURER_SIZE 64 +#define SSDP_MANUFACTURER_URL_SIZE 128 + +typedef enum { + NONE, + SEARCH, + NOTIFY +} ssdp_method_t; + + +struct SSDPTimer; + +class SSDPClass{ + public: + SSDPClass(); + ~SSDPClass(); + + bool begin(); + void end(); + + void schema(WiFiClient client); + + void setDeviceType(const String& deviceType) { setDeviceType(deviceType.c_str()); } + void setDeviceType(const char *deviceType); + void setName(const String& name) { setName(name.c_str()); } + void setName(const char *name); + void setURL(const String& url) { setURL(url.c_str()); } + void setURL(const char *url); + void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); } + void setSchemaURL(const char *url); + void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); } + void setSerialNumber(const char *serialNumber); + void setSerialNumber(const uint32_t serialNumber); + void setModelName(const String& name) { setModelName(name.c_str()); } + void setModelName(const char *name); + void setModelNumber(const String& num) { setModelNumber(num.c_str()); } + void setModelNumber(const char *num); + void setModelURL(const String& url) { setModelURL(url.c_str()); } + void setModelURL(const char *url); + void setManufacturer(const String& name) { setManufacturer(name.c_str()); } + void setManufacturer(const char *name); + void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); } + void setManufacturerURL(const char *url); + void setHTTPPort(uint16_t port); + void setTTL(uint8_t ttl); + + protected: + void _send(ssdp_method_t method); + void _update(); + void _startTimer(); + void _stopTimer(); + static void _onTimerStatic(SSDPClass* self); + + WiFiUDP *_server; + SSDPTimer* _timer; + uint16_t _port; + uint8_t _ttl; + + IPAddress _respondToAddr; + uint16_t _respondToPort; + + bool _pending; + unsigned short _delay; + unsigned long _process_time; + unsigned long _notify_time; + + char _schemaURL[SSDP_SCHEMA_URL_SIZE]; + char _uuid[SSDP_UUID_SIZE]; + char _deviceType[SSDP_DEVICE_TYPE_SIZE]; + char _friendlyName[SSDP_FRIENDLY_NAME_SIZE]; + char _serialNumber[SSDP_SERIAL_NUMBER_SIZE]; + char _presentationURL[SSDP_PRESENTATION_URL_SIZE]; + char _manufacturer[SSDP_MANUFACTURER_SIZE]; + char _manufacturerURL[SSDP_MANUFACTURER_URL_SIZE]; + char _modelName[SSDP_MODEL_NAME_SIZE]; + char _modelURL[SSDP_MODEL_URL_SIZE]; + char _modelNumber[SSDP_MODEL_VERSION_SIZE]; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP) +extern SSDPClass SSDP; +#endif + +#endif diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/README.rst b/Grbl_Esp32-master/libraries/ESP32SSDP/README.rst new file mode 100644 index 0000000..b2c92c7 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/README.rst @@ -0,0 +1,22 @@ +ESP32 Simple Service Discovery Copyright (c) 2015 Hristo Gochkov +Original (Arduino) version by Filippo Sallemi, July 23, 2014. Can be +found at: https://github.com/nomadnt/uSSDP + +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. diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/examples/SSDP/SSDP.ino b/Grbl_Esp32-master/libraries/ESP32SSDP/examples/SSDP/SSDP.ino new file mode 100644 index 0000000..46f0243 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/examples/SSDP/SSDP.ino @@ -0,0 +1,52 @@ +#include +#include +#include + +const char* ssid = "********"; +const char* password = "********"; + +WebServer HTTP(80); + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println("Starting WiFi..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + + Serial.printf("Starting HTTP...\n"); + HTTP.on("/index.html", HTTP_GET, [](){ + HTTP.send(200, "text/plain", "Hello World!"); + }); + HTTP.on("/description.xml", HTTP_GET, [](){ + SSDP.schema(HTTP.client()); + }); + HTTP.begin(); + + Serial.printf("Starting SSDP...\n"); + SSDP.setSchemaURL("description.xml"); + SSDP.setHTTPPort(80); + SSDP.setName("Philips hue clone"); + SSDP.setSerialNumber("001788102201"); + SSDP.setURL("index.html"); + SSDP.setModelName("Philips hue bridge 2012"); + SSDP.setModelNumber("929000226503"); + SSDP.setModelURL("http://www.meethue.com"); + SSDP.setManufacturer("Royal Philips Electronics"); + SSDP.setManufacturerURL("http://www.philips.com"); + SSDP.setDeviceType("upnp:rootdevice"); //to appear as root device + SSDP.begin(); + + Serial.printf("Ready!\n"); + } else { + Serial.printf("WiFi Failed\n"); + while(1) delay(100); + } +} + +void loop() { + HTTP.handleClient(); + delay(1); +} diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/keywords.txt b/Grbl_Esp32-master/libraries/ESP32SSDP/keywords.txt new file mode 100644 index 0000000..241d341 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/keywords.txt @@ -0,0 +1,53 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP8266SSDP KEYWORD1 +SSDP KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +schema KEYWORD2 +setName KEYWORD2 +setURL KEYWORD2 +setHTTPPort KEYWORD2 +setSchemaURL KEYWORD2 +setSerialNumber KEYWORD2 +setModelName KEYWORD2 +setModelNumber KEYWORD2 +setModelURL KEYWORD2 +setManufacturer KEYWORD2 +setManufacturerURL KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +SSDP_INTERVAL LITERAL1 +SSDP_PORT LITERAL1 +SSDP_METHOD_SIZE LITERAL1 +SSDP_URI_SIZE LITERAL1 +SSDP_BUFFER_SIZE LITERAL1 +SSDP_BASE_SIZE LITERAL1 +SSDP_FRIENDLY_NAME_SIZE LITERAL1 +SSDP_SERIAL_NUMBER_SIZE LITERAL1 +SSDP_PRESENTATION_URL_SIZE LITERAL1 +SSDP_MODEL_NAME_SIZE LITERAL1 +SSDP_MODEL_URL_SIZE LITERAL1 +SSDP_MODEL_VERSION_SIZE LITERAL1 +SSDP_MANUFACTURER_SIZE LITERAL1 +SSDP_MANUFACTURER_URL_SIZE LITERAL1 +SEARCH LITERAL1 +NOTIFY LITERAL1 +BASIC LITERAL1 +MANAGEABLE LITERAL1 +SOLARPROTECTIONBLIND LITERAL1 +DIGITALSECURITYCAMERA LITERAL1 +HVAC LITERAL1 +LIGHTINGCONTROL LITERAL1 diff --git a/Grbl_Esp32-master/libraries/ESP32SSDP/library.properties b/Grbl_Esp32-master/libraries/ESP32SSDP/library.properties new file mode 100644 index 0000000..e37cd25 --- /dev/null +++ b/Grbl_Esp32-master/libraries/ESP32SSDP/library.properties @@ -0,0 +1,9 @@ +name=ESP32SSPD +version=1.0 +author=Me-No-Dev +maintainer=Me-No-Dev +sentence=Simple SSDP library for ESP32 +paragraph=Only for ESP32 +category=Communication +url= +architectures=esp32 diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/.gitignore b/Grbl_Esp32-master/libraries/arduinoWebSockets/.gitignore new file mode 100644 index 0000000..44b2c85 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +/tests/webSocketServer/node_modules diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/.travis.yml b/Grbl_Esp32-master/libraries/arduinoWebSockets/.travis.yml new file mode 100644 index 0000000..14693dd --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/.travis.yml @@ -0,0 +1,40 @@ +sudo: false +language: bash +os: + - linux +env: + matrix: + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:CpuFrequency=80" IDE_VERSION=1.6.5 + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:CpuFrequency=80,FlashSize=1M0,FlashMode=qio,FlashFreq=80" IDE_VERSION=1.8.5 + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:CpuFrequency=80,Debug=Serial1" IDE_VERSION=1.6.5 + - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.6.5 + - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.5 + +script: + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + - sleep 3 + - export DISPLAY=:1.0 + - wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz + - tar xf arduino-$IDE_VERSION-linux64.tar.xz + - mv arduino-$IDE_VERSION $HOME/arduino_ide + - export PATH="$HOME/arduino_ide:$PATH" + - which arduino + - mkdir -p $HOME/Arduino/libraries + - cp -r $TRAVIS_BUILD_DIR $HOME/Arduino/libraries/arduinoWebSockets + - source $TRAVIS_BUILD_DIR/travis/common.sh + - get_core $CPU + - cd $TRAVIS_BUILD_DIR + - arduino --board $BOARD --save-prefs + - arduino --get-pref sketchbook.path + - build_sketches arduino $HOME/Arduino/libraries/arduinoWebSockets/examples/$CPU $CPU + +notifications: + email: + on_success: change + on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/1aa78fbe15080b0c2e37 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/LICENSE b/Grbl_Esp32-master/libraries/arduinoWebSockets/LICENSE new file mode 100644 index 0000000..f166cc5 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/README.md b/Grbl_Esp32-master/libraries/arduinoWebSockets/README.md new file mode 100644 index 0000000..63eef3e --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/README.md @@ -0,0 +1,98 @@ +WebSocket Server and Client for Arduino [![Build Status](https://travis-ci.org/Links2004/arduinoWebSockets.svg?branch=master)](https://travis-ci.org/Links2004/arduinoWebSockets) +=========================================== + +a WebSocket Server and Client for Arduino based on RFC6455. + + +##### Supported features of RFC6455 ##### + - text frame + - binary frame + - connection close + - ping + - pong + - continuation frame + +##### Limitations ##### + - max input length is limited to the ram size and the ```WEBSOCKETS_MAX_DATA_SIZE``` define + - max output length has no limit (the hardware is the limit) + - Client send big frames with mask 0x00000000 (on AVR all frames) + - continuation frame reassembly need to be handled in the application code + + ##### Limitations for Async ##### + - Functions called from within the context of the websocket event might not honor `yield()` and/or `delay()`. See [this issue](https://github.com/Links2004/arduinoWebSockets/issues/58#issuecomment-192376395) for more info and a potential workaround. + - wss / SSL is not possible. + +##### Supported Hardware ##### + - ESP8266 [Arduino for ESP8266](https://github.com/esp8266/Arduino/) + - ESP32 [Arduino for ESP32](https://github.com/espressif/arduino-esp32) + - ESP31B + - Particle with STM32 ARM Cortex M3 + - ATmega328 with Ethernet Shield (ATmega branch) + - ATmega328 with enc28j60 (ATmega branch) + - ATmega2560 with Ethernet Shield (ATmega branch) + - ATmega2560 with enc28j60 (ATmega branch) + +###### Note: ###### + + version 2.0 and up is not compatible with AVR/ATmega, check ATmega branch. + + Arduino for AVR not supports std namespace of c++. + +### wss / SSL ### + supported for: + - wss client on the ESP8266 + - wss / SSL is not natively supported in WebSocketsServer however it is possible to achieve secure websockets + by running the device behind an SSL proxy. See [Nginx](examples/Nginx/esp8266.ssl.reverse.proxy.conf) for a + sample Nginx server configuration file to enable this. + +### ESP Async TCP ### + +This libary can run in Async TCP mode on the ESP. + +The mode can be activated in the ```WebSockets.h``` (see WEBSOCKETS_NETWORK_TYPE define). + +[ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) libary is required. + + +### High Level Client API ### + + - `begin` : Initiate connection sequence to the websocket host. +``` +void begin(const char *host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); +void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); + ``` + - `onEvent`: Callback to handle for websocket events + + ``` + void onEvent(WebSocketClientEvent cbEvent); + ``` + + - `WebSocketClientEvent`: Handler for websocket events + ``` + void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length) + ``` +Where `WStype_t type` is defined as: + ``` + typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN, + WStype_FRAGMENT_TEXT_START, + WStype_FRAGMENT_BIN_START, + WStype_FRAGMENT, + WStype_FRAGMENT_FIN, + } WStype_t; + ``` + +### Issues ### +Submit issues to: https://github.com/Links2004/arduinoWebSockets/issues + +[![Join the chat at https://gitter.im/Links2004/arduinoWebSockets](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Links2004/arduinoWebSockets?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +### License and credits ### + +The library is licensed under [LGPLv2.1](https://github.com/Links2004/arduinoWebSockets/blob/master/LICENSE) + +[libb64](http://libb64.sourceforge.net/) written by Chris Venter. It is distributed under Public Domain see [LICENSE](https://github.com/Links2004/arduinoWebSockets/blob/master/src/libb64/LICENSE). diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf new file mode 100644 index 0000000..ec5aa89 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf @@ -0,0 +1,83 @@ +# ESP8266 nginx SSL reverse proxy configuration file (tested and working on nginx v1.10.0) + +# proxy cache location +proxy_cache_path /opt/etc/nginx/cache levels=1:2 keys_zone=ESP8266_cache:10m max_size=10g inactive=5m use_temp_path=off; + +# webserver proxy +server { + + # general server parameters + listen 50080; + server_name myDomain.net; + access_log /opt/var/log/nginx/myDomain.net.access.log; + + # SSL configuration + ssl on; + ssl_certificate /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem; + ssl_certificate_key /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem; + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + location / { + + # proxy caching configuration + proxy_cache ESP8266_cache; + proxy_cache_revalidate on; + proxy_cache_min_uses 1; + proxy_cache_use_stale off; + proxy_cache_lock on; + # proxy_cache_bypass $http_cache_control; + # include the sessionId cookie value as part of the cache key - keeps the cache per user + # proxy_cache_key $proxy_host$request_uri$cookie_sessionId; + + # header pass through configuration + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # ESP8266 custom headers which identify to the device that it's running through an SSL proxy + proxy_set_header X-SSL On; + proxy_set_header X-SSL-WebserverPort 50080; + proxy_set_header X-SSL-WebsocketPort 50081; + + # extra debug headers + add_header X-Proxy-Cache $upstream_cache_status; + add_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # actual proxying configuration + proxy_ssl_session_reuse on; + # target the IP address of the device with proxy_pass + proxy_pass http://192.168.0.20; + proxy_read_timeout 90; + } + } + +# websocket proxy +server { + + # general server parameters + listen 50081; + server_name myDomain.net; + access_log /opt/var/log/nginx/myDomain.net.wss.access.log; + + # SSL configuration + ssl on; + ssl_certificate /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem; + ssl_certificate_key /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem; + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + location / { + + # websocket upgrade tunnel configuration + proxy_pass http://192.168.0.20:81; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_read_timeout 86400; + } + } diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino new file mode 100644 index 0000000..9d49d14 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino @@ -0,0 +1,84 @@ +/* + * WebSocketClientAVR.ino + * + * Created on: 10.12.2015 + * + */ + +#include + +#include +#include + +#include + + + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +// Set the static IP address to use if the DHCP fails to assign +IPAddress ip(192, 168, 0, 177); + +WebSocketsClient webSocket; + + + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + Serial.println("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + Serial.print("[WSc] Connected to url: "); + Serial.println((char *)payload); + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + Serial.print("[WSc] get text: "); + Serial.println((char *)payload); + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + Serial.print("[WSc] get binary length: "); + Serial.println(length); + // hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() +{ + // Open serial communications and wait for port to open: + Serial.begin(115200); + while (!Serial) {} + + // start the Ethernet connection: + if (Ethernet.begin(mac) == 0) { + Serial.println("Failed to configure Ethernet using DHCP"); + // no point in carrying on, so do nothing forevermore: + // try to congifure using IP address instead of DHCP: + Ethernet.begin(mac, ip); + } + + webSocket.begin("192.168.0.123", 8011); + webSocket.onEvent(webSocketEvent); + +} + + +void loop() +{ + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino new file mode 100644 index 0000000..5e5ead4 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino @@ -0,0 +1,110 @@ +/* + * WebSocketClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include +#include + +#include + + +WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("192.168.0.123", 81, "/"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // use HTTP Basic Authorization this is optional remove if not needed + webSocket.setAuthorization("user", "Password"); + + // try ever 5000 again if connection has failed + webSocket.setReconnectInterval(5000); + +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 0000000..9d72242 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino @@ -0,0 +1,106 @@ +/* + * WebSocketClientSSL.ino + * + * Created on: 10.12.2015 + * + * note SSL is only possible with the ESP8266 + * + */ + +#include + +#include +#include +#include + +#include + + +WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.beginSSL("192.168.0.123", 81); + webSocket.onEvent(webSocketEvent); + +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino new file mode 100644 index 0000000..3e0d4f5 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino @@ -0,0 +1,104 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include + +#include + +WiFiMulti WiFiMulti; +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino new file mode 100644 index 0000000..b990c13 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino @@ -0,0 +1,92 @@ +/* + * WebSocketClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("192.168.0.123", 81, "/"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // use HTTP Basic Authorization this is optional remove if not needed + webSocket.setAuthorization("user", "Password"); + + // try ever 5000 again if connection has failed + webSocket.setReconnectInterval(5000); + +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 0000000..d45060e --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino @@ -0,0 +1,88 @@ +/* + * WebSocketClientSSL.ino + * + * Created on: 10.12.2015 + * + * note SSL is only possible with the ESP8266 + * + */ + +#include + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + + +#define USE_SERIAL Serial1 + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.beginSSL("192.168.0.123", 81); + webSocket.onEvent(webSocketEvent); + +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino new file mode 100644 index 0000000..40e343e --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino @@ -0,0 +1,113 @@ +/* + * WebSocketClientSocketIO.ino + * + * Created on: 06.06.2016 + * + */ + +#include + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + + +#define USE_SERIAL Serial1 + +#define MESSAGE_INTERVAL 30000 +#define HEARTBEAT_INTERVAL 25000 + +uint64_t messageTimestamp = 0; +uint64_t heartbeatTimestamp = 0; +bool isConnected = false; + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + isConnected = false; + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + isConnected = true; + + // send message to server when Connected + // socket.io upgrade confirmation message (required) + webSocket.sendTXT("5"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.beginSocketIO("192.168.0.123", 81); + //webSocket.setAuthorization("user", "Password"); // HTTP Basic Authorization + webSocket.onEvent(webSocketEvent); + +} + +void loop() { + webSocket.loop(); + + if(isConnected) { + + uint64_t now = millis(); + + if(now - messageTimestamp > MESSAGE_INTERVAL) { + messageTimestamp = now; + // example socket.io message with type "messageType" and JSON payload + webSocket.sendTXT("42[\"messageType\",{\"greeting\":\"hello\"}]"); + } + if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) { + heartbeatTimestamp = now; + // socket.io heartbeat message + webSocket.sendTXT("2"); + } + } +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino new file mode 100644 index 0000000..a0eb011 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino @@ -0,0 +1,149 @@ +/* + WebSocketClientStomp.ino + + Example for connecting and maintining a connection with a STOMP websocket connection. + In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html). + + Created on: 25.09.2017 + Author: Martin Becker , Contact: becker@informatik.uni-wuerzburg.de +*/ + +// PRE + +#define USE_SERIAL Serial + + +// LIBRARIES + +#include +#include + +#include +#include + + +// SETTINGS + +const char* wlan_ssid = "yourssid"; +const char* wlan_password = "somepassword"; + +const char* ws_host = "the.host.net"; +const int ws_port = 80; + +// URL for STOMP endpoint. +// For the default config of Spring's STOMP support, the default URL is "/socketentry/websocket". +const char* stompUrl = "/socketentry/websocket"; // don't forget the leading "/" !!! + + +// VARIABLES + +WebSocketsClient webSocket; + + +// FUNCTIONS + +/** + * STOMP messages need to be NULL-terminated (i.e., \0 or \u0000). + * However, when we send a String or a char[] array without specifying + * a length, the size of the message payload is derived by strlen() internally, + * thus dropping any NULL values appended to the "msg"-String. + * + * To solve this, we first convert the String to a NULL terminated char[] array + * via "c_str" and set the length of the payload to include the NULL value. + */ +void sendMessage(String & msg) { + webSocket.sendTXT(msg.c_str(), msg.length() + 1); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch (type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + String msg = "CONNECT\r\naccept-version:1.1,1.0\r\nheart-beat:10000,10000\r\n\r\n"; + sendMessage(msg); + } + break; + case WStype_TEXT: + { + // ##################### + // handle STOMP protocol + // ##################### + + String text = (char*) payload; + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + if (text.startsWith("CONNECTED")) { + + // subscribe to some channels + + String msg = "SUBSCRIBE\nid:sub-0\ndestination:/user/queue/messages\n\n"; + sendMessage(msg); + delay(1000); + + // and send a message + + msg = "SEND\ndestination:/app/message\n\n{\"user\":\"esp\",\"message\":\"Hello!\"}"; + sendMessage(msg); + delay(1000); + + } else { + + // do something with messages + + } + + break; + } + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + + // setup serial + + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + + + // connect to WiFi + + USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ..."); + WiFi.mode(WIFI_STA); + WiFi.begin(wlan_ssid, wlan_password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + USE_SERIAL.print("."); + } + USE_SERIAL.println(" success."); + USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP()); + + + // connect to websocket + webSocket.begin(ws_host, ws_port, stompUrl); + webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config + // webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino new file mode 100644 index 0000000..cb0c45b --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino @@ -0,0 +1,150 @@ +/* + WebSocketClientStompOverSockJs.ino + + Example for connecting and maintining a connection with a SockJS+STOMP websocket connection. + In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html). + + Created on: 18.07.2017 + Author: Martin Becker , Contact: becker@informatik.uni-wuerzburg.de +*/ + +// PRE + +#define USE_SERIAL Serial + + +// LIBRARIES + +#include +#include + +#include +#include + + +// SETTINGS + +const char* wlan_ssid = "yourssid"; +const char* wlan_password = "somepassword"; + +const char* ws_host = "the.host.net"; +const int ws_port = 80; + +// base URL for SockJS (websocket) connection +// The complete URL will look something like this(cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36): +// ws://://<3digits>//websocket +// For the default config of Spring's SockJS/STOMP support, the default base URL is "/socketentry/". +const char* ws_baseurl = "/socketentry/"; // don't forget leading and trailing "/" !!! + + +// VARIABLES + +WebSocketsClient webSocket; + + +// FUNCTIONS + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch (type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + } + break; + case WStype_TEXT: + { + // ##################### + // handle SockJs+STOMP protocol + // ##################### + + String text = (char*) payload; + + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + if (payload[0] == 'h') { + + USE_SERIAL.println("Heartbeat!"); + + } else if (payload[0] == 'o') { + + // on open connection + char *msg = "[\"CONNECT\\naccept-version:1.1,1.0\\nheart-beat:10000,10000\\n\\n\\u0000\"]"; + webSocket.sendTXT(msg); + + } else if (text.startsWith("a[\"CONNECTED")) { + + // subscribe to some channels + + char *msg = "[\"SUBSCRIBE\\nid:sub-0\\ndestination:/user/queue/messages\\n\\n\\u0000\"]"; + webSocket.sendTXT(msg); + delay(1000); + + // and send a message + + msg = "[\"SEND\\ndestination:/app/message\\n\\n{\\\"user\\\":\\\"esp\\\",\\\"message\\\":\\\"Hello!\\\"}\\u0000\"]"; + webSocket.sendTXT(msg); + delay(1000); + } + + break; + } + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + + // setup serial + + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + + + // connect to WiFi + + USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ..."); + WiFi.mode(WIFI_STA); + WiFi.begin(wlan_ssid, wlan_password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + USE_SERIAL.print("."); + } + USE_SERIAL.println(" success."); + USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP()); + + + // ##################### + // create socket url according to SockJS protocol (cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36) + // ##################### + String socketUrl = ws_baseurl; + socketUrl += random(0, 999); + socketUrl += "/"; + socketUrl += random(0, 999999); // should be a random string, but this works (see ) + socketUrl += "/websocket"; + + // connect to websocket + webSocket.begin(ws_host, ws_port, socketUrl); + webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config + // webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino new file mode 100644 index 0000000..1ac3002 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino @@ -0,0 +1,86 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} + diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino new file mode 100644 index 0000000..5fed1a9 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino @@ -0,0 +1,132 @@ +/* + * WebSocketServerAllFunctionsDemo.ino + * + * Created on: 10.05.2018 + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define LED_RED 15 +#define LED_GREEN 12 +#define LED_BLUE 13 + +#define USE_SERIAL Serial + +ESP8266WiFiMulti WiFiMulti; + +ESP8266WebServer server(80); +WebSocketsServer webSocket = WebSocketsServer(81); + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + if(payload[0] == '#') { + // we get RGB data + + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); + + analogWrite(LED_RED, ((rgb >> 16) & 0xFF)); + analogWrite(LED_GREEN, ((rgb >> 8) & 0xFF)); + analogWrite(LED_BLUE, ((rgb >> 0) & 0xFF)); + } + + break; + } + +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + digitalWrite(LED_RED, 1); + digitalWrite(LED_GREEN, 1); + digitalWrite(LED_BLUE, 1); + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // start webSocket server + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + if(MDNS.begin("esp8266")) { + USE_SERIAL.println("MDNS responder started"); + } + + // handle index + server.on("/", []() { + // send index.html + server.send(200, "text/html", "LED Control:

R:
G:
B:
"); + }); + + server.begin(); + + // Add service to MDNS + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 81); + + digitalWrite(LED_RED, 0); + digitalWrite(LED_GREEN, 0); + digitalWrite(LED_BLUE, 0); + +} + +unsigned long last_10sec = 0; +unsigned int counter = 0; + +void loop() { + unsigned long t = millis(); + webSocket.loop(); + server.handleClient(); + + if((t - last_10sec) > 10 * 1000) { + counter++; + bool ping = (counter % 2); + int i = webSocket.connectedClients(ping); + USE_SERIAL.printf("%d Connected websocket clients ping: %d\n", i, ping); + last_10sec = millis(); + } +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino new file mode 100644 index 0000000..84c9775 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino @@ -0,0 +1,94 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial + +String fragmentBuffer = ""; + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + break; + + // Fragmentation / continuation opcode handling + // case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT_TEXT_START: + fragmentBuffer = (char*)payload; + USE_SERIAL.printf("[%u] get start start of Textfragment: %s\n", num, payload); + break; + case WStype_FRAGMENT: + fragmentBuffer += (char*)payload; + USE_SERIAL.printf("[%u] get Textfragment : %s\n", num, payload); + break; + case WStype_FRAGMENT_FIN: + fragmentBuffer += (char*)payload; + USE_SERIAL.printf("[%u] get end of Textfragment: %s\n", num, payload); + USE_SERIAL.printf("[%u] full frame: %s\n", num, fragmentBuffer.c_str()); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} + diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino new file mode 100644 index 0000000..8bc646c --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino @@ -0,0 +1,86 @@ +/* + * WebSocketServerHttpHeaderValidation.ino + * + * Created on: 08.06.2016 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +const unsigned long int validSessionId = 12345; //some arbitrary value to act as a valid sessionId + +/* + * Returns a bool value as an indicator to describe whether a user is allowed to initiate a websocket upgrade + * based on the value of a cookie. This function expects the rawCookieHeaderValue to look like this "sessionId=|" + */ +bool isCookieValid(String rawCookieHeaderValue) { + + if (rawCookieHeaderValue.indexOf("sessionId") != -1) { + String sessionIdStr = rawCookieHeaderValue.substring(rawCookieHeaderValue.indexOf("sessionId=") + 10, rawCookieHeaderValue.indexOf("|")); + unsigned long int sessionId = strtoul(sessionIdStr.c_str(), NULL, 10); + return sessionId == validSessionId; + } + return false; +} + +/* + * The WebSocketServerHttpHeaderValFunc delegate passed to webSocket.onValidateHttpHeader + */ +bool validateHttpHeader(String headerName, String headerValue) { + + //assume a true response for any headers not handled by this validator + bool valid = true; + + if(headerName.equalsIgnoreCase("Cookie")) { + //if the header passed is the Cookie header, validate it according to the rules in 'isCookieValid' function + valid = isCookieValid(headerValue); + } + + return valid; +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + //connecting clients must supply a valid session cookie at websocket upgrade handshake negotiation time + const char * headerkeys[] = { "Cookie" }; + size_t headerKeyCount = sizeof(headerkeys) / sizeof(char*); + webSocket.onValidateHttpHeader(validateHttpHeader, headerkeys, headerKeyCount); + webSocket.begin(); +} + +void loop() { + webSocket.loop(); +} + diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino new file mode 100644 index 0000000..8f32e75 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino @@ -0,0 +1,121 @@ +/* + * WebSocketServer_LEDcontrol.ino + * + * Created on: 26.11.2015 + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define LED_RED 15 +#define LED_GREEN 12 +#define LED_BLUE 13 + +#define USE_SERIAL Serial + + +ESP8266WiFiMulti WiFiMulti; + +ESP8266WebServer server(80); +WebSocketsServer webSocket = WebSocketsServer(81); + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + if(payload[0] == '#') { + // we get RGB data + + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); + + analogWrite(LED_RED, ((rgb >> 16) & 0xFF)); + analogWrite(LED_GREEN, ((rgb >> 8) & 0xFF)); + analogWrite(LED_BLUE, ((rgb >> 0) & 0xFF)); + } + + break; + } + +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + digitalWrite(LED_RED, 1); + digitalWrite(LED_GREEN, 1); + digitalWrite(LED_BLUE, 1); + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // start webSocket server + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + if(MDNS.begin("esp8266")) { + USE_SERIAL.println("MDNS responder started"); + } + + // handle index + server.on("/", []() { + // send index.html + server.send(200, "text/html", "LED Control:

R:
G:
B:
"); + }); + + server.begin(); + + // Add service to MDNS + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 81); + + digitalWrite(LED_RED, 0); + digitalWrite(LED_GREEN, 0); + digitalWrite(LED_BLUE, 0); + +} + +void loop() { + webSocket.loop(); + server.handleClient(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/particle/ParticleWebSocketClient/application.cpp b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/particle/ParticleWebSocketClient/application.cpp new file mode 100644 index 0000000..461228f --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/examples/particle/ParticleWebSocketClient/application.cpp @@ -0,0 +1,46 @@ +/* To compile using make CLI, create a folder under \firmware\user\applications and copy application.cpp there. +* Then, copy src files under particleWebSocket folder. +*/ + +#include "application.h" +#include "particleWebSocket/WebSocketsClient.h" + +WebSocketsClient webSocket; + +void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) +{ + switch (type) + { + case WStype_DISCONNECTED: + Serial.printlnf("[WSc] Disconnected!"); + break; + case WStype_CONNECTED: + Serial.printlnf("[WSc] Connected to URL: %s", payload); + webSocket.sendTXT("Connected\r\n"); + break; + case WStype_TEXT: + Serial.printlnf("[WSc] get text: %s", payload); + break; + case WStype_BIN: + Serial.printlnf("[WSc] get binary length: %u", length); + break; + } +} + +void setup() +{ + Serial.begin(9600); + + WiFi.setCredentials("[SSID]", "[PASSWORD]", WPA2, WLAN_CIPHER_AES_TKIP); + WiFi.connect(); + + webSocket.begin("192.168.1.153", 85, "/ClientService/?variable=Test1212"); + webSocket.onEvent(webSocketEvent); +} + +void loop() +{ + webSocket.sendTXT("Hello world!"); + delay(500); + webSocket.loop(); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/library.json b/Grbl_Esp32-master/libraries/arduinoWebSockets/library.json new file mode 100644 index 0000000..f1a5892 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/library.json @@ -0,0 +1,25 @@ +{ + "name": "WebSockets", + "description": "WebSocket Server and Client for Arduino based on RFC6455", + "keywords": "wifi, http, web, server, client, websocket", + "authors": [ + { + "name": "Markus Sattler", + "url": "https://github.com/Links2004", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/Links2004/arduinoWebSockets.git" + }, + "version": "2.1.2", + "license": "LGPL-2.1", + "export": { + "exclude": [ + "tests" + ] + }, + "frameworks": "arduino", + "platforms": "atmelavr, espressif8266, espressif32" +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/library.properties b/Grbl_Esp32-master/libraries/arduinoWebSockets/library.properties new file mode 100644 index 0000000..00d0945 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/library.properties @@ -0,0 +1,9 @@ +name=WebSockets +version=2.1.2 +author=Markus Sattler +maintainer=Markus Sattler +sentence=WebSockets for Arduino (Server + Client) +paragraph=use 2.x.x for ESP and 1.3 for AVR +category=Communication +url=https://github.com/Links2004/arduinoWebSockets +architectures=* diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.cpp b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.cpp new file mode 100644 index 0000000..727e472 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.cpp @@ -0,0 +1,655 @@ +/** + * @file WebSockets.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" + +#ifdef ESP8266 +#include +#endif + +extern "C" { +#ifdef CORE_HAS_LIBB64 +#include +#else +#include "libb64/cencode_inc.h" +#endif +} + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include +#else + +extern "C" { +#include "libsha1/libsha1.h" +} + +#endif + + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param code uint16_t see RFC + * @param reason ptr to the disconnect reason message + * @param reasonLen length of the disconnect reason message + */ +void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); + if(client->status == WSC_CONNECTED && code) { + if(reason) { + sendFrame(client, WSop_close, (uint8_t *) reason, reasonLen); + } else { + uint8_t buffer[2]; + buffer[0] = ((code >> 8) & 0xFF); + buffer[1] = (code & 0xFF); + sendFrame(client, WSop_close, &buffer[0], 2); + } + } + clientDisconnect(client); +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * ptr to the payload + * @param length size_t length of the payload + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) + * @return true if ok + */ +bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool mask, bool fin, bool headerToPayload) { + + if(client->tcp && !client->tcp->connected()) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); + return false; + } + + if(client->status != WSC_CONNECTED) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); + return false; + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, mask, length, headerToPayload); + + if(opcode == WSop_text) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); + } + + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize; + uint8_t * headerPtr; + uint8_t * payloadPtr = payload; + bool useInternBuffer = false; + bool ret = true; + + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + +#ifdef WEBSOCKETS_USE_BIG_MEM + // only for ESP since AVR has less HEAP + // try to send data in one TCP package (only if some free Heap is there) + if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); + uint8_t * dataPtr = (uint8_t *) malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); + if(dataPtr) { + memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); + headerToPayload = true; + useInternBuffer = true; + payloadPtr = dataPtr; + } + } +#endif + + // set Header Pointer + if(headerToPayload) { + // calculate offset in payload + headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); + } else { + headerPtr = &buffer[0]; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; + headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } + + if(mask) { + if(useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random(0xFF); + *headerPtr = maskKey[x]; + headerPtr++; + } + + uint8_t * dataMaskPtr; + + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); + } else { + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); + } + + } else { + *headerPtr = maskKey[0]; + headerPtr++; + *headerPtr = maskKey[1]; + headerPtr++; + *headerPtr = maskKey[2]; + headerPtr++; + *headerPtr = maskKey[3]; + headerPtr++; + } + } + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + if(headerToPayload) { + // header has be added to payload + // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings + // offset in payload is calculatetd 14 - headerSize + if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { + ret = false; + } + } else { + // send header + if(write(client, &buffer[0], headerSize) != headerSize) { + ret = false; + } + + if(payloadPtr && length > 0) { + // send payload + if(write(client, &payloadPtr[0], length) != length) { + ret = false; + } + } + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); + +#ifdef WEBSOCKETS_USE_BIG_MEM + if(useInternBuffer && payloadPtr) { + free(payloadPtr); + } +#endif + + return ret; +} + +/** + * callen when HTTP header is done + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::headerDone(WSclient_t * client) { + client->status = WSC_CONNECTED; + client->cWsRXsize = 0; + DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; + handleWebsocket(client); +#endif +} + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + if(client->cWsRXsize == 0) { + handleWebsocketCb(client); + } +} + +/** + * wait for + * @param client + * @param size + */ +bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + if(size > WEBSOCKETS_MAX_HEADER_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); + return false; + } + + if(client->cWsRXsize >= size) { + return true; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); + readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); + if(ok) { + client->cWsRXsize = size; + server->handleWebsocketCb(client); + } else { + DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); + client->cWsRXsize = 0; + // timeout or error + server->clientDisconnect(client, 1002); + } + }, this, size, std::placeholders::_1, std::placeholders::_2)); + return false; +} + +void WebSockets::handleWebsocketCb(WSclient_t * client) { + + if(!client->tcp || !client->tcp->connected()) { + return; + } + + uint8_t * buffer = client->cWsHeader; + + WSMessageHeader_t * header = &client->cWsHeaderDecode; + uint8_t * payload = NULL; + + uint8_t headerLen = 2; + + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + // split first 2 bytes in the data + header->fin = ((*buffer >> 7) & 0x01); + header->rsv1 = ((*buffer >> 6) & 0x01); + header->rsv2 = ((*buffer >> 5) & 0x01); + header->rsv3 = ((*buffer >> 4) & 0x01); + header->opCode = (WSopcode_t) (*buffer & 0x0F); + buffer++; + + header->mask = ((*buffer >> 7) & 0x01); + header->payloadLen = (WSopcode_t) (*buffer & 0x7F); + buffer++; + + if(header->payloadLen == 126) { + headerLen += 2; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->payloadLen = buffer[0] << 8 | buffer[1]; + buffer += 2; + } else if(header->payloadLen == 127) { + headerLen += 8; + // read 64bit integer as length + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really too big! + header->payloadLen = 0xFFFFFFFF; + } else { + header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + buffer += 8; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); + + if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); + clientDisconnect(client, 1009); + return; + } + + if(header->mask) { + headerLen += 4; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->maskKey = buffer; + buffer += 4; + } + + if(header->payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *) malloc(header->payloadLen + 1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); + clientDisconnect(client, 1011); + return; + } + readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + } else { + handleWebsocketPayloadCb(client, true, NULL); + } +} + +void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { + + WSMessageHeader_t * header = &client->cWsHeaderDecode; + if(ok) { + if(header->payloadLen > 0) { + payload[header->payloadLen] = 0x00; + + if(header->mask) { + //decode XOR + for(size_t i = 0; i < header->payloadLen; i++) { + payload[i] = (payload[i] ^ header->maskKey[i % 4]); + } + } + } + + switch(header->opCode) { + case WSop_text: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); + // no break here! + case WSop_binary: + case WSop_continuation: + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_ping: + // send pong back + sendFrame(client, WSop_pong, payload, header->payloadLen, true); + break; + case WSop_pong: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char*)payload : ""); + break; + case WSop_close: { + #ifndef NODEBUG_WEBSOCKETS + uint16_t reasonCode = 1000; + if(header->payloadLen >= 2) { + reasonCode = payload[0] << 8 | payload[1]; + } + #endif + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d", client->num, reasonCode); + if(header->payloadLen > 2) { + DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); + } else { + DEBUG_WEBSOCKETS("\n"); + } + clientDisconnect(client, 1000); + } + break; + default: + clientDisconnect(client, 1002); + break; + } + + if(payload) { + free(payload); + } + + // reset input + client->cWsRXsize = 0; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + //register callback for next message + handleWebsocketWaitFor(client, 2); +#endif + + } else { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); + free(payload); + clientDisconnect(client, 1002); + } +} + +/** + * generate the key for Sec-WebSocket-Accept + * @param clientKey String + * @return String Accept Key + */ +String WebSockets::acceptKey(String & clientKey) { + uint8_t sha1HashBin[20] = { 0 }; +#ifdef ESP8266 + sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); +#elif defined(ESP32) + String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + esp_sha(SHA1, (unsigned char*)data.c_str(), data.length(), &sha1HashBin[0]); +#else + clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char*)clientKey.c_str(), clientKey.length()); + SHA1Final(&sha1HashBin[0], &ctx); +#endif + + String key = base64_encode(sha1HashBin, 20); + key.trim(); + + return key; +} + +/** + * base64_encode + * @param data uint8_t * + * @param length size_t + * @return base64 encoded String + */ +String WebSockets::base64_encode(uint8_t * data, size_t length) { + size_t size = ((length * 1.6f) + 1); + char * buffer = (char *) malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + + String base64 = String(buffer); + free(buffer); + return base64; + } + return String("-FAIL-"); +} + +/** + * read x byte from tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return true if ok + */ +bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { + if(cb) { + cb(client, ok); + } + }, client, std::placeholders::_1, cb)); + +#else + unsigned long t = millis(); + size_t len; + DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[readCb] not connected!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->available()) { +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + continue; + } + + len = client->tcp->read((uint8_t*) out, n); + if(len) { + t = millis(); + out += len; + n -= len; + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } else { + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + if(cb) { + cb(client, true); + } +#endif + return true; +} + +/** + * write x byte to tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return bytes send + */ +size_t WebSockets::write(WSclient_t * client, uint8_t *out, size_t n) { + if(out == NULL) return 0; + if(client == NULL) return 0; + unsigned long t = millis(); + size_t len = 0; + size_t total = 0; + DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[write] tcp is null!\n"); + break; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[write] not connected!\n"); + break; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t)); + break; + } + + len = client->tcp->write((const uint8_t*)out, n); + if(len) { + t = millis(); + out += len; + n -= len; + total += len; + //DEBUG_WEBSOCKETS("write %d left %d!\n", len, n); + } else { + //DEBUG_WEBSOCKETS("write %d failed left %d!\n", len, n); + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + return total; +} + +size_t WebSockets::write(WSclient_t * client, const char *out) { + if(client == NULL) return 0; + if(out == NULL) return 0; + return write(client, (uint8_t*)out, strlen(out)); +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.h new file mode 100644 index 0000000..dee6397 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSockets.h @@ -0,0 +1,311 @@ +/** + * @file WebSockets.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETS_H_ +#define WEBSOCKETS_H_ + +#ifdef STM32_DEVICE +#include +#define bit(b) (1UL << (b)) // Taken directly from Arduino.h +#else +#include +#include +#endif + +#ifdef ARDUINO_ARCH_AVR +#error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++. +#error Use Version 1.x.x. (ATmega branch) +#else +#include +#endif + + +#ifndef NODEBUG_WEBSOCKETS +#ifdef DEBUG_ESP_PORT +#define DEBUG_WEBSOCKETS(...) DEBUG_ESP_PORT.printf( __VA_ARGS__ ) +#else +//#define DEBUG_WEBSOCKETS(...) os_printf( __VA_ARGS__ ) +#endif +#endif + + +#ifndef DEBUG_WEBSOCKETS +#define DEBUG_WEBSOCKETS(...) +#define NODEBUG_WEBSOCKETS +#endif + +#if defined(ESP8266) || defined(ESP32) + +#define WEBSOCKETS_MAX_DATA_SIZE (15*1024) +#define WEBSOCKETS_USE_BIG_MEM +#define GET_FREE_HEAP ESP.getFreeHeap() +// moves all Header strings to Flash (~300 Byte) +//#define WEBSOCKETS_SAVE_RAM + +#elif defined(STM32_DEVICE) + +#define WEBSOCKETS_MAX_DATA_SIZE (15*1024) +#define WEBSOCKETS_USE_BIG_MEM +#define GET_FREE_HEAP System.freeMemory() + +#else + +//atmega328p has only 2KB ram! +#define WEBSOCKETS_MAX_DATA_SIZE (1024) +// moves all Header strings to Flash +#define WEBSOCKETS_SAVE_RAM + +#endif + + +#define WEBSOCKETS_TCP_TIMEOUT (2000) + +#define NETWORK_ESP8266_ASYNC (0) +#define NETWORK_ESP8266 (1) +#define NETWORK_W5100 (2) +#define NETWORK_ENC28J60 (3) +#define NETWORK_ESP32 (4) + +// max size of the WS Message Header +#define WEBSOCKETS_MAX_HEADER_SIZE (14) + +#if !defined(WEBSOCKETS_NETWORK_TYPE) +// select Network type based +#if defined(ESP8266) || defined(ESP31B) +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266 +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266_ASYNC +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100 + +#elif defined(ESP32) +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP32 + +#else +#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100 + +#endif +#endif + +// Includes and defined based on Network Type +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + +// Note: +// No SSL/WSS support for client in Async mode +// TLS lib need a sync interface! + + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#include +#elif defined(ESP31B) +#include +#else +#error "network type ESP8266 ASYNC only possible on the ESP mcu!" +#endif + +#include +#include +#define WEBSOCKETS_NETWORK_CLASS AsyncTCPbuffer +#define WEBSOCKETS_NETWORK_SERVER_CLASS AsyncServer + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + +#if !defined(ESP8266) && !defined(ESP31B) +#error "network type ESP8266 only possible on the ESP mcu!" +#endif + +#ifdef ESP8266 +#include +#else +#include +#endif +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100) + +#ifdef STM32_DEVICE +#define WEBSOCKETS_NETWORK_CLASS TCPClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS TCPServer +#else +#include +#include +#define WEBSOCKETS_NETWORK_CLASS EthernetClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer +#endif + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ENC28J60) + +#include +#define WEBSOCKETS_NETWORK_CLASS UIPClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS UIPServer + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + +#include +#include +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer + +#else +#error "no network type selected!" +#endif + +// moves all Header strings to Flash (~300 Byte) +#ifdef WEBSOCKETS_SAVE_RAM +#define WEBSOCKETS_STRING(var) F(var) +#else +#define WEBSOCKETS_STRING(var) var +#endif + +typedef enum { + WSC_NOT_CONNECTED, + WSC_HEADER, + WSC_CONNECTED +} WSclientsStatus_t; + +typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN, + WStype_FRAGMENT_TEXT_START, + WStype_FRAGMENT_BIN_START, + WStype_FRAGMENT, + WStype_FRAGMENT_FIN, +} WStype_t; + +typedef enum { + WSop_continuation = 0x00, ///< %x0 denotes a continuation frame + WSop_text = 0x01, ///< %x1 denotes a text frame + WSop_binary = 0x02, ///< %x2 denotes a binary frame + ///< %x3-7 are reserved for further non-control frames + WSop_close = 0x08, ///< %x8 denotes a connection close + WSop_ping = 0x09, ///< %x9 denotes a ping + WSop_pong = 0x0A ///< %xA denotes a pong + ///< %xB-F are reserved for further control frames +} WSopcode_t; + +typedef struct { + + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + + WSopcode_t opCode; + bool mask; + + size_t payloadLen; + + uint8_t * maskKey; +} WSMessageHeader_t; + +typedef struct { + uint8_t num; ///< connection number + + WSclientsStatus_t status; + + WEBSOCKETS_NETWORK_CLASS * tcp; + + bool isSocketIO; ///< client for socket.io server + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + bool isSSL; ///< run in ssl mode + WiFiClientSecure * ssl; +#endif + + String cUrl; ///< http url + uint16_t cCode; ///< http code + + bool cIsUpgrade; ///< Connection == Upgrade + bool cIsWebsocket; ///< Upgrade == websocket + + String cSessionId; ///< client Set-Cookie (session id) + String cKey; ///< client Sec-WebSocket-Key + String cAccept; ///< client Sec-WebSocket-Accept + String cProtocol; ///< client Sec-WebSocket-Protocol + String cExtensions; ///< client Sec-WebSocket-Extensions + uint16_t cVersion; ///< client Sec-WebSocket-Version + + uint8_t cWsRXsize; ///< State of the RX + uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE]; ///< RX WS Message buffer + WSMessageHeader_t cWsHeaderDecode; + + String base64Authorization; ///< Base64 encoded Auth request + String plainAuthorization; ///< Base64 encoded Auth request + + String extraHeaders; + + bool cHttpHeadersValid; ///< non-websocket http header validity indicator + size_t cMandatoryHeadersCount; ///< non-websocket mandatory http headers present count + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + String cHttpLine; ///< HTTP header lines +#endif + +} WSclient_t; + + + +class WebSockets { + protected: +#ifdef __AVR__ + typedef void (*WSreadWaitCb)(WSclient_t * client, bool ok); +#else + typedef std::function WSreadWaitCb; +#endif + + virtual void clientDisconnect(WSclient_t * client) = 0; + virtual bool clientIsConnected(WSclient_t * client) = 0; + + virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0; + + void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool mask = false, bool fin = true, bool headerToPayload = false); + + void headerDone(WSclient_t * client); + + void handleWebsocket(WSclient_t * client); + + bool handleWebsocketWaitFor(WSclient_t * client, size_t size); + void handleWebsocketCb(WSclient_t * client); + void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload); + + String acceptKey(String & clientKey); + String base64_encode(uint8_t * data, size_t length); + + bool readCb(WSclient_t * client, uint8_t *out, size_t n, WSreadWaitCb cb); + virtual size_t write(WSclient_t * client, uint8_t *out, size_t n); + size_t write(WSclient_t * client, const char *out); + + +}; + +#ifndef UNUSED +#define UNUSED(var) (void)(var) +#endif +#endif /* WEBSOCKETS_H_ */ diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.cpp b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.cpp new file mode 100644 index 0000000..f98822a --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.cpp @@ -0,0 +1,762 @@ +/** + * @file WebSocketsClient.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" + +WebSocketsClient::WebSocketsClient() { + _cbEvent = NULL; + _client.num = 0; + _client.extraHeaders = WEBSOCKETS_STRING("Origin: file://"); +} + +WebSocketsClient::~WebSocketsClient() { + disconnect(); +} + +/** + * calles to init the Websockets server + */ +void WebSocketsClient::begin(const char *host, uint16_t port, const char * url, const char * protocol) { + _host = host; + _port = port; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + _fingerprint = ""; +#endif + + _client.num = 0; + _client.status = WSC_NOT_CONNECTED; + _client.tcp = NULL; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + _client.isSSL = false; + _client.ssl = NULL; +#endif + _client.cUrl = url; + _client.cCode = 0; + _client.cIsUpgrade = false; + _client.cIsWebsocket = true; + _client.cKey = ""; + _client.cAccept = ""; + _client.cProtocol = protocol; + _client.cExtensions = ""; + _client.cVersion = 0; + _client.base64Authorization = ""; + _client.plainAuthorization = ""; + _client.isSocketIO = false; + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#else + // todo find better seed + randomSeed(millis()); +#endif +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + asyncConnect(); +#endif + + _lastConnectionFail = 0; + _reconnectInterval = 500; +} + +void WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) { + begin(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) { + return begin(host.toString().c_str(), port, url, protocol); +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +void WebSocketsClient::beginSSL(const char *host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = fingerprint; +} + +void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) { + beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str()); +} +#endif + +void WebSocketsClient::beginSocketIO(const char *host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; +} + +void WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) { + beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +void WebSocketsClient::beginSocketIOSSL(const char *host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = ""; +} + +void WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) { + beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str()); +} +#endif + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * called in arduino loop + */ +void WebSocketsClient::loop(void) { + if(!clientIsConnected(&_client)) { + // do not flood the server + if((millis() - _lastConnectionFail) < _reconnectInterval) { + return; + } + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + if(_client.isSSL) { + DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n"); + if(_client.ssl) { + delete _client.ssl; + _client.ssl = NULL; + _client.tcp = NULL; + } + _client.ssl = new WiFiClientSecure(); + _client.tcp = _client.ssl; + } else { + DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n"); + if(_client.tcp) { + delete _client.tcp; + _client.tcp = NULL; + } + _client.tcp = new WiFiClient(); + } +#else + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); +#endif + + if(!_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + + if(_client.tcp->connect(_host.c_str(), _port)) { + connectedCb(); + _lastConnectionFail = 0; + } else { + connectFailedCb(); + _lastConnectionFail = millis(); + + } + } else { + handleClientData(); + } +} +#endif + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { + _cbEvent = cbEvent; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) { + if(length == 0) { + length = strlen((const char *) payload); + } + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_text, payload, length, true, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { + return sendTXT((uint8_t *) payload, length); +} + +bool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) { + return sendTXT((uint8_t *) payload, length, headerToPayload); +} + +bool WebSocketsClient::sendTXT(const char * payload, size_t length) { + return sendTXT((uint8_t *) payload, length); +} + +bool WebSocketsClient::sendTXT(String & payload) { + return sendTXT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) { + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_binary, payload, length, true, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { + return sendBIN((uint8_t *) payload, length); +} + +/** + * sends a WS ping to Server + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) { + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_ping, payload, length, true); + } + return false; +} + +bool WebSocketsClient::sendPing(String & payload) { + return sendPing((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsClient::disconnect(void) { + if(clientIsConnected(&_client)) { + WebSockets::clientDisconnect(&_client, 1000); + } +} + +/** + * set the Authorizatio for the http request + * @param user const char * + * @param password const char * + */ +void WebSocketsClient::setAuthorization(const char * user, const char * password) { + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _client.base64Authorization = base64_encode((uint8_t *) auth.c_str(), auth.length()); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void WebSocketsClient::setAuthorization(const char * auth) { + if(auth) { + //_client.base64Authorization = auth; + _client.plainAuthorization = auth; + } +} + +/** + * set extra headers for the http request; + * separate headers by "\r\n" + * @param extraHeaders const char * extraHeaders + */ +void WebSocketsClient::setExtraHeaders(const char * extraHeaders) { + _client.extraHeaders = extraHeaders; +} + +/** + * set the reconnect Interval + * how long to wait after a connection initiate failed + * @param time in ms + */ +void WebSocketsClient::setReconnectInterval(unsigned long time) { + _reconnectInterval = time; +} + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { + WStype_t type = WStype_ERROR; + + UNUSED(client); + + switch(opcode) { + case WSop_text: + type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; + break; + case WSop_binary: + type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; + break; + case WSop_continuation: + type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; + break; + case WSop_close: + case WSop_ping: + case WSop_pong: + default: + break; + } + + runCbEvent(type, payload, length); + +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::clientDisconnect(WSclient_t * client) { + + bool event = false; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + event = true; + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif + + if(client->tcp) { + if(client->tcp->connected()) { +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + client->tcp->flush(); +#endif + client->tcp->stop(); + } + event = true; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->status = WSC_NOT_CONNECTED; +#else + delete client->tcp; +#endif + client->tcp = NULL; + } + + client->cCode = 0; + client->cKey = ""; + client->cAccept = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + client->cSessionId = ""; + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); + if(event) { + runCbEvent(WStype_DISCONNECTED, NULL, 0); + } +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsClient::clientIsConnected(WSclient_t * client) { + + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n"); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + clientDisconnect(client); + } + + return false; +} +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * Handel incomming data from Client + */ +void WebSocketsClient::handleClientData(void) { + int len = _client.tcp->available(); + if(len > 0) { + switch(_client.status) { + case WSC_HEADER: { + String headerLine = _client.tcp->readStringUntil('\n'); + handleHeader(&_client, &headerLine); + } + break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(&_client); + break; + default: + WebSockets::clientDisconnect(&_client, 1002); + break; + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + delay(0); +#endif +} +#endif + +/** + * send the WebSocket header to Server + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::sendHeader(WSclient_t * client) { + + static const char * NEW_LINE = "\r\n"; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n"); + + uint8_t randomKey[16] = { 0 }; + + for(uint8_t i = 0; i < sizeof(randomKey); i++) { + randomKey[i] = random(0xFF); + } + + client->cKey = base64_encode(&randomKey[0], 16); + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + String handshake; + bool ws_header = true; + String url = client->cUrl; + + if(client->isSocketIO) { + if(client->cSessionId.length() == 0) { + url += WEBSOCKETS_STRING("&transport=polling"); + ws_header = false; + } else { + url += WEBSOCKETS_STRING("&transport=websocket&sid="); + url += client->cSessionId; + } + } + + handshake = WEBSOCKETS_STRING("GET "); + handshake += url + WEBSOCKETS_STRING(" HTTP/1.1\r\n" + "Host: "); + handshake += _host + ":" + _port + NEW_LINE; + + if(ws_header) { + handshake += WEBSOCKETS_STRING("Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: "); + handshake += client->cKey + NEW_LINE; + + if(client->cProtocol.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); + handshake += client->cProtocol + NEW_LINE; + } + + if(client->cExtensions.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Extensions: "); + handshake += client->cExtensions + NEW_LINE; + } + } else { + handshake += WEBSOCKETS_STRING("Connection: keep-alive\r\n"); + } + + // add extra headers; by default this includes "Origin: file://" + if(client->extraHeaders) { + handshake += client->extraHeaders + NEW_LINE; + } + + handshake += WEBSOCKETS_STRING("User-Agent: arduino-WebSocket-Client\r\n"); + + if(client->base64Authorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: Basic "); + handshake += client->base64Authorization + NEW_LINE; + } + + if(client->plainAuthorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: "); + handshake += client->plainAuthorization + NEW_LINE; + } + + handshake += NEW_LINE; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] handshake %s", (uint8_t* )handshake.c_str()); + write(client, (uint8_t*) handshake.c_str(), handshake.length()); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%luus).\n", (micros() - start)); + +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { + + headerLine->trim(); // remove \r + + if(headerLine->length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine->c_str()); + + if(headerLine->startsWith(WEBSOCKETS_STRING("HTTP/1."))) { + // "HTTP/1.1 101 Switching Protocols" + client->cCode = headerLine->substring(9, headerLine->indexOf(' ', 9)).toInt(); + } else if(headerLine->indexOf(':')) { + String headerName = headerLine->substring(0, headerLine->indexOf(':')); + String headerValue = headerLine->substring(headerLine->indexOf(':') + 1); + + // remove space in the beginning (RFC2616) + if(headerValue[0] == ' ') { + headerValue.remove(0, 1); + } + + if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("upgrade"))) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Accept"))) { + client->cAccept = headerValue; + client->cAccept.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { + client->cExtensions = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { + client->cVersion = headerValue.toInt(); + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Set-Cookie"))) { + if(headerValue.indexOf(WEBSOCKETS_STRING("HttpOnly")) > -1) { + client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1, headerValue.indexOf(";")); + } else { + client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1); + } + } + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); + } + + (*headerLine) = ""; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n"); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str()); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n", client->cCode); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n", client->cAccept.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + switch(client->cCode) { + case 101: ///< Switching Protocols + + break; + case 200: + if(client->isSocketIO) { + break; + } + case 403: ///< Forbidden + // todo handle login + default: ///< Server dont unterstand requrst + ok = false; + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode); + clientDisconnect(client); + _lastConnectionFail = millis(); + break; + } + } + + if(ok) { + + if(client->cAccept.length() == 0) { + ok = false; + } else { + // generate Sec-WebSocket-Accept key for check + String sKey = acceptKey(client->cKey); + if(sKey != client->cAccept) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n"); + ok = false; + } + } + } + + if(ok) { + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n"); + headerDone(client); + + runCbEvent(WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length()); + + } else if(clientIsConnected(client) && client->isSocketIO && client->cSessionId.length() > 0) { + sendHeader(client); + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); + _lastConnectionFail = millis(); + if(clientIsConnected(client)) { + write(client, "This is a webSocket client!"); + } + clientDisconnect(client); + } + } +} + +void WebSocketsClient::connectedCb() { + + DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _client.tcp->onDisconnect(std::bind([](WebSocketsClient * c, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { + DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); + client->status = WSC_NOT_CONNECTED; + client->tcp = NULL; + + // reconnect + c->asyncConnect(); + + return true; + }, this, std::placeholders::_1, &_client)); +#endif + + _client.status = WSC_HEADER; + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + // set Timeout for readBytesUntil and readStringUntil + _client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); +#endif + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _client.tcp->setNoDelay(true); + + if(_client.isSSL && _fingerprint.length()) { + if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) { + DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n"); + WebSockets::clientDisconnect(&_client, 1000); + return; + } + } +#endif + + // send Header to Server + sendHeader(&_client); + +} + +void WebSocketsClient::connectFailedCb() { + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Faild\n", _host.c_str(), _port); +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + +void WebSocketsClient::asyncConnect() { + + DEBUG_WEBSOCKETS("[WS-Client] asyncConnect...\n"); + + AsyncClient * tcpclient = new AsyncClient(); + + if(!tcpclient) { + DEBUG_WEBSOCKETS("[WS-Client] creating AsyncClient class failed!\n"); + return; + } + + tcpclient->onDisconnect([](void *obj, AsyncClient* c) { + c->free(); + delete c; + }); + + tcpclient->onConnect(std::bind([](WebSocketsClient * ws , AsyncClient * tcp) { + ws->_client.tcp = new AsyncTCPbuffer(tcp); + if(!ws->_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!\n"); + ws->connectFailedCb(); + return; + } + ws->connectedCb(); + }, this, std::placeholders::_2)); + + tcpclient->onError(std::bind([](WebSocketsClient * ws , AsyncClient * tcp) { + ws->connectFailedCb(); + + // reconnect + ws->asyncConnect(); + }, this, std::placeholders::_2)); + + if(!tcpclient->connect(_host.c_str(), _port)) { + connectFailedCb(); + delete tcpclient; + } + +} + +#endif diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.h new file mode 100644 index 0000000..61b8ea2 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsClient.h @@ -0,0 +1,136 @@ +/** + * @file WebSocketsClient.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSCLIENT_H_ +#define WEBSOCKETSCLIENT_H_ + +#include "WebSockets.h" + +class WebSocketsClient: private WebSockets { + public: +#ifdef __AVR__ + typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); +#else + typedef std::function WebSocketClientEvent; +#endif + + + WebSocketsClient(void); + virtual ~WebSocketsClient(void); + + void begin(const char *host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); + void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + void beginSSL(const char *host, uint16_t port, const char * url = "/", const char * = "", const char * protocol = "arduino"); + void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = "", String protocol = "arduino"); +#endif + + void beginSocketIO(const char *host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIO(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + void beginSocketIOSSL(const char *host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIOSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); +#endif + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); +#else + // Async interface not need a loop call + void loop(void) __attribute__ ((deprecated)) {} +#endif + + void onEvent(WebSocketClientEvent cbEvent); + + bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const uint8_t * payload, size_t length = 0); + bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const char * payload, size_t length = 0); + bool sendTXT(String & payload); + + bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + bool sendBIN(const uint8_t * payload, size_t length); + + bool sendPing(uint8_t * payload = NULL, size_t length = 0); + bool sendPing(String & payload); + + void disconnect(void); + + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + + void setExtraHeaders(const char * extraHeaders = NULL); + + void setReconnectInterval(unsigned long time); + + protected: + String _host; + uint16_t _port; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + String _fingerprint; +#endif + WSclient_t _client; + + WebSocketClientEvent _cbEvent; + + unsigned long _lastConnectionFail; + unsigned long _reconnectInterval; + + void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleClientData(void); +#endif + + void sendHeader(WSclient_t * client); + void handleHeader(WSclient_t * client, String * headerLine); + + void connectedCb(); + void connectFailedCb(); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + void asyncConnect(); +#endif + + /** + * called for sending a Event to the app + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(type, payload, length); + } + } + +}; + +#endif /* WEBSOCKETSCLIENT_H_ */ diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.cpp b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.cpp new file mode 100644 index 0000000..b6f950f --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.cpp @@ -0,0 +1,873 @@ +/** + * @file WebSocketsServer.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsServer.h" + +WebSocketsServer::WebSocketsServer(uint16_t port, String origin, String protocol) { + _port = port; + _origin = origin; + _protocol = protocol; + _runnning = false; + + _server = new WEBSOCKETS_NETWORK_SERVER_CLASS(port); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _server->onClient([](void *s, AsyncClient* c){ + ((WebSocketsServer*)s)->newClient(new AsyncTCPbuffer(c)); + }, this); +#endif + + _cbEvent = NULL; + + _httpHeaderValidationFunc = NULL; + _mandatoryHttpHeaders = NULL; + _mandatoryHttpHeaderCount = 0; + + memset(&_clients[0], 0x00, (sizeof(WSclient_t) * WEBSOCKETS_SERVER_CLIENT_MAX)); +} + + +WebSocketsServer::~WebSocketsServer() { + // disconnect all clients + close(); + + if (_mandatoryHttpHeaders) + delete[] _mandatoryHttpHeaders; + + _mandatoryHttpHeaderCount = 0; +} + +/** + * called to initialize the Websocket server + */ +void WebSocketsServer::begin(void) { + WSclient_t * client; + + // init client storage + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + + client->num = i; + client->status = WSC_NOT_CONNECTED; + client->tcp = NULL; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + client->isSSL = false; + client->ssl = NULL; +#endif + client->cUrl = ""; + client->cCode = 0; + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->base64Authorization = ""; + + client->cWsRXsize = 0; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; +#endif + } + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#elif defined(ESP32) + #define DR_REG_RNG_BASE 0x3ff75144 + randomSeed(READ_PERI_REG(DR_REG_RNG_BASE)); +#else + // TODO find better seed + randomSeed(millis()); +#endif + + _runnning = true; + _server->begin(); + + DEBUG_WEBSOCKETS("[WS-Server] Server Started.\n"); +} + +void WebSocketsServer::close(void) { + _runnning = false; + disconnect(); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _server->close(); +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _server->end(); +#else + // TODO how to close server? +#endif + +} + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * called in arduino loop + */ +void WebSocketsServer::loop(void) { + if(_runnning) { + handleNewClients(); + handleClientData(); + } +} +#endif + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsServer::onEvent(WebSocketServerEvent cbEvent) { + _cbEvent = cbEvent; +} + +/* + * Sets the custom http header validator function + * @param httpHeaderValidationFunc WebSocketServerHttpHeaderValFunc ///< pointer to the custom http header validation function + * @param mandatoryHttpHeaders[] const char* ///< the array of named http headers considered to be mandatory / must be present in order for websocket upgrade to succeed + * @param mandatoryHttpHeaderCount size_t ///< the number of items in the mandatoryHttpHeaders array + */ +void WebSocketsServer::onValidateHttpHeader( + WebSocketServerHttpHeaderValFunc validationFunc, + const char* mandatoryHttpHeaders[], + size_t mandatoryHttpHeaderCount) +{ + _httpHeaderValidationFunc = validationFunc; + + if (_mandatoryHttpHeaders) + delete[] _mandatoryHttpHeaders; + + _mandatoryHttpHeaderCount = mandatoryHttpHeaderCount; + _mandatoryHttpHeaders = new String[_mandatoryHttpHeaderCount]; + + for (size_t i = 0; i < _mandatoryHttpHeaderCount; i++) { + _mandatoryHttpHeaders[i] = mandatoryHttpHeaders[i]; + } +} + +/* + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServer::sendTXT(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + if(length == 0) { + length = strlen((const char *) payload); + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_text, payload, length, false, true, headerToPayload); + } + return false; +} + +bool WebSocketsServer::sendTXT(uint8_t num, const uint8_t * payload, size_t length) { + return sendTXT(num, (uint8_t *) payload, length); +} + +bool WebSocketsServer::sendTXT(uint8_t num, char * payload, size_t length, bool headerToPayload) { + return sendTXT(num, (uint8_t *) payload, length, headerToPayload); +} + +bool WebSocketsServer::sendTXT(uint8_t num, const char * payload, size_t length) { + return sendTXT(num, (uint8_t *) payload, length); +} + +bool WebSocketsServer::sendTXT(uint8_t num, String & payload) { + return sendTXT(num, (uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send text data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServer::broadcastTXT(uint8_t * payload, size_t length, bool headerToPayload) { + WSclient_t * client; + bool ret = true; + if(length == 0) { + length = strlen((const char *) payload); + } + + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_text, payload, length, false, true, headerToPayload)) { + ret = false; + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + return ret; +} + +bool WebSocketsServer::broadcastTXT(const uint8_t * payload, size_t length) { + return broadcastTXT((uint8_t *) payload, length); +} + +bool WebSocketsServer::broadcastTXT(char * payload, size_t length, bool headerToPayload) { + return broadcastTXT((uint8_t *) payload, length, headerToPayload); +} + +bool WebSocketsServer::broadcastTXT(const char * payload, size_t length) { + return broadcastTXT((uint8_t *) payload, length); +} + +bool WebSocketsServer::broadcastTXT(String & payload) { + return broadcastTXT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServer::sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_binary, payload, length, false, true, headerToPayload); + } + return false; +} + +bool WebSocketsServer::sendBIN(uint8_t num, const uint8_t * payload, size_t length) { + return sendBIN(num, (uint8_t *) payload, length); +} + +/** + * send binary data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServer::broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload) { + WSclient_t * client; + bool ret = true; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_binary, payload, length, false, true, headerToPayload)) { + ret = false; + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + return ret; +} + +bool WebSocketsServer::broadcastBIN(const uint8_t * payload, size_t length) { + return broadcastBIN((uint8_t *) payload, length); +} + + +/** + * sends a WS ping to Client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServer::sendPing(uint8_t num, uint8_t * payload, size_t length) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_ping, payload, length); + } + return false; +} + +bool WebSocketsServer::sendPing(uint8_t num, String & payload) { + return sendPing(num, (uint8_t *) payload.c_str(), payload.length()); +} + +/** + * sends a WS ping to all Client + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServer::broadcastPing(uint8_t * payload, size_t length) { + WSclient_t * client; + bool ret = true; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_ping, payload, length)) { + ret = false; + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + return ret; +} + +bool WebSocketsServer::broadcastPing(String & payload) { + return broadcastPing((uint8_t *) payload.c_str(), payload.length()); +} + + +/** + * disconnect all clients + */ +void WebSocketsServer::disconnect(void) { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } + } +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsServer::disconnect(uint8_t num) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } +} + + +/* + * set the Authorization for the http request + * @param user const char * + * @param password const char * + */ +void WebSocketsServer::setAuthorization(const char * user, const char * password) { + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length()); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void WebSocketsServer::setAuthorization(const char * auth) { + if(auth) { + _base64Authorization = auth; + } +} + +/** + * count the connected clients (optional ping them) + * @param ping bool ping the connected clients + */ +int WebSocketsServer::connectedClients(bool ping) { + WSclient_t * client; + int count = 0; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(client->status == WSC_CONNECTED) { + if(ping != true || sendPing(i)) { + count++; + } + } + } + return count; +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +/** + * get an IP for a client + * @param num uint8_t client id + * @return IPAddress + */ +IPAddress WebSocketsServer::remoteIP(uint8_t num) { + if(num < WEBSOCKETS_SERVER_CLIENT_MAX) { + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return client->tcp->remoteIP(); + } + } + + return IPAddress(); +} +#endif + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * handle new client connection + * @param client + */ +bool WebSocketsServer::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient) { + WSclient_t * client; + // search free list entry for client + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + + // state is not connected or tcp connection is lost + if(!clientIsConnected(client)) { + + client->tcp = TCPclient; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + client->isSSL = false; + client->tcp->setNoDelay(true); +#endif +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + // set Timeout for readBytesUntil and readStringUntil + client->tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); +#endif + client->status = WSC_HEADER; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + IPAddress ip = client->tcp->remoteIP(); + DEBUG_WEBSOCKETS("[WS-Server][%d] new client from %d.%d.%d.%d\n", client->num, ip[0], ip[1], ip[2], ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server][%d] new client\n", client->num); +#endif + + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->onDisconnect(std::bind([](WebSocketsServer * server, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { + DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); + + AsyncTCPbuffer ** sl = &server->_clients[client->num].tcp; + if(*sl == obj) { + client->status = WSC_NOT_CONNECTED; + *sl = NULL; + } + return true; + }, this, std::placeholders::_1, client)); + + + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServer::handleHeader, this, client, &(client->cHttpLine))); +#endif + + return true; + break; + } + } + return false; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsServer::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { + WStype_t type = WStype_ERROR; + + switch(opcode) { + case WSop_text: + type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; + break; + case WSop_binary: + type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; + break; + case WSop_continuation: + type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; + break; + case WSop_close: + case WSop_ping: + case WSop_pong: + default: + break; + } + + runCbEvent(client->num, type, payload, length); + +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsServer::clientDisconnect(WSclient_t * client) { + + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif + + if(client->tcp) { + if(client->tcp->connected()) { +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + client->tcp->flush(); +#endif + client->tcp->stop(); + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->status = WSC_NOT_CONNECTED; +#else + delete client->tcp; +#endif + client->tcp = NULL; + } + + client->cUrl = ""; + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->cWsRXsize = 0; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; +#endif + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Server][%d] client disconnected.\n", client->num); + + runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0); + +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = connected + */ +bool WebSocketsServer::clientIsConnected(WSclient_t * client) { + + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Server][%d] client connection lost.\n", client->num); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + DEBUG_WEBSOCKETS("[WS-Server][%d] client list cleanup.\n", client->num); + clientDisconnect(client); + } + + return false; +} +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * Handle incoming Connection Request + */ +void WebSocketsServer::handleNewClients(void) { + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + while(_server->hasClient()) { +#endif + bool ok = false; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + // store new connection + WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available()); +#else + WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available()); +#endif + + if(!tcpClient) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + + ok = newClient(tcpClient); + + if(!ok) { + // no free space to handle client +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + IPAddress ip = tcpClient->remoteIP(); + DEBUG_WEBSOCKETS("[WS-Server] no free space new client from %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server] no free space new client\n"); +#endif + tcpClient->stop(); + } + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + delay(0); + } +#endif + +} + + +/** + * Handel incomming data from Client + */ +void WebSocketsServer::handleClientData(void) { + + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + int len = client->tcp->available(); + if(len > 0) { + //DEBUG_WEBSOCKETS("[WS-Server][%d][handleClientData] len: %d\n", client->num, len); + switch(client->status) { + case WSC_HEADER: + { + String headerLine = client->tcp->readStringUntil('\n'); + handleHeader(client, &headerLine); + } + break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(client); + break; + default: + WebSockets::clientDisconnect(client, 1002); + break; + } + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } +} +#endif + +/* + * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection + * @param headerName String ///< the name of the header being checked + */ +bool WebSocketsServer::hasMandatoryHeader(String headerName) { + for (size_t i = 0; i < _mandatoryHttpHeaderCount; i++) { + if (_mandatoryHttpHeaders[i].equalsIgnoreCase(headerName)) + return true; + } + return false; +} + + +/** + * handles http header reading for WebSocket upgrade + * @param client WSclient_t * ///< pointer to the client struct + * @param headerLine String ///< the header being read / processed + */ +void WebSocketsServer::handleHeader(WSclient_t * client, String * headerLine) { + + static const char * NEW_LINE = "\r\n"; + + headerLine->trim(); // remove \r + + if(headerLine->length() > 0) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] RX: %s\n", client->num, headerLine->c_str()); + + // websocket requests always start with GET see rfc6455 + if(headerLine->startsWith("GET ")) { + + // cut URL out + client->cUrl = headerLine->substring(4, headerLine->indexOf(' ', 4)); + + //reset non-websocket http header validation state for this client + client->cHttpHeadersValid = true; + client->cMandatoryHeadersCount = 0; + + } else if(headerLine->indexOf(':')) { + String headerName = headerLine->substring(0, headerLine->indexOf(':')); + String headerValue = headerLine->substring(headerLine->indexOf(':') + 1); + + // remove space in the beginning (RFC2616) + if(headerValue[0] == ' ') { + headerValue.remove(0, 1); + } + + if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) { + headerValue.toLowerCase(); + if(headerValue.indexOf(WEBSOCKETS_STRING("upgrade")) >= 0) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { + client->cVersion = headerValue.toInt(); + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Key"))) { + client->cKey = headerValue; + client->cKey.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { + client->cExtensions = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Authorization"))) { + client->base64Authorization = headerValue; + } else { + client->cHttpHeadersValid &= execHttpHeaderValidation(headerName, headerValue); + if(_mandatoryHttpHeaderCount > 0 && hasMandatoryHeader(headerName)) { + client->cMandatoryHeadersCount++; + } + } + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); + } + + (*headerLine) = ""; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServer::handleHeader, this, client, &(client->cHttpLine))); +#endif + } else { + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Header read fin.\n", client->num); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cURL: %s\n", client->num, client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cIsUpgrade: %d\n", client->num, client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cIsWebsocket: %d\n", client->num, client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cKey: %s\n", client->num, client->cKey.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cProtocol: %s\n", client->num, client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cExtensions: %s\n", client->num, client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cVersion: %d\n", client->num, client->cVersion); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - base64Authorization: %s\n", client->num, client->base64Authorization.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cHttpHeadersValid: %d\n", client->num, client->cHttpHeadersValid); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cMandatoryHeadersCount: %d\n", client->num, client->cMandatoryHeadersCount); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + if(client->cUrl.length() == 0) { + ok = false; + } + if(client->cKey.length() == 0) { + ok = false; + } + if(client->cVersion != 13) { + ok = false; + } + if(!client->cHttpHeadersValid) { + ok = false; + } + if (client->cMandatoryHeadersCount != _mandatoryHttpHeaderCount) { + ok = false; + } + } + + if(_base64Authorization.length() > 0) { + String auth = WEBSOCKETS_STRING("Basic "); + auth += _base64Authorization; + if(auth != client->base64Authorization) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] HTTP Authorization failed!\n", client->num); + handleAuthorizationFailed(client); + return; + } + } + + if(ok) { + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Websocket connection incoming.\n", client->num); + + // generate Sec-WebSocket-Accept key + String sKey = acceptKey(client->cKey); + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - sKey: %s\n", client->num, sKey.c_str()); + + client->status = WSC_CONNECTED; + + String handshake = WEBSOCKETS_STRING("HTTP/1.1 101 Switching Protocols\r\n" + "Server: arduino-WebSocketsServer\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Accept: "); + handshake += sKey + NEW_LINE; + + if(_origin.length() > 0) { + handshake += WEBSOCKETS_STRING("Access-Control-Allow-Origin: "); + handshake +=_origin + NEW_LINE; + } + + if(client->cProtocol.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); + handshake +=_protocol + NEW_LINE; + } + + // header end + handshake += NEW_LINE; + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] handshake %s", client->num, (uint8_t*)handshake.c_str()); + + write(client, (uint8_t*)handshake.c_str(), handshake.length()); + + headerDone(client); + + // send ping + WebSockets::sendFrame(client, WSop_ping); + + runCbEvent(client->num, WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length()); + + } else { + handleNonWebsocketConnection(client); + } + } +} + + + diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.h new file mode 100644 index 0000000..db945a6 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/WebSocketsServer.h @@ -0,0 +1,212 @@ +/** + * @file WebSocketsServer.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSSERVER_H_ +#define WEBSOCKETSSERVER_H_ + +#include "WebSockets.h" + +#ifndef WEBSOCKETS_SERVER_CLIENT_MAX +#define WEBSOCKETS_SERVER_CLIENT_MAX (5) +#endif + + + + +class WebSocketsServer: protected WebSockets { +public: + +#ifdef __AVR__ + typedef void (*WebSocketServerEvent)(uint8_t num, WStype_t type, uint8_t * payload, size_t length); + typedef bool (*WebSocketServerHttpHeaderValFunc)(String headerName, String headerValue); +#else + typedef std::function WebSocketServerEvent; + typedef std::function WebSocketServerHttpHeaderValFunc; +#endif + + WebSocketsServer(uint16_t port, String origin = "", String protocol = "arduino"); + virtual ~WebSocketsServer(void); + + void begin(void); + void close(void); + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); +#else + // Async interface not need a loop call + void loop(void) __attribute__ ((deprecated)) {} +#endif + + void onEvent(WebSocketServerEvent cbEvent); + void onValidateHttpHeader( + WebSocketServerHttpHeaderValFunc validationFunc, + const char* mandatoryHttpHeaders[], + size_t mandatoryHttpHeaderCount); + + + bool sendTXT(uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(uint8_t num, const uint8_t * payload, size_t length = 0); + bool sendTXT(uint8_t num, char * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(uint8_t num, const char * payload, size_t length = 0); + bool sendTXT(uint8_t num, String & payload); + + bool broadcastTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool broadcastTXT(const uint8_t * payload, size_t length = 0); + bool broadcastTXT(char * payload, size_t length = 0, bool headerToPayload = false); + bool broadcastTXT(const char * payload, size_t length = 0); + bool broadcastTXT(String & payload); + + bool sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload = false); + bool sendBIN(uint8_t num, const uint8_t * payload, size_t length); + + bool broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + bool broadcastBIN(const uint8_t * payload, size_t length); + + bool sendPing(uint8_t num, uint8_t * payload = NULL, size_t length = 0); + bool sendPing(uint8_t num, String & payload); + + bool broadcastPing(uint8_t * payload = NULL, size_t length = 0); + bool broadcastPing(String & payload); + + void disconnect(void); + void disconnect(uint8_t num); + + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + + int connectedClients(bool ping = false); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + IPAddress remoteIP(uint8_t num); +#endif + +protected: + uint16_t _port; + String _origin; + String _protocol; + String _base64Authorization; ///< Base64 encoded Auth request + String * _mandatoryHttpHeaders; + size_t _mandatoryHttpHeaderCount; + + WEBSOCKETS_NETWORK_SERVER_CLASS * _server; + + WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX]; + + WebSocketServerEvent _cbEvent; + WebSocketServerHttpHeaderValFunc _httpHeaderValidationFunc; + + bool _runnning; + + bool newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient); + + void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleNewClients(void); + void handleClientData(void); +#endif + + void handleHeader(WSclient_t * client, String * headerLine); + + /** + * called if a non Websocket connection is coming in. + * Note: can be override + * @param client WSclient_t * ptr to the client struct + */ + virtual void handleNonWebsocketConnection(WSclient_t * client) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] no Websocket connection close.\n", client->num); + client->tcp->write("HTTP/1.1 400 Bad Request\r\n" + "Server: arduino-WebSocket-Server\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 32\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + "This is a Websocket server only!"); + clientDisconnect(client); + } + + /** + * called if a non Authorization connection is coming in. + * Note: can be override + * @param client WSclient_t * ptr to the client struct + */ + virtual void handleAuthorizationFailed(WSclient_t *client) { + client->tcp->write("HTTP/1.1 401 Unauthorized\r\n" + "Server: arduino-WebSocket-Server\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 45\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "WWW-Authenticate: Basic realm=\"WebSocket Server\"" + "\r\n" + "This Websocket server requires Authorization!"); + clientDisconnect(client); + } + + /** + * called for sending a Event to the app + * @param num uint8_t + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(num, type, payload, length); + } + } + + /* + * Called at client socket connect handshake negotiation time for each http header that is not + * a websocket specific http header (not Connection, Upgrade, Sec-WebSocket-*) + * If the custom httpHeaderValidationFunc returns false for any headerName / headerValue passed, the + * socket negotiation is considered invalid and the upgrade to websockets request is denied / rejected + * This mechanism can be used to enable custom authentication schemes e.g. test the value + * of a session cookie to determine if a user is logged on / authenticated + */ + virtual bool execHttpHeaderValidation(String headerName, String headerValue) { + if(_httpHeaderValidationFunc) { + //return the value of the custom http header validation function + return _httpHeaderValidationFunc(headerName, headerValue); + } + //no custom http header validation so just assume all is good + return true; + } + +private: + /* + * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection + * @param headerName String ///< the name of the header being checked + */ + bool hasMandatoryHeader(String headerName); + +}; + + + +#endif /* WEBSOCKETSSERVER_H_ */ diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/AUTHORS b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/AUTHORS new file mode 100644 index 0000000..af68737 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/LICENSE b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/LICENSE new file mode 100644 index 0000000..a6b5606 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode.c b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode.c new file mode 100644 index 0000000..e135da2 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode.c @@ -0,0 +1,98 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cdecode_inc.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +#endif diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode_inc.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode_inc.h new file mode 100644 index 0000000..d0d7f48 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cdecode_inc.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode.c b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode.c new file mode 100644 index 0000000..afe1463 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode.c @@ -0,0 +1,119 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cencode_inc.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = 0x00; + + return codechar - code_out; +} + +#endif diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode_inc.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode_inc.h new file mode 100644 index 0000000..c1e3464 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libb64/cencode_inc.h @@ -0,0 +1,31 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.c b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.c new file mode 100644 index 0000000..48f4df5 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.c @@ -0,0 +1,202 @@ +/* from valgrind tests */ + +/* ================ sha1.c ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#if !defined(ESP8266) && !defined(ESP32) + +#define SHA1HANDSOFF + +#include +#include +#include + +#include "libsha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) +{ + uint32_t i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t; + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +/* ================ end of sha1.c ================ */ + + +#endif diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.h b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.h new file mode 100644 index 0000000..ee3718e --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/src/libsha1/libsha1.h @@ -0,0 +1,21 @@ +/* ================ sha1.h ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#if !defined(ESP8266) && !defined(ESP32) + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); + +#endif diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocket.html b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocket.html new file mode 100644 index 0000000..66a2708 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocket.html @@ -0,0 +1,49 @@ + + + + + + + +LED Control:
+
+R:
+G:
+B:
+ + \ No newline at end of file diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/index.js b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/index.js new file mode 100644 index 0000000..389e193 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/index.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node +var WebSocketServer = require('websocket').server; +var http = require('http'); + +var server = http.createServer(function(request, response) { + console.log((new Date()) + ' Received request for ' + request.url); + response.writeHead(404); + response.end(); +}); +server.listen(8011, function() { + console.log((new Date()) + ' Server is listening on port 8011'); +}); + +wsServer = new WebSocketServer({ + httpServer: server, + // You should not use autoAcceptConnections for production + // applications, as it defeats all standard cross-origin protection + // facilities built into the protocol and the browser. You should + // *always* verify the connection's origin and decide whether or not + // to accept it. + autoAcceptConnections: false +}); + +function originIsAllowed(origin) { + // put logic here to detect whether the specified origin is allowed. + return true; +} + +wsServer.on('request', function(request) { + + if (!originIsAllowed(request.origin)) { + // Make sure we only accept requests from an allowed origin + request.reject(); + console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.'); + return; + } + + var connection = request.accept('arduino', request.origin); + console.log((new Date()) + ' Connection accepted.'); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + console.log('Received Message: ' + message.utf8Data); + // connection.sendUTF(message.utf8Data); + } + else if (message.type === 'binary') { + console.log('Received Binary Message of ' + message.binaryData.length + ' bytes'); + //connection.sendBytes(message.binaryData); + } + }); + + connection.on('close', function(reasonCode, description) { + console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); + }); + + connection.sendUTF("Hallo Client!"); +}); diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/package.json b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/package.json new file mode 100644 index 0000000..9538323 --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/tests/webSocketServer/package.json @@ -0,0 +1,27 @@ +{ + "name": "webSocketServer", + "version": "1.0.0", + "description": "WebSocketServer for testing", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/Links2004/arduinoWebSockets" + }, + "keywords": [ + "esp8266", + "websocket", + "arduino" + ], + "author": "Markus Sattler", + "license": "LGPLv2", + "bugs": { + "url": "https://github.com/Links2004/arduinoWebSockets/issues" + }, + "homepage": "https://github.com/Links2004/arduinoWebSockets", + "dependencies": { + "websocket": "^1.0.18" + } +} diff --git a/Grbl_Esp32-master/libraries/arduinoWebSockets/travis/common.sh b/Grbl_Esp32-master/libraries/arduinoWebSockets/travis/common.sh new file mode 100644 index 0000000..be959fa --- /dev/null +++ b/Grbl_Esp32-master/libraries/arduinoWebSockets/travis/common.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +function build_sketches() +{ + local arduino=$1 + local srcpath=$2 + local platform=$3 + local sketches=$(find $srcpath -name *.ino) + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + if [[ -f "$sketchdir/.$platform.skip" ]]; then + echo -e "\n\n ------------ Skipping $sketch ------------ \n\n"; + continue + fi + echo -e "\n\n ------------ Building $sketch ------------ \n\n"; + $arduino --verify $sketch; + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($sketch) build verbose..." + $arduino --verify --verbose --preserve-temp-files $sketch + result=$? + fi + if [ $result -ne 0 ]; then + echo "Build failed ($1) $sketch" + return $result + fi + done +} + + +function get_core() +{ + echo Setup core for $1 + + cd $HOME/arduino_ide/hardware + + if [ "$1" = "esp8266" ] ; then + mkdir esp8266com + cd esp8266com + git clone https://github.com/esp8266/Arduino.git esp8266 + cd esp8266/tools + python get.py + fi + + if [ "$1" = "esp32" ] ; then + mkdir espressif + cd espressif + git clone https://github.com/espressif/arduino-esp32.git esp32 + cd esp32/tools + python get.py + fi + +} diff --git a/Grbl_Esp32-master/platformio.ini b/Grbl_Esp32-master/platformio.ini new file mode 100644 index 0000000..0ca16f2 --- /dev/null +++ b/Grbl_Esp32-master/platformio.ini @@ -0,0 +1,29 @@ +[platformio] +src_dir=Grbl_Esp32 +lib_dir=libraries +data_dir=Grbl_Esp32/data + +[common_env_data] +lib_deps_builtin = + ArduinoOTA + BluetoothSerial + DNSServer + EEPROM + ESPmDNS + FS + Preferences + SD + SPI + SPIFFS + Update + WebServer + WiFi + WiFiClientSecure + +[env:nodemcu-32s] +platform = espressif32 +board = nodemcu-32s +framework = arduino +upload_speed = 512000 +board_build.partitions = min_spiffs.csv +monitor_speed = 115200 diff --git a/Ressource/README.md b/Ressource/README.md index 5b8e1a5..3a2d566 100644 --- a/Ressource/README.md +++ b/Ressource/README.md @@ -54,8 +54,6 @@ M5 = spindle off M3 = spindle on -Spindle gpio 22 sur l'esp32 boolean 0-3.3v - Setup le G28 (position safe de la machine) En envoyant ? ou en activant le verbose mode grbl report status. @@ -67,3 +65,86 @@ Commande de job envoyé par l'interface web $J=G91 G21 F1000 X-10 + +###CONFIG GRBL_ESP32 CRA4: +- Augmenter le temps de démarrage de la spindle à 3-4sec +- Activer la carte sd OK +- Ne pas ignorer les pin de contrôle qui sur un esp seul n'ont pas de pullup et pose problème contrairement à ici. Cycle Start | Feed Hold | Reset | Safety Door OK +- Inverser la logique de commande de la spindle. 3.3vHIGH = off 0vLOW = on OK +- Possibilité de desactivé la pwm sur la spindle qui n'est ici pas utilisé +- Augmenter le BLOCK_BUFFER_SIZE et RX_BUFFER_SIZE OK +- Comme les capas de filtrages et les résistances de pullup suffisent pas activer ENABLE_SOFTWARE_DEBOUNCE (faudrait des optos coupleurs normalement et des endstop en >12v au lieu de 3.3v) OK + +###PINOUT GRBL_ESP32 CRA4 (v3.5) +Stepper disable sur GPIO 2 (c'est celle ou il y a led bleu relié sur l'esp car si utilisé en input il y a des problèmes) +X_LIMIT_PIN GPIO_NUM_13 + +#ifdef CPU_MAP_ESP32 + // This is the CPU Map for the ESP32 CNC Controller R2 + + // It is OK to comment out any step and direction pins. This + // won't affect operation except that there will be no output + // form the pins. Grbl will virtually move the axis. This could + // be handy if you are using a servo, etc. for another axis. + #define CPU_MAP_NAME "CPU_MAP_ESP32" + + #define X_STEP_PIN GPIO_NUM_12 + #define X_DIRECTION_PIN GPIO_NUM_26 + #define X_RMT_CHANNEL 0 + + #define Y_STEP_PIN GPIO_NUM_14 + #define Y_DIRECTION_PIN GPIO_NUM_25 + #define Y_RMT_CHANNEL 1 + + #define Z_STEP_PIN GPIO_NUM_27 + #define Z_DIRECTION_PIN GPIO_NUM_33 + #define Z_RMT_CHANNEL 2 + + // OK to comment out to use pin for other features + #define STEPPERS_DISABLE_PIN GPIO_NUM_2 + + // *** the flood coolant feature code is activated by defining this pins + // *** Comment it out to use the pin for other features + #define COOLANT_FLOOD_PIN GPIO_NUM_16 + //#define COOLANT_MIST_PIN GPIO_NUM_21 + + // If SPINDLE_PWM_PIN is commented out, this frees up the pin, but Grbl will still + // use a virtual spindle. Do not comment out the other parameters for the spindle. + #define SPINDLE_PWM_PIN GPIO_NUM_17 + #define SPINDLE_PWM_CHANNEL 0 + // PWM Generator is based on 80,000,000 Hz counter + // Therefor the freq determines the resolution + // 80,000,000 / freq = max resolution + // For 5000 that is 80,000,000 / 5000 = 16000 + // round down to nearest bit count for SPINDLE_PWM_MAX_VALUE = 13bits (8192) + #define SPINDLE_PWM_BASE_FREQ 5000 // Hz + #define SPINDLE_PWM_BIT_PRECISION 8 // be sure to match this with SPINDLE_PWM_MAX_VALUE + #define SPINDLE_PWM_OFF_VALUE 0 + #define SPINDLE_PWM_MAX_VALUE 255 // (2^SPINDLE_PWM_BIT_PRECISION) + + #ifndef SPINDLE_PWM_MIN_VALUE + #define SPINDLE_PWM_MIN_VALUE 1 // Must be greater than zero. + #endif + + #define SPINDLE_ENABLE_PIN GPIO_NUM_22 + + #define SPINDLE_PWM_RANGE (SPINDLE_PWM_MAX_VALUE-SPINDLE_PWM_MIN_VALUE) + + // if these spindle function pins are defined, they will be activated in the code + // comment them out to use the pins for other functions + //#define SPINDLE_ENABLE_PIN GPIO_NUM_16 + //#define SPINDLE_DIR_PIN GPIO_NUM_16 + + #define X_LIMIT_PIN GPIO_NUM_13 + #define Y_LIMIT_PIN GPIO_NUM_4 + #define Z_LIMIT_PIN GPIO_NUM_15 + #define LIMIT_MASK B111 + + #define PROBE_PIN GPIO_NUM_32 + + #define CONTROL_SAFETY_DOOR_PIN GPIO_NUM_35 // needs external pullup + #define CONTROL_RESET_PIN GPIO_NUM_34 // needs external pullup + #define CONTROL_FEED_HOLD_PIN GPIO_NUM_36 // needs external pullup + #define CONTROL_CYCLE_START_PIN GPIO_NUM_39 // needs external pullup + +#endif diff --git a/Ressource/configGRBL_Probe.txt b/Ressource/configGRBL.txt similarity index 89% rename from Ressource/configGRBL_Probe.txt rename to Ressource/configGRBL.txt index 071ae66..405acdc 100644 --- a/Ressource/configGRBL_Probe.txt +++ b/Ressource/configGRBL.txt @@ -1,4 +1,18 @@ -$0=10 (Step pulse time, microseconds) +Probe cncjs +; Z-Probe v2 +G91 +G38.2 Z-10 F10 ; Vitesse de descente 10, descente de max 10mm +G90 +; Set the active WCS Z0 +G10 L20 P1 Z0 ; Touch plate sans epaisseur +; Retract from the touch plate +G91 +G28 G91 Z0 ; Le z d'abord +G28 ; Go G28 +G90 + + +$0=12 (Step pulse time, microseconds) $1=250 (Step idle delay, milliseconds) $2=0 (Step pulse invert, mask) $3=0 (Step direction invert, mask) @@ -27,21 +41,8 @@ $110=5000.000 (X-axis maximum rate, mm/min) $111=6000.000 (Y-axis maximum rate, mm/min) $112=6000.000 (Z-axis maximum rate, mm/min) $120=200.000 (X-axis acceleration, mm/sec^2) -$121=200.000 (Y-axis acceleration, mm/sec^2) +$121=250.000 (Y-axis acceleration, mm/sec^2) $122=200.000 (Z-axis acceleration, mm/sec^2) $130=300.000 (X-axis maximum travel, millimeters) $131=210.000 (Y-axis maximum travel, millimeters) -$132=100.000 (Z-axis maximum travel, mi - -Probe cncjs -; Z-Probe v2 -G91 -G38.2 Z-10 F10 ; Vitesse de descente 10, descente de max 10mm -G90 -; Set the active WCS Z0 -G10 L20 P1 Z0 ; Touch plate sans epaisseur -; Retract from the touch plate -G91 -G28 G91 Z0 ; Le z d'abord -G28 ; Go G28 -G90 \ No newline at end of file +$132=100.000 (Z-axis maximum travel, millimeters)