Allow fractional ms to be specified for HW PWM in switch period setting. still defaul...
[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
31 #define temperatureswitch_checksum CHECKSUM("temperatureswitch")
32 #define enable_checksum CHECKSUM("enable")
33 #define temperatureswitch_hotend_checksum CHECKSUM("hotend")
34 #define temperatureswitch_threshold_temp_checksum CHECKSUM("threshold_temp")
35 #define temperatureswitch_type_checksum CHECKSUM("type")
36 #define temperatureswitch_switch_checksum CHECKSUM("switch")
37 #define temperatureswitch_heatup_poll_checksum CHECKSUM("heatup_poll")
38 #define temperatureswitch_cooldown_poll_checksum CHECKSUM("cooldown_poll")
39 #define temperatureswitch_trigger_checksum CHECKSUM("trigger")
40 #define temperatureswitch_inverted_checksum CHECKSUM("inverted")
41 #define temperatureswitch_arm_command_checksum CHECKSUM("arm_mcode")
42 #define designator_checksum CHECKSUM("designator")
43
44 TemperatureSwitch::TemperatureSwitch()
45 {
46 this->temperatureswitch_state = false;
47 this->second_counter = 0;
48 }
49
50 // Load module
51 void TemperatureSwitch::on_module_loaded()
52 {
53 vector<uint16_t> modulist;
54 // allow for multiple temperature switches
55 THEKERNEL->config->get_module_list(&modulist, temperatureswitch_checksum);
56 for (auto m : modulist) {
57 load_config(m);
58 }
59
60 // no longer need this instance as it is just used to load the other instances
61 delete this;
62 }
63
64
65 bool TemperatureSwitch::load_config(uint16_t modcs)
66 {
67 // see if enabled
68 if (!THEKERNEL->config->value(temperatureswitch_checksum, modcs, enable_checksum)->by_default(false)->as_bool()) {
69 return false;
70 }
71
72 // create a temperature control and load settings
73
74 char designator= 0;
75 string s= THEKERNEL->config->value(temperatureswitch_checksum, modcs, designator_checksum)->by_default("")->as_string();
76 if(s.empty()){
77 // for backward compatibility temperatureswitch.hotend will need designator 'T' by default @DEPRECATED
78 if(modcs == temperatureswitch_hotend_checksum) designator= 'T';
79
80 }else{
81 designator= s[0];
82 }
83
84 if(designator == 0) return false; // no designator then not valid
85
86 // create a new temperature switch module
87 TemperatureSwitch *ts= new TemperatureSwitch();
88
89 // get the list of temperature controllers and remove any that don't have designator == specified designator
90 auto& tempcontrollers= THEKERNEL->temperature_control_pool->get_controllers();
91
92 // see what its designator is and add to list of it the one we specified
93 void *returned_temp;
94 for (auto controller : tempcontrollers) {
95 bool temp_ok = PublicData::get_value(temperature_control_checksum, controller, current_temperature_checksum, &returned_temp);
96 if (temp_ok) {
97 struct pad_temperature temp = *static_cast<struct pad_temperature *>(returned_temp);
98 if (temp.designator[0] == designator) {
99 ts->temp_controllers.push_back(controller);
100 }
101 }
102 }
103
104 // if we don't have any matching controllers, then not valid
105 if (ts->temp_controllers.empty()) {
106 delete ts;
107 return false;
108 }
109
110 // load settings from config file
111 s = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_switch_checksum)->by_default("")->as_string();
112 if(s.empty()) {
113 // handle old configs where this was called type @DEPRECATED
114 s = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_type_checksum)->by_default("")->as_string();
115 if(s.empty()) {
116 // no switch specified so invalid entry
117 delete this;
118 return false;
119 }
120 }
121
122 // if we should turn the switch on or off when trigger is hit
123 ts->inverted = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_inverted_checksum)->by_default(false)->as_bool();
124
125 // if we should trigger when above and below, or when rising through, or when falling through the specified temp
126 string trig = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_trigger_checksum)->by_default("level")->as_string();
127 if(trig == "level") ts->trigger= LEVEL;
128 else if(trig == "rising") ts->trigger= RISING;
129 else if(trig == "falling") ts->trigger= FALLING;
130 else ts->trigger= LEVEL;
131
132 ts->edge= (ts->trigger != LEVEL);
133
134 // the mcode used to arm the switch
135 ts->arm_mcode = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_arm_command_checksum)->by_default(0)->as_number();
136 // if not defined then always armed, otherwise start out disarmed
137 if(ts->arm_mcode != 0) {
138 ts->can_arm= true;
139 ts->armed= false;
140 }else{
141 ts->can_arm= false;
142 ts->armed= true;
143 }
144
145 ts->temperatureswitch_switch_cs= get_checksum(s); // checksum of the switch to use
146
147 ts->temperatureswitch_threshold_temp = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_threshold_temp_checksum)->by_default(50.0f)->as_number();
148
149 // these are to tune the heatup and cooldown polling frequencies
150 ts->temperatureswitch_heatup_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_heatup_poll_checksum)->by_default(15)->as_number();
151 ts->temperatureswitch_cooldown_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_cooldown_poll_checksum)->by_default(60)->as_number();
152 ts->current_delay = ts->temperatureswitch_heatup_poll;
153
154 // set initial state
155 float current_temp = ts->get_highest_temperature();
156 ts->lower= current_temp < ts->temperatureswitch_threshold_temp;
157
158 // Register for events
159 ts->register_for_event(ON_SECOND_TICK);
160 ts->register_for_event(ON_GCODE_RECEIVED);
161 return true;
162 }
163
164 void TemperatureSwitch::on_gcode_received(void *argument)
165 {
166 if(this->arm_mcode == 0) return;
167
168 Gcode *gcode = static_cast<Gcode *>(argument);
169 if(gcode->has_m && gcode->m == this->arm_mcode) {
170 this->armed= (gcode->has_letter('S') && gcode->get_value('S') != 0);
171 gcode->stream->printf("temperature switch %s\n", this->armed ? "armed" : "disarmed");
172 }
173 }
174
175 // Called once a second but we only need to service on the cooldown and heatup poll intervals
176 void TemperatureSwitch::on_second_tick(void *argument)
177 {
178 second_counter++;
179 if (second_counter < current_delay) {
180 return;
181
182 } else {
183 second_counter = 0;
184 float current_temp = this->get_highest_temperature();
185
186 if (current_temp >= this->temperatureswitch_threshold_temp) {
187 // temp >= threshold temp, call set_switch if trigger is LEVEL, or if we were lower and RISING
188 if (this->trigger == LEVEL || (this->lower && this->trigger == RISING)) {
189 this->lower= false;
190 set_switch(true);
191 current_delay = temperatureswitch_cooldown_poll;
192 }
193 if(this->lower && this->trigger == FALLING) {
194 this->lower= false;
195 }
196
197 } else {
198 // temp < threshold temp, call set_switch if trigger is LEVEL, or if we were not lower and FALLING
199 if (this->trigger == LEVEL || (!this->lower && this->trigger == FALLING)) {
200 this->lower= true;
201 set_switch(false);
202 current_delay = temperatureswitch_heatup_poll;
203 }
204 if(!this->lower && this->trigger == RISING) {
205 this->lower= true;
206 }
207 }
208 }
209 }
210
211 // Get the highest temperature from the set of temperature controllers
212 float TemperatureSwitch::get_highest_temperature()
213 {
214 void *returned_temp;
215 float high_temp = 0.0;
216
217 for (auto controller : temp_controllers) {
218 bool temp_ok = PublicData::get_value(temperature_control_checksum, controller, current_temperature_checksum, &returned_temp);
219 if (temp_ok) {
220 struct pad_temperature temp = *static_cast<struct pad_temperature *>(returned_temp);
221 // check if this controller's temp is the highest and save it if so
222 if (temp.current_temperature > high_temp) {
223 high_temp = temp.current_temperature;
224 }
225 }
226 }
227 return high_temp;
228 }
229
230 // Turn the switch on (true) or off (false)
231 void TemperatureSwitch::set_switch(bool switch_state)
232 {
233
234 if(!this->edge) {
235 // for level triggered do not repeat if already in requested state
236 if(this->temperatureswitch_state == switch_state) return;
237 this->temperatureswitch_state = switch_state;
238 }
239
240 if(!this->armed) return; // do not actually switch anything if not armed, but we do need to keep the state
241
242 if(this->can_arm && this->edge) {
243 // only fires once per arming if edge triggered
244 this->armed= false;
245 }
246
247 if(this->inverted) switch_state= !switch_state; // turn switch on or off inverted
248
249
250 bool ok = PublicData::set_value(switch_checksum, this->temperatureswitch_switch_cs, state_checksum, &switch_state);
251 if (!ok) {
252 THEKERNEL->streams->printf("// Failed changing switch state.\r\n");
253 }
254 }