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/>.
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.
13 Author: Michael Hackney, mhackney@eclecticangler.com
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"
25 #include "ConfigValue.h"
26 #include "checksumm.h"
27 #include "PublicData.h"
28 #include "StreamOutputPool.h"
29 #include "TemperatureControlPool.h"
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")
45 TemperatureSwitch::TemperatureSwitch()
49 TemperatureSwitch::~TemperatureSwitch()
51 THEKERNEL
->unregister_for_event(ON_SECOND_TICK
, this);
52 THEKERNEL
->unregister_for_event(ON_GCODE_RECEIVED
, this);
56 void TemperatureSwitch::on_module_loaded()
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
) {
65 // no longer need this instance as it is just used to load the other instances
69 TemperatureSwitch
* TemperatureSwitch::load_config(uint16_t modcs
)
72 if (!THEKERNEL
->config
->value(temperatureswitch_checksum
, modcs
, enable_checksum
)->by_default(false)->as_bool()) {
76 // create a temperature control and load settings
78 string s
= THEKERNEL
->config
->value(temperatureswitch_checksum
, modcs
, designator_checksum
)->by_default("")->as_string();
80 // for backward compatibility temperatureswitch.hotend will need designator 'T' by default @DEPRECATED
81 if(modcs
== temperatureswitch_hotend_checksum
) designator
= 'T';
87 if(designator
== 0) return nullptr; // no designator then not valid
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");
101 // create a new temperature switch module
102 TemperatureSwitch
*ts
= new TemperatureSwitch();
105 ts
->designator
= designator
;
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();
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
;
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();
120 ts
->temperatureswitch_switch_cs
= get_checksum(switchname
); // checksum of the switch to use
122 ts
->temperatureswitch_threshold_temp
= THEKERNEL
->config
->value(temperatureswitch_checksum
, modcs
, temperatureswitch_threshold_temp_checksum
)->by_default(50.0f
)->as_number();
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
;
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);
135 // Register for events
136 ts
->register_for_event(ON_SECOND_TICK
);
138 if(ts
->arm_mcode
!= 0) {
139 ts
->register_for_event(ON_GCODE_RECEIVED
);
144 void TemperatureSwitch::on_gcode_received(void *argument
)
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");
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
)
157 if (second_counter
< current_delay
) return;
160 float current_temp
= this->get_highest_temperature();
162 if (current_temp
>= this->temperatureswitch_threshold_temp
) {
163 set_state(HIGH_TEMP
);
170 void TemperatureSwitch::set_state(STATE state
)
172 if(state
== this->current_state
) return; // state did not change
175 switch(this->trigger
) {
177 // switch on or off depending on HIGH or LOW
178 set_switch(state
== HIGH_TEMP
);
182 // switch on if rising edge
183 if(this->current_state
== LOW_TEMP
&& state
== HIGH_TEMP
) set_switch(true);
187 // switch off if falling edge
188 if(this->current_state
== HIGH_TEMP
&& state
== LOW_TEMP
) set_switch(false);
192 this->current_delay
= state
== HIGH_TEMP
? this->temperatureswitch_cooldown_poll
: this->temperatureswitch_heatup_poll
;
193 this->current_state
= state
;
196 // Get the highest temperature from the set of temperature controllers
197 float TemperatureSwitch::get_highest_temperature()
199 float high_temp
= 0.0;
201 std::vector
<struct pad_temperature
> controllers
;
202 bool ok
= PublicData::get_value(temperature_control_checksum
, poll_controls_checksum
, &controllers
);
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
;
215 // Turn the switch on (true) or off (false)
216 void TemperatureSwitch::set_switch(bool switch_state
)
218 if(!this->armed
) return; // do not actually switch anything if not armed
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
225 if(this->inverted
) switch_state
= !switch_state
; // turn switch on or off inverted
227 // get current switch state
228 struct pad_switch pad
;
229 bool ok
= PublicData::get_value(switch_checksum
, this->temperatureswitch_switch_cs
, 0, &pad
);
231 THEKERNEL
->streams
->printf("// Failed to get switch state.\r\n");
235 if(pad
.state
== switch_state
) return; // switch is already in the requested state
237 ok
= PublicData::set_value(switch_checksum
, this->temperatureswitch_switch_cs
, state_checksum
, &switch_state
);
239 THEKERNEL
->streams
->printf("// Failed changing switch state.\r\n");