meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerk40t/__init__.py +1 -1
- meerk40t/balormk/balor_params.py +167 -167
- meerk40t/balormk/clone_loader.py +457 -457
- meerk40t/balormk/controller.py +1566 -1512
- meerk40t/balormk/cylindermod.py +64 -0
- meerk40t/balormk/device.py +966 -1959
- meerk40t/balormk/driver.py +778 -591
- meerk40t/balormk/galvo_commands.py +1194 -0
- meerk40t/balormk/gui/balorconfig.py +237 -111
- meerk40t/balormk/gui/balorcontroller.py +191 -184
- meerk40t/balormk/gui/baloroperationproperties.py +116 -115
- meerk40t/balormk/gui/corscene.py +845 -0
- meerk40t/balormk/gui/gui.py +179 -147
- meerk40t/balormk/livelightjob.py +466 -382
- meerk40t/balormk/mock_connection.py +131 -109
- meerk40t/balormk/plugin.py +133 -135
- meerk40t/balormk/usb_connection.py +306 -301
- meerk40t/camera/__init__.py +1 -1
- meerk40t/camera/camera.py +514 -397
- meerk40t/camera/gui/camerapanel.py +1241 -1095
- meerk40t/camera/gui/gui.py +58 -58
- meerk40t/camera/plugin.py +441 -399
- meerk40t/ch341/__init__.py +27 -27
- meerk40t/ch341/ch341device.py +628 -628
- meerk40t/ch341/libusb.py +595 -589
- meerk40t/ch341/mock.py +171 -171
- meerk40t/ch341/windriver.py +157 -157
- meerk40t/constants.py +13 -0
- meerk40t/core/__init__.py +1 -1
- meerk40t/core/bindalias.py +550 -539
- meerk40t/core/core.py +47 -47
- meerk40t/core/cutcode/cubiccut.py +73 -73
- meerk40t/core/cutcode/cutcode.py +315 -312
- meerk40t/core/cutcode/cutgroup.py +141 -137
- meerk40t/core/cutcode/cutobject.py +192 -185
- meerk40t/core/cutcode/dwellcut.py +37 -37
- meerk40t/core/cutcode/gotocut.py +29 -29
- meerk40t/core/cutcode/homecut.py +29 -29
- meerk40t/core/cutcode/inputcut.py +34 -34
- meerk40t/core/cutcode/linecut.py +33 -33
- meerk40t/core/cutcode/outputcut.py +34 -34
- meerk40t/core/cutcode/plotcut.py +335 -335
- meerk40t/core/cutcode/quadcut.py +61 -61
- meerk40t/core/cutcode/rastercut.py +168 -148
- meerk40t/core/cutcode/waitcut.py +34 -34
- meerk40t/core/cutplan.py +1843 -1316
- meerk40t/core/drivers.py +330 -329
- meerk40t/core/elements/align.py +801 -669
- meerk40t/core/elements/branches.py +1858 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4595 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4315 -3617
- meerk40t/core/elements/files.py +117 -64
- meerk40t/core/elements/geometry.py +473 -224
- meerk40t/core/elements/grid.py +467 -316
- meerk40t/core/elements/materials.py +158 -94
- meerk40t/core/elements/notes.py +50 -38
- meerk40t/core/elements/offset_clpr.py +934 -912
- meerk40t/core/elements/offset_mk.py +963 -955
- meerk40t/core/elements/penbox.py +339 -267
- meerk40t/core/elements/placements.py +300 -83
- meerk40t/core/elements/render.py +785 -687
- meerk40t/core/elements/shapes.py +2618 -2092
- meerk40t/core/elements/testcases.py +105 -0
- meerk40t/core/elements/trace.py +651 -563
- meerk40t/core/elements/tree_commands.py +415 -409
- meerk40t/core/elements/undo_redo.py +116 -58
- meerk40t/core/elements/wordlist.py +319 -200
- meerk40t/core/exceptions.py +9 -9
- meerk40t/core/laserjob.py +220 -220
- meerk40t/core/logging.py +63 -63
- meerk40t/core/node/blobnode.py +83 -86
- meerk40t/core/node/bootstrap.py +105 -103
- meerk40t/core/node/branch_elems.py +40 -31
- meerk40t/core/node/branch_ops.py +45 -38
- meerk40t/core/node/branch_regmark.py +48 -41
- meerk40t/core/node/cutnode.py +29 -32
- meerk40t/core/node/effect_hatch.py +375 -257
- meerk40t/core/node/effect_warp.py +398 -0
- meerk40t/core/node/effect_wobble.py +441 -309
- meerk40t/core/node/elem_ellipse.py +404 -309
- meerk40t/core/node/elem_image.py +1082 -801
- meerk40t/core/node/elem_line.py +358 -292
- meerk40t/core/node/elem_path.py +259 -201
- meerk40t/core/node/elem_point.py +129 -102
- meerk40t/core/node/elem_polyline.py +310 -246
- meerk40t/core/node/elem_rect.py +376 -286
- meerk40t/core/node/elem_text.py +445 -418
- meerk40t/core/node/filenode.py +59 -40
- meerk40t/core/node/groupnode.py +138 -74
- meerk40t/core/node/image_processed.py +777 -766
- meerk40t/core/node/image_raster.py +156 -113
- meerk40t/core/node/layernode.py +31 -31
- meerk40t/core/node/mixins.py +135 -107
- meerk40t/core/node/node.py +1427 -1304
- meerk40t/core/node/nutils.py +117 -114
- meerk40t/core/node/op_cut.py +463 -335
- meerk40t/core/node/op_dots.py +296 -251
- meerk40t/core/node/op_engrave.py +414 -311
- meerk40t/core/node/op_image.py +755 -369
- meerk40t/core/node/op_raster.py +787 -522
- meerk40t/core/node/place_current.py +37 -40
- meerk40t/core/node/place_point.py +329 -126
- meerk40t/core/node/refnode.py +58 -47
- meerk40t/core/node/rootnode.py +225 -219
- meerk40t/core/node/util_console.py +48 -48
- meerk40t/core/node/util_goto.py +84 -65
- meerk40t/core/node/util_home.py +61 -61
- meerk40t/core/node/util_input.py +102 -102
- meerk40t/core/node/util_output.py +102 -102
- meerk40t/core/node/util_wait.py +65 -65
- meerk40t/core/parameters.py +709 -707
- meerk40t/core/planner.py +875 -785
- meerk40t/core/plotplanner.py +656 -652
- meerk40t/core/space.py +120 -113
- meerk40t/core/spoolers.py +706 -705
- meerk40t/core/svg_io.py +1836 -1549
- meerk40t/core/treeop.py +534 -445
- meerk40t/core/undos.py +278 -124
- meerk40t/core/units.py +784 -680
- meerk40t/core/view.py +393 -322
- meerk40t/core/webhelp.py +62 -62
- meerk40t/core/wordlist.py +513 -504
- meerk40t/cylinder/cylinder.py +247 -0
- meerk40t/cylinder/gui/cylindersettings.py +41 -0
- meerk40t/cylinder/gui/gui.py +24 -0
- meerk40t/device/__init__.py +1 -1
- meerk40t/device/basedevice.py +322 -123
- meerk40t/device/devicechoices.py +50 -0
- meerk40t/device/dummydevice.py +163 -128
- meerk40t/device/gui/defaultactions.py +618 -602
- meerk40t/device/gui/effectspanel.py +114 -0
- meerk40t/device/gui/formatterpanel.py +253 -290
- meerk40t/device/gui/warningpanel.py +337 -260
- meerk40t/device/mixins.py +13 -13
- meerk40t/dxf/__init__.py +1 -1
- meerk40t/dxf/dxf_io.py +766 -554
- meerk40t/dxf/plugin.py +47 -35
- meerk40t/external_plugins.py +79 -79
- meerk40t/external_plugins_build.py +28 -28
- meerk40t/extra/cag.py +112 -116
- meerk40t/extra/coolant.py +403 -0
- meerk40t/extra/encode_detect.py +204 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +834 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +628 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +283 -0
- meerk40t/extra/param_functions.py +1542 -1556
- meerk40t/extra/potrace.py +257 -253
- meerk40t/extra/serial_exchange.py +118 -0
- meerk40t/extra/updater.py +602 -453
- meerk40t/extra/vectrace.py +147 -146
- meerk40t/extra/winsleep.py +83 -83
- meerk40t/extra/xcs_reader.py +597 -0
- meerk40t/fill/fills.py +781 -335
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +614 -567
- meerk40t/grbl/control.py +87 -87
- meerk40t/grbl/controller.py +990 -903
- meerk40t/grbl/device.py +1084 -768
- meerk40t/grbl/driver.py +989 -771
- meerk40t/grbl/emulator.py +532 -497
- meerk40t/grbl/gcodejob.py +783 -767
- meerk40t/grbl/gui/grblconfiguration.py +373 -298
- meerk40t/grbl/gui/grblcontroller.py +485 -271
- meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
- meerk40t/grbl/gui/grbloperationconfig.py +105 -0
- meerk40t/grbl/gui/gui.py +147 -116
- meerk40t/grbl/interpreter.py +44 -44
- meerk40t/grbl/loader.py +22 -22
- meerk40t/grbl/mock_connection.py +56 -56
- meerk40t/grbl/plugin.py +294 -264
- meerk40t/grbl/serial_connection.py +93 -88
- meerk40t/grbl/tcp_connection.py +81 -79
- meerk40t/grbl/ws_connection.py +112 -0
- meerk40t/gui/__init__.py +1 -1
- meerk40t/gui/about.py +2042 -296
- meerk40t/gui/alignment.py +1644 -1608
- meerk40t/gui/autoexec.py +199 -0
- meerk40t/gui/basicops.py +791 -670
- meerk40t/gui/bufferview.py +77 -71
- meerk40t/gui/busy.py +232 -133
- meerk40t/gui/choicepropertypanel.py +1662 -1469
- meerk40t/gui/consolepanel.py +706 -542
- meerk40t/gui/devicepanel.py +687 -581
- meerk40t/gui/dialogoptions.py +110 -107
- meerk40t/gui/executejob.py +316 -306
- meerk40t/gui/fonts.py +90 -90
- meerk40t/gui/functionwrapper.py +252 -0
- meerk40t/gui/gui_mixins.py +729 -0
- meerk40t/gui/guicolors.py +205 -182
- meerk40t/gui/help_assets/help_assets.py +218 -201
- meerk40t/gui/helper.py +154 -0
- meerk40t/gui/hersheymanager.py +1440 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +897 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2944 -0
- meerk40t/gui/materialtest.py +1722 -1694
- meerk40t/gui/mkdebug.py +646 -359
- meerk40t/gui/mwindow.py +163 -140
- meerk40t/gui/navigationpanels.py +2605 -2467
- meerk40t/gui/notes.py +143 -142
- meerk40t/gui/opassignment.py +414 -410
- meerk40t/gui/operation_info.py +310 -299
- meerk40t/gui/plugin.py +500 -328
- meerk40t/gui/position.py +714 -669
- meerk40t/gui/preferences.py +901 -650
- meerk40t/gui/propertypanels/attributes.py +1461 -1131
- meerk40t/gui/propertypanels/blobproperty.py +117 -114
- meerk40t/gui/propertypanels/consoleproperty.py +83 -80
- meerk40t/gui/propertypanels/gotoproperty.py +77 -0
- meerk40t/gui/propertypanels/groupproperties.py +223 -217
- meerk40t/gui/propertypanels/hatchproperty.py +489 -469
- meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
- meerk40t/gui/propertypanels/inputproperty.py +59 -58
- meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
- meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
- meerk40t/gui/propertypanels/outputproperty.py +59 -58
- meerk40t/gui/propertypanels/pathproperty.py +389 -380
- meerk40t/gui/propertypanels/placementproperty.py +1214 -383
- meerk40t/gui/propertypanels/pointproperty.py +140 -136
- meerk40t/gui/propertypanels/propertywindow.py +313 -181
- meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
- meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
- meerk40t/gui/propertypanels/textproperty.py +770 -755
- meerk40t/gui/propertypanels/waitproperty.py +56 -55
- meerk40t/gui/propertypanels/warpproperty.py +121 -0
- meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
- meerk40t/gui/ribbon.py +2471 -2210
- meerk40t/gui/scene/scene.py +1100 -1051
- meerk40t/gui/scene/sceneconst.py +22 -22
- meerk40t/gui/scene/scenepanel.py +439 -349
- meerk40t/gui/scene/scenespacewidget.py +365 -365
- meerk40t/gui/scene/widget.py +518 -505
- meerk40t/gui/scenewidgets/affinemover.py +215 -215
- meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
- meerk40t/gui/scenewidgets/bedwidget.py +120 -97
- meerk40t/gui/scenewidgets/elementswidget.py +137 -107
- meerk40t/gui/scenewidgets/gridwidget.py +785 -745
- meerk40t/gui/scenewidgets/guidewidget.py +765 -765
- meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
- meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
- meerk40t/gui/scenewidgets/nodeselector.py +28 -28
- meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
- meerk40t/gui/simpleui.py +362 -333
- meerk40t/gui/simulation.py +2451 -2094
- meerk40t/gui/snapoptions.py +208 -203
- meerk40t/gui/spoolerpanel.py +1227 -1180
- meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
- meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
- meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
- meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
- meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
- meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
- meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
- meerk40t/gui/themes.py +200 -78
- meerk40t/gui/tips.py +590 -0
- meerk40t/gui/toolwidgets/circlebrush.py +35 -35
- meerk40t/gui/toolwidgets/toolcircle.py +248 -242
- meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
- meerk40t/gui/toolwidgets/tooldraw.py +97 -90
- meerk40t/gui/toolwidgets/toolellipse.py +219 -212
- meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
- meerk40t/gui/toolwidgets/toolline.py +39 -144
- meerk40t/gui/toolwidgets/toollinetext.py +79 -236
- meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
- meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
- meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
- meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
- meerk40t/gui/toolwidgets/toolparameter.py +754 -668
- meerk40t/gui/toolwidgets/toolplacement.py +108 -108
- meerk40t/gui/toolwidgets/toolpoint.py +68 -59
- meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
- meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
- meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
- meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
- meerk40t/gui/toolwidgets/toolrect.py +211 -207
- meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
- meerk40t/gui/toolwidgets/toolribbon.py +598 -113
- meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
- meerk40t/gui/toolwidgets/tooltext.py +98 -89
- meerk40t/gui/toolwidgets/toolvector.py +213 -204
- meerk40t/gui/toolwidgets/toolwidget.py +39 -39
- meerk40t/gui/usbconnect.py +98 -91
- meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
- meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
- meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
- meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
- meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
- meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
- meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
- meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
- meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
- meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
- meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
- meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
- meerk40t/gui/wordlisteditor.py +985 -931
- meerk40t/gui/wxmeerk40t.py +1447 -1169
- meerk40t/gui/wxmmain.py +5644 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1631 -1453
- meerk40t/gui/wxmtree.py +2416 -2089
- meerk40t/gui/wxutils.py +1769 -1099
- meerk40t/gui/zmatrix.py +102 -102
- meerk40t/image/__init__.py +1 -1
- meerk40t/image/dither.py +429 -0
- meerk40t/image/imagetools.py +2793 -2269
- meerk40t/internal_plugins.py +150 -130
- meerk40t/kernel/__init__.py +63 -12
- meerk40t/kernel/channel.py +259 -212
- meerk40t/kernel/context.py +538 -538
- meerk40t/kernel/exceptions.py +41 -41
- meerk40t/kernel/functions.py +463 -414
- meerk40t/kernel/jobs.py +100 -100
- meerk40t/kernel/kernel.py +3828 -3571
- meerk40t/kernel/lifecycles.py +71 -71
- meerk40t/kernel/module.py +49 -49
- meerk40t/kernel/service.py +147 -147
- meerk40t/kernel/settings.py +383 -343
- meerk40t/lihuiyu/controller.py +883 -876
- meerk40t/lihuiyu/device.py +1181 -1069
- meerk40t/lihuiyu/driver.py +1466 -1372
- meerk40t/lihuiyu/gui/gui.py +127 -106
- meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
- meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
- meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
- meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
- meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
- meerk40t/lihuiyu/interpreter.py +53 -53
- meerk40t/lihuiyu/laserspeed.py +450 -450
- meerk40t/lihuiyu/loader.py +90 -90
- meerk40t/lihuiyu/parser.py +404 -404
- meerk40t/lihuiyu/plugin.py +101 -102
- meerk40t/lihuiyu/tcp_connection.py +111 -109
- meerk40t/main.py +231 -165
- meerk40t/moshi/builder.py +788 -781
- meerk40t/moshi/controller.py +505 -499
- meerk40t/moshi/device.py +495 -442
- meerk40t/moshi/driver.py +862 -696
- meerk40t/moshi/gui/gui.py +78 -76
- meerk40t/moshi/gui/moshicontrollergui.py +538 -522
- meerk40t/moshi/gui/moshidrivergui.py +87 -75
- meerk40t/moshi/plugin.py +43 -43
- meerk40t/network/console_server.py +140 -57
- meerk40t/network/kernelserver.py +10 -9
- meerk40t/network/tcp_server.py +142 -140
- meerk40t/network/udp_server.py +103 -77
- meerk40t/network/web_server.py +404 -0
- meerk40t/newly/controller.py +1158 -1144
- meerk40t/newly/device.py +874 -732
- meerk40t/newly/driver.py +540 -412
- meerk40t/newly/gui/gui.py +219 -188
- meerk40t/newly/gui/newlyconfig.py +116 -101
- meerk40t/newly/gui/newlycontroller.py +193 -186
- meerk40t/newly/gui/operationproperties.py +51 -51
- meerk40t/newly/mock_connection.py +82 -82
- meerk40t/newly/newly_params.py +56 -56
- meerk40t/newly/plugin.py +1214 -1246
- meerk40t/newly/usb_connection.py +322 -322
- meerk40t/rotary/gui/gui.py +52 -46
- meerk40t/rotary/gui/rotarysettings.py +240 -232
- meerk40t/rotary/rotary.py +202 -98
- meerk40t/ruida/control.py +291 -91
- meerk40t/ruida/controller.py +138 -1088
- meerk40t/ruida/device.py +676 -231
- meerk40t/ruida/driver.py +534 -472
- meerk40t/ruida/emulator.py +1494 -1491
- meerk40t/ruida/exceptions.py +4 -4
- meerk40t/ruida/gui/gui.py +71 -76
- meerk40t/ruida/gui/ruidaconfig.py +239 -72
- meerk40t/ruida/gui/ruidacontroller.py +187 -184
- meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
- meerk40t/ruida/loader.py +54 -52
- meerk40t/ruida/mock_connection.py +57 -109
- meerk40t/ruida/plugin.py +124 -87
- meerk40t/ruida/rdjob.py +2084 -945
- meerk40t/ruida/serial_connection.py +116 -0
- meerk40t/ruida/tcp_connection.py +146 -0
- meerk40t/ruida/udp_connection.py +73 -0
- meerk40t/svgelements.py +9671 -9669
- meerk40t/tools/driver_to_path.py +584 -579
- meerk40t/tools/geomstr.py +5583 -4680
- meerk40t/tools/jhfparser.py +357 -292
- meerk40t/tools/kerftest.py +904 -890
- meerk40t/tools/livinghinges.py +1168 -1033
- meerk40t/tools/pathtools.py +987 -949
- meerk40t/tools/pmatrix.py +234 -0
- meerk40t/tools/pointfinder.py +942 -942
- meerk40t/tools/polybool.py +941 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +1047 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
- meerk40t-0.9.7020.dist-info/RECORD +446 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
- meerk40t/balormk/elementlightjob.py +0 -159
- meerk40t-0.9.3001.dist-info/RECORD +0 -437
- test/bootstrap.py +0 -63
- test/test_cli.py +0 -12
- test/test_core_cutcode.py +0 -418
- test/test_core_elements.py +0 -144
- test/test_core_plotplanner.py +0 -397
- test/test_core_viewports.py +0 -312
- test/test_drivers_grbl.py +0 -108
- test/test_drivers_lihuiyu.py +0 -443
- test/test_drivers_newly.py +0 -113
- test/test_element_degenerate_points.py +0 -43
- test/test_elements_classify.py +0 -97
- test/test_elements_penbox.py +0 -22
- test/test_file_svg.py +0 -176
- test/test_fill.py +0 -155
- test/test_geomstr.py +0 -1523
- test/test_geomstr_nodes.py +0 -18
- test/test_imagetools_actualize.py +0 -306
- test/test_imagetools_wizard.py +0 -258
- test/test_kernel.py +0 -200
- test/test_laser_speeds.py +0 -3303
- test/test_length.py +0 -57
- test/test_lifecycle.py +0 -66
- test/test_operations.py +0 -251
- test/test_operations_hatch.py +0 -57
- test/test_ruida.py +0 -19
- test/test_spooler.py +0 -22
- test/test_tools_rasterplotter.py +0 -29
- test/test_wobble.py +0 -133
- test/test_zingl.py +0 -124
- {test → meerk40t/cylinder}/__init__.py +0 -0
- /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/grbl/controller.py
CHANGED
@@ -1,903 +1,990 @@
|
|
1
|
-
"""
|
2
|
-
GRBL Controller
|
3
|
-
|
4
|
-
Tasked with sending data to the different connection.
|
5
|
-
|
6
|
-
Validation Stages.
|
7
|
-
Stage 0, we are disconnected and invalid.
|
8
|
-
Stage 1, we are connected and need to check if we are GRBL send $
|
9
|
-
Stage 2, we parsed $ and need to try $$ $G
|
10
|
-
Stage 3, we successfully parsed $$
|
11
|
-
Stage 4, we successfully parsed $G, send ?
|
12
|
-
Stage 5, we successfully parsed ?
|
13
|
-
"""
|
14
|
-
import
|
15
|
-
import
|
16
|
-
import
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
short = f"Error #{code}"
|
103
|
-
if code == 1:
|
104
|
-
long = "GCode Command letter was not found."
|
105
|
-
elif code == 2:
|
106
|
-
long = "GCode Command value invalid or missing."
|
107
|
-
elif code == 3:
|
108
|
-
long = "Grbl '$' not recognized or supported."
|
109
|
-
elif code == 4:
|
110
|
-
long = "Negative value for an expected positive value."
|
111
|
-
elif code == 5:
|
112
|
-
long = "Homing fail. Homing not enabled in settings."
|
113
|
-
elif code == 6:
|
114
|
-
long = "Min step pulse must be greater than 3usec."
|
115
|
-
elif code == 7:
|
116
|
-
long = "EEPROM read failed. Default values used."
|
117
|
-
elif code == 8:
|
118
|
-
long = "Grbl '$' command Only valid when Idle."
|
119
|
-
elif code == 9:
|
120
|
-
long = "GCode commands invalid in alarm or jog state."
|
121
|
-
elif code == 10:
|
122
|
-
long = "Soft limits require homing to be enabled."
|
123
|
-
elif code == 11:
|
124
|
-
long = "Max characters per line exceeded. Ignored."
|
125
|
-
elif code == 12:
|
126
|
-
long = "Grbl '$' setting exceeds the maximum step rate."
|
127
|
-
elif code == 13:
|
128
|
-
long = "Safety door opened and door state initiated."
|
129
|
-
elif code == 14:
|
130
|
-
long = "Build info or start-up line > EEPROM line length"
|
131
|
-
elif code == 15:
|
132
|
-
long = "Jog target exceeds machine travel, ignored."
|
133
|
-
elif code == 16:
|
134
|
-
long = "Jog Cmd missing '=' or has prohibited GCode."
|
135
|
-
elif code == 17:
|
136
|
-
long = "Laser mode requires PWM output."
|
137
|
-
elif code == 20:
|
138
|
-
long = "Unsupported or invalid GCode command."
|
139
|
-
elif code == 21:
|
140
|
-
long = "> 1 GCode command in a modal group in block."
|
141
|
-
elif code == 22:
|
142
|
-
long = "Feed rate has not yet been set or is undefined."
|
143
|
-
elif code == 23:
|
144
|
-
long = "GCode command requires an integer value."
|
145
|
-
elif code == 24:
|
146
|
-
long = "> 1 GCode command using axis words found."
|
147
|
-
elif code == 25:
|
148
|
-
long = "Repeated GCode word found in block."
|
149
|
-
elif code == 26:
|
150
|
-
long = "No axis words found in command block."
|
151
|
-
elif code == 27:
|
152
|
-
long = "Line number value is invalid."
|
153
|
-
elif code == 28:
|
154
|
-
long = "GCode Cmd missing a required value word."
|
155
|
-
elif code == 29:
|
156
|
-
long = "G59.x WCS are not supported."
|
157
|
-
elif code == 30:
|
158
|
-
long = "G53 only valid with G0 and G1 motion modes."
|
159
|
-
elif code == 31:
|
160
|
-
long = "Unneeded Axis words found in block."
|
161
|
-
elif code == 32:
|
162
|
-
long = "G2/G3 arcs need >= 1 in-plane axis word."
|
163
|
-
elif code == 33:
|
164
|
-
long = "Motion command target is invalid."
|
165
|
-
elif code == 34:
|
166
|
-
long = "Arc radius value is invalid."
|
167
|
-
elif code == 35:
|
168
|
-
long = "G2/G3 arcs need >= 1 in-plane offset word."
|
169
|
-
elif code == 36:
|
170
|
-
long = "Unused value words found in block."
|
171
|
-
elif code == 37:
|
172
|
-
long = "G43.1 offset not assigned to tool length axis."
|
173
|
-
elif code == 38:
|
174
|
-
long = "Tool number greater than max value."
|
175
|
-
else:
|
176
|
-
long = f"
|
177
|
-
return short, long
|
178
|
-
|
179
|
-
|
180
|
-
def grbl_alarm_message(code):
|
181
|
-
if code == 1:
|
182
|
-
short = "Hard limit"
|
183
|
-
long = (
|
184
|
-
"Hard limit has been triggered."
|
185
|
-
+ " Machine position is likely lost due to sudden halt."
|
186
|
-
+ " Re-homing is highly recommended."
|
187
|
-
)
|
188
|
-
elif code == 2:
|
189
|
-
short = "Soft limit"
|
190
|
-
long = (
|
191
|
-
"Soft limit alarm. G-code motion target exceeds machine travel."
|
192
|
-
+ " Machine position retained. Alarm may be safely unlocked."
|
193
|
-
)
|
194
|
-
elif code == 3:
|
195
|
-
short = "Abort during cycle"
|
196
|
-
long = (
|
197
|
-
"Reset while in motion. Machine position is likely lost due to sudden halt."
|
198
|
-
+ " Re-homing is highly recommended. May be due to issuing g-code"
|
199
|
-
+ " commands that exceed the limit of the machine."
|
200
|
-
)
|
201
|
-
elif code == 4:
|
202
|
-
short = "Probe fail"
|
203
|
-
long = (
|
204
|
-
"Probe fail. Probe is not in the expected initial state before"
|
205
|
-
+ " starting probe cycle when G38.2 and G38.3 is not triggered"
|
206
|
-
+ " and G38.4 and G38.5 is triggered."
|
207
|
-
)
|
208
|
-
elif code == 5:
|
209
|
-
short = "Probe fail"
|
210
|
-
long = (
|
211
|
-
"Probe fail. Probe did not contact the workpiece within the programmed"
|
212
|
-
+ " travel for G38.2 and G38.4."
|
213
|
-
)
|
214
|
-
elif code == 6:
|
215
|
-
short = "Homing fail"
|
216
|
-
long = "Homing fail. The active homing cycle was reset."
|
217
|
-
elif code == 7:
|
218
|
-
short = "Homing fail"
|
219
|
-
long = "Homing fail. Safety door was opened during homing cycle."
|
220
|
-
elif code == 8:
|
221
|
-
short = "Homing fail"
|
222
|
-
long = (
|
223
|
-
"Homing fail. Pull off travel failed to clear limit switch."
|
224
|
-
+ " Try increasing pull-off setting or check wiring."
|
225
|
-
)
|
226
|
-
elif code == 9:
|
227
|
-
short = "Homing fail"
|
228
|
-
long = (
|
229
|
-
"Homing fail. Could not find limit switch within search distances."
|
230
|
-
+ " Try increasing max travel, decreasing pull-off distance,"
|
231
|
-
+ " or check wiring."
|
232
|
-
)
|
233
|
-
else:
|
234
|
-
short = f"Alarm #{code}"
|
235
|
-
long = "Unknow alarm status"
|
236
|
-
long += "\nTry to clear the alarm status."
|
237
|
-
return short, long
|
238
|
-
|
239
|
-
|
240
|
-
class GrblController:
|
241
|
-
def __init__(self, context):
|
242
|
-
self.service = context
|
243
|
-
self.connection = None
|
244
|
-
self._validation_stage = 0
|
245
|
-
|
246
|
-
self.update_connection()
|
247
|
-
|
248
|
-
self.driver = self.service.driver
|
249
|
-
|
250
|
-
#
|
251
|
-
self.
|
252
|
-
|
253
|
-
|
254
|
-
self.
|
255
|
-
self.
|
256
|
-
|
257
|
-
self.
|
258
|
-
self.
|
259
|
-
self.
|
260
|
-
|
261
|
-
self.
|
262
|
-
self.
|
263
|
-
|
264
|
-
self.
|
265
|
-
|
266
|
-
self.
|
267
|
-
self.
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
self.
|
272
|
-
|
273
|
-
def
|
274
|
-
return
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
"""
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
n
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
def
|
332
|
-
self._watchers.
|
333
|
-
|
334
|
-
def
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
self
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
)
|
350
|
-
self._grbl_recv(data)
|
351
|
-
elif type == "event":
|
352
|
-
if not hasattr(self, "_grbl_events"):
|
353
|
-
self._grbl_events = self.service.channel(f"events-{
|
354
|
-
self._grbl_events(data)
|
355
|
-
|
356
|
-
def open(self):
|
357
|
-
"""
|
358
|
-
Opens the connection calling connection.connect.
|
359
|
-
|
360
|
-
Reads the first line this should be GRBL version and information.
|
361
|
-
@return:
|
362
|
-
"""
|
363
|
-
if self.connection.connected:
|
364
|
-
return
|
365
|
-
self.connection.connect()
|
366
|
-
if not self.connection.connected:
|
367
|
-
self.log("Could not connect.", type="event")
|
368
|
-
return
|
369
|
-
self.log("Connecting to GRBL...", type="event")
|
370
|
-
self.
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
if
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
)
|
399
|
-
self.
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
@
|
408
|
-
|
409
|
-
|
410
|
-
self.
|
411
|
-
self.
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
""
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
)
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
self.
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
""
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
self.
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
self.
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
self.
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
self.
|
716
|
-
|
717
|
-
elif
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
elif
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
self.
|
886
|
-
|
887
|
-
|
888
|
-
self.service.signal("grbl:
|
889
|
-
|
890
|
-
|
891
|
-
message
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
1
|
+
"""
|
2
|
+
GRBL Controller
|
3
|
+
|
4
|
+
Tasked with sending data to the different connection.
|
5
|
+
|
6
|
+
Validation Stages.
|
7
|
+
Stage 0, we are disconnected and invalid.
|
8
|
+
Stage 1, we are connected and need to check if we are GRBL send $
|
9
|
+
Stage 2, we parsed $ and need to try $$ $G
|
10
|
+
Stage 3, we successfully parsed $$
|
11
|
+
Stage 4, we successfully parsed $G, send ?
|
12
|
+
Stage 5, we successfully parsed ?
|
13
|
+
"""
|
14
|
+
import ast
|
15
|
+
import re
|
16
|
+
import threading
|
17
|
+
import time
|
18
|
+
|
19
|
+
from meerk40t.kernel import signal_listener
|
20
|
+
|
21
|
+
SETTINGS_MESSAGE = re.compile(r"^\$([0-9]+)=(.*)")
|
22
|
+
|
23
|
+
|
24
|
+
def hardware_settings(code):
|
25
|
+
"""
|
26
|
+
Given a $# code returns the parameter and the units.
|
27
|
+
|
28
|
+
@param code: $$ code.
|
29
|
+
@return: parameter, units
|
30
|
+
"""
|
31
|
+
if code == 0:
|
32
|
+
return 10, "step pulse time", "microseconds", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#0--step-pulse-microseconds"
|
33
|
+
if code == 1:
|
34
|
+
return 25, "step idle delay", "milliseconds", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#1---step-idle-delay-milliseconds"
|
35
|
+
if code == 2:
|
36
|
+
return 0, "step pulse invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#2--step-port-invert-mask"
|
37
|
+
if code == 3:
|
38
|
+
return 0, "step direction invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#3--direction-port-invert-mask"
|
39
|
+
if code == 4:
|
40
|
+
return 0, "invert step enable pin", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#4---step-enable-invert-boolean"
|
41
|
+
if code == 5:
|
42
|
+
return 0, "invert limit pins", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#5----limit-pins-invert-boolean"
|
43
|
+
if code == 6:
|
44
|
+
return 0, "invert probe pin", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#6----probe-pin-invert-boolean"
|
45
|
+
if code == 10:
|
46
|
+
return 255, "status report options", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#10---status-report-mask"
|
47
|
+
if code == 11:
|
48
|
+
return 0.010, "Junction deviation", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#11---junction-deviation-mm"
|
49
|
+
if code == 12:
|
50
|
+
return 0.002, "arc tolerance", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#12--arc-tolerance-mm"
|
51
|
+
if code == 13:
|
52
|
+
return 0, "Report in inches", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#13---report-inches-boolean"
|
53
|
+
if code == 20:
|
54
|
+
return 0, "Soft limits enabled", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#20---soft-limits-boolean"
|
55
|
+
if code == 21:
|
56
|
+
return 0, "hard limits enabled", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#21---hard-limits-boolean"
|
57
|
+
if code == 22:
|
58
|
+
return 0, "Homing cycle enable", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#22---homing-cycle-boolean"
|
59
|
+
if code == 23:
|
60
|
+
return 0, "Homing direction invert", "bitmask", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#23---homing-dir-invert-mask"
|
61
|
+
if code == 24:
|
62
|
+
return 25.000, "Homing locate feed rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#24---homing-feed-mmmin"
|
63
|
+
if code == 25:
|
64
|
+
return 500.000, "Homing search seek rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#25---homing-seek-mmmin"
|
65
|
+
if code == 26:
|
66
|
+
return 250, "Homing switch debounce delay", "ms", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#26---homing-debounce-milliseconds"
|
67
|
+
if code == 27:
|
68
|
+
return 1.000, "Homing switch pull-off distance", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#27---homing-pull-off-mm"
|
69
|
+
if code == 30:
|
70
|
+
return 1000, "Maximum spindle speed", "RPM", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#30---max-spindle-speed-rpm"
|
71
|
+
if code == 31:
|
72
|
+
return 0, "Minimum spindle speed", "RPM", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#31---min-spindle-speed-rpm"
|
73
|
+
if code == 32:
|
74
|
+
return 1, "Laser mode enable", "boolean", int, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#32---laser-mode-boolean"
|
75
|
+
if code == 100:
|
76
|
+
return 250.000, "X-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
|
77
|
+
if code == 101:
|
78
|
+
return 250.000, "Y-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
|
79
|
+
if code == 102:
|
80
|
+
return 250.000, "Z-axis steps per millimeter", "steps", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#100-101-and-102--xyz-stepsmm"
|
81
|
+
if code == 110:
|
82
|
+
return 500.000, "X-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
|
83
|
+
if code == 111:
|
84
|
+
return 500.000, "Y-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
|
85
|
+
if code == 112:
|
86
|
+
return 500.000, "Z-axis max rate", "mm/min", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#110-111-and-112--xyz-max-rate-mmmin"
|
87
|
+
if code == 120:
|
88
|
+
return 10.000, "X-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
|
89
|
+
if code == 121:
|
90
|
+
return 10.000, "Y-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
|
91
|
+
if code == 122:
|
92
|
+
return 10.000, "Z-axis acceleration", "mm/s^2", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#120-121-122--xyz-acceleration-mmsec2"
|
93
|
+
if code == 130:
|
94
|
+
return 200.000, "X-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
|
95
|
+
if code == 131:
|
96
|
+
return 200.000, "Y-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
|
97
|
+
if code == 132:
|
98
|
+
return 200.000, "Z-axis max travel", "mm", float, "https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md#130-131-132--xyz-max-travel-mm"
|
99
|
+
|
100
|
+
|
101
|
+
def grbl_error_code(code):
|
102
|
+
short = f"Error #{code}"
|
103
|
+
if code == 1:
|
104
|
+
long = "GCode Command letter was not found."
|
105
|
+
elif code == 2:
|
106
|
+
long = "GCode Command value invalid or missing."
|
107
|
+
elif code == 3:
|
108
|
+
long = "Grbl '$' not recognized or supported."
|
109
|
+
elif code == 4:
|
110
|
+
long = "Negative value for an expected positive value."
|
111
|
+
elif code == 5:
|
112
|
+
long = "Homing fail. Homing not enabled in settings."
|
113
|
+
elif code == 6:
|
114
|
+
long = "Min step pulse must be greater than 3usec."
|
115
|
+
elif code == 7:
|
116
|
+
long = "EEPROM read failed. Default values used."
|
117
|
+
elif code == 8:
|
118
|
+
long = "Grbl '$' command Only valid when Idle."
|
119
|
+
elif code == 9:
|
120
|
+
long = "GCode commands invalid in alarm or jog state."
|
121
|
+
elif code == 10:
|
122
|
+
long = "Soft limits require homing to be enabled."
|
123
|
+
elif code == 11:
|
124
|
+
long = "Max characters per line exceeded. Ignored."
|
125
|
+
elif code == 12:
|
126
|
+
long = "Grbl '$' setting exceeds the maximum step rate."
|
127
|
+
elif code == 13:
|
128
|
+
long = "Safety door opened and door state initiated."
|
129
|
+
elif code == 14:
|
130
|
+
long = "Build info or start-up line > EEPROM line length"
|
131
|
+
elif code == 15:
|
132
|
+
long = "Jog target exceeds machine travel, ignored."
|
133
|
+
elif code == 16:
|
134
|
+
long = "Jog Cmd missing '=' or has prohibited GCode."
|
135
|
+
elif code == 17:
|
136
|
+
long = "Laser mode requires PWM output."
|
137
|
+
elif code == 20:
|
138
|
+
long = "Unsupported or invalid GCode command."
|
139
|
+
elif code == 21:
|
140
|
+
long = "> 1 GCode command in a modal group in block."
|
141
|
+
elif code == 22:
|
142
|
+
long = "Feed rate has not yet been set or is undefined."
|
143
|
+
elif code == 23:
|
144
|
+
long = "GCode command requires an integer value."
|
145
|
+
elif code == 24:
|
146
|
+
long = "> 1 GCode command using axis words found."
|
147
|
+
elif code == 25:
|
148
|
+
long = "Repeated GCode word found in block."
|
149
|
+
elif code == 26:
|
150
|
+
long = "No axis words found in command block."
|
151
|
+
elif code == 27:
|
152
|
+
long = "Line number value is invalid."
|
153
|
+
elif code == 28:
|
154
|
+
long = "GCode Cmd missing a required value word."
|
155
|
+
elif code == 29:
|
156
|
+
long = "G59.x WCS are not supported."
|
157
|
+
elif code == 30:
|
158
|
+
long = "G53 only valid with G0 and G1 motion modes."
|
159
|
+
elif code == 31:
|
160
|
+
long = "Unneeded Axis words found in block."
|
161
|
+
elif code == 32:
|
162
|
+
long = "G2/G3 arcs need >= 1 in-plane axis word."
|
163
|
+
elif code == 33:
|
164
|
+
long = "Motion command target is invalid."
|
165
|
+
elif code == 34:
|
166
|
+
long = "Arc radius value is invalid."
|
167
|
+
elif code == 35:
|
168
|
+
long = "G2/G3 arcs need >= 1 in-plane offset word."
|
169
|
+
elif code == 36:
|
170
|
+
long = "Unused value words found in block."
|
171
|
+
elif code == 37:
|
172
|
+
long = "G43.1 offset not assigned to tool length axis."
|
173
|
+
elif code == 38:
|
174
|
+
long = "Tool number greater than max value."
|
175
|
+
else:
|
176
|
+
long = f"Unrecognised error code #{code}"
|
177
|
+
return short, long
|
178
|
+
|
179
|
+
|
180
|
+
def grbl_alarm_message(code):
|
181
|
+
if code == 1:
|
182
|
+
short = "Hard limit"
|
183
|
+
long = (
|
184
|
+
"Hard limit has been triggered."
|
185
|
+
+ " Machine position is likely lost due to sudden halt."
|
186
|
+
+ " Re-homing is highly recommended."
|
187
|
+
)
|
188
|
+
elif code == 2:
|
189
|
+
short = "Soft limit"
|
190
|
+
long = (
|
191
|
+
"Soft limit alarm. G-code motion target exceeds machine travel."
|
192
|
+
+ " Machine position retained. Alarm may be safely unlocked."
|
193
|
+
)
|
194
|
+
elif code == 3:
|
195
|
+
short = "Abort during cycle"
|
196
|
+
long = (
|
197
|
+
"Reset while in motion. Machine position is likely lost due to sudden halt."
|
198
|
+
+ " Re-homing is highly recommended. May be due to issuing g-code"
|
199
|
+
+ " commands that exceed the limit of the machine."
|
200
|
+
)
|
201
|
+
elif code == 4:
|
202
|
+
short = "Probe fail"
|
203
|
+
long = (
|
204
|
+
"Probe fail. Probe is not in the expected initial state before"
|
205
|
+
+ " starting probe cycle when G38.2 and G38.3 is not triggered"
|
206
|
+
+ " and G38.4 and G38.5 is triggered."
|
207
|
+
)
|
208
|
+
elif code == 5:
|
209
|
+
short = "Probe fail"
|
210
|
+
long = (
|
211
|
+
"Probe fail. Probe did not contact the workpiece within the programmed"
|
212
|
+
+ " travel for G38.2 and G38.4."
|
213
|
+
)
|
214
|
+
elif code == 6:
|
215
|
+
short = "Homing fail"
|
216
|
+
long = "Homing fail. The active homing cycle was reset."
|
217
|
+
elif code == 7:
|
218
|
+
short = "Homing fail"
|
219
|
+
long = "Homing fail. Safety door was opened during homing cycle."
|
220
|
+
elif code == 8:
|
221
|
+
short = "Homing fail"
|
222
|
+
long = (
|
223
|
+
"Homing fail. Pull off travel failed to clear limit switch."
|
224
|
+
+ " Try increasing pull-off setting or check wiring."
|
225
|
+
)
|
226
|
+
elif code == 9:
|
227
|
+
short = "Homing fail"
|
228
|
+
long = (
|
229
|
+
"Homing fail. Could not find limit switch within search distances."
|
230
|
+
+ " Try increasing max travel, decreasing pull-off distance,"
|
231
|
+
+ " or check wiring."
|
232
|
+
)
|
233
|
+
else:
|
234
|
+
short = f"Alarm #{code}"
|
235
|
+
long = "Unknow alarm status"
|
236
|
+
long += "\nTry to clear the alarm status."
|
237
|
+
return short, long
|
238
|
+
|
239
|
+
|
240
|
+
class GrblController:
|
241
|
+
def __init__(self, context):
|
242
|
+
self.service = context
|
243
|
+
self.connection = None
|
244
|
+
self._validation_stage = 0
|
245
|
+
|
246
|
+
self.update_connection()
|
247
|
+
|
248
|
+
self.driver = self.service.driver
|
249
|
+
|
250
|
+
# Sending variables.
|
251
|
+
self._sending_thread = None
|
252
|
+
self._recving_thread = None
|
253
|
+
|
254
|
+
self._forward_lock = threading.Lock()
|
255
|
+
self._sending_lock = threading.Lock()
|
256
|
+
self._realtime_lock = threading.Lock()
|
257
|
+
self._loop_cond = threading.Condition()
|
258
|
+
self._sending_queue = []
|
259
|
+
self._realtime_queue = []
|
260
|
+
# buffer for feedback...
|
261
|
+
self._assembled_response = []
|
262
|
+
self._forward_buffer = bytearray()
|
263
|
+
self._device_buffer_size = self.service.planning_buffer_size
|
264
|
+
self._log = None
|
265
|
+
|
266
|
+
self._paused = False
|
267
|
+
self._watchers = []
|
268
|
+
self.is_shutdown = False
|
269
|
+
|
270
|
+
def __repr__(self):
|
271
|
+
return f"GRBLController('{self.service.location()}')"
|
272
|
+
|
273
|
+
def __len__(self):
|
274
|
+
return (
|
275
|
+
len(self._sending_queue)
|
276
|
+
+ len(self._realtime_queue)
|
277
|
+
+ len(self._forward_buffer)
|
278
|
+
)
|
279
|
+
|
280
|
+
@property
|
281
|
+
def _length_of_next_line(self):
|
282
|
+
"""
|
283
|
+
Lookahead and provide length of the next line.
|
284
|
+
@return:
|
285
|
+
"""
|
286
|
+
if not self._sending_queue:
|
287
|
+
return 0
|
288
|
+
return len(self._sending_queue[0])
|
289
|
+
|
290
|
+
@property
|
291
|
+
def _index_of_forward_line(self):
|
292
|
+
try:
|
293
|
+
r = self._forward_buffer.index(b"\r")
|
294
|
+
except ValueError:
|
295
|
+
r = -1
|
296
|
+
try:
|
297
|
+
n = self._forward_buffer.index(b"\n")
|
298
|
+
except ValueError:
|
299
|
+
n = -1
|
300
|
+
|
301
|
+
if n != -1:
|
302
|
+
return min(n, r) if r != -1 else n
|
303
|
+
else:
|
304
|
+
return r
|
305
|
+
|
306
|
+
@signal_listener("update_interface")
|
307
|
+
def update_connection(self, origin=None, *args):
|
308
|
+
if self.service.permit_serial and self.service.interface == "serial":
|
309
|
+
try:
|
310
|
+
from .serial_connection import SerialConnection
|
311
|
+
|
312
|
+
self.connection = SerialConnection(self.service, self)
|
313
|
+
except ImportError:
|
314
|
+
pass
|
315
|
+
elif self.service.permit_tcp and self.service.interface == "tcp":
|
316
|
+
from meerk40t.grbl.tcp_connection import TCPOutput
|
317
|
+
|
318
|
+
self.connection = TCPOutput(self.service, self)
|
319
|
+
elif self.service.permit_ws and self.service.interface == "ws":
|
320
|
+
from meerk40t.grbl.ws_connection import WSOutput
|
321
|
+
try:
|
322
|
+
self.connection = WSOutput(self.service, self)
|
323
|
+
except ModuleNotFoundError:
|
324
|
+
response = self.service.kernel.prompt(str, "Could not open websocket-connection (websocket installed?)")
|
325
|
+
else:
|
326
|
+
# Mock
|
327
|
+
from .mock_connection import MockConnection
|
328
|
+
|
329
|
+
self.connection = MockConnection(self.service, self)
|
330
|
+
|
331
|
+
def add_watcher(self, watcher):
|
332
|
+
self._watchers.append(watcher)
|
333
|
+
|
334
|
+
def remove_watcher(self, watcher):
|
335
|
+
self._watchers.remove(watcher)
|
336
|
+
|
337
|
+
def log(self, data, type):
|
338
|
+
for w in self._watchers:
|
339
|
+
w(data, type=type)
|
340
|
+
|
341
|
+
def _channel_log(self, data, type=None):
|
342
|
+
name = self.service.safe_label
|
343
|
+
if type == "send":
|
344
|
+
if not hasattr(self, "_grbl_send"):
|
345
|
+
self._grbl_send = self.service.channel(f"send-{name}", pure=True)
|
346
|
+
self._grbl_send(data)
|
347
|
+
elif type == "recv":
|
348
|
+
if not hasattr(self, "_grbl_recv"):
|
349
|
+
self._grbl_recv = self.service.channel(f"recv-{name}", pure=True)
|
350
|
+
self._grbl_recv(data)
|
351
|
+
elif type == "event":
|
352
|
+
if not hasattr(self, "_grbl_events"):
|
353
|
+
self._grbl_events = self.service.channel(f"events-{name}")
|
354
|
+
self._grbl_events(data)
|
355
|
+
|
356
|
+
def open(self):
|
357
|
+
"""
|
358
|
+
Opens the connection calling connection.connect.
|
359
|
+
|
360
|
+
Reads the first line this should be GRBL version and information.
|
361
|
+
@return:
|
362
|
+
"""
|
363
|
+
if self.connection.connected:
|
364
|
+
return
|
365
|
+
self.connection.connect()
|
366
|
+
if not self.connection.connected:
|
367
|
+
self.log("Could not connect.", type="event")
|
368
|
+
return
|
369
|
+
self.log("Connecting to GRBL...", type="event")
|
370
|
+
if self.service.reset_on_connect:
|
371
|
+
self.driver.reset()
|
372
|
+
if not self.service.require_validator:
|
373
|
+
# We are required to wait for the validation.
|
374
|
+
if self.service.boot_connect_sequence:
|
375
|
+
self._validation_stage = 1
|
376
|
+
self.validate_start("$")
|
377
|
+
else:
|
378
|
+
self._validation_stage = 5
|
379
|
+
if self.service.startup_commands:
|
380
|
+
self.log("Queue startup commands", type="event")
|
381
|
+
lines = self.service.startup_commands.split("\n")
|
382
|
+
line_end = self.service.driver.line_end
|
383
|
+
for line in lines:
|
384
|
+
if line.startswith("#"):
|
385
|
+
self.log(f"Startup: {line}", type="event")
|
386
|
+
else:
|
387
|
+
self.service.driver(f"{line}{line_end}")
|
388
|
+
|
389
|
+
def close(self):
|
390
|
+
"""
|
391
|
+
Close the GRBL connection.
|
392
|
+
|
393
|
+
@return:
|
394
|
+
"""
|
395
|
+
if not self.connection.connected:
|
396
|
+
return
|
397
|
+
self.connection.disconnect()
|
398
|
+
self.log("Disconnecting from GRBL...", type="event")
|
399
|
+
self.validate_stop("*")
|
400
|
+
self._validation_stage = 0
|
401
|
+
|
402
|
+
def write(self, data):
|
403
|
+
"""
|
404
|
+
Write data to the sending queue.
|
405
|
+
|
406
|
+
@param data:
|
407
|
+
@return:
|
408
|
+
"""
|
409
|
+
self.start()
|
410
|
+
self.service.signal("grbl;write", data)
|
411
|
+
with self._sending_lock:
|
412
|
+
self._sending_queue.append(data)
|
413
|
+
self.service.signal(
|
414
|
+
"grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
|
415
|
+
)
|
416
|
+
self._send_resume()
|
417
|
+
|
418
|
+
def realtime(self, data):
|
419
|
+
"""
|
420
|
+
Write data to the realtime queue.
|
421
|
+
|
422
|
+
The realtime queue should preemt the regular dataqueue.
|
423
|
+
|
424
|
+
@param data:
|
425
|
+
@return:
|
426
|
+
"""
|
427
|
+
self.start()
|
428
|
+
self.service.signal("grbl;write", data)
|
429
|
+
with self._realtime_lock:
|
430
|
+
self._realtime_queue.append(data)
|
431
|
+
if "\x18" in data:
|
432
|
+
with self._sending_lock:
|
433
|
+
self._sending_queue.clear()
|
434
|
+
self.service.signal(
|
435
|
+
"grbl;buffer", len(self._sending_queue) + len(self._realtime_queue)
|
436
|
+
)
|
437
|
+
self._send_resume()
|
438
|
+
|
439
|
+
####################
|
440
|
+
# Control GRBL Sender
|
441
|
+
####################
|
442
|
+
|
443
|
+
def start(self):
|
444
|
+
"""
|
445
|
+
Starts the driver thread.
|
446
|
+
|
447
|
+
@return:
|
448
|
+
"""
|
449
|
+
self.open()
|
450
|
+
if self._channel_log not in self._watchers:
|
451
|
+
self.add_watcher(self._channel_log)
|
452
|
+
|
453
|
+
if self._sending_thread is None or (
|
454
|
+
self._sending_thread != True and not self._sending_thread.is_alive()
|
455
|
+
):
|
456
|
+
self._sending_thread = True # Avoid race condition.
|
457
|
+
self._sending_thread = self.service.threaded(
|
458
|
+
self._sending,
|
459
|
+
thread_name=f"sender-{self.service.location()}",
|
460
|
+
result=self.stop,
|
461
|
+
daemon=True,
|
462
|
+
)
|
463
|
+
if self._recving_thread is None or (
|
464
|
+
self._recving_thread != True and not self._recving_thread.is_alive()
|
465
|
+
):
|
466
|
+
self._recving_thread = True # Avoid race condition.
|
467
|
+
self._recving_thread = self.service.threaded(
|
468
|
+
self._recving,
|
469
|
+
thread_name=f"recver-{self.service.location()}",
|
470
|
+
result=self._rstop,
|
471
|
+
daemon=True,
|
472
|
+
)
|
473
|
+
|
474
|
+
def shutdown(self):
|
475
|
+
self.is_shutdown = True
|
476
|
+
self._forward_buffer.clear()
|
477
|
+
|
478
|
+
def validate_start(self, cmd):
|
479
|
+
if cmd == "$":
|
480
|
+
delay = self.service.connect_delay / 1000
|
481
|
+
else:
|
482
|
+
delay = 0
|
483
|
+
name = self.service.safe_label
|
484
|
+
if delay:
|
485
|
+
self.service(f".timer 1 {delay} .gcode_realtime {cmd}")
|
486
|
+
self.service(
|
487
|
+
f".timer-{name}{cmd} 1 {delay} .timer-{name}{cmd} 0 1 gcode_realtime {cmd}"
|
488
|
+
)
|
489
|
+
else:
|
490
|
+
self.service(f".gcode_realtime {cmd}")
|
491
|
+
self.service(f".timer-{name}{cmd} 0 1 gcode_realtime {cmd}")
|
492
|
+
|
493
|
+
def validate_stop(self, cmd):
|
494
|
+
name = self.service.safe_label
|
495
|
+
if cmd == "*":
|
496
|
+
self.service(f".timer-{name}* -q --off")
|
497
|
+
return
|
498
|
+
self.service(f".timer-{name}{cmd} -q --off")
|
499
|
+
if cmd == "$":
|
500
|
+
if len(self._forward_buffer) > 3:
|
501
|
+
# If the forward planning buffer is longer than 3 it must have filled with failed attempts.
|
502
|
+
with self._forward_lock:
|
503
|
+
self._forward_buffer.clear()
|
504
|
+
|
505
|
+
def _rstop(self, *args):
|
506
|
+
self._recving_thread = None
|
507
|
+
|
508
|
+
def stop(self, *args):
|
509
|
+
"""
|
510
|
+
Processes the stopping of the sending queue.
|
511
|
+
|
512
|
+
@param args:
|
513
|
+
@return:
|
514
|
+
"""
|
515
|
+
self._sending_thread = None
|
516
|
+
self.close()
|
517
|
+
self._send_resume()
|
518
|
+
|
519
|
+
try:
|
520
|
+
self.remove_watcher(self._channel_log)
|
521
|
+
except (AttributeError, ValueError):
|
522
|
+
pass
|
523
|
+
|
524
|
+
####################
|
525
|
+
# GRBL SEND ROUTINES
|
526
|
+
####################
|
527
|
+
|
528
|
+
def _send(self, line):
|
529
|
+
"""
|
530
|
+
Write the line to the connection, announce it to the send channel, and add it to the forward buffer.
|
531
|
+
|
532
|
+
@param line:
|
533
|
+
@return:
|
534
|
+
"""
|
535
|
+
with self._forward_lock:
|
536
|
+
self._forward_buffer += bytes(line, encoding="latin-1")
|
537
|
+
self.connection.write(line)
|
538
|
+
self.log(line, type="send")
|
539
|
+
|
540
|
+
def _sending_realtime(self):
|
541
|
+
"""
|
542
|
+
Send one line of realtime queue.
|
543
|
+
|
544
|
+
@return:
|
545
|
+
"""
|
546
|
+
with self._realtime_lock:
|
547
|
+
line = self._realtime_queue.pop(0)
|
548
|
+
if "!" in line:
|
549
|
+
self._paused = True
|
550
|
+
if "~" in line:
|
551
|
+
self._paused = False
|
552
|
+
if line is not None:
|
553
|
+
self._send(line)
|
554
|
+
if "\x18" in line:
|
555
|
+
self._paused = False
|
556
|
+
with self._forward_lock:
|
557
|
+
self._forward_buffer.clear()
|
558
|
+
|
559
|
+
def _sending_single_line(self):
|
560
|
+
"""
|
561
|
+
Send one line of sending queue.
|
562
|
+
|
563
|
+
@return:
|
564
|
+
"""
|
565
|
+
with self._sending_lock:
|
566
|
+
line = self._sending_queue.pop(0)
|
567
|
+
if line:
|
568
|
+
self._send(line)
|
569
|
+
self.service.signal("grbl;buffer", len(self._sending_queue))
|
570
|
+
return True
|
571
|
+
|
572
|
+
def _send_halt(self):
|
573
|
+
"""
|
574
|
+
This is called internally in the _sending command.
|
575
|
+
@return:
|
576
|
+
"""
|
577
|
+
with self._loop_cond:
|
578
|
+
self._loop_cond.wait()
|
579
|
+
|
580
|
+
def _send_resume(self):
|
581
|
+
"""
|
582
|
+
Other threads are expected to call this routine to permit _sending to resume.
|
583
|
+
|
584
|
+
@return:
|
585
|
+
"""
|
586
|
+
with self._loop_cond:
|
587
|
+
self._loop_cond.notify()
|
588
|
+
|
589
|
+
def _sending(self):
|
590
|
+
"""
|
591
|
+
Generic sender, delegate the function according to the desired mode.
|
592
|
+
|
593
|
+
This function is only run with the self.sending_thread
|
594
|
+
@return:
|
595
|
+
|
596
|
+
"""
|
597
|
+
while self.connection.connected:
|
598
|
+
if self._realtime_queue:
|
599
|
+
# Send realtime data.
|
600
|
+
self._sending_realtime()
|
601
|
+
continue
|
602
|
+
if self._paused or not self.fully_validated():
|
603
|
+
# We are paused or invalid. We do not send anything other than realtime commands.
|
604
|
+
time.sleep(0.05)
|
605
|
+
continue
|
606
|
+
if not self._sending_queue:
|
607
|
+
# There is nothing to write/realtime
|
608
|
+
self.service.laser_status = "idle"
|
609
|
+
self._send_halt()
|
610
|
+
continue
|
611
|
+
buffer = len(self._forward_buffer)
|
612
|
+
if buffer:
|
613
|
+
self.service.laser_status = "active"
|
614
|
+
|
615
|
+
if self.service.buffer_mode == "sync":
|
616
|
+
if buffer:
|
617
|
+
# Any buffer is too much buffer. Halt.
|
618
|
+
self._send_halt()
|
619
|
+
continue
|
620
|
+
else:
|
621
|
+
# Buffered
|
622
|
+
if self._device_buffer_size <= buffer + self._length_of_next_line:
|
623
|
+
# Stop sending when buffer is the size of permitted buffer size.
|
624
|
+
self._send_halt()
|
625
|
+
continue
|
626
|
+
# Go for send_line
|
627
|
+
self._sending_single_line()
|
628
|
+
self.service.laser_status = "idle"
|
629
|
+
|
630
|
+
####################
|
631
|
+
# GRBL RECV ROUTINES
|
632
|
+
####################
|
633
|
+
|
634
|
+
def get_forward_command(self):
|
635
|
+
"""
|
636
|
+
Gets the forward command from the front of the forward buffer. This was the oldest command that the controller
|
637
|
+
has not processed.
|
638
|
+
|
639
|
+
@return:
|
640
|
+
"""
|
641
|
+
q = self._index_of_forward_line
|
642
|
+
if q == -1:
|
643
|
+
raise ValueError("No forward command exists.")
|
644
|
+
with self._forward_lock:
|
645
|
+
cmd_issued = self._forward_buffer[: q + 1]
|
646
|
+
self._forward_buffer = self._forward_buffer[q + 1 :]
|
647
|
+
return cmd_issued
|
648
|
+
|
649
|
+
def _recving(self):
|
650
|
+
"""
|
651
|
+
Generic recver, delegate the function according to the desired mode.
|
652
|
+
|
653
|
+
Read and process response from grbl.
|
654
|
+
|
655
|
+
This function is only run with the self.recver_thread
|
656
|
+
@return:
|
657
|
+
"""
|
658
|
+
while self.connection.connected:
|
659
|
+
# reading responses.
|
660
|
+
response = None
|
661
|
+
while not response:
|
662
|
+
try:
|
663
|
+
response = self.connection.read()
|
664
|
+
except (ConnectionAbortedError, AttributeError):
|
665
|
+
return
|
666
|
+
if not response:
|
667
|
+
time.sleep(0.01)
|
668
|
+
if self.is_shutdown:
|
669
|
+
return
|
670
|
+
self.service.signal("grbl;response", response)
|
671
|
+
self.log(response, type="recv")
|
672
|
+
if response == "ok":
|
673
|
+
# Indicates that the command line received was parsed and executed (or set to be executed).
|
674
|
+
try:
|
675
|
+
cmd_issued = self.get_forward_command()
|
676
|
+
cmd_issued = cmd_issued.decode(encoding="latin-1")
|
677
|
+
except ValueError:
|
678
|
+
# We got an ok. But, had not sent anything.
|
679
|
+
self.log(
|
680
|
+
f"Response: {response}, but this was unexpected", type="event"
|
681
|
+
)
|
682
|
+
self._assembled_response = []
|
683
|
+
continue
|
684
|
+
# raise ConnectionAbortedError from e
|
685
|
+
self.log(
|
686
|
+
f"{response} / {len(self._forward_buffer)} -- {cmd_issued}",
|
687
|
+
type="recv",
|
688
|
+
)
|
689
|
+
self.service.signal(
|
690
|
+
"grbl;response", cmd_issued, self._assembled_response
|
691
|
+
)
|
692
|
+
self._assembled_response = []
|
693
|
+
self._send_resume()
|
694
|
+
elif response.startswith("error"):
|
695
|
+
# Indicates that the command line received contained an error, with an error code x, and was purged.
|
696
|
+
try:
|
697
|
+
cmd_issued = self.get_forward_command()
|
698
|
+
cmd_issued = cmd_issued.decode(encoding="latin-1")
|
699
|
+
except ValueError:
|
700
|
+
cmd_issued = ""
|
701
|
+
try:
|
702
|
+
error_num = int(response[6:])
|
703
|
+
except ValueError:
|
704
|
+
error_num = -1
|
705
|
+
short, long = grbl_error_code(error_num)
|
706
|
+
error_desc = f"#{error_num} '{cmd_issued}' {short}\n{long}"
|
707
|
+
self.service.signal("grbl;error", f"GRBL: {error_desc}", response, 4)
|
708
|
+
self.log(f"ERROR {error_desc}", type="recv")
|
709
|
+
self._assembled_response = []
|
710
|
+
self._send_resume()
|
711
|
+
continue
|
712
|
+
elif response.startswith("<"):
|
713
|
+
self._process_status_message(response)
|
714
|
+
elif response.startswith("["):
|
715
|
+
self._process_feedback_message(response)
|
716
|
+
continue
|
717
|
+
elif response.startswith("$"):
|
718
|
+
if self._validation_stage == 2:
|
719
|
+
self.log("Stage 3: $$ was successfully parsed.", type="event")
|
720
|
+
self.validate_stop("$$")
|
721
|
+
self._validation_stage = 3
|
722
|
+
self._process_settings_message(response)
|
723
|
+
elif response.startswith("Alarm|"):
|
724
|
+
# There's no errorcode
|
725
|
+
error_num = 1
|
726
|
+
short, long = grbl_alarm_message(error_num)
|
727
|
+
alarm_desc = f"#{error_num}, {short}\n{long}"
|
728
|
+
self.service.signal("warning", f"GRBL: {alarm_desc}", response, 4)
|
729
|
+
self.log(f"Alarm {alarm_desc}", type="recv")
|
730
|
+
self._assembled_response = []
|
731
|
+
|
732
|
+
elif response.startswith("ALARM"):
|
733
|
+
try:
|
734
|
+
error_num = int(response[6:])
|
735
|
+
except ValueError:
|
736
|
+
error_num = -1
|
737
|
+
short, long = grbl_alarm_message(error_num)
|
738
|
+
alarm_desc = f"#{error_num}, {short}\n{long}"
|
739
|
+
self.service.signal("warning", f"GRBL: {alarm_desc}", response, 4)
|
740
|
+
self.log(f"Alarm {alarm_desc}", type="recv")
|
741
|
+
self._assembled_response = []
|
742
|
+
elif response.startswith(">"):
|
743
|
+
self.log(f"STARTUP: {response}", type="event")
|
744
|
+
elif response.startswith(self.service.welcome):
|
745
|
+
if not self.service.require_validator:
|
746
|
+
# Validation is not required, we reboot.
|
747
|
+
if self.fully_validated():
|
748
|
+
if self.service.boot_connect_sequence:
|
749
|
+
# Boot sequence is required. Restart sequence.
|
750
|
+
self.log(
|
751
|
+
"Device Reset, revalidation required", type="event"
|
752
|
+
)
|
753
|
+
self._validation_stage = 1
|
754
|
+
self.validate_start("$")
|
755
|
+
else:
|
756
|
+
# Validation is required. This was stage 0.
|
757
|
+
if self.service.boot_connect_sequence:
|
758
|
+
# Boot sequence is required. Restart sequence.
|
759
|
+
self._validation_stage = 1
|
760
|
+
self.validate_start("$")
|
761
|
+
else:
|
762
|
+
# No boot sequence required. Declare fully connected.
|
763
|
+
self._validation_stage = 5
|
764
|
+
else:
|
765
|
+
self._assembled_response.append(response)
|
766
|
+
|
767
|
+
def fully_validated(self):
|
768
|
+
return self._validation_stage == 5
|
769
|
+
|
770
|
+
def force_validate(self):
|
771
|
+
self._validation_stage = 5
|
772
|
+
self.validate_stop("*")
|
773
|
+
|
774
|
+
def _process_status_message(self, response):
|
775
|
+
message = response[1:-1]
|
776
|
+
data = list(message.split("|"))
|
777
|
+
self.service.signal("grbl:state", data[0])
|
778
|
+
for datum in data[1:]:
|
779
|
+
# While valid some grbl replies might violate the parsing convention.
|
780
|
+
try:
|
781
|
+
name, info = datum.split(":")
|
782
|
+
except ValueError:
|
783
|
+
continue
|
784
|
+
if name == "F":
|
785
|
+
self.service.signal("grbl:speed", float(info))
|
786
|
+
elif name == "S":
|
787
|
+
self.service.signal("grbl:power", float(info))
|
788
|
+
elif name == "FS":
|
789
|
+
f, s = info.split(",")
|
790
|
+
self.service.signal("grbl:speed", float(f))
|
791
|
+
self.service.signal("grbl:power", float(s))
|
792
|
+
elif name == "MPos":
|
793
|
+
coords = info.split(",")
|
794
|
+
try:
|
795
|
+
nx = float(coords[0])
|
796
|
+
ny = float(coords[1])
|
797
|
+
|
798
|
+
if not self.fully_validated():
|
799
|
+
# During validation, we declare positions.
|
800
|
+
self.driver.declare_position(nx, ny)
|
801
|
+
ox = self.driver.mpos_x
|
802
|
+
oy = self.driver.mpos_y
|
803
|
+
|
804
|
+
x, y = self.service.view_mm.position(f"{nx}mm", f"{ny}mm")
|
805
|
+
|
806
|
+
(
|
807
|
+
self.driver.mpos_x,
|
808
|
+
self.driver.mpos_y,
|
809
|
+
) = self.service.view_mm.scene_position(f"{x}mm", f"{y}mm")
|
810
|
+
|
811
|
+
if len(coords) >= 3:
|
812
|
+
self.driver.mpos_z = float(coords[2])
|
813
|
+
self.service.signal(
|
814
|
+
"status;position",
|
815
|
+
(ox, oy, self.driver.mpos_x, self.driver.mpos_y),
|
816
|
+
)
|
817
|
+
except ValueError:
|
818
|
+
pass
|
819
|
+
elif name == "WPos":
|
820
|
+
coords = info.split(",")
|
821
|
+
self.driver.wpos_x = coords[0]
|
822
|
+
self.driver.wpos_y = coords[1]
|
823
|
+
if len(coords) >= 3:
|
824
|
+
self.driver.wpos_z = coords[2]
|
825
|
+
# See: https://github.com/grbl/grbl/blob/master/grbl/report.c#L421
|
826
|
+
# MPos: Coord values. Machine Position.
|
827
|
+
# WPos: MPos but with applied work coordinates. Work Position.
|
828
|
+
# RX: serial rx buffer count.
|
829
|
+
# Buf: plan block buffer count.
|
830
|
+
# Ln: line number.
|
831
|
+
# Lim: limits states
|
832
|
+
# Ctl: control pins and mask (binary).
|
833
|
+
self.service.signal(f"grbl:status:{name}", info)
|
834
|
+
if self._validation_stage in (2, 3, 4):
|
835
|
+
self.log("Connection Confirmed.", type="event")
|
836
|
+
self._validation_stage = 5
|
837
|
+
self.validate_stop("*")
|
838
|
+
|
839
|
+
def _process_feedback_message(self, response):
|
840
|
+
if response.startswith("[MSG:"):
|
841
|
+
message = response[5:-1]
|
842
|
+
self.log(message, type="event")
|
843
|
+
self.service.channel("console")(message)
|
844
|
+
elif response.startswith("[GC:"):
|
845
|
+
# Parsing $G
|
846
|
+
message = response[4:-1]
|
847
|
+
states = list(message.split(" "))
|
848
|
+
if not self.fully_validated():
|
849
|
+
self.log("Stage 4: $G was successfully parsed.", type="event")
|
850
|
+
self.driver.declare_modals(states)
|
851
|
+
self._validation_stage = 4
|
852
|
+
self.validate_stop("$G")
|
853
|
+
self.validate_start("?")
|
854
|
+
self.log(message, type="event")
|
855
|
+
self.service.signal("grbl:states", states)
|
856
|
+
elif response.startswith("[HLP:"):
|
857
|
+
# Parsing $
|
858
|
+
message = response[5:-1]
|
859
|
+
if self._validation_stage == 1:
|
860
|
+
self.log("Stage 2: $ was successfully parsed.", type="event")
|
861
|
+
self._validation_stage = 2
|
862
|
+
self.validate_stop("$")
|
863
|
+
if "$$" in message:
|
864
|
+
self.validate_start("$$")
|
865
|
+
if "$G" in message:
|
866
|
+
self.validate_start("$G")
|
867
|
+
elif "?" in message:
|
868
|
+
# No $G just request status.
|
869
|
+
self.validate_start("?")
|
870
|
+
self.log(message, type="event")
|
871
|
+
elif response.startswith("[G54:"):
|
872
|
+
message = response[5:-1]
|
873
|
+
self.service.signal("grbl:g54", message)
|
874
|
+
elif response.startswith("[G55:"):
|
875
|
+
message = response[5:-1]
|
876
|
+
self.service.signal("grbl:g55", message)
|
877
|
+
elif response.startswith("[G56:"):
|
878
|
+
message = response[5:-1]
|
879
|
+
self.service.signal("grbl:g56", message)
|
880
|
+
elif response.startswith("[G57:"):
|
881
|
+
message = response[5:-1]
|
882
|
+
self.service.signal("grbl:g57", message)
|
883
|
+
elif response.startswith("[G58:"):
|
884
|
+
message = response[5:-1]
|
885
|
+
self.service.signal("grbl:g58", message)
|
886
|
+
elif response.startswith("[G59:"):
|
887
|
+
message = response[5:-1]
|
888
|
+
self.service.signal("grbl:g59", message)
|
889
|
+
elif response.startswith("[G28:"):
|
890
|
+
message = response[5:-1]
|
891
|
+
self.service.signal("grbl:g28", message)
|
892
|
+
elif response.startswith("[G30:"):
|
893
|
+
message = response[5:-1]
|
894
|
+
self.service.signal("grbl:g30", message)
|
895
|
+
elif response.startswith("[G92:"):
|
896
|
+
message = response[5:-1]
|
897
|
+
self.service.signal("grbl:g92", message)
|
898
|
+
elif response.startswith("[TLO:"):
|
899
|
+
message = response[5:-1]
|
900
|
+
self.service.signal("grbl:tlo", message)
|
901
|
+
elif response.startswith("[PRB:"):
|
902
|
+
message = response[5:-1]
|
903
|
+
self.service.signal("grbl:prb", message)
|
904
|
+
elif response.startswith("[VER:"):
|
905
|
+
message = response[5:-1]
|
906
|
+
self.service.signal("grbl:ver", message)
|
907
|
+
elif response.startswith("[OPT:"):
|
908
|
+
message = response[5:-1]
|
909
|
+
opts = list(message.split(","))
|
910
|
+
codes = opts[0]
|
911
|
+
block_buffer_size = opts[1]
|
912
|
+
rx_buffer_size = opts[2]
|
913
|
+
self.log(f"codes: {codes}", type="event")
|
914
|
+
if "V" in codes:
|
915
|
+
# Variable spindle enabled
|
916
|
+
pass
|
917
|
+
if "N" in codes:
|
918
|
+
# Line numbers enabled
|
919
|
+
pass
|
920
|
+
|
921
|
+
if "M" in codes:
|
922
|
+
# Mist coolant enabled
|
923
|
+
pass
|
924
|
+
if "C" in codes:
|
925
|
+
# CoreXY enabled
|
926
|
+
pass
|
927
|
+
if "P" in codes:
|
928
|
+
# Parking motion enabled
|
929
|
+
pass
|
930
|
+
if "Z" in codes:
|
931
|
+
# Homing force origin enabled
|
932
|
+
pass
|
933
|
+
if "H" in codes:
|
934
|
+
# Homing single axis enabled
|
935
|
+
pass
|
936
|
+
if "T" in codes:
|
937
|
+
# Two limit switches on axis enabled
|
938
|
+
pass
|
939
|
+
if "A" in codes:
|
940
|
+
# Allow feed rate overrides in probe cycles
|
941
|
+
pass
|
942
|
+
if "*" in codes:
|
943
|
+
# Restore all EEPROM disabled
|
944
|
+
pass
|
945
|
+
if "$" in codes:
|
946
|
+
# Restore EEPROM $ settings disabled
|
947
|
+
pass
|
948
|
+
if "#" in codes:
|
949
|
+
# Restore EEPROM parameter data disabled
|
950
|
+
pass
|
951
|
+
if "I" in codes:
|
952
|
+
# Build info write user string disabled
|
953
|
+
pass
|
954
|
+
if "E" in codes:
|
955
|
+
# Force sync upon EEPROM write disabled
|
956
|
+
pass
|
957
|
+
if "W" in codes:
|
958
|
+
# Force sync upon work coordinate offset change disabled
|
959
|
+
pass
|
960
|
+
if "L" in codes:
|
961
|
+
# Homing init lock sets Grbl into an alarm state upon power up
|
962
|
+
pass
|
963
|
+
if "2" in codes:
|
964
|
+
# Dual axis motors with self-squaring enabled
|
965
|
+
pass
|
966
|
+
self.log(f"blockBufferSize: {block_buffer_size}", type="event")
|
967
|
+
self.log(f"rxBufferSize: {rx_buffer_size}", type="event")
|
968
|
+
self.service.signal("grbl:block_buffer", int(block_buffer_size))
|
969
|
+
self.service.signal("grbl:rx_buffer", int(rx_buffer_size))
|
970
|
+
self.service.signal("grbl:opt", message)
|
971
|
+
elif response.startswith("[echo:"):
|
972
|
+
message = response[6:-1]
|
973
|
+
self.service.channel("console")(message)
|
974
|
+
|
975
|
+
def _process_settings_message(self, response):
|
976
|
+
match = SETTINGS_MESSAGE.match(response)
|
977
|
+
if match:
|
978
|
+
try:
|
979
|
+
key = int(match.group(1))
|
980
|
+
value = match.group(2)
|
981
|
+
try:
|
982
|
+
value = ast.literal_eval(value)
|
983
|
+
except SyntaxError:
|
984
|
+
# GRBLHal can have things like "", and "Grbl" and "192.168.1.39" in the settings.
|
985
|
+
pass
|
986
|
+
|
987
|
+
self.service.hardware_config[key] = value
|
988
|
+
self.service.signal("grbl:hwsettings", key, value)
|
989
|
+
except ValueError:
|
990
|
+
pass
|