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