Commit | Line | Data |
---|---|---|
df27a6a3 | 1 | /* |
cc1d3b1f AW |
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. | |
df27a6a3 | 5 | You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. |
cc1d3b1f AW |
6 | */ |
7 | ||
8 | #include "libs/Module.h" | |
9 | #include "libs/Kernel.h" | |
26ef0ec6 | 10 | #include "libs/SerialMessage.h" |
cc1d3b1f AW |
11 | #include <math.h> |
12 | #include "Switch.h" | |
13 | #include "libs/Pin.h" | |
3c4f2dd8 | 14 | #include "modules/robot/Conveyor.h" |
2f5d0afa | 15 | #include "PublicDataRequest.h" |
d2d6c070 | 16 | #include "SwitchPublicAccess.h" |
61134a65 JM |
17 | #include "SlowTicker.h" |
18 | #include "Config.h" | |
19 | #include "Gcode.h" | |
7af0714f | 20 | #include "checksumm.h" |
8d54c34c | 21 | #include "ConfigValue.h" |
def93cb3 JM |
22 | #include "StreamOutput.h" |
23 | #include "StreamOutputPool.h" | |
24 | ||
25 | #include "PwmOut.h" | |
cc1d3b1f | 26 | |
8f91e4e6 MM |
27 | #include "MRI_Hooks.h" |
28 | ||
fe04e329 JM |
29 | #define startup_state_checksum CHECKSUM("startup_state") |
30 | #define startup_value_checksum CHECKSUM("startup_value") | |
31 | #define input_pin_checksum CHECKSUM("input_pin") | |
32 | #define input_pin_behavior_checksum CHECKSUM("input_pin_behavior") | |
33 | #define toggle_checksum CHECKSUM("toggle") | |
34 | #define momentary_checksum CHECKSUM("momentary") | |
35 | #define input_on_command_checksum CHECKSUM("input_on_command") | |
36 | #define input_off_command_checksum CHECKSUM("input_off_command") | |
37 | #define output_pin_checksum CHECKSUM("output_pin") | |
e9f88113 | 38 | #define output_type_checksum CHECKSUM("output_type") |
ef318bf8 | 39 | #define max_pwm_checksum CHECKSUM("max_pwm") |
fe04e329 JM |
40 | #define output_on_command_checksum CHECKSUM("output_on_command") |
41 | #define output_off_command_checksum CHECKSUM("output_off_command") | |
def93cb3 | 42 | #define pwm_period_ms_checksum CHECKSUM("pwm_period_ms") |
44ad0689 JM |
43 | #define failsafe_checksum CHECKSUM("failsafe_set_to") |
44 | #define ignore_onhalt_checksum CHECKSUM("ignore_on_halt") | |
fe04e329 | 45 | |
bf9e8a8f | 46 | Switch::Switch() {} |
cc1d3b1f | 47 | |
bf9e8a8f JM |
48 | Switch::Switch(uint16_t name) |
49 | { | |
cc1d3b1f | 50 | this->name_checksum = name; |
26ef0ec6 | 51 | //this->dummy_stream = &(StreamOutput::NullStream); |
cc1d3b1f AW |
52 | } |
53 | ||
44ad0689 JM |
54 | // set the pin to the fail safe value on halt |
55 | void Switch::on_halt(void *arg) | |
56 | { | |
57 | if(arg == nullptr) { | |
58 | if(this->ignore_on_halt) return; | |
59 | ||
60 | // set pin to failsafe value | |
61 | switch(this->output_type) { | |
62 | case DIGITAL: this->digital_pin->set(this->failsafe); break; | |
63 | case SIGMADELTA: this->sigmadelta_pin->set(this->failsafe); break; | |
64 | case HWPWM: this->pwm_pin->write(0); break; | |
65 | case NONE: break; | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
bf9e8a8f JM |
70 | void Switch::on_module_loaded() |
71 | { | |
e62e8f7c | 72 | this->switch_changed = false; |
26ef0ec6 | 73 | |
3c4f2dd8 | 74 | this->register_for_event(ON_GCODE_RECEIVED); |
e62e8f7c | 75 | this->register_for_event(ON_MAIN_LOOP); |
2f5d0afa L |
76 | this->register_for_event(ON_GET_PUBLIC_DATA); |
77 | this->register_for_event(ON_SET_PUBLIC_DATA); | |
44ad0689 | 78 | this->register_for_event(ON_HALT); |
bca315cc | 79 | |
cc1d3b1f AW |
80 | // Settings |
81 | this->on_config_reload(this); | |
cc1d3b1f AW |
82 | } |
83 | ||
cc1d3b1f | 84 | // Get config |
bf9e8a8f JM |
85 | void Switch::on_config_reload(void *argument) |
86 | { | |
fe04e329 | 87 | this->input_pin.from_string( THEKERNEL->config->value(switch_checksum, this->name_checksum, input_pin_checksum )->by_default("nc")->as_string())->as_input(); |
44ad0689 JM |
88 | this->input_pin_behavior = THEKERNEL->config->value(switch_checksum, this->name_checksum, input_pin_behavior_checksum )->by_default(momentary_checksum)->as_number(); |
89 | std::string input_on_command = THEKERNEL->config->value(switch_checksum, this->name_checksum, input_on_command_checksum )->by_default("")->as_string(); | |
90 | std::string input_off_command = THEKERNEL->config->value(switch_checksum, this->name_checksum, input_off_command_checksum )->by_default("")->as_string(); | |
91 | this->output_on_command = THEKERNEL->config->value(switch_checksum, this->name_checksum, output_on_command_checksum )->by_default("")->as_string(); | |
92 | this->output_off_command = THEKERNEL->config->value(switch_checksum, this->name_checksum, output_off_command_checksum )->by_default("")->as_string(); | |
93 | this->switch_state = THEKERNEL->config->value(switch_checksum, this->name_checksum, startup_state_checksum )->by_default(false)->as_bool(); | |
94 | string type = THEKERNEL->config->value(switch_checksum, this->name_checksum, output_type_checksum )->by_default("pwm")->as_string(); | |
95 | this->failsafe= THEKERNEL->config->value(switch_checksum, this->name_checksum, failsafe_checksum )->by_default(0)->as_number(); | |
96 | this->ignore_on_halt= THEKERNEL->config->value(switch_checksum, this->name_checksum, ignore_onhalt_checksum )->by_default(false)->as_bool(); | |
e9f88113 | 97 | |
def93cb3 JM |
98 | if(type == "pwm"){ |
99 | this->output_type= SIGMADELTA; | |
100 | this->sigmadelta_pin= new Pwm(); | |
101 | this->sigmadelta_pin->from_string(THEKERNEL->config->value(switch_checksum, this->name_checksum, output_pin_checksum )->by_default("nc")->as_string())->as_output(); | |
873fc9a7 | 102 | if(this->sigmadelta_pin->connected()) { |
44ad0689 JM |
103 | if(failsafe == 1) { |
104 | set_high_on_debug(sigmadelta_pin->port_number, sigmadelta_pin->pin); | |
105 | }else{ | |
106 | set_low_on_debug(sigmadelta_pin->port_number, sigmadelta_pin->pin); | |
107 | } | |
873fc9a7 JM |
108 | }else{ |
109 | this->output_type= NONE; | |
110 | delete this->sigmadelta_pin; | |
111 | this->sigmadelta_pin= nullptr; | |
112 | } | |
def93cb3 JM |
113 | |
114 | }else if(type == "digital"){ | |
115 | this->output_type= DIGITAL; | |
116 | this->digital_pin= new Pin(); | |
117 | this->digital_pin->from_string(THEKERNEL->config->value(switch_checksum, this->name_checksum, output_pin_checksum )->by_default("nc")->as_string())->as_output(); | |
873fc9a7 | 118 | if(this->digital_pin->connected()) { |
44ad0689 JM |
119 | if(failsafe == 1) { |
120 | set_high_on_debug(digital_pin->port_number, digital_pin->pin); | |
121 | }else{ | |
122 | set_low_on_debug(digital_pin->port_number, digital_pin->pin); | |
123 | } | |
873fc9a7 JM |
124 | }else{ |
125 | this->output_type= NONE; | |
126 | delete this->digital_pin; | |
127 | this->digital_pin= nullptr; | |
128 | } | |
def93cb3 JM |
129 | |
130 | }else if(type == "hwpwm"){ | |
131 | this->output_type= HWPWM; | |
132 | Pin *pin= new Pin(); | |
def93cb3 JM |
133 | pin->from_string(THEKERNEL->config->value(switch_checksum, this->name_checksum, output_pin_checksum )->by_default("nc")->as_string())->as_output(); |
134 | this->pwm_pin= pin->hardware_pwm(); | |
44ad0689 JM |
135 | if(failsafe == 1) { |
136 | set_high_on_debug(pin->port_number, pin->pin); | |
137 | }else{ | |
138 | set_low_on_debug(pin->port_number, pin->pin); | |
139 | } | |
def93cb3 JM |
140 | delete pin; |
141 | if(this->pwm_pin == nullptr) { | |
142 | THEKERNEL->streams->printf("Selected Switch output pin is not PWM capable - disabled"); | |
873fc9a7 | 143 | this->output_type= NONE; |
def93cb3 | 144 | } |
e9f88113 | 145 | |
def93cb3 JM |
146 | } else { |
147 | this->output_type= NONE; | |
148 | } | |
149 | ||
150 | if(this->output_type == SIGMADELTA) { | |
151 | this->sigmadelta_pin->max_pwm(THEKERNEL->config->value(switch_checksum, this->name_checksum, max_pwm_checksum )->by_default(255)->as_number()); | |
152 | this->switch_value = THEKERNEL->config->value(switch_checksum, this->name_checksum, startup_value_checksum )->by_default(this->sigmadelta_pin->max_pwm())->as_number(); | |
153 | if(this->switch_state) { | |
154 | this->sigmadelta_pin->pwm(this->switch_value); // will be truncated to max_pwm | |
ef318bf8 | 155 | } else { |
def93cb3 | 156 | this->sigmadelta_pin->set(false); |
ef318bf8 | 157 | } |
8f91e4e6 | 158 | |
def93cb3 JM |
159 | } else if(this->output_type == HWPWM) { |
160 | // default is 50Hz | |
ba2aa86f JM |
161 | float p= THEKERNEL->config->value(switch_checksum, this->name_checksum, pwm_period_ms_checksum )->by_default(20)->as_number() * 1000.0F; // ms but fractions are allowed |
162 | this->pwm_pin->period_us(p); | |
163 | ||
def93cb3 JM |
164 | // default is 0% duty cycle |
165 | this->switch_value = THEKERNEL->config->value(switch_checksum, this->name_checksum, startup_value_checksum )->by_default(0)->as_number(); | |
166 | if(this->switch_state) { | |
e7b2ebb4 | 167 | this->pwm_pin->write(this->switch_value/100.0F); |
def93cb3 JM |
168 | } else { |
169 | this->pwm_pin->write(0); | |
170 | } | |
171 | ||
172 | } else if(this->output_type == DIGITAL){ | |
173 | this->digital_pin->set(this->switch_state); | |
174 | } | |
fe04e329 JM |
175 | |
176 | // Set the on/off command codes, Use GCode to do the parsing | |
bf9e8a8f JM |
177 | input_on_command_letter = 0; |
178 | input_off_command_letter = 0; | |
fe04e329 JM |
179 | |
180 | if(!input_on_command.empty()) { | |
181 | Gcode gc(input_on_command, NULL); | |
bf9e8a8f JM |
182 | if(gc.has_g) { |
183 | input_on_command_letter = 'G'; | |
184 | input_on_command_code = gc.g; | |
fe04e329 | 185 | } else if(gc.has_m) { |
bf9e8a8f JM |
186 | input_on_command_letter = 'M'; |
187 | input_on_command_code = gc.m; | |
fe04e329 JM |
188 | } |
189 | } | |
190 | if(!input_off_command.empty()) { | |
191 | Gcode gc(input_off_command, NULL); | |
bf9e8a8f JM |
192 | if(gc.has_g) { |
193 | input_off_command_letter = 'G'; | |
194 | input_off_command_code = gc.g; | |
fe04e329 | 195 | } else if(gc.has_m) { |
bf9e8a8f JM |
196 | input_off_command_letter = 'M'; |
197 | input_off_command_code = gc.m; | |
fe04e329 JM |
198 | } |
199 | } | |
200 | ||
bf9e8a8f | 201 | if(input_pin.connected()) { |
10702aa5 JM |
202 | // set to initial state |
203 | this->input_pin_state = this->input_pin.get(); | |
bf9e8a8f JM |
204 | // input pin polling |
205 | THEKERNEL->slow_ticker->attach( 100, this, &Switch::pinpoll_tick); | |
206 | } | |
207 | ||
def93cb3 JM |
208 | if(this->output_type == SIGMADELTA) { |
209 | // SIGMADELTA | |
210 | THEKERNEL->slow_ticker->attach(1000, this->sigmadelta_pin, &Pwm::on_tick); | |
bf9e8a8f | 211 | } |
fe04e329 JM |
212 | } |
213 | ||
bf9e8a8f JM |
214 | bool Switch::match_input_on_gcode(const Gcode *gcode) const |
215 | { | |
216 | return ((input_on_command_letter == 'M' && gcode->has_m && gcode->m == input_on_command_code) || | |
e31ee311 | 217 | (input_on_command_letter == 'G' && gcode->has_g && gcode->g == input_on_command_code)); |
cc1d3b1f AW |
218 | } |
219 | ||
bf9e8a8f JM |
220 | bool Switch::match_input_off_gcode(const Gcode *gcode) const |
221 | { | |
fe04e329 JM |
222 | return ((input_off_command_letter == 'M' && gcode->has_m && gcode->m == input_off_command_code) || |
223 | (input_off_command_letter == 'G' && gcode->has_g && gcode->g == input_off_command_code)); | |
224 | } | |
3c4f2dd8 | 225 | |
bf9e8a8f JM |
226 | void Switch::on_gcode_received(void *argument) |
227 | { | |
228 | Gcode *gcode = static_cast<Gcode *>(argument); | |
3c4f2dd8 | 229 | // Add the gcode to the queue ourselves if we need it |
bfca9c55 JM |
230 | if (!(match_input_on_gcode(gcode) || match_input_off_gcode(gcode))) { |
231 | return; | |
3c4f2dd8 | 232 | } |
3c4f2dd8 | 233 | |
f181397a JM |
234 | // we need to sync this with the queue, so we need to wait for queue to empty, however due to certain slicers |
235 | // issuing redundant swicth on calls regularly we need to optimize by making sure the value is actually changing | |
236 | // hence we need to do the wait for queue in each case rather than just once at the start | |
bf9e8a8f | 237 | if(match_input_on_gcode(gcode)) { |
def93cb3 JM |
238 | if (this->output_type == SIGMADELTA) { |
239 | // SIGMADELTA output pin turn on (or off if S0) | |
ffb9f8fa | 240 | if(gcode->has_letter('S')) { |
de081023 | 241 | int v = round(gcode->get_value('S') * sigmadelta_pin->max_pwm() / 255.0); // scale by max_pwm so input of 255 and max_pwm of 128 would set value to 128 |
f181397a JM |
242 | if(v != this->sigmadelta_pin->get_pwm()){ // optimize... ignore if already set to the same pwm |
243 | // drain queue | |
244 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
245 | this->sigmadelta_pin->pwm(v); | |
246 | this->switch_state= (v > 0); | |
247 | } | |
ffb9f8fa | 248 | } else { |
f181397a JM |
249 | // drain queue |
250 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
def93cb3 | 251 | this->sigmadelta_pin->pwm(this->switch_value); |
6a1bd7a2 | 252 | this->switch_state= (this->switch_value > 0); |
ffb9f8fa | 253 | } |
def93cb3 JM |
254 | |
255 | } else if (this->output_type == HWPWM) { | |
f181397a JM |
256 | // drain queue |
257 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
def93cb3 JM |
258 | // PWM output pin set duty cycle 0 - 100 |
259 | if(gcode->has_letter('S')) { | |
de081023 | 260 | float v = gcode->get_value('S'); |
def93cb3 JM |
261 | if(v > 100) v= 100; |
262 | else if(v < 0) v= 0; | |
e7b2ebb4 | 263 | this->pwm_pin->write(v/100.0F); |
def93cb3 JM |
264 | this->switch_state= (v != 0); |
265 | } else { | |
266 | this->pwm_pin->write(this->switch_value); | |
267 | this->switch_state= (this->switch_value != 0); | |
268 | } | |
269 | ||
270 | } else if (this->output_type == DIGITAL) { | |
f181397a JM |
271 | // drain queue |
272 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
bf9e8a8f | 273 | // logic pin turn on |
def93cb3 | 274 | this->digital_pin->set(true); |
6a1bd7a2 | 275 | this->switch_state = true; |
df27a6a3 | 276 | } |
def93cb3 | 277 | |
bf9e8a8f | 278 | } else if(match_input_off_gcode(gcode)) { |
f181397a JM |
279 | // drain queue |
280 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
be5f889c | 281 | this->switch_state = false; |
def93cb3 JM |
282 | if (this->output_type == SIGMADELTA) { |
283 | // SIGMADELTA output pin | |
284 | this->sigmadelta_pin->set(false); | |
285 | ||
286 | } else if (this->output_type == HWPWM) { | |
287 | this->pwm_pin->write(0); | |
288 | ||
289 | } else if (this->output_type == DIGITAL) { | |
bf9e8a8f | 290 | // logic pin turn off |
def93cb3 | 291 | this->digital_pin->set(false); |
bf9e8a8f | 292 | } |
26ef0ec6 | 293 | } |
cc1d3b1f | 294 | } |
26ef0ec6 | 295 | |
bf9e8a8f JM |
296 | void Switch::on_get_public_data(void *argument) |
297 | { | |
298 | PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument); | |
2f5d0afa L |
299 | |
300 | if(!pdr->starts_with(switch_checksum)) return; | |
301 | ||
302 | if(!pdr->second_element_is(this->name_checksum)) return; // likely fan, but could be anything | |
303 | ||
304 | // ok this is targeted at us, so send back the requested data | |
564cf1f0 | 305 | // caller has provided the location to write the state to |
1ae7e276 JM |
306 | struct pad_switch *pad= static_cast<struct pad_switch *>(pdr->get_data_ptr()); |
307 | pad->name = this->name_checksum; | |
308 | pad->state = this->switch_state; | |
309 | pad->value = this->switch_value; | |
2f5d0afa L |
310 | pdr->set_taken(); |
311 | } | |
312 | ||
bf9e8a8f JM |
313 | void Switch::on_set_public_data(void *argument) |
314 | { | |
315 | PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument); | |
2f5d0afa L |
316 | |
317 | if(!pdr->starts_with(switch_checksum)) return; | |
318 | ||
319 | if(!pdr->second_element_is(this->name_checksum)) return; // likely fan, but could be anything | |
320 | ||
321 | // ok this is targeted at us, so set the value | |
322 | if(pdr->third_element_is(state_checksum)) { | |
bf9e8a8f | 323 | bool t = *static_cast<bool *>(pdr->get_data_ptr()); |
2f5d0afa L |
324 | this->switch_state = t; |
325 | pdr->set_taken(); | |
e31ee311 JM |
326 | this->switch_changed= true; |
327 | ||
bf9e8a8f JM |
328 | } else if(pdr->third_element_is(value_checksum)) { |
329 | float t = *static_cast<float *>(pdr->get_data_ptr()); | |
2f5d0afa | 330 | this->switch_value = t; |
ae91dea4 | 331 | this->switch_changed= true; |
2f5d0afa L |
332 | pdr->set_taken(); |
333 | } | |
334 | } | |
335 | ||
bf9e8a8f JM |
336 | void Switch::on_main_loop(void *argument) |
337 | { | |
338 | if(this->switch_changed) { | |
339 | if(this->switch_state) { | |
e31ee311 | 340 | if(!this->output_on_command.empty()) this->send_gcode( this->output_on_command, &(StreamOutput::NullStream) ); |
def93cb3 JM |
341 | |
342 | if(this->output_type == SIGMADELTA) { | |
343 | this->sigmadelta_pin->pwm(this->switch_value); // this requires the value has been set otherwise it switches on to whatever it last was | |
344 | ||
345 | } else if (this->output_type == HWPWM) { | |
e7b2ebb4 | 346 | this->pwm_pin->write(this->switch_value/100.0F); |
def93cb3 JM |
347 | |
348 | } else if (this->output_type == DIGITAL) { | |
349 | this->digital_pin->set(true); | |
e31ee311 JM |
350 | } |
351 | ||
bf9e8a8f | 352 | } else { |
def93cb3 | 353 | |
e31ee311 | 354 | if(!this->output_off_command.empty()) this->send_gcode( this->output_off_command, &(StreamOutput::NullStream) ); |
def93cb3 JM |
355 | |
356 | if(this->output_type == SIGMADELTA) { | |
357 | this->sigmadelta_pin->set(false); | |
358 | ||
359 | } else if (this->output_type == HWPWM) { | |
360 | this->pwm_pin->write(0); | |
361 | ||
362 | } else if (this->output_type == DIGITAL) { | |
363 | this->digital_pin->set(false); | |
e31ee311 | 364 | } |
e62e8f7c | 365 | } |
bf9e8a8f | 366 | this->switch_changed = false; |
e62e8f7c L |
367 | } |
368 | } | |
369 | ||
bf9e8a8f | 370 | // TODO Make this use InterruptIn |
bf9e8a8f JM |
371 | // Check the state of the button and act accordingly |
372 | uint32_t Switch::pinpoll_tick(uint32_t dummy) | |
373 | { | |
374 | if(!input_pin.connected()) return 0; | |
375 | ||
26ef0ec6 | 376 | // If pin changed |
e62e8f7c | 377 | bool current_state = this->input_pin.get(); |
bf9e8a8f | 378 | if(this->input_pin_state != current_state) { |
e62e8f7c | 379 | this->input_pin_state = current_state; |
26ef0ec6 | 380 | // If pin high |
bf9e8a8f | 381 | if( this->input_pin_state ) { |
26ef0ec6 | 382 | // if switch is a toggle switch |
bf9e8a8f | 383 | if( this->input_pin_behavior == toggle_checksum ) { |
26ef0ec6 | 384 | this->flip(); |
bf9e8a8f JM |
385 | // else default is momentary |
386 | } else { | |
26ef0ec6 L |
387 | this->flip(); |
388 | } | |
bf9e8a8f JM |
389 | // else if button released |
390 | } else { | |
26ef0ec6 | 391 | // if switch is momentary |
10702aa5 | 392 | if( this->input_pin_behavior == momentary_checksum ) { |
26ef0ec6 L |
393 | this->flip(); |
394 | } | |
395 | } | |
396 | } | |
397 | return 0; | |
398 | } | |
399 | ||
bf9e8a8f JM |
400 | void Switch::flip() |
401 | { | |
26ef0ec6 | 402 | this->switch_state = !this->switch_state; |
e62e8f7c | 403 | this->switch_changed = true; |
26ef0ec6 L |
404 | } |
405 | ||
bf9e8a8f JM |
406 | void Switch::send_gcode(std::string msg, StreamOutput *stream) |
407 | { | |
26ef0ec6 L |
408 | struct SerialMessage message; |
409 | message.message = msg; | |
410 | message.stream = stream; | |
314ab8f7 | 411 | THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message ); |
26ef0ec6 L |
412 | } |
413 |