From 0bb0e2f12193a776791a1f1d3734454321591dc8 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Sun, 13 Jun 2021 12:43:50 +0200 Subject: [PATCH] WIP More Wifi setup --- App.js | 133 +++++++++++------- app.json | 5 +- components/SetupView.js | 14 +- .../SwimTrackerWebsocketConnection.js | 82 +++++++---- state/DeviceReduxCoupling.js | 12 +- views/MainMenuView.js | 7 +- views/WifiPasswordView.js | 62 ++++++-- views/WifiSelectionView.js | 74 ++++++---- 8 files changed, 263 insertions(+), 126 deletions(-) diff --git a/App.js b/App.js index 2980ba1..c7fc148 100644 --- a/App.js +++ b/App.js @@ -6,7 +6,7 @@ import * as Font from 'expo-font'; // Redux + Storage import swimtrackerReducer from './state/Reducer'; import { createStore } from 'redux'; -import { DeviceReduxCoupling } from './state/DeviceReduxCoupling'; +import { ConnState, WifiState, DeviceReduxCoupling } from './state/DeviceReduxCoupling'; import { Provider } from 'react-redux'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { persistStore, persistReducer } from 'redux-persist' @@ -43,17 +43,32 @@ export default class App extends React.Component { super(props); this.state = { isReady: false, + disconnected: true, + isProvisioning: false, }; + this.unsubscribe = undefined; } - async componentDidMount() { - /*await Font.loadAsync({ - Roboto: require('native-base/Fonts/Roboto.ttf'), - Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'), - ...Ionicons.font, - });*/ + componentDidMount() { this.setState({ isReady: true }); this.device = new DeviceReduxCoupling(store); + + console.log("subscribing"); + let theApp = this; + this.unsubscribe = store.subscribe(() => { + const state = store.getState(); + theApp.setState({ + disconnected: state.deviceState.connState == ConnState.DISCONNECTED, + isProvisioning: state.deviceState.wifiState == WifiState.AP_PROVISIONING || state.deviceState.wifiState == WifiState.UNKNOWN, + }); + }); + } + + componentWillUnmount() { + if (this.unsubscribe) { + this.unsubscribe(); + console.log("unsubscribe"); + } } render() { @@ -64,61 +79,77 @@ export default class App extends React.Component { headerShown: false, }; + let disconnectedView = ( + <> + + + ); + + let provisioningView = ( + <> + + {props => } + + + + + ); + + let normalView = ( + <> + + + + + + ); + + let activeView; + if (this.state.disconnected) + activeView = disconnectedView; + else if (this.state.isProvisioning) + activeView = provisioningView; + else + activeView = normalView; return ( } persistor={persistor}> - - - {props => } - - - + + {activeView} ); - /* - return ( - - } persistor={persistor}> - - - - - - - - - - - ); - */ + } } diff --git a/app.json b/app.json index 5cf5487..eb6b338 100644 --- a/app.json +++ b/app.json @@ -25,6 +25,9 @@ "ios": { "supportsTablet": true }, - "description": "" + "description": "", + "android": { + "package": "tech.bauer.swimtracker" + } } } diff --git a/components/SetupView.js b/components/SetupView.js index c0f4c4d..b704ff8 100644 --- a/components/SetupView.js +++ b/components/SetupView.js @@ -14,12 +14,12 @@ function AdditionalOptionsBottomBar(props) { return ( { props.leftText ? - + {props.leftText} : } {props.rightText && - + {props.rightText} } @@ -35,6 +35,9 @@ const bottomBarStyles = StyleSheet.create({ }, text: { color: "rgba(255,255,255,0.5)", + }, + button: { + borderStyle: "dotted" } }) @@ -55,7 +58,7 @@ function SetupView(props) { {props.backButton && - + props.navigation.goBack()}> } @@ -63,7 +66,9 @@ function SetupView(props) { {props.headerText} - {props.children} + + {props.children} + { + conn._wifiScanPromises.push({ resolve: resolve, reject: reject }); + }); - let conn = this; - return new Promise((resolve, reject) => { - conn._wifiScanPromise = { resolve: resolve, reject: reject }; - }); + } + + wifiResetToProvisioning() { + this._sendMsg(OpCodes.WIFI_STATE_SET, { + "reset_to_provisioning": true, + }); + + } + + wifiSetModeAP(password) { + this._sendMsg(OpCodes.WIFI_STATE_SET, { + "ap_password": password, + }); + } + + wifiSetModeSTA(ssid, password) { + this._sendMsg(OpCodes.WIFI_STATE_SET, { + "sta_ssid": ssid, + "sta_password": password, + }); + } + + _sendMsg(code, data) { + let msg = undefined; + if (data) { + const serializedData = msgpack.encode(data); + msg = new Uint8Array([code, ...serializedData]); + } else { + msg = new Uint8Array(1); + msg[0] = OpCodes.WIFI_TRIGGER_SCAN; } + this.ws.send(msg); } _onMessage = (e) => { const dv = new DataView(e.data); const opCode = dv.getInt8(0); + const payload = new Uint8Array(e.data).slice(1); if (opCode === OpCodes.INITIAL_INFO) { const headerSize = 6; @@ -116,14 +137,15 @@ export default class SwimTrackerWebsocketConnection { const data = new Uint16Array(e.data.slice(1)); this.onData(data); } else if (opCode === OpCodes.WIFI_SCAN_RESPONSE) { - console.log("got data", e.data); - const scanResult = msgpack.decode(new Uint8Array(e.data).slice(1), { codec: this.msgpackCodec }); - if (this._wifiScanPromise !== null) { - this._wifiScanPromise.resolve(scanResult); - this._wifiScanPromise = null; - } else { - console.log("Got unexpected WiFi scan result", scanResult); + const scanResult = msgpack.decode(payload, { codec: this.msgpackCodec }); + + for (let i = 0; i < this._wifiScanPromises.length; ++i) { + this._wifiScanPromises[i].resolve(scanResult); } + this._wifiScanPromises.length = 0; + } else if (opCode === OpCodes.WIFI_STATE_RESPONSE) { + const wifiInfo = msgpack.decode(payload, { codec: this.msgpackCodec }); + this.onWifiStateInfo(wifiInfo); } } diff --git a/state/DeviceReduxCoupling.js b/state/DeviceReduxCoupling.js index c52cb8e..2e5a2d2 100644 --- a/state/DeviceReduxCoupling.js +++ b/state/DeviceReduxCoupling.js @@ -87,6 +87,7 @@ export class DeviceReduxCoupling { this._onNewData, (sessionId) => this.reduxStore.dispatch(reportSessionStarted(sessionId)), () => this.reduxStore.dispatch(reportSessionStopped()), + (response) => this.reduxStore.dispatch(reportNewWifiState(response["state"])), () => this.reduxStore.dispatch(reportDeviceConnect()), () => this.reduxStore.dispatch(reportDeviceDisconnect()) ); @@ -152,15 +153,14 @@ export const deviceStateReducer = (state = INITIAL_DEVICE_STATE, action) => { return state; return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_STOPPING }; case WIFI_SET_STATE: - console.log("here"); let wifState = WifiState.UNKNOWN; - if (action.data === "STATION_MODE") { wifState = WifiState.STA; } - else if (action.data === "AP_PROVISIONING") { wifState = WifiState.AP_PROVISIONING; } - else if (action.data === "AP_SECURE") { wifState = WifiState.AP_SECURE; } + if (action.newStateStr === "STATION_MODE") { wifState = WifiState.STA; } + else if (action.newStateStr === "AP_PROVISIONING") { wifState = WifiState.AP_PROVISIONING; } + else if (action.newStateStr === "AP_SECURE") { wifState = WifiState.AP_SECURE; } return { ...state, wifiState: wifState }; default: - console.log("Unhandled state in deviceStateReducer", action, action.type); - return state + //console.log("Unhandled state in deviceStateReducer", action, action.type, "state", state); + return state; } }; diff --git a/views/MainMenuView.js b/views/MainMenuView.js index d59f43b..b3d4114 100644 --- a/views/MainMenuView.js +++ b/views/MainMenuView.js @@ -10,6 +10,11 @@ import { import themeColors from '../components/themeColors'; import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons"; import EntypoIcon from "react-native-vector-icons/Entypo"; +import FeatherIcon from "react-native-vector-icons/Feather"; + +import { MaterialCommunityIcons } from '@expo/vector-icons'; + + import { ConnState, startSession } from '../state/DeviceReduxCoupling'; import { connect } from 'react-redux'; @@ -83,7 +88,7 @@ function ButtonGrid(props) { style={[{ backgroundColor: themeColors["MIDNIGHT BLUE"] }, buttonGridStyles.button]} activeOpacity={0.6} > - + EINSTELLUNGEN diff --git a/views/WifiPasswordView.js b/views/WifiPasswordView.js index 66e34b2..b2ad7d7 100644 --- a/views/WifiPasswordView.js +++ b/views/WifiPasswordView.js @@ -1,33 +1,69 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { StyleSheet, Text, View, TouchableOpacity, TextInput, + Keyboard } from "react-native"; import SetupView from '../components/SetupView'; import EvilIcon from "react-native-vector-icons/EvilIcons"; import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons"; import themeColors from '../components/themeColors'; - function WifiPasswordView(props) { - props = {...props, ...props.route.params}; + props = { ...props, ...props.route.params }; + + useEffect(() => { + Keyboard.addListener("keyboardDidShow", _keyboardDidShow); + Keyboard.addListener("keyboardDidHide", _keyboardDidHide); + + // cleanup function + return () => { + Keyboard.removeListener("keyboardDidShow", _keyboardDidShow); + Keyboard.removeListener("keyboardDidHide", _keyboardDidHide); + }; + }, []); + + const [keyboardStatus, setKeyboardStatus] = useState(undefined); + const [password1, setPassword1] = useState(""); + const [password2, setPassword2] = useState(""); + const [errorMsg, setErrorMsg] = useState(""); + + const _keyboardDidShow = () => setKeyboardStatus(true); + const _keyboardDidHide = () => setKeyboardStatus(false); let iconName = "wifi-strength-" + props.strength; if (props.lock) { iconName += "-lock"; } + const onSubmit = () => { + + if (props.confirmPwInput && password1 != password2) + setErrorMsg("Passwords don't match"); + else if (password1.length < 8) + setErrorMsg("Password has to be at least 8 characters long") + else if (password1.length > 128) + setErrorMsg("Password too long"); + else + setErrorMsg(""); + }; + return ( - - {props.subText} - + + {!keyboardStatus && + + {props.subText} + + } @@ -38,6 +74,7 @@ function WifiPasswordView(props) { } - + {errorMsg.length > 0 && + + {errorMsg} + + } + + {props.buttonText} - @@ -91,9 +134,10 @@ const styles = StyleSheet.create({ subtext: { color: "rgba(255,255,255,1)", textAlign: "left", - fontSize: 18, + fontSize: 16, lineHeight: 25, width: "80%", + paddingBottom: 30, }, formContainer: { }, diff --git a/views/WifiSelectionView.js b/views/WifiSelectionView.js index 31f0ed3..8d89e93 100644 --- a/views/WifiSelectionView.js +++ b/views/WifiSelectionView.js @@ -56,6 +56,7 @@ class WifiSelectionView extends React.Component { constructor() { super(); this.state = { wifiInfo: [] }; + this.mounted = false; } processDeviceResponse(response) { @@ -91,39 +92,56 @@ class WifiSelectionView extends React.Component { } componentDidMount() { + let component = this; + component.mounted = true; this.props.device.conn.scanWifiNetworks().then( (result) => { - this.setState({ wifiInfo: this.processDeviceResponse(result) }) + if(component.mounted) { + this.setState({ wifiInfo: this.processDeviceResponse(result) }) + } } ); } + componentWillUnmount() { + this.mounted = false; + } + render() { let inner; if (this.state.wifiInfo.length > 0) { inner = ( - - {this.state.wifiInfo.map(e => ( - { this.props.navigation.navigate("WifiPasswordView", { - ssid: e.ssid, - lock: e.locked, - strength: e.strength, - buttonText: "Set Password", - subText: "Please enter password for your home WiFi", - }); }}> - ) - )} - ) + + + {this.state.wifiInfo.map(e => ( + { + this.props.navigation.navigate("WifiPasswordView", { + ssid: e.ssid, + lock: e.locked, + strength: e.strength, + confirmPwInput: false, + buttonText: "OK", + subText: "Please enter the password for your home WiFi", + }); + }}> + ) + )} + + + ) } else { - inner = ( - + inner = ( + + Scanning WiFi networks + + ) } @@ -131,11 +149,19 @@ class WifiSelectionView extends React.Component { { + this.props.navigation.navigate("WifiPasswordView", { + ssid: "swimtracker-E2842S", // todo real id here + lock: true, + strength: 4, + confirmPwInput: true, + buttonText: "Set Password", + subText: "Use this option only if you're home WiFi doesn't reach the SwimTracker. The SwimTracker creates its own WiFi with the password you set here.", + }); + }} lowerRightButtonText="Need help?" > - - {inner} - + {inner} ) } @@ -145,7 +171,7 @@ class WifiSelectionView extends React.Component { const styles = StyleSheet.create({ listContainer: { height: "75%", - //backgroundColor: "red", + flex: 1, } });