com.willwinder.universalgcodesender.firmware.grbl.GrblFirmwareSettings.java Source code

Java tutorial

Introduction

Here is the source code for com.willwinder.universalgcodesender.firmware.grbl.GrblFirmwareSettings.java

Source

/*
Copyright 2018 Will Winder
    
This file is part of Universal Gcode Sender (UGS).
    
UGS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
UGS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with UGS.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.willwinder.universalgcodesender.firmware.grbl;

import com.willwinder.universalgcodesender.IController;
import com.willwinder.universalgcodesender.firmware.FirmwareSetting;
import com.willwinder.universalgcodesender.firmware.FirmwareSettingsException;
import com.willwinder.universalgcodesender.firmware.IFirmwareSettings;
import com.willwinder.universalgcodesender.firmware.IFirmwareSettingsListener;
import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.listeners.SerialCommunicatorListener;
import com.willwinder.universalgcodesender.model.Axis;
import com.willwinder.universalgcodesender.model.UnitUtils;
import com.willwinder.universalgcodesender.types.GcodeCommand;
import org.apache.commons.lang3.math.NumberUtils;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Handles the firmware settings on a GRBL controller. It needs to be registered as a listener
 * to {@link com.willwinder.universalgcodesender.AbstractCommunicator#setListenAll(SerialCommunicatorListener)}
 * for it to be able to process all commands to/from the controller.
 *
 * @author Joacim Breiler
 * @author MerrellM
 */
