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