Merge pull request #736 from Smoothieware/development/test-framework
[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
5cbf2253
JM
89 // create a new temperature switch module
90 TemperatureSwitch *ts= new TemperatureSwitch();
91
93ea6adb
JM
92 //__debugbreak();
93
3bfb2639
JM
94 // make a list of temperature controls with matching designators with the same first letter
95 // the list is added t the controllers vector given below
93ea6adb
JM
96 {
97 std::vector<struct pad_temperature> controllers;
98 bool ok = PublicData::get_value(temperature_control_checksum, poll_controls_checksum, &controllers);
99 if (ok) {
100 for (auto &c : controllers) {
101 if (c.designator[0] == designator) {
102 ts->temp_controllers.push_back(c.id);
103 }
f53fe49d 104 }
105 }
106 }
57235957 107
5cbf2253
JM
108 // if we don't have any matching controllers, then not valid
109 if (ts->temp_controllers.empty()) {
110 delete ts;
93ea6adb 111 return nullptr;
57235957
JM
112 }
113
abe97b79 114 // load settings from config file
fe3323e7 115 s = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_switch_checksum)->by_default("")->as_string();
5cbf2253 116 if(s.empty()) {
fe3323e7
JM
117 // handle old configs where this was called type @DEPRECATED
118 s = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_type_checksum)->by_default("")->as_string();
119 if(s.empty()) {
120 // no switch specified so invalid entry
121 delete this;
93ea6adb 122 return nullptr;
fe3323e7 123 }
5cbf2253 124 }
c0c375b0
JM
125
126 // if we should turn the switch on or off when trigger is hit
127 ts->inverted = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_inverted_checksum)->by_default(false)->as_bool();
128
129 // if we should trigger when above and below, or when rising through, or when falling through the specified temp
130 string trig = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_trigger_checksum)->by_default("level")->as_string();
81300c80
JM
131 if(trig == "level") ts->trigger= LEVEL;
132 else if(trig == "rising") ts->trigger= RISING;
133 else if(trig == "falling") ts->trigger= FALLING;
134 else ts->trigger= LEVEL;
135
136 // the mcode used to arm the switch
137 ts->arm_mcode = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_arm_command_checksum)->by_default(0)->as_number();
c0c375b0 138
fe3323e7 139 ts->temperatureswitch_switch_cs= get_checksum(s); // checksum of the switch to use
abe97b79 140
5cbf2253 141 ts->temperatureswitch_threshold_temp = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_threshold_temp_checksum)->by_default(50.0f)->as_number();
abe97b79 142
5cbf2253
JM
143 // these are to tune the heatup and cooldown polling frequencies
144 ts->temperatureswitch_heatup_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_heatup_poll_checksum)->by_default(15)->as_number();
145 ts->temperatureswitch_cooldown_poll = THEKERNEL->config->value(temperatureswitch_checksum, modcs, temperatureswitch_cooldown_poll_checksum)->by_default(60)->as_number();
146 ts->current_delay = ts->temperatureswitch_heatup_poll;
f53fe49d 147
c0c375b0 148 // set initial state
cb32dae3
JM
149 ts->current_state= NONE;
150 this->second_counter = ts->current_delay; // do test immediately on first second_tick
151 // if not defined then always armed, otherwise start out disarmed
152 ts->armed= (ts->arm_mcode == 0);
c0c375b0 153
f53fe49d 154 // Register for events
5cbf2253 155 ts->register_for_event(ON_SECOND_TICK);
cb32dae3
JM
156
157 if(this->arm_mcode != 0) {
158 ts->register_for_event(ON_GCODE_RECEIVED);
159 }
93ea6adb 160 return ts;
abe97b79 161}
162
81300c80
JM
163void TemperatureSwitch::on_gcode_received(void *argument)
164{
81300c80
JM
165 Gcode *gcode = static_cast<Gcode *>(argument);
166 if(gcode->has_m && gcode->m == this->arm_mcode) {
167 this->armed= (gcode->has_letter('S') && gcode->get_value('S') != 0);
1e4b11dd 168 gcode->stream->printf("temperature switch %s\n", this->armed ? "armed" : "disarmed");
81300c80
JM
169 }
170}
171
abe97b79 172// Called once a second but we only need to service on the cooldown and heatup poll intervals
173void TemperatureSwitch::on_second_tick(void *argument)
174{
175 second_counter++;
cb32dae3
JM
176 if (second_counter < current_delay) return;
177
178 second_counter = 0;
179 float current_temp = this->get_highest_temperature();
180
181 if (current_temp >= this->temperatureswitch_threshold_temp) {
182 set_state(HIGH_TEMP);
c0c375b0 183
abe97b79 184 } else {
cb32dae3
JM
185 set_state(LOW_TEMP);
186 }
187}
c0c375b0 188
cb32dae3
JM
189void TemperatureSwitch::set_state(STATE state)
190{
191 if(state == this->current_state) return; // state did not change
192
193 // state has changed
194 switch(this->trigger) {
195 case LEVEL:
196 // switch on or off depending on HIGH or LOW
197 set_switch(state == HIGH_TEMP);
198 break;
199
200 case RISING:
201 // switch on if rising edge
202 if(this->current_state == LOW_TEMP && state == HIGH_TEMP) set_switch(true);
203 break;
204
205 case FALLING:
206 // switch off if falling edge
207 if(this->current_state == HIGH_TEMP && state == LOW_TEMP) set_switch(false);
208 break;
abe97b79 209 }
cb32dae3
JM
210
211 this->current_delay = state == HIGH_TEMP ? this->temperatureswitch_cooldown_poll : this->temperatureswitch_heatup_poll;
212 this->current_state= state;
abe97b79 213}
214
215// Get the highest temperature from the set of temperature controllers
216float TemperatureSwitch::get_highest_temperature()
217{
3bfb2639 218 struct pad_temperature temp;
abe97b79 219 float high_temp = 0.0;
220
221 for (auto controller : temp_controllers) {
56a6c8c1 222 bool ok = PublicData::get_value(temperature_control_checksum, current_temperature_checksum, controller, &temp);
3bfb2639 223 if (ok) {
abe97b79 224 // check if this controller's temp is the highest and save it if so
57235957 225 if (temp.current_temperature > high_temp) {
abe97b79 226 high_temp = temp.current_temperature;
abe97b79 227 }
228 }
229 }
230 return high_temp;
231}
232
233// Turn the switch on (true) or off (false)
234void TemperatureSwitch::set_switch(bool switch_state)
235{
cb32dae3 236 if(!this->armed) return; // do not actually switch anything if not armed
1e4b11dd 237
cb32dae3
JM
238 if(this->arm_mcode != 0 && this->trigger != LEVEL) {
239 // if edge triggered we only trigger once per arming, if level triggered we switch as long as we are armed
240 this->armed= false;
7e6429d7
JM
241 }
242
c0c375b0
JM
243 if(this->inverted) switch_state= !switch_state; // turn switch on or off inverted
244
cb32dae3 245 // get current switch state
1ae7e276
JM
246 struct pad_switch pad;
247 bool ok = PublicData::get_value(switch_checksum, this->temperatureswitch_switch_cs, 0, &pad);
cb32dae3
JM
248 if (!ok) {
249 THEKERNEL->streams->printf("// Failed to get switch state.\r\n");
250 return;
251 }
252
1ae7e276 253 if(pad.state == switch_state) return; // switch is already in the requested state
cb32dae3
JM
254
255 ok = PublicData::set_value(switch_checksum, this->temperatureswitch_switch_cs, state_checksum, &switch_state);
abe97b79 256 if (!ok) {
c0c375b0 257 THEKERNEL->streams->printf("// Failed changing switch state.\r\n");
abe97b79 258 }
259}