{"id":21262790,"url":"https://github.com/michaelfranzl/grbl-streamer","last_synced_at":"2025-07-11T04:31:06.992Z","repository":{"id":52627623,"uuid":"36599470","full_name":"michaelfranzl/grbl-streamer","owner":"michaelfranzl","description":"Universal interface module written in Python 3 for the grbl CNC firmware","archived":false,"fork":false,"pushed_at":"2024-05-02T06:58:19.000Z","size":164,"stargazers_count":48,"open_issues_count":0,"forks_count":19,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-14T10:08:47.752Z","etag":null,"topics":["cnc","gcode","gcode-sender","gcode-streaming","python","serial-port"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michaelfranzl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-31T11:07:51.000Z","updated_at":"2024-11-11T13:05:46.000Z","dependencies_parsed_at":"2023-12-28T09:26:44.521Z","dependency_job_id":"2168947a-a565-4853-b520-5d274e747233","html_url":"https://github.com/michaelfranzl/grbl-streamer","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelfranzl%2Fgrbl-streamer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelfranzl%2Fgrbl-streamer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelfranzl%2Fgrbl-streamer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelfranzl%2Fgrbl-streamer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michaelfranzl","download_url":"https://codeload.github.com/michaelfranzl/grbl-streamer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225674898,"owners_count":17506272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cnc","gcode","gcode-sender","gcode-streaming","python","serial-port"],"created_at":"2024-11-21T04:59:23.847Z","updated_at":"2024-11-21T04:59:24.542Z","avatar_url":"https://github.com/michaelfranzl.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GrblStreamer\n\nUniversal interface module for the [grbl](https://github.com/grbl/grbl) CNC firmware, implemented as a Python 3 class.\nIt provides a convenient high-level API for scripting or integration into parent applications (e.g. GUIs).\n\nThere are a number of streaming applications available for the grbl CNC controller, but none of them seem to be an universal, re-usable standard Python module. GrblStreamer attempts to fill that gap.\n\n\n## Features\n\n* Re-usable module\n* Callback based\n   * grbl's machine position updates\n   * grbl's probe events (PRB)\n   * grbl's alarm events (ALARM)\n   * grbl's errors\n   * grbl's booting\n   * grbl's settings updates\n   * grbl's g-code parser state updates\n   * log lines\n* keeps a copy of grbl's state for immediate inspection\n* Non-blocking streaming of g-code\n* Two streaming modes:\n   * Incremental\n   * fast (keep grbl's receive buffer as full as possible by keeping track of submitted characters)\n* Streaming from files or Python lists\n* Keeps track of the fill percentage of grbl's receive buffer\n* Requesting of grbl's settings\n* Requesting of grbl's parser state ('G' modes)\n* Dry run (simulator) mode\n* g-code buffer stashing\n* pausing and resuming of the g-code stream\n* controlled shutdown\n\nThe following features are also available, coming from my library [gcode-machine](https://github.com/michaelfranzl/gcode_machine):\n\n* G-Code compression by cleaning it up\n* G-Code variable expansion\n* Dynamic feed override\n* Dynamic spindle override\n\n\n## Integration example\n\nSee GrblStreamer integrated with its full feature set in a graphical user interface based on Python 3 with Qt5 bindings: https://github.com/michaelfranzl/gerbil_gui.\n\n\n## Installation\n\n```sh\npip install grbl-streamer\n```\n\n## Documentation\n\nThe module is only about 1300 lines of code, which is extensively documented.\n\n\n## Getting started\n\n```python\nfrom grbl_streamer import GrblStreamer\n```\n\nDefine a callback function that GrblStreamer will call asynchronously whenever some event happens.\nThe following example function does nothing else than logging to standard output.\nIn a real GUI application, you would update numbers, sliders etc. from this method.\n\n```python\ndef my_callback(eventstring, *data):\n    args = []\n    for d in data:\n        args.append(str(d))\n    print(\"MY CALLBACK: event={} data={}\".format(eventstring.ljust(30), \", \".join(args)))\n```\n\nHere, for just one example, is what above function `my_callback` will print out at this point:\n\nInstantiate the GrblStreamer class:\n\n```python\ngrbl = GrblStreamer(my_callback)\ngrbl.setup_logging()\n```\n\nWe now can connect to the grbl firmware, the actual CNC machine (adapt path and baudrate):\n\n```python\ngrbl.cnect(\"/dev/ttyUSB0\", 115200)\n```\n\nThis will emit the events `on_boot`, `on_settings_downloaded` and others.\n\nWe will poll every half second for the state of the CNC machine (working position, etc.):\n\n```python\ngrbl.poll_start()\n```\n\nNow, we'll send our first G-Code command.\nThis will emit events like `on_movement`, `on_stateupdate`, `on_standstill` and others.\n\n\n```python\ngrbl.send_immediately(\"G1 X5 F50\")\n```\n\nNow let's send the `$#` command to the firmware, so that it reports back coordinate system offset information. The event `on_hash_stateupdate` will be emitted:\n\n```python\ngrbl.hash_state_requested = True\n```\n\nExample result:\n\n```\nMY CALLBACK: event=on_hash_stateupdate            data={'G54': (0.0, 0.0, 0.0), 'G55': (0.0, 0.0, 0.0), 'G56': (0.0, 0.0, 0.0), 'G57': (0.0, 0.0, 0.0), 'G58': (0.0, 0.0, 0.0), 'G59': (0.0, 0.0, 0.0), 'G28': (0.0, 0.0, 0.0), 'G30': (0.0, 0.0, 0.0), 'G92': (0.0, 0.0, 0.0), 'TLO': (0.0,), 'PRB': (0.0, 0.0, 0.0)}\n```\n\nNext, let's request the firmware G-code parser state (grbl's `$G` command):\n\n```python\ngrbl.gcode_parser_state_requested = True\n```\n\nExample result:\n\n```\nMY CALLBACK: event=on_gcode_parser_stateupdate    data=['1', '54', '17', '21', '90', '94', '0', '5', '9', '0', '50.', '0.']\n```\n\nWe also can request the settings (grbl's `$$` command), the result will be a dict:\n\n```python\ngrbl.request_settings()\n```\n\nGrblStreamer supports dynamic feed override. You could have a slider in your GUI controlling the milling speed of your machine as it runs:\n\n```python\ngrbl.set_feed_override(True)\ngrbl.request_feed(800)\ngrbl.stream(\"F100 G1 X210 \\n G1 X200 \\n G1 Y210 \\n G1 Y200 \\n\")\n```\n\nWhen we're done, we disconnect from the firmware:\n\n```python\ngrbl.disconnect()\n```\n\n\n## Testing\n\nOne log file says more than a thousand words. The following is a printout of running `pipenv run make test` against a real microcontroller with grbl 0.9j:\n\n\n```\n        on_settings_downloaded: ({130: {'val': '1000', 'cmt': 'width'}, 131: {'val': '1000', 'cmt': 'height'}},)\n           on_hash_stateupdate: ({'G54': (0, 0, 0), 'G55': (0, 0, 0), 'G56': (0, 0, 0), 'G57': (0, 0, 0), 'G58': (0, 0, 0), 'G59': (0, 0, 0), 'G28': (0, 0, 0), 'G30': (0, 0, 0), 'G92': (0, 0, 0), 'TLO': 0, 'PRB': (0, 0, 0)},)\n   on_gcode_parser_stateupdate: (['0', '54', '17', '21', '90', '94', '0', '0', '5', '0', '99', '0'],)\n                        on_log: mygrbl: Setting up interface on /dev/ttyUSB0\n                        on_log: iface_mygrbl: connecting to /dev/ttyUSB0 with baudrate 115200\n                       on_read: (\"Grbl 0.9j ['$' for help]\",)\n              on_job_completed: ()\n                on_feed_change: (None,)\n           on_progress_percent: (0,)\n          on_rx_buffer_percent: (0,)\n                        on_log: mygrbl: Grbl has booted!\n                       on_boot: ()\n                      on_write: ('$$\\n',)\n                       on_read: ('$0=10 (step pulse, usec)',)\n                       on_read: ('$1=25 (step idle delay, msec)',)\n                       on_read: ('$2=0 (step port invert mask:00000000)',)\n                       on_read: ('$3=0 (dir port invert mask:00000000)',)\n                       on_read: ('$4=0 (step enable invert, bool)',)\n                       on_read: ('$5=0 (limit pins invert, bool)',)\n                       on_read: ('$6=0 (probe pin invert, bool)',)\n                       on_read: ('$10=3 (status report mask:00000011)',)\n                       on_read: ('$11=0.010 (junction deviation, mm)',)\n                       on_read: ('$12=0.002 (arc tolerance, mm)',)\n                       on_read: ('$13=0 (report inches, bool)',)\n                       on_read: ('$20=0 (soft limits, bool)',)\n                       on_read: ('$21=0 (hard limits, bool)',)\n                       on_read: ('$22=0 (homing cycle, bool)',)\n                       on_read: ('$23=0 (homing dir invert mask:00000000)',)\n                       on_read: ('$24=25.000 (homing feed, mm/min)',)\n                       on_read: ('$25=500.000 (homing seek, mm/min)',)\n                       on_read: ('$26=250 (homing debounce, msec)',)\n                       on_read: ('$27=1.000 (homing pull-off, mm)',)\n                       on_read: ('$100=250.000 (x, step/mm)',)\n                       on_read: ('$101=250.000 (y, step/mm)',)\n                       on_read: ('$102=250.000 (z, step/mm)',)\n                       on_read: ('$110=500.000 (x max rate, mm/min)',)\n                       on_read: ('$111=500.000 (y max rate, mm/min)',)\n                       on_read: ('$112=500.000 (z max rate, mm/min)',)\n                       on_read: ('$120=10.000 (x accel, mm/sec^2)',)\n                       on_read: ('$121=10.000 (y accel, mm/sec^2)',)\n                       on_read: ('$122=10.000 (z accel, mm/sec^2)',)\n                       on_read: ('$130=200.000 (x max travel, mm)',)\n                       on_read: ('$131=200.000 (y max travel, mm)',)\n                       on_read: ('$132=200.000 (z max travel, mm)',)\n        on_settings_downloaded: ({130: {'val': '200.000', 'cmt': 'x max travel, mm'}, 131: {'val': '200.000', 'cmt': 'y max travel, mm'}, 0: {'val': '10', 'cmt': 'step pulse, usec'}, 1: {'val': '25', 'cmt': 'step idle delay, msec'}, 2: {'val': '0', 'cmt': 'step port invert mask:00000000'}, 3: {'val': '0', 'cmt': 'dir port invert mask:00000000'}, 4: {'val': '0', 'cmt': 'step enable invert, bool'}, 5: {'val': '0', 'cmt': 'l\nimit pins invert, bool'}, 6: {'val': '0', 'cmt': 'probe pin invert, bool'}, 10: {'val': '3', 'cmt': 'status report mask:00000011'}, 11: {'val': '0.010', 'cmt': 'junction deviation, mm'}, 12: {'val': '0.002', 'cmt': 'arc tolerance, mm'}, 13: {'val': '0', 'cmt': 'report inches, bool'}, 20: {'val': '0', 'cmt': 'soft limits, bool'}, 21: {'val': '0', 'cmt': 'hard limits, bool'}, 22: {'val': '0', 'cmt': 'homing cycle, bool'}, 2\n3: {'val': '0', 'cmt': 'homing dir invert mask:00000000'}, 24: {'val': '25.000', 'cmt': 'homing feed, mm/min'}, 25: {'val': '500.000', 'cmt': 'homing seek, mm/min'}, 26: {'val': '250', 'cmt': 'homing debounce, msec'}, 27: {'val': '1.000', 'cmt': 'homing pull-off, mm'}, 100: {'val': '250.000', 'cmt': 'x, step/mm'}, 101: {'val': '250.000', 'cmt': 'y, step/mm'}, 102: {'val': '250.000', 'cmt': 'z, step/mm'}, 110: {'val': '500\n.000', 'cmt': 'x max rate, mm/min'}, 111: {'val': '500.000', 'cmt': 'y max rate, mm/min'}, 112: {'val': '500.000', 'cmt': 'z max rate, mm/min'}, 120: {'val': '10.000', 'cmt': 'x accel, mm/sec^2'}, 121: {'val': '10.000', 'cmt': 'y accel, mm/sec^2'}, 122: {'val': '10.000', 'cmt': 'z accel, mm/sec^2'}, 132: {'val': '200.000', 'cmt': 'z max travel, mm'}},)\n          on_rx_buffer_percent: (0,)\n                      on_write: ('$#\\n',)\n                        on_log: mygrbl: Polling thread started\n             on_bufsize_change: (2,)\n                on_vars_change: ({},)\n           on_progress_percent: (0,)\n                      on_write: ('G00Y3\\n',)\n                  on_line_sent: (1, 'G00Y3')\n           on_progress_percent: (50,)\n                      on_write: ('\\n',)\n                  on_line_sent: (2, '')\n           on_progress_percent: (100,)\n                       on_read: ('[G54:0.000,0.000,0.000]',)\n                       on_read: ('[G55:0.000,0.000,0.000]',)\n                       on_read: ('[G56:0.000,0.000,0.000]',)\n                       on_read: ('[G57:0.000,0.000,0.000]',)\n                       on_read: ('[G58:0.000,0.000,0.000]',)\n                       on_read: ('[G59:0.000,0.000,0.000]',)\n                       on_read: ('[G28:0.000,0.000,0.000]',)\n                       on_read: ('[G30:0.000,0.000,0.000]',)\n                       on_read: ('[G92:0.000,0.000,0.000]',)\n                       on_read: ('[TLO:0.000]',)\n                       on_read: ('[PRB:0.000,0.000,0.000:0]',)\n           on_hash_stateupdate: ({'G54': (0.0, 0.0, 0.0), 'G55': (0.0, 0.0, 0.0), 'G56': (0.0, 0.0, 0.0), 'G57': (0.0, 0.0, 0.0), 'G58': (0.0, 0.0, 0.0), 'G59': (0.0, 0.0, 0.0), 'G28': (0.0, 0.0, 0.0), 'G30': (0.0, 0.0, 0.0), 'G92': (0.0, 0.0, 0.0), 'TLO': (0.0,), 'PRB': (0.0, 0.0, 0.0)},)\n          on_processed_command: (0, 'G00Y3')\n          on_rx_buffer_percent: (0,)\n          on_processed_command: (1, '')\n              on_job_completed: ()\n          on_rx_buffer_percent: (0,)\n          on_rx_buffer_percent: (0,)\n                      on_write: ('$G\\n',)\n   on_gcode_parser_stateupdate: (['0', '54', '17', '21', '90', '94', '0', '5', '9', '0', '0.', '0.'],)\n                       on_read: ('[G0 G54 G17 G21 G90 G94 M0 M5 M9 T0 F0. S0.]',)\n          on_rx_buffer_percent: (0,)\n                on_stateupdate: ('Run', (0.0, 0.724, 0.0), (0.0, 0.724, 0.0))\n                on_stateupdate: ('Run', (0.0, 1.676, 0.0), (0.0, 1.676, 0.0))\n                on_stateupdate: ('Run', (0.0, 2.508, 0.0), (0.0, 2.508, 0.0))\n                on_stateupdate: ('Run', (0.0, 2.936, 0.0), (0.0, 2.936, 0.0))\n                on_stateupdate: ('Idle', (0.0, 3.0, 0.0), (0.0, 3.0, 0.0))\n                      on_write: ('$G\\n',)\n   on_gcode_parser_stateupdate: (['0', '54', '17', '21', '90', '94', '0', '5', '9', '0', '0.', '0.'],)\n                       on_read: ('[G0 G54 G17 G21 G90 G94 M0 M5 M9 T0 F0. S0.]',)\n          on_rx_buffer_percent: (0,)\n.                        on_log: mygrbl: Please wait until polling thread has joined...\n                        on_log: mygrbl: Polling has been stopped\n                        on_log: mygrbl: Polling thread has successfully  joined...\n                        on_log: iface_mygrbl: stop()\n                        on_log: iface_mygrbl: JOINED thread\n                        on_log: iface_mygrbl: Closing port\n                        on_log: mygrbl: Please wait until reading thread has joined...\n                       on_read: ('dummy_msg_for_joining_thread',)\n                        on_log: mygrbl: Reading thread successfully joined.\n               on_disconnected: ()\n\n----------------------------------------------------------------------\nRan 1 test in 6.830s\n\nOK\n```\n\n\n## Development\n\nInstall the Python version specified in the file `.python-version`.\n\nDependencies are managed using `pipenv`:\n\n```sh\npip install pipenv --user\npipenv install --dev\n```\n\n\n### Building\n\n```sh\npipenv run make dist\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaelfranzl%2Fgrbl-streamer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichaelfranzl%2Fgrbl-streamer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaelfranzl%2Fgrbl-streamer/lists"}