├── settings.png ├── paypal-with-text.png ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── stale.yml ├── .gitignore ├── patreon-with-text-new.png ├── settings_topic_editor.png ├── babel.cfg ├── MANIFEST.in ├── requirements.txt ├── .editorconfig ├── translations └── README.txt ├── extras └── mqttsubscribe.md ├── octoprint_mqttsubscribe ├── static │ └── js │ │ ├── mqttsubscribe.js │ │ ├── knockout-sortable.js │ │ ├── knockout-sortable.1.2.0.js │ │ └── jquery-ui.min.js ├── templates │ └── mqttsubscribe_settings.jinja2 └── __init__.py ├── README.md └── setup.py /settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jneilliii/OctoPrint-MQTTSubscribe/HEAD/settings.png -------------------------------------------------------------------------------- /paypal-with-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jneilliii/OctoPrint-MQTTSubscribe/HEAD/paypal-with-text.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jneilliii] 2 | patreon: jneilliii 3 | custom: ['https://www.paypal.me/jneilliii'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .idea 4 | *.iml 5 | build 6 | dist 7 | *.egg* 8 | .DS_Store 9 | *.zip 10 | -------------------------------------------------------------------------------- /patreon-with-text-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jneilliii/OctoPrint-MQTTSubscribe/HEAD/patreon-with-text-new.png -------------------------------------------------------------------------------- /settings_topic_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jneilliii/OctoPrint-MQTTSubscribe/HEAD/settings_topic_editor.png -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: */**.py] 2 | [jinja2: */**.jinja2] 3 | extensions=jinja2.ext.autoescape, jinja2.ext.with_ 4 | 5 | [javascript: */**.js] 6 | extract_messages = gettext, ngettext 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | recursive-include octoprint_tasmota-mqtt/templates * 3 | recursive-include octoprint_tasmota-mqtt/translations * 4 | recursive-include octoprint_tasmota-mqtt/static * 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ### 2 | # This file is only here to make sure that something like 3 | # 4 | # pip install -e . 5 | # 6 | # works as expected. Requirements can be found in setup.py. 7 | ### 8 | 9 | . 10 | requests 11 | jsonpath_rw 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [**.py] 13 | indent_style = tab 14 | 15 | [**.js] 16 | indent_style = space 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - enhancement 8 | - bug 9 | - solved 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | activity in 14 days. It will be closed if no further activity occurs in 7 days. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark Stale Issues 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | #permissions: 7 | # actions: write 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v9 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had activity in 14 days. It will be closed if no further activity occurs in 7 days' 16 | days-before-stale: 14 17 | days-before-close: 7 18 | stale-issue-label: 'stale' 19 | days-before-issue-stale: 14 20 | days-before-pr-stale: -1 21 | days-before-issue-close: 7 22 | days-before-pr-close: -1 23 | exempt-issue-labels: 'bug,enhancement' 24 | # - uses: actions/checkout@v4 25 | # - uses: gautamkrishnar/keepalive-workflow@v2 26 | # with: 27 | # use_api: true 28 | -------------------------------------------------------------------------------- /translations/README.txt: -------------------------------------------------------------------------------- 1 | Your plugin's translations will reside here. The provided setup.py supports a 2 | couple of additional commands to make managing your translations easier: 3 | 4 | babel_extract 5 | Extracts any translateable messages (marked with Jinja's `_("...")` or 6 | JavaScript's `gettext("...")`) and creates the initial `messages.pot` file. 7 | babel_refresh 8 | Reruns extraction and updates the `messages.pot` file. 9 | babel_new --locale= 10 | Creates a new translation folder for locale ``. 11 | babel_compile 12 | Compiles the translations into `mo` files, ready to be used within 13 | OctoPrint. 14 | babel_pack --locale= [ --author= ] 15 | Packs the translation for locale `` up as an installable 16 | language pack that can be manually installed by your plugin's users. This is 17 | interesting for languages you can not guarantee to keep up to date yourself 18 | with each new release of your plugin and have to depend on contributors for. 19 | 20 | If you want to bundle translations with your plugin, create a new folder 21 | `octoprint_tasmota-mqtt/translations`. When that folder exists, 22 | an additional command becomes available: 23 | 24 | babel_bundle --locale= 25 | Moves the translation for locale `` to octoprint_tasmota-mqtt/translations, 26 | effectively bundling it with your plugin. This is interesting for languages 27 | you can guarantee to keep up to date yourself with each new release of your 28 | plugin. 29 | -------------------------------------------------------------------------------- /extras/mqttsubscribe.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: plugin 3 | 4 | id: mqttsubscribe 5 | title: MQTT Subscribe 6 | description: This plugin allows controlling OctoPrint via MQTT messages. 7 | author: jneilliii 8 | license: AGPLv3 9 | 10 | date: 2020-05-27 11 | 12 | homepage: https://github.com/jneilliii/OctoPrint-MQTTSubscribe 13 | source: https://github.com/jneilliii/OctoPrint-MQTTSubscribe 14 | archive: https://github.com/jneilliii/OctoPrint-MQTTSubscribe/archive/master.zip 15 | 16 | follow_dependency_links: false 17 | 18 | tags: 19 | - MQTT 20 | - control 21 | 22 | compatibility: 23 | python: ">=2.7,<4" 24 | 25 | --- 26 | 27 | # MQTT Subscribe 28 | 29 | This plugin can control OctoPrint by submitting commands to the [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html). 30 | 31 | ## Prerequisites 32 | 33 | Install the [MQTT](https://github.com/OctoPrint/OctoPrint-MQTT) plugin via the Plugin Manager or manually using this url: 34 | 35 | https://github.com/OctoPrint/OctoPrint-MQTT/archive/master.zip 36 | 37 | Once installed configure the MQTT server connection in the MQTT plugin's settings. This will be the same server that the MQTT Subscribe plugin will connect to for subscribing configured topics. 38 | 39 | ## Setup 40 | 41 | Install via the Plugin Manager or manually using this URL: 42 | 43 | https://github.com/jneilliii/OctoPrint-MQTTSubscribe/archive/master.zip 44 | 45 | ## Configuration 46 | 47 | Once both plugins are installed configure the topics/commands you want to subscribe/submit to and generate your API key. 48 | 49 | ## Settings 50 | 51 | ![settings screenshot](/assets/img/plugins/mqttsubscribe/settings.png) 52 | 53 | ### Topics 54 | - List of configured topics 55 | - Click the plus button in the top right to add new topics 56 | - Click the pencil button to edit a configured topic 57 | - Click the copy button to duplicate a topic 58 | - Click the trash icon to delete a topic 59 | ### General 60 | - API Key: API key to use to authenticate to the [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html) 61 | - Click the plus button to generate your API key and accept the request 62 | - Click the trash icon to clear your API key 63 | - Click the copy button to copy the API key to your clipboard 64 | 65 | ## MQTT Topic Editor 66 | 67 | ![topic editor screenshot](/assets/img/plugins/mqttsubscribe/settings_topic_editor.png) 68 | 69 | - Topic: MQTT topic to subscribe 70 | - JSONPath Extract: JSON Path expression to extract from sent data, see [here](https://github.com/jneilliii/OctoPrint-MQTTSubscribe/issues/7#issuecomment-582166178) for an example, leave blank if substitution is not necessary in the `REST Parameters` described below 71 | - Type: The type of REST API submission, either post or get 72 | - REST API: The [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html) command that you want to submit 73 | - REST Parameters: The `JSON parameters` to submit to the REST API configured above 74 | 75 | ## Get Help 76 | 77 | If you experience issues with this plugin or need assistance please use the issue tracker at the plugin's Homepage linked on the right. 78 | 79 | ### Additional Plugins 80 | 81 | Check out my other plugins [here](https://plugins.octoprint.org/by_author/#jneilliii). 82 | 83 | ### Support My Efforts 84 | I, jneilliii, programmed this plugin for fun and do my best effort to support those that have issues with it, please return the favor and leave me a tip or become a Patron if you find this plugin helpful and want me to continue future development. 85 | 86 | [![Patreon](/assets/img/plugins/mqttsubscribe/patreon-with-text-new.png)](https://www.patreon.com/jneilliii) [![paypal](/assets/img/plugins/mqttsubscribe/paypal-with-text.png)](https://paypal.me/jneilliii) 87 | 88 | No paypal.me? Send funds via PayPal to jneilliii@gmail.com 89 | -------------------------------------------------------------------------------- /octoprint_mqttsubscribe/static/js/mqttsubscribe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * View model for OctoPrint-MQTTSubscribe 3 | * 4 | * Author: jneilliii 5 | * License: AGPLv3 6 | */ 7 | $(function() { 8 | function MQTTSubscribeViewModel(parameters) { 9 | var self = this; 10 | 11 | self.loginStateViewModel = parameters[0]; 12 | self.settingsViewModel = parameters[1]; 13 | 14 | self.topics = ko.observableArray(); 15 | self.selected_topic = ko.observable(); 16 | 17 | self.retrieving_key = ko.observable(false); 18 | 19 | self.onBeforeBinding = function() { 20 | self.topics(self.settingsViewModel.settings.plugins.mqttsubscribe.topics()); 21 | } 22 | 23 | self.onEventSettingsUpdated = function(payload) { 24 | self.topics(self.settingsViewModel.settings.plugins.mqttsubscribe.topics()); 25 | } 26 | 27 | self.onDataUpdaterPluginMessage = function(plugin, data) { 28 | if (plugin !== "mqttsubscribe") { 29 | return; 30 | } 31 | 32 | if(data.topic) { 33 | new PNotify({ 34 | title: 'MQTT Command Received for ' + data.topic, 35 | text: 'message:
' + data.message + '
', 36 | type: 'info', 37 | hide: true 38 | }); 39 | } 40 | 41 | if(data.error) { 42 | new PNotify({ 43 | title: 'MQTTSubscribe Error', 44 | text: '
' + data.error + '
', 45 | type: 'error', 46 | hide: false 47 | }); 48 | } 49 | }; 50 | 51 | self.getAppKey = function() { 52 | self.retrieving_key(true); 53 | OctoPrint.plugins.appkeys.authenticate("MQTT Subscribe", self.loginStateViewModel.userMenuText()) 54 | .done(function(api_key) { 55 | self.settingsViewModel.settings.plugins.mqttsubscribe.api_key(api_key); 56 | self.retrieving_key(false); 57 | }) 58 | .fail(function() { 59 | self.retrieving_key(false); 60 | new PNotify({ 61 | title: 'MQTTSubscribe Error', 62 | text: 'There was an error requesting an API key or the request was denied.', 63 | type: 'error', 64 | hide: true 65 | }); 66 | }); 67 | } 68 | 69 | self.showEditor = function(data) { 70 | self.selected_topic(data); 71 | $('#mqttTopicEditor').modal('show'); 72 | }; 73 | 74 | self.copyKey = function(data){ 75 | copyToClipboard(data.settingsViewModel.settings.plugins.mqttsubscribe.api_key()); 76 | } 77 | 78 | self.removeKey = function(data){ 79 | self.settingsViewModel.settings.plugins.mqttsubscribe.api_key(''); 80 | } 81 | 82 | self.addTopic = function(data) { 83 | self.selected_topic({'topic':ko.observable(''), 84 | 'extract':ko.observable(''), 85 | 'type':ko.observable('post'), 86 | 'rest':ko.observable(''), 87 | 'command':ko.observable(''), 88 | 'disable_popup':ko.observable(false) 89 | }); 90 | self.settingsViewModel.settings.plugins.mqttsubscribe.topics.push(self.selected_topic()); 91 | $('#mqttTopicEditor').modal('show'); 92 | } 93 | 94 | self.copyTopic = function(data) { 95 | self.selected_topic({'topic':ko.observable(data.topic()), 96 | 'extract':ko.observable(data.extract()), 97 | 'type':ko.observable(data.type()), 98 | 'rest':ko.observable(data.rest()), 99 | 'command':ko.observable(data.command()), 100 | 'disable_popup':ko.observable(data.disable_popup()) 101 | }); 102 | self.settingsViewModel.settings.plugins.mqttsubscribe.topics.push(self.selected_topic()); 103 | $('#mqttTopicEditor').modal('show'); 104 | } 105 | 106 | self.removeTopic = function(data) { 107 | self.settingsViewModel.settings.plugins.mqttsubscribe.topics.remove(data); 108 | } 109 | } 110 | 111 | OCTOPRINT_VIEWMODELS.push({ 112 | construct: MQTTSubscribeViewModel, 113 | dependencies: ["loginStateViewModel", "settingsViewModel"], 114 | elements: ["#settings_plugin_mqttsubscribe"] 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTT Subscribe 2 | 3 | This plugin can control OctoPrint by submitting commands to the [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html). 4 | 5 | ## Prerequisites 6 | 7 | Install the [MQTT](https://github.com/OctoPrint/OctoPrint-MQTT) plugin via the Plugin Manager or manually using this url: 8 | 9 | https://github.com/OctoPrint/OctoPrint-MQTT/archive/master.zip 10 | 11 | Once installed configure the MQTT server connection in the MQTT plugin's settings. This will be the same server that the MQTT Subscribe plugin will connect to for subscribing configured topics. 12 | 13 | ## Setup 14 | 15 | Install via the Plugin Manager or manually using this URL: 16 | 17 | https://github.com/jneilliii/OctoPrint-MQTTSubscribe/archive/master.zip 18 | 19 | ## Configuration 20 | 21 | Once both plugins are installed configure the topics/commands you want to subscribe/submit to and generate your API key. 22 | 23 | ## Settings 24 | 25 | ![settings screenshot](settings.png) 26 | 27 | ### Topics 28 | - List of configured topics 29 | - Click the plus button in the top right to add new topics 30 | - Click the pencil button to edit a configured topic 31 | - Click the copy button to duplicate a topic 32 | - Click the trash icon to delete a topic 33 | ### General 34 | - API Key: API key to use to authenticate to the [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html) 35 | - Click the plus button to generate your API key and accept the request 36 | - Click the trash icon to clear your API key 37 | - Click the copy button to copy the API key to your clipboard 38 | 39 | ## MQTT Topic Editor 40 | 41 | ![topic editor screenshot](settings_topic_editor.png) 42 | 43 | - Topic: MQTT topic to subscribe 44 | - JSONPath Extract: JSON Path expression to extract from sent data, see [here](https://github.com/jneilliii/OctoPrint-MQTTSubscribe/issues/7#issuecomment-582166178) for an example, leave blank if substitution is not necessary in the `REST Parameters` described below 45 | - Type: The type of REST API submission, either post or get 46 | - REST API: The [OctoPrint REST API](http://docs.octoprint.org/en/master/api/index.html) command that you want to submit 47 | - REST Parameters: The `JSON parameters` to submit to the REST API configured above. 48 | 49 | To substitute a portion of the REST parameters or the REST API URL for a parameter from the output of JSONPath Extract, use Python-style string-substitution, like e.g. below: 50 | 51 | JSON the plugin receives via the MQTT topic: `{"mycommand":"disconnect"}` 52 | JSONPath Extract: `$.mycommand` 53 | Rest Parameters: `{"command":"{0}"}` 54 | The output that gets sent to the API: `{"command":"disconnect"}` 55 | 56 | ## Most Recent Release 57 | 58 | **[0.1.7](https://github.com/jneilliii/OctoPrint-MQTTSubscribe/releases/tag/0.1.7)** (3/26/2022) 59 | 60 | **Updated** 61 | 62 | * remove whitespace requirements with opening/closing curly brackets in REST parameters 63 | 64 | ### [All Releases](https://github.com/jneilliii/OctoPrint-MQTTSubscribe/releases) 65 | 66 | ## Get Help 67 | 68 | If you experience issues with this plugin or need assistance please use the issue tracker by clicking issues above. 69 | 70 | ### Additional Plugins 71 | 72 | Check out my other plugins [here](https://plugins.octoprint.org/by_author/#jneilliii) 73 | 74 | ### Sponsors 75 | - Andreas Lindermayr 76 | - [@TheTuxKeeper](https://github.com/thetuxkeeper) 77 | - [@tideline3d](https://github.com/tideline3d/) 78 | - [SimplyPrint](https://simplyprint.io/) 79 | - [Andrew Beeman](https://github.com/Kiendeleo) 80 | - [Calanish](https://github.com/calanish) 81 | - [Lachlan Bell](https://lachy.io/) 82 | - [Jonny Bergdahl](https://github.com/bergdahl) 83 | ## Support My Efforts 84 | I, jneilliii, programmed this plugin for fun and do my best effort to support those that have issues with it, please return the favor and leave me a tip or become a Patron if you find this plugin helpful and want me to continue future development. 85 | 86 | [![Patreon](patreon-with-text-new.png)](https://www.patreon.com/jneilliii) [![paypal](paypal-with-text.png)](https://paypal.me/jneilliii) 87 | 88 | No paypal.me? Send funds via PayPal to jneilliii@gmail.com 89 | -------------------------------------------------------------------------------- /octoprint_mqttsubscribe/templates/mqttsubscribe_settings.jinja2: -------------------------------------------------------------------------------- 1 |
2 |

Topics

3 |
4 |
{{ _('Topic') }}
5 |
{{ _('Rest API') }}
6 |
{{ _('REST Parameters') }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 |
21 |

General

22 |
23 |
24 |
25 | API KEY 26 | 27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | ######################################################################################################################## 4 | ### Do not forget to adjust the following variables to your own plugin. 5 | 6 | # The plugin's identifier, has to be unique 7 | plugin_identifier = "mqttsubscribe" 8 | 9 | # The plugin's python package, should be "octoprint_", has to be unique 10 | plugin_package = "octoprint_mqttsubscribe" 11 | 12 | # The plugin's human readable name. Can be overwritten within OctoPrint's internal data via __plugin_name__ in the 13 | # plugin module 14 | plugin_name = "OctoPrint-MQTTSubscribe" 15 | 16 | # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module 17 | plugin_version = "0.1.8" 18 | 19 | # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin 20 | # module 21 | plugin_description = """Plugin to send MQTT command from button in navbar.""" 22 | 23 | # The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module 24 | plugin_author = "jneilliii" 25 | 26 | # The plugin's author's mail address. 27 | plugin_author_email = "jneilliii+github@gmail.com" 28 | 29 | # The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module 30 | plugin_url = "https://github.com/jneilliii/OctoPrint-MQTTSubscribe" 31 | 32 | # The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module 33 | plugin_license = "AGPLv3" 34 | 35 | # Any additional requirements besides OctoPrint should be listed here 36 | plugin_requires = ["jsonpath_rw"] 37 | 38 | ### -------------------------------------------------------------------------------------------------------------------- 39 | ### More advanced options that you usually shouldn't have to touch follow after this point 40 | ### -------------------------------------------------------------------------------------------------------------------- 41 | 42 | # Additional package data to install for this plugin. The subfolders "templates", "static" and "translations" will 43 | # already be installed automatically if they exist. Note that if you add something here you'll also need to update 44 | # MANIFEST.in to match to ensure that python setup.py sdist produces a source distribution that contains all your 45 | # files. This is sadly due to how python's setup.py works, see also http://stackoverflow.com/a/14159430/2028598 46 | plugin_additional_data = [] 47 | 48 | # Any additional python packages you need to install with your plugin that are not contained in .* 49 | plugin_additional_packages = [] 50 | 51 | # Any python packages within .* you do NOT want to install with your plugin 52 | plugin_ignored_packages = [] 53 | 54 | # Additional parameters for the call to setuptools.setup. If your plugin wants to register additional entry points, 55 | # define dependency links or other things like that, this is the place to go. Will be merged recursively with the 56 | # default setup parameters as provided by octoprint_setuptools.create_plugin_setup_parameters using 57 | # octoprint.util.dict_merge. 58 | # 59 | # Example: 60 | # plugin_requires = ["someDependency==dev"] 61 | # additional_setup_parameters = {"dependency_links": ["https://github.com/someUser/someRepo/archive/master.zip#egg=someDependency-dev"]} 62 | additional_setup_parameters = {} 63 | 64 | ######################################################################################################################## 65 | 66 | from setuptools import setup 67 | 68 | try: 69 | import octoprint_setuptools 70 | except: 71 | print("Could not import OctoPrint's setuptools, are you sure you are running that under " 72 | "the same python installation that OctoPrint is installed under?") 73 | import sys 74 | sys.exit(-1) 75 | 76 | setup_parameters = octoprint_setuptools.create_plugin_setup_parameters( 77 | identifier=plugin_identifier, 78 | package=plugin_package, 79 | name=plugin_name, 80 | version=plugin_version, 81 | description=plugin_description, 82 | author=plugin_author, 83 | mail=plugin_author_email, 84 | url=plugin_url, 85 | license=plugin_license, 86 | requires=plugin_requires, 87 | additional_packages=plugin_additional_packages, 88 | ignored_packages=plugin_ignored_packages, 89 | additional_data=plugin_additional_data 90 | ) 91 | 92 | if len(additional_setup_parameters): 93 | from octoprint.util import dict_merge 94 | setup_parameters = dict_merge(setup_parameters, additional_setup_parameters) 95 | 96 | setup(**setup_parameters) 97 | -------------------------------------------------------------------------------- /octoprint_mqttsubscribe/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | 4 | import octoprint.plugin 5 | import re 6 | import requests 7 | import json 8 | import jsonpath_rw 9 | 10 | 11 | class MQTTSubscribePlugin(octoprint.plugin.SettingsPlugin, 12 | octoprint.plugin.AssetPlugin, 13 | octoprint.plugin.TemplatePlugin, 14 | octoprint.plugin.StartupPlugin): 15 | 16 | def __init__(self): 17 | self.subscribed_topics = [] 18 | 19 | ##~~ SettingsPlugin mixin 20 | 21 | def get_settings_defaults(self): 22 | return dict( 23 | topics=[], 24 | api_key="" 25 | ) 26 | 27 | def get_settings_version(self): 28 | return 3 29 | 30 | def on_settings_migrate(self, target, current=None): 31 | topics_new = self._settings.get(['topics']) 32 | if current is None or current < 1: 33 | topics_new = self.get_settings_defaults()["topics"] 34 | if current == 1: 35 | for topic in topics_new: 36 | # Add new fields and remove unused 37 | if not topic.get("extract", False): 38 | topic["extract"] = "" 39 | if not topic.get("rest", False): 40 | topic["rest"] = "/api/" + topic["topic"] 41 | if not topic.get("command", False): 42 | topic["command"] = topic["subscribecommand"] 43 | topic.pop("subscribecommand", None) 44 | if not topic.get("disable_popup", False): 45 | topic["disable_popup"] = False 46 | if topic.get("idx", False): 47 | topic.pop("idx", None) 48 | 49 | # Update topic to remain functional 50 | topic["topic"] = "octoprint/plugins/mqttsubscribe/" + topic["topic"] 51 | if current == 2: 52 | for topic in topics_new: 53 | if not topic.get("disable_popup", False): 54 | topic["disable_popup"] = False 55 | 56 | self._settings.set(["topics"], topics_new) 57 | 58 | def on_settings_save(self, data): 59 | octoprint.plugin.SettingsPlugin.on_settings_save(self, data) 60 | 61 | try: 62 | to_unsubscribe = list(self.subscribed_topics) 63 | for topic in self._settings.get(["topics"]): 64 | if topic["topic"] in self.subscribed_topics: 65 | if topic["topic"] in to_unsubscribe: 66 | to_unsubscribe.remove(topic["topic"]) 67 | else: 68 | self.subscribed_topics.append(topic["topic"]) 69 | self._logger.debug('Subscribing to ' + topic["topic"]) 70 | self.mqtt_subscribe(topic["topic"], self._on_mqtt_subscription) 71 | # Unsubscribe previously subscribed topics that are no longer listed 72 | for topic in to_unsubscribe: 73 | self._logger.debug('Unsubscribing from %s' % topic) 74 | self.mqtt_unsubscribe(self._on_mqtt_subscription, topic) 75 | except Exception as e: 76 | self._logger.debug("Exception: %s" % e) 77 | 78 | ##~~ StartupPlugin mixin 79 | 80 | def on_startup(self, host, port): 81 | self.port = port 82 | 83 | def on_after_startup(self): 84 | helpers = self._plugin_manager.get_helpers("mqtt", "mqtt_publish", "mqtt_subscribe", "mqtt_unsubscribe") 85 | if helpers: 86 | if "mqtt_publish" in helpers: 87 | self.mqtt_publish = helpers["mqtt_publish"] 88 | if "mqtt_subscribe" in helpers: 89 | self.mqtt_subscribe = helpers["mqtt_subscribe"] 90 | for topic in self._settings.get(["topics"]): 91 | if topic.get("topic", "") != "" and topic["topic"] not in self.subscribed_topics: 92 | self.subscribed_topics.append(topic["topic"]) 93 | self._logger.debug('Subscribing to ' + topic["topic"]) 94 | self.mqtt_subscribe(topic["topic"], self._on_mqtt_subscription) 95 | if "mqtt_unsubscribe" in helpers: 96 | self.mqtt_unsubscribe = helpers["mqtt_unsubscribe"] 97 | 98 | try: 99 | self.mqtt_publish("{}plugins/mqttsubscribe/debug".format(self._settings.global_get(["plugins", "mqtt", "publish", "baseTopic"])), "OctoPrint-MQTTSubscribe monitoring.") 100 | except Exception as e: 101 | self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) 102 | 103 | def _substitute(self, s, matches): 104 | s = s.replace("{", "{{").replace("}", "}}") 105 | regex_double_bracket = re.compile("{{([\d]*)}}", re.MULTILINE) 106 | s = regex_double_bracket.sub(r"{\1}", s) 107 | return s.format(*matches) 108 | 109 | def _on_mqtt_subscription(self, topic, message, retained=None, qos=None, *args, **kwargs): 110 | self._logger.debug("Received from %s|%s" % (topic, message)) 111 | 112 | for t in self._settings.get(["topics"]): 113 | if topic == t["topic"]: 114 | self._logger.debug("Found match " + t["topic"]) 115 | try: 116 | address = "localhost" 117 | port = self.port 118 | headers = {'Content-type': 'application/json', 'X-Api-Key': self._settings.get(["api_key"])} 119 | # parse message extraction expression 120 | extract = t["extract"] 121 | expr = jsonpath_rw.parse(extract if extract else '$') 122 | # extract data from message 123 | if octoprint.util.to_native_str(message).startswith("{"): 124 | args = [match.value for match in expr.find(json.loads(message))] 125 | else: 126 | args = [json.dumps(octoprint.util.to_native_str(message))] 127 | # substitute matches in command 128 | data = self._substitute(t["command"], args) 129 | # substitute matches in REST API 130 | if t["rest"].startswith("/"): 131 | url = self._substitute("http://%s:%s%s" % (address, port, t["rest"]), args) 132 | else: 133 | url = self._substitute("http://%s:%s/%s" % (address, port, t["rest"]), args) 134 | if t["type"] == "post": 135 | r = requests.post(url, data=data, headers=headers) 136 | self.mqtt_publish(t["topic"] + "/response", '{ "status" : %s, "response" : %s, "data" : %s }' % ( 137 | r.status_code, r.text, data)) 138 | if not t.get("disable_popup", False): 139 | self._plugin_manager.send_plugin_message(self._identifier, 140 | dict(topic=t["topic"], message=message, 141 | command="Status code: %s" % r.status_code)) 142 | if t["type"] == "get": 143 | r = requests.get(url, headers=headers) 144 | self.mqtt_publish(t["topic"] + "/response", 145 | '{ "status" : %s, "response" : %s }' % (r.status_code, r.text)) 146 | if not t.get("disable_popup", False): 147 | self._plugin_manager.send_plugin_message(self._identifier, 148 | dict(topic=t["topic"], message=message, 149 | command="Response: %s" % r.text)) 150 | 151 | except Exception as e: 152 | self._logger.error(e) 153 | self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) 154 | 155 | ##~~ AssetPlugin mixin 156 | 157 | def get_assets(self): 158 | return dict( 159 | js=["js/jquery-ui.min.js", "js/knockout-sortable.1.2.0.js", "js/mqttsubscribe.js"] 160 | ) 161 | 162 | ##~~ TemplatePlugin mixin 163 | 164 | def get_template_configs(self): 165 | return [ 166 | dict(type="settings", custom_bindings=True) 167 | ] 168 | 169 | ##~~ Softwareupdate hook 170 | 171 | def get_update_information(self): 172 | return dict( 173 | mqttsubscribe=dict( 174 | displayName="MQTT Subscribe", 175 | displayVersion=self._plugin_version, 176 | 177 | # version check: github repository 178 | type="github_release", 179 | user="jneilliii", 180 | repo="OctoPrint-MQTTSubscribe", 181 | current=self._plugin_version, 182 | stable_branch=dict( 183 | name="Stable", branch="master", comittish=["master"] 184 | ), 185 | prerelease_branches=[ 186 | dict( 187 | name="Release Candidate", 188 | branch="rc", 189 | comittish=["rc", "master"], 190 | ) 191 | ], 192 | 193 | # update method: pip 194 | pip="https://github.com/jneilliii/OctoPrint-MQTTSubscribe/archive/{target_version}.zip" 195 | ) 196 | ) 197 | 198 | 199 | __plugin_name__ = "MQTT Subscribe" 200 | __plugin_pythoncompat__ = ">=2.7,<4" 201 | 202 | 203 | def __plugin_load__(): 204 | global __plugin_implementation__ 205 | __plugin_implementation__ = MQTTSubscribePlugin() 206 | 207 | global __plugin_hooks__ 208 | __plugin_hooks__ = { 209 | "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information 210 | } 211 | -------------------------------------------------------------------------------- /octoprint_mqttsubscribe/static/js/knockout-sortable.js: -------------------------------------------------------------------------------- 1 | ;(function(factory) { 2 | if (typeof define === "function" && define.amd) { 3 | // AMD anonymous module 4 | define(["knockout", "jquery", "jquery-ui/ui/widgets/sortable", "jquery-ui/ui/widgets/draggable", "jquery-ui/ui/widgets/droppable"], factory); 5 | } else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { 6 | // CommonJS module 7 | var ko = require("knockout"), 8 | jQuery = require("jquery"); 9 | require("jquery-ui/ui/widgets/sortable"); 10 | require("jquery-ui/ui/widgets/draggable"); 11 | require("jquery-ui/ui/widgets/droppable"); 12 | factory(ko, jQuery); 13 | } else { 14 | // No module loader (plain