From 0493c8a98f895534fbbf290ddb57584231113d62 Mon Sep 17 00:00:00 2001 From: Alberto Geniola Date: Sat, 22 Jan 2022 19:45:22 +0100 Subject: [PATCH] - Implemented Meross cloud paring vs custom - Fix wifi-handlers being called multiple times - Improved wifi-location warning banner logic - Minor labeling and textual refactor - Minor UI improvements --- app/src/main/AndroidManifest.xml | 4 +- .../albertogeniola/merossconf/Constants.java | 2 + .../merossconf/ui/PairActivityViewModel.java | 19 ++++++++ .../ui/fragments/account/AccountFragment.java | 8 ++++ .../fragments/pair/AbstractWifiFragment.java | 6 ++- .../fragments/pair/ConfigureMqttFragment.java | 26 ++++++++++ .../fragments/pair/ConfigureWifiFragment.java | 10 ++-- .../pair/ExecutePairingFragment.java | 17 +++++-- app/src/main/res/layout/activity_main.xml | 1 + app/src/main/res/layout/fragment_account.xml | 17 ++++++- .../main/res/layout/fragment_mqtt_config.xml | 47 ++++++++++++++++++- app/src/main/res/values/attrs.xml | 3 -- app/src/main/res/values/colors.xml | 11 +++-- app/src/main/res/values/strings.xml | 19 +++++--- app/src/main/res/values/styles.xml | 10 ++-- 15 files changed, 168 insertions(+), 32 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index afcd335..fef6c2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,12 +19,12 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> - + targetMqttConfig; private MutableLiveData deviceInfo; private MutableLiveData wifiLocationStatus; + private MutableLiveData overridedKey; + private MutableLiveData overridedUserId; public PairActivityViewModel() { @@ -30,6 +33,8 @@ public PairActivityViewModel() { deviceInfo = new MutableLiveData<>(null); deviceAvailableWifis = new MutableLiveData<>(null); wifiLocationStatus = new MutableLiveData<>(null); + overridedKey = new MutableLiveData<>(null); + overridedUserId = new MutableLiveData<>(null); } public LiveData getDevice() { @@ -80,4 +85,18 @@ public LiveData getWifiLocationStatus() { public void setWifiLocationStatus(WifiLocationStatus status) { this.wifiLocationStatus.setValue(status); } + + public LiveData getOverridedUserId() { + return this.overridedUserId; + } + public void setOverrideUserId(String overrideUserId) { + this.overridedUserId.setValue(overrideUserId); + } + + public LiveData getOverridedKey() { + return this.overridedKey; + } + public void setOverrideKey(String overrideKey) { + this.overridedKey.setValue(overrideKey); + } } \ No newline at end of file diff --git a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/account/AccountFragment.java b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/account/AccountFragment.java index 2cbd85f..59f8192 100644 --- a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/account/AccountFragment.java +++ b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/account/AccountFragment.java @@ -7,6 +7,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -44,10 +45,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, final Button merossCloudLoginButton = root.findViewById(R.id.merossCloudLoginButton); final CardView loginCardView = root.findViewById(R.id.loginCard); final Button manualSetupButton = root.findViewById(R.id.setManualButton); + final ImageView loggedInAccountLogoImageView = root.findViewById(R.id.loggedInAccountLogo); mainActivityViewModel.getCredentials().observe(getViewLifecycleOwner(), new Observer() { @Override public void onChanged(ApiCredentials apiCredentials) { + if (apiCredentials == null || apiCredentials.getApiServer().compareTo(Constants.MEROSS_CLOUD_EP)==0) { + loggedInAccountLogoImageView.setImageResource(R.drawable.meross_logo); + } else { + loggedInAccountLogoImageView.setImageResource(R.drawable.ha_logo); + } + if (apiCredentials == null || Strings.isEmpty(apiCredentials.getApiServer())) { httpUrlEditText.setText("Not set"); } else { diff --git a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/AbstractWifiFragment.java b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/AbstractWifiFragment.java index af29a70..f94304d 100644 --- a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/AbstractWifiFragment.java +++ b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/AbstractWifiFragment.java @@ -86,6 +86,11 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onDestroy() { super.onDestroy(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); // Unschedule the timeout task, if any if (mTimeoutTask!=null) { @@ -108,7 +113,6 @@ public void onDestroy() { mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } - @Override public void onPause() { super.onPause(); diff --git a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureMqttFragment.java b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureMqttFragment.java index ffe209d..de3cbbd 100644 --- a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureMqttFragment.java +++ b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureMqttFragment.java @@ -23,6 +23,8 @@ import com.albertogeniola.merossconf.R; import com.albertogeniola.merossconf.model.MqttConfiguration; import com.albertogeniola.merossconf.ui.PairActivityViewModel; +import com.albertogeniola.merosslib.model.http.ApiCredentials; +import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; import java.util.List; @@ -35,6 +37,9 @@ public class ConfigureMqttFragment extends Fragment { private TextInputLayout mqttPortEditText; private Spinner mqttConfigurationSpinner; private CheckBox saveCheckbox; + private CheckBox overrideMqttParamsCheckbox; + private TextInputLayout customMqttUserId; + private TextInputLayout customMqttKey; private MqttConfiguration mDiscoveredConfig; private ArrayAdapter adapter; private MqttConfiguration newMqttConfig = new MqttConfiguration("Add new...", null, -1); @@ -81,6 +86,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mqttPortEditText = view.findViewById(R.id.mqttPortEditText); Button pairButton = view.findViewById(R.id.pairButton); saveCheckbox = view.findViewById(R.id.saveCheckbox); + customMqttUserId = view.findViewById(R.id.customMqttUserId); + customMqttKey = view.findViewById(R.id.customMqttKey); + overrideMqttParamsCheckbox = view.findViewById(R.id.overrideMqttParams); + + customMqttKey.setVisibility(View.GONE); + customMqttUserId.setVisibility(View.GONE); List configurations = AndroidPreferencesManager.loadAllMqttConfigurations(requireContext()); configurations.add(this.newMqttConfig); @@ -94,6 +105,13 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mqttConfigurationNameEditText.setVisibility(isChecked ? View.VISIBLE : View.GONE); } }); + overrideMqttParamsCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + customMqttUserId.setVisibility(isChecked ? View.VISIBLE:View.GONE); + customMqttKey.setVisibility(isChecked ? View.VISIBLE:View.GONE); + } + }); mqttConfigurationSpinner.setAdapter(adapter); mqttConfigurationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @@ -187,6 +205,14 @@ public void onClick(View v) { pairActivityViewModel.setTargetMqttConfig(tmp); } + if (overrideMqttParamsCheckbox.isChecked()) { + pairActivityViewModel.setOverrideKey(customMqttKey.getEditText().toString()); + pairActivityViewModel.setOverrideUserId(customMqttUserId.getEditText().toString()); + } else { + pairActivityViewModel.setOverrideKey(null); + pairActivityViewModel.setOverrideUserId(null); + } + NavController ctrl = NavHostFragment.findNavController(ConfigureMqttFragment.this); ctrl.navigate(R.id.action_configureMqtt_to_executePair, null, new NavOptions.Builder().setEnterAnim(android.R.animator.fade_in).setExitAnim(android.R.animator.fade_out).build()); } diff --git a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureWifiFragment.java b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureWifiFragment.java index 5ba0843..9f256c5 100644 --- a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureWifiFragment.java +++ b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ConfigureWifiFragment.java @@ -31,6 +31,7 @@ import androidx.navigation.fragment.NavHostFragment; import com.albertogeniola.merossconf.AndroidPreferencesManager; +import com.albertogeniola.merossconf.Constants; import com.albertogeniola.merossconf.MerossUtils; import com.albertogeniola.merossconf.R; import com.albertogeniola.merossconf.model.WifiConfiguration; @@ -198,10 +199,10 @@ private void startApiDiscovery() { mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); } - // Start a timer for aborting discovery after 10 seconds if nothing is found. + // Start a timer for aborting discovery after some time if nothing is found. if (mTimer == null) { mTimer = new Timer(); - mTimer.schedule(new ConfigureWifiFragment.TimeoutTask(), 5000); + mTimer.schedule(new ConfigureWifiFragment.TimeoutTask(), Constants.MDNS_DISCOVERY_TIMEOUT_MILLISEOONDS); } } @@ -307,8 +308,10 @@ private void notifyResolveCompleted(@Nullable final String hostname, } // Cancel the timeout task - if (mTimer!=null) + if (mTimer!=null) { mTimer.cancel(); + mTimer = null; + } Runnable r = new Runnable() { @Override @@ -457,7 +460,6 @@ public void onServiceResolved(final NsdServiceInfo serviceInfo) { private class TimeoutTask extends TimerTask { @Override public void run() { - mTimer = null; if (mDiscoveryInProgress) mNsdManager.stopServiceDiscovery(mDiscoveryListener); diff --git a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ExecutePairingFragment.java b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ExecutePairingFragment.java index 1af5d84..9e71568 100644 --- a/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ExecutePairingFragment.java +++ b/app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ExecutePairingFragment.java @@ -18,6 +18,7 @@ import androidx.navigation.fragment.NavHostFragment; import com.albertogeniola.merossconf.AndroidPreferencesManager; +import com.albertogeniola.merossconf.Constants; import com.albertogeniola.merossconf.R; import com.albertogeniola.merossconf.model.MqttConfiguration; import com.albertogeniola.merossconf.model.exception.PermissionNotGrantedException; @@ -76,7 +77,7 @@ private void stateMachine(Signal signal) { if (signal == Signal.RESUMED) {connectToDeviceWifiAp();} break; case CONNECTING_DEVICE_WIFI_AP: - if (signal == Signal.DEVICE_WIFI_CONNECTED) {configureDevice(mCreds.getUserId(), mCreds.getKey());} + if (signal == Signal.DEVICE_WIFI_CONNECTED) {configureDevice();} break; case SENDING_PAIRING_COMMAND: if (signal == Signal.DEVICE_CONFIGURED) { @@ -93,6 +94,7 @@ private void stateMachine(Signal signal) { break; case VERIFYING_PAIRING_SUCCEEDED: if (signal == Signal.DEVICE_PAIRED) {completeActivityFragment(true);} + if (signal == Signal.MISSING_CONFIRMATION) {completeActivityFragment(false);} break; } @@ -142,7 +144,7 @@ private void connectToLocalWifi() { private void pollDeviceList() { state = State.VERIFYING_PAIRING_SUCCEEDED; - final long timeout = GregorianCalendar.getInstance().getTimeInMillis() + 60000; // 30 seconds timeout + final long timeout = GregorianCalendar.getInstance().getTimeInMillis() + Constants.PAIRING_VERIFY_TIMEOUT_MILLISECONDS; ScheduledFuture future = worker.schedule(new Runnable() { private @Nullable DeviceInfo findDevice(Collection devices, String deviceUuid) { for (DeviceInfo d : devices) { @@ -184,6 +186,7 @@ public void run() { Log.e(TAG, "An unexpected exception occurred", e); } finally { timedOut = GregorianCalendar.getInstance().getTimeInMillis() >= timeout; + error = "Timeout: waiting for confirmation from remote broker."; try { Thread.sleep(1000); } catch (InterruptedException e) { @@ -195,13 +198,15 @@ public void run() { } } - final boolean finalSucceeed = succeeed; + final boolean finalTimedOut = timedOut; uiThreadHandler.post(new Runnable() { @Override public void run() { if (finalSucceeed) stateMachine(Signal.DEVICE_PAIRED); + else if (finalTimedOut) + stateMachine(Signal.MISSING_CONFIRMATION); else stateMachine(Signal.ERROR); } @@ -210,8 +215,11 @@ public void run() { }, 2, TimeUnit.SECONDS); } - private void configureDevice(final String userId, final String key) { + private void configureDevice() { state = State.SENDING_PAIRING_COMMAND; + // In custom credentials has been specified, override userId/key + final String userId = pairActivityViewModel.getOverridedUserId().getValue() != null ? pairActivityViewModel.getOverridedUserId().getValue() : mCreds.getUserId(); + final String key = pairActivityViewModel.getOverridedKey().getValue() != null ? pairActivityViewModel.getOverridedKey().getValue() : mCreds.getKey(); worker.schedule(new Runnable() { @Override @@ -438,6 +446,7 @@ enum Signal { DEVICE_CONFIGURED, LOCAL_WIFI_CONNECTED, DEVICE_PAIRED, + MISSING_CONFIRMATION, ERROR } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8e69e22..a0411dd 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" + android:theme="@style/AppTheme" tools:openDrawer="start"> + + + + + android:text="N/A" + android:textIsSelectable="true" /> - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 13acc41..67f3375 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -9,8 +9,5 @@ - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 47cb140..27a6a3d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,13 @@ - #6200EE - #3700B3 - #03DAC5 + #009688 + #00796B + #B2DFDB + #FFC107 + #757575 + + #212121 + #BDBDBD #66000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89367ad..cd5034c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,12 +25,6 @@ Pairing completed icon "The discovery processs has queried the device and obtained the following information. " Pair! - Meross plugs are only capable to connect to MQTT over TLS connections (default port 8883). Using a clear-mqtt connection won\'t work. - MQTT Server - Configuration Name... - Server hostname or IP... - Server Port - Save for future use Configuring MQTT info... Configuring WiFi... "Please be patient while the app connects to the Meross Device. " @@ -61,7 +55,7 @@ Navigation header Settings - Account + Account & Login Pair Devices (coming soon!) DRAWER OPEN @@ -72,6 +66,17 @@ Fill in your credentials as you would do in the Official Meross app to proceed. Make sure the Meross Local Broker (on HomeAssistant) Addon is started and log-in using the credentials configured in the configuration section of that Addon. + Meross plugs are only capable to connect to MQTT over TLS connections (default port 8883). Using a clear-mqtt connection won\'t work. + MQTT Server + Configuration Name... + Server hostname or IP... + Server Port + Save for future use + Override USER-ID/KEY parameters. + Custom User-Id + Custom key + Your account data is represented as follows. + User Key Hello blank fragment diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f1a59da..6e94cfb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,18 +2,18 @@