Merge remote-tracking branch 'upstream/edge' into upstream-master
[clinton/Smoothieware.git] / src / modules / tools / temperatureswitch / TemperatureSwitch.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 /*
9 TemperatureSwitch is an optional module that will automatically turn on or off a switch
10 based on a setpoint temperature. It is commonly used to turn on/off a cooling fan or water pump
11 to cool the hot end's cold zone. Specifically, it turns one of the small MOSFETs on or off.
12
13 Author: Michael Hackney, mhackney@eclecticangler.com
14 */
15
16 #include "TemperatureSwitch.h"
17 #include "libs/Module.h"
18 #include "libs/Kernel.h"
19 #include "modules/tools/temperaturecontrol/TemperatureControlPublicAccess.h"
20 #include "SwitchPublicAccess.h"
21
22 #include "utils.h"
23 #include "Gcode.h"
24 #include "Config.h"
25 #include "ConfigValue.h"
26 #include "checksumm.h"
27 #include "PublicData.h"
28 #include "StreamOutputPool.h"
29 #include "TemperatureControlPool.h"
30 #include "mri.h"
31
32 #define temperatureswitch_checksum CHECKSUM("temperatureswitch")
33 #define enable_checksum CHECKSUM("enable")
34 #define temperatureswitch_hotend_checksum CHECKSUM("hotend")
35 #define temperatureswitch_threshold_temp_checksum CHECKSUM("threshold_temp")
36 #define temperatureswitch_type_checksum CHECKSUM("type")
37 #define temperatureswitch_switch_checksum CHECKSUM("switch")
38 #define temperatureswitch_heatup_poll_checksum CHECKSUM("heatup_poll")
39 #define temperatureswitch_cooldown_poll_checksum CHECKSUM("cooldown_poll")
40 #define temperatureswitch_trigger_checksum CHECKSUM("trigger")
41 #define temperatureswitch_inverted_checksum CHECKSUM("inverted")
42 #define temperatureswitch_arm_command_checksum CHECKSUM("arm_mcode")
43 #define designator_checksum CHECKSUM("designator")
44
45 TemperatureSwitch::TemperatureSwitch()
46 {
47 }
48
49 TemperatureSwitch::~TemperatureSwitch()
50 {
51 THEKERNEL->unregister_for_event(ON_SECOND_TICK, this);
52 THEKERNEL->unregister_for_event(ON_GCODE_RECEIVED, this);
53 }
54
55 // Load module
56 void TemperatureSwitch::on_module_loaded()
57 {
58 vector<uint16_t> modulist;
59 // allow for multiple temperature switches
60 THEKERNEL->config->get_module_list(&modulist, temperatureswitch_checksum);
61 for (auto m : modulist) {
62 load_config(m);
63 }
64
65 // no longer need this instance as it is just used to load the other instances
66 delete this;
67 }
68
69 TemperatureSwitch* TemperatureSwitch::load_config(uint16_t modcs)
70 {
71 // see if enabled
72 if (!THEKERNEL->config->value(temperatureswitch_checksum, modcs, enable_checksum)->by_default(false)->as_bool()) {
73 return nullptr;
74 }
75
76 // create a temperature control and load settings
77 char designator= 0;
78 string s= THEKERNEL->config->value(temperatureswitch_checksum, modcs, designator_checksum)->by_default("")->as_string();
79 if(s.empty()){
80 // for backward compatibility temperatureswitch.hotend will need designator 'T' by default @DEPRECATED
81 if(modcs == temperatureswitch_hotend_checksum) designator= 'T';
82
83 }else{
84 designator= s[0];
85 }
86
87 if(designator == 0) return nullptr; // no designator then not valid
88
89 // load settings from config file
90 string switchname = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_switch_checksum)->by_default("")->as_string();
91 if(switchname.empty()) {
92 // handle old configs where this was called type @DEPRECATED
93 switchname = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_type_checksum)->by_default("")->as_string();
94 if(switchname.empty()) {
95 // no switch specified so invalid entry
96 THEKERNEL->streams->printf("WARNING TEMPERATURESWITCH: no switch specified\n");
97 return nullptr;
98 }
99 }
100
101 // create a new temperature switch module
102 TemperatureSwitch *ts= new TemperatureSwitch();
103
104 // save designator
105 ts->designator= designator;
106
107 // if we should turn the switch on or off when trigger is hit
108 ts->inverted = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_inverted_checksum)->by_default(false)->as_bool();
109
110 // if we should trigger when above and below, or when rising through, or when falling through the specified temp
111 string trig = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_trigger_checksum)->by_default("level")->as_string();
112 if(trig == "level") ts->trigger= LEVEL;
113 else if(trig == "rising") ts->trigger= RISING;
114 else if(trig == "falling") ts->trigger= FALLING;
115 else ts->trigger= LEVEL;
116
117 // the mcode used to arm the switch
118 ts->arm_mcode = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_arm_command_checksum)->by_default(0)->as_number();
119
120 ts->temperatureswitch_switch_cs= get_checksum(switchname); // checksum of the switch to use
121
122 ts->temperatureswitch_threshold_temp = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_threshold_temp_checksum)->by_default(50.0f)->as_number();
123
124 // these are to tune the heatup and cooldown polling frequencies
125 ts->temperatureswitch_heatup_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_heatup_poll_checksum)->by_default(15)->as_number();
126 ts->temperatureswitch_cooldown_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_cooldown_poll_checksum)->by_default(60)->as_number();
127 ts->current_delay = ts->temperatureswitch_heatup_poll;
128
129 // set initial state
130 ts->current_state= NONE;
131 ts->second_counter = ts->current_delay; // do test immediately on first second_tick
132 // if not defined then always armed, otherwise start out disarmed
133 ts->armed= (ts->arm_mcode == 0);
134
135 // Register for events
136 ts->register_for_event(ON_SECOND_TICK);
137
138 if(ts->arm_mcode != 0) {
139 ts->register_for_event(ON_GCODE_RECEIVED);
140 }
141 return ts;
142 }
143
144 void TemperatureSwitch::on_gcode_received(void *argument)
145 {
146 Gcode *gcode = static_cast<Gcode *>(argument);
147 if(gcode->has_m && gcode->m == this->arm_mcode) {
148 this->armed= (gcode->has_letter('S') && gcode->get_value('S') != 0);
149 gcode->stream->printf("temperature switch %s\n", this->armed ? "armed" : "disarmed");
150 }
151 }
152
153 // Called once a second but we only need to service on the cooldown and heatup poll intervals
154 void TemperatureSwitch::on_second_tick(void *argument)
155 {
156 second_counter++;
157 if (second_counter < current_delay) return;
158
159 second_counter = 0;
160 float current_temp = this->get_highest_temperature();
161
162 if (current_temp >= this->temperatureswitch_threshold_temp) {
163 set_state(HIGH_TEMP);
164
165 } else {
166 set_state(LOW_TEMP);
167 }
168 }
169
170 void TemperatureSwitch::set_state(STATE state)
171 {
172 if(state == this->current_state) return; // state did not change
173
174 // state has changed
175 switch(this->trigger) {
176 case LEVEL:
177 // switch on or off depending on HIGH or LOW
178 set_switch(state == HIGH_TEMP);
179 break;
180
181 case RISING:
182 // switch on if rising edge
183 if(this->current_state == LOW_TEMP && state == HIGH_TEMP) set_switch(true);
184 break;
185
186 case FALLING:
187 // switch off if falling edge
188 if(this->current_state == HIGH_TEMP && state == LOW_TEMP) set_switch(false);
189 break;
190 }
191
192 this->current_delay = state == HIGH_TEMP ? this->temperatureswitch_cooldown_poll : this->temperatureswitch_heatup_poll;
193 this->current_state= state;
194 }
195
196 // Get the highest temperature from the set of temperature controllers
197 float TemperatureSwitch::get_highest_temperature()
198 {
199 float high_temp = 0.0;
200
201 std::vector<struct pad_temperature> controllers;
202 bool ok = PublicData::get_value(temperature_control_checksum, poll_controls_checksum, &controllers);
203 if (ok) {
204 for (auto &c : controllers) {
205 // check if this controller's temp is the highest and save it if so
206 if (c.designator[0] == this->designator && c.current_temperature > high_temp) {
207 high_temp = c.current_temperature;
208 }
209 }
210 }
211
212 return high_temp;
213 }
214
215 // Turn the switch on (true) or off (false)
216 void TemperatureSwitch::set_switch(bool switch_state)
217 {
218 if(!this->armed) return; // do not actually switch anything if not armed
219
220 if(this->arm_mcode != 0 && this->trigger != LEVEL) {
221 // if edge triggered we only trigger once per arming, if level triggered we switch as long as we are armed
222 this->armed= false;
223 }
224
225 if(this->inverted) switch_state= !switch_state; // turn switch on or off inverted
226
227 // get current switch state
228 struct pad_switch pad;
229 bool ok = PublicData::get_value(switch_checksum, this->temperatureswitch_switch_cs, 0, &pad);
230 if (!ok) {
231 THEKERNEL->streams->printf("// Failed to get switch state.\r\n");
232 return;
233 }
234
235 if(pad.state == switch_state) return; // switch is already in the requested state
236
237 ok = PublicData::set_value(switch_checksum, this->temperatureswitch_switch_cs, state_checksum, &switch_state);
238 if (!ok) {
239 THEKERNEL->streams->printf("// Failed changing switch state.\r\n");
240 }
241 }