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 | |
abe97b79 | 89 | // load settings from config file |
030a339d JM |
90 | string switchname = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_switch_checksum)->by_default("")->as_string(); |
91 | if(switchname.empty()) { | |
fe3323e7 | 92 | // handle old configs where this was called type @DEPRECATED |
030a339d JM |
93 | switchname = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_type_checksum)->by_default("")->as_string(); |
94 | if(switchname.empty()) { | |
fe3323e7 | 95 | // no switch specified so invalid entry |
15bd90c3 | 96 | THEKERNEL->streams->printf("WARNING TEMPERATURESWITCH: no switch specified\n"); |
93ea6adb | 97 | return nullptr; |
fe3323e7 | 98 | } |
5cbf2253 | 99 | } |
c0c375b0 | 100 | |
030a339d JM |
101 | // create a new temperature switch module |
102 | TemperatureSwitch *ts= new TemperatureSwitch(); | |
103 | ||
104 | // save designator | |
105 | ts->designator= designator; | |
106 | ||
c0c375b0 JM |
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(); | |
81300c80 JM |
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(); | |
c0c375b0 | 119 | |
030a339d | 120 | ts->temperatureswitch_switch_cs= get_checksum(switchname); // checksum of the switch to use |
abe97b79 | 121 | |
5cbf2253 | 122 | ts->temperatureswitch_threshold_temp = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_threshold_temp_checksum)->by_default(50.0f)->as_number(); |
abe97b79 | 123 | |
5cbf2253 JM |
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; | |
f53fe49d | 128 | |
c0c375b0 | 129 | // set initial state |
cb32dae3 | 130 | ts->current_state= NONE; |
15bd90c3 | 131 | ts->second_counter = ts->current_delay; // do test immediately on first second_tick |
cb32dae3 JM |
132 | // if not defined then always armed, otherwise start out disarmed |
133 | ts->armed= (ts->arm_mcode == 0); | |
c0c375b0 | 134 | |
f53fe49d | 135 | // Register for events |
5cbf2253 | 136 | ts->register_for_event(ON_SECOND_TICK); |
cb32dae3 | 137 | |
15bd90c3 | 138 | if(ts->arm_mcode != 0) { |
cb32dae3 JM |
139 | ts->register_for_event(ON_GCODE_RECEIVED); |
140 | } | |
93ea6adb | 141 | return ts; |
abe97b79 | 142 | } |
143 | ||
81300c80 JM |
144 | void TemperatureSwitch::on_gcode_received(void *argument) |
145 | { | |
81300c80 JM |
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); | |
1e4b11dd | 149 | gcode->stream->printf("temperature switch %s\n", this->armed ? "armed" : "disarmed"); |
81300c80 JM |
150 | } |
151 | } | |
152 | ||
abe97b79 | 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++; | |
cb32dae3 JM |
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); | |
c0c375b0 | 164 | |
abe97b79 | 165 | } else { |
cb32dae3 JM |
166 | set_state(LOW_TEMP); |
167 | } | |
168 | } | |
c0c375b0 | 169 | |
cb32dae3 JM |
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; | |
abe97b79 | 190 | } |
cb32dae3 JM |
191 | |
192 | this->current_delay = state == HIGH_TEMP ? this->temperatureswitch_cooldown_poll : this->temperatureswitch_heatup_poll; | |
193 | this->current_state= state; | |
abe97b79 | 194 | } |
195 | ||
196 | // Get the highest temperature from the set of temperature controllers | |
197 | float TemperatureSwitch::get_highest_temperature() | |
198 | { | |
abe97b79 | 199 | float high_temp = 0.0; |
200 | ||
030a339d JM |
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) { | |
abe97b79 | 205 | // check if this controller's temp is the highest and save it if so |
030a339d JM |
206 | if (c.designator[0] == this->designator && c.current_temperature > high_temp) { |
207 | high_temp = c.current_temperature; | |
abe97b79 | 208 | } |
209 | } | |
210 | } | |
030a339d | 211 | |
abe97b79 | 212 | return high_temp; |
213 | } | |
214 | ||
215 | // Turn the switch on (true) or off (false) | |
216 | void TemperatureSwitch::set_switch(bool switch_state) | |
217 | { | |
cb32dae3 | 218 | if(!this->armed) return; // do not actually switch anything if not armed |
1e4b11dd | 219 | |
cb32dae3 JM |
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; | |
7e6429d7 JM |
223 | } |
224 | ||
c0c375b0 JM |
225 | if(this->inverted) switch_state= !switch_state; // turn switch on or off inverted |
226 | ||
cb32dae3 | 227 | // get current switch state |
1ae7e276 JM |
228 | struct pad_switch pad; |
229 | bool ok = PublicData::get_value(switch_checksum, this->temperatureswitch_switch_cs, 0, &pad); | |
cb32dae3 JM |
230 | if (!ok) { |
231 | THEKERNEL->streams->printf("// Failed to get switch state.\r\n"); | |
232 | return; | |
233 | } | |
234 | ||
1ae7e276 | 235 | if(pad.state == switch_state) return; // switch is already in the requested state |
cb32dae3 JM |
236 | |
237 | ok = PublicData::set_value(switch_checksum, this->temperatureswitch_switch_cs, state_checksum, &switch_state); | |
abe97b79 | 238 | if (!ok) { |
c0c375b0 | 239 | THEKERNEL->streams->printf("// Failed changing switch state.\r\n"); |
abe97b79 | 240 | } |
241 | } |