{"id":19383970,"url":"https://github.com/leandro-bertoluzzi/remote-cnc","last_synced_at":"2026-05-01T21:07:13.323Z","repository":{"id":249897203,"uuid":"832341532","full_name":"Leandro-Bertoluzzi/remote-cnc","owner":"Leandro-Bertoluzzi","description":"Qt app and web API to monitor and manage a GRBL-based CNC machine","archived":false,"fork":false,"pushed_at":"2026-04-30T01:29:53.000Z","size":1744,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-30T03:28:59.476Z","etag":null,"topics":["celery","cnc-controller","fastapi","pyqt5","python","raspberry-pi"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Leandro-Bertoluzzi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-22T20:33:24.000Z","updated_at":"2026-04-30T01:29:56.000Z","dependencies_parsed_at":"2024-11-10T09:28:50.488Z","dependency_job_id":"87ec11d5-64e4-4b7f-b50d-1c4bb1afbbc6","html_url":"https://github.com/Leandro-Bertoluzzi/remote-cnc","commit_stats":null,"previous_names":["leandro-bertoluzzi/remote-cnc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Leandro-Bertoluzzi/remote-cnc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leandro-Bertoluzzi%2Fremote-cnc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leandro-Bertoluzzi%2Fremote-cnc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leandro-Bertoluzzi%2Fremote-cnc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leandro-Bertoluzzi%2Fremote-cnc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Leandro-Bertoluzzi","download_url":"https://codeload.github.com/Leandro-Bertoluzzi/remote-cnc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leandro-Bertoluzzi%2Fremote-cnc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32512746,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["celery","cnc-controller","fastapi","pyqt5","python","raspberry-pi"],"created_at":"2024-11-10T09:28:33.110Z","updated_at":"2026-05-01T21:07:13.310Z","avatar_url":"https://github.com/Leandro-Bertoluzzi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eCNC admin\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"Github top language\" src=\"https://img.shields.io/github/languages/top/Leandro-Bertoluzzi/remote-cnc?color=56BEB8\"\u003e\n\n  \u003cimg alt=\"Github language count\" src=\"https://img.shields.io/github/languages/count/Leandro-Bertoluzzi/remote-cnc?color=56BEB8\"\u003e\n\n  \u003cimg alt=\"Repository size\" src=\"https://img.shields.io/github/repo-size/Leandro-Bertoluzzi/remote-cnc?color=56BEB8\"\u003e\n\n  \u003cimg alt=\"License\" src=\"https://img.shields.io/github/license/Leandro-Bertoluzzi/remote-cnc?color=56BEB8\"\u003e\n\u003c/p\u003e\n\n\u003c!-- Status --\u003e\n\n\u003ch4 align=\"center\"\u003e\n\t🚧 CNC admin 🚀 Under construction...  🚧\n\u003c/h4\u003e\n\n\u003chr\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#dart-about\"\u003eAbout\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#sparkles-features\"\u003eFeatures\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#rocket-technologies\"\u003eTechnologies\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#white_check_mark-requirements\"\u003eRequirements\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#checkered_flag-installation\"\u003eInstallation\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#checkered_flag-development\"\u003eDevelopment\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#rocket-deploy-changes\"\u003eDeploy changes\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#gear-cnc-gateway\"\u003eCNC gateway\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#gear-cnc-worker\"\u003eCNC worker\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"#memo-license\"\u003eLicense\u003c/a\u003e \u0026#xa0; | \u0026#xa0;\n  \u003ca href=\"https://github.com/Leandro-Bertoluzzi\" target=\"_blank\"\u003eAuthors\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr\u003e\n\n## :dart: About\n\nThis repository comprises the following components:\n\n- **Desktop**: A small desktop app to monitor and control an Arduino-based CNC machine, optimized for touchscreen.\n- **API**: REST API to integrate the app's functionalities in a remote client.\n- **Gateway**: Long-running process that manages serial communication with the CNC device.\n- **Worker**: Celery background worker for long-running tasks (e.g. file execution, thumbnail generation).\n\n## :sparkles: Features\n\n:heavy_check_mark: PostgreSQL database management\\\n:heavy_check_mark: G-code files management\\\n:heavy_check_mark: Real time monitoring of CNC status\\\n:heavy_check_mark: Communication with GRBL-compatible CNC machine via USB\\\n:heavy_check_mark: Long-running process delegation via message broker\n\n## :rocket: Technologies\n\nThe following tools were used in this project:\n\n- Programming language: [Python](https://www.python.org/)\n- API framework: [FastAPI](https://fastapi.tiangolo.com/)\n- UI (desktop) framework: [PyQt](https://wiki.python.org/moin/PyQt)\n- Database: [PostgreSQL](https://www.postgresql.org/)\n- ORM: [SQLAlchemy](https://www.sqlalchemy.org/)\n- DB migrations: [Alembic](https://alembic.sqlalchemy.org/en/latest/)\n- Tasks queue: [Celery](https://docs.celeryq.dev/en/stable/)\n- Message broker: [Redis](https://redis.io/)\n- Containerization: [Docker](https://www.docker.com/)\n\n## :white_check_mark: Requirements\n\nBefore starting :checkered_flag:, you need to have [Python](https://www.python.org/), [uv](https://docs.astral.sh/uv/) and [Docker](https://www.docker.com/) installed.\n\nOptionally, install [just](https://just.systems/) to use the project's command runner (`justfile`). See the [available recipes](#available-recipes) for a full reference.\n\n## :checkered_flag: Installation\n\nThere is a folder for each subproject in docs, which contain instructions to start using them in production:\n\n- Desktop: See installation docs for desktop app [here](docs/desktop/installation.md).\n- API: See installation docs for API [here](docs/api/server-setup.md).\n\n### Set up database\n\nYou can copy the script `deployment/db_schema.py` to the Raspberry and follow [these steps](docs/db/db-management.md#execute-a-sql-script).\n\n## :checkered_flag: Development\n\n### Project structure\n\nThis is a **uv workspace** monorepo. All Python source lives under `src/`:\n\n```text\nsrc/\n├── pyproject.toml          # Workspace root (virtual)\n├── uv.lock                 # Single lockfile for the whole workspace\n├── core/                   # Shared library: database, config, utilities, schemas\n│   ├── pyproject.toml\n│   ├── core/               # Python package\n│   ├── alembic/            # Database migrations\n│   └── alembic.ini\n├── api/                    # FastAPI REST API\n│   ├── pyproject.toml\n│   ├── main.py             # Entry point\n│   ├── routes/\n│   ├── middleware/\n│   └── tests/\n├── worker/                 # Celery background worker\n│   ├── pyproject.toml\n│   ├── main.py             # Entry point\n│   ├── tasks/\n│   └── tests/\n├── gateway/                # CNC gateway (serial communication)\n│   ├── pyproject.toml\n│   └── gateway/            # Python package\n├── desktop/                # PyQt5 desktop application\n│   ├── pyproject.toml\n│   ├── main.py             # Entry point\n│   ├── views/\n│   ├── components/\n│   └── tests/\n└── tests/                  # Core / shared tests + mocks\n    ├── conftest.py\n    └── mocks/\n```\n\n### Available recipes\n\nThe `justfile` at the root of the repository contains all common development and operations commands, organised by group. Run `just` (no arguments) to see the full list at any time.\n\n\u003e `*` Requires Docker containers to be running.\n\n| Recipe                                | Group    | Description                                   |\n| ------------------------------------- | -------- | --------------------------------------------- |\n| `sync`                                | setup    | Install / update deps from lockfile           |\n| `lock`                                | setup    | Re-resolve \u0026 update `uv.lock`                 |\n| `test`                                | quality  | Run all tests                                 |\n| `test-core`                           | quality  | Core / shared tests                           |\n| `test-api`                            | quality  | API tests                                     |\n| `test-worker`                         | quality  | Worker tests                                  |\n| `test-desktop`                        | quality  | Desktop tests                                 |\n| `lint`                                | quality  | Run linter                                    |\n| `lint-fix`                            | quality  | Run linter with auto-fix                      |\n| `format`                              | quality  | Run formatter                                 |\n| `typecheck`                           | quality  | Run type checker                              |\n| `check`                               | quality  | lint + typecheck + test                       |\n| `start-api`                           | run      | Start the API with uvicorn (dev)              |\n| `start-desktop`                       | run      | Start the desktop (PyQt5) app                 |\n| `start-desktop-watch`                 | run      | Desktop app with auto-reload                  |\n| `start-worker`                        | run      | Start the Celery worker                       |\n| `start-worker-watch`                  | run      | Celery worker with auto-reload                |\n| `db-upgrade`                          | database | Apply pending migrations (local)              |\n| `db-downgrade`                        | database | Revert last migration (local)                 |\n| `db-revision \u003cmsg\u003e`                   | database | Auto-generate a new migration                 |\n| `db-seed`                             | database | Seed the database (local)                     |\n| `db-generate-schema`                  | database | Export full schema as SQL                     |\n| `db-generate-migration \u003cstart\u003e \u003cend\u003e` | database | Export SQL for a migration range              |\n| `db-upgrade-docker` `*`               | database | Apply migrations in the API container         |\n| `db-seed-docker` `*`                  | database | Seed the database in the API container        |\n| `db-backup` `*`                       | database | Backup DB from the PostgreSQL container       |\n| `db-execute-script \u003cpath\u003e` `*`        | database | Run a SQL script against the DB container     |\n| `docker-up`                           | docker   | Start API + worker + infra                    |\n| `docker-up-dev`                       | docker   | Start everything + simulated CNC gateway      |\n| `docker-down`                         | docker   | Stop all containers                           |\n| `docker-build`                        | docker   | Rebuild Docker images                         |\n| `docker-logs`                         | docker   | Tail logs of all containers                   |\n| `docker-shell` `*`                    | docker   | Open a shell in the API container             |\n| `deploy-create-builder`               | deploy   | Create multi-arch buildx builder (once)       |\n| `deploy-api \u003cuser\u003e`                   | deploy   | Build \u0026 push multi-arch API image             |\n| `deploy-worker \u003cuser\u003e`                | deploy   | Build \u0026 push multi-arch worker image          |\n| `deploy-gateway \u003cuser\u003e`               | deploy   | Build \u0026 push multi-arch gateway image         |\n| `clean`                               | cleanup  | Remove compiled files, caches, logs, coverage |\n\n### First-time setup\n\n```bash\n# 1. Clone the repository\n$ git clone https://github.com/Leandro-Bertoluzzi/remote-cnc.git\n$ cd remote-cnc\n\n# 2. Copy and fill in environment variables\n$ cp .env.example .env\n\n# 3. Install all dependencies (creates a single virtualenv for the workspace)\n$ just sync\n```\n\n### Running with Docker (recommended)\n\nThe easiest way to run the needed services is with `Docker`. This will start the API, the Celery worker, and the following services:\n\n- PostgreSQL DB.\n- Message broker (Redis).\n- Flower, to monitor the Celery worker.\n\n```bash\n$ just docker-up\n```\n\nIf you want to also start the CNC gateway to connect a physical CNC device (Linux only, see [this section](#gear-cnc-gateway)):\n\n```bash\n$ docker compose --profile=device up -d\n```\n\nOpen [http://localhost:8000](http://localhost:8000) with your browser to check if the API works.\n\n### Running locally (without Docker)\n\n```bash\n# Start the API\n$ just start-api\n\n# Start the desktop app\n$ just start-desktop\n\n# Start the Celery worker\n$ just start-worker\n```\n\nYou can find further information in each subproject's docs folder:\n\n- Desktop: See development docs for desktop app [here](docs/desktop/development.md).\n- API: See development docs for API [here](docs/api/development.md).\n\n### Mock external services\n\nYou can also run a gateway with a mocked version of the GRBL device, which runs the [GRBL simulator](https://github.com/grbl/grbl-sim).\n**NOTE:** This also works on Windows, since the simulated gateway doesn't need USB device access.\n\n```bash\n$ docker compose --profile=simulator up\n```\n\nThe simulated gateway automatically creates the virtual serial port on startup using the GRBL simulator. Make sure your environment has the following variable set:\n\n```bash\nSERIAL_PORT=/dev/ttyUSBFAKE\n```\n\n### Manage database\n\nTo see your database, you can connect to it with a client like [DBeaver](https://dbeaver.io/).\n\nYou can manage database migrations with the following `just` recipes (which wrap Alembic commands):\n\n- Apply all migrations:\n\n```bash\n$ just db-upgrade\n```\n\n- Revert last migration:\n\n```bash\n$ just db-downgrade\n```\n\n- Auto-generate a new revision:\n\n```bash\n$ just db-revision \"add users table\"\n```\n\n- Seed DB with initial data:\n\n```bash\n$ just db-seed\n```\n\nMore info about Alembic usage [here](https://alembic.sqlalchemy.org/en/latest/tutorial.html).\n\nIf you are using `docker compose`, you can apply migrations and seed the database without entering the container:\n\n```bash\n$ just db-upgrade-docker\n$ just db-seed-docker\n```\n\n## :rocket: Deploy changes\n\nThere is a folder for each subproject in docs, which contain instructions to deploy changes to production:\n\n- Desktop: See deployment docs for desktop app [here](docs/desktop/deployment.md).\n- API: See deployment docs for API [here](docs/api/deployment.md).\n\n### Update Docker containers\n\nIf we modify the Docker image for the API, Worker, or Gateway, or we just need to update the version of one of the other services, we have to follow the next steps.\n\n1. If not logged, log in to your Docker account:\n\n```bash\n$ docker login\n```\n\n2. In the server, stop, update and restart the project in production mode:\n\n```bash\n$ cd /home/username/adminapp\n$ docker compose -f docker-compose.yaml -f docker-compose.production.yaml stop\n$ docker compose -f docker-compose.yaml -f docker-compose.production.yaml rm -f\n$ docker compose -f docker-compose.yaml -f docker-compose.production.yaml pull\n$ docker compose -f docker-compose.yaml -f docker-compose.production.yaml up -d\n```\n\n**NOTE:** Take into account that you may add `--profile=device` to each command above if you are using the CNC `gateway` service. The same applies for other optional services (ngrok).\n\n### Database migrations\n\n1. Generate a SQL script for the migration following [these steps](rpi/db-management.md#generate-sql-from-migrations-development).\n1. You can copy the migration script to the Raspberry and follow [these steps](rpi/db-management.md#execute-a-sql-script).\n\n### Update CNC worker Docker image\n\nIf you have made changes to the worker code, you must generate a Docker image for the architecture of the Raspberry (ARM 32 v7), to pull it in production. The easiest method to achieve that is by [using buildx](https://docs.docker.com/build/building/multi-platform/#multiple-native-nodes).\n\n**The first time** we generate the image, we must create a custom builder.\n\n```bash\ndocker buildx create --name raspberry --driver=docker-container\n```\n\nThen, the command to actually generate the image and update the remote repository is the following:\n\n```bash\ndocker buildx build --platform linux/arm/v7,linux/amd64 --tag {{your_dockerhub_user}}/cnc-worker:latest --builder=raspberry --target production --file src/Dockerfile.worker --push src\n```\n\n**NOTE:** You may have to log in with `docker login` previous to run the build command.\n\nThen, follow the guide to [update Docker containers](#update-docker-containers) in the Raspberry.\n\n### Update CNC gateway Docker image\n\nSimilarly, if you have made changes to the gateway code, generate a multi-arch image:\n\n```bash\ndocker buildx build --platform linux/arm/v7,linux/amd64 --tag {{your_dockerhub_user}}/cnc-gateway:latest --builder=raspberry --target production --file src/Dockerfile.gateway --push src\n```\n\nThen, follow the guide to [update Docker containers](#update-docker-containers) in the Raspberry.\n\n## :gear: CNC gateway\n\nThe CNC gateway manages serial communication with the physical CNC device. It should start automatically when running `docker compose --profile=device up`, with certain conditions:\n\n- It only works with Docker CE without Docker Desktop, because the latter can't mount devices. You can view a discussion about it [here](https://forums.docker.com/t/usb-devices-mapping-not-works-with-docker-desktop/132148).\n- Therefore, and given that devices in Windows work in a completely different way (there is no `/dev` folder), you won't be able to run the `gateway` service on Windows. For that reason, in Windows you can use the [simulated gateway](#mock-external-services) instead.\n\n## :gear: CNC worker\n\n### Start the Celery worker manually (Linux)\n\nIn case you don't use Docker or just want to run it manually, you can follow the next steps.\n\n```bash\n# Start Celery's worker server\n$ just start-worker\n```\n\nOptionally, if you are going to make changes in the worker's code and want to see them in real time, you can start the Celery worker with auto-reload.\n\n```bash\n$ cd src \u0026\u0026 uv run watchmedo auto-restart --directory=./ --pattern=*.py -- celery --app worker.main worker --loglevel=INFO --logfile=logs/celery.log\n```\n\n### Start the Celery worker manually (Windows)\n\nDue to a known problem with Celery's default pool (prefork), it is not as straightforward to start the worker in Windows. In order to do so, we have to explicitly indicate Celery to use another pool. You can read more about this issue [here](https://celery.school/celery-on-windows).\n\n- **solo**: The solo pool is a simple, single-threaded execution pool. It simply executes incoming tasks in the same process and thread as the worker.\n\n```bash\n$ cd src \u0026\u0026 uv run celery --app worker.main worker --loglevel=INFO --logfile=logs/celery.log --pool=solo\n```\n\n- **threads**: The threads in the threads pool type are managed directly by the operating system kernel. As long as Python's ThreadPoolExecutor supports Windows threads, this pool type will work on Windows.\n\n```bash\n$ cd src \u0026\u0026 uv run celery --app worker.main worker --loglevel=INFO --logfile=logs/celery.log --pool=threads\n```\n\n- **gevent**: The [gevent package](http://www.gevent.org/) officially supports Windows, so it remains a suitable option for IO-bound task processing on Windows. Downside is that you have to install it first.\n\n```bash\n# 1. Install gevent\n$ cd src \u0026\u0026 uv add gevent\n\n# 2. Start Celery's worker server\n$ cd src \u0026\u0026 uv run celery --app worker.main worker --loglevel=INFO --logfile=logs/celery.log --pool=gevent\n```\n\n## :wrench: Running tests\n\nYou can use the following commands to execute tests and quality checks:\n\n```bash\n# Run all tests\n$ just test\n\n# Run tests for a specific service\n$ just test-core\n$ just test-api\n$ just test-worker\n$ just test-desktop\n\n# Run all quality checks (lint + typecheck + test)\n$ just check\n```\n\n## :memo: License\n\nThis project is under license from MIT. For more details, see the [LICENSE](LICENSE.md) file.\n\n## :writing_hand: Authors\n\nMade with :heart: by \u003ca href=\"https://github.com/Leandro-Bertoluzzi\" target=\"_blank\"\u003eLeandro Bertoluzzi\u003c/a\u003e and Martín Sellart.\n\n\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandro-bertoluzzi%2Fremote-cnc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleandro-bertoluzzi%2Fremote-cnc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandro-bertoluzzi%2Fremote-cnc/lists"}