public class GrblFirmwareSettings
        implements SerialCommunicatorListener, IFirmwareSettingsListener, IFirmwareSettings {
    private static final Logger LOGGER = Logger.getLogger(GrblFirmwareSettings.class.getName());

    /**
     * Setting keys for GRBL
     */
    private static final String KEY_REPORTING_UNITS_IN_INCHES = "$13";
    private static final String KEY_SOFT_LIMITS_ENABLED = "$20";
    private static final String KEY_HARD_LIMITS_ENABLED = "$21";
    private static final String KEY_HOMING_ENABLED = "$22";
    private static final String KEY_HOMING_INVERT_DIRECTION = "$23";
    private static final String KEY_INVERT_DIRECTION = "$3";
    private static final String KEY_INVERT_LIMIT_PINS = "$5";
    private static final String KEY_STEPS_PER_MM_X = "$100";
    private static final String KEY_STEPS_PER_MM_Y = "$101";
    private static final String KEY_STEPS_PER_MM_Z = "$102";
    private static final String KEY_SOFT_LIMIT_X = "$130";
    private static final String KEY_SOFT_LIMIT_Y = "$131";
    private static final String KEY_SOFT_LIMIT_Z = "$132";

    /**
     * A GRBL settings description lookups
     */
    private final Map<String, FirmwareSetting> settings = new ConcurrentHashMap<>();

    /**
     * A delegate for all serial communication handling
     */
    private final GrblFirmwareSettingsSerialCommunicator serialCommunicatorDelegate;

    public GrblFirmwareSettings(IController controller) {
        this.serialCommunicatorDelegate = new GrblFirmwareSettingsSerialCommunicator(controller);
        this.serialCommunicatorDelegate.addListener(this);
    }

    /**
     * Sets a property value on the controller. It will wait until the setting has been stored,
     * if this fails a {@link FirmwareSettingsException} will be thrown.
     *
     * @param key   the name of the setting to update
     * @param value the value of the setting
     * @return the value stored on the controller
     * @throws FirmwareSettingsException if the value couldn't be persisted on the controller.
     */
    @Override
    synchronized public FirmwareSetting setValue(final String key, final String value)
            throws FirmwareSettingsException {

        final FirmwareSetting oldSetting = getSetting(key).orElseThrow(
                () -> new FirmwareSettingsException("Couldn't find setting with key " + key + " to update."));

        // The setting already contains the value so we do not update
        if (oldSetting.getValue().equals(value)) {
            return oldSetting;
        }

        // Make a copy of existing property and send it to our controller
        final FirmwareSetting newSetting = new FirmwareSetting(oldSetting.getKey(), value, oldSetting.getUnits(),
                oldSetting.getDescription(), oldSetting.getShortDescription());
        return serialCommunicatorDelegate.updateSettingOnController(newSetting).orElse(oldSetting);
    }

    /**
     * Sets a property value on the controller. It will wait until the setting has been stored,
     * if this fails a {@link FirmwareSettingsException} will be thrown.
     *
     * @param key   the name of the setting to update
     * @param value the value of the setting
     * @return the value stored on the controller
     * @throws FirmwareSettingsException if the value couldn't be persisted on the controller.
     */
    public FirmwareSetting setValue(final String key, final double value) throws FirmwareSettingsException {

        final FirmwareSetting oldSetting = getSetting(key).orElseThrow(
                () -> new FirmwareSettingsException("Couldn't find setting with key " + key + " to update."));

        // The setting already contains the value so we do not update
        if (getValueAsDouble(key) == value) {
            return oldSetting;
        }

        DecimalFormat decimalFormat = new DecimalFormat("0.0##", Localization.dfs);
        return setValue(key, decimalFormat.format(value));
    }

    @Override
    public void addListener(IFirmwareSettingsListener listener) {
        serialCommunicatorDelegate.addListener(listener);
    }

    @Override
    public void removeListener(IFirmwareSettingsListener listener) {
        serialCommunicatorDelegate.removeListener(listener);
    }

    @Override
    public Optional<FirmwareSetting> getSetting(String key) {
        return Optional.ofNullable(settings.get(key));
    }

    @Override
    public List<FirmwareSetting> getAllSettings() {
        return new ArrayList<>(settings.values());
    }

    @Override
    public boolean isHardLimitsEnabled() throws FirmwareSettingsException {
        return getValueAsBoolean(KEY_HARD_LIMITS_ENABLED);
    }

    @Override
    public void setHardLimitsEnabled(boolean enabled) throws FirmwareSettingsException {
        setValue(KEY_HARD_LIMITS_ENABLED, enabled ? "1" : "0");
    }

    @Override
    public boolean isSoftLimitsEnabled() throws FirmwareSettingsException {
        return getValueAsBoolean(KEY_SOFT_LIMITS_ENABLED);
    }

    @Override
    public void setSoftLimitsEnabled(boolean enabled) throws FirmwareSettingsException {
        setValue(KEY_SOFT_LIMITS_ENABLED, enabled ? "1" : "0");
    }

    @Override
    public boolean isInvertDirectionX() {
        return (getInvertDirectionMask() & 1) == 1;
    }

    @Override
    public void setInvertDirectionX(boolean inverted) throws FirmwareSettingsException {
        Integer directionMask = getInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b1; // set first bit from LSB
        } else {
            directionMask &= ~0b1; // unset first bit from LSB
        }

        setValue(KEY_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public boolean isInvertDirectionY() {
        return (getInvertDirectionMask() & 2) == 2;
    }

    @Override
    public void setInvertDirectionY(boolean inverted) throws FirmwareSettingsException {
        Integer directionMask = getInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b10; // set second bit from LSB
        } else {
            directionMask &= ~0b10; // unset second bit from LSB
        }

        setValue(KEY_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public boolean isInvertDirectionZ() {
        return (getInvertDirectionMask() & 4) == 4;
    }

    @Override
    public void setInvertDirectionZ(boolean inverted) throws FirmwareSettingsException {
        int directionMask = getInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b100; // set third bit from LSB
        } else {
            directionMask &= ~0b100; // unset third bit from LSB
        }

        setValue(KEY_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public int getStepsPerMillimeter(Axis axis) throws FirmwareSettingsException {
        switch (axis) {
        case X:
            return getValueAsInteger(KEY_STEPS_PER_MM_X);
        case Y:
            return getValueAsInteger(KEY_STEPS_PER_MM_Y);
        case Z:
            return getValueAsInteger(KEY_STEPS_PER_MM_Z);
        default:
            return 0;
        }
    }

    @Override
    public double getSoftLimitX() throws FirmwareSettingsException {
        return getValueAsDouble(KEY_SOFT_LIMIT_X);
    }

    @Override
    public void setSoftLimitX(double limit) throws FirmwareSettingsException {
        setValue(KEY_SOFT_LIMIT_X, limit);
    }

    @Override
    public double getSoftLimitY() throws FirmwareSettingsException {
        return getValueAsDouble(KEY_SOFT_LIMIT_Y);
    }

    @Override
    public void setSoftLimitY(double limit) throws FirmwareSettingsException {
        setValue(KEY_SOFT_LIMIT_Y, limit);
    }

    @Override
    public double getSoftLimitZ() throws FirmwareSettingsException {
        return getValueAsDouble(KEY_SOFT_LIMIT_Z);
    }

    @Override
    public void setSoftLimitZ(double limit) throws FirmwareSettingsException {
        setValue(KEY_SOFT_LIMIT_Z, limit);
    }

    @Override
    public double getSoftLimit(Axis axis) throws FirmwareSettingsException {
        switch (axis) {
        case X:
            return getSoftLimitX();
        case Y:
            return getSoftLimitY();
        case Z:
            return getSoftLimitZ();
        default:
            return 0;
        }
    }

    @Override
    public boolean isHomingDirectionInvertedX() {
        return (getHomingInvertDirectionMask() & 1) == 1;
    }

    @Override
    public void setHomingDirectionInvertedX(boolean inverted) throws FirmwareSettingsException {
        Integer directionMask = getHomingInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b1; // set first bit from LSB
        } else {
            directionMask &= ~0b1; // unset first bit from LSB
        }

        setValue(KEY_HOMING_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public boolean isHomingDirectionInvertedY() {
        return (getHomingInvertDirectionMask() & 2) == 2;
    }

    @Override
    public void setHomingDirectionInvertedY(boolean inverted) throws FirmwareSettingsException {
        Integer directionMask = getHomingInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b10; // set first bit from LSB
        } else {
            directionMask &= ~0b10; // unset first bit from LSB
        }

        setValue(KEY_HOMING_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public boolean isHomingDirectionInvertedZ() {
        return (getHomingInvertDirectionMask() & 4) == 4;
    }

    @Override
    public void setHomingDirectionInvertedZ(boolean inverted) throws FirmwareSettingsException {
        Integer directionMask = getHomingInvertDirectionMask();

        if (inverted) {
            directionMask |= 0b100; // set first bit from LSB
        } else {
            directionMask &= ~0b100; // unset first bit from LSB
        }

        setValue(KEY_HOMING_INVERT_DIRECTION, String.valueOf(directionMask));
    }

    @Override
    public void setHardLimitsInverted(boolean inverted) throws FirmwareSettingsException {
        setValue(KEY_INVERT_LIMIT_PINS, inverted ? "1" : "0");
    }

    @Override
    public boolean isHardLimitsInverted() throws FirmwareSettingsException {
        return getValueAsBoolean(KEY_INVERT_LIMIT_PINS);
    }

    @Override
    public void setSettings(List<FirmwareSetting> settings) throws FirmwareSettingsException {
        settings.forEach(setting -> {
            try {
                setValue(setting.getKey(), setting.getValue());
            } catch (FirmwareSettingsException e) {
                LOGGER.warning("Couldn't set the firmware setting " + setting.getKey() + " to value "
                        + setting.getValue() + ". Error message: " + e.getMessage());
            }
        });
    }

    private int getInvertDirectionMask() {
        return getSetting(KEY_INVERT_DIRECTION).map(FirmwareSetting::getValue).map(Integer::valueOf).orElse(0);
    }

    private int getHomingInvertDirectionMask() {
        return getSetting(KEY_HOMING_INVERT_DIRECTION).map(FirmwareSetting::getValue).map(Integer::valueOf)
                .orElse(0);
    }

    @Override
    public boolean isHomingEnabled() throws FirmwareSettingsException {
        return getValueAsBoolean(KEY_HOMING_ENABLED);
    }

    @Override
    public void setHomingEnabled(boolean enabled) throws FirmwareSettingsException {
        setValue(KEY_HOMING_ENABLED, enabled ? "1" : "0");
    }

    @Override
    public UnitUtils.Units getReportingUnits() {
        return getSetting(KEY_REPORTING_UNITS_IN_INCHES).map(FirmwareSetting::getValue).map(value -> {
            if ("0".equals(value)) {
                return UnitUtils.Units.MM;
            } else if ("1".equals(value)) {
                return UnitUtils.Units.INCH;
            } else {
                return UnitUtils.Units.UNKNOWN;
            }
        }).orElse(UnitUtils.Units.UNKNOWN);
    }

    /*
     * SerialCommunicatorListener
     */
    @Override
    public void rawResponseListener(String response) {
        serialCommunicatorDelegate.rawResponseListener(response);
    }

    @Override
    public void commandSent(GcodeCommand command) {
        serialCommunicatorDelegate.commandSent(command);
    }

    @Override
    public void commandSkipped(GcodeCommand command) {
        serialCommunicatorDelegate.commandSkipped(command);
    }

    @Override
    public void communicatorPausedOnError() {
        serialCommunicatorDelegate.communicatorPausedOnError();
    }

    /*
     * IFirmwareSettingsListener
     */
    @Override
    public void onUpdatedFirmwareSetting(FirmwareSetting setting) {
        LOGGER.log(Level.FINE, "Updating setting " + setting.getKey() + " = " + setting.getValue());
        settings.put(setting.getKey(), setting);
    }

    /*
     * Helpers
     */
    private int getValueAsInteger(String key) throws FirmwareSettingsException {
        FirmwareSetting firmwareSetting = getSetting(key)
                .orElseThrow(() -> new FirmwareSettingsException("Couldn't find setting with key: " + key));
        if (!NumberUtils.isNumber(firmwareSetting.getValue())) {
            throw new FirmwareSettingsException("Expected the key " + key + " to contain a numeric value but was "
                    + firmwareSetting.getValue());
        }

        return NumberUtils.createNumber(firmwareSetting.getValue()).intValue();
    }

    private double getValueAsDouble(String key) throws FirmwareSettingsException {
        FirmwareSetting firmwareSetting = getSetting(key)
                .orElseThrow(() -> new FirmwareSettingsException("Couldn't find setting with key: " + key));
        if (!NumberUtils.isNumber(firmwareSetting.getValue())) {
            throw new FirmwareSettingsException("Expected the key " + key + " to contain a numeric value but was "
                    + firmwareSetting.getValue());
        }

        return NumberUtils.createNumber(firmwareSetting.getValue()).doubleValue();
    }

    private boolean getValueAsBoolean(String key) throws FirmwareSettingsException {
        FirmwareSetting firmwareSetting = getSetting(key)
                .orElseThrow(() -> new FirmwareSettingsException("Couldn't find setting with key: " + key));
        return "1".equalsIgnoreCase(firmwareSetting.getValue());
    }
}