| 1 | /* |
| 2 | This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). |
| 3 | Smoothie 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. |
| 4 | Smoothie 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. |
| 5 | You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. |
| 6 | */ |
| 7 | |
| 8 | |
| 9 | #include "libs/Kernel.h" |
| 10 | #include "SimpleShell.h" |
| 11 | #include "libs/nuts_bolts.h" |
| 12 | #include "libs/utils.h" |
| 13 | #include "libs/SerialMessage.h" |
| 14 | #include "libs/StreamOutput.h" |
| 15 | #include "modules/robot/Conveyor.h" |
| 16 | #include "DirHandle.h" |
| 17 | #include "mri.h" |
| 18 | #include "version.h" |
| 19 | #include "PublicDataRequest.h" |
| 20 | |
| 21 | #include "modules/tools/temperaturecontrol/TemperatureControlPublicAccess.h" |
| 22 | #include "modules/robot/RobotPublicAccess.h" |
| 23 | #include "NetworkPublicAccess.h" |
| 24 | #include "platform_memory.h" |
| 25 | |
| 26 | extern unsigned int g_maximumHeapAddress; |
| 27 | |
| 28 | #include <malloc.h> |
| 29 | #include <mri.h> |
| 30 | #include <stdio.h> |
| 31 | #include <stdint.h> |
| 32 | |
| 33 | extern "C" uint32_t __end__; |
| 34 | extern "C" uint32_t __malloc_free_list; |
| 35 | extern "C" uint32_t _sbrk(int size); |
| 36 | |
| 37 | #define get_temp_command_checksum CHECKSUM("temp") |
| 38 | #define get_pos_command_checksum CHECKSUM("pos") |
| 39 | |
| 40 | // command lookup table |
| 41 | SimpleShell::ptentry_t SimpleShell::commands_table[] = { |
| 42 | {CHECKSUM("ls"), &SimpleShell::ls_command}, |
| 43 | {CHECKSUM("cd"), &SimpleShell::cd_command}, |
| 44 | {CHECKSUM("pwd"), &SimpleShell::pwd_command}, |
| 45 | {CHECKSUM("cat"), &SimpleShell::cat_command}, |
| 46 | {CHECKSUM("rm"), &SimpleShell::rm_command}, |
| 47 | {CHECKSUM("reset"), &SimpleShell::reset_command}, |
| 48 | {CHECKSUM("dfu"), &SimpleShell::dfu_command}, |
| 49 | {CHECKSUM("break"), &SimpleShell::break_command}, |
| 50 | {CHECKSUM("help"), &SimpleShell::help_command}, |
| 51 | {CHECKSUM("?"), &SimpleShell::help_command}, |
| 52 | {CHECKSUM("version"), &SimpleShell::version_command}, |
| 53 | {CHECKSUM("mem"), &SimpleShell::mem_command}, |
| 54 | {CHECKSUM("get"), &SimpleShell::get_command}, |
| 55 | {CHECKSUM("set_temp"), &SimpleShell::set_temp_command}, |
| 56 | {CHECKSUM("net"), &SimpleShell::net_command}, |
| 57 | |
| 58 | // unknown command |
| 59 | {0, NULL} |
| 60 | }; |
| 61 | |
| 62 | // Adam Greens heap walk from http://mbed.org/forum/mbed/topic/2701/?page=4#comment-22556 |
| 63 | static uint32_t heapWalk(StreamOutput *stream, bool verbose) |
| 64 | { |
| 65 | uint32_t chunkNumber = 1; |
| 66 | // The __end__ linker symbol points to the beginning of the heap. |
| 67 | uint32_t chunkCurr = (uint32_t)&__end__; |
| 68 | // __malloc_free_list is the head pointer to newlib-nano's link list of free chunks. |
| 69 | uint32_t freeCurr = __malloc_free_list; |
| 70 | // Calling _sbrk() with 0 reserves no more memory but it returns the current top of heap. |
| 71 | uint32_t heapEnd = _sbrk(0); |
| 72 | // accumulate totals |
| 73 | uint32_t freeSize = 0; |
| 74 | uint32_t usedSize = 0; |
| 75 | |
| 76 | stream->printf("Used Heap Size: %lu\n", heapEnd - chunkCurr); |
| 77 | |
| 78 | // Walk through the chunks until we hit the end of the heap. |
| 79 | while (chunkCurr < heapEnd) { |
| 80 | // Assume the chunk is in use. Will update later. |
| 81 | int isChunkFree = 0; |
| 82 | // The first 32-bit word in a chunk is the size of the allocation. newlib-nano over allocates by 8 bytes. |
| 83 | // 4 bytes for this 32-bit chunk size and another 4 bytes to allow for 8 byte-alignment of returned pointer. |
| 84 | uint32_t chunkSize = *(uint32_t *)chunkCurr; |
| 85 | // The start of the next chunk is right after the end of this one. |
| 86 | uint32_t chunkNext = chunkCurr + chunkSize; |
| 87 | |
| 88 | // The free list is sorted by address. |
| 89 | // Check to see if we have found the next free chunk in the heap. |
| 90 | if (chunkCurr == freeCurr) { |
| 91 | // Chunk is free so flag it as such. |
| 92 | isChunkFree = 1; |
| 93 | // The second 32-bit word in a free chunk is a pointer to the next free chunk (again sorted by address). |
| 94 | freeCurr = *(uint32_t *)(freeCurr + 4); |
| 95 | } |
| 96 | |
| 97 | // Skip past the 32-bit size field in the chunk header. |
| 98 | chunkCurr += 4; |
| 99 | // 8-byte align the data pointer. |
| 100 | chunkCurr = (chunkCurr + 7) & ~7; |
| 101 | // newlib-nano over allocates by 8 bytes, 4 bytes for the 32-bit chunk size and another 4 bytes to allow for 8 |
| 102 | // byte-alignment of the returned pointer. |
| 103 | chunkSize -= 8; |
| 104 | if (verbose) |
| 105 | stream->printf(" Chunk: %lu Address: 0x%08lX Size: %lu %s\n", chunkNumber, chunkCurr, chunkSize, isChunkFree ? "CHUNK FREE" : ""); |
| 106 | |
| 107 | if (isChunkFree) freeSize += chunkSize; |
| 108 | else usedSize += chunkSize; |
| 109 | |
| 110 | chunkCurr = chunkNext; |
| 111 | chunkNumber++; |
| 112 | } |
| 113 | stream->printf("Allocated: %lu, Free: %lu\r\n", usedSize, freeSize); |
| 114 | return freeSize; |
| 115 | } |
| 116 | |
| 117 | |
| 118 | void SimpleShell::on_module_loaded() |
| 119 | { |
| 120 | this->current_path = "/"; |
| 121 | this->register_for_event(ON_CONSOLE_LINE_RECEIVED); |
| 122 | this->register_for_event(ON_GCODE_RECEIVED); |
| 123 | this->register_for_event(ON_SECOND_TICK); |
| 124 | |
| 125 | this->reset_delay_secs = 0; |
| 126 | } |
| 127 | |
| 128 | void SimpleShell::on_second_tick(void *) |
| 129 | { |
| 130 | // we are timing out for the reset |
| 131 | if (this->reset_delay_secs > 0) { |
| 132 | if (--this->reset_delay_secs == 0) { |
| 133 | system_reset(false); |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | void SimpleShell::on_gcode_received(void *argument) |
| 139 | { |
| 140 | Gcode *gcode = static_cast<Gcode *>(argument); |
| 141 | string args= get_arguments(gcode->command); |
| 142 | |
| 143 | if (gcode->has_m) { |
| 144 | if (gcode->m == 20) { // list sd card |
| 145 | gcode->mark_as_taken(); |
| 146 | gcode->stream->printf("Begin file list\r\n"); |
| 147 | ls_command("/sd", gcode->stream); |
| 148 | gcode->stream->printf("End file list\r\n"); |
| 149 | |
| 150 | } else if (gcode->m == 30) { // remove file |
| 151 | gcode->mark_as_taken(); |
| 152 | rm_command("/sd/" + args, gcode->stream); |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | bool SimpleShell::parse_command(unsigned short cs, string args, StreamOutput *stream) |
| 158 | { |
| 159 | for (ptentry_t *p = commands_table; p->pfunc != NULL; ++p) { |
| 160 | if (cs == p->command_cs) { |
| 161 | PFUNC fnc= p->pfunc; |
| 162 | (this->*fnc)(args, stream); |
| 163 | return true; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | return false; |
| 168 | } |
| 169 | |
| 170 | // When a new line is received, check if it is a command, and if it is, act upon it |
| 171 | void SimpleShell::on_console_line_received( void *argument ) |
| 172 | { |
| 173 | SerialMessage new_message = *static_cast<SerialMessage *>(argument); |
| 174 | |
| 175 | // ignore comments |
| 176 | if (new_message.message[0] == ';') return; |
| 177 | |
| 178 | string possible_command = new_message.message; |
| 179 | |
| 180 | //new_message.stream->printf("Received %s\r\n", possible_command.c_str()); |
| 181 | |
| 182 | unsigned short check_sum = get_checksum( possible_command.substr(0, possible_command.find_first_of(" \r\n")) ); // todo: put this method somewhere more convenient |
| 183 | |
| 184 | // find command and execute it |
| 185 | parse_command(check_sum, get_arguments(possible_command), new_message.stream); |
| 186 | } |
| 187 | |
| 188 | // Convert a path indication ( absolute or relative ) into a path ( absolute ) |
| 189 | string SimpleShell::absolute_from_relative( string path ) |
| 190 | { |
| 191 | if ( path[0] == '/' ) { |
| 192 | return path; |
| 193 | } |
| 194 | if ( path[0] == '.' ) { |
| 195 | return this->current_path; |
| 196 | } |
| 197 | return this->current_path + path; |
| 198 | } |
| 199 | |
| 200 | // Act upon an ls command |
| 201 | // Convert the first parameter into an absolute path, then list the files in that path |
| 202 | void SimpleShell::ls_command( string parameters, StreamOutput *stream ) |
| 203 | { |
| 204 | string folder = this->absolute_from_relative( parameters ); |
| 205 | DIR *d; |
| 206 | struct dirent *p; |
| 207 | d = opendir(folder.c_str()); |
| 208 | if (d != NULL) { |
| 209 | while ((p = readdir(d)) != NULL) { |
| 210 | stream->printf("%s\r\n", lc(string(p->d_name)).c_str()); |
| 211 | } |
| 212 | closedir(d); |
| 213 | } else { |
| 214 | stream->printf("Could not open directory %s \r\n", folder.c_str()); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | // Delete a file |
| 219 | void SimpleShell::rm_command( string parameters, StreamOutput *stream ) |
| 220 | { |
| 221 | const char *fn= this->absolute_from_relative(shift_parameter( parameters )).c_str(); |
| 222 | int s = remove(fn); |
| 223 | if (s != 0) stream->printf("Could not delete %s \r\n", fn); |
| 224 | } |
| 225 | |
| 226 | // Change current absolute path to provided path |
| 227 | void SimpleShell::cd_command( string parameters, StreamOutput *stream ) |
| 228 | { |
| 229 | string folder = this->absolute_from_relative( parameters ); |
| 230 | if ( folder[folder.length() - 1] != '/' ) { |
| 231 | folder += "/"; |
| 232 | } |
| 233 | DIR *d; |
| 234 | d = opendir(folder.c_str()); |
| 235 | if (d == NULL) { |
| 236 | stream->printf("Could not open directory %s \r\n", folder.c_str() ); |
| 237 | } else { |
| 238 | this->current_path = folder; |
| 239 | closedir(d); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | // Responds with the present working directory |
| 244 | void SimpleShell::pwd_command( string parameters, StreamOutput *stream ) |
| 245 | { |
| 246 | stream->printf("%s\r\n", this->current_path.c_str()); |
| 247 | } |
| 248 | |
| 249 | // Output the contents of a file, first parameter is the filename, second is the limit ( in number of lines to output ) |
| 250 | void SimpleShell::cat_command( string parameters, StreamOutput *stream ) |
| 251 | { |
| 252 | // Get parameters ( filename and line limit ) |
| 253 | string filename = this->absolute_from_relative(shift_parameter( parameters )); |
| 254 | string limit_paramater = shift_parameter( parameters ); |
| 255 | int limit = -1; |
| 256 | if ( limit_paramater != "" ) { |
| 257 | char *e = NULL; |
| 258 | limit = strtol(limit_paramater.c_str(), &e, 10); |
| 259 | if (e <= limit_paramater.c_str()) |
| 260 | limit = -1; |
| 261 | } |
| 262 | |
| 263 | // Open file |
| 264 | FILE *lp = fopen(filename.c_str(), "r"); |
| 265 | if (lp == NULL) { |
| 266 | stream->printf("File not found: %s\r\n", filename.c_str()); |
| 267 | return; |
| 268 | } |
| 269 | string buffer; |
| 270 | int c; |
| 271 | int newlines = 0; |
| 272 | int linecnt= 0; |
| 273 | // Print each line of the file |
| 274 | while ((c = fgetc (lp)) != EOF) { |
| 275 | buffer.append((char *)&c, 1); |
| 276 | if ( char(c) == '\n' || ++linecnt > 80) { |
| 277 | newlines++; |
| 278 | stream->puts(buffer.c_str()); |
| 279 | buffer.clear(); |
| 280 | if(linecnt > 80) linecnt= 0; |
| 281 | } |
| 282 | if ( newlines == limit ) { |
| 283 | break; |
| 284 | } |
| 285 | }; |
| 286 | fclose(lp); |
| 287 | |
| 288 | } |
| 289 | |
| 290 | // show free memory |
| 291 | void SimpleShell::mem_command( string parameters, StreamOutput *stream) |
| 292 | { |
| 293 | bool verbose = shift_parameter( parameters ).find_first_of("Vv") != string::npos ; |
| 294 | unsigned long heap = (unsigned long)_sbrk(0); |
| 295 | unsigned long m = g_maximumHeapAddress - heap; |
| 296 | stream->printf("Unused Heap: %lu bytes\r\n", m); |
| 297 | |
| 298 | uint32_t f= heapWalk(stream, verbose); |
| 299 | stream->printf("Total Free RAM: %lu bytes\r\n", m + f); |
| 300 | |
| 301 | stream->printf("Free AHB0: %lu, AHB1: %lu\r\n", AHB0.free(), AHB1.free()); |
| 302 | if (verbose) |
| 303 | { |
| 304 | AHB0.debug(stream); |
| 305 | AHB1.debug(stream); |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | static uint32_t getDeviceType() |
| 310 | { |
| 311 | #define IAP_LOCATION 0x1FFF1FF1 |
| 312 | uint32_t command[1]; |
| 313 | uint32_t result[5]; |
| 314 | typedef void (*IAP)(uint32_t *, uint32_t *); |
| 315 | IAP iap = (IAP) IAP_LOCATION; |
| 316 | |
| 317 | __disable_irq(); |
| 318 | |
| 319 | command[0] = 54; |
| 320 | iap(command, result); |
| 321 | |
| 322 | __enable_irq(); |
| 323 | |
| 324 | return result[1]; |
| 325 | } |
| 326 | |
| 327 | // get network config |
| 328 | void SimpleShell::net_command( string parameters, StreamOutput *stream) |
| 329 | { |
| 330 | void *returned_data; |
| 331 | bool ok= THEKERNEL->public_data->get_value( network_checksum, get_ipconfig_checksum, &returned_data ); |
| 332 | if(ok) { |
| 333 | char *str= (char *)returned_data; |
| 334 | stream->printf("%s\r\n", str); |
| 335 | free(str); |
| 336 | |
| 337 | }else{ |
| 338 | stream->printf("No network detected\n"); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | // print out build version |
| 343 | void SimpleShell::version_command( string parameters, StreamOutput *stream) |
| 344 | { |
| 345 | Version vers; |
| 346 | uint32_t dev = getDeviceType(); |
| 347 | const char *mcu = (dev & 0x00100000) ? "LPC1769" : "LPC1768"; |
| 348 | stream->printf("Build version: %s, Build date: %s, MCU: %s, System Clock: %ldMHz\r\n", vers.get_build(), vers.get_build_date(), mcu, SystemCoreClock / 1000000); |
| 349 | } |
| 350 | |
| 351 | // Reset the system |
| 352 | void SimpleShell::reset_command( string parameters, StreamOutput *stream) |
| 353 | { |
| 354 | stream->printf("Smoothie out. Peace. Rebooting in 5 seconds...\r\n"); |
| 355 | this->reset_delay_secs = 5; // reboot in 5 seconds |
| 356 | } |
| 357 | |
| 358 | // go into dfu boot mode |
| 359 | void SimpleShell::dfu_command( string parameters, StreamOutput *stream) |
| 360 | { |
| 361 | stream->printf("Entering boot mode...\r\n"); |
| 362 | system_reset(true); |
| 363 | } |
| 364 | |
| 365 | // Break out into the MRI debugging system |
| 366 | void SimpleShell::break_command( string parameters, StreamOutput *stream) |
| 367 | { |
| 368 | stream->printf("Entering MRI debug mode...\r\n"); |
| 369 | __debugbreak(); |
| 370 | } |
| 371 | |
| 372 | // used to test out the get public data events |
| 373 | void SimpleShell::get_command( string parameters, StreamOutput *stream) |
| 374 | { |
| 375 | int what = get_checksum(shift_parameter( parameters )); |
| 376 | void *returned_data; |
| 377 | |
| 378 | if (what == get_temp_command_checksum) { |
| 379 | string type = shift_parameter( parameters ); |
| 380 | bool ok = THEKERNEL->public_data->get_value( temperature_control_checksum, get_checksum(type), current_temperature_checksum, &returned_data ); |
| 381 | |
| 382 | if (ok) { |
| 383 | struct pad_temperature temp = *static_cast<struct pad_temperature *>(returned_data); |
| 384 | stream->printf("%s temp: %f/%f @%d\r\n", type.c_str(), temp.current_temperature, temp.target_temperature, temp.pwm); |
| 385 | } else { |
| 386 | stream->printf("%s is not a known temperature device\r\n", type.c_str()); |
| 387 | } |
| 388 | |
| 389 | } else if (what == get_pos_command_checksum) { |
| 390 | bool ok = THEKERNEL->public_data->get_value( robot_checksum, current_position_checksum, &returned_data ); |
| 391 | |
| 392 | if (ok) { |
| 393 | float *pos = static_cast<float *>(returned_data); |
| 394 | stream->printf("Position X: %f, Y: %f, Z: %f\r\n", pos[0], pos[1], pos[2]); |
| 395 | |
| 396 | } else { |
| 397 | stream->printf("get pos command failed\r\n"); |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | // used to test out the get public data events |
| 403 | void SimpleShell::set_temp_command( string parameters, StreamOutput *stream) |
| 404 | { |
| 405 | string type = shift_parameter( parameters ); |
| 406 | string temp = shift_parameter( parameters ); |
| 407 | float t = temp.empty() ? 0.0 : strtof(temp.c_str(), NULL); |
| 408 | bool ok = THEKERNEL->public_data->set_value( temperature_control_checksum, get_checksum(type), &t ); |
| 409 | |
| 410 | if (ok) { |
| 411 | stream->printf("%s temp set to: %3.1f\r\n", type.c_str(), t); |
| 412 | } else { |
| 413 | stream->printf("%s is not a known temperature device\r\n", type.c_str()); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | void SimpleShell::help_command( string parameters, StreamOutput *stream ) |
| 418 | { |
| 419 | stream->printf("Commands:\r\n"); |
| 420 | stream->printf("version\r\n"); |
| 421 | stream->printf("mem [-v]\r\n"); |
| 422 | stream->printf("ls [folder]\r\n"); |
| 423 | stream->printf("cd folder\r\n"); |
| 424 | stream->printf("pwd\r\n"); |
| 425 | stream->printf("cat file [limit]\r\n"); |
| 426 | stream->printf("rm file\r\n"); |
| 427 | stream->printf("play file [-v]\r\n"); |
| 428 | stream->printf("progress - shows progress of current play\r\n"); |
| 429 | stream->printf("abort - abort currently playing file\r\n"); |
| 430 | stream->printf("reset - reset smoothie\r\n"); |
| 431 | stream->printf("dfu - enter dfu boot loader\r\n"); |
| 432 | stream->printf("break - break into debugger\r\n"); |
| 433 | stream->printf("config-get [<configuration_source>] <configuration_setting>\r\n"); |
| 434 | stream->printf("config-set [<configuration_source>] <configuration_setting> <value>\r\n"); |
| 435 | stream->printf("config-load [<file_name>]\r\n"); |
| 436 | stream->printf("get temp [bed|hotend]\r\n"); |
| 437 | stream->printf("set_temp bed|hotend 185\r\n"); |
| 438 | stream->printf("get pos\r\n"); |
| 439 | stream->printf("net\r\n"); |
| 440 | } |
| 441 | |