From 288aa117f8179aa7f3fcaffd72d58290d5029f48 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Thu, 17 Feb 2022 03:35:08 +0200 Subject: [PATCH] Remapable IO --- CHANGELOG.md | 4 + jshint.json | 5 +- scripts/avr109-flash | 47 +- scripts/install.sh | 19 +- src/avr/src/config.h | 3 +- src/avr/src/drv8711.c | 1 + src/avr/src/io.c | 211 +- src/avr/src/io.h | 7 +- src/avr/src/pins.h | 1 + src/avr/src/usart.c | 23 +- src/avr/src/vars.def | 16 +- src/avr/src/vfd_spindle.c | 121 +- src/js/app.js | 4 + src/js/breakout.js | 110 + src/js/indicators.js | 82 +- src/js/io-functions.js | 89 + src/js/io-indicator.js | 157 +- src/js/io-pins.js | 72 + src/js/main.js | 82 +- src/js/mapped-io.js | 70 + src/js/power.js | 82 + src/js/settings-io.js | 29 +- src/js/settings-motor.js | 4 +- src/js/templated-select.js | 40 + src/pug/templates/breakout.pug | 55 + src/pug/templates/db15-breakout.pug | 57 + src/pug/templates/db25-breakout.pug | 58 + src/pug/templates/indicators.pug | 263 +- src/pug/templates/io-functions.pug | 46 + src/pug/templates/io-pins.pug | 46 + src/pug/templates/mapped-io.pug | 32 + src/pug/templates/power.pug | 153 + src/pug/templates/settings-admin.pug | 1 - src/pug/templates/settings-io.pug | 39 +- src/pug/templates/settings-tool.pug | 7 + src/pug/templates/templated-select.pug | 30 + src/pug/templates/view-control.pug | 15 +- src/pwr2/main.c | 4 - src/py/bbctrl/Comm.py | 1 + src/py/bbctrl/Preplanner.py | 7 +- src/py/bbctrl/State.py | 4 +- src/py/bbctrl/Web.py | 87 +- src/resources/config-template.json | 36 +- src/resources/images/DB15_breakout_box.png | Bin 103735 -> 0 bytes src/resources/images/DB25_breakout_box.png | Bin 425187 -> 0 bytes src/resources/images/db15_breakout.svg | 1519 +++++++++ src/resources/images/db25_breakout.svg | 3593 ++++++++++++++++++++ src/stylus/indicators.styl | 59 +- src/stylus/view-settings.styl | 7 + 49 files changed, 6643 insertions(+), 755 deletions(-) create mode 100644 src/js/breakout.js create mode 100644 src/js/io-functions.js create mode 100644 src/js/io-pins.js create mode 100644 src/js/mapped-io.js create mode 100644 src/js/power.js create mode 100644 src/js/templated-select.js create mode 100644 src/pug/templates/breakout.pug create mode 100644 src/pug/templates/db15-breakout.pug create mode 100644 src/pug/templates/db25-breakout.pug create mode 100644 src/pug/templates/io-functions.pug create mode 100644 src/pug/templates/io-pins.pug create mode 100644 src/pug/templates/mapped-io.pug create mode 100644 src/pug/templates/power.pug create mode 100644 src/pug/templates/templated-select.pug delete mode 100644 src/resources/images/DB15_breakout_box.png delete mode 100644 src/resources/images/DB25_breakout_box.png create mode 100644 src/resources/images/db15_breakout.svg create mode 100644 src/resources/images/db25_breakout.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index f175d55d..f5cb81af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Buildbotics CNC Controller Firmware Changelog ============================================= +## v1.0.2 + - Remappable IO + ## v1.0.1 - Handle case correctly when assigning named GCode variables. - Increased gamepad deadband to 15% but with rescaling to improve precision. @@ -11,6 +14,7 @@ Buildbotics CNC Controller Firmware Changelog - Correction for voltage measurements. - Removed load current and faults for newer hardware from indicators and LCD. - Added DB15 breakout box to indicators page. + - Send stop command to VFD on estop. ## v1.0.0 - Added online GCode editor. diff --git a/jshint.json b/jshint.json index 93298410..42cb1f9e 100644 --- a/jshint.json +++ b/jshint.json @@ -3,6 +3,7 @@ "browser": true, "devel": true, "strict": "global", + "esversion": 6, "globals": { "$": false, "require": false, @@ -11,6 +12,8 @@ "SockJS": false, "Gauge": false, "Clusterize": false, - "CodeMirror": false + "CodeMirror": false, + "Chart": false, + "THREE": false } } diff --git a/scripts/avr109-flash b/scripts/avr109-flash index 2fec9366..f7ca79d5 100755 --- a/scripts/avr109-flash +++ b/scripts/avr109-flash @@ -1,15 +1,14 @@ #!/usr/bin/env python3 import sys +import os import time import serial import binascii +import argparse -dev = '/dev/ttyAMA0' -baud = 921600 boot_id = 'bbctrl ' -verbose = False def crc16(data): @@ -70,41 +69,57 @@ def read_intel_hex(f): def send(data): - if verbose: print('Sending:', data) + if args.verbose: print('Sending:', data) sp.write(bytes(data, 'utf8')) def send_int(x, size): - if verbose: print('Sending: %d', x) + if args.verbose: print('Sending: %d', x) sp.write((x).to_bytes(size, byteorder = 'big')) def recv(count): data = sp.read(count).decode('utf8') - if count and verbose: print('Received:', data) + if count and args.verbose: print('Received:', data) return data def recv_int(size): x = int.from_bytes(sp.read(size), byteorder = 'big') - if verbose: print('Received:', x) + if args.verbose: print('Received:', x) return x +# Parse arguments +parser = argparse.ArgumentParser(description = 'Program AVR over serial port ' + 'using the AVR109 bootloader protocol.') +parser.add_argument('-b', '--baud', default = 921600, type = int, + help = 'Set baud rate.') +parser.add_argument('-t', '--timeout', default = 10, type = int, + help = 'Timeout in seconds.') +parser.add_argument('-d', '--device', default = '/dev/ttyAMA0', + help = 'Serial device.') +parser.add_argument('-r', '--reset', default = 27, type = int, + help = 'Reset line GPIO.') +parser.add_argument('-v', '--verbose', help = 'Enable verbose logging', + action = 'count', default = 0) +parser.add_argument('file', help = 'The Intel hex file to program.') +args = parser.parse_args() # Read firmware hex file -data = list(read_intel_hex(open(sys.argv[1], 'r'))) +data = list(read_intel_hex(open(args.file, 'r'))) # Open serial port -sp = serial.Serial(dev, baud, timeout = 10) +sp = serial.Serial(args.device, args.baud, timeout = args.timeout) # Reset AVR -import RPi.GPIO as gpio -gpio.setwarnings(False) -gpio.setmode(gpio.BCM) -gpio.setup(27, gpio.OUT) -gpio.output(27, 0) -gpio.output(27, 1) -gpio.setup(27, gpio.IN, pull_up_down = gpio.PUD_UP) +reset_gpio_path = '/sys/class/gpio/gpio%s' % args.reset +if not os.path.exists(reset_gpio_path): + with open('/sys/class/gpio/export', 'w') as f: + f.write(str(args.reset)) + +with open(reset_gpio_path + '/direction', 'w') as f: f.write('out') +with open(reset_gpio_path + '/value', 'w') as f: f.write('0') +with open(reset_gpio_path + '/value', 'w') as f: f.write('1') time.sleep(0.1) # Sync diff --git a/scripts/install.sh b/scripts/install.sh index 9a86d4f2..47c95fe6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -14,7 +14,21 @@ while [ $# -gt 0 ]; do shift 1 done -service bbctrl stop +if [ -e /etc/systemd/system/bbctrl.service ]; then + service bbctrl stop +fi + +# Find devices +if [ -e /dev/ttyAMA0 ]; then + AVR_DEV=/dev/ttyAMA0 + AVR_RESET=27 +elif [ -e /dev/ttyS2 ]; then + AVR_DEV=/dev/ttyS2 + AVR_RESET=117 +else + >&2 echo "Cannot find AVR serial device." + UPDATE_AVR=false +fi if $UPDATE_PY; then # Update service @@ -26,7 +40,8 @@ if $UPDATE_PY; then fi if $UPDATE_AVR; then - ./scripts/avr109-flash src/avr/bbctrl-avr-firmware.hex + ./scripts/avr109-flash -d $AVR_DEV -r $AVR_RESET \ + src/avr/bbctrl-avr-firmware.hex fi # Update config.txt diff --git a/src/avr/src/config.h b/src/avr/src/config.h index 5ef516c0..03da59c4 100644 --- a/src/avr/src/config.h +++ b/src/avr/src/config.h @@ -94,8 +94,9 @@ enum { #define AXES 6 // number of axes #define MOTORS 4 // number of motors on the board +#define INS 6 // number of supported pin outputs #define OUTS 10 // number of supported pin outputs -#define ANALOGS 4 // number of supported analog inputs +#define ANALOGS 2 // number of supported analog inputs #define DIGITALS 4 // number of supported digital inputs #define VFDREG 32 // number of supported VFD modbus registers #define IO_PINS 17 // number of supported i/o pins diff --git a/src/avr/src/drv8711.c b/src/avr/src/drv8711.c index 2e0c9ed1..301e6500 100644 --- a/src/avr/src/drv8711.c +++ b/src/avr/src/drv8711.c @@ -174,6 +174,7 @@ static uint8_t _driver_get_torque(drv8711_driver_t *drv) { } } + static uint16_t _driver_get_torque_reg(drv8711_driver_t *drv) { uint16_t reg; diff --git a/src/avr/src/io.c b/src/avr/src/io.c index 270de293..0649980f 100644 --- a/src/avr/src/io.c +++ b/src/avr/src/io.c @@ -32,11 +32,7 @@ #include -#define IO_MIN_INDEX 'a' -#define IO_MAX_INDEX 'q' - -#define IF_INVALID_INDEX_RETURN \ - if (index < IO_MIN_INDEX || IO_MAX_INDEX < index) return +#define IO_MAX_INDEX 17 // macros for finding io function given the axis number #define MIN_INPUT(axis) ((io_function_t)(INPUT_MOTOR_0_MIN + axis)) @@ -65,6 +61,7 @@ typedef struct { typedef struct { uint8_t pin; + uint8_t types; uint8_t adc_ch; uint8_t adc_mux; io_function_t function; @@ -80,31 +77,32 @@ typedef struct { static io_pin_t _pins[] = { - {IO_01_PIN, IO_INVALID}, - {IO_02_PIN, IO_INVALID}, - {IO_03_PIN, 1, ADC_CH_MUXPOS_PIN0_gc}, - {IO_04_PIN, 1, ADC_CH_MUXPOS_PIN1_gc}, - {IO_05_PIN, 1, ADC_CH_MUXPOS_PIN4_gc}, - {IO_08_PIN, 1, ADC_CH_MUXPOS_PIN5_gc}, - {IO_09_PIN, 1, ADC_CH_MUXPOS_PIN6_gc}, - {IO_10_PIN, 1, ADC_CH_MUXPOS_PIN7_gc}, - {IO_11_PIN, 1, ADC_CH_MUXPOS_PIN2_gc}, - {IO_12_PIN, 1, ADC_CH_MUXPOS_PIN3_gc}, - {IO_15_PIN, 0, ADC_CH_MUXPOS_PIN5_gc}, - {IO_16_PIN, 0, ADC_CH_MUXPOS_PIN4_gc}, - {IO_18_PIN, 0, ADC_CH_MUXPOS_PIN7_gc}, - {IO_21_PIN, IO_INVALID}, - {IO_22_PIN, IO_INVALID}, - {IO_23_PIN, IO_INVALID}, - {IO_24_PIN, 0, ADC_CH_MUXPOS_PIN6_gc}, + // Remapable pins + {IO_01_PIN, IO_TYPE_OUTPUT}, + {IO_02_PIN, IO_TYPE_OUTPUT}, + {IO_03_PIN, IO_TYPE_INPUT}, + {IO_04_PIN, IO_TYPE_INPUT}, + {IO_05_PIN, IO_TYPE_INPUT}, + {IO_08_PIN, IO_TYPE_INPUT}, + {IO_09_PIN, IO_TYPE_INPUT}, + {IO_10_PIN, IO_TYPE_INPUT}, + {IO_11_PIN, IO_TYPE_INPUT}, + {IO_12_PIN, IO_TYPE_INPUT}, + {IO_15_PIN, IO_TYPE_OUTPUT}, + {IO_16_PIN, IO_TYPE_OUTPUT}, + {IO_18_PIN, IO_TYPE_ANALOG, 0, ADC_CH_MUXPOS_PIN7_gc}, + {IO_21_PIN, IO_TYPE_OUTPUT}, + {IO_22_PIN, IO_TYPE_INPUT}, + {IO_23_PIN, IO_TYPE_INPUT}, + {IO_24_PIN, IO_TYPE_ANALOG, 0, ADC_CH_MUXPOS_PIN6_gc}, // Hard wired pins - {STALL_0_PIN, IO_INVALID, 0, INPUT_STALL_0}, - {STALL_1_PIN, IO_INVALID, 0, INPUT_STALL_1}, - {STALL_2_PIN, IO_INVALID, 0, INPUT_STALL_2}, - {STALL_3_PIN, IO_INVALID, 0, INPUT_STALL_3}, - {MOTOR_FAULT_PIN, IO_INVALID, 0, INPUT_MOTOR_FAULT}, - {TEST_PIN, IO_INVALID, 0, OUTPUT_TEST}, + {STALL_0_PIN, IO_TYPE_INPUT, 0, 0, INPUT_STALL_0, HI_LO}, + {STALL_1_PIN, IO_TYPE_INPUT, 0, 0, INPUT_STALL_1, HI_LO}, + {STALL_2_PIN, IO_TYPE_INPUT, 0, 0, INPUT_STALL_2, HI_LO}, + {STALL_3_PIN, IO_TYPE_INPUT, 0, 0, INPUT_STALL_3, HI_LO}, + {MOTOR_FAULT_PIN, IO_TYPE_INPUT, 0, 0, INPUT_MOTOR_FAULT, HI_LO}, + {TEST_PIN, IO_TYPE_OUTPUT, 0, 0, OUTPUT_TEST}, {0}, // Sentinal }; @@ -115,16 +113,29 @@ static float _analog_ports[ANALOGS]; static io_function_t _output_to_function(int id) { switch (id) { - case 0: return OUTPUT_0; - case 1: return OUTPUT_1; - case 2: return OUTPUT_2; - case 3: return OUTPUT_3; - case 4: return OUTPUT_MIST; - case 5: return OUTPUT_FLOOD; - case 6: return OUTPUT_FAULT; - case 7: return OUTPUT_TOOL_DIRECTION; - case 8: return OUTPUT_TOOL_ENABLE; - case 9: return OUTPUT_TEST; + case 0: return OUTPUT_0; + case 1: return OUTPUT_1; + case 2: return OUTPUT_2; + case 3: return OUTPUT_3; + case 4: return OUTPUT_MIST; + case 5: return OUTPUT_FLOOD; + case 6: return OUTPUT_FAULT; + case 7: return OUTPUT_TOOL_ENABLE; + case 8: return OUTPUT_TOOL_DIRECTION; + case 9: return OUTPUT_TEST; + default: return IO_DISABLED; + } +} + + +static io_function_t _input_to_function(int id) { + switch (id) { + case 0: return INPUT_0; + case 1: return INPUT_1; + case 2: return INPUT_2; + case 3: return INPUT_3; + case 4: return INPUT_ESTOP; + case 5: return INPUT_PROBE; default: return IO_DISABLED; } } @@ -135,7 +146,14 @@ static uint8_t _function_to_analog_port(io_function_t function) { } +static bool _is_valid(io_function_t function, io_type_t type) { + return io_get_type(function) == type; +} + + static void _state_set_active(io_function_t function, bool active) { + if (!_is_valid(function, IO_TYPE_INPUT)) return; + io_func_state_t *state = &_func_state[function]; if (active) { @@ -149,23 +167,6 @@ static void _state_set_active(io_function_t function, bool active) { } -static bool _is_valid(io_function_t function, io_type_t type) { - return io_get_type(function) == type; -} - - -static bool _is_input_active(io_pin_t *pin) { - if (!_is_valid(pin->function, IO_TYPE_INPUT) || !pin->input.initialized) - return false; - - switch (pin->mode) { - case HI_LO: return !pin->input.state; - case LO_HI: return pin->input.state; - default: return false; - } -} - - static uint8_t _mode_state(io_mode_t mode, bool active) { switch (mode) { case LO_HI: return active ? IO_HI : IO_LO; @@ -179,13 +180,39 @@ static uint8_t _mode_state(io_mode_t mode, bool active) { } +static uint8_t _get_pin_output_mode(io_pin_t *pin) { + if (!DIR_PIN(pin->pin)) return IO_TRI; + return OUT_PIN(pin->pin) ? IO_HI : IO_LO; +} + + +static bool _is_active(io_pin_t *pin) { + switch (io_get_type(pin->function)) { + case IO_TYPE_INPUT: + if (!pin->input.initialized) return false; + + switch (pin->mode) { + case HI_LO: return !pin->input.state; + case LO_HI: return pin->input.state; + default: return false; + } + break; + + case IO_TYPE_OUTPUT: + return _get_pin_output_mode(pin) == _mode_state(pin->mode, true); + + default: return false; + } +} + + static void _set_output(io_pin_t *pin, bool active) { uint8_t state = _mode_state(pin->mode, active); switch (state) { - case 0: OUTCLR_PIN(pin->pin); DIRSET_PIN(pin->pin); break; - case 1: OUTSET_PIN(pin->pin); DIRSET_PIN(pin->pin); break; - default: DIRCLR_PIN(pin->pin); break; + case IO_LO: OUTCLR_PIN(pin->pin); DIRSET_PIN(pin->pin); break; + case IO_HI: OUTSET_PIN(pin->pin); DIRSET_PIN(pin->pin); break; + default: DIRCLR_PIN(pin->pin); break; } } @@ -196,7 +223,7 @@ static void _set_function(io_pin_t *pin, io_function_t function) { switch (oldType) { case IO_TYPE_INPUT: // Release active input - if (_is_input_active(pin)) _state_set_active(pin->function, false); + if (_is_active(pin)) _state_set_active(pin->function, false); memset(&pin->input, 0, sizeof(pin->input)); // Reset input state break; @@ -208,8 +235,10 @@ static void _set_function(io_pin_t *pin, io_function_t function) { } pin->function = function; + io_type_t newType = io_get_type(function); + if (!(pin->types & newType)) newType = IO_TYPE_DISABLED; - switch (io_get_type(function)) { + switch (newType) { case IO_TYPE_INPUT: PINCTRL_PIN(pin->pin) = PORT_OPC_PULLUP_gc; // Pull up DIRCLR_PIN(pin->pin); // Input @@ -221,13 +250,9 @@ static void _set_function(io_pin_t *pin, io_function_t function) { case IO_TYPE_ANALOG: _analog_ports[_function_to_analog_port(function)] = 0; - - if (pin->adc_ch == IO_INVALID) pin->function = IO_DISABLED; - else { - PINCTRL_PIN(pin->pin) = 0; // Disable pull up - DIRCLR_PIN(pin->pin); // Input - break; - } + PINCTRL_PIN(pin->pin) = 0; // Disable pull up + DIRCLR_PIN(pin->pin); // Input + break; // Fall through if analog disabled @@ -408,12 +433,15 @@ void io_rtc_callback() { bool state = IN_PIN(pin->pin); if (state == input->state && input->initialized) input->debounce = 0; else if (++input->debounce == _io.debounce) { + bool first = !input->initialized; + input->state = state; input->debounce = 0; input->initialized = true; input->lockout = _io.lockout; - _state_set_active(pin->function, _is_input_active(pin)); + if (!first || _is_active(pin)) + _state_set_active(pin->function, _is_active(pin)); } } } @@ -438,46 +466,48 @@ static uint8_t _get_state(io_function_t function) { if (!_is_valid(function, IO_TYPE_INPUT) || !io_is_enabled(function)) return IO_INVALID; - return _func_state[function].active; + return !!_func_state[function].active; } // Var callbacks uint8_t get_io_function(int index) { - IF_INVALID_INDEX_RETURN IO_DISABLED; - return _pins[index].function; + return IO_MAX_INDEX <= index ? IO_DISABLED : _pins[index].function; } void set_io_function(int index, uint8_t function) { - IF_INVALID_INDEX_RETURN; - if (IO_FUNCTION_COUNT <= function || _pins[index].function == function) - return; + if (IO_MAX_INDEX <= index || IO_FUNCTION_COUNT <= function || + _pins[index].function == function) return; _set_function(&_pins[index], (io_function_t)function); } uint8_t get_io_mode(int index) { - IF_INVALID_INDEX_RETURN 0; - return _pins[index].mode; + return IO_MAX_INDEX <= index ? 0 : _pins[index].mode; } void set_io_mode(int index, uint8_t mode) { - IF_INVALID_INDEX_RETURN; - if (LO_TRI < mode) return; + if (IO_MAX_INDEX <= index || LO_TRI < mode) return; - // Changing the mode may change the input state io_pin_t *pin = &_pins[index]; - bool wasActive = _is_input_active(pin); + bool wasActive = _is_active(pin); pin->mode = (io_mode_t)mode; - bool isActive = _is_input_active(pin); - if (wasActive != isActive) _state_set_active(pin->function, isActive); + bool isActive = _is_active(pin); + + // Changing the mode may change the pin state + if (wasActive != isActive) + switch (io_get_type(pin->function)) { + case IO_TYPE_OUTPUT: _set_output(pin, wasActive); break; + case IO_TYPE_INPUT: _state_set_active(pin->function, isActive); break; + default: break; + } } -uint8_t get_io_state(int pin) {return _is_input_active(&_pins[pin]);} +uint8_t get_io_state(int pin) {return _is_active(&_pins[pin]);} void set_input_debounce(uint16_t debounce) { @@ -494,26 +524,21 @@ void set_input_lockout(uint16_t lockout) { uint16_t get_input_lockout() {return _io.lockout;} - - uint8_t get_min_input(int axis) {return _get_state(MIN_INPUT(axis));} uint8_t get_max_input(int axis) {return _get_state(MAX_INPUT(axis));} -uint8_t get_estop_input() {return _get_state(INPUT_ESTOP);} -uint8_t get_probe_input() {return _get_state(INPUT_PROBE);} +uint8_t get_input(int index) {return _get_state(_input_to_function(index));} -bool get_output_active(int id) { - return _func_state[_output_to_function(id)].active; +uint8_t get_output_active(int index) { + return _func_state[_output_to_function(index)].active; } -void set_output_active(int id, bool active) { - io_function_t f = _output_to_function(id); - if (f != IO_DISABLED) _func_state[f].active = active; +void set_output_active(int index, uint8_t active) { + io_set_output(_output_to_function(index), active); } float get_analog_input(int port) { - return io_get_analog(io_get_port_function(false, port)); } diff --git a/src/avr/src/io.h b/src/avr/src/io.h index 4a731836..39a18f04 100644 --- a/src/avr/src/io.h +++ b/src/avr/src/io.h @@ -33,6 +33,8 @@ // Must be kept in synch with resources/config-template.json typedef enum { IO_DISABLED, + + // Mappable functions INPUT_MOTOR_0_MAX, INPUT_MOTOR_1_MAX, INPUT_MOTOR_2_MAX, INPUT_MOTOR_3_MAX, INPUT_MOTOR_0_MIN, INPUT_MOTOR_1_MIN, INPUT_MOTOR_2_MIN, INPUT_MOTOR_3_MIN, INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_ESTOP, INPUT_PROBE, @@ -54,7 +56,10 @@ typedef enum {LO_HI, HI_LO, TRI_LO, TRI_HI, LO_TRI, HI_TRI} io_mode_t; typedef enum { - IO_TYPE_DISABLED, IO_TYPE_INPUT, IO_TYPE_OUTPUT, IO_TYPE_ANALOG + IO_TYPE_DISABLED = 0, + IO_TYPE_INPUT = 1 << 0, + IO_TYPE_OUTPUT = 1 << 1, + IO_TYPE_ANALOG = 1 << 2, } io_type_t; diff --git a/src/avr/src/pins.h b/src/avr/src/pins.h index 43252e25..c52211c0 100644 --- a/src/avr/src/pins.h +++ b/src/avr/src/pins.h @@ -43,6 +43,7 @@ extern PORT_t *pin_ports[]; #define DIRSET_PIN(PIN) PIN_PORT(PIN)->DIRSET = PIN_BM(PIN) #define DIRCLR_PIN(PIN) PIN_PORT(PIN)->DIRCLR = PIN_BM(PIN) +#define DIR_PIN(PIN) (!!(PIN_PORT(PIN)->DIR & PIN_BM(PIN))) #define OUTCLR_PIN(PIN) PIN_PORT(PIN)->OUTCLR = PIN_BM(PIN) #define OUTSET_PIN(PIN) PIN_PORT(PIN)->OUTSET = PIN_BM(PIN) #define OUTTGL_PIN(PIN) PIN_PORT(PIN)->OUTTGL = PIN_BM(PIN) diff --git a/src/avr/src/usart.c b/src/avr/src/usart.c index d5345af6..19811dd7 100644 --- a/src/avr/src/usart.c +++ b/src/avr/src/usart.c @@ -51,6 +51,8 @@ #include "ringbuf.def" static bool _flush = false; +static uint16_t rx_max_fill = 0; +static uint16_t cts_thresh = SERIAL_CTS_THRESH; static void _set_dre_interrupt(bool enable) { @@ -61,7 +63,7 @@ static void _set_dre_interrupt(bool enable) { static void _set_rxc_interrupt(bool enable) { if (enable) { - if (SERIAL_CTS_THRESH <= rx_buf_space()) + if (cts_thresh <= rx_buf_space()) OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable) SERIAL_PORT.CTRLA |= USART_RXCINTLVL_HI_gc; @@ -84,9 +86,14 @@ ISR(SERIAL_DRE_vect) { // Data received interrupt vector ISR(SERIAL_RXC_vect) { if (rx_buf_full()) _set_rxc_interrupt(false); // Disable interrupt - else rx_buf_push(SERIAL_PORT.DATA); + else { + rx_buf_push(SERIAL_PORT.DATA); + + uint16_t fill = rx_buf_fill(); + if (rx_max_fill < fill) rx_max_fill = fill; + } - if (rx_buf_space() < SERIAL_CTS_THRESH) + if (rx_buf_space() < cts_thresh) OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable) } @@ -279,6 +286,12 @@ void usart_flush() { void usart_rx_flush() {rx_buf_init();} int16_t usart_rx_space() {return rx_buf_space();} -int16_t usart_rx_fill() {return rx_buf_fill();} +int16_t usart_rx_fill() {return rx_buf_fill();} int16_t usart_tx_space() {return tx_buf_space();} -int16_t usart_tx_fill() {return tx_buf_fill();} +int16_t usart_tx_fill() {return tx_buf_fill();} + + +// Variable callbacks +uint16_t get_rx_max_fill() {return rx_max_fill;} +void set_cts_thresh(uint16_t x) {cts_thresh = x;} +uint16_t get_cts_thresh() {return cts_thresh;} diff --git a/src/avr/src/vars.def b/src/avr/src/vars.def index a97d33f8..f825e5b3 100644 --- a/src/avr/src/vars.def +++ b/src/avr/src/vars.def @@ -25,11 +25,12 @@ \******************************************************************************/ -#define AXES_LABEL "xyzabc" -#define MOTORS_LABEL "0123" -#define OUTS_LABEL "0123MFfdet" +#define AXES_LABEL "xyzabc" +#define MOTORS_LABEL "0123" +#define OUTS_LABEL "0123MFfedt" +#define INS_LABEL "0123ep" #define ANALOGS_LABEL "0123" -#define VFDREG_LABEL "0123456789abcdefghijklmnopqrstuv" +#define VFDREG_LABEL "0123456789abcdefghijklmnopqrstuv" #define IO_PINS_LABEL "abcdefghijklmnopq" // VAR(name, code, type, index, settable, report) @@ -78,9 +79,8 @@ VAR(input_debounce, sd, u16, 0, 1, 1) // Input debounce time in ms VAR(input_lockout, sc, u16, 0, 1, 1) // Input lockout time in ms VAR(min_input, lw, u8, MOTORS, 0, 1) // Minimum switch input state VAR(max_input, xw, u8, MOTORS, 0, 1) // Maximum switch input state -VAR(estop_input, ew, u8, 0, 0, 1) // Estop input state -VAR(probe_input, pw, u8, 0, 0, 1) // Probe input state -VAR(output_active, oa, b8, OUTS, 1, 1) // Digital output active +VAR(input, w, u8, INS, 0, 1) // Digital input state +VAR(output_active, oa, u8, OUTS, 1, 1) // Digital output active VAR(analog_input, ai, f32, ANALOGS, 0, 0) // Analog input state // Spindle @@ -143,3 +143,5 @@ VAR(state_count, xc, u16, 0, 0, 1) // Machine state change count VAR(hold_reason, pr, pstr, 0, 0, 1) // Machine pause reason VAR(underrun, un, u32, 0, 0, 1) // Stepper buffer underrun count VAR(dwell_time, dt, f32, 0, 0, 1) // Dwell timer +VAR(rx_max_fill, rf, u16, 0, 0, 1) // UART receive buffer max fill +VAR(cts_thresh, ct, u16, 0, 1, 1) // UART receive buffer max fill diff --git a/src/avr/src/vfd_spindle.c b/src/avr/src/vfd_spindle.c index 4976e1fc..5cd392c0 100644 --- a/src/avr/src/vfd_spindle.c +++ b/src/avr/src/vfd_spindle.c @@ -87,69 +87,75 @@ const vfd_reg_t ac_tech_regs[] PROGMEM = { {REG_REV_WRITE, 1, 64}, // Reverse {REG_REV_WRITE, 1, 8}, // Start drive {REG_FREQ_ACTECH_READ, 24, 0}, // Actual freq + {REG_DISCONNECT_WRITE, 1, 4}, // Stop on disconnect {REG_DISCONNECT_WRITE, 1, 2}, // Lock controls and parameters {REG_DISABLED}, }; const vfd_reg_t nowforever_regs[] PROGMEM = { - {REG_MAX_FREQ_READ, 7, 0}, // Max frequency - {REG_FREQ_SET, 2305, 0}, // Frequency - {REG_STOP_WRITE, 2304, 0}, // Stop drive - {REG_FWD_WRITE, 2304, 1}, // Forward - {REG_REV_WRITE, 2304, 3}, // Reverse - {REG_FREQ_READ, 1282, 0}, // Output freq - {REG_STATUS_READ, 768, 0}, // Status + {REG_MAX_FREQ_READ, 0x007, 0}, // Max frequency + {REG_FREQ_SET, 0x901, 0}, // Frequency + {REG_STOP_WRITE, 0x900, 0}, // Stop drive + {REG_FWD_WRITE, 0x900, 1}, // Forward + {REG_REV_WRITE, 0x900, 3}, // Reverse + {REG_FREQ_READ, 0x502, 0}, // Output freq + {REG_STATUS_READ, 0x300, 0}, // Status + {REG_DISCONNECT_WRITE, 0x900, 0}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t delta_vfd015m21a_regs[] PROGMEM = { - {REG_CONNECT_WRITE, 0x2002, 2}, // Reset fault - {REG_MAX_FREQ_READ, 3, 0}, // Max frequency - {REG_FREQ_SET, 0x2001, 0}, // Frequency - {REG_STOP_WRITE, 0x2000, 1}, // Stop drive - {REG_FWD_WRITE, 0x2000, 18}, // Forward - {REG_REV_WRITE, 0x2000, 34}, // Reverse - {REG_FREQ_READ, 0x2103, 0}, // Output freq - {REG_STATUS_READ, 0x2100, 0}, // Status + {REG_CONNECT_WRITE, 0x2002, 2}, // Reset fault + {REG_MAX_FREQ_READ, 3, 0}, // Max frequency + {REG_FREQ_SET, 0x2001, 0}, // Frequency + {REG_STOP_WRITE, 0x2000, 1}, // Stop drive + {REG_FWD_WRITE, 0x2000, 18}, // Forward + {REG_REV_WRITE, 0x2000, 34}, // Reverse + {REG_FREQ_READ, 0x2103, 0}, // Output freq + {REG_STATUS_READ, 0x2100, 0}, // Status + {REG_DISCONNECT_WRITE, 0x2000, 1}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t yl600_regs[] PROGMEM = { - {REG_CONNECT_WRITE, 0x2000, 128}, // Reset all errors - {REG_MAX_FREQ_READ, 0x0004, 0}, // Max frequency - {REG_FREQ_SET, 0x2001, 0}, // Frequency - {REG_STOP_WRITE, 0x2000, 1}, // Stop drive - {REG_FWD_WRITE, 0x2000, 18}, // Forward - {REG_REV_WRITE, 0x2000, 34}, // Reverse - {REG_FREQ_READ, 0x200b, 0}, // Output freq - {REG_STATUS_READ, 0x2008, 0}, // Status + {REG_CONNECT_WRITE, 0x2000, 128}, // Reset all errors + {REG_MAX_FREQ_READ, 0x0004, 0}, // Max frequency + {REG_FREQ_SET, 0x2001, 0}, // Frequency + {REG_STOP_WRITE, 0x2000, 1}, // Stop drive + {REG_FWD_WRITE, 0x2000, 18}, // Forward + {REG_REV_WRITE, 0x2000, 34}, // Reverse + {REG_FREQ_READ, 0x200b, 0}, // Output freq + {REG_STATUS_READ, 0x2008, 0}, // Status + {REG_DISCONNECT_WRITE, 0x2000, 1}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t fr_d700_regs[] PROGMEM = { - {REG_MAX_FREQ_READ, 1000, 0}, // Max frequency - {REG_FREQ_SET, 13, 0}, // Frequency - {REG_STOP_WRITE, 8, 1}, // Stop drive - {REG_FWD_WRITE, 8, 2}, // Forward - {REG_REV_WRITE, 8, 4}, // Reverse - {REG_FREQ_READ, 200, 0}, // Output freq + {REG_MAX_FREQ_READ, 1000, 0}, // Max frequency + {REG_FREQ_SET, 13, 0}, // Frequency + {REG_STOP_WRITE, 8, 1}, // Stop drive + {REG_FWD_WRITE, 8, 2}, // Forward + {REG_REV_WRITE, 8, 4}, // Reverse + {REG_FREQ_READ, 200, 0}, // Output freq + {REG_DISCONNECT_WRITE, 8, 1}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t sunfar_e300_regs[] PROGMEM = { - {REG_CONNECT_WRITE, 0x1001, 32}, // Reset all errors - {REG_MAX_FREQ_READ, 0xf004, 0}, // Max frequency F0.4 - {REG_FREQ_SET, 0x1002, 0}, // Frequency - {REG_STOP_WRITE, 0x1001, 3}, // Stop drive - {REG_FWD_WRITE, 0x1001, 1}, // Forward - {REG_REV_WRITE, 0x1001, 2}, // Reverse - {REG_FREQ_READ, 0xd000, 0}, // Output freq d.0 - {REG_STATUS_READ, 0x2000, 0}, // Status + {REG_CONNECT_WRITE, 0x1001, 32}, // Reset all errors + {REG_MAX_FREQ_READ, 0xf004, 0}, // Max frequency F0.4 + {REG_FREQ_SET, 0x1002, 0}, // Frequency + {REG_STOP_WRITE, 0x1001, 3}, // Stop drive + {REG_FWD_WRITE, 0x1001, 1}, // Forward + {REG_REV_WRITE, 0x1001, 2}, // Reverse + {REG_FREQ_READ, 0xd000, 0}, // Output freq d.0 + {REG_STATUS_READ, 0x2000, 0}, // Status + {REG_DISCONNECT_WRITE, 0x1001, 3}, // Stop on disconnect {REG_DISABLED}, }; @@ -170,35 +176,38 @@ const vfd_reg_t omron_mx2_regs[] PROGMEM = { const vfd_reg_t v70_regs[] PROGMEM = { - {REG_MAX_FREQ_READ, 0x0005, 0}, // Maximum operating frequency - {REG_FREQ_SET, 0x0201, 0}, // Set frequency in 0.1Hz - {REG_STOP_WRITE, 0x0200, 0}, // Stop - {REG_FWD_WRITE, 0x0200, 1}, // Run forward - {REG_REV_WRITE, 0x0200, 5}, // Run reverse - {REG_FREQ_READ, 0x0220, 0}, // Read operating frequency - {REG_STATUS_READ, 0x0210, 0}, // Read status + {REG_MAX_FREQ_READ, 0x0005, 0}, // Maximum operating frequency + {REG_FREQ_SET, 0x0201, 0}, // Set frequency in 0.1Hz + {REG_STOP_WRITE, 0x0200, 0}, // Stop + {REG_FWD_WRITE, 0x0200, 1}, // Run forward + {REG_REV_WRITE, 0x0200, 5}, // Run reverse + {REG_FREQ_READ, 0x0220, 0}, // Read operating frequency + {REG_STATUS_READ, 0x0210, 0}, // Read status + {REG_DISCONNECT_WRITE, 0x0200, 0}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t dmm_dyn4_regs[] PROGMEM = { - {REG_MAX_FREQ_FIXED, 0x00, 6000}, // Maximum operating frequency - {REG_FREQ_SIGN_SET, 0x0e, 0}, // Set frequency in 0.1Hz - {REG_STOP_WRITE, 0x0e, 0}, // Stop - {REG_FREQ_SIGN_READ, 0x14, 0}, // Read operating frequency - {REG_STATUS_READ, 0x02, 0}, // Read status + {REG_MAX_FREQ_FIXED, 0x00, 6000}, // Maximum operating frequency + {REG_FREQ_SIGN_SET, 0x0e, 0}, // Set frequency in 0.1Hz + {REG_STOP_WRITE, 0x0e, 0}, // Stop + {REG_FREQ_SIGN_READ, 0x14, 0}, // Read operating frequency + {REG_STATUS_READ, 0x02, 0}, // Read status + {REG_DISCONNECT_WRITE, 0x0e, 0}, // Stop on disconnect {REG_DISABLED}, }; const vfd_reg_t galt_g200_regs[] PROGMEM = { - {REG_MAX_FREQ_READ, 0x0003, 0}, // Read maximum operating frequency in 0.01Hz - {REG_FREQ_SET, 0x2001, 0}, // Set frequency in 0.01Hz - {REG_FREQ_READ, 0x3000, 0}, // Read frequency with 0.01Hz - {REG_FWD_WRITE, 0x2000, 1}, // Run forward - {REG_REV_WRITE, 0x2000, 2}, // Run reverse - {REG_STOP_WRITE, 0x2000, 5}, // Stop - {REG_STATUS_READ, 0x2100, 0}, // Read status + {REG_MAX_FREQ_READ, 0x0003, 0}, // Read max operating frequency in 0.01Hz + {REG_FREQ_SET, 0x2001, 0}, // Set frequency in 0.01Hz + {REG_FREQ_READ, 0x3000, 0}, // Read frequency with 0.01Hz + {REG_FWD_WRITE, 0x2000, 1}, // Run forward + {REG_REV_WRITE, 0x2000, 2}, // Run reverse + {REG_STOP_WRITE, 0x2000, 5}, // Stop + {REG_STATUS_READ, 0x2100, 0}, // Read status + {REG_DISCONNECT_WRITE, 0x2000, 5}, // Stop on disconnect {REG_DISABLED}, }; diff --git a/src/js/app.js b/src/js/app.js index 37ea5904..fadc77bb 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -136,9 +136,13 @@ module.exports = new Vue({ events: { route: function (path) { + let oldView = this.currentView; + if (typeof this.$options.components['view-' + path[0]] == 'undefined') return location.hash = 'control'; else this.currentView = path[0]; + + if (oldView != this.currentView) $('#main').scrollTop(0) }, diff --git a/src/js/breakout.js b/src/js/breakout.js new file mode 100644 index 00000000..274c9ecd --- /dev/null +++ b/src/js/breakout.js @@ -0,0 +1,110 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +function make_tmpl(name, data) { + return { + props: ['state', 'template'], + template: '#' + name + '-template', + data() {return data || {}} + } +} + + +module.exports = { + template: '#breakout-template', + props: ['name', 'state', 'template'], + + data() {return {active_pin: undefined}}, + + + components: { + 'db15-breakout': {'template': '#db15-breakout-template'}, + 'db25-breakout': {'template': '#db25-breakout-template'}, + 'db15-pin-1': {'template': '#db15-fault-template'}, + 'db15-pin-2': make_tmpl('db15-step', {ch: 0}), + 'db15-pin-3': make_tmpl('db15-step', {ch: 1}), + 'db15-pin-4': make_tmpl('db15-step', {ch: 2}), + 'db15-pin-5': make_tmpl('db15-step', {ch: 3}), + 'db15-pin-6': make_tmpl('breakout-5v'), + 'db15-pin-7': make_tmpl('breakout-reserved'), + 'db15-pin-8': make_tmpl('breakout-reserved'), + 'db15-pin-9': make_tmpl('db15-enable'), + 'db15-pin-10': make_tmpl('db15-dir', {ch: 0}), + 'db15-pin-11': make_tmpl('db15-dir', {ch: 1}), + 'db15-pin-12': make_tmpl('db15-dir', {ch: 2}), + 'db15-pin-13': make_tmpl('db15-dir', {ch: 3}), + 'db15-pin-14': make_tmpl('breakout-ground'), + 'db15-pin-15': make_tmpl('breakout-ground'), + 'db15-pin-gnd': make_tmpl('breakout-ground'), + 'db25-pin-1': make_tmpl('db25-io', {pin: 1}), + 'db25-pin-2': make_tmpl('db25-io', {pin: 2}), + 'db25-pin-3': make_tmpl('db25-io', {pin: 3}), + 'db25-pin-4': make_tmpl('db25-io', {pin: 4}), + 'db25-pin-5': make_tmpl('db25-io', {pin: 5}), + 'db25-pin-6': make_tmpl('db25-0to10'), + 'db25-pin-7': make_tmpl('breakout-ground'), + 'db25-pin-8': make_tmpl('db25-io', {pin: 8}), + 'db25-pin-9': make_tmpl('db25-io', {pin: 9}), + 'db25-pin-10': make_tmpl('db25-io', {pin: 10}), + 'db25-pin-11': make_tmpl('db25-io', {pin: 11}), + 'db25-pin-12': make_tmpl('db25-io', {pin: 12}), + 'db25-pin-13': make_tmpl('db25-rs485', {ch: 'A'}), + 'db25-pin-14': make_tmpl('db25-rs485', {ch: 'B'}), + 'db25-pin-15': make_tmpl('db25-io', {pin: 15}), + 'db25-pin-16': make_tmpl('db25-io', {pin: 16}), + 'db25-pin-17': make_tmpl('db25-pwm'), + 'db25-pin-18': make_tmpl('db25-io', {pin: 18}), + 'db25-pin-19': make_tmpl('breakout-ground'), + 'db25-pin-20': make_tmpl('breakout-5v'), + 'db25-pin-21': make_tmpl('db25-io', {pin: 21}), + 'db25-pin-22': make_tmpl('db25-io', {pin: 22}), + 'db25-pin-23': make_tmpl('db25-io', {pin: 23}), + 'db25-pin-24': make_tmpl('db25-io', {pin: 24}), + 'db25-pin-25': make_tmpl('breakout-ground'), + 'db25-pin-gnd': make_tmpl('breakout-ground') + }, + + + ready() { + $(this.$refs.breakout.$el).find('.pin').mouseover((e) => { + let classes = e.currentTarget.classList + + for (let i = 0; i < classes.length; i++) + if (classes[i].startsWith('pin-')) { + this.active_pin = classes[i].substr(4) + + if (this.active != undefined) + this.active.classList.remove('selected-pin') + this.active = e.currentTarget + classes.add('selected-pin') + } + }) + } +} diff --git a/src/js/indicators.js b/src/js/indicators.js index 36d64311..82b0f8a3 100644 --- a/src/js/indicators.js +++ b/src/js/indicators.js @@ -27,88 +27,8 @@ 'use strict' -var modbus = require('./modbus.js'); - module.exports = { template: '#indicators-template', - props: ['state'], - - - computed: { - watts: function () { - var I = parseFloat(this.state.motor) - var V = parseFloat(this.state.vout) - return I * V - }, - - - modbus_status: function () {return modbus.status_to_string(this.state.mx)}, - - - sense_error: function () { - var error = ''; - - if (this.state.motor_voltage_sense_error) error += 'Motor voltage\n'; - if (this.state.motor_current_sense_error) error += 'Motor current\n'; - if (this.state.load1_sense_error) error += 'Load 1\n'; - if (this.state.load2_sense_error) error += 'Load 2\n'; - if (this.state.vdd_current_sense_error) error += 'Vdd current\n'; - - return error; - } - }, - - - methods: { - is_motor_enabled: function (motor) { - return typeof this.state[motor + 'me'] != 'undefined' && - this.state[motor + 'me']; - }, - - - get_min_pin: function (motor) { - switch (motor) { - case 0: return 3; - case 1: return 5; - case 2: return 9; - case 3: return 11; - } - }, - - - get_max_pin: function (motor) { - switch (motor) { - case 0: return 4; - case 1: return 8; - case 2: return 10; - case 3: return 12; - } - }, - - - motor_fault_class: function (motor, bit) { - if (typeof motor == 'undefined') { - var status = this.state['fa']; - if (typeof status == 'undefined') return 'fa-question'; - return 'fa-thumbs-' + (status ? 'down error' : 'up success') - } - - var flags = this.state[motor + 'df']; - if (typeof flags == 'undefined') return 'fa-question'; - return (flags & (1 << bit)) ? 'fa-thumbs-down error' : - 'fa-thumbs-up success'; - }, - - - motor_reset: function (motor) { - if (typeof motor == 'undefined') { - var cmd = ''; - for (var i = 0; i < 4; i++) - cmd += '\\$' + i + 'df=0\n'; - this.$dispatch('send', cmd); - - } else this.$dispatch('send', '\\$' + motor + 'df=0'); - } - } + props: ['state', 'template'] } diff --git a/src/js/io-functions.js b/src/js/io-functions.js new file mode 100644 index 00000000..34a35155 --- /dev/null +++ b/src/js/io-functions.js @@ -0,0 +1,89 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +module.exports = { + template: "#io-functions-template", + props: ['template', 'state'], + + + components: { + 'io-functions-row': { + template: "#io-functions-row-template", + props: ['func'], + replace: true + } + }, + + + computed: { + columns: () => ['function', 'state', 'pins', '', + 'function', 'state', 'pins'], + + + rows: function () {return Math.ceil(this.functions.length / 2.0)}, + + + functions: function () { + let l = [] + let io_map = this.template['io-map'] + let pins = io_map.pins + let tmpl = io_map.template.function + let funcs = tmpl.values + let codes = tmpl.codes + let title = '' + + for (let i = 1; i < funcs.length; i++) { + let p = [] + + for (let j = 0; j < pins.length; j++) { + let code = String.fromCharCode(97 + j) + 'io' + if (i == this.state[code]) p.push(pins[j].id) + } + + let state = this.state[codes[i]] + let type = funcs[i].split('-')[0] + if (!p.length) { + type = 'disabled' + title = 'Function not mapped to any pins.' + } + + l.push({ + name: funcs[i], + state, + pins: p, + type, + title + }) + } + + return l + } + } +} diff --git a/src/js/io-indicator.js b/src/js/io-indicator.js index a115f360..7c1bffa9 100644 --- a/src/js/io-indicator.js +++ b/src/js/io-indicator.js @@ -30,148 +30,53 @@ module.exports = { template: "#io-indicator-template", - props: ['name', 'state'], + props: { + state: {type: Number}, + mode: String, + type: String + }, computed: { klass: function () { - if (this.name == 'min-switch-0') return this.get_motor_min_class(0); - if (this.name == 'min-switch-1') return this.get_motor_min_class(1); - if (this.name == 'min-switch-2') return this.get_motor_min_class(2); - if (this.name == 'min-switch-3') return this.get_motor_min_class(3); - if (this.name == 'max-switch-0') return this.get_motor_max_class(0); - if (this.name == 'max-switch-1') return this.get_motor_max_class(1); - if (this.name == 'max-switch-2') return this.get_motor_max_class(2); - if (this.name == 'max-switch-3') return this.get_motor_max_class(3); - if (this.name == 'estop') return this.get_input_class('ew', 'et'); - if (this.name == 'probe') return this.get_input_class('pw', 'pt'); - if (this.name == 'load-1') return this.get_output_class('1'); - if (this.name == 'load-2') return this.get_output_class('2'); - if (this.name == 'fault') return this.get_output_class('f'); - if (this.name == 'tool-enable-mode') return this.get_output_class('e'); - if (this.name == 'tool-direction-mode') return this.get_output_class('d'); - }, - - - tooltip: function () { - if (this.name == 'min-switch-0') return this.get_motor_min_tooltip(0); - if (this.name == 'min-switch-1') return this.get_motor_min_tooltip(1); - if (this.name == 'min-switch-2') return this.get_motor_min_tooltip(2); - if (this.name == 'min-switch-3') return this.get_motor_min_tooltip(3); - if (this.name == 'max-switch-0') return this.get_motor_max_tooltip(0); - if (this.name == 'max-switch-1') return this.get_motor_max_tooltip(1); - if (this.name == 'max-switch-2') return this.get_motor_max_tooltip(2); - if (this.name == 'max-switch-3') return this.get_motor_max_tooltip(3); - if (this.name == 'estop') return this.get_input_tooltip('ew', 'et'); - if (this.name == 'probe') return this.get_input_tooltip('pw', 'pt'); - if (this.name == 'load-1') return this.get_output_tooltip('1'); - if (this.name == 'load-2') return this.get_output_tooltip('2'); - if (this.name == 'fault') return this.get_output_tooltip('f'); - if (this.name == 'tool-direction-mode') - return this.get_output_tooltip('d'); - if (this.name == 'tool-enable-mode') - return this.get_output_tooltip('e'); - } - }, - - - methods: { - get_io_state_class: function (active, state) { - if (typeof active == 'undefined' || typeof state == 'undefined') - return 'fa-exclamation-triangle warn'; - - if (state == 2) return 'fa-circle-o'; - - return (state ? 'fa-plus-circle' : 'fa-minus-circle') + ' ' + - (active ? 'active' : 'inactive'); - }, - - - get_input_active: function (stateCode, typeCode) { - var type = this.state[typeCode]; - var state = this.state[stateCode]; - - if (type == 1) return !state; // Normally open - else if (type == 2) return state; // Normally closed - - return false - }, - - - get_input_class: function (stateCode, typeCode) { - return this.get_io_state_class(this.get_input_active(stateCode, typeCode), - this.state[stateCode]); - }, - - - get_output_class: function (output) { - return this.get_io_state_class(this.state[output + 'oa'], - this.state[output + 'os']); - }, - + let klass = '' - get_motor_min_class: function (motor) { - return this.get_input_class(motor + 'lw', motor + 'ls'); - }, + if (this.type == 'disabled') return 'fa-ban' + if (this.type == 'analog') return 'fa-circle-o' + if (this.state == 0) klass = 'inactive' + if (this.state == 1) klass = 'active' - get_motor_max_class: function (motor) { - return this.get_input_class(motor + 'xw', motor + 'xs'); - }, + if (this.mode) { + let parts = this.mode.split('-') + let state = 'tri' + if (this.state == 0) state = parts[0] + if (this.state == 1) state = parts[1] - get_tooltip: function (mode, active, state) { - if (typeof mode == 'undefined' || typeof active == 'undefined' || - typeof state == 'undefined') return 'Invalid'; + if (state == 'lo') return klass + ' fa-minus-circle' + if (state == 'hi') return klass + ' fa-plus-circle' + if (state == 'tri') return klass + ' fa-circle-o' - if (state == 0) state = 'Lo/Gnd'; - else if (state == 1) state = 'Hi/+3.3v'; - else if (state == 2) state = 'Tristated'; - else return 'Invalid'; + } else { + if (this.state == 0) return klass + ' fa-circle' + if (this.state == 1) return klass + ' fa-circle' + if (this.state == 0xff) return klass + ' fa-ban' + } - return 'Mode: ' + mode + '\nActive: ' + (active ? 'True' : 'False') + - '\nLevel: ' + state; + return 'fa-exclamation-triangle warn' }, - get_input_tooltip: function (stateCode, typeCode) { - var type = this.state[typeCode]; - if (type == 0) return 'Disabled'; - else if (type == 1) type = 'Normally open'; - else if (type == 2) type = 'Normally closed'; - - var active = this.get_input_active(stateCode, typeCode); - var state = this.state[stateCode]; - - return this.get_tooltip(type, active, state); - }, - - - get_output_tooltip: function (output) { - var mode = this.state[output + 'om']; - if (mode == 0) return 'Disabled'; - else if (mode == 1) mode = 'Lo/Hi'; - else if (mode == 2) mode = 'Hi/Lo'; - else if (mode == 3) mode = 'Tri/Lo'; - else if (mode == 4) mode = 'Tri/Hi'; - else if (mode == 5) mode = 'Lo/Tri'; - else if (mode == 6) mode = 'Hi/Tri'; - else mode = undefined; - - var active = this.state[output + 'oa']; - var state = this.state[output + 'os']; - - return this.get_tooltip(mode, active, state); - }, - - - get_motor_min_tooltip: function (motor) { - return this.get_input_tooltip(motor + 'lw', motor + 'ls'); - }, + tooltip: function () { + let parts = [] + if (this.type) parts.push('Type: ' + this.type) + if (this.mode) parts.push('Mode: ' + this.mode) + if (this.type != 'analog') + parts.push('Active: ' + (this.state == 1 ? 'True' : 'False')) - get_motor_max_tooltip: function (motor) { - return this.get_input_tooltip(motor + 'xw', motor + 'xs'); + return parts.join('\n') } } } diff --git a/src/js/io-pins.js b/src/js/io-pins.js new file mode 100644 index 00000000..17abe3fa --- /dev/null +++ b/src/js/io-pins.js @@ -0,0 +1,72 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +module.exports = { + template: "#io-pins-template", + props: ['template', 'state'], + + + components: { + 'io-pins-row': { + template: "#io-pins-row-template", + props: ['pin'], + replace: true + } + }, + + + computed: { + columns: () => ['pin', 'state', 'mode', 'function', '', + 'pin', 'state', 'mode', 'function'], + + + rows: function () {return Math.ceil(this.io_pins.length / 2.0)}, + + + io_pins: function () { + let l = [] + let io_map = this.template['io-map'] + let pins = io_map.pins + let modes = io_map.template.mode.values + let functions = io_map.template.function.values + + for (let i = 0; i < pins.length; i++) { + let c = String.fromCharCode(97 + i) + let state = this.state[c + 'is'] + let mode = modes[this.state[c + 'im']] + let func = functions[this.state[c + 'io']] + + l.push({id: pins[i].id, state, mode, func}) + } + + return l + } + } +} diff --git a/src/js/main.js b/src/js/main.js index 43b405a8..49c2d5d5 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -2,7 +2,7 @@ This file is part of the Buildbotics firmware. - Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + Copyright (c) 2015 - 2022, Buildbotics LLC, All rights reserved. This Source describes Open Hardware and is licensed under the CERN-OHL-S v2. @@ -25,65 +25,71 @@ \******************************************************************************/ -'use strict'; +'use strict' -var cookie = require('./cookie'); -var util = require('./util'); -var api = require('./api'); +var cookie = require('./cookie') +var util = require('./util') +var api = require('./api') function menu_ui() { - var layout = document.getElementById('layout'); - var menuLink = document.getElementById('menuLink'); + var layout = document.getElementById('layout') + var menuLink = document.getElementById('menuLink') var collapse = function () { - layout.classList.remove('active'); - window.removeEventListener('click', collapse); + layout.classList.remove('active') + window.removeEventListener('click', collapse) } menuLink.onclick = function (e) { - e.preventDefault(); - e.stopPropagation(); - layout.classList.toggle('active'); - window.addEventListener('click', collapse); + e.preventDefault() + e.stopPropagation() + layout.classList.toggle('active') + window.addEventListener('click', collapse) } } $(function() { - menu_ui(); + menu_ui() if (typeof cookie.get('client-id') == 'undefined') - cookie.set('client-id', util.uuid()); + cookie.set('client-id', util.uuid()) // Vue debugging - Vue.config.debug = true; + Vue.config.debug = true // Init global modules - require('./cm-gcode'); - require('./keyboard'); + require('./cm-gcode') + require('./keyboard') // Register global components - Vue.component('templated-input', require('./templated-input')); - Vue.component('message', require('./message')); - Vue.component('loading-message', require('./loading-message')); - Vue.component('dialog', require('./dialog')); - Vue.component('indicators', require('./indicators')); - Vue.component('io-indicator', require('./io-indicator')); - Vue.component('console', require('./console')); - Vue.component('unit-value', require('./unit-value')); - Vue.component('files', require('./files')); - Vue.component('file-dialog', require('./file-dialog')); - Vue.component('upload-dialog', require('./upload-dialog')); - Vue.component('nav-menu', require('./nav-menu')); - Vue.component('nav-item', require('./nav-item')); - Vue.component('video', require('./video')); - Vue.component('color-picker', require('./color-picker')); - Vue.component('dragbar', require('./dragbar')); - - require('./filters')(); + Vue.component('templated-input', require('./templated-input')) + Vue.component('templated-select', require('./templated-select')) + Vue.component('message', require('./message')) + Vue.component('loading-message', require('./loading-message')) + Vue.component('dialog', require('./dialog')) + Vue.component('power', require('./power')) + Vue.component('indicators', require('./indicators')) + Vue.component('io-functions', require('./io-functions')) + Vue.component('io-pins', require('./io-pins')) + Vue.component('io-indicator', require('./io-indicator')) + Vue.component('breakout', require('./breakout')) + Vue.component('console', require('./console')) + Vue.component('unit-value', require('./unit-value')) + Vue.component('files', require('./files')) + Vue.component('file-dialog', require('./file-dialog')) + Vue.component('upload-dialog', require('./upload-dialog')) + Vue.component('nav-menu', require('./nav-menu')) + Vue.component('nav-item', require('./nav-item')) + Vue.component('video', require('./video')) + Vue.component('color-picker', require('./color-picker')) + Vue.component('dragbar', require('./dragbar')) + Vue.component('mapped-io', require('./mapped-io')) + + require('./filters')() // Vue app - require('./app'); -}); + require('./app') +}) diff --git a/src/js/mapped-io.js b/src/js/mapped-io.js new file mode 100644 index 00000000..794b7dd1 --- /dev/null +++ b/src/js/mapped-io.js @@ -0,0 +1,70 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2022, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +module.exports = { + template: '#mapped-io-template', + props: ['pin', 'state', 'template'], + + + computed: { + index() { + let io_map = this.template['io-map'] + let code = '' + + for (let i = 0; i < io_map.pins.length; i++) { + let pin = io_map.pins[i] + if (pin.id == this.pin) return i + } + + return undefined + }, + + + code() {return this.template['io-map'].index[this.index]}, + type() {return this.template['io-map'].pins[this.index].type}, + + + func() { + let funcID = this.state[this.code + 'io'] + return this.template['io-map'].template.function.values[funcID] + }, + + + mode() { + let modeID = this.state[this.code + 'im'] + return this.template['io-map'].template.mode.values[modeID] + } + }, + + + methods: { + capitalize(s) {return s.charAt(0).toUpperCase() + s.slice(1)} + } +} diff --git a/src/js/power.js b/src/js/power.js new file mode 100644 index 00000000..8c993388 --- /dev/null +++ b/src/js/power.js @@ -0,0 +1,82 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +module.exports = { + template: '#power-template', + props: ['state', 'template'], + + computed: { + watts: function () { + var I = parseFloat(this.state.motor) + var V = parseFloat(this.state.vout) + return I * V + }, + + + sense_error: function () { + var error = ''; + + if (this.state.motor_voltage_sense_error) error += 'Motor voltage\n'; + if (this.state.motor_current_sense_error) error += 'Motor current\n'; + if (this.state.load1_sense_error) error += 'Load 1\n'; + if (this.state.load2_sense_error) error += 'Load 2\n'; + if (this.state.vdd_current_sense_error) error += 'Vdd current\n'; + + return error; + } + }, + + + methods: { + motor_fault_class: function (motor, bit) { + if (motor == undefined) { + var status = this.state.fa; + if (status == undefined) return 'fa-question'; + return 'fa-thumbs-' + (status ? 'down error' : 'up success') + } + + var flags = this.state[motor + 'df']; + if (typeof flags == 'undefined') return 'fa-question'; + return (flags & (1 << bit)) ? 'fa-thumbs-down error' : + 'fa-thumbs-up success'; + }, + + + motor_reset: function (motor) { + if (typeof motor == 'undefined') { + var cmd = ''; + for (var i = 0; i < 4; i++) + cmd += '\\$' + i + 'df=0\n'; + this.$dispatch('send', cmd); + + } else this.$dispatch('send', '\\$' + motor + 'df=0'); + } + } +} diff --git a/src/js/settings-io.js b/src/js/settings-io.js index bdb8e40c..09e5bd0e 100644 --- a/src/js/settings-io.js +++ b/src/js/settings-io.js @@ -30,5 +30,32 @@ module.exports = { template: '#settings-io-template', - props: ['config', 'template', 'state'] + props: ['config', 'template', 'state'], + + computed: { + io() { + let io = [] + let config = this.config['io-map'] + let template = this.template['io-map'] + let indices = template.index; + let functions = template.template.function.values; + let modes = template.template.mode.values; + + for (let i = 0; i < indices.length; i++) { + let type = template.pins[i].type + let funcs = functions.filter(name => name.startsWith(type)) + funcs.unshift('disabled') + + io.push({ + id: template.pins[i].id, + type, + config: config[i], + functions: funcs, + modes: type == 'output' ? modes : [] + }) + } + + return io + } + } } diff --git a/src/js/settings-motor.js b/src/js/settings-motor.js index 37861b14..f73c89b7 100644 --- a/src/js/settings-motor.js +++ b/src/js/settings-motor.js @@ -55,12 +55,12 @@ module.exports = { maxMaxVelocity: function () { - return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3); + return 1 * (15 * this.umPerStep / this.motor.microsteps).toFixed(3); }, ustepPerSec: function () { - return this.rpm * this.stepsPerRev * this.motor['microsteps'] / 60; + return this.rpm * this.stepsPerRev * this.motor.microsteps / 60; }, diff --git a/src/js/templated-select.js b/src/js/templated-select.js new file mode 100644 index 00000000..3a232c51 --- /dev/null +++ b/src/js/templated-select.js @@ -0,0 +1,40 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +'use strict' + + +module.exports = { + replace: true, + template: '#templated-select-template', + props: ['model', 'values'], + + + methods: { + change: function () {this.$dispatch('input-changed')} + } +} diff --git a/src/pug/templates/breakout.pug b/src/pug/templates/breakout.pug new file mode 100644 index 00000000..d67b38ba --- /dev/null +++ b/src/pug/templates/breakout.pug @@ -0,0 +1,55 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#breakout-template(type="text/x-template") + .breakout + h2 {{name.toUpperCase()}} breakout box + .breakout-body + div(:is="name + '-breakout'", v-ref:breakout) + .breakout-pin + div(v-if="active_pin") + h3 Pin {{active_pin}} + div(:is="name + '-pin-' + active_pin", :state="state", + :template="template") + + +script#breakout-ground-template(type="text/x-template") + .pin + h4 Ground + div This pin is connected to ground. + + +script#breakout-5v-template(type="text/x-template") + .pin + h4 5v + div This pin is connected to 5 volts. It can supply a maximum of 3A. + + +script#breakout-reserved-template(type="text/x-template") + .pin + h4 Reserved + div This is a reserved pin. Do not connect. diff --git a/src/pug/templates/db15-breakout.pug b/src/pug/templates/db15-breakout.pug new file mode 100644 index 00000000..803dfc2e --- /dev/null +++ b/src/pug/templates/db15-breakout.pug @@ -0,0 +1,57 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#db15-breakout-template(type="text/x-template") + include http/images/db15_breakout.svg + + +script#db15-fault-template(type="text/x-template") + .pin + h4 Motor fault output + div. + This pin will be pulled to ground when a motor fault is detected. + + +script#db15-enable-template(type="text/x-template") + .pin + h4 Motor enable output + div. + This pin will be pulled high when the motor drivers are enabled. + + +script#db15-step-template(type="text/x-template") + .pin + h4 Motor channel {{ch}} step + div. + The channel {{ch}} step signal is present on this pin. + + +script#db15-dir-template(type="text/x-template") + .pin + h4 Motor channel {{ch}} direction + div. + The channel {{ch}} direction signal is present on this pin. diff --git a/src/pug/templates/db25-breakout.pug b/src/pug/templates/db25-breakout.pug new file mode 100644 index 00000000..9250aa56 --- /dev/null +++ b/src/pug/templates/db25-breakout.pug @@ -0,0 +1,58 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#db25-breakout-template(type="text/x-template") + include http/images/db25_breakout.svg + + +script#db25-io-template(type="text/x-template") + .pin + h4 Mappable IO {{pin}} + mapped-io(:pin="pin", :state="state", :template="template") + + +script#db25-rs485-template(type="text/x-template") + .pin + h4 RS485 channel {{ch}} + div. + RS485 communication is used to talk to VFDs using the Modbus protocol. + + +script#db25-pwm-template(type="text/x-template") + .pin + h4 Tool PWM output + div. + This pin outputs a Pulse Width Modulated signal which can be used to + control spindle speed or LASER intensity. + + +script#db25-0to10-template(type="text/x-template") + .pin + h4 0 to 10v output + div. + This pin outputs a voltage between 0 and 10 which can be used to + control spindle speed or LASER intensity. diff --git a/src/pug/templates/indicators.pug b/src/pug/templates/indicators.pug index 5b6048a7..0d5b3c2b 100644 --- a/src/pug/templates/indicators.pug +++ b/src/pug/templates/indicators.pug @@ -27,241 +27,38 @@ script#indicators-template(type="text/x-template") .indicators - table.legend - tr - th.header(colspan=100) Legend - - tr - td - .fa.fa-plus-circle.io - th Hi/+3.3v - tr - td - .fa.fa-minus-circle.io - th Lo/Gnd - tr - td - .fa.fa-circle.io.active - th Active - tr - td - .fa.fa-circle.io.inactive - th Inactive - tr - td - .fa.fa-circle-o.io - th Tristated/Disabled - - table.inputs - tr - th.header(colspan=7) Inputs + div + io-pins(:state="state", :template="template") - tr - th State - th Pin - th Name - th.separator - th State - th Pin - th Name + table.legend + tr + th.header(colspan=100) Legend - each motor in '0123' tr - td: io-indicator(name=`min-switch-${motor}`, :state="state") - td {{get_min_pin(#{motor})}} - th Motor #{motor} Min + td + .fa.fa-circle.io.active + th Active th.separator - td: io-indicator(name=`max-switch-${motor}`, :state="state") - td {{get_max_pin(#{motor})}} - th Motor #{motor} Max - - tr - td: io-indicator(name="estop", :state="state") - td 23 - th EStop - th.separator - td: io-indicator(name="probe", :state="state") - td 22 - th Probe - - tr(v-if="false") - td {{state['1ai'] | percent 0}} - td 24 - th Analog 1 - th.separator - td {{state['2ai'] | percent 0}} - td 18 - th Analog 2 - - table.outputs - tr - th.header(colspan=7) Outputs - - tr - th State - th Pin - th Name - th.separator - th State - th Pin - th Name - - tr - td: io-indicator(name="tool-enable-mode", :state="state") - td 15 - th Tool Enable - th.separator - td: io-indicator(name="load-1", :state="state") - td 2 - th Load 1 - - tr - td: io-indicator(name="tool-direction-mode", :state="state") - td 16 - th Tool Direction - th.separator - td: io-indicator(name="load-2", :state="state") - td 1 - th Load 2 - - tr - td {{state.pd | percent 0}} - td 17 - th Tool PWM - th.separator - td: io-indicator(name="fault", :state="state") - td 21 - th Fault - - table.pwr_fault - tr - th.header(colspan=5). - Power Faults - #[span(v-if="state.pwr_version") (Version {{state.pwr_version}})] - tr - th(:class="{error: state.under_voltage}") Under voltage - td(:class="{error: state.under_voltage}") - | {{state.under_voltage ? 'True' : 'False'}} - th.separator - th(:class="{error: state.over_voltage}") Over voltage - td(:class="{error: state.over_voltage}") - | {{state.over_voltage ? 'True' : 'False'}} - tr - th(:class="{error: state.over_current}") Over current - td(:class="{error: state.over_current}") - | {{state.over_current ? 'True' : 'False'}} - th.separator - th(:class="{error: state.sense_error}", :title="sense_error") - | Sense error - td(:class="{error: state.sense_error}") - | {{state.sense_error ? 'True' : 'False'}} - tr - th(:class="{error: state.shunt_overload}") Shunt overload - td(:class="{error: state.shunt_overload}") - | {{state.shunt_overload ? 'True' : 'False'}} - th.separator - th(:class="{error: state.shunt_error}") Shunt error - td(:class="{error: state.shunt_error}") - | {{state.shunt_error ? 'True' : 'False'}} - tr(v-if="state.pwr_version_int < 0x100") - th(:class="{error: state.load1_shutdown}") Load 1 shutdown - td(:class="{error: state.load1_shutdown}") - | {{state.load1_shutdown ? 'True' : 'False'}} - th.separator - th(:class="{error: state.load2_shutdown}") Load 2 shutdown - td(:class="{error: state.load2_shutdown}") - | {{state.load2_shutdown ? 'True' : 'False'}} - tr - th(:class="{error: state.motor_under_voltage}") Motor under volt - td(:class="{error: state.motor_under_voltage}") - | {{state.motor_under_voltage ? 'True' : 'False'}} - th.separator - th(:class="{error: state.motor_overload}") Motor overload - td(:class="{error: state.motor_overload}") - | {{state.motor_overload ? 'True' : 'False'}} - - tr - th(:class="{error: state.power_shutdown}") Power shutdown - td(:class="{error: state.power_shutdown}") - | {{state.power_shutdown ? 'True' : 'False'}} - th.separator - th(:class="{error: state.gate_error}") Gate error - td(:class="{error: state.gate_error}") - | {{state.gate_error ? 'True' : 'False'}} - - table.motor_fault - tr - th.header(colspan=99) - | Motor Faults - .fa(:class="motor_fault_class()", title="General motor driver fault") - - tr - th Motor - th(title="Overtemperature fault"): .fa.fa-thermometer-full - th(title="Overcurrent motor channel A") A #[.fa.fa-bolt] - th(title="Predriver fault motor channel A") - | A #[.fa.fa-exclamation-triangle] - th(title="Overcurrent motor channel B") B #[.fa.fa-bolt] - th(title="Predriver fault motor channel B") - | B #[.fa.fa-exclamation-triangle] - th(title="Driver communication failure"): .fa.fa-handshake-o - th(title="Reset all motor flags") - .fa.fa-eraser(@click="motor_reset()") - - tr(v-for="motor in [0, 1, 2, 3]") - td {{motor}} - td: .fa(:class="motor_fault_class(motor, 0)", - title="Overtemperature fault") - td: .fa(:class="motor_fault_class(motor, 1)", - title="Overcurrent motor channel A") - td: .fa(:class="motor_fault_class(motor, 3)", - title="Predriver fault motor channel A") - td: .fa(:class="motor_fault_class(motor, 2)", - title="Overcurrent motor channel B") - td: .fa(:class="motor_fault_class(motor, 4)", - title="Predriver fault motor channel B") - td: .fa(:class="motor_fault_class(motor, 8)", - title="Driver communication failure") - td(:title="'Reset motor ' + motor + ' flags'") - .fa.fa-eraser(@click="motor_reset(motor)") - - table.measurements - tr - th.header(colspan=5) Measurements - - tr - td {{state.vin | fixed 1}} V - th Input Voltage - th.separator - td {{watts | fixed 2}} W - th Motor Power - - tr - td {{state.vout | fixed 1}} V - th Motor Voltage - th.separator - td {{state.motor | fixed 2}} A - th Motor Current - - tr(v-if="state.pwr_version_int < 0x100") - td {{state.load1 | fixed 2}} A - th Load 1 - th.separator - td {{state.load2 | fixed 2}} A - th Load 2 - - tr - td {{state.temp | fixed 0}} ℃ - th Board Temp - th.separator - td(:class="{'error': 80 <= state.rpi_temp}") - | {{state.rpi_temp | fixed 0}} ℃ - th RPi Temp - - div - h2 DB25 breakout box - img(src="images/DB25_breakout_box.png") + td + .fa.fa-circle.io.inactive + th Inactive + tr + td + .fa.fa-plus-circle.io + th Hi + th.separator + td + .fa.fa-minus-circle.io + th Lo + tr + td + .fa.fa-circle-o.io + th Tristated + th.separator + td + .fa.fa-ban.io + th Disabled + div: io-functions(:state="state", :template="template") - div - h2 DB15 breakout box - img(src="images/DB15_breakout_box.png") + breakout(name="db25", :state="state", :template="template") + breakout(name="db15", :state="state", :template="template") diff --git a/src/pug/templates/io-functions.pug b/src/pug/templates/io-functions.pug new file mode 100644 index 00000000..eaa95a93 --- /dev/null +++ b/src/pug/templates/io-functions.pug @@ -0,0 +1,46 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#io-functions-template(type="text/x-template") + table.functions + tr: th.header(colspan=100) I/O Functions + + tr + th(v-for="name in columns", track-by="$index", + :class="name || 'separator'") {{name}} + + tr(v-for="i in rows") + td(is="io-functions-row", :func="functions[i * 2 + 0]") + th.separator + td(is="io-functions-row", :func="functions[i * 2 + 1]", + v-if="functions[i * 2 + 1]") + + +script#io-functions-row-template(type="text/x-template") + td.function(:class="func.type", :title="func.title") {{func.name}} + td.state: io-indicator(:state="func.state", :type="func.type") + td.pins {{func.pins.join(' ')}} diff --git a/src/pug/templates/io-pins.pug b/src/pug/templates/io-pins.pug new file mode 100644 index 00000000..199b7cc2 --- /dev/null +++ b/src/pug/templates/io-pins.pug @@ -0,0 +1,46 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#io-pins-template(type="text/x-template") + table.io-pins + tr: th.header(colspan=100) Mappable I/O Pins + + tr + th(v-for="name in columns", track-by="$index", + :class="name || 'separator'") {{name}} + + tr(v-for="i in rows") + td(is="io-pins-row", :pin="io_pins[i * 2 + 0]") + th.separator + td(is="io-pins-row", :pin="io_pins[i * 2 + 1]", v-if="io_pins[i * 2 + 1]") + + +script#io-pins-row-template(type="text/x-template") + td.pin {{pin.id}} + td.state: io-indicator(:state="pin.state", :mode="pin.mode") + td.mode {{pin.mode}} + td.function {{pin.func}} diff --git a/src/pug/templates/mapped-io.pug b/src/pug/templates/mapped-io.pug new file mode 100644 index 00000000..412d9c3a --- /dev/null +++ b/src/pug/templates/mapped-io.pug @@ -0,0 +1,32 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#mapped-io-template(type="text/x-template") + .mapped-io + div. + {{capitalize(type)}} pin currently mapped to function #[tt {{func}}]. + #[span(v-if="type == 'output'") Output mode set to #[tt {{mode}}].] diff --git a/src/pug/templates/power.pug b/src/pug/templates/power.pug new file mode 100644 index 00000000..fe12479a --- /dev/null +++ b/src/pug/templates/power.pug @@ -0,0 +1,153 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#power-template(type="text/x-template") + .power + table.pwr_fault + tr + th.header(colspan=5). + Power Faults + #[span(v-if="state.pwr_version") (Version {{state.pwr_version}})] + tr + th(:class="{error: state.under_voltage}") Under voltage + td(:class="{error: state.under_voltage}") + | {{state.under_voltage ? 'True' : 'False'}} + th.separator + th(:class="{error: state.over_voltage}") Over voltage + td(:class="{error: state.over_voltage}") + | {{state.over_voltage ? 'True' : 'False'}} + tr + th(:class="{error: state.over_current}") Over current + td(:class="{error: state.over_current}") + | {{state.over_current ? 'True' : 'False'}} + th.separator + th(:class="{error: state.sense_error}", :title="sense_error") + | Sense error + td(:class="{error: state.sense_error}") + | {{state.sense_error ? 'True' : 'False'}} + tr + th(:class="{error: state.shunt_overload}") Shunt overload + td(:class="{error: state.shunt_overload}") + | {{state.shunt_overload ? 'True' : 'False'}} + th.separator + th(:class="{error: state.shunt_error}") Shunt error + td(:class="{error: state.shunt_error}") + | {{state.shunt_error ? 'True' : 'False'}} + tr(v-if="state.pwr_version_int < 0x100") + th(:class="{error: state.load1_shutdown}") Load 1 shutdown + td(:class="{error: state.load1_shutdown}") + | {{state.load1_shutdown ? 'True' : 'False'}} + th.separator + th(:class="{error: state.load2_shutdown}") Load 2 shutdown + td(:class="{error: state.load2_shutdown}") + | {{state.load2_shutdown ? 'True' : 'False'}} + tr + th(:class="{error: state.motor_under_voltage}") Motor under volt + td(:class="{error: state.motor_under_voltage}") + | {{state.motor_under_voltage ? 'True' : 'False'}} + th.separator + th(:class="{error: state.motor_overload}") Motor overload + td(:class="{error: state.motor_overload}") + | {{state.motor_overload ? 'True' : 'False'}} + + tr + th(:class="{error: state.power_shutdown}") Power shutdown + td(:class="{error: state.power_shutdown}") + | {{state.power_shutdown ? 'True' : 'False'}} + th.separator + th(:class="{error: state.gate_error}") Gate error + td(:class="{error: state.gate_error}") + | {{state.gate_error ? 'True' : 'False'}} + + table.motor_fault + tr + th.header(colspan=99) + | Motor Faults + .fa(:class="motor_fault_class()", title="General motor driver fault") + + tr + th Motor + th(title="Overtemperature fault"): .fa.fa-thermometer-full + th(title="Overcurrent motor channel A") A #[.fa.fa-bolt] + th(title="Predriver fault motor channel A") + | A #[.fa.fa-exclamation-triangle] + th(title="Overcurrent motor channel B") B #[.fa.fa-bolt] + th(title="Predriver fault motor channel B") + | B #[.fa.fa-exclamation-triangle] + th(title="Driver communication failure"): .fa.fa-handshake-o + th(title="Reset all motor flags") + .fa.fa-eraser(@click="motor_reset()") + + tr(v-for="motor in [0, 1, 2, 3]") + td {{motor}} + td: .fa(:class="motor_fault_class(motor, 0)", + title="Overtemperature fault") + td: .fa(:class="motor_fault_class(motor, 1)", + title="Overcurrent motor channel A") + td: .fa(:class="motor_fault_class(motor, 3)", + title="Predriver fault motor channel A") + td: .fa(:class="motor_fault_class(motor, 2)", + title="Overcurrent motor channel B") + td: .fa(:class="motor_fault_class(motor, 4)", + title="Predriver fault motor channel B") + td: .fa(:class="motor_fault_class(motor, 8)", + title="Driver communication failure") + td(:title="'Reset motor ' + motor + ' flags'") + .fa.fa-eraser(@click="motor_reset(motor)") + + table.measurements + tr + th.header(colspan=5) Measurements + + tr + td {{state.vin | fixed 1}} V + th Input Voltage + th.separator + td {{watts | fixed 2}} W + th Motor Power + + tr + td {{state.vout | fixed 1}} V + th Motor Voltage + th.separator + td {{state.motor | fixed 2}} A + th Motor Current + + tr(v-if="state.pwr_version_int < 0x100") + td {{state.load1 | fixed 2}} A + th Load 1 + th.separator + td {{state.load2 | fixed 2}} A + th Load 2 + + tr + td {{state.temp | fixed 0}} ℃ + th Board Temp + th.separator + td(:class="{'error': 80 <= state.rpi_temp}") + | {{state.rpi_temp | fixed 0}} ℃ + th RPi Temp diff --git a/src/pug/templates/settings-admin.pug b/src/pug/templates/settings-admin.pug index 4153bacf..15399d90 100644 --- a/src/pug/templates/settings-admin.pug +++ b/src/pug/templates/settings-admin.pug @@ -35,7 +35,6 @@ script#settings-admin-template(type="text/x-template") @change="change_enable_keyboard") label   Enable automatic virtual keyboard on local screen. - h2 Configuration button.pure-button.pure-button-primary(@click="backup") Backup button.pure-button.pure-button-primary(@click="restore_config") Restore diff --git a/src/pug/templates/settings-io.pug b/src/pug/templates/settings-io.pug index 576c860a..4480f125 100644 --- a/src/pug/templates/settings-io.pug +++ b/src/pug/templates/settings-io.pug @@ -26,24 +26,31 @@ //-///////////////////////////////////////////////////////////////////////////// script#settings-io-template(type="text/x-template") - #io + #settings-io h1 I/O Configuration - .pure-form.pure-form-aligned - fieldset - h2 Switches - templated-input(v-for="templ in template.switches", :name="$key", - :model.sync="config.switches[$key]", :template="templ") + h2 Remapable Pins + table.pure-table.pure-table-striped + thead + tr + th Pin + th Type + th Function + th Mode - label.extra(slot="extra", v-if="templ.pin") - | Pin {{templ.pin}} - io-indicator(:name="$key", :state="state") + tbody + tr(v-for="pin in io") + td {{pin.id}} + td {{pin.type}} + td + templated-select.function(:model.sync="pin.config.function", + :values="pin.functions") + td + templated-select.mode(v-if="pin.modes.length", + :model.sync="pin.config.mode", :values="pin.modes") + h2 Input + .pure-form.pure-form-aligned fieldset - h2 Outputs - templated-input(v-for="templ in template.outputs", :name="$key", - :model.sync="config.outputs[$key]", :template="templ") - - label.extra(slot="extra") - | Pin {{templ.pin}} - io-indicator(:name="$key", :state="state") + templated-input(v-for="templ in template.input", :name="$key", + :model.sync="config.input[$key]", :template="templ") diff --git a/src/pug/templates/settings-tool.pug b/src/pug/templates/settings-tool.pug index b967ab0f..46226a66 100644 --- a/src/pug/templates/settings-tool.pug +++ b/src/pug/templates/settings-tool.pug @@ -194,6 +194,13 @@ script#settings-tool-template(type="text/x-template") td.reg-value 0 td 1 start, 8 data, no parity, 1 stop td Must match #[tt parity] above + p + | Other settings according to the + | + a(href="https://buildbotics.com/upload/vfd/nowforever-e100.pdf", + target="_blank") Nowforever VFD manual + | + | and spindle type. .notes(v-if="tool_type.startsWith('DELTA VFD015M21A')") h2 Notes diff --git a/src/pug/templates/templated-select.pug b/src/pug/templates/templated-select.pug new file mode 100644 index 00000000..b22f7f01 --- /dev/null +++ b/src/pug/templates/templated-select.pug @@ -0,0 +1,30 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. // +//- // +//- This Source describes Open Hardware and is licensed under the // +//- CERN-OHL-S v2. // +//- // +//- You may redistribute and modify this Source and make products // +//- using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). // +//- This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED // +//- WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS // +//- FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable // +//- conditions. // +//- // +//- Source location: https://github.com/buildbotics // +//- // +//- As per CERN-OHL-S v2 section 4, should You produce hardware based on // +//- these sources, You must maintain the Source Location clearly visible on // +//- the external case of the CNC Controller or other product you make using // +//- this Source. // +//- // +//- For more information, email info@buildbotics.com // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#templated-select-template(type="text/x-template") + select(v-model="model", @change="change") + option(v-for="opt in values", :value="opt") {{opt}} diff --git a/src/pug/templates/view-control.pug b/src/pug/templates/view-control.pug index 0f6e1bb7..a1dd778f 100644 --- a/src/pug/templates/view-control.pug +++ b/src/pug/templates/view-control.pug @@ -164,15 +164,6 @@ script#view-control-template(type="text/x-template") span(v-if="!isNaN(state.s)")  ({{state.s | fixed 0}}) = ' RPM' - tr(title="Load switch states.") - th Loads - td - span(:class="state['1oa'] ? 'load-on' : ''") - | 1:{{state['1oa'] ? 'On' : 'Off'}} - |   - span(:class="state['2oa'] ? 'load-on' : ''") - | 2:{{state['2oa'] ? 'On' : 'Off'}} - table.info tr th Remaining @@ -222,6 +213,9 @@ script#view-control-template(type="text/x-template") input#tab5(type="radio", name="tabs", @click="tab = 'indicators'") label(for="tab5") Indicators + input#tab6(type="radio", name="tabs", @click="tab = 'power'") + label(for="tab6") Power + section#content1.tab-content.pure-form .toolbar.pure-control-group button.pure-button(:class="{'attention': is_holding}", @@ -324,3 +318,6 @@ script#view-control-template(type="text/x-template") section#content5.tab-content indicators(:state="state", :template="template") + + section#content6.tab-content + power(:state="state", :template="template") diff --git a/src/pwr2/main.c b/src/pwr2/main.c index 9bc0161d..4039a4f1 100644 --- a/src/pwr2/main.c +++ b/src/pwr2/main.c @@ -330,10 +330,6 @@ ISR(TWI0_TWIS_vect) { static uint16_t reg; static uint8_t pec = 0; - // Stretch clock longer to work around RPi bug - // See https://github.com/raspberrypi/linux/issues/254 - //_delay_us(100); // Must use software delay while in interrupt - uint8_t status = TWI0.SSTATUS; if (status & TWI_DIF_bm) { diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py index c709138f..5bc14bb9 100644 --- a/src/py/bbctrl/Comm.py +++ b/src/py/bbctrl/Comm.py @@ -250,6 +250,7 @@ def connect(self): try: # Resume once current queue of GCode commands has flushed self.queue_command(Cmd.RESUME) + self.queue_command(Cmd.set('ct', 128)) self.queue_command(Cmd.HELP) # Load AVR commands and variables except Exception as e: diff --git a/src/py/bbctrl/Preplanner.py b/src/py/bbctrl/Preplanner.py index 3dff300e..ab9c0a1c 100644 --- a/src/py/bbctrl/Preplanner.py +++ b/src/py/bbctrl/Preplanner.py @@ -32,6 +32,7 @@ import glob import tempfile import signal +import shutil from concurrent.futures import Future from tornado import gen, process, iostream import bbctrl @@ -162,9 +163,9 @@ def _exec(self): proc.stdout.close() if not self.cancel: - os.rename(tmpdir + '/meta.json', self.files[0]) - os.rename(tmpdir + '/positions.gz', self.files[1]) - os.rename(tmpdir + '/speeds.gz', self.files[2]) + shutil.move(tmpdir + '/meta.json', self.files[0]) + shutil.move(tmpdir + '/positions.gz', self.files[1]) + shutil.move(tmpdir + '/speeds.gz', self.files[2]) self.preplanner.clean() os.sync() diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index 9d3e663a..c3d979d9 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -94,7 +94,7 @@ def init(self): def reset(self): # Unhome all motors - for i in range(4): self.set('%dhomed' % i, False) + for i in range(4): self.set('%dhomed' % i, 0) # Zero offsets and positions for axis in 'xyzabc': @@ -261,7 +261,7 @@ def find_motor(self, axis): return motor - def is_axis_homed(self, axis): return self.get('%s_homed' % axis, False) + def is_axis_homed(self, axis): return self.get('%s_homed' % axis, 0) def is_axis_enabled(self, axis): diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index 2d46edd5..7abe3f6a 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -577,51 +577,52 @@ def __init__(self, args, ioloop): self.monitor = bbctrl.MonitorTemp(self) handlers = [ - (r'/websocket', WSConnection), - (r'/api/state(/.*)?', StateHandler), - (r'/api/log', LogHandler), - (r'/api/message/(\d+)/ack', MessageAckHandler), - (r'/api/bugreport', BugReportHandler), - (r'/api/reboot', RebootHandler), - (r'/api/hostname', HostnameHandler), - (r'/api/wifi', WifiHandler), - (r'/api/remote/username', UsernameHandler), - (r'/api/remote/password', PasswordHandler), - (r'/api/config/load', ConfigLoadHandler), - (r'/api/config/download', ConfigDownloadHandler), - (r'/api/config/save', ConfigSaveHandler), - (r'/api/config/reset', ConfigResetHandler), - (r'/api/firmware/update', FirmwareUpdateHandler), - (r'/api/upgrade', UpgradeHandler), - (r'/api/queue/(.*)', QueueHandler), - (r'/api/usb/update', USBUpdateHandler), - (r'/api/usb/eject/(.*)', USBEjectHandler), - (r'/api/fs/(.*)', bbctrl.FileSystemHandler), - (r'/api/macro/(\d+)', MacroHandler), - (r'/api/(path)/(.*)', PathHandler), - (r'/api/(positions)/(.*)', PathHandler), - (r'/api/(speeds)/(.*)', PathHandler), + (r'/websocket', WSConnection), + (r'/api/state(/.*)?', StateHandler), + (r'/api/log', LogHandler), + (r'/api/message/(\d+)/ack', MessageAckHandler), + (r'/api/bugreport', BugReportHandler), + (r'/api/reboot', RebootHandler), + (r'/api/hostname', HostnameHandler), + (r'/api/wifi', WifiHandler), + (r'/api/remote/username', UsernameHandler), + (r'/api/remote/password', PasswordHandler), + (r'/api/config/load', ConfigLoadHandler), + (r'/api/config/download', ConfigDownloadHandler), + (r'/api/config/save', ConfigSaveHandler), + (r'/api/config/reset', ConfigResetHandler), + (r'/api/firmware/update', FirmwareUpdateHandler), + (r'/api/upgrade', UpgradeHandler), + (r'/api/queue/(.*)', QueueHandler), + (r'/api/usb/update', USBUpdateHandler), + (r'/api/usb/eject/(.*)', USBEjectHandler), + (r'/api/fs/(.*)', bbctrl.FileSystemHandler), + (r'/api/macro/(\d+)', MacroHandler), + (r'/api/(path)/(.*)', PathHandler), + (r'/api/(positions)/(.*)', PathHandler), + (r'/api/(speeds)/(.*)', PathHandler), (r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler), - (r'/api/start', StartHandler), - (r'/api/estop', EStopHandler), - (r'/api/clear', ClearHandler), - (r'/api/stop', StopHandler), - (r'/api/pause', PauseHandler), - (r'/api/unpause', UnpauseHandler), - (r'/api/pause/optional', OptionalPauseHandler), - (r'/api/step', StepHandler), + (r'/api/start', StartHandler), + (r'/api/estop', EStopHandler), + (r'/api/clear', ClearHandler), + (r'/api/stop', StopHandler), + (r'/api/pause', PauseHandler), + (r'/api/unpause', UnpauseHandler), + (r'/api/pause/optional', OptionalPauseHandler), + (r'/api/step', StepHandler), (r'/api/position/([xyzabcXYZABC])', PositionHandler), - (r'/api/override/feed/([\d.]+)', OverrideFeedHandler), - (r'/api/override/speed/([\d.]+)', OverrideSpeedHandler), - (r'/api/modbus/read', ModbusReadHandler), - (r'/api/modbus/write', ModbusWriteHandler), - (r'/api/jog', JogHandler), - (r'/api/video', bbctrl.VideoHandler), - (r'/api/keyboard/((show)|(hide))', KeyboardHandler), - (r'/(.*)', StaticFileHandler, - {'path': bbctrl.get_resource('http/'), - 'default_filename': 'index.html'}), - ] + (r'/api/override/feed/([\d.]+)', OverrideFeedHandler), + (r'/api/override/speed/([\d.]+)', OverrideSpeedHandler), + (r'/api/modbus/read', ModbusReadHandler), + (r'/api/modbus/write', ModbusWriteHandler), + (r'/api/jog', JogHandler), + (r'/api/video', bbctrl.VideoHandler), + (r'/api/keyboard/((show)|(hide))', KeyboardHandler), + (r'/(.*)', StaticFileHandler, { + 'path': bbctrl.get_resource('http/'), + 'default_filename': 'index.html' + }), + ] router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs') router.app = self diff --git a/src/resources/config-template.json b/src/resources/config-template.json index 9482353e..66a5a55d 100644 --- a/src/resources/config-template.json +++ b/src/resources/config-template.json @@ -374,9 +374,28 @@ "io-map": { "type": "list", "index": "abcdefghijklmnopq", + "pins": [ + {"id": 1, "type": "output"}, + {"id": 2, "type": "output"}, + {"id": 3, "type": "input"}, + {"id": 4, "type": "input"}, + {"id": 5, "type": "input"}, + {"id": 8, "type": "input"}, + {"id": 9, "type": "input"}, + {"id": 10, "type": "input"}, + {"id": 11, "type": "input"}, + {"id": 12, "type": "input"}, + {"id": 15, "type": "output"}, + {"id": 16, "type": "output"}, + {"id": 18, "type": "analog"}, + {"id": 21, "type": "output"}, + {"id": 22, "type": "input"}, + {"id": 23, "type": "input"}, + {"id": 24, "type": "analog"} + ], "default": [ - {"function": "output-flood"}, - {"function": "output-mist"}, + {"function": "output-flood", "mode": "lo-hi"}, + {"function": "output-mist", "mode": "lo-hi"}, {"function": "input-motor-0-min"}, {"function": "input-motor-0-max"}, {"function": "input-motor-1-min"}, @@ -385,10 +404,10 @@ {"function": "input-motor-2-max"}, {"function": "input-motor-3-min"}, {"function": "input-motor-3-max"}, - {"function": "output-tool-enable"}, - {"function": "output-tool-direction"}, + {"function": "output-tool-enable", "mode": "lo-hi"}, + {"function": "output-tool-direction", "mode": "lo-hi"}, {"function": "analog-1"}, - {"function": "output-fault"}, + {"function": "output-fault", "mode": "lo-hi"}, {"function": "input-probe"}, {"function": "input-estop"}, {"function": "analog-0"} @@ -408,12 +427,17 @@ "output-tool-enable", "output-tool-direction", "analog-0", "analog-1", "analog-2", "analog-3" ], + "codes": [ + null, "0xw", "1xw", "2xw", "3xw", "0lw", "1lw", "2lw", "3lw", + "0w", "1w", "2w", "3w", "ew", "pw", "0oa", "1oa", "2oa", "3oa", + "Moa", "Foa", "foa", "eoa", "doa", "0ai", "1ai", "2ai", "3ai" + ], "code": "io" }, "mode": { "type": "enum", "values": ["lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "hi-tri"], - "default": "lo-hi", + "default": "hi-lo", "code": "im" } } diff --git a/src/resources/images/DB15_breakout_box.png b/src/resources/images/DB15_breakout_box.png deleted file mode 100644 index 5a5e4fb816c1a6ccbed246fcb2c9d6c1227e3bc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103735 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