Merge remote-tracking branch 'upstream/edge' into upstream-master
[clinton/Smoothieware.git] / src / modules / tools / temperatureswitch / TemperatureSwitch.cpp
CommitLineData
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/*
9TemperatureSwitch is an optional module that will automatically turn on or off a switch
10based on a setpoint temperature. It is commonly used to turn on/off a cooling fan or water pump
11to cool the hot end's cold zone. Specifically, it turns one of the small MOSFETs on or off.
12
13Author: 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 45TemperatureSwitch::TemperatureSwitch()
46{
47}
48
93ea6adb
JM
49TemperatureSwitch::~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
56void 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 69TemperatureSwitch* 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
144void 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
154void 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
170void 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
197float 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)
216void 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}