├── .vscode ├── settings.json └── extensions.json ├── .releaseconfig.json ├── .DS_Store ├── eslint.config.mjs ├── admin ├── klipper-moonraker.png ├── tsconfig.json ├── style.css └── jsonConfig.json ├── prettier.config.mjs ├── test ├── tsconfig.json ├── package.js ├── mocharc.custom.json ├── unit.js ├── integration.js └── mocha.setup.js ├── .github ├── dependabot.yml ├── workflows │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml ├── auto-merge.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── tsconfig.check.json ├── .npmignore ├── lib ├── adapter-config.d.ts ├── apiResponse.json └── stateAttr.js ├── main.test.js ├── .create-adapter.json ├── LICENSE ├── tsconfig.json ├── package.json ├── README.md ├── io-package.json └── main.js /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true 3 | } -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license", "manual-review"] 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrozmotiX/ioBroker.klipper-moonraker/HEAD/.DS_Store -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import config from '@iobroker/eslint-config'; 2 | 3 | export default [...config]; 4 | -------------------------------------------------------------------------------- /admin/klipper-moonraker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrozmotiX/ioBroker.klipper-moonraker/HEAD/admin/klipper-moonraker.png -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; 2 | 3 | export default prettierConfig; 4 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": false 5 | }, 6 | "include": [ 7 | "./**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Validate the package files 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | target-branch: "dependencyAutoUpdate" 8 | open-pull-requests-limit: 30 9 | -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "test/mocha.setup.js" 4 | ], 5 | "watch-files": [ 6 | "!(node_modules|test)/**/*.test.js", 7 | "*.test.js", 8 | "test/**/test!(PackageFiles|Startup).js" 9 | ] 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | *.code-workspace 4 | node_modules 5 | nbproject 6 | 7 | # npm package files 8 | iobroker.*.tgz 9 | 10 | Thumbs.db 11 | 12 | # i18n intermediate files 13 | admin/i18n/flat.txt 14 | admin/i18n/*/flat.txt -------------------------------------------------------------------------------- /admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "./admin.d.ts", 5 | "./**/*.js", 6 | // include the adapter-config definition if it exists 7 | "../src/lib/adapter-config.d.ts", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/unit.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run unit tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.unit(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.integration(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /tsconfig.check.json: -------------------------------------------------------------------------------- 1 | // Specialized tsconfig for type-checking js files 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": [ 6 | "**/*.js", 7 | "**/*.d.ts" 8 | ], 9 | "exclude": [ 10 | "**/build", 11 | "node_modules/", 12 | "widgets/", 13 | "gulpfile.js" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | node_modules/ 3 | nbproject/ 4 | *.code-workspace 5 | Thumbs.db 6 | gulpfile.js 7 | 8 | # CI test files 9 | test/ 10 | travis/ 11 | appveyor.yaml 12 | 13 | # Type checking configuration 14 | tsconfig.json 15 | tsconfig.*.json 16 | 17 | # npm package files 18 | iobroker.*.tgz 19 | package-lock.json 20 | 21 | # i18n intermediate files 22 | admin/i18n 23 | 24 | # maintenance scripts 25 | maintenance/** -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | // Don't silently swallow unhandled rejections 2 | process.on('unhandledRejection', e => { 3 | throw e; 4 | }); 5 | 6 | // enable the should interface with sinon 7 | // and load chai-as-promised and sinon-chai by default 8 | const sinonChai = require('sinon-chai'); 9 | const chaiAsPromised = require('chai-as-promised'); 10 | const { should, use } = require('chai'); 11 | 12 | should(); 13 | use(sinonChai); 14 | use(chaiAsPromised); 15 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Automatically merge Dependabot PRs when version comparison is within the range 2 | # that is configured in .github/auto-merge.yml 3 | 4 | name: Auto-Merge Dependabot PRs 5 | 6 | on: 7 | pull_request_target: 8 | 9 | jobs: 10 | auto-merge: 11 | if: github.actor == 'dependabot[bot]' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 15 | with: 16 | target: minor 17 | github-token: ${{ secrets.AUTOMERGER }} -------------------------------------------------------------------------------- /lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | // using the actual properties present in io-package.json 3 | // in order to provide typings for adapter.config properties 4 | 5 | import type { native } from '../io-package.json'; 6 | 7 | type _AdapterConfig = typeof native; 8 | 9 | // Augment the globally declared type ioBroker.AdapterConfig 10 | declare global { 11 | namespace ioBroker { 12 | // eslint-disable-next-line 13 | interface AdapterConfig extends _AdapterConfig { 14 | // Do not enter anything here! 15 | } 16 | } 17 | } 18 | 19 | // this is required so the above AdapterConfig is found by TypeScript / type checking 20 | export {}; 21 | -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Configure here which dependency updates should be merged automatically. 2 | # The recommended configuration is the following: 3 | - match: 4 | # Only merge patches for production dependencies 5 | dependency_type: production 6 | update_type: "semver:patch" 7 | - match: 8 | # Except for security fixes, here we allow minor patches 9 | dependency_type: production 10 | update_type: "security:minor" 11 | - match: 12 | # and development dependencies can have a minor update, too 13 | dependency_type: development 14 | update_type: "semver:minor" 15 | 16 | # The syntax is based on the legacy dependabot v1 automerged_updates syntax, see: 17 | # https://dependabot.com/docs/config-file/#automerged_updates 18 | -------------------------------------------------------------------------------- /admin/style.css: -------------------------------------------------------------------------------- 1 | /* You can delete those if you want. I just found them very helpful */ 2 | * { 3 | box-sizing: border-box 4 | } 5 | .m { 6 | /* Don't cut off dropdowns! */ 7 | overflow: initial; 8 | } 9 | .m.adapter-container, 10 | .m.adapter-container > div.App { 11 | /* Fix layout/scrolling issues with tabs */ 12 | height: 100%; 13 | width: 100%; 14 | position: relative; 15 | } 16 | .m .select-wrapper + label { 17 | /* The positioning for dropdown labels is messed up */ 18 | transform: none !important; 19 | } 20 | 21 | label > i[title] { 22 | /* Display the help cursor for the tooltip icons and fix their positioning */ 23 | cursor: help; 24 | margin-left: 0.25em; 25 | } 26 | 27 | .dropdown-content { 28 | /* Don't wrap text in dropdowns */ 29 | white-space: nowrap; 30 | } 31 | 32 | /* Add your styles here */ 33 | -------------------------------------------------------------------------------- /main.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is a dummy TypeScript test file using chai and mocha 5 | * 6 | * It's automatically excluded from npm and its build output is excluded from both git and npm. 7 | * It is advised to test all your modules with accompanying *.test.js-files 8 | */ 9 | 10 | const { expect } = require('chai'); 11 | const { describe, it } = require('mocha'); 12 | // import { functionToTest } from "./moduleToTest"; 13 | 14 | describe('module to test => function to test', () => { 15 | // initializing logic 16 | const expected = 5; 17 | 18 | it(`should return ${expected}`, () => { 19 | const result = 5; 20 | // assign result a value from functionToTest 21 | expect(result).to.equal(expected); 22 | // or using the should() syntax 23 | result.should.equal(expected); 24 | }); 25 | // ... more tests => it 26 | }); 27 | 28 | // ... more test suites => describe 29 | -------------------------------------------------------------------------------- /.create-adapter.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": true, 3 | "adapterName": "klipper-moonraker", 4 | "title": "Klipper Moonraker", 5 | "description": "Adapter to interact with klipper by API", 6 | "keywords": [ 7 | "klipper", 8 | "printing", 9 | "3d" 10 | ], 11 | "expert": "yes", 12 | "features": [ 13 | "adapter" 14 | ], 15 | "adminFeatures": [], 16 | "type": "hardware", 17 | "startMode": "daemon", 18 | "connectionType": "local", 19 | "dataSource": "poll", 20 | "connectionIndicator": "yes", 21 | "language": "JavaScript", 22 | "adminReact": "no", 23 | "tools": [ 24 | "ESLint", 25 | "type checking" 26 | ], 27 | "indentation": "Tab", 28 | "quotes": "single", 29 | "es6class": "yes", 30 | "authorName": "DutchmanNL", 31 | "authorGithub": "DrozmotiX", 32 | "authorEmail": "rdrozda@hotmail.com", 33 | "gitRemoteProtocol": "HTTPS", 34 | "gitCommit": "yes", 35 | "license": "MIT License", 36 | "ci": "gh-actions", 37 | "dependabot": "yes", 38 | "creatorVersion": "1.31.0" 39 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '...' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots & Logfiles** 23 | If applicable, add screenshots and logfiles to help explain your problem. 24 | 25 | **Versions:** 26 | - Adapter version: 27 | - JS-Controller version: 28 | - Node version: 29 | - Operating system: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 DutchmanNL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Root tsconfig to set the settings and power editor support for all TS files 2 | { 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | // do not compile anything, this file is just to configure type checking 6 | "noEmit": true, 7 | 8 | // check JS files 9 | "allowJs": true, 10 | "checkJs": true, 11 | 12 | "module": "commonjs", 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | // this is necessary for the automatic typing of the adapter config 16 | "resolveJsonModule": true, 17 | "useUnknownInCatchVariables": false, 18 | 19 | // Set this to false if you want to disable the very strict rules (not recommended) 20 | "strict": true, 21 | // Or enable some of those features for more fine-grained control 22 | // "strictNullChecks": true, 23 | // "strictPropertyInitialization": true, 24 | // "strictBindCallApply": true, 25 | "noImplicitAny": false, 26 | // "noUnusedLocals": true, 27 | // "noUnusedParameters": true, 28 | 29 | // Consider targetting es2019 or higher if you only support Node.js 12+ 30 | "target": "es2018", 31 | 32 | }, 33 | "include": [ 34 | "**/*.js", 35 | "**/*.d.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules/**" 39 | ] 40 | } -------------------------------------------------------------------------------- /admin/jsonConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "items": { 4 | "customName": { 5 | "label": "Name", 6 | "default": "My 3D printer", 7 | "type": "text", 8 | "help": "Define your own printer name for this instance" 9 | }, 10 | "klipperPort": { 11 | "newLine": true, 12 | "label": "Port", 13 | "type": "number", 14 | "default": 80 15 | }, 16 | "klipperIP": { 17 | "label": "IP", 18 | "type": "text", 19 | "default": "192.168.1.50", 20 | "help": "IP of your Klipper installation" 21 | }, 22 | "apiRefreshInterval": { 23 | "newLine": true, 24 | "label": "Refresh interval", 25 | "default": 60, 26 | "min": 1, 27 | "type": "number", 28 | "help": "in Seconds" 29 | }, 30 | "apiRefreshIntervalOperational": { 31 | "label": "Operational refresh interval", 32 | "default": 30, 33 | "min": 1, 34 | "type": "number", 35 | "help": "in Seconds" 36 | }, 37 | "apiRefreshIntervalPrinting": { 38 | "label": "Printing refresh interval", 39 | "default": 10, 40 | "min": 1, 41 | "type": "number", 42 | "help": "in Seconds" 43 | }, 44 | "auth": { 45 | "newLine": true, 46 | "label": "Authenticate", 47 | "type": "checkbox", 48 | "default": false, 49 | "help": "If authentication is required" 50 | }, 51 | "useSsl": { 52 | "type": "checkbox", 53 | "label": "Use SSL", 54 | "help": "If the API is reachable over https" 55 | }, 56 | "user": { 57 | "newLine": true, 58 | "label": "Username", 59 | "type": "text", 60 | "default": "", 61 | "hidden": "!data.auth" 62 | }, 63 | "password": { 64 | "label": "Password", 65 | "type": "password", 66 | "default": "", 67 | "hidden": "!data.auth" 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.klipper-moonraker", 3 | "version": "0.1.1", 4 | "description": "Adapter to interact with klipper by API", 5 | "author": { 6 | "name": "DutchmanNL", 7 | "email": "rdrozda@hotmail.com" 8 | }, 9 | "homepage": "https://github.com/DrozmotiX/ioBroker.klipper-moonraker", 10 | "license": "MIT", 11 | "keywords": [ 12 | "klipper", 13 | "printing", 14 | "3d" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/DrozmotiX/ioBroker.klipper-moonraker" 19 | }, 20 | "engines": { 21 | "node": ">=18" 22 | }, 23 | "dependencies": { 24 | "@iobroker/adapter-core": "^3.1.4", 25 | "ws": "7.5.9" 26 | }, 27 | "devDependencies": { 28 | "@alcalzone/release-script": "^3.7.0", 29 | "@alcalzone/release-script-plugin-iobroker": "^3.7.0", 30 | "@alcalzone/release-script-plugin-license": "^3.7.0", 31 | "@alcalzone/release-script-plugin-manual-review": "^3.7.0", 32 | "@iobroker/dev-server": "^0.7.2", 33 | "@iobroker/eslint-config": "^2.0.1", 34 | "@iobroker/testing": "^5.0.4", 35 | "@types/chai": "^4.3.14", 36 | "@types/chai-as-promised": "^7.1.8", 37 | "@types/mocha": "^10.0.6", 38 | "@types/node": "^20.12.7", 39 | "@types/proxyquire": "^1.3.31", 40 | "@types/sinon": "^17.0.3", 41 | "@types/sinon-chai": "^3.2.12", 42 | "@types/ws": "^8.5.10", 43 | "axios": "0.21.4", 44 | "chai": "^4.4.1", 45 | "chai-as-promised": "^7.1.1", 46 | "mocha": "10.4.0", 47 | "proxyquire": "^2.1.3", 48 | "sinon": "^17.0.1", 49 | "sinon-chai": "^3.7.0", 50 | "typescript": "^5.4.5" 51 | }, 52 | "main": "main.js", 53 | "scripts": { 54 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 55 | "test:package": "mocha test/package --exit", 56 | "test:unit": "mocha test/unit --exit", 57 | "test:integration": "mocha test/integration --exit", 58 | "test": "npm run test:js && npm run test:package", 59 | "check": "tsc --noEmit -p tsconfig.check.json", 60 | "lint": "eslint", 61 | "release": "release-script" 62 | }, 63 | "bugs": { 64 | "url": "https://github.com/DrozmotiX/ioBroker.klipper-moonraker/issues" 65 | }, 66 | "readmeFilename": "README.md" 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | 3 | # Run this job on all pushes and pull requests 4 | # as well as tags with a semantic version 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | tags: 10 | # normal versions 11 | - "v[0-9]+.[0-9]+.[0-9]+" 12 | # pre-releases 13 | - "v[0-9]+.[0-9]+.[0-9]+-**" 14 | pull_request: {} 15 | 16 | # Cancel previous PR/branch runs when a new commit is pushed 17 | concurrency: 18 | group: ${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | # Performs quick checks before the expensive test runs 23 | check-and-lint: 24 | if: contains(github.event.head_commit.message, '[skip ci]') == false 25 | 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: ioBroker/testing-action-check@v1 30 | with: 31 | node-version: '18.x' 32 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 33 | # install-command: 'npm install' 34 | lint: true 35 | 36 | # Runs adapter tests on all supported node versions and OSes 37 | adapter-tests: 38 | if: contains(github.event.head_commit.message, '[skip ci]') == false 39 | 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | matrix: 43 | node-version: [18.x, 20.x] 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | 46 | steps: 47 | - uses: ioBroker/testing-action-adapter@v1 48 | with: 49 | node-version: ${{ matrix.node-version }} 50 | os: ${{ matrix.os }} 51 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 52 | # install-command: 'npm install' 53 | 54 | # TODO: To enable automatic npm releases, create a token on npmjs.org 55 | # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options 56 | # Then uncomment the following block: 57 | 58 | # Deploys the final package to NPM 59 | deploy: 60 | needs: [check-and-lint, adapter-tests] 61 | 62 | # Trigger this step only when a commit on any branch is tagged with a version number 63 | if: | 64 | contains(github.event.head_commit.message, '[skip ci]') == false && 65 | github.event_name == 'push' && 66 | startsWith(github.ref, 'refs/tags/v') 67 | 68 | runs-on: ubuntu-latest 69 | 70 | # Write permissions are required to create Github releases 71 | permissions: 72 | contents: write 73 | 74 | steps: 75 | - uses: ioBroker/testing-action-deploy@v1 76 | with: 77 | node-version: '18.x' 78 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 79 | # install-command: 'npm install' 80 | npm-token: ${{ secrets.NPM_TOKEN }} 81 | github-token: ${{ secrets.AUTOMERGER }} 82 | 83 | # When using Sentry for error reporting, Sentry can be informed about new releases 84 | # To enable create a API-Token in Sentry (User settings, API keys) 85 | # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options 86 | # Then uncomment and customize the following block: 87 | sentry: true 88 | sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 89 | sentry-project: "iobroker-klipper-moonraker" 90 | sentry-version-prefix: "iobroker.klipper-moonraker" 91 | # If your sentry project is linked to a GitHub repository, you can enable the following option 92 | # sentry-github-integration: true 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/klipper-moonraker.png) 2 | # ioBroker.klipper-moonraker 3 | 4 | [![NPM version](http://img.shields.io/npm/v/iobroker.klipper-moonraker.svg)](https://www.npmjs.com/package/iobroker.klipper-moonraker) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.klipper-moonraker.svg)](https://www.npmjs.com/package/iobroker.klipper-moonraker) 6 | ![Number of Installations (latest)](http://iobroker.live/badges/klipper-moonraker-installed.svg) 7 | ![Number of Installations (stable)](http://iobroker.live/badges/klipper-moonraker-stable.svg) 8 | [![Dependency Status](https://img.shields.io/david/DrozmotiX/iobroker.klipper-moonraker.svg)](https://david-dm.org/DrozmotiX/iobroker.klipper-moonraker) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/DrozmotiX/ioBroker.klipper-moonraker/badge.svg)](https://snyk.io/test/github/DrozmotiX/ioBroker.klipper-moonraker) 10 | 11 | [![NPM](https://nodei.co/npm/iobroker.klipper-moonraker.png?downloads=true)](https://nodei.co/npm/iobroker.klipper-moonraker/) 12 | 13 | **Tests:** ![Test and Release](https://github.com/DrozmotiX/ioBroker.klipper-moonraker/workflows/Test%20and%20Release/badge.svg) 14 | 15 | ## klipper-moonraker adapter for ioBroker 16 | 17 | An IOBroker Adapter to interact with klipper by the Moonraker-API. 18 | 19 | The Testclient was created with Kiauh. Kiauh is a Script that help you creating a perfect environment for your Klipper Setup. 20 | 21 | Kiauh: 22 | https://github.com/th33xitus/kiauh 23 | 24 | 25 | ## Changelog 26 | 27 | 31 | ### 0.1.1 (2025-05-07) 32 | * (@foxriver76) detect stale connections 33 | 34 | ### 0.1.0 (2024-04-22) 35 | * IMPORTANT: The adapter requires Node.js 18.x+ 36 | * (foxriver76) added state to run custom GCODE commands 37 | * (foxriver76) added possiblity to use authentication 38 | * (foxriver76) corrected some state definitions 39 | * (foxriver76) ported UI to json config 40 | 41 | ### 0.0.4 (2021-03-17) 42 | * (DutchmanNL) Implemented rounding of digits 43 | * (DutchmanNL) Transfer ownership to DrozmotiX 44 | * (DutchmanNL) Improve automerge for dependency updates 45 | * (Basti-RX) Update state attribute relations 46 | 47 | ### 0.0.3 (2021-01-07) 48 | * (DutchmanNL) Switch from data-polling to live socket events :-) 49 | * (DutchmanNL) Ensure all states and objects available are created 50 | * (DutchmanNL) reconnect if connection closes (retry after 10 sec, ToDo : make adjustable) 51 | 52 | ### 0.0.2 (2021-01-05) 53 | * (DutchmanNL) Implement control commands 54 | * (DutchmanNL) Proper error handling for API calls 55 | * (DutchmanNL) update state attributes for control commands 56 | 57 | ### 0.0.1 58 | * (DutchmanNL) initial release 59 | 60 | ## License 61 | MIT License 62 | 63 | Copyright (c) 2020-2025 DutchmanNL 64 | 65 | Permission is hereby granted, free of charge, to any person obtaining a copy 66 | of this software and associated documentation files (the "Software"), to deal 67 | in the Software without restriction, including without limitation the rights 68 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 69 | copies of the Software, and to permit persons to whom the Software is 70 | furnished to do so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in all 73 | copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 76 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 77 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 78 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 79 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 80 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 81 | SOFTWARE. 82 | -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "klipper-moonraker", 4 | "version": "0.1.1", 5 | "news": { 6 | "0.1.1": { 7 | "en": "(@foxriver76) detect stale connections", 8 | "de": "(@foxriver76) verbindungen erkennen", 9 | "ru": "(@foxriver76) обнаружение устаревших соединений", 10 | "pt": "(@foxriver76) detecta conexões de estalo", 11 | "nl": "(@foxriver76) detecteer oude verbindingen", 12 | "fr": "(@foxriver76) détecter les connexions statiques", 13 | "it": "(@foxriver76) rileva collegamenti stanti", 14 | "es": "(@foxriver76) detecta conexiones de estantería", 15 | "pl": "(@ foxriver76) wykryj nieregularne połączenia", 16 | "uk": "(@foxriver76) виявляти застібки", 17 | "zh-cn": "(@foxriver76) 检测断层连接" 18 | }, 19 | "0.1.0": { 20 | "en": "IMPORTANT: The adapter requires Node.js 18.x+\nadded state to run custom GCODE commands\nadded possiblity to use authentication\ncorrected some state definitions\nported UI to json config", 21 | "de": "WICHTIG: Der Adapter benötigt Node.js 18.x+\nstatus hinzugefügt, um benutzerdefinierte GCODE-Befehle auszuführen\nhinzugefügt possiblity zu verwenden authentifizierung\neinige staatliche definitionen korrigiert\nportiert UI zu json config", 22 | "ru": "ВАЖНО: адаптер требует Node.js 18.x+\nдобавленное состояние для запуска пользовательских команд GCODE\nдобавлена возможность использования аутентификации\nисправить некоторые определения состояния\nпортированный пользовательский интерфейс к конфигурации json", 23 | "pt": "IMPORTANTE: O adaptador requer Node.js 18.x+\nestado adicionado para executar comandos GCODE personalizados\npossiblity adicionado para usar a autenticação\ncorrigiu algumas definições de estado\nported UI to json config", 24 | "nl": "BELANGRIJK: De adapter vereist Node.js 18.x+\nstatus toegevoegd om aangepaste GCODE-opdrachten uit te voeren\ntoegevoegde mogelijkheid om authenticatie te gebruiken\ngecorrigeerd sommige staat definities\nuI geporteerd naar json config", 25 | "fr": "IMPORTANT: L'adaptateur nécessite Node.js 18.x+\nétat ajouté pour exécuter des commandes GCODE personnalisées\npossibilité d'utiliser l'authentification\ncorrigé certaines définitions d'état\nl'interface utilisateur portée à json config", 26 | "it": "IMPORTANTE: L'adattatore richiede Node.js 18.x+\nstato aggiunto per eseguire comandi GCODE personalizzati\naggiunto possiblity per utilizzare l'autenticazione\ncorretto alcune definizioni di stato\nported UI a json config", 27 | "es": "IMPORTANTE: El adaptador requiere Node.js 18.x+\nestado añadido para ejecutar comandos GCODE personalizados\nañadir possiblity a utilizar autenticación\ncorregido algunas definiciones estatales\nported UI to json config", 28 | "pl": "WAŻNE: Adapter wymaga Node.js 18.x +\ndodany stan do uruchamiania własnych poleceń GCODE\ndodana możliwość korzystania z uwierzytelniania\nskorygowane niektóre definicje stanu\nprzetransportowane UI do json config", 29 | "uk": "IMPORTANT: Адаптер вимагає Node.js 18.x+\nдодано стан для запуску користувацьких команд GCODE\nдодано можливість використання автентичності\nвиправлено деякі визначення стану\nported UI в json config", 30 | "zh-cn": "重要:适配器需要Node.js 18.x+\n为运行自定义的 GCODE 命令添加状态\n添加 possiblity 以使用认证\n更正一些州定义\n将 UI 移植到 Json 配置" 31 | }, 32 | "0.0.4": { 33 | "en": "Implemented rounding of digits\nTransfer ownership to DrozmotiX\nImprove automerge for dependency updates\nUpdate state attribute relations", 34 | "de": "Rundung von Ziffern implementiert\nÜbertragen Sie das Eigentum an DrozmotiX\nVerbessern Sie Automerge für Abhängigkeitsaktualisierungen\nAktualisieren Sie die Statusattributbeziehungen", 35 | "ru": "Реализовано округление цифр\nПередать право собственности DrozmotiX\nУлучшение автоматического слияния для обновлений зависимостей\nОбновить отношения атрибутов состояния", 36 | "pt": "Arredondamento de dígitos implementado\nTransferir propriedade para DrozmotiX\nMelhore o automerge para atualizações de dependências\nAtualizar relações de atributo de estado", 37 | "nl": "Afronding van cijfers geïmplementeerd\nDraag het eigendom over aan DrozmotiX\nVerbeter automerge voor afhankelijkheidsupdates\nUpdate statusattribuutrelaties", 38 | "fr": "Arrondi des chiffres implémenté\nTransférer la propriété à DrozmotiX\nAméliorer la fusion automatique pour les mises à jour des dépendances\nMettre à jour les relations d'attributs d'état", 39 | "it": "Arrotondamento delle cifre implementato\nTrasferisci la proprietà a DrozmotiX\nMigliora l'automerge per gli aggiornamenti delle dipendenze\nAggiorna le relazioni degli attributi di stato", 40 | "es": "Redondeo de dígitos implementado\nTransferir la propiedad a DrozmotiX\nMejorar la automatización para las actualizaciones de dependencias\nActualizar relaciones de atributos de estado", 41 | "pl": "Zaimplementowano zaokrąglanie cyfr\nPrzenieś własność na DrozmotiX\nPopraw automerge dla aktualizacji zależności\nZaktualizuj relacje atrybutów stanu", 42 | "zh-cn": "实施数字舍入\n将所有权转让给DrozmotiX\n改进自动合并以更新依赖项\n更新状态属性关系" 43 | }, 44 | "0.0.3": { 45 | "en": "Switch from data-polling to live socket events :-)\nEnsure all states and objects available are created\nreconnect if connection closes (retry after 10 sec, ToDo : make adjustable)", 46 | "de": "Wechseln Sie von Datenabfragen zu Live-Socket-Ereignissen :-)\nStellen Sie sicher, dass alle verfügbaren Status und Objekte erstellt wurden\nStellen Sie die Verbindung wieder her, wenn die Verbindung geschlossen wird (versuchen Sie es nach 10 Sekunden erneut. ToDo: Einstellbar machen)", 47 | "ru": "Переключитесь с опроса данных на живые события сокета :-)\nУбедитесь, что созданы все доступные состояния и объекты.\nповторно подключиться, если соединение закрывается (повторить попытку через 10 секунд, ToDo: сделать настраиваемым)", 48 | "pt": "Mudar de pesquisa de dados para eventos de soquete ao vivo :-)\nCertifique-se de que todos os estados e objetos disponíveis sejam criados\nreconecte se a conexão for fechada (tente novamente após 10 segundos, ToDo: tornar ajustável)", 49 | "nl": "Overschakelen van datapolling naar live socket-evenementen :-)\nZorg ervoor dat alle beschikbare staten en objecten zijn gemaakt\nmaak opnieuw verbinding als de verbinding wordt verbroken (probeer het opnieuw na 10 seconden, ToDo: aanpasbaar maken)", 50 | "fr": "Passer de l'interrogation de données aux événements de socket en direct :-)\nAssurez-vous que tous les états et objets disponibles sont créés\nse reconnecter si la connexion se ferme (réessayer après 10 sec, ToDo: rendre réglable)", 51 | "it": "Passa dal polling dei dati agli eventi live socket :-)\nAssicurarsi che tutti gli stati e gli oggetti disponibili siano stati creati\nriconnettersi se la connessione si chiude (riprovare dopo 10 sec, ToDo: rendere regolabile)", 52 | "es": "Cambie del sondeo de datos a eventos de socket en vivo :-)\nAsegúrese de que se creen todos los estados y objetos disponibles\nvuelva a conectar si la conexión se cierra (vuelva a intentarlo después de 10 segundos, Tareas pendientes: hacer ajustable)", 53 | "pl": "Przełącz się z odpytywania danych na zdarzenia dotyczące gniazd na żywo :-)\nUpewnij się, że zostały utworzone wszystkie dostępne stany i obiekty\npołącz ponownie, jeśli połączenie zostanie zamknięte (spróbuj ponownie po 10 sekundach, Do zrobienia: ustaw)", 54 | "zh-cn": "从数据轮询切换到实时套接字事件:-)\n确保创建了所有可用状态和对象\n如果连接关闭则重新连接(10秒钟后重试,待办事项:可调)" 55 | }, 56 | "0.0.2": { 57 | "en": "Implement control commands\nProper error handling for API calls\nupdate state attributes for control commands", 58 | "de": "Implementieren Sie Steuerbefehle\nRichtige Fehlerbehandlung für API-Aufrufe\nStatusattribute für Steuerbefehle aktualisieren", 59 | "ru": "Команды управления агрегатом\nПравильная обработка ошибок для вызовов API\nобновить атрибуты состояния для управляющих команд", 60 | "pt": "Implementar comandos de controle\nTratamento adequado de erros para chamadas de API\natualizar atributos de estado para comandos de controle", 61 | "nl": "Implementeer besturingsopdrachten\nJuiste foutafhandeling voor API-aanroepen\nupdate statusattributen voor besturingsopdrachten", 62 | "fr": "Mettre en œuvre des commandes de contrôle\nGestion correcte des erreurs pour les appels d'API\nmettre à jour les attributs d'état pour les commandes de contrôle", 63 | "it": "Implementare i comandi di controllo\nCorretta gestione degli errori per le chiamate API\naggiorna gli attributi di stato per i comandi di controllo", 64 | "es": "Implementar comandos de control\nManejo adecuado de errores para llamadas a API\nactualizar atributos de estado para comandos de control", 65 | "pl": "Wdrażaj polecenia sterujące\nWłaściwa obsługa błędów dla wywołań API\nzaktualizuj atrybuty stanu dla poleceń sterujących", 66 | "zh-cn": "实施控制命令\nAPI调用的正确错误处理\n更新控制命令的状态属性" 67 | }, 68 | "0.0.1": { 69 | "en": "initial release", 70 | "de": "Erstveröffentlichung", 71 | "ru": "Начальная версия", 72 | "pt": "lançamento inicial", 73 | "nl": "Eerste uitgave", 74 | "fr": "Première version", 75 | "it": "Versione iniziale", 76 | "es": "Versión inicial", 77 | "pl": "Pierwsze wydanie", 78 | "zh-cn": "首次出版" 79 | } 80 | }, 81 | "title": "Klipper Moonraker", 82 | "titleLang": { 83 | "en": "Klipper Moonraker", 84 | "de": "Klipper Moonraker", 85 | "ru": "Клиппер Мунрейкер", 86 | "pt": "Klipper Moonraker", 87 | "nl": "Klipper Moonraker", 88 | "fr": "Klipper Moonraker", 89 | "it": "Klipper Moonraker", 90 | "es": "Klipper Moonraker", 91 | "pl": "Klipper Moonraker", 92 | "zh-cn": "Klipper Moonraker" 93 | }, 94 | "desc": { 95 | "en": "Adapter to interact with klipper by API", 96 | "de": "Adapter zur Interaktion mit klipper per API", 97 | "ru": "Адаптер для взаимодействия с klipper по API", 98 | "pt": "Adaptador para interagir com klipper por API", 99 | "nl": "Adapter voor interactie met klipper via API", 100 | "fr": "Adaptateur pour interagir avec klipper par API", 101 | "it": "Adattatore per interagire con klipper tramite API", 102 | "es": "Adaptador para interactuar con klipper por API", 103 | "pl": "Adapter do interakcji z klipper przez API", 104 | "zh-cn": "通过API与Klipper交互的适配器" 105 | }, 106 | "authors": [ 107 | "DutchmanNL " 108 | ], 109 | "keywords": [ 110 | "klipper", 111 | "printing", 112 | "3d" 113 | ], 114 | "platform": "Javascript/Node.js", 115 | "main": "main.js", 116 | "icon": "klipper-moonraker.png", 117 | "enabled": true, 118 | "extIcon": "https://raw.githubusercontent.com/DrozmotiX/ioBroker.klipper-moonraker/master/admin/klipper-moonraker.png", 119 | "readme": "https://github.com/DrozmotiX/ioBroker.klipper-moonraker/blob/master/README.md", 120 | "loglevel": "info", 121 | "mode": "daemon", 122 | "type": "hardware", 123 | "compact": true, 124 | "connectionType": "local", 125 | "dataSource": "poll", 126 | "materialize": true, 127 | "adminUI": { 128 | "config": "json" 129 | }, 130 | "dependencies": [ 131 | { 132 | "js-controller": ">=4.0.0" 133 | } 134 | ], 135 | "globalDependencies": [ 136 | { 137 | "admin": ">=6.0.0" 138 | } 139 | ], 140 | "tier": 2, 141 | "licenseInformation": { 142 | "license": "MIT", 143 | "type": "free" 144 | } 145 | }, 146 | "native": { 147 | "customName": "My 3D printer", 148 | "klipperPort": "80", 149 | "klipperIP": "192.168.1.50", 150 | "apiRefreshInterval": 60, 151 | "apiRefreshIntervalOperational": 30, 152 | "apiRefreshIntervalPrinting": 10, 153 | "useSsl": false, 154 | "auth": false, 155 | "password": "", 156 | "user": "" 157 | }, 158 | "protectedNative": [ 159 | "user", 160 | "password" 161 | ], 162 | "encryptedNative": [ 163 | "password" 164 | ], 165 | "objects": [], 166 | "instanceObjects": [ 167 | { 168 | "_id": "info", 169 | "type": "channel", 170 | "common": { 171 | "name": "Information" 172 | }, 173 | "native": {} 174 | }, 175 | { 176 | "_id": "info.connection", 177 | "type": "state", 178 | "common": { 179 | "role": "indicator.connected", 180 | "name": "Device or service connected", 181 | "type": "boolean", 182 | "read": true, 183 | "write": false, 184 | "def": false 185 | }, 186 | "native": {} 187 | } 188 | ] 189 | } 190 | -------------------------------------------------------------------------------- /lib/apiResponse.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "state_message": "Printer is ready", 6 | "klipper_path": "/home/pi/klipper", 7 | "config_file": "/home/pi/klipper_config/printer.cfg", 8 | "software_version": "v0.9.1-140-ge68cf08d", 9 | "hostname": "WLAN-OctoPI", 10 | "cpu_info": "4 core ARMv7 Processor rev 4 (v7l)", 11 | "state": "ready", 12 | "python_path": "/home/pi/klippy-env/bin/python", 13 | "log_file": "/tmp/klippy.log" 14 | }, 15 | "id": "printer.info" 16 | }, 17 | { 18 | "jsonrpc": "2.0", 19 | "result": { 20 | "objects": [ 21 | "webhooks", 22 | "configfile", 23 | "pause_resume", 24 | "gcode_macro PAUSE", 25 | "gcode_macro RESUME", 26 | "gcode_macro CANCEL_PRINT", 27 | "heaters", 28 | "heater_bed", 29 | "fan", 30 | "probe", 31 | "gcode_move", 32 | "bed_mesh", 33 | "gcode_macro G29", 34 | "menu", 35 | "display_status", 36 | "output_pin beeper", 37 | "print_stats", 38 | "virtual_sdcard", 39 | "query_endstops", 40 | "idle_timeout", 41 | "toolhead", 42 | "extruder" 43 | ] 44 | }, 45 | "id": "printer.objects.list" 46 | }, 47 | { 48 | "jsonrpc": "2.0", 49 | "result": { 50 | "status": { 51 | "webhooks": { 52 | "state": "ready", 53 | "state_message": "Printer is ready" 54 | }, 55 | "virtual_sdcard": { 56 | "progress": 0.0, 57 | "is_active": false, 58 | "file_position": 0 59 | }, 60 | "filament_switch_sensor": { 61 | 62 | }, 63 | "output_pin": { 64 | 65 | }, 66 | "bed_mesh": { 67 | "mesh_max": [ 68 | 205.0, 69 | 225.0 70 | ], 71 | "mesh_matrix": [ 72 | [ 73 | -0.07, 74 | -0.04713, 75 | -0.042654, 76 | -0.0475, 77 | -0.054444, 78 | -0.058117, 79 | -0.055, 80 | -0.043426, 81 | -0.02358, 82 | 0.0025, 83 | 0.030926, 84 | 0.055957, 85 | 0.07 86 | ], 87 | [ 88 | -0.066574, 89 | -0.046672, 90 | -0.045315, 91 | -0.052377, 92 | -0.059988, 93 | -0.062544, 94 | -0.056698, 95 | -0.041363, 96 | -0.017714, 97 | 0.010813, 98 | 0.038523, 99 | 0.05746, 100 | 0.057407 101 | ], 102 | [ 103 | -0.052253, 104 | -0.04455, 105 | -0.048852, 106 | -0.057377, 107 | -0.064223, 108 | -0.065369, 109 | -0.058673, 110 | -0.04387, 111 | -0.022578, 112 | 0.001708, 113 | 0.023613, 114 | 0.035883, 115 | 0.029383 116 | ], 117 | [ 118 | -0.0325, 119 | -0.039835, 120 | -0.05036, 121 | -0.06, 122 | -0.065823, 123 | -0.066039, 124 | -0.06, 125 | -0.0482, 126 | -0.032274, 127 | -0.015, 128 | -0.000298, 129 | 0.00677, 130 | 0.0 131 | ], 132 | [ 133 | -0.011759, 134 | -0.032156, 135 | -0.047982, 136 | -0.058673, 137 | -0.064046, 138 | -0.064303, 139 | -0.060031, 140 | -0.052199, 141 | -0.042162, 142 | -0.031656, 143 | -0.022804, 144 | -0.01811, 145 | -0.020463 146 | ], 147 | [ 148 | 0.006543, 149 | -0.021701, 150 | -0.040908, 151 | -0.052747, 152 | -0.05873, 153 | -0.060213, 154 | -0.058395, 155 | -0.054318, 156 | -0.048866, 157 | -0.042767, 158 | -0.036594, 159 | -0.030761, 160 | -0.025525 161 | ], 162 | [ 163 | 0.02, 164 | -0.009218, 165 | -0.029372, 166 | -0.0425, 167 | -0.050298, 168 | -0.054126, 169 | -0.055, 170 | -0.053601, 171 | -0.050267, 172 | -0.045, 173 | -0.037459, 174 | -0.026965, 175 | -0.0125 176 | ], 177 | [ 178 | 0.027222, 179 | 0.003986, 180 | -0.014657, 181 | -0.029136, 182 | -0.039755, 183 | -0.046698, 184 | -0.050031, 185 | -0.049695, 186 | -0.045511, 187 | -0.037181, 188 | -0.024284, 189 | -0.006278, 190 | 0.0175 191 | ], 192 | [ 193 | 0.02784, 194 | 0.016046, 195 | 0.000909, 196 | -0.014784, 197 | -0.028685, 198 | -0.038893, 199 | -0.043951, 200 | -0.042843, 201 | -0.035001, 202 | -0.020298, 203 | 0.000946, 204 | 0.02797, 205 | 0.059568 206 | ], 207 | [ 208 | 0.0225, 209 | 0.024537, 210 | 0.013951, 211 | -0.0025, 212 | -0.019259, 213 | -0.031975, 214 | -0.0375, 215 | -0.033889, 216 | -0.020401, 217 | 0.0025, 218 | 0.033148, 219 | 0.068673, 220 | 0.105 221 | ], 222 | [ 223 | 0.01287, 224 | 0.026474, 225 | 0.020047, 226 | 0.003735, 227 | -0.014228, 228 | -0.027512, 229 | -0.031698, 230 | -0.024274, 231 | -0.004638, 232 | 0.025905, 233 | 0.064142, 234 | 0.104949, 235 | 0.141296 236 | ], 237 | [ 238 | 0.001636, 239 | 0.018315, 240 | 0.013731, 241 | -0.000988, 242 | -0.016925, 243 | -0.027374, 244 | -0.02784, 245 | -0.016038, 246 | 0.008104, 247 | 0.042449, 248 | 0.082648, 249 | 0.122142, 250 | 0.15216 251 | ], 252 | [ 253 | -0.0075, 254 | -0.004043, 255 | -0.011512, 256 | -0.0225, 257 | -0.031265, 258 | -0.033735, 259 | -0.0275, 260 | -0.011821, 261 | 0.012377, 262 | 0.0425, 263 | 0.07429, 264 | 0.101821, 265 | 0.1175 266 | ] 267 | ], 268 | "profile_name": "default", 269 | "mesh_min": [ 270 | 10.0, 271 | 10.0 272 | ], 273 | "probed_matrix": [ 274 | [ 275 | -0.07, 276 | -0.0475, 277 | -0.055, 278 | 0.0025, 279 | 0.07 280 | ], 281 | [ 282 | -0.0325, 283 | -0.06, 284 | -0.06, 285 | -0.015, 286 | 0.0 287 | ], 288 | [ 289 | 0.02, 290 | -0.0425, 291 | -0.055, 292 | -0.045, 293 | -0.0125 294 | ], 295 | [ 296 | 0.0225, 297 | -0.0025, 298 | -0.0375, 299 | 0.0025, 300 | 0.105 301 | ], 302 | [ 303 | -0.0075, 304 | -0.0225, 305 | -0.0275, 306 | 0.0425, 307 | 0.1175 308 | ] 309 | ] 310 | }, 311 | "gcode": { 312 | 313 | }, 314 | "temperature_fan": { 315 | 316 | }, 317 | "print_stats": { 318 | "print_duration": 0.0, 319 | "total_duration": 0.0, 320 | "filament_used": 0.0, 321 | "filename": "", 322 | "state": "standby", 323 | "message": "" 324 | }, 325 | "heater_bed": { 326 | "temperature": 23.298736858836772, 327 | "power": 0.0, 328 | "target": 0.0 329 | }, 330 | "idle_timeout": { 331 | "state": "Idle", 332 | "printing_time": 0.0 333 | }, 334 | "toolhead": { 335 | "square_corner_velocity": 5.0, 336 | "max_accel": 3000.0, 337 | "homed_axes": "", 338 | "estimated_print_time": 48972.450532194445, 339 | "max_velocity": 300.0, 340 | "print_time": 0.001, 341 | "max_accel_to_decel": 1500.0, 342 | "axis_minimum": [ 343 | 0.0, 344 | 0.0, 345 | -5.0, 346 | 0.0 347 | ], 348 | "axis_maximum": [ 349 | 249.0, 350 | 235.0, 351 | 235.0, 352 | 0.0 353 | ], 354 | "position": [ 355 | 0.0, 356 | 0.0, 357 | 0.0, 358 | 0.0 359 | ], 360 | "extruder": "extruder" 361 | }, 362 | "gcode_macro": { 363 | 364 | }, 365 | "display_status": { 366 | "progress": 0.0, 367 | "message": null 368 | }, 369 | "fan": { 370 | "speed": 0.0 371 | }, 372 | "gcode_move": { 373 | "homing_origin": [ 374 | 0.0, 375 | 0.0, 376 | 0.0, 377 | 0.0 378 | ], 379 | "speed_factor": 1.0, 380 | "gcode_position": [ 381 | 0.0, 382 | 0.0, 383 | 0.0, 384 | 0.0 385 | ], 386 | "absolute_extrude": true, 387 | "absolute_coordinates": true, 388 | "position": [ 389 | 0.0, 390 | 0.0, 391 | 0.0, 392 | 0.0 393 | ], 394 | "speed": 1500.0, 395 | "extrude_factor": 1.0 396 | }, 397 | "temperature_sensor": { 398 | 399 | }, 400 | "configfile": { 401 | "config": { 402 | "tmc2209 extruder": { 403 | "uart_pin": "PC11", 404 | "hold_current": "0.500", 405 | "stealthchop_threshold": "4", 406 | "run_current": "0.650", 407 | "microsteps": "16" 408 | }, 409 | "board_pins": { 410 | "aliases": "\nEXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=,\nEXP1_2=PB6, EXP1_4=, EXP1_6=PB9, EXP1_8=PB7, EXP1_10=<5V>" 411 | }, 412 | "pause_resume": { 413 | 414 | }, 415 | "safe_z_home": { 416 | "z_hop_speed": "10.0", 417 | "speed": "80.0", 418 | "home_xy_position": "117.5,117.5", 419 | "z_hop": "10.0" 420 | }, 421 | "output_pin beeper": { 422 | "pin": "EXP1_1" 423 | }, 424 | "mcu": { 425 | "serial": "/dev/serial/by-id/usb-Klipper_stm32f103xe_30FFD8054242323413671657-if00" 426 | }, 427 | "bed_mesh default": { 428 | "tension": "0.2", 429 | "min_x": "10.0", 430 | "min_y": "10.0", 431 | "y_count": "5", 432 | "mesh_y_pps": "2", 433 | "x_count": "5", 434 | "version": "1", 435 | "algo": "lagrange", 436 | "mesh_x_pps": "2", 437 | "max_y": "225.0", 438 | "max_x": "205.0", 439 | "points": "\n-0.070000, -0.047500, -0.055000, 0.002500, 0.070000\n-0.032500, -0.060000, -0.060000, -0.015000, 0.000000\n0.020000, -0.042500, -0.055000, -0.045000, -0.012500\n0.022500, -0.002500, -0.037500, 0.002500, 0.105000\n-0.007500, -0.022500, -0.027500, 0.042500, 0.117500" 440 | }, 441 | "virtual_sdcard": { 442 | "path": "~/sdcard" 443 | }, 444 | "stepper_z": { 445 | "position_max": "235", 446 | "endstop_pin": "probe:z_virtual_endstop", 447 | "step_pin": "PB0", 448 | "position_min": "-5", 449 | "dir_pin": "PC5", 450 | "step_distance": ".0025", 451 | "enable_pin": "!PB1" 452 | }, 453 | "stepper_y": { 454 | "position_endstop": "0", 455 | "position_max": "235", 456 | "endstop_pin": "^PC1", 457 | "step_pin": "PB10", 458 | "homing_speed": "50", 459 | "dir_pin": "!PB2", 460 | "step_distance": ".0125", 461 | "enable_pin": "!PB11" 462 | }, 463 | "stepper_x": { 464 | "position_endstop": "0", 465 | "position_max": "249", 466 | "endstop_pin": "^PC0", 467 | "step_pin": "PB13", 468 | "homing_speed": "50", 469 | "dir_pin": "!PB12", 470 | "step_distance": ".0125", 471 | "enable_pin": "!PB14" 472 | }, 473 | "static_digital_output usb_pullup_enable": { 474 | "pins": "!PC13" 475 | }, 476 | "heater_bed": { 477 | "control": "pid", 478 | "pid_kp": "64.890", 479 | "sensor_pin": "PC3", 480 | "heater_pin": "PC9", 481 | "sensor_type": "NTC 100K beta 3950", 482 | "pid_kd": "549.943", 483 | "pid_ki": "1.914", 484 | "min_temp": "0", 485 | "max_temp": "130" 486 | }, 487 | "printer": { 488 | "max_velocity": "300", 489 | "max_z_velocity": "5", 490 | "kinematics": "cartesian", 491 | "max_accel": "3000", 492 | "max_z_accel": "100" 493 | }, 494 | "gcode_macro RESUME": { 495 | "rename_existing": "BASE_RESUME", 496 | "default_parameter_e": "1", 497 | "gcode": "\nG91\nG1 E{E} F2100\nG90\nRESTORE_GCODE_STATE NAME=PAUSE_state MOVE=1\nBASE_RESUME" 498 | }, 499 | "gcode_macro PAUSE": { 500 | "rename_existing": "BASE_PAUSE", 501 | "default_parameter_e": "1", 502 | "default_parameter_y": "230", 503 | "default_parameter_x": "230", 504 | "default_parameter_z": "10", 505 | "gcode": "\nSAVE_GCODE_STATE NAME=PAUSE_state\nBASE_PAUSE\nG91\nG1 E-{E} F2100\nG1 Z{Z}\nG90\nG1 X{X} Y{Y} F6000" 506 | }, 507 | "fan": { 508 | "pin": "PA8" 509 | }, 510 | "tmc2209 stepper_z": { 511 | "uart_pin": "PC10", 512 | "hold_current": "0.500", 513 | "stealthchop_threshold": "5", 514 | "run_current": "0.580", 515 | "microsteps": "16" 516 | }, 517 | "tmc2209 stepper_x": { 518 | "uart_pin": "PB15", 519 | "hold_current": "0.500", 520 | "stealthchop_threshold": "250", 521 | "run_current": "0.580", 522 | "microsteps": "16" 523 | }, 524 | "tmc2209 stepper_y": { 525 | "uart_pin": "PC6", 526 | "hold_current": "0.500", 527 | "stealthchop_threshold": "250", 528 | "run_current": "0.580", 529 | "microsteps": "16" 530 | }, 531 | "bed_mesh": { 532 | "horizontal_move_z": "5", 533 | "mesh_pps": "2,2", 534 | "mesh_min": "10, 10", 535 | "mesh_max": "205, 225", 536 | "fade_start": "1.0", 537 | "probe_count": "5,5", 538 | "speed": "100", 539 | "relative_reference_index": "5" 540 | }, 541 | "display_status": { 542 | 543 | }, 544 | "display": { 545 | "click_pin": "^!EXP1_2", 546 | "cs_pin": "EXP1_7", 547 | "encoder_pins": "^EXP1_5, ^EXP1_3", 548 | "lcd_type": "st7920", 549 | "sid_pin": "EXP1_8", 550 | "sclk_pin": "EXP1_6" 551 | }, 552 | "gcode_macro CANCEL_PRINT": { 553 | "rename_existing": "BASE_CANCEL_PRINT", 554 | "gcode": "\nTURN_OFF_HEATERS\nCLEAR_PAUSE\nSDCARD_RESET_FILE\nBASE_CANCEL_PRINT" 555 | }, 556 | "bltouch": { 557 | "sensor_pin": "^PC2", 558 | "control_pin": "PA1", 559 | "pin_move_time": "0.4", 560 | "y_offset": "-8", 561 | "x_offset": "-41", 562 | "z_offset": "2.01" 563 | }, 564 | "gcode_macro G29": { 565 | "gcode": "\nBED_MESH_CLEAR\nBED_MESH_CALIBRATE\nBED_MESH_OUTPUT" 566 | }, 567 | "extruder": { 568 | "control": "pid", 569 | "pid_kp": "22.668", 570 | "sensor_type": "EPCOS 100K B57560G104F", 571 | "sensor_pin": "PA0", 572 | "nozzle_diameter": "0.400", 573 | "heater_pin": "PC8", 574 | "pressure_advance": "0.48", 575 | "max_extrude_only_distance": "120.0", 576 | "step_pin": "PB3", 577 | "pid_kd": "112.207", 578 | "min_temp": "0", 579 | "pid_ki": "1.145", 580 | "filament_diameter": "1.750", 581 | "dir_pin": "!PB4", 582 | "max_temp": "250", 583 | "step_distance": "0.002398", 584 | "enable_pin": "!PD2" 585 | } 586 | }, 587 | "save_config_pending": false 588 | }, 589 | "extruder": { 590 | "pressure_advance": 0.48, 591 | "target": 40.0, 592 | "smooth_time": 0.04, 593 | "power": 0.010654141978730249, 594 | "temperature": 40.033073117865676 595 | } 596 | }, 597 | "eventtime": 48969.272864392 598 | }, 599 | "id": "printer.objects.status" 600 | } 601 | ] -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Created with @iobroker/create-adapter v1.31.0 5 | */ 6 | 7 | // The adapter-core module gives you access to the core ioBroker functions 8 | // you need to create an adapter 9 | const utils = require('@iobroker/adapter-core'); 10 | const { default: axios } = require('axios'); // Lib to handle http requests 11 | const stateAttr = require('./lib/stateAttr.js'); // Load attribute library 12 | const WebSocket = require('ws'); 13 | let ws = null; //Global variable reserved for socket connection 14 | let reconnectTimer = null; // Polling timer 15 | let connectionState = null; 16 | const stateExpire = {}; 17 | const warnMessages = {}; // Timers to reset online state of device 18 | const disableSentry = false; // Ensure to set to true during development ! 19 | const https = require('node:https'); 20 | 21 | class KlipperMoonraker extends utils.Adapter { 22 | /** 23 | * @param {Partial} [options] 24 | */ 25 | constructor(options) { 26 | super({ 27 | ...options, 28 | name: 'klipper-moonraker', 29 | }); 30 | 31 | this.on('ready', this.onReady.bind(this)); 32 | this.on('stateChange', this.onStateChange.bind(this)); 33 | this.on('unload', this.onUnload.bind(this)); 34 | 35 | /** Refresh token after X ms */ 36 | this.REFRESH_TOKEN_MS = 50 * 60 * 1_000; 37 | /** Retry if login failed after X ms */ 38 | this.RETRY_LOGIN_MS = 90_000; 39 | /** The one shot token for websocket auth */ 40 | this.oneShotToken = ''; 41 | /** The current token used for authentication */ 42 | this.token = ''; 43 | /** The current refresh token */ 44 | this.refreshToken = ''; 45 | /** Array to store state objects to avoid unneeded object changes */ 46 | this.createdStatesDetails = {}; 47 | /** Store all available methods to handle data calls */ 48 | this.availableMethods = {}; 49 | /** List of config definitions for subscription of events */ 50 | this.subscribeMethods = {}; 51 | /** Timeout method if no pong received in time */ 52 | this.pingTimeout = null; 53 | /** Check for ping every X ms */ 54 | this.PING_INTERVAL = 30_000; 55 | this.axios = axios.create(); 56 | } 57 | 58 | /** 59 | * Get the one shot token for authenticating websocket connection 60 | * 61 | * @returns {Promise} 62 | */ 63 | async getOneShotToken() { 64 | try { 65 | const res = await this.axios.get(`${this.getApiBaseUrl()}/access/oneshot_token`, { 66 | headers: { 67 | Authorization: `Bearer ${this.token}`, 68 | }, 69 | }); 70 | 71 | this.oneShotToken = res.data.result; 72 | } catch (e) { 73 | throw new Error(`Could not retrieve one shot token: ${e.message}`); 74 | } 75 | } 76 | 77 | /** 78 | * Perform login with credentials from config 79 | * 80 | * @returns {Promise} 81 | */ 82 | async login() { 83 | this.log.info('Login into API'); 84 | try { 85 | const res = await this.axios.post(`${this.getApiBaseUrl()}/access/login`, { 86 | username: this.config.user, 87 | password: this.config.password, 88 | source: 'moonraker', 89 | }); 90 | 91 | this.log.info(`Successfully logged in as ${res.data.result.username}`); 92 | this.token = res.data.result.token; 93 | this.refreshToken = res.data.result.refresh_token; 94 | } catch (e) { 95 | throw new Error(`Could not login: ${e.message}`); 96 | } 97 | } 98 | 99 | /** 100 | * Configure axios according to the instance config 101 | */ 102 | configureAxios() { 103 | if (!this.config.useSsl) { 104 | return; 105 | } 106 | 107 | this.axios = axios.create({ 108 | httpsAgent: new https.Agent({ 109 | rejectUnauthorized: false, 110 | }), 111 | }); 112 | } 113 | 114 | /** 115 | * Is called when databases are connected and adapter received configuration. 116 | */ 117 | async onReady() { 118 | this.configureAxios(); 119 | return this.init(); 120 | } 121 | 122 | /** 123 | * Refresh the access token 124 | * 125 | * @returns {Promise} 126 | */ 127 | async refreshAccessToken() { 128 | try { 129 | const res = await this.axios.post(`${this.getApiBaseUrl()}/access/refresh_jwt`, { 130 | refresh_token: this.refreshToken, 131 | }); 132 | 133 | this.token = res.data.result.token; 134 | } catch (e) { 135 | throw new Error(`Could not refresh access token: ${e.message}`); 136 | } 137 | } 138 | 139 | /** 140 | * Start the authorization procedure 141 | * Login, getting one shot token and refreshing regularly 142 | * 143 | * @returns {Promise} 144 | */ 145 | async startAuthorization() { 146 | await this.login(); 147 | await this.getOneShotToken(); 148 | 149 | this.setInterval(() => { 150 | this.log.info('Refresh access token'); 151 | try { 152 | this.refreshAccessToken(); 153 | this.log.info('Access token successfully refreshed'); 154 | } catch (e) { 155 | this.log.error(`Could not refresh access token: ${e.message}`); 156 | // we need to login from scratch, restart instance to achieve this 157 | this.log.error('Restarting instance'); 158 | this.restart(); 159 | } 160 | }, this.REFRESH_TOKEN_MS); 161 | } 162 | 163 | /** 164 | * Executes the initial adapter logic 165 | * 166 | * @returns {Promise} 167 | */ 168 | async init() { 169 | if (this.config.auth) { 170 | try { 171 | await this.startAuthorization(); 172 | } catch (e) { 173 | this.log.error(e.message); 174 | 175 | this.log.info(`Will try again in ${this.RETRY_LOGIN_MS / 1_000} seconds`); 176 | this.setTimeout(() => this.init(), this.RETRY_LOGIN_MS); 177 | return; 178 | } 179 | } 180 | 181 | // Reset the connection indicator during startup 182 | this.setState('info.connection', false, true); 183 | return this.handleWebSocket(); 184 | } 185 | 186 | /** 187 | * Handle all websocket related data interaction 188 | */ 189 | handleWebSocket() { 190 | let wsUrl = `${this.config.useSsl ? 'wss' : 'ws'}://${this.config.klipperIP}:${this.config.klipperPort}/websocket`; 191 | if (this.config.auth) { 192 | wsUrl += `?token=${this.oneShotToken}`; 193 | } 194 | 195 | /** Check that we receive a ping request in time */ 196 | const heartbeat = () => { 197 | this.clearTimeout(this.pingTimeout); 198 | 199 | this.log.debug('Heartbeat received'); 200 | 201 | this.pingTimeout = setTimeout(() => { 202 | this.log.error('No heartbeat received in time'); 203 | ws.terminate(); 204 | }, this.PING_INTERVAL); 205 | }; 206 | 207 | // Open socket connection 208 | ws = new WebSocket(wsUrl, { 209 | rejectUnauthorized: !this.config.useSsl, 210 | }); 211 | 212 | ws.on('ping', heartbeat); 213 | 214 | // Connection successfully open, handle routine to initiates all objects and states 215 | ws.on('open', () => { 216 | this.log.info(`Successfully connected to ${this.config.klipperIP}:${this.config.klipperPort}`); 217 | this.setState('info.connection', true, true); 218 | connectionState = true; 219 | 220 | heartbeat(); 221 | 222 | // Get printer basic information 223 | ws.send( 224 | JSON.stringify({ 225 | jsonrpc: '2.0', 226 | method: 'printer.info', 227 | id: 'printer.info', 228 | }), 229 | ); 230 | 231 | // Get active spool 232 | ws.send( 233 | JSON.stringify({ 234 | jsonrpc: '2.0', 235 | method: 'server.spoolman.get_spool_id', 236 | id: 'printer.spoolID', 237 | }), 238 | ); 239 | 240 | // Call update for all methods 241 | this.getAvailableMethods(); 242 | }); 243 | 244 | // Handle messages received from socket connection 245 | ws.on('message', async data => { 246 | const errorOutput = data => { 247 | this.log.debug(`Unexpected message received ${JSON.stringify(data)}`); 248 | }; 249 | 250 | const rpc_data = JSON.parse(data); 251 | 252 | // Handle error message and return function 253 | if (rpc_data.error) { 254 | this.log.error(`Received error message for "${rpc_data.id}" over websocket: ${rpc_data.error.message}`); 255 | return; 256 | } 257 | 258 | //Handle state_message Data 259 | if (rpc_data.id) { 260 | if (rpc_data.id == `printer.info`) { 261 | // await this.readData(rpc_data.result, `_info`); 262 | this.TraverseJson(rpc_data.result, null, false, false); 263 | 264 | // Create additional states not included in JSON-API of klipper-mooonraker but available as SET command 265 | await this.create_state('control.runGcode', 'Run G-code', ''); 266 | await this.create_state('control.emergencyStop', 'Emergency Stop', false); 267 | await this.create_state('control.printCancel', 'Cancel current printing', false); 268 | await this.create_state('control.printPause', 'Pause current printing', false); 269 | await this.create_state('control.printResume', 'Resume current printing', false); 270 | await this.create_state('control.restartFirmware', 'Restart Firmware', false); 271 | await this.create_state('control.restartHost', 'Restart Host', false); 272 | await this.create_state('control.restartServer', 'Restart Server', false); 273 | await this.create_state('control.systemReboot', 'Reboot the system', false); 274 | await this.create_state('control.systemShutdown', 'Shutdown the system', false); 275 | } else if (rpc_data.id == `printer.objects.status`) { 276 | this.TraverseJson(rpc_data.result.status, null, false, false); 277 | } else if (rpc_data.id == `printer.objects.list`) { 278 | // Ensure array is empty 279 | this.availableMethods.objects = {}; 280 | 281 | // Create array with possible object/states subscriptions 282 | for (const method in rpc_data.result.objects) { 283 | this.availableMethods.objects[rpc_data.result.objects[method]] = null; 284 | } 285 | 286 | this.log.debug(`All available methods : ${JSON.stringify(this.availableMethods.objects)}`); 287 | 288 | // Request state data for all available methods 289 | ws.send( 290 | JSON.stringify({ 291 | jsonrpc: '2.0', 292 | method: 'printer.objects.query', 293 | params: { 294 | objects: this.availableMethods.objects, 295 | }, 296 | id: 'printer.objects.status', 297 | }), 298 | ); 299 | 300 | // Request status updates of all methods 301 | this.subscribeMethods = this.availableMethods; 302 | 303 | // Subscribe to all states including proper configuration 304 | ws.send( 305 | JSON.stringify({ 306 | jsonrpc: '2.0', 307 | method: 'printer.objects.subscribe', 308 | params: { 309 | objects: this.subscribeMethods.objects, 310 | }, 311 | id: 'printer.objects.subscribe', 312 | }), 313 | ); 314 | } else if (rpc_data.id === `printer.spoolID`) { 315 | this.log.info(`PrinterSpool ID message: ${JSON.stringify(rpc_data)}`); 316 | // await this.create_state('spoolID', 'Shutdown the system', false); 317 | } else { 318 | errorOutput(rpc_data); 319 | } 320 | } else if (rpc_data.method && rpc_data.method == 'notify_status_update' && rpc_data.params) { 321 | this.log.debug(`Status update data received ${JSON.stringify(rpc_data)}`); 322 | for (const methods in rpc_data.params) { 323 | this.log.debug(`Status update data received ${JSON.stringify(rpc_data)}`); 324 | this.TraverseJson(rpc_data.params[methods], null, false, false); 325 | } 326 | } else { 327 | errorOutput(rpc_data); 328 | } 329 | }); 330 | 331 | // Handle closure of socket connection, try to connect again in 10seconds (if adapter enabled) 332 | ws.on('close', () => { 333 | this.clearTimeout(this.pingTimeout); 334 | 335 | this.log.info(`Connection closed`); 336 | this.setState('info.connection', false, true); 337 | connectionState = false; 338 | 339 | // Try to reconnect if connections is closed after 10 seconds 340 | if (reconnectTimer) { 341 | this.clearTimeout(reconnectTimer); 342 | reconnectTimer = null; 343 | } 344 | reconnectTimer = this.setTimeout(async () => { 345 | this.log.info(`Trying to reconnect`); 346 | if (this.config.auth) { 347 | try { 348 | await this.getOneShotToken(); 349 | } catch (e) { 350 | this.log.error(e.message); 351 | } 352 | } 353 | this.handleWebSocket(); 354 | }, 10_000); 355 | }); 356 | 357 | // Handle errors on socket connection 358 | ws.on('error', error => { 359 | this.log.error(`Connection error: ${error}`); 360 | this.setState('info.connection', false, true); 361 | connectionState = false; 362 | }); 363 | } 364 | 365 | /** 366 | * Query all available method endpoints, socket will reply with data which initialises all available states and objects 367 | */ 368 | getAvailableMethods() { 369 | if (connectionState) { 370 | // Printer Object list 371 | ws.send( 372 | JSON.stringify({ 373 | jsonrpc: '2.0', 374 | method: 'printer.objects.list', 375 | id: 'printer.objects.list', 376 | }), 377 | ); 378 | } else { 379 | this.log.error(`No active connection, cannot run 'getAvailableMethods'`); 380 | } 381 | } 382 | 383 | /** 384 | * Get the API base url based on the configuration 385 | * 386 | * @returns {string} 387 | */ 388 | getApiBaseUrl() { 389 | return `${this.config.useSsl ? 'https' : 'http'}://${this.config.klipperIP}:${this.config.klipperPort}`; 390 | } 391 | 392 | async postAPI(endpoint) { 393 | this.log.debug(`Post API called for: ${endpoint}`); 394 | const headers = {}; 395 | 396 | if (this.config.auth) { 397 | headers.Authorization = `Bearer ${this.token}`; 398 | } 399 | 400 | try { 401 | if (!endpoint) { 402 | return; 403 | } 404 | endpoint = `${this.getApiBaseUrl()}${endpoint}`; 405 | const result = this.axios 406 | .post(endpoint, null, { headers }) 407 | .then(response => { 408 | this.log.debug(`Sending command to Klippy API: ${endpoint}`); 409 | return response.data; 410 | }) 411 | .catch(error => { 412 | this.log.debug(`Sending command to Klippy API: ${endpoint} failed with error ${error}`); 413 | return error; 414 | }); 415 | return result; 416 | } catch (e) { 417 | this.log.error(`Issue in postAPI: ${e.message}`); 418 | } 419 | } 420 | 421 | /** 422 | * Traverses the json-object and provides all information for creating/updating states 423 | * 424 | * @param {object} o Json-object to be added as states 425 | * @param {string | null} parent Defines the parent object in the state tree 426 | * @param {boolean} replaceName Steers if name from child should be used as name for structure element (channel) 427 | * @param {boolean} replaceID Steers if ID from child should be used as ID for structure element (channel) 428 | */ 429 | async TraverseJson(o, parent = null, replaceName = false, replaceID = false) { 430 | let id = null; 431 | let value = null; 432 | let name = null; 433 | 434 | try { 435 | for (const i in o) { 436 | name = i; 437 | if (!!o[i] && typeof o[i] == 'object' && o[i] == '[object Object]') { 438 | if (parent == null) { 439 | id = i; 440 | if (replaceName) { 441 | if (o[i].name) { 442 | name = o[i].name; 443 | } 444 | } 445 | if (replaceID) { 446 | if (o[i].id) { 447 | id = o[i].id; 448 | } 449 | } 450 | } else { 451 | id = `${parent}.${i}`; 452 | if (replaceName) { 453 | if (o[i].name) { 454 | name = o[i].name; 455 | } 456 | } 457 | if (replaceID) { 458 | if (o[i].id) { 459 | id = `${parent}.${o[i].id}`; 460 | } 461 | } 462 | } 463 | // Avoid channel creation for empty arrays/objects 464 | if (Object.keys(o[i]).length !== 0) { 465 | await this.setObjectAsync(id, { 466 | type: 'channel', 467 | common: { 468 | name: name, 469 | }, 470 | native: {}, 471 | }); 472 | this.TraverseJson(o[i], id, replaceName, replaceID); 473 | } else { 474 | this.log.debug(`State ${id} received with empty array, ignore channel creation`); 475 | } 476 | } else { 477 | value = o[i]; 478 | if (parent == null) { 479 | id = i; 480 | } else { 481 | id = `${parent}.${i}`; 482 | } 483 | if (typeof o[i] == 'object') { 484 | value = JSON.stringify(value); 485 | } 486 | this.log.debug(`create id ${id} with value ${value} and name ${name}`); 487 | 488 | this.create_state(id, name, value); 489 | } 490 | } 491 | } catch (error) { 492 | this.log.error(`Error in function TraverseJson: ${error}`); 493 | } 494 | } 495 | 496 | /** 497 | * Is called when adapter shuts down - callback has to be called under any circumstances! 498 | * 499 | * @param callback 500 | */ 501 | onUnload(callback) { 502 | try { 503 | // Cancel reconnect timer if running 504 | if (reconnectTimer) { 505 | this.clearTimeout(reconnectTimer); 506 | reconnectTimer = null; 507 | } 508 | // Close socket connection 509 | if (ws) { 510 | ws.close(); 511 | } 512 | callback(); 513 | } catch (e) { 514 | this.log.error(e); 515 | callback(); 516 | } 517 | } 518 | 519 | /** 520 | * Is called if a subscribed state changes 521 | * 522 | * @param {string} id 523 | * @param {ioBroker.State | null | undefined} state 524 | */ 525 | async onStateChange(id, state) { 526 | //Only execute when ACK = false 527 | if (!state || state.ack) { 528 | return; 529 | } 530 | 531 | // Split state name in segments to be used later 532 | const deviceId = id.split('.'); 533 | // If state us related to control commands, customize API post call 534 | this.log.debug(`Control command received ${deviceId[3]}`); 535 | let apiResult = null; 536 | switch (deviceId[3]) { 537 | case 'emergencyStop': 538 | apiResult = await this.postAPI(`/printer/emergency_stop`); 539 | break; 540 | case 'printCancel': 541 | apiResult = await this.postAPI(`/printer/print/cancel`); 542 | break; 543 | case 'printPause': 544 | apiResult = await this.postAPI(`/printer/print/pause`); 545 | break; 546 | case 'printResume': 547 | apiResult = await this.postAPI(`/printer/print/resume`); 548 | break; 549 | case 'restartFirmware': 550 | apiResult = await this.postAPI(`/printer/firmware_restart`); 551 | break; 552 | case 'restartHost': 553 | apiResult = await this.postAPI(`/printer/restart`); 554 | break; 555 | case 'restartServer': 556 | apiResult = await this.postAPI(`/server/restart`); 557 | break; 558 | case 'systemReboot': 559 | apiResult = await this.postAPI(`/machine/reboot`); 560 | break; 561 | case 'systemShutdown': 562 | apiResult = await this.postAPI(`/machine/shutdown`); 563 | break; 564 | case 'runGcode': 565 | apiResult = await this.postAPI(`/printer/gcode/script?script=${state.val}`); 566 | break; 567 | } 568 | if (apiResult) { 569 | if (apiResult.result === 'ok') { 570 | this.log.info(`Command "${deviceId[3]}" send successfully`); 571 | } else { 572 | this.log.error( 573 | `Sending command "${deviceId[3]}" failed, error : ${JSON.stringify(apiResult.message)}`, 574 | ); 575 | } 576 | } 577 | } 578 | 579 | /** 580 | * @param stateName {string} ID of the state 581 | * @param name {string} Name of state (also used for stattAttrlib!) 582 | * @param value {boolean | string | null} Value of the state 583 | */ 584 | async create_state(stateName, name, value) { 585 | this.log.debug(`Create_state called for : ${stateName} with value : ${value}`); 586 | 587 | /** 588 | * Value rounding 1 digits 589 | * 590 | * @param {number} [value] - Number to round with . separator 591 | * @param {object} [adapter] - intance "this" object 592 | */ 593 | function rondOneDigit(value, adapter) { 594 | try { 595 | let rounded = Number(value); 596 | rounded = Math.round(rounded * 100) / 100; 597 | adapter.log.debug(`roundCosts with ${value} rounded ${rounded}`); 598 | if (!rounded) { 599 | return value; 600 | } 601 | return rounded; 602 | } catch (error) { 603 | adapter.log.error(`[roundCosts ${value}`); 604 | adapter.sendSentry(error); 605 | } 606 | } 607 | /** 608 | * Value rounding 2 digits 609 | * 610 | * @param {number} [value] - Number to round with , separator 611 | * @param {object} [adapter] - instance "this" object 612 | */ 613 | function roundTwoDigits(value, adapter) { 614 | let rounded; 615 | try { 616 | rounded = Number(value); 617 | rounded = Math.round(rounded * 1000) / 1000; 618 | adapter.log.debug(`roundDigits with ${value} rounded ${rounded}`); 619 | if (!rounded) { 620 | return value; 621 | } 622 | return rounded; 623 | } catch (error) { 624 | adapter.log.error(`[roundDigits ${value}`); 625 | adapter.sendSentry(error); 626 | rounded = value; 627 | return rounded; 628 | } 629 | } 630 | /** 631 | * Value rounding 3 digits 632 | * 633 | * @param {number} [value] - Number to round with , separator 634 | * @param {object} [adapter] - intance "this" object 635 | */ 636 | function roundThreeDigits(value, adapter) { 637 | let rounded; 638 | try { 639 | rounded = Number(value); 640 | rounded = Math.round(rounded * 1000) / 1000; 641 | adapter.log.debug(`roundDigits with ${value} rounded ${rounded}`); 642 | if (!rounded) { 643 | return value; 644 | } 645 | return rounded; 646 | } catch (error) { 647 | adapter.log.error(`[roundDigits ${value}`); 648 | adapter.sendSentry(error); 649 | rounded = value; 650 | return rounded; 651 | } 652 | } 653 | 654 | try { 655 | // Try to get details from state lib, if not use defaults. throw warning is states is not known in attribute list 656 | const common = {}; 657 | if (!stateAttr[name]) { 658 | const warnMessage = `State attribute definition missing for "${name}" (received value ${value} - ${typeof value})`; 659 | if (warnMessages[name] !== warnMessage) { 660 | warnMessages[name] = warnMessage; 661 | // Send information to Sentry 662 | this.sendSentry(warnMessage); 663 | } 664 | } 665 | let createStateName = stateName; 666 | 667 | // Todo: Disable stateAttr based channel creation 668 | // const channel = stateAttr[name] !== undefined ? stateAttr[name].root || '' : ''; 669 | const channel = ''; 670 | if (channel !== '') { 671 | await this.setObjectNotExistsAsync(channel, { 672 | type: 'channel', 673 | common: { 674 | name: stateAttr[name] !== undefined ? stateAttr[name].rootName || '' : '', 675 | }, 676 | native: {}, 677 | }); 678 | createStateName = `${channel}.${stateName}`; 679 | } 680 | common.name = stateAttr[name] !== undefined ? stateAttr[name].name || name : name; 681 | common.type = stateAttr[name] !== undefined ? stateAttr[name].type || typeof value : typeof value; 682 | common.role = stateAttr[name] !== undefined ? stateAttr[name].role || 'state' : 'state'; 683 | common.read = true; 684 | common.unit = stateAttr[name] !== undefined ? stateAttr[name].unit || '' : ''; 685 | common.write = stateAttr[name] !== undefined ? stateAttr[name].write || false : false; 686 | 687 | if ( 688 | !this.createdStatesDetails[stateName] || 689 | (this.createdStatesDetails[stateName] && 690 | (common.name !== this.createdStatesDetails[stateName].name || 691 | common.name !== this.createdStatesDetails[stateName].name || 692 | common.type !== this.createdStatesDetails[stateName].type || 693 | common.role !== this.createdStatesDetails[stateName].role || 694 | common.read !== this.createdStatesDetails[stateName].read || 695 | common.unit !== this.createdStatesDetails[stateName].unit || 696 | common.write !== this.createdStatesDetails[stateName].write)) 697 | ) { 698 | await this.extendObjectAsync(createStateName, { 699 | type: 'state', 700 | common, 701 | }); 702 | } 703 | 704 | // Store current object definition to memory 705 | this.createdStatesDetails[stateName] = common; 706 | 707 | // Check if value should be rounded, active switch 708 | const roundingOneDigit = stateAttr[name] !== undefined ? stateAttr[name].round_1 || false : false; 709 | const roundingTwoDigits = stateAttr[name] !== undefined ? stateAttr[name].round_2 || false : false; 710 | const roundingThreeDigits = stateAttr[name] !== undefined ? stateAttr[name].round_3 || false : false; 711 | 712 | // Set value to state including expiration time 713 | if (value !== null && value !== undefined) { 714 | // Check if value should be rounded, if yes execute 715 | if (typeof value == 'number' || typeof value == 'string') { 716 | if (roundingOneDigit) { 717 | value = rondOneDigit(value, this); 718 | } else if (roundingTwoDigits) { 719 | value = roundTwoDigits(value, this); 720 | } else if (roundingThreeDigits) { 721 | value = roundThreeDigits(value, this); 722 | } 723 | } 724 | await this.setStateChangedAsync(createStateName, { 725 | val: value, 726 | ack: true, 727 | }); 728 | } 729 | 730 | // Timer to set online state to FALSE when not updated during 2 time-sync intervals 731 | if (name === 'klippy connected') { 732 | // Clear running timer 733 | if (stateExpire[stateName]) { 734 | this.clearTimeout(stateExpire[createStateName]); 735 | stateExpire[stateName] = null; 736 | } 737 | 738 | // timer 739 | stateExpire[stateName] = this.setTimeout(async () => { 740 | // Set value to state including expiration time 741 | await this.setState(createStateName, { 742 | val: false, 743 | ack: true, 744 | }); 745 | this.log.debug(`Online state expired for ${stateName}`); 746 | }, this.config.apiRefreshInterval * 2_000); 747 | this.log.debug( 748 | `Expire time set for state : ${name} with time in seconds : ${this.config.apiRefreshInterval * 2}`, 749 | ); 750 | } 751 | 752 | // Subscribe on state changes if writable 753 | common.write && this.subscribeStates(createStateName); 754 | } catch (error) { 755 | this.log.error(`Create state error = ${error}`); 756 | } 757 | } 758 | 759 | /** 760 | * Send error's to sentry, only if sentry not disabled 761 | * 762 | * @param {string} msg ID of the state 763 | */ 764 | sendSentry(msg) { 765 | if (!disableSentry) { 766 | if (this.supportsFeature && this.supportsFeature('PLUGINS')) { 767 | const sentryInstance = this.getPluginInstance('sentry'); 768 | if (sentryInstance) { 769 | this.log.info(`[Error caught and sent to Sentry, thank you for collaborating!] error: ${msg}`); 770 | sentryInstance.getSentryObject().captureException(msg); 771 | } 772 | } 773 | } else { 774 | this.log.error(`Sentry disabled, error caught: ${msg}`); 775 | } 776 | } 777 | } 778 | 779 | if (require.main !== module) { 780 | // Export the constructor in compact mode 781 | /** 782 | * @param {Partial} [options] 783 | */ 784 | module.exports = options => new KlipperMoonraker(options); 785 | } else { 786 | // otherwise start the instance directly 787 | new KlipperMoonraker(); 788 | } 789 | -------------------------------------------------------------------------------- /lib/stateAttr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Object definitions can contain these elements to be called by stateSetCreate function, if not set default values are used 3 | 'Cancel current printing': { // id of state (name) submitted by stateSetCreate function 4 | root: '_Info', // {default: NotUsed} Upper channel root 5 | rootName: 'Device Info channel, // {default: NotUsed} Upper channel name 6 | name: 'Name of state', // {default: same as id} Name definition for object 7 | type: >typeof (value)<, // {default: typeof (value)} type of value automatically detected 8 | read: true, // {default: true} Name defition for object 9 | write: true, // {default: false} Name defition for object 10 | role: 'indicator.info', // {default: state} Role as defined by https://github.com/ioBroker/ioBroker/blob/master/doc/STATE_ROLES.md 11 | round_1: true, // {default: NotUsed} Executed rounding function to 1 digit 12 | round_2: true, // {default: NotUsed} Executed rounding function to 1 digit 13 | round_3: true, // {default: NotUsed} Executed rounding function to 1 digit 14 | }, 15 | */ 16 | 17 | const stateAttrb = { 18 | // State object 19 | 'Cancel current printing': { 20 | root: 'control', 21 | rootName: 'Control Klipper', 22 | name: 'Cancel current printing', 23 | type: 'boolean', 24 | read: true, 25 | write: true, 26 | role: 'button', 27 | }, 28 | 'Emergency Stop': { 29 | root: 'control', 30 | rootName: 'Control Klipper', 31 | name: 'Emergency Stop', 32 | type: 'boolean', 33 | read: true, 34 | write: true, 35 | role: 'button', 36 | }, 37 | total_duration: { 38 | root: 'printjob.time', 39 | rootName: 'Printjob Information - Time', 40 | name: 'Total Duration in seconds', 41 | read: true, 42 | write: false, 43 | role: 'value', 44 | unit: 'sec', 45 | }, 46 | progress: { 47 | root: 'printjob', 48 | rootName: 'Printjob Information', 49 | name: 'Progress in Percent', 50 | read: true, 51 | write: false, 52 | role: 'value', 53 | unit: '%', 54 | }, 55 | print_duration: { 56 | root: 'printjob.time', 57 | rootName: 'Printjob Information - Time', 58 | name: 'Time taken so far in seconds', 59 | read: true, 60 | write: false, 61 | role: 'value', 62 | unit: 'sec', 63 | }, 64 | filament_used: { 65 | root: 'printjob', 66 | rootName: 'Printjob Information', 67 | name: 'Filament used in mm', 68 | read: true, 69 | write: false, 70 | role: 'value', 71 | unit: 'mm', 72 | }, 73 | filename: { 74 | root: 'printjob', 75 | rootName: 'Printjob Information', 76 | name: 'Filename of the Print', 77 | read: true, 78 | write: false, 79 | role: 'value', 80 | }, 81 | file_position: { 82 | root: 'printjob', 83 | rootName: 'Printjob Information', 84 | name: 'File Position in kb', 85 | read: true, 86 | write: false, 87 | role: 'value', 88 | unit: 'kb', 89 | }, 90 | state: { 91 | root: 'printjob', 92 | rootName: 'Printjob Information', 93 | name: 'Status of Printer', 94 | read: true, 95 | write: false, 96 | role: 'string', 97 | }, 98 | state_message: { 99 | root: 'printjob', 100 | rootName: 'Printjob Information', 101 | name: 'Statustext of Printer', 102 | read: true, 103 | write: false, 104 | role: 'string', 105 | }, 106 | klipper_path: { 107 | root: 'host.path', 108 | rootName: 'Host Information - Path of Files', 109 | name: 'Klipper Path', 110 | read: true, 111 | write: false, 112 | role: 'string', 113 | }, 114 | config_file: { 115 | root: 'host.path', 116 | rootName: 'Host Information - Path of Files', 117 | name: 'Klipper Config Path', 118 | read: true, 119 | write: false, 120 | role: 'string', 121 | }, 122 | log_file: { 123 | root: 'host.path', 124 | rootName: 'Host Information - Path of Files', 125 | name: 'Klipper Log Path', 126 | read: true, 127 | write: false, 128 | role: 'string', 129 | }, 130 | python_path: { 131 | root: 'host.path', 132 | rootName: 'Host Information - Path of Files', 133 | name: 'Python Path', 134 | read: true, 135 | write: false, 136 | role: 'string', 137 | }, 138 | hostname: { 139 | root: 'host', 140 | rootName: 'Host Information', 141 | name: 'Hostname', 142 | read: true, 143 | write: false, 144 | role: 'string', 145 | }, 146 | eventtime: { 147 | root: 'host', 148 | rootName: 'Host Information', 149 | name: 'Uptime in seconds', 150 | read: true, 151 | write: false, 152 | role: 'value', 153 | unit: 'sec', 154 | }, 155 | software_version: { 156 | root: 'host', 157 | rootName: 'Host Information', 158 | name: 'Klipper Version', 159 | read: true, 160 | write: false, 161 | role: 'string', 162 | }, 163 | cpu_info: { 164 | root: 'host', 165 | rootName: 'Host Information', 166 | name: 'Information about CPU', 167 | read: true, 168 | write: false, 169 | role: 'string', 170 | }, 171 | klippy_connected: { 172 | root: 'server', 173 | rootName: 'Server Information', 174 | name: 'klippy connected', 175 | type: 'boolean', 176 | read: true, 177 | write: false, 178 | role: 'indicator.connected', 179 | }, 180 | klippy_state: { 181 | root: 'server', 182 | rootName: 'Server Information', 183 | name: 'klippy state', 184 | type: 'string', 185 | read: true, 186 | write: false, 187 | role: 'indicator.working', 188 | }, 189 | 'Pause current printing': { 190 | root: 'control', 191 | rootName: 'Control Klipper', 192 | name: 'Pause current printing', 193 | type: 'boolean', 194 | read: true, 195 | write: true, 196 | role: 'button', 197 | }, 198 | plugins: { 199 | root: 'server', 200 | rootName: 'Server Information', 201 | name: 'installed plugins', 202 | type: 'string', 203 | read: true, 204 | write: false, 205 | role: 'indicator.working', 206 | }, 207 | 'Reboot the system': { 208 | root: 'control', 209 | rootName: 'Control Klipper', 210 | name: 'Reboot the system', 211 | type: 'boolean', 212 | read: true, 213 | write: true, 214 | role: 'button', 215 | }, 216 | 'Restart Firmware': { 217 | root: 'control', 218 | rootName: 'Control Klipper', 219 | name: 'Restart Firmware', 220 | type: 'boolean', 221 | read: true, 222 | write: true, 223 | role: 'button', 224 | }, 225 | 'Restart Host': { 226 | root: 'control', 227 | rootName: 'Control Klipper', 228 | name: 'Restart Host', 229 | type: 'boolean', 230 | read: true, 231 | write: true, 232 | role: 'button', 233 | }, 234 | 'Restart Server': { 235 | root: 'control', 236 | rootName: 'Control Klipper', 237 | name: 'Restart Server', 238 | type: 'boolean', 239 | read: true, 240 | write: true, 241 | role: 'button', 242 | }, 243 | 'Resume current printing': { 244 | root: 'control', 245 | rootName: 'Control Klipper', 246 | name: 'Resume current printing', 247 | type: 'boolean', 248 | read: true, 249 | write: true, 250 | role: 'button', 251 | }, 252 | 'Run G-code': { 253 | root: 'control', 254 | rootName: 'Control Klipper', 255 | name: 'Run G-code, be careful!', 256 | type: 'string', 257 | read: true, 258 | write: true, 259 | role: 'text', 260 | }, 261 | 'Shutdown the system': { 262 | root: 'control', 263 | rootName: 'Control Klipper', 264 | name: 'Shutdown the system', 265 | type: 'boolean', 266 | read: true, 267 | write: true, 268 | role: 'button', 269 | }, 270 | mesh_matrix: { 271 | name: 'Bed Mesh Values', 272 | type: 'string', 273 | read: true, 274 | write: false, 275 | role: 'state', 276 | }, 277 | mesh_max: { 278 | name: 'Bed Mesh Max Position', 279 | type: 'string', 280 | read: true, 281 | write: false, 282 | role: 'state', 283 | }, 284 | mesh_min: { 285 | name: 'Bed Mesh Min Position', 286 | type: 'string', 287 | read: true, 288 | write: false, 289 | role: 'state', 290 | }, 291 | probed_matrix: { 292 | name: 'Bed Mesh Values which are from your Bed Probe', 293 | type: 'string', 294 | read: true, 295 | write: false, 296 | role: 'state', 297 | }, 298 | profile_name: { 299 | name: 'Profile Name', 300 | type: 'string', 301 | read: true, 302 | write: false, 303 | role: 'state', 304 | }, 305 | fade_start: { 306 | name: 'Fade Start', 307 | type: 'string', 308 | read: true, 309 | write: false, 310 | role: 'state', 311 | }, 312 | horizontal_move_z: { 313 | name: 'Horizontal Movement for Z at the Probe', 314 | type: 'string', 315 | read: true, 316 | write: false, 317 | role: 'state', 318 | }, 319 | save_config_pending: { 320 | name: 'Status about the Save Config pending', 321 | type: 'boolean', 322 | read: true, 323 | write: false, 324 | role: 'state', 325 | }, 326 | power: { 327 | name: 'Power', 328 | type: 'number', 329 | read: true, 330 | write: false, 331 | role: 'value', 332 | }, 333 | pressure_advance: { 334 | name: 'Your Pressure Advanced Value', 335 | type: 'number', 336 | read: true, 337 | write: false, 338 | role: 'value', 339 | }, 340 | smooth_time: { 341 | name: 'Your Smooth Time Value', 342 | type: 'number', 343 | read: true, 344 | write: false, 345 | role: 'value', 346 | }, 347 | target: { 348 | name: 'Target', 349 | type: 'number', 350 | read: true, 351 | write: false, 352 | role: 'value', 353 | }, 354 | speed: { 355 | name: 'Speed', 356 | type: 'number', 357 | read: true, 358 | write: false, 359 | role: 'state', 360 | }, 361 | absolute_coordinates: { 362 | name: 'Absolute Coordinates', 363 | type: 'boolean', 364 | read: true, 365 | write: false, 366 | role: 'switch', 367 | }, 368 | absolute_extrude: { 369 | name: 'Absolute Extrude', 370 | type: 'boolean', 371 | read: true, 372 | write: false, 373 | role: 'switch', 374 | }, 375 | extrude_factor: { 376 | name: 'Your Extrude Factor', 377 | type: 'string', 378 | read: true, 379 | write: false, 380 | role: 'value', 381 | }, 382 | gcode_position: { 383 | name: 'Actual G Code Position', 384 | type: 'string', 385 | read: true, 386 | write: false, 387 | role: 'state', 388 | }, 389 | homing_origin: { 390 | name: 'Homing Origin Position', 391 | type: 'string', 392 | read: true, 393 | write: false, 394 | role: 'state', 395 | }, 396 | position: { 397 | name: 'Actual Position', 398 | type: 'string', 399 | read: true, 400 | write: false, 401 | role: 'state', 402 | }, 403 | speed_factor: { 404 | name: 'Speed Factor', 405 | type: 'number', 406 | read: true, 407 | write: false, 408 | role: 'state', 409 | }, 410 | temperature: { 411 | name: 'Actual Temperature', 412 | type: 'number', 413 | read: true, 414 | write: false, 415 | role: 'state', 416 | }, 417 | available_heaters: { 418 | name: 'Available Heaters', 419 | type: 'string', 420 | read: true, 421 | write: false, 422 | role: 'state', 423 | }, 424 | available_sensors: { 425 | name: 'Available Sensors', 426 | type: 'string', 427 | read: true, 428 | write: false, 429 | role: 'state', 430 | }, 431 | printing_time: { 432 | name: 'Printing Time', 433 | type: 'number', 434 | read: true, 435 | write: false, 436 | role: 'state', 437 | }, 438 | cols: { 439 | name: 'Columns', 440 | type: 'number', 441 | read: true, 442 | write: false, 443 | role: 'state', 444 | }, 445 | rows: { 446 | name: 'Rows', 447 | type: 'number', 448 | read: true, 449 | write: false, 450 | role: 'state', 451 | }, 452 | running: { 453 | name: 'Running Status', 454 | type: 'string', 455 | read: true, 456 | write: false, 457 | role: 'switch', 458 | }, 459 | timeout: { 460 | name: 'Timeout', 461 | type: 'number', 462 | read: true, 463 | write: false, 464 | role: 'state', 465 | }, 466 | is_paused: { 467 | name: 'is Paused Status', 468 | type: 'string', 469 | read: true, 470 | write: false, 471 | role: 'state', 472 | }, 473 | last_query: { 474 | name: 'Last Query', 475 | type: 'string', 476 | read: true, 477 | write: false, 478 | role: 'state', 479 | }, 480 | axis_maximum: { 481 | name: 'Maximum Axis Values', 482 | type: 'string', 483 | read: true, 484 | write: false, 485 | role: 'state', 486 | }, 487 | axis_minimum: { 488 | name: 'Minimum Axis Values', 489 | type: 'string', 490 | read: true, 491 | write: false, 492 | role: 'state', 493 | }, 494 | estimated_print_time: { 495 | name: 'Estimated Print Time', 496 | type: 'number', 497 | read: true, 498 | write: false, 499 | role: 'state', 500 | }, 501 | extruder: { 502 | name: 'Extruder Name', 503 | type: 'string', 504 | read: true, 505 | write: false, 506 | role: 'state', 507 | }, 508 | homed_axes: { 509 | name: 'Homed Axes', 510 | type: 'string', 511 | read: true, 512 | write: false, 513 | role: 'state', 514 | }, 515 | max_accel: { 516 | name: 'Maximum Acceleration', 517 | type: 'number', 518 | read: true, 519 | write: false, 520 | role: 'state', 521 | }, 522 | max_accel_to_decel: { 523 | name: 'Maximum Acceleration to Deceleration', 524 | type: 'number', 525 | read: true, 526 | write: false, 527 | role: 'state', 528 | }, 529 | max_velocity: { 530 | name: 'Maximum Velocity', 531 | type: 'number', 532 | read: true, 533 | write: false, 534 | role: 'state', 535 | }, 536 | print_time: { 537 | name: 'Actual Print Time', 538 | type: 'number', 539 | read: true, 540 | write: false, 541 | role: 'state', 542 | }, 543 | square_corner_velocity: { 544 | name: 'Square Corner Velocity', 545 | type: 'number', 546 | read: true, 547 | write: false, 548 | role: 'state', 549 | }, 550 | is_active: { 551 | name: 'Is Active Status', 552 | type: 'boolean', 553 | read: true, 554 | write: false, 555 | role: 'state', 556 | }, 557 | mesh_pps: { 558 | name: 'Mesh Points', 559 | type: 'string', 560 | read: true, 561 | write: false, 562 | role: 'state', 563 | }, 564 | probe_count: { 565 | name: 'Probe Count', 566 | type: 'number', 567 | read: true, 568 | write: false, 569 | role: 'state', 570 | }, 571 | relative_reference_index: { 572 | name: 'Relative Refernce Index', 573 | type: 'number', 574 | read: true, 575 | write: false, 576 | role: 'state', 577 | }, 578 | algo: { 579 | name: 'Algorithmus', 580 | type: 'string', 581 | read: true, 582 | write: false, 583 | role: 'state', 584 | }, 585 | max_x: { 586 | name: 'Maximum X', 587 | type: 'number', 588 | read: true, 589 | write: false, 590 | role: 'state', 591 | }, 592 | max_y: { 593 | name: 'Maximum Y', 594 | type: 'number', 595 | read: true, 596 | write: false, 597 | role: 'state', 598 | }, 599 | mesh_x_pps: { 600 | name: 'Mesh X PPS', 601 | type: 'number', 602 | read: true, 603 | write: false, 604 | role: 'state', 605 | }, 606 | mesh_y_pps: { 607 | name: 'Mesh Y PPS', 608 | type: 'number', 609 | read: true, 610 | write: false, 611 | role: 'state', 612 | }, 613 | min_x: { 614 | name: 'Minimum X', 615 | type: 'number', 616 | read: true, 617 | write: false, 618 | role: 'state', 619 | }, 620 | min_y: { 621 | name: 'Minimum Y', 622 | type: 'number', 623 | read: true, 624 | write: false, 625 | role: 'state', 626 | }, 627 | point: { 628 | name: 'Points', 629 | type: 'string', 630 | read: true, 631 | write: false, 632 | role: 'state', 633 | }, 634 | tension: { 635 | name: 'Tension', 636 | type: 'number', 637 | read: true, 638 | write: false, 639 | role: 'state', 640 | }, 641 | x_count: { 642 | name: 'X Count', 643 | type: 'number', 644 | read: true, 645 | write: false, 646 | role: 'state', 647 | }, 648 | y_count: { 649 | name: 'Y Count', 650 | type: 'number', 651 | read: true, 652 | write: false, 653 | role: 'state', 654 | }, 655 | control_pin: { 656 | name: 'Control Pin', 657 | type: 'string', 658 | read: true, 659 | write: false, 660 | role: 'state', 661 | }, 662 | pin_move_time: { 663 | name: 'Pin Movement Time', 664 | type: 'number', 665 | read: true, 666 | write: false, 667 | role: 'state', 668 | }, 669 | sensor_pin: { 670 | name: 'Sensor Pin', 671 | type: 'string', 672 | read: true, 673 | write: false, 674 | role: 'state', 675 | }, 676 | x_offset: { 677 | name: 'X Offset Value', 678 | type: 'number', 679 | read: true, 680 | write: false, 681 | role: 'state', 682 | }, 683 | y_offset: { 684 | name: 'Y Offset Value', 685 | type: 'number', 686 | read: true, 687 | write: false, 688 | role: 'state', 689 | }, 690 | z_offset: { 691 | name: 'Z Offset Value', 692 | type: 'number', 693 | read: true, 694 | write: false, 695 | role: 'state', 696 | }, 697 | aliases: { 698 | name: 'Aliases', 699 | type: 'string', 700 | read: true, 701 | write: false, 702 | role: 'state', 703 | }, 704 | click_pin: { 705 | name: 'Click Pin', 706 | type: 'string', 707 | read: true, 708 | write: false, 709 | role: 'state', 710 | }, 711 | cs_pin: { 712 | name: 'CS Pin', 713 | type: 'string', 714 | read: true, 715 | write: false, 716 | role: 'state', 717 | }, 718 | encoder_pins: { 719 | name: 'Encoder Pins', 720 | type: 'string', 721 | read: true, 722 | write: false, 723 | role: 'state', 724 | }, 725 | lcd_type: { 726 | name: 'LCD Display Type', 727 | type: 'string', 728 | read: true, 729 | write: false, 730 | role: 'state', 731 | }, 732 | sclk_pin: { 733 | name: 'SCLK Pin', 734 | type: 'string', 735 | read: true, 736 | write: false, 737 | role: 'state', 738 | }, 739 | sid_pin: { 740 | name: 'SID Pin', 741 | type: 'string', 742 | read: true, 743 | write: false, 744 | role: 'state', 745 | }, 746 | control: { 747 | name: 'Control', 748 | type: 'string', 749 | read: true, 750 | write: false, 751 | role: 'state', 752 | }, 753 | dir_pin: { 754 | name: 'DIR Pin', 755 | type: 'string', 756 | read: true, 757 | write: false, 758 | role: 'state', 759 | }, 760 | enable_pin: { 761 | name: 'Enable Pin', 762 | type: 'string', 763 | read: true, 764 | write: false, 765 | role: 'state', 766 | }, 767 | filament_diameter: { 768 | name: 'Filament Diameter', 769 | type: 'string', 770 | read: true, 771 | write: false, 772 | role: 'state', 773 | }, 774 | heater_pin: { 775 | name: 'Heater Pin', 776 | type: 'string', 777 | read: true, 778 | write: false, 779 | role: 'state', 780 | }, 781 | max_extrude_only_distance: { 782 | name: 'Maximum Extrude Only Distance', 783 | type: 'number', 784 | read: true, 785 | write: false, 786 | role: 'state', 787 | }, 788 | max_temp: { 789 | name: 'Maximum Temperatur', 790 | type: 'number', 791 | read: true, 792 | write: false, 793 | role: 'state', 794 | }, 795 | min_temp: { 796 | name: 'Minimum Temperatur', 797 | type: 'number', 798 | read: true, 799 | write: false, 800 | role: 'state', 801 | }, 802 | nozzle_diameter: { 803 | name: 'Nozzle Diameter', 804 | type: 'number', 805 | read: true, 806 | write: false, 807 | role: 'state', 808 | }, 809 | pid_kd: { 810 | name: 'PID kd', 811 | type: 'number', 812 | read: true, 813 | write: false, 814 | role: 'state', 815 | }, 816 | pid_ki: { 817 | name: 'PID ki', 818 | type: 'number', 819 | read: true, 820 | write: false, 821 | role: 'state', 822 | }, 823 | pid_kp: { 824 | name: 'PID kp', 825 | type: 'number', 826 | read: true, 827 | write: false, 828 | role: 'state', 829 | }, 830 | sensor_type: { 831 | name: 'Sensor Type', 832 | type: 'string', 833 | read: true, 834 | write: false, 835 | role: 'state', 836 | }, 837 | step_distance: { 838 | name: 'Step Distance', 839 | type: 'number', 840 | read: true, 841 | write: false, 842 | role: 'state', 843 | }, 844 | step_pin: { 845 | name: 'Step Pin', 846 | type: 'string', 847 | read: true, 848 | write: false, 849 | role: 'state', 850 | }, 851 | pin: { 852 | name: 'Pin', 853 | type: 'string', 854 | read: true, 855 | write: false, 856 | role: 'state', 857 | }, 858 | gcode: { 859 | name: 'G Code', 860 | type: 'string', 861 | read: true, 862 | write: false, 863 | role: 'state', 864 | }, 865 | rename_existing: { 866 | name: 'Rename Existing', 867 | type: 'string', 868 | read: true, 869 | write: false, 870 | role: 'state', 871 | }, 872 | default_parameter_e: { 873 | name: 'Default Parameter E', 874 | type: 'number', 875 | read: true, 876 | write: false, 877 | role: 'state', 878 | }, 879 | default_parameter_x: { 880 | name: 'Default Parameter X', 881 | type: 'number', 882 | read: true, 883 | write: false, 884 | role: 'state', 885 | }, 886 | default_parameter_y: { 887 | name: 'Default Parameter Y', 888 | type: 'number', 889 | read: true, 890 | write: false, 891 | role: 'state', 892 | }, 893 | default_parameter_z: { 894 | name: 'Default Parameter Z', 895 | type: 'number', 896 | read: true, 897 | write: false, 898 | role: 'state', 899 | }, 900 | serial: { 901 | name: 'Serial', 902 | type: 'string', 903 | read: true, 904 | write: false, 905 | role: 'state', 906 | }, 907 | kinematics: { 908 | name: 'Kinematics', 909 | type: 'string', 910 | read: true, 911 | write: false, 912 | role: 'state', 913 | }, 914 | max_z_accel: { 915 | name: 'Maximum Z Acceleration', 916 | type: 'number', 917 | read: true, 918 | write: false, 919 | role: 'state', 920 | }, 921 | max_z_velocity: { 922 | name: 'Maximum Z Velocity', 923 | type: 'number', 924 | read: true, 925 | write: false, 926 | role: 'state', 927 | }, 928 | home_xy_position: { 929 | name: 'Homing Position of X & Y', 930 | type: 'string', 931 | read: true, 932 | write: false, 933 | role: 'state', 934 | }, 935 | z_hop: { 936 | name: 'Z Hop', 937 | type: 'number', 938 | read: true, 939 | write: false, 940 | role: 'state', 941 | }, 942 | z_hop_speed: { 943 | name: 'Z Hop Speed', 944 | type: 'number', 945 | read: true, 946 | write: false, 947 | role: 'state', 948 | }, 949 | endstop_pin: { 950 | name: 'Endstop Pin', 951 | type: 'number', 952 | read: true, 953 | write: false, 954 | role: 'state', 955 | }, 956 | position_endstop: { 957 | name: 'Endstop Position', 958 | type: 'number', 959 | read: true, 960 | write: false, 961 | role: 'state', 962 | }, 963 | position_max: { 964 | name: 'Maximum Position', 965 | type: 'number', 966 | read: true, 967 | write: false, 968 | role: 'state', 969 | }, 970 | uart_pin: { 971 | name: 'UART Pin', 972 | type: 'string', 973 | read: true, 974 | write: false, 975 | role: 'state', 976 | }, 977 | message: { 978 | name: 'Message', 979 | type: 'string', 980 | read: true, 981 | write: false, 982 | role: 'state', 983 | }, 984 | position_min: { 985 | name: 'Minimum Position', 986 | type: 'number', 987 | read: true, 988 | write: false, 989 | role: 'state', 990 | }, 991 | value: { 992 | name: 'Value', 993 | type: 'number', 994 | read: true, 995 | write: false, 996 | role: 'state', 997 | }, 998 | hold_current: { 999 | name: 'Hold Current', 1000 | type: 'number', 1001 | read: true, 1002 | write: false, 1003 | role: 'state', 1004 | }, 1005 | stealthchop_threshold: { 1006 | name: 'Threshold', 1007 | type: 'number', 1008 | read: true, 1009 | write: false, 1010 | role: 'state', 1011 | }, 1012 | microsteps: { 1013 | name: 'Microsteps', 1014 | type: 'number', 1015 | read: true, 1016 | write: false, 1017 | role: 'state', 1018 | }, 1019 | version: { 1020 | name: 'Version', 1021 | type: 'string', 1022 | read: true, 1023 | write: false, 1024 | role: 'state', 1025 | }, 1026 | points: { 1027 | name: 'Points', 1028 | type: 'string', 1029 | read: true, 1030 | write: false, 1031 | role: 'state', 1032 | }, 1033 | pins: { 1034 | name: 'Pins', 1035 | type: 'string', 1036 | read: true, 1037 | write: false, 1038 | role: 'state', 1039 | }, 1040 | run_current: { 1041 | name: 'Run Current', 1042 | type: 'number', 1043 | read: true, 1044 | write: false, 1045 | role: 'state', 1046 | }, 1047 | path: { 1048 | name: 'Path', 1049 | type: 'string', 1050 | read: true, 1051 | write: false, 1052 | role: 'state', 1053 | }, 1054 | max_extrude_only_accel: { 1055 | name: 'Max extrude only acceleration', 1056 | type: 'number', 1057 | read: true, 1058 | write: false, 1059 | role: 'state', 1060 | }, 1061 | pressure_advance_smooth_time: { 1062 | name: 'Pressure Advance Smooth Time', 1063 | type: 'number', 1064 | read: true, 1065 | write: false, 1066 | role: 'state', 1067 | }, 1068 | instantaneous_corner_velocity: { 1069 | name: 'Instantaneous Corner Velocity', 1070 | type: 'number', 1071 | read: true, 1072 | write: false, 1073 | role: 'state', 1074 | }, 1075 | max_extrude_only_velocity: { 1076 | name: 'Max Extrude only velocity', 1077 | type: 'number', 1078 | read: true, 1079 | write: false, 1080 | role: 'state', 1081 | }, 1082 | max_extrude_cross_section: { 1083 | name: 'Max Extrude cross section', 1084 | type: 'number', 1085 | read: true, 1086 | write: false, 1087 | role: 'state', 1088 | }, 1089 | rpm: { 1090 | name: 'RPM', 1091 | type: 'number', 1092 | read: true, 1093 | write: false, 1094 | role: 'state', 1095 | }, 1096 | bytes_invalid: { 1097 | name: 'Invalid Bytes', 1098 | type: 'number', 1099 | read: true, 1100 | write: false, 1101 | role: 'state', 1102 | }, 1103 | bytes_read: { 1104 | name: 'Read Bytes', 1105 | type: 'number', 1106 | read: true, 1107 | write: false, 1108 | role: 'state', 1109 | }, 1110 | bytes_retransmit: { 1111 | name: 'Retransmit Bytes', 1112 | type: 'number', 1113 | read: true, 1114 | write: false, 1115 | role: 'state', 1116 | }, 1117 | bytes_write: { 1118 | name: 'Writen Bytes', 1119 | type: 'number', 1120 | read: true, 1121 | write: false, 1122 | role: 'state', 1123 | }, 1124 | freq: { 1125 | name: 'Frequence', 1126 | type: 'number', 1127 | read: true, 1128 | write: false, 1129 | role: 'state', 1130 | }, 1131 | mcu_awake: { 1132 | name: 'MCU Awake', 1133 | type: 'number', 1134 | read: true, 1135 | write: false, 1136 | role: 'state', 1137 | }, 1138 | mcu_task_avg: { 1139 | name: 'Average MCU Task', 1140 | type: 'number', 1141 | read: true, 1142 | write: false, 1143 | role: 'state', 1144 | }, 1145 | mcu_task_stddev: { 1146 | name: 'MCU Task stddev', 1147 | type: 'number', 1148 | read: true, 1149 | write: false, 1150 | role: 'state', 1151 | }, 1152 | ready_bytes: { 1153 | name: 'Ready Bytes', 1154 | type: 'number', 1155 | read: true, 1156 | write: false, 1157 | role: 'state', 1158 | }, 1159 | receive_seq: { 1160 | name: 'Receive Sequence', 1161 | type: 'number', 1162 | read: true, 1163 | write: false, 1164 | role: 'state', 1165 | }, 1166 | retransmit_seq: { 1167 | name: 'Retransmited Sequence', 1168 | type: 'number', 1169 | read: true, 1170 | write: false, 1171 | role: 'state', 1172 | }, 1173 | rto: { 1174 | name: 'RTO', 1175 | type: 'number', 1176 | read: true, 1177 | write: false, 1178 | role: 'state', 1179 | }, 1180 | rttvar: { 1181 | name: 'RTTVAR', 1182 | type: 'number', 1183 | read: true, 1184 | write: false, 1185 | role: 'state', 1186 | }, 1187 | srtt: { 1188 | name: 'SRTT', 1189 | type: 'number', 1190 | read: true, 1191 | write: false, 1192 | role: 'state', 1193 | }, 1194 | stalled_bytes: { 1195 | name: 'Stalled Bytes', 1196 | type: 'number', 1197 | read: true, 1198 | write: false, 1199 | role: 'state', 1200 | }, 1201 | BUS_PINS_spi1: { 1202 | name: 'Bus Pins SPI1', 1203 | type: 'string', 1204 | read: true, 1205 | write: false, 1206 | role: 'state', 1207 | }, 1208 | BUS_PINS_spi2: { 1209 | name: 'Bus Pins SPI2', 1210 | type: 'string', 1211 | read: true, 1212 | write: false, 1213 | role: 'state', 1214 | }, 1215 | BUS_PINS_spi3: { 1216 | name: 'Bus Pins SPI3', 1217 | type: 'string', 1218 | read: true, 1219 | write: false, 1220 | role: 'state', 1221 | }, 1222 | BUS_PINS_i2c1: { 1223 | name: 'Bus Pins I2C1', 1224 | type: 'string', 1225 | read: true, 1226 | write: false, 1227 | role: 'state', 1228 | }, 1229 | BUS_PINS_i2c2: { 1230 | name: 'Bus Pins I2C2', 1231 | type: 'string', 1232 | read: true, 1233 | write: false, 1234 | role: 'state', 1235 | }, 1236 | CLOCK_FREQ: { 1237 | name: 'Clock Frequence', 1238 | type: 'number', 1239 | read: true, 1240 | write: false, 1241 | role: 'state', 1242 | }, 1243 | MCU: { 1244 | name: 'MCU', 1245 | type: 'string', 1246 | read: true, 1247 | write: false, 1248 | role: 'state', 1249 | }, 1250 | RESERVE_PINS_USB: { 1251 | name: 'USB Pins', 1252 | type: 'string', 1253 | read: true, 1254 | write: false, 1255 | role: 'state', 1256 | }, 1257 | send_seq: { 1258 | name: 'Send Sequence', 1259 | type: 'number', 1260 | read: true, 1261 | write: false, 1262 | role: 'state', 1263 | }, 1264 | STATS_SUMSQ_BASE: { 1265 | name: 'STATS SUMSQ BASE', 1266 | type: 'number', 1267 | read: true, 1268 | write: false, 1269 | role: 'state', 1270 | }, 1271 | STEP_DELAY: { 1272 | name: 'Step Delay', 1273 | type: 'number', 1274 | read: true, 1275 | write: false, 1276 | role: 'state', 1277 | }, 1278 | mcu_version: { 1279 | name: 'MCU Version', 1280 | type: 'string', 1281 | read: true, 1282 | write: false, 1283 | role: 'state', 1284 | }, 1285 | mcu_build_versions: { 1286 | name: 'MCU Build Version', 1287 | type: 'string', 1288 | read: true, 1289 | write: false, 1290 | role: 'state', 1291 | }, 1292 | algorithm: { 1293 | name: 'Algorithmus', 1294 | type: 'string', 1295 | read: true, 1296 | write: false, 1297 | role: 'state', 1298 | }, 1299 | bicubic_tension: { 1300 | name: 'Bicubic Tension', 1301 | type: 'number', 1302 | read: true, 1303 | write: false, 1304 | role: 'state', 1305 | }, 1306 | fade_end: { 1307 | name: 'Fade End', 1308 | type: 'number', 1309 | read: true, 1310 | write: false, 1311 | role: 'state', 1312 | }, 1313 | lift_speed: { 1314 | name: 'Lift Speed', 1315 | type: 'number', 1316 | read: true, 1317 | write: false, 1318 | role: 'state', 1319 | }, 1320 | pin_up_reports_not_triggered: { 1321 | name: 'Pin Up Reports not Triggered', 1322 | type: 'string', 1323 | read: true, 1324 | write: false, 1325 | role: 'state', 1326 | }, 1327 | pin_up_touch_mode_reports_triggered: { 1328 | name: 'Pin Up Touch Mode Reports', 1329 | type: 'string', 1330 | read: true, 1331 | write: false, 1332 | role: 'state', 1333 | }, 1334 | sample_retract_dist: { 1335 | name: 'Sample Retract Distance', 1336 | type: 'number', 1337 | read: true, 1338 | write: false, 1339 | role: 'state', 1340 | }, 1341 | samples_result: { 1342 | name: 'Sample Result', 1343 | type: 'string', 1344 | read: true, 1345 | write: false, 1346 | role: 'state', 1347 | }, 1348 | samples_tolerance: { 1349 | name: 'Sample Tolerance', 1350 | type: 'number', 1351 | read: true, 1352 | write: false, 1353 | role: 'state', 1354 | }, 1355 | samples_tolerance_retries: { 1356 | name: 'Sample Tolerance Retries', 1357 | type: 'number', 1358 | read: true, 1359 | write: false, 1360 | role: 'state', 1361 | }, 1362 | stow_on_each_sample: { 1363 | name: 'Stow on each Sample', 1364 | type: 'string', 1365 | read: true, 1366 | write: false, 1367 | role: 'state', 1368 | }, 1369 | samples: { 1370 | name: 'Samples', 1371 | type: 'number', 1372 | read: true, 1373 | write: false, 1374 | role: 'state', 1375 | }, 1376 | mcu: { 1377 | name: 'MCU', 1378 | type: 'string', 1379 | read: true, 1380 | write: false, 1381 | role: 'state', 1382 | }, 1383 | display_group: { 1384 | name: 'Display Group', 1385 | type: 'string', 1386 | read: true, 1387 | write: false, 1388 | role: 'state', 1389 | }, 1390 | encoder_fast_rate: { 1391 | name: 'Encoder Fastrate', 1392 | type: 'number', 1393 | read: true, 1394 | write: false, 1395 | role: 'state', 1396 | }, 1397 | menu_reverse_navigation: { 1398 | name: 'Menu Reverse Navigation', 1399 | type: 'string', 1400 | read: true, 1401 | write: false, 1402 | role: 'state', 1403 | }, 1404 | menu_root: { 1405 | name: 'Menu Root', 1406 | type: 'string', 1407 | read: true, 1408 | write: false, 1409 | role: 'state', 1410 | }, 1411 | menu_timeout: { 1412 | name: 'Menu Timeout', 1413 | type: 'number', 1414 | read: true, 1415 | write: false, 1416 | role: 'state', 1417 | }, 1418 | max_power: { 1419 | name: 'Maximum Power', 1420 | type: 'number', 1421 | read: true, 1422 | write: false, 1423 | role: 'state', 1424 | }, 1425 | min_extrude_temp: { 1426 | name: 'Minimum Extruder Temperatur', 1427 | type: 'number', 1428 | read: true, 1429 | write: false, 1430 | role: 'state', 1431 | }, 1432 | pid_integral_max: { 1433 | name: 'PID Integral Max', 1434 | type: 'number', 1435 | read: true, 1436 | write: false, 1437 | role: 'state', 1438 | }, 1439 | pullup_resistor: { 1440 | name: 'Pullup Resistor', 1441 | type: 'number', 1442 | read: true, 1443 | write: false, 1444 | role: 'state', 1445 | }, 1446 | pwm_cycle_time: { 1447 | name: 'PWM Cycle Resistor', 1448 | type: 'number', 1449 | read: true, 1450 | write: false, 1451 | role: 'state', 1452 | }, 1453 | cycle_time: { 1454 | name: 'Cycle Time', 1455 | type: 'number', 1456 | read: true, 1457 | write: false, 1458 | role: 'state', 1459 | }, 1460 | hardware_pwm: { 1461 | name: 'Hardware PWM', 1462 | type: 'string', 1463 | read: true, 1464 | write: false, 1465 | role: 'state', 1466 | }, 1467 | kick_start_time: { 1468 | name: 'Kickstart Time', 1469 | type: 'number', 1470 | read: true, 1471 | write: false, 1472 | role: 'state', 1473 | }, 1474 | off_below: { 1475 | name: 'Off below', 1476 | type: 'number', 1477 | read: true, 1478 | write: false, 1479 | role: 'state', 1480 | }, 1481 | shutdown_speed: { 1482 | name: 'Shutdown Speed', 1483 | type: 'number', 1484 | read: true, 1485 | write: false, 1486 | role: 'state', 1487 | }, 1488 | enable_force_mode: { 1489 | name: 'Enable Force Mode', 1490 | type: 'string', 1491 | read: true, 1492 | write: false, 1493 | role: 'state', 1494 | }, 1495 | inline_resistor: { 1496 | name: 'Inline Resistor', 1497 | type: 'number', 1498 | read: true, 1499 | write: false, 1500 | role: 'state', 1501 | }, 1502 | last_z_result: { 1503 | name: 'Last Z-Result', 1504 | type: 'number', 1505 | read: true, 1506 | write: false, 1507 | role: 'state', 1508 | }, 1509 | INITIAL_PINS: { 1510 | name: 'Initial Pins', 1511 | type: 'string', 1512 | read: true, 1513 | write: false, 1514 | role: 'state', 1515 | }, 1516 | max_error: { 1517 | name: 'Maximum Error', 1518 | type: 'number', 1519 | read: true, 1520 | write: false, 1521 | role: 'state', 1522 | }, 1523 | hysteresis: { 1524 | name: 'Hysteresis', 1525 | type: 'number', 1526 | read: true, 1527 | write: false, 1528 | role: 'state', 1529 | }, 1530 | heating_gain: { 1531 | name: 'Heating Gain', 1532 | type: 'number', 1533 | read: true, 1534 | write: false, 1535 | role: 'state', 1536 | }, 1537 | check_gain_time: { 1538 | name: 'Check Gain Time', 1539 | type: 'number', 1540 | read: true, 1541 | write: false, 1542 | role: 'state', 1543 | }, 1544 | driver_hend: { 1545 | name: 'Driver Hend', 1546 | type: 'number', 1547 | read: true, 1548 | write: false, 1549 | role: 'state', 1550 | }, 1551 | driver_hstrt: { 1552 | name: 'Driver HSTRT?', 1553 | type: 'number', 1554 | read: true, 1555 | write: false, 1556 | role: 'state', 1557 | }, 1558 | driver_iholddelay: { 1559 | name: 'Driver Holddelay', 1560 | type: 'number', 1561 | read: true, 1562 | write: false, 1563 | role: 'state', 1564 | }, 1565 | driver_pwm_autograd: { 1566 | name: 'Driver PWM Autograd', 1567 | type: 'number', 1568 | read: true, 1569 | write: false, 1570 | role: 'state', 1571 | }, 1572 | driver_pwm_autoscale: { 1573 | name: 'Driver PWM Autoscale', 1574 | type: 'number', 1575 | read: true, 1576 | write: false, 1577 | role: 'state', 1578 | }, 1579 | driver_pwm_freq: { 1580 | name: 'Driver PWM Frequence', 1581 | type: 'number', 1582 | read: true, 1583 | write: false, 1584 | role: 'state', 1585 | }, 1586 | driver_pwm_grad: { 1587 | name: 'Driver PWM Grad', 1588 | type: 'number', 1589 | read: true, 1590 | write: false, 1591 | role: 'state', 1592 | }, 1593 | driver_pwm_lim: { 1594 | name: 'Driver PWM Lim', 1595 | type: 'number', 1596 | read: true, 1597 | write: false, 1598 | role: 'state', 1599 | }, 1600 | driver_pwm_ofs: { 1601 | name: 'Driver PWM Ofs', 1602 | type: 'number', 1603 | read: true, 1604 | write: false, 1605 | role: 'state', 1606 | }, 1607 | driver_pwm_reg: { 1608 | name: 'Driver PWM Reg', 1609 | type: 'number', 1610 | read: true, 1611 | write: false, 1612 | role: 'state', 1613 | }, 1614 | driver_sgthrs: { 1615 | name: 'Driver sgthrs', 1616 | type: 'number', 1617 | read: true, 1618 | write: false, 1619 | role: 'state', 1620 | }, 1621 | driver_tbl: { 1622 | name: 'Driver Tbl', 1623 | type: 'number', 1624 | read: true, 1625 | write: false, 1626 | role: 'state', 1627 | }, 1628 | driver_toff: { 1629 | name: 'Driver Toff', 1630 | type: 'number', 1631 | read: true, 1632 | write: false, 1633 | role: 'state', 1634 | }, 1635 | driver_tpowerdown: { 1636 | name: 'Driver TPowerdown', 1637 | type: 'number', 1638 | read: true, 1639 | write: false, 1640 | role: 'state', 1641 | }, 1642 | uart_address: { 1643 | name: 'UART Address', 1644 | type: 'number', 1645 | read: true, 1646 | write: false, 1647 | role: 'state', 1648 | }, 1649 | interpolate: { 1650 | name: 'Interpolate', 1651 | type: 'string', 1652 | read: true, 1653 | write: false, 1654 | role: 'state', 1655 | }, 1656 | sense_resistor: { 1657 | name: 'Sense Resistor', 1658 | type: 'number', 1659 | read: true, 1660 | write: false, 1661 | role: 'state', 1662 | }, 1663 | homing_retract_speed: { 1664 | name: 'Homing Retract Speed', 1665 | type: 'number', 1666 | read: true, 1667 | write: false, 1668 | role: 'state', 1669 | }, 1670 | homing_speed: { 1671 | name: 'Homing Speed', 1672 | type: 'number', 1673 | read: true, 1674 | write: false, 1675 | role: 'state', 1676 | }, 1677 | homing_retract_dist: { 1678 | name: 'Homing Retract Distance', 1679 | type: 'number', 1680 | read: true, 1681 | write: false, 1682 | role: 'state', 1683 | }, 1684 | second_homing_speed: { 1685 | name: 'Homing second Speed', 1686 | type: 'number', 1687 | read: true, 1688 | write: false, 1689 | role: 'state', 1690 | }, 1691 | move_to_previous: { 1692 | name: 'Move to Previous', 1693 | type: 'string', 1694 | read: true, 1695 | write: false, 1696 | role: 'state', 1697 | }, 1698 | buffer_time_high: { 1699 | name: 'Buffer Time High', 1700 | type: 'number', 1701 | read: true, 1702 | write: false, 1703 | role: 'state', 1704 | }, 1705 | buffer_time_low: { 1706 | name: 'Buffer Time Low', 1707 | type: 'number', 1708 | read: true, 1709 | write: false, 1710 | role: 'state', 1711 | }, 1712 | buffer_time_start: { 1713 | name: 'Buffer Time Start', 1714 | type: 'number', 1715 | read: true, 1716 | write: false, 1717 | role: 'state', 1718 | }, 1719 | move_flush_time: { 1720 | name: 'Move Flushtime', 1721 | type: 'number', 1722 | read: true, 1723 | write: false, 1724 | role: 'state', 1725 | }, 1726 | recover_velocity: { 1727 | name: 'Velocity Recover', 1728 | type: 'number', 1729 | read: true, 1730 | write: false, 1731 | role: 'state', 1732 | }, 1733 | pwm: { 1734 | name: 'PWM', 1735 | type: 'string', 1736 | read: true, 1737 | write: false, 1738 | role: 'state', 1739 | }, 1740 | shutdown_value: { 1741 | name: 'Shutdown Value', 1742 | type: 'number', 1743 | read: true, 1744 | write: false, 1745 | role: 'state', 1746 | }, 1747 | baud: { 1748 | name: 'BAUD Rate', 1749 | type: 'number', 1750 | read: true, 1751 | write: false, 1752 | role: 'state', 1753 | }, 1754 | max_stepper_error: { 1755 | name: 'Max Stepper Error', 1756 | type: 'number', 1757 | read: true, 1758 | write: false, 1759 | role: 'state', 1760 | }, 1761 | damping_ratio_y: { 1762 | name: 'Damping Ratio Y', 1763 | type: 'number', 1764 | read: true, 1765 | write: false, 1766 | role: 'state', 1767 | }, 1768 | damping_ratio_x: { 1769 | name: 'Damping Ratio X', 1770 | type: 'number', 1771 | read: true, 1772 | write: false, 1773 | role: 'state', 1774 | }, 1775 | shaper_type: { 1776 | name: 'Shaper Type', 1777 | type: 'string', 1778 | read: true, 1779 | write: false, 1780 | role: 'state', 1781 | }, 1782 | shaper_type_y: { 1783 | name: 'Shaper Type Y', 1784 | type: 'string', 1785 | read: true, 1786 | write: false, 1787 | role: 'state', 1788 | }, 1789 | shaper_type_x: { 1790 | name: 'Shaper Type X', 1791 | type: 'string', 1792 | read: true, 1793 | write: false, 1794 | role: 'state', 1795 | }, 1796 | shaper_freq_y: { 1797 | name: 'Shaper Frequence Y', 1798 | type: 'string', 1799 | read: true, 1800 | write: false, 1801 | role: 'state', 1802 | }, 1803 | shaper_freq_x: { 1804 | name: 'Shaper Frequence X', 1805 | type: 'string', 1806 | read: true, 1807 | write: false, 1808 | role: 'state', 1809 | }, 1810 | move_check_distance: { 1811 | name: 'Check Distance Move', 1812 | type: 'number', 1813 | read: true, 1814 | write: false, 1815 | role: 'state', 1816 | }, 1817 | split_delta_z: { 1818 | name: 'Split Delta Z', 1819 | type: 'number', 1820 | read: true, 1821 | write: false, 1822 | role: 'state', 1823 | }, 1824 | probe_with_touch_mode: { 1825 | name: 'Probe with Touchmode', 1826 | type: 'string', 1827 | read: true, 1828 | write: false, 1829 | role: 'state', 1830 | }, 1831 | enable_force_move: { 1832 | name: 'Enable Force Move', 1833 | type: 'string', 1834 | read: true, 1835 | write: false, 1836 | role: 'state', 1837 | }, 1838 | ADC_MAX: { 1839 | name: 'ADC Maximum', 1840 | type: 'number', 1841 | read: true, 1842 | write: false, 1843 | role: 'state', 1844 | }, 1845 | }; 1846 | 1847 | module.exports = stateAttrb; 1848 | --------------------------------------------------------------------------------