본문 바로가기

3D Printer/OctoPrint

OctoPrint Plugin 만들기 #3 (Blueprintplugin 추가)

옥토 프린트 플러그인 만들기 (챔버 매니저)



□ Blueprintplugin 추가


octoprint.plugin.BlueprintPlugin 활용



※ 각 버튼을 클릭하면 정해진 시리얼포트로 명령어 전송 기능 추가


1. UI



2. Hyper V에 설치된 옥토프린트 플러그인에서 시리얼로 데이터를 전송 결과





3. Source Code


[chambermanager.js]



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
$(function() {
    function ChamberManagerViewModel(parameters) {
        var self = this;
        
        self.settings = parameters[0];
        
        self.pluginName = "chambermanager";
        
        self.disableControl = ko.observable(false);
        
        self.cm_serial_port = ko.observable(undefined);
        self.cm_serial_baudrate = ko.observable(undefined);
        self.cm_serial_portOptions = ko.observable(undefined);
        self.cm_serial_baudrateOptions = ko.observable(undefined);
        
        self.tempControlOptions = ko.observableArray(['Manual','ON/OFF','PID']);
        self.tempControlMode = ko.observable('ON/OFF');
        self.tempControlTarget = ko.observable(26);
        self.tempControlTargetUpdate = ko.observable(26);
        self.tempControlActual = ko.observable(25.5);
        
        self.doorVentilMaxPos = ko.observable(undefined);
        self.doorVentilMinPos = ko.observable(undefined);
        self.doorFilterMaxPos = ko.observable(undefined);
        self.doorFilterMinPos = ko.observable(undefined);
        
        self.doorControlOptions = ko.observableArray(['Manual','Auto']);
        self.doorControlMode = ko.observable('Auto');
        self.doorControlAngle = ko.observable('undefined');
        self.doorControlVentilAngle = ko.observable(undefined);
        self.doorControlFilterAngle = ko.observable(undefined);
        self.doorControlPosOptions = ko.observableArray(['CLOSE','UNKNOWN','OPEN']);
        self.doorControlPosValue = ko.observable('UNKNOWN');
 
        self.fanControlOptions = ko.observableArray(['Manual','Auto']);
        self.fanControlMode = ko.observable('Auto');
        self.fanControlVentilSpeed = ko.observable(100);
        self.fanControlFilterSpeed = ko.observable(100);
        
        self.lightControlOptions = ko.observableArray(['Off','On','Auto']);
        self.lightControlMode = ko.observable('Auto');
        self.lightControlSensitive = ko.observable(undefined);
 
    
        self.changeControlMode = function(mode) {
            try {
                self.tempControlMode(mode);
                self.settings.settings.plugins.chambermanager.tempControlMode(mode);
                
                if(mode === 'Manual') {
                    self.disableControl(false);
                }
                else {
                    self.disableControl(true);
                }
                self.settings.saveData();
                self.setControlValue('TempControlMode', mode);
            } catch(ex) {
                alert(ex);
            }
        }
        
        self.tempControlDecrementTarget = function() {
            var value = self.tempControlTarget();
 
            try {
                value = parseInt(value);
                if (value <= 0return;
                self.tempControlTarget(value - 1);
            } catch (ex) {
                alert(ex);
            }
        }
        
        self.tempControlIncrementTarget = function() {
            var value = self.tempControlTarget();
            
            try {
                value = parseInt(value);
                if (value >= 999return;
                self.tempControlTarget(value + 1);
            } catch (ex) {
                alert(ex);
            }
        }
        
        self.setTempControlTarget = function() {
            self.setControlValue('TempTarget', self.tempControlTarget());
        }
        
        self.setDoorControlAngle = function() {
            self.settings.settings.plugins.chambermanager.doorControlAngle(self.doorControlAngle());
            self.settings.saveData();
            
            self.setControlValue('DoorAngle', self.doorControlAngle());
        }
        
        self.setDoorControlPosition = function(mode) {
            try {
                if(mode === 'OPEN') {
                    self.doorControlAngle(90);
                }
                if(mode === 'UNKNOWN') {
                    self.doorControlAngle(45);
                }
                if(mode === 'CLOSE') {
                    self.doorControlAngle(0);
                }
                
                self.doorControlPosValue(mode);
                self.settings.settings.plugins.chambermanager.doorControlPosValue(mode);
                self.settings.settings.plugins.chambermanager.doorControlAngle(self.doorControlAngle());
                self.settings.saveData();
                
                self.setControlValue('DoorAngle', self.doorControlAngle());
            } catch(ex) {
                alert(ex);
            }
        }        
        
        self.setVentilFanSpeed = function() {
            self.settings.settings.plugins.chambermanager.fanControlVentilSpeed(self.fanControlVentilSpeed());
            self.settings.saveData();            
            
            self.setControlValue('VentilFanSpeed', self.fanControlVentilSpeed());
        }
        
        self.setFilterFanSpeed = function() {
            self.settings.settings.plugins.chambermanager.fanControlFilterSpeed(self.fanControlFilterSpeed());
            self.settings.saveData();
 
            self.setControlValue('FilterFanSpeed', self.fanControlFilterSpeed());
        }
        
        self.setLightControlMode = function(mode) {
            self.lightControlMode(mode);
            self.settings.settings.plugins.chambermanager.lightControlMode(mode);
            
            if(mode === 'Manual') {
                self.disableControl(false);
            }
            else {
                self.disableControl(true);
            }
            self.setControlValue('LightControlMode', mode);
            
            self.settings.saveData();
        }
        
 
        // This will get called before the ChamberManagerViewModel gets bound to the DOM, but after its
        // dependencies have already been initialized. It is especially guaranteed that this method
        // gets called _after_ the settings have been retrieved from the OctoPrint backend and thus
        // the SettingsViewModel been properly populated.
        self.onBeforeBinding = function() {
            self.bindFromSettings();
        }
        
        self.onSettingsBeforeSave = function() {
            self.bindFromSettings();
        }
        
        
        self.bindFromSettings = function () {
            
            try {
                self.cm_serial_port(self.settings.settings.plugins.chambermanager.serial_port());
                self.cm_serial_baudrate(self.settings.settings.plugins.chambermanager.serial_baudrate());
            
                self.tempControlMode(self.settings.settings.plugins.chambermanager.tempControlMode());
                self.tempControlTarget(self.settings.settings.plugins.chambermanager.tempControlTarget());
                
                self.doorVentilMaxPos(self.settings.settings.plugins.chambermanager.doorVentilMaxPos());
                self.doorVentilMinPos(self.settings.settings.plugins.chambermanager.doorVentilMinPos());
                self.doorFilterMaxPos(self.settings.settings.plugins.chambermanager.doorFilterMaxPos());
                self.doorFilterMinPos(self.settings.settings.plugins.chambermanager.doorFilterMinPos());
 
                self.doorControlMode(self.settings.settings.plugins.chambermanager.doorControlMode());
                self.doorControlAngle(self.settings.settings.plugins.chambermanager.doorControlAngle());
                self.doorControlPosValue(self.settings.settings.plugins.chambermanager.doorControlPosValue());
 
                self.fanControlMode(self.settings.settings.plugins.chambermanager.fanControlMode());
                self.fanControlVentilSpeed(self.settings.settings.plugins.chambermanager.fanControlVentilSpeed());
                self.fanControlFilterSpeed(self.settings.settings.plugins.chambermanager.fanControlFilterSpeed());
                
                self.lightControlMode(self.settings.settings.plugins.chambermanager.lightControlMode());
                self.lightControlSensitive(self.settings.settings.plugins.chambermanager.lightControlSensitive());
                
                if(self.tempControlMode() != 'Manual') {
                    self.disableControl(true);
                }
                else {
                    self.disableControl(false);
                }
            
            } catch(ex) {
                alert(ex);
            }
            
        }
        
        self.setControlValue = function(object, value) {
            var request = {object, value};
 
            $.ajax({
                url: self.buildPluginUrl("/setControlValues"),
                type: "GET",
                dataType: "json",
                data: request,
                success: function (data) {
                    var ospData = data
                },
                error: function (textStatus, errorThrown) {
                    new PNotify({
                      title: "Chamber Manager",
                      text: jqXHR,
                      type: "error"
                    });
                }
            });
        }
 
        self.getControlValue = function(object) {
            var request = {object};
 
            $.ajax({
                url: self.buildPluginUrl("/getControlValues"),
                type: "GET",
                dataType: "json",
                data: request,
                success: function (data) {         
 
                },
                error: function (textStatus, errorThrown) {
                    new PNotify({
                      title: "Chamber Manager",
                      text: textStatus,
                      type: "error"
                    });
                }
            });
        }
 
        self.buildPluginUrl = function (path) {
          return window.PLUGIN_BASEURL + self.pluginName + path;
        };        
        
    }
 
    // This is how our plugin registers itself with the application, by adding some configuration
    // information to the global variable OCTOPRINT_VIEWMODELS
    OCTOPRINT_VIEWMODELS.push({
        construct: ChamberManagerViewModel,
        dependencies: ["settingsViewModel"],
        elements: ["#settings_plugin_chambermanager""#tab_plugin_chambermanager"]
    });
});
cs




[ chambermanager._tab.jinja2 ]


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<div class="control-group">
    <h4>{{ _('Temperature') }}</h4>
    <table>
        <tr>
            <td width="200" valign="center">
                <label class="control-label" for="tempControlMode">{{ _('Control Mode') }}</label>
            </td>
            <td width="400">
                <div class="tempControlOptions">
                    <div class="btn-group" data-toggle="buttons-radio" id="tempControlMode">
                        <!-- ko foreach: tempControlOptions -->
                        <button type="button" class="btn distance" style="width: 130px;padding: 3px 0;height: 30px" data-bind="text: $data, click: function() { $root.changeControlMode($data) }, css: { active: $root.tempControlMode() === $data }, attr: { id: 'tempControlMode_' + $data }"></button>
                        <!-- /ko -->
                    </div>
                </div>
            </td>
        </tr>
        <tr>
            <td width="200"><label class="control-label" for="settings-tempControlTarget">{{ _('Ambient') }}</label></td>
            <td width="400">
                <table><tr>
                    <td width="120" class="temperature_actual" data-bind="html: 'Actual : ' + formatTemperature(tempControlActual()), attr: {title: formatTemperature(tempControlActual())}"></td>
                    </td>
                    <td class="temperature_target">
                        <div class="input-prepend input-append">
                            <button type="button" class="btn btn-input-dec" data-bind="attr : {'disabled' : $root.disableControl}, click: $root.tempControlDecrementTarget" title="{{ _('Fine adjust: -1°C') }}"><i class="fa fa-minus"></i></button>
                            <input type="number" min="0" max="999" class="input-mini input-nospin" style="width: 30px" data-bind="attr : {'disabled' : $root.disableControl}, value: tempControlTarget(), valueUpdate: 'input'">
                            <span class="add-on">&deg;C</span>
                            <button type="button" class="btn btn-input-inc" data-bind="attr : {'disabled' : $root.disableControl}, click: $root.tempControlIncrementTarget" title="{{ _('Fine adjust: +1°C') }}"><i class="fa fa-plus"></i></button>
                            <button type="submit" class="btn btn-primary" data-bind="attr : {'disabled' : $root.disableControl}, click: $root.setTempControlTarget" title="{{ _('Set') }}"><i class="fa fa-check"></i></button>
                        </div>
                    </td>
                </tr></table>
            </td>
        </tr>
    </table>
 
 
    
    
</div>
 
<br>
<div class="control-group">
    <h4>{{ _('Doors') }}</h4>
    <table>
        <tr>
            <td width="200" valign="center"><label class="control-label" for="settings-doorControlAngle">{{ _('Angle') }}</label></td>
            <td width="400" align="center">
                <div id="control-door-angle" class="jog-panel">
                    <input type="number" style="width: 350px" data-bind="slider: {min: 0, max: 90, step: 1, value: doorControlAngle, tooltip: 'hide', disabled: $root.disableControl}">
                    <button class="btn btn-block" style="width: 390px" data-bind="attr : {'disabled' : $root.disableControl}, click: function() { $root.setDoorControlAngle() }"><span data-bind="text: doorControlAngle()"></span></button>
                </div>
            </td>
        </tr>
        <tr>
            <td><label class="control-label" for="settings-doorControlPosition">{{ _('Position') }}</label></td>
            <td align="center">
            
                <div class="doorControlPosOptions">
                    <div class="btn-group" data-toggle="buttons-radio" id="doorControlModePosition">
                        <!-- ko foreach: doorControlPosOptions -->
                        <button type="button" class="btn distance" style="width: 130px;padding: 3px 0;height: 30px" data-bind="text: $data, click: function() { $root.setDoorControlPosition($data) }, css: { active: $root.doorControlPosValue() === $data }, attr: { id: 'doorControlModePosition_' + $data, 'disabled' : $root.disableControl}"></button>
                        <!-- /ko -->
                    </div>
                </div>
            
            </td>
        </tr>
    </table>
</div>
 
<br>
<div class="control-group">
    <h4>{{ _('Fans') }}</h4>
    <table>
        <tr>
            <th width="200"> </th>
            <th width="200"><h4>Ventilator</h4></th>
            <th width="200"><h4>Filter</h4></th>
        </tr>
    </table>
    <table>
        <tr>
            <td width="200" valign="center"><label class="control-label" for="settings-fansControlSpeed">{{ _('Speed') }}</label></td>
            <td width="200" align="center">
                <div id="control-fan-ventil-speed" class="jog-panel">
                    <input type="number" style="width: 153px" data-bind="slider: {min: 0, max: 100, step: 1, value: fanControlVentilSpeed, tooltip: 'hide'}">
                    <button class="btn btn-block" style="width: 169px" data-bind="attr : {'disabled' : $root.disableControl}, click: function() { $root.setVentilFanSpeed() }"><span data-bind="text: fanControlVentilSpeed() + '%'"></span></button>
                </div>
            </td>
            <td width="200" align="center">
                <div id="control-fan-filter-speed" class="jog-panel">
                    <input type="number" style="width: 153px" data-bind="slider: {min: 0, max: 100, step: 1, value: fanControlFilterSpeed, tooltip: 'hide'}">
                    <button class="btn btn-block" style="width: 169px" data-bind="attr : {'disabled' : $root.disableControl}, click: function() { $root.setFilterFanSpeed() }"><span data-bind="text: fanControlFilterSpeed() + '%'"></span></button>
                </div>
            </td>
        </tr>
    </table>
</div>
 
    
<br>
<div class="control-group">
    <h4>{{ _('Lights') }}</h4>
    <table>
        <tr>
            <td width="200">
                <label class="control-label" for="lightControlMode">{{ _('Control Mode') }}</label>
            </td>
            <td width="400">
                <div class="tempControlOptions">
                    <div class="btn-group" data-toggle="buttons-radio" id="lightControlMode">
                        <!-- ko foreach: lightControlOptions -->
                        <button type="button" class="btn distance" style="width: 130px;padding: 3px 0;height: 30px" data-bind="text: $data, click: function() { $root.setLightControlMode($data) }, css: { active: $root.lightControlMode() === $data }, attr: { id: 'lightControlMode_' + $data }"></button>
                        <!-- /ko -->
                    </div>
                </div>
            </td>
        </tr>
    </table>
</div>
 
 
cs



[__init__.py]


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# coding=utf-8
from __future__ import absolute_import
 
from octoprint.events import eventManager, Events
from octoprint.util import RepeatedTimer
from serial import SerialException
 
import octoprint.plugin
import flask
import serial
import json
 
 
class ChamberManagerPlugin(octoprint.plugin.StartupPlugin,
                      octoprint.plugin.TemplatePlugin,
                      octoprint.plugin.SettingsPlugin,
                      octoprint.plugin.BlueprintPlugin,
                      octoprint.plugin.AssetPlugin):
 
 
    device = serial.Serial()
    serial_Connected = False
                      
    def on_after_startup(self):
        try:
            port = self._settings.get(["serial_port"])
            baudrate = self._settings.get(["serial_baudrate"])
            timeout = self._settings.get(["serial_timeout"])
 
            self.device.port = port
            self.device.baudrate = baudrate
            self.device.timeout = timeout
            self.device.open()
            self.device.flush()
            
            self._logger.info("Connected to device. [port="+port+" baudrate="+str(baudrate)+"]")
            
        except SerialException as e:
            self._logger.error("Failed to open serial port")
            
    
 
    def get_settings_defaults(self):
        return dict(url="https://en.wikipedia.org/wiki/Hello_world")
 
    def get_template_configs(self):
        return [
            dict(type="navbar", custom_bindings=False),
            dict(type="settings", custom_bindings=False)
        ]
 
    def get_assets(self):
        return dict(
            js=["js/chambermanager.js"],
            css=["css/chambermanager.css"],
            less=["less/chambermanager.less"]
        )
        
        
    TempControlModeOptionsStringToCode = {
        'Manual''0',
        'ON/OFF''1',
        'PID''2'
    }
 
    TempControlModeOptionsCodeToString = {
        '0''Manual',
        '1''ON/OFF',
        '2''PID'
    }
 
    LightControlModeOptionsStringToCode = {
        'Off''0',
        'On''1',
        'Auto''2'
    }
    
    LightControlModeOptionsCodeToString = {
        '0''Off',
        '1''On',
        '2''Auto'
    }    
        
    ControlObjectToID = {
        'VentilDoorPosMax'        : '111',
        'VentilDoorPosMin'        : '112',
        'FilterDoorPosMax'        : '113',
        'FilterDoorPosMin'        : '114',
        'WriteEEPROM'            : '100',
        'TempControlMode'        : '200',
        'TempTarget'            : '201',
        'TempCurrent'            : '202',
        'FanEnable'                : '300',
        'DoorAngle'                : '310',
        'DoorFanSpeed'            : '311',
        'VentilServoAngle'        : '321',
        'VentilFanSpeed'        : '322',
        'FilterServoAngle'        : '331',
        'FilterFanSpeed'        : '332',
        'LightControlMode'        : '400'
    }
 
    # ~~ Blueprintplugin mixin
    @octoprint.plugin.BlueprintPlugin.route("/setControlValues", methods=["GET"])
    def setControlValues(self):
        try:
            set_object = flask.request.values["object"]
            set_value = flask.request.values["value"]
            
            self._logger.info('Object: '+set_object+' Value: '+set_value);
            
            if set_object == 'TempControlMode' :
                set_value = self.TempControlModeOptionsStringToCode[set_value]
            if set_object == 'LightControlMode' :
                set_value = self.LightControlModeOptionsStringToCode[set_value]
            id = self.ControlObjectToID[set_object]
 
            # Serial data send
            msg = "CMD O"+id+" W"+set_value+'\n'
            self.device.writelines(msg.encode('ascii','ignore'));
            ret = self.device.read_until('\r');
            if ret.startswith('OK')==True :
                return flask.jsonify(success=True)
        except SerialException as e:
            self._logger.error(e)
        except Exception as e:
            self._logger.error(e)
            
#        return json.dumps(set_object), 200, 'application/json'
        return flask.jsonify(error=set_object)
        
    @octoprint.plugin.BlueprintPlugin.route("/getControlValues", methods=["GET"])
    def getControlValues(self):
        get_object = flast.request.values["object"]
        id = ControlObjectToID(get_object)
        
        # Serial data send
        try:
            self.serial.writelines("ENC O"+id+" R0")
            r = self.serial.readline()
            if r.startswith("OK") :
                # Return value ex: OK 1
                tok = r.split(' ')
                if id == '200':
                    tok[1= TempControlModeOptionsCodeToString(tok[1])
                return flask.Response(json.dumps(TempControlModeOptionsCodeToString(tok[1])), mimetype='application/json')
 
        except SerialException as e:
            raise self._logger.error("Failed to write to serial port")
    
        return flask.jsonify(success=False)        
 
 
 
 
__plugin_name__ = "Chamber Manager"
__plugin_implementation__ = ChamberManagerPlugin()
cs