/* * Copyright 2014 Bevbot LLC <info@bevbot.com> * * This file is part of the Kegtab package from the Kegbot project. For * more information on Kegtab or Kegbot, see <http://kegbot.org/>. * * Kegtab 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, version 2. * * Kegtab 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 Kegtab. If not, see <http://www.gnu.org/licenses/>. */ package org.kegbot.app; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.SeekBar; import android.widget.TextView; import com.squareup.otto.Subscribe; import org.kegbot.app.config.AppConfiguration; import org.kegbot.app.util.Units; import org.kegbot.app.view.BadgeView; import org.kegbot.backend.Backend; import org.kegbot.backend.BackendException; import org.kegbot.core.FlowMeter; import org.kegbot.core.KegbotCore; import org.kegbot.core.hardware.HardwareManager; import org.kegbot.core.hardware.MeterUpdateEvent; import org.kegbot.proto.Models.KegTap; public class CalibrationActivity extends CoreActivity { private static final String TAG = CalibrationActivity.class.getSimpleName(); private static final boolean DEBUG = false; private static final String EXTRA_METER_NAME = "meter_name"; private static final String EXTRA_ML_PER_TICK = "ml_per_tick"; // 50% - 150% // (0, 0.5) // (100, 1.5) private static final double SCALE_M = (1.5 - 0.5) / 100; // y = Mx + b // b = y - Mx private static final double SCALE_B = 1.5 - (SCALE_M * 100); private HardwareManager mHardwareManager; private KegTap mTap; private String mMeterName; private double mTicksPerMl; private double mExistingTicksPerMl; private BadgeView mTicksBadge; private SeekBar mSeekBar; private BadgeView mVolumeBadge; private TextView mPercentText; private TextView mCalibratedMlTickText; private TextView mOriginalMlTickText; private Button mDoneButton; private Button mResetButton; private long mLastReading = Long.MIN_VALUE; private long mTicks = 0; private boolean mMetric = false; @Subscribe public void onMeterUpdate(MeterUpdateEvent event) { final FlowMeter meter = event.getMeter(); final String name = meter.getMeterName(); Log.d(TAG, "Meter update: " + name); if (name.equals(mMeterName)) { handleMeterUpdate(meter.getTicks()); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.calibration_activity); } @Override protected void onStart() { super.onStart(); final AppConfiguration config = KegbotApplication.get(this).getConfig(); mMetric = config.getUseMetric(); mMeterName = getIntent().getStringExtra(EXTRA_METER_NAME); mTap = KegbotCore.getInstance(this).getTapManager().getTapForMeterName(mMeterName); if (mTap == null || mTap.getMeter() == null) { Log.e(TAG, "No tap for meter name: " + mMeterName); finish(); return; } mExistingTicksPerMl = mTap.getMeter().getTicksPerMl(); mTicksPerMl = mExistingTicksPerMl; mTicksBadge = (BadgeView) findViewById(R.id.ticksBadge); mTicksBadge.setBadgeCaption("Ticks"); mTicksBadge.setBadgeValue("0"); mVolumeBadge = (BadgeView) findViewById(R.id.volumeBadge); if (mMetric) { mVolumeBadge.setBadgeCaption(getString(R.string.calibration_badge_actual_metric)); mVolumeBadge.setBadgeValue("0"); } else { mVolumeBadge.setBadgeCaption(getString(R.string.calibration_badge_actual_imperial)); mVolumeBadge.setBadgeValue("0.0"); } mVolumeBadge.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onVolumeBadgeClicked(); } }); mPercentText = (TextView) findViewById(R.id.calibratePercent); mCalibratedMlTickText = (TextView) findViewById(R.id.calibratedTicksPerMl); mOriginalMlTickText = (TextView) findViewById(R.id.originalTicksPerMl); mOriginalMlTickText.setText(Double.valueOf(mExistingTicksPerMl).toString()); mOriginalMlTickText.setText(String.format("%.2f", Double.valueOf(mExistingTicksPerMl))); mSeekBar = (SeekBar) findViewById(R.id.calibrateSeekBar); mSeekBar.setMax(99); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { double mult = getMultiplier(); mTicksPerMl = mExistingTicksPerMl * mult; updateMetrics(); if (DEBUG) Log.d(TAG, "onProgressChanged: progress=" + progress + " mult=" + mult); } }); mResetButton = (Button) findViewById(R.id.calibrateReset); mResetButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { resetCalibration(); } }); mDoneButton = (Button) findViewById(R.id.calibrateDone); mDoneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finishCalibration(); } }); Log.d(TAG, "Started: meterName=" + mMeterName); resetCalibration(); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); mHardwareManager = KegbotCore.getInstance(getApplicationContext()).getHardwareManager(); mHardwareManager.toggleOutput(mTap, true); final KegbotCore core = KegbotCore.getInstance(this); core.getBus().register(this); core.getFlowManager().setPaused(true); mTap = KegbotCore.getInstance(getApplicationContext()).getTapManager().getTapForMeterName( mMeterName); mHardwareManager.toggleOutput(mTap, true); } @Override protected void onPause() { mHardwareManager.toggleOutput(mTap, false); final KegbotCore core = KegbotCore.getInstance(this); core.getFlowManager().setPaused(false); core.getBus().unregister(this); super.onPause(); } private void handleMeterUpdate(long meterReading) { if (mLastReading < 0) { mLastReading = meterReading; return; } final long delta = meterReading - mLastReading; mTicks += delta; Log.d(TAG, "Update: delta=" + delta + " ticks=" + mTicks); mLastReading = meterReading; runOnUiThread(new Runnable() { @Override public void run() { updateMetrics(); } }); } private double getMultiplier() { double result = SCALE_M * mSeekBar.getProgress() + SCALE_B; if (DEBUG) { Log.d(TAG, "getMultipler: " + result + " = " + SCALE_M + "*" + mSeekBar.getProgress() + " + " + SCALE_B); } return result; } private void updateMetrics() { mTicksBadge.setBadgeValue(String.valueOf(mTicks)); mVolumeBadge.setBadgeValue(getDisplayVolume()); mCalibratedMlTickText.setText(String.format("%.2f", Double.valueOf(mTicksPerMl))); Integer percent = Integer.valueOf((int) (getMultiplier() * 100)); mPercentText.setText(String.format("%s%%", percent)); if (mTicksPerMl != mExistingTicksPerMl) { mResetButton.setEnabled(true); mDoneButton.setEnabled(true); } else { mResetButton.setEnabled(false); mDoneButton.setEnabled(false); } } private void resetCalibration() { mTicksPerMl = mExistingTicksPerMl; mSeekBar.setEnabled(true); mSeekBar.setProgress(50); mResetButton.setEnabled(false); updateMetrics(); } private void finishCalibration() { if (mExistingTicksPerMl == mTicksPerMl) { Log.d(TAG, "No change."); finish(); return; } final ProgressDialog dialog = new ProgressDialog(this); dialog.setIndeterminate(true); dialog.setCancelable(false); dialog.setTitle("Calibrating Tap"); dialog.setMessage("Please wait ..."); dialog.show(); new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { final KegbotCore core = KegbotCore.getInstance(CalibrationActivity.this); final Backend backend = core.getBackend(); try { backend.calibrateMeter(mTap.getMeter(), mTicksPerMl); core.getSyncManager().requestSync(); return ""; } catch (BackendException e) { Log.w(TAG, "Error calibrating: " + e, e); return e.toString(); } } @Override protected void onPostExecute(String result) { dialog.dismiss(); if (result.isEmpty()) { Log.d(TAG, "Calibrated successfully!"); finish(); return; } new AlertDialog.Builder(CalibrationActivity.this) .setCancelable(true) .setNegativeButton("Ok", null) .setTitle("Calibration failed") .setMessage("Calibration failed: " + result) .show(); } }.execute(); } private void onVolumeBadgeClicked() { if (mTicks <= 0) { showNeedTicksDialog(); } else { showCustomVolumeDialog(); } } /** Admonishes the user to pour something before calibrating. */ private void showNeedTicksDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true) .setTitle(R.string.calibrate_dialog_no_ticks_title) .setMessage(R.string.calibrate_dialog_no_ticks_message) .setPositiveButton("Ok", null) .show(); } /** Prompts the user to enter a custom volume for current ticks. */ private void showCustomVolumeDialog() { final View dialogView = getLayoutInflater().inflate(R.layout.calibrate_custom_volume_dialog, null); final EditText dialogVolume = (EditText) dialogView.findViewById(R.id.calibrateCustomVolume); dialogVolume.append(getDisplayVolume()); // advances cursor to end, too final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true) .setTitle(R.string.calibrate_dialog_custom_title) .setMessage(R.string.calibrate_dialog_custom_message) .setPositiveButton("Use This Volume", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d(TAG, "Custom volume: " + dialogVolume.getText()); onCustomVolumeEntered(dialogVolume.getText().toString()); } }) .setView(dialogView) .setNegativeButton("Cancel", null) .show(); } private void onCustomVolumeEntered(String stringValue) { final Double value; try { value = Double.valueOf(stringValue); } catch (NumberFormatException e) { Log.w(TAG, "Dropping bogus value: " + e); return; } if (value.doubleValue() <= 0) { Log.w(TAG, "Value out of range: " + value); return; } if (mMetric) { mTicksPerMl = mTicks / value.doubleValue(); } else { mTicksPerMl = mTicks / Units.volumeOuncesToMl(value.doubleValue()); } mVolumeBadge.setBadgeValue(String.format("%.2f", value)); mSeekBar.setEnabled(false); mResetButton.setEnabled(true); updateMetrics(); } private double getTicksPerMl() { return getMultiplier() * mTicksPerMl; } private double getVolumeMl() { return mTicks / getTicksPerMl(); } private String getDisplayVolume() { if (mMetric) { return String.format("%s", (int) getVolumeMl()); } else { return String.format("%.2f", Double.valueOf(Units.volumeMlToOunces(getVolumeMl()))); } } static Intent getStartIntent(final Context context, final KegTap tap) { final Intent intent = new Intent(context, CalibrationActivity.class); intent.putExtra(EXTRA_METER_NAME, tap.getMeter().getName()); intent.putExtra(EXTRA_ML_PER_TICK, 1 / tap.getMeter().getTicksPerMl()); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); return intent; } }