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