change the temperature public data access to pass in the pad_temp for the result...
[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"
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 44TemperatureSwitch::TemperatureSwitch()
45{
5cbf2253
JM
46 this->temperatureswitch_state = false;
47 this->second_counter = 0;
abe97b79 48}
49
50// Load module
51void 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
65bool 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
159void 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
171void 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
207float 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)
225void 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}