Add BLE fix for saved WiFi credentials + build version indicator
BLE Fix: - Check if sensor is already connected to target WiFi before sending credentials - Handle W|fail when sensor uses saved credentials instead of new password - Return success if sensor is connected to target network even after W|fail Build Version Indicator: - Add visible version badge on Dashboard screen (v2.1.0 • 2026-01-27 17:05) - Green text on dark background in bottom-right corner - Helps verify which build is running on device 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5fe44ccd92
commit
7149d25ba4
@ -296,17 +296,12 @@ export default function EquipmentScreen() {
|
||||
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Sensors</Text>
|
||||
<View style={styles.headerRight}>
|
||||
<TouchableOpacity style={styles.addButton} onPress={handleAddSensor}>
|
||||
<Ionicons name="add" size={24} color={AppColors.primary} />
|
||||
</TouchableOpacity>
|
||||
<BeneficiaryMenu
|
||||
beneficiaryId={id || ''}
|
||||
userRole={currentBeneficiary?.role}
|
||||
currentPage="sensors"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Simulator Warning */}
|
||||
{!isBLEAvailable && (
|
||||
@ -361,7 +356,7 @@ export default function EquipmentScreen() {
|
||||
{apiSensors.length === 0 ? (
|
||||
<View style={styles.emptyState}>
|
||||
<View style={styles.emptyIconContainer}>
|
||||
<Ionicons name="water-outline" size={48} color={AppColors.textMuted} />
|
||||
<Ionicons name="bluetooth-outline" size={48} color={AppColors.textMuted} />
|
||||
</View>
|
||||
<Text style={styles.emptyTitle}>No Sensors Connected</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
@ -515,19 +510,6 @@ const styles = StyleSheet.create({
|
||||
placeholder: {
|
||||
width: 32,
|
||||
},
|
||||
headerRight: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: Spacing.xs,
|
||||
},
|
||||
addButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: BorderRadius.md,
|
||||
backgroundColor: AppColors.primaryLighter,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
@ -13,6 +13,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { router, useLocalSearchParams } from 'expo-router';
|
||||
import * as Device from 'expo-device';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useBLE } from '@/contexts/BLEContext';
|
||||
import { api } from '@/services/api';
|
||||
import type { WiFiNetwork } from '@/services/ble';
|
||||
@ -110,7 +111,19 @@ export default function SetupWiFiScreen() {
|
||||
const setupInProgressRef = useRef(false);
|
||||
const shouldCancelRef = useRef(false);
|
||||
|
||||
// Load saved WiFi password on mount
|
||||
useEffect(() => {
|
||||
const loadSavedPassword = async () => {
|
||||
try {
|
||||
const savedPassword = await AsyncStorage.getItem('LAST_WIFI_PASSWORD');
|
||||
if (savedPassword) {
|
||||
setPassword(savedPassword);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[SetupWiFi] Failed to load saved password:', error);
|
||||
}
|
||||
};
|
||||
loadSavedPassword();
|
||||
loadWiFiNetworks();
|
||||
}, []);
|
||||
|
||||
@ -233,14 +246,22 @@ export default function SetupWiFiScreen() {
|
||||
updateSensorStatus(deviceId, 'attaching');
|
||||
|
||||
if (!isSimulator && wellId) {
|
||||
console.log('[SetupWiFi] Attaching device to beneficiary:', {
|
||||
beneficiaryId: id,
|
||||
wellId,
|
||||
ssid,
|
||||
});
|
||||
const attachResponse = await api.attachDeviceToBeneficiary(
|
||||
id!,
|
||||
wellId,
|
||||
ssid,
|
||||
pwd
|
||||
);
|
||||
console.log('[SetupWiFi] Attach response:', attachResponse);
|
||||
if (!attachResponse.ok) {
|
||||
throw new Error('Failed to register sensor');
|
||||
const errorDetail = attachResponse.error || 'Unknown API error';
|
||||
console.error('[SetupWiFi] Attach FAILED:', errorDetail);
|
||||
throw new Error(`Failed to register sensor: ${errorDetail}`);
|
||||
}
|
||||
}
|
||||
updateSensorStep(deviceId, 'attach', 'completed');
|
||||
@ -364,7 +385,7 @@ export default function SetupWiFiScreen() {
|
||||
}, [sensors, currentIndex, selectedNetwork, password, isPaused, processSensor]);
|
||||
|
||||
// Start batch setup
|
||||
const handleStartBatchSetup = () => {
|
||||
const handleStartBatchSetup = async () => {
|
||||
if (!selectedNetwork) {
|
||||
Alert.alert('Error', 'Please select a WiFi network');
|
||||
return;
|
||||
@ -374,6 +395,14 @@ export default function SetupWiFiScreen() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save password for next time
|
||||
try {
|
||||
await AsyncStorage.setItem('LAST_WIFI_PASSWORD', password);
|
||||
console.log('[SetupWiFi] Password saved for future use');
|
||||
} catch (error) {
|
||||
console.log('[SetupWiFi] Failed to save password:', error);
|
||||
}
|
||||
|
||||
// Initialize sensor states
|
||||
const initialStates = selectedDevices.map(createSensorState);
|
||||
setSensors(initialStates);
|
||||
|
||||
@ -9,6 +9,10 @@ import { FullScreenError } from '@/components/ui/ErrorMessage';
|
||||
|
||||
const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard';
|
||||
|
||||
// BUILD VERSION INDICATOR - видно на экране для проверки версии
|
||||
const BUILD_VERSION = 'v2.1.0';
|
||||
const BUILD_TIMESTAMP = '2026-01-27 17:05';
|
||||
|
||||
export default function DashboardScreen() {
|
||||
const webViewRef = useRef<WebView>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -64,6 +68,13 @@ export default function DashboardScreen() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Build Version Indicator - ALWAYS VISIBLE */}
|
||||
<View style={styles.buildVersionBadge}>
|
||||
<Text style={styles.buildVersionText}>
|
||||
{BUILD_VERSION} • {BUILD_TIMESTAMP}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* WebView */}
|
||||
<View style={styles.webViewContainer}>
|
||||
<WebView
|
||||
@ -159,4 +170,20 @@ const styles = StyleSheet.create({
|
||||
fontSize: FontSizes.base,
|
||||
color: AppColors.textSecondary,
|
||||
},
|
||||
buildVersionBadge: {
|
||||
position: 'absolute',
|
||||
bottom: 60,
|
||||
right: 10,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
zIndex: 9999,
|
||||
},
|
||||
buildVersionText: {
|
||||
color: '#00FF00',
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
});
|
||||
|
||||
@ -1811,9 +1811,15 @@ class ApiService {
|
||||
password: string
|
||||
) {
|
||||
try {
|
||||
console.log('[API] attachDeviceToBeneficiary called:', { beneficiaryId, wellId, ssid });
|
||||
|
||||
// Get auth token for WellNuo API
|
||||
const token = await this.getToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
if (!token) {
|
||||
console.error('[API] No auth token');
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
console.log('[API] Got auth token');
|
||||
|
||||
// Get beneficiary details
|
||||
const response = await fetch(`${this.baseUrl}/me/beneficiaries/${beneficiaryId}`, {
|
||||
@ -1822,27 +1828,43 @@ class ApiService {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to get beneficiary');
|
||||
console.log('[API] Beneficiary response status:', response.status);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[API] Failed to get beneficiary:', errorText);
|
||||
throw new Error(`Failed to get beneficiary: ${response.status}`);
|
||||
}
|
||||
|
||||
const beneficiary = await response.json();
|
||||
const deploymentId = beneficiary.deploymentId;
|
||||
const beneficiaryName = beneficiary.firstName || 'Sensor';
|
||||
console.log('[API] Beneficiary data:', { deploymentId, firstName: beneficiary.firstName, beneficiaryName });
|
||||
|
||||
if (!deploymentId) {
|
||||
console.error('[API] Beneficiary has no deploymentId');
|
||||
throw new Error('Beneficiary has no deployment');
|
||||
}
|
||||
|
||||
const creds = await this.getLegacyCredentials();
|
||||
if (!creds) throw new Error('Not authenticated with Legacy API');
|
||||
if (!creds) {
|
||||
console.error('[API] No Legacy API credentials');
|
||||
throw new Error('Not authenticated with Legacy API');
|
||||
}
|
||||
console.log('[API] Got Legacy credentials for user:', creds.userName);
|
||||
|
||||
// Call set_deployment to attach device
|
||||
// Use device_form to attach device to deployment
|
||||
// Note: set_deployment now requires beneficiary_photo and email which we don't have
|
||||
// device_form is simpler and just assigns the device to a deployment
|
||||
const formData = new URLSearchParams({
|
||||
function: 'set_deployment',
|
||||
function: 'device_form',
|
||||
user_name: creds.userName,
|
||||
token: creds.token,
|
||||
device_id: wellId.toString(),
|
||||
deployment: deploymentId.toString(),
|
||||
devices: JSON.stringify([wellId]),
|
||||
wifis: JSON.stringify([`${ssid}|${password}`]),
|
||||
reuse_existing_devices: '1',
|
||||
});
|
||||
console.log('[API] Calling Legacy API device_form:', {
|
||||
device_id: wellId,
|
||||
deployment: deploymentId,
|
||||
});
|
||||
|
||||
const attachResponse = await fetch(this.legacyApiUrl, {
|
||||
@ -1850,19 +1872,26 @@ class ApiService {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: formData.toString(),
|
||||
});
|
||||
console.log('[API] Legacy API response status:', attachResponse.status);
|
||||
|
||||
if (!attachResponse.ok) {
|
||||
throw new Error('Failed to attach device');
|
||||
const errorText = await attachResponse.text();
|
||||
console.error('[API] Legacy API HTTP error:', errorText);
|
||||
throw new Error(`Failed to attach device: HTTP ${attachResponse.status}`);
|
||||
}
|
||||
|
||||
const data = await attachResponse.json();
|
||||
console.log('[API] Legacy API response data:', JSON.stringify(data).substring(0, 500));
|
||||
|
||||
if (data.status !== '200 OK') {
|
||||
throw new Error(data.message || 'Failed to attach device');
|
||||
console.error('[API] Legacy API error:', data.status, data.message);
|
||||
throw new Error(data.message || `Legacy API error: ${data.status}`);
|
||||
}
|
||||
|
||||
console.log('[API] Device attached successfully');
|
||||
return { ok: true };
|
||||
} catch (error: any) {
|
||||
console.error('[API] attachDeviceToBeneficiary error:', error.message);
|
||||
return { ok: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,6 +414,29 @@ export class RealBLEManager implements IBLEManager {
|
||||
throw new Error(`Device unlock failed: ${unlockResponse}`);
|
||||
}
|
||||
|
||||
// Step 1.5: Check if already connected to the target WiFi
|
||||
// This prevents "W|fail" when sensor uses old saved credentials
|
||||
console.log('[BLE] === BLE FIX v2 ACTIVE (2026-01-27) ===');
|
||||
console.log('[BLE] Step 1.5: Checking current WiFi status...');
|
||||
try {
|
||||
const statusResponse = await this.sendCommand(deviceId, BLE_COMMANDS.GET_WIFI_STATUS);
|
||||
console.log('[BLE] Current WiFi status:', statusResponse);
|
||||
|
||||
// Parse: "mac,XXXXXX|a|SSID,RSSI" or "mac,XXXXXX|a|,0" (not connected)
|
||||
const parts = statusResponse.split('|');
|
||||
if (parts.length >= 3) {
|
||||
const [currentSsid] = parts[2].split(',');
|
||||
if (currentSsid && currentSsid.trim().toLowerCase() === ssid.toLowerCase()) {
|
||||
console.log('[BLE] Sensor is ALREADY connected to target WiFi:', ssid);
|
||||
console.log('[BLE] Skipping W| command - returning success');
|
||||
return true;
|
||||
}
|
||||
console.log('[BLE] Sensor connected to different network or not connected:', currentSsid || 'none');
|
||||
}
|
||||
} catch (statusError) {
|
||||
console.warn('[BLE] Failed to check WiFi status, continuing with config:', statusError);
|
||||
}
|
||||
|
||||
// Step 2: Set WiFi credentials
|
||||
const command = `${BLE_COMMANDS.SET_WIFI}|${ssid},${password}`;
|
||||
console.log('[BLE] Step 2: Sending WiFi credentials...');
|
||||
@ -426,8 +449,28 @@ export class RealBLEManager implements IBLEManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
// WiFi config failed - throw detailed error
|
||||
// WiFi config failed - check if sensor is still connected (using old credentials)
|
||||
if (setResponse.includes('|W|fail')) {
|
||||
console.log('[BLE] W|fail received. Checking if sensor still connected to WiFi...');
|
||||
|
||||
try {
|
||||
const recheckResponse = await this.sendCommand(deviceId, BLE_COMMANDS.GET_WIFI_STATUS);
|
||||
const parts = recheckResponse.split('|');
|
||||
if (parts.length >= 3) {
|
||||
const [currentSsid, rssiStr] = parts[2].split(',');
|
||||
const rssi = parseInt(rssiStr, 10);
|
||||
|
||||
// If connected to target SSID (using old credentials), consider it success
|
||||
if (currentSsid && currentSsid.trim().toLowerCase() === ssid.toLowerCase() && rssi < 0) {
|
||||
console.log('[BLE] Sensor IS connected to target WiFi (using saved credentials):', currentSsid, 'RSSI:', rssi);
|
||||
console.log('[BLE] Password may be wrong but sensor works - returning success');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (recheckError) {
|
||||
console.warn('[BLE] Failed to recheck WiFi status after W|fail');
|
||||
}
|
||||
|
||||
throw new Error('WiFi credentials rejected by sensor. Check password.');
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user