React Native BLE Manager (Android) status code 14 on write to characteristic - android

I'm using the react native ble manager package to build a react native app that communicates with a python client over BLE.
When writing to a characteristic on Android (this bug does not seem to appear on IOS) the write is successful but shortly after it I receive this error:
ERROR Error writing eeee2a38-0000-1000-8000-00805f9b34fb status=14
This is the simplified code that handles connecting, notifications and writing on the Android side:
import { NativeModules, NativeEventEmitter, Platform } from 'react-native'
import BleManager, { Peripheral } from 'react-native-ble-manager'
import { END } from 'redux-saga'
import { bytesToString } from 'convert-string'
const UPDATE_SERVICE_UUID = '0000180d-aaaa-1000-8000-00805f9b34fb'
export const Characteristic =
{
WIFI_STATUS_UUID: 'bbbb2a38-0000-1000-8000-00805f9b34fb',
WIFI_CREDS_UUID: 'aaaa2a38-0000-1000-8000-00805f9b34fb',
VERSION_UUID: 'cccc2a38-0000-1000-8000-00805f9b34fb',
UPDATE_STATUS_UUID: 'dddd2a38-0000-1000-8000-00805f9b34fb',
DO_UPDATE_UUID: 'eeee2a38-0000-1000-8000-00805f9b34fb',
ERROR_UUID: 'ffff2a38-0000-1000-8000-00805f9b34fb',
}
class BLEManager {
bleManagerModule: any
bleManagerEmitter: any
scanning: boolean
dispatch: any
stopScanListener: any
peripheralDiscoverListener: any
characteristicUpdateListener: any
onDisconnectListener: any
connectTimeout: any
constructor() {
BleManager.start({ showAlert: false })
this.bleManagerModule = NativeModules.BleManager
this.bleManagerEmitter = new NativeEventEmitter(this.bleManagerModule)
this.scanning = false
}
startScan = (onPeripheralFound: (peripheral: Peripheral | null) => void) => {
if (!this.scanning) {
BleManager.scan([], 3, true)
.then(() => {
console.log('Scanning...')
this.scanning = true
this.peripheralDiscoverListener = this.bleManagerEmitter.addListener(
'BleManagerDiscoverPeripheral',
onPeripheralFound,
)
this.stopScanListener = this.bleManagerEmitter.addListener(
'BleManagerStopScan',
() => {
onPeripheralFound(END)
},
)
return
})
.catch(err => {
console.error(err)
})
} else {
console.log('already scanning')
}
return () => {
console.log('stopped scanning')
this.peripheralDiscoverListener.remove()
this.stopScanListener.remove()
}
}
getBondedDevices = (onGetBondedPeripherals: any) => {
BleManager.getBondedPeripherals().then(bondedPeripheralsArray => {
onGetBondedPeripherals(bondedPeripheralsArray)
// TODO: is the END message here necessary?
onGetBondedPeripherals(END)
return
})
return () => {}
}
connectToPeripheral = async (peripheralID: string) => {
try {
await new Promise(async (resolve, reject) => {
this.connectTimeout = setTimeout(reject, 3000)
console.log('connecting to ' + peripheralID)
try {
await BleManager.connect(peripheralID)
await BleManager.retrieveServices(peripheralID)
} catch (error) {
reject()
}
if (this.connectTimeout) {
clearTimeout(this.connectTimeout)
this.connectTimeout = null
this.onDisconnectListener = this.bleManagerEmitter.addListener(
'BleManagerDisconnectPeripheral',
this.onDisconnectPeripheral,
)
resolve()
}
})
} catch (err) {
clearTimeout(this.connectTimeout)
this.connectTimeout = null
console.error('Could not connect to device.')
throw new Error(err)
}
return
}
watchForCharacteristicsUpdates = async (
updateCharValue: (arg0: { payload: any }) => void,
peripheralID: string,
) => {
try {
await BleManager.startNotification(
peripheralID,
UPDATE_SERVICE_UUID,
Characteristic.ERROR_UUID,
)
await BleManager.startNotification(
peripheralID,
UPDATE_SERVICE_UUID,
Characteristic.VERSION_UUID,
)
await BleManager.startNotification(
peripheralID,
UPDATE_SERVICE_UUID,
Characteristic.UPDATE_STATUS_UUID,
)
} catch (e) {
updateCharValue(new Error(e))
console.error(e)
}
console.log('watch for notifications')
this.characteristicUpdateListener = this.bleManagerEmitter.addListener(
'BleManagerDidUpdateValueForCharacteristic',
({ value, characteristic }) => {
// Convert bytes array to string
const data = bytesToString(value)
console.log(
`Received ${data} (${value}) for characteristic ${characteristic}`,
)
updateCharValue({
payload: {
characteristic: characteristic,
data: data,
},
})
},
)
}
disconnectFromPeripheral = async (peripheralID: string) => {
await BleManager.disconnect(peripheralID)
this.characteristicUpdateListener.remove()
}
onDisconnectPeripheral = (peripheralID: string) => {
console.log(peripheralID + ' disconnected')
this.onDisconnectListener.remove()
}
checkIfConnected = async (peripheralID: string) => {
return await BleManager.isPeripheralConnected(peripheralID, [])
}
triggerUpdateCheck = async (peripheralID: string) => {
return await BleManager.write(
peripheralID,
UPDATE_SERVICE_UUID,
Characteristic.WIFI_STATUS_UUID,
[1],
)
}
runUpdate = async (peripheralID: string) => {
return await BleManager.write(
peripheralID,
UPDATE_SERVICE_UUID,
Characteristic.DO_UPDATE_UUID,
[1],
)
}
}
const bleManager = new BLEManager()
export default bleManager
I've researched this a bit and it seems that some people have the problem but I could not find an explanation or solution to it.
I'm even unsure where to start debugging. Any suggestions are welcome.
Details:
Device: [Pixel 6]
OS: [Android 12]
react-native-ble-manager version: ^8.4.1
react-native version: 0.67.4
Note: I've also asked this question on Github: https://github.com/innoveit/react-native-ble-manager/issues/887

The problem (as mentioned by Martijn) was the bug in Bluez which is fixed in 5.65. Simply upgrading and clearing the Bluetooth cache fixed it.

Related

How can a regular React application save video to Android Galery?

I figured out it is not possible to share video content onto Instagram directly on Android in behalf of the user. This was my original intention.
Seems I can not use navigator.share call which works on iOS but not on Android, or only for text and url, but not for video / mp4.
I would not use MediaStore and similar React Nativ solutions as my app is a regular React.
What elso approach do you know?
I have now this:
const showShareDialog = async () => {
try {
log(
{
text: 'setIsDownloadFileInProgress(true) ' + getTempUserShortId(),
},
env
)
setIsDownloadFileInProgress(true)
const res = await axios.get(
`https://t44-post-cover.s3.eu-central-1.amazonaws.com/${imgId}.mp4`,
{ withCredentials: false, responseType: 'blob' }
)
setIsDownloadFileInProgress(false)
const shareData = {
files: [
new File([res.data], `${imgId}.mp4`, {
type: 'video/mp4',
}),
],
title: 'Megosztom',
text: 'Mentsd le a képekbe, és oszd meg storyban.',
}
//showShareDialog(shareData, 0)
if (navigator.share && !isDesktop) {
log(
{
text:
`navigator.share && !isDesktop shareData.files[0].size: ${shareData.files[0].size} ` +
getTempUserShortId(),
},
env
)
if (isAndroid()) {
const { status } = await MediaLibrary.requestPermissionsAsync()
if (status === 'granted') {
const asset = await MediaLibrary.createAssetAsync(res.data)
await MediaLibrary.createAlbumAsync(`${imgId}.mp4`, asset, false)
}
} else {
navigator
.share(shareData)
.then(() => {
log(
{
text: `'The share was successful 1 ` + getTempUserShortId(),
},
env
)
})
.catch((error) => {
log(
{
text:
`There was an error sharing 1 ${error} ` +
getTempUserShortId(),
},
env
)
})
}
} else {
log(
{
text: 'else ' + getTempUserShortId(),
},
env
)
setIsDesktopDownloadDialogVisible(true)
}
} catch (error) {
console.error(error)
log(
{
text: `a onclick error 2: ${error} ` + getTempUserShortId(),
},
env
)
}
}

Open PDF file in expo after download

I’m downloading a file, and I’ve expected open the file after finished downloading, but this does not work, it’s viewed on a black screen when opening using Linking, and I open this file in the folder where it is and it opens correctly. How can I fix this?
This is the URL of the file in STORAGE:
content://com.android.externalstorage.documents/tree/primary%3ADownload%2FSigaa/document/primary%3ADownload%2FSigaa%2Fhistory.pdf
My code:
const downloadPath = FileSystem.documentDirectory! + (Platform.OS == "android" ? "" : "");
const ensureDirAsync: any = async (dir: any, intermediates = true) => {
const props = await FileSystem.getInfoAsync(dir);
if (props.exists && props.isDirectory) {
return props;
}
let _ = await FileSystem.makeDirectoryAsync(dir, { intermediates });
return await ensureDirAsync(dir, intermediates);
};
const downloadFile = async (fileUrl) => {
if (Platform.OS == "android") {
const dir = ensureDirAsync(downloadPath);
}
let fileName = "history";
const downloadResumable = FileSystem.createDownloadResumable(
fileUrl,
downloadPath + fileName,
{}
);
console.log(downloadPath);
try {
const { uri } = await downloadResumable.downloadAsync();
saveAndroidFile(uri, fileName);
} catch (e) {
console.error("download error:", e);
}
};
const saveAndroidFile = async (fileUri, fileName = "File") => {
try {
const fileString = await FileSystem.readAsStringAsync(fileUri, { encoding: FileSystem.EncodingType.Base64 });
if (local === "") {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
return;
}
await AsyncStorage.setItem("local", permissions.directoryUri);
}
try {
await StorageAccessFramework.createFileAsync(local, fileName, "application/pdf")
.then(async (uri) => {
await FileSystem.writeAsStringAsync(uri, fileString, { encoding: FileSystem.EncodingType.Base64 });
Linking.openURL(uri);
alert("Success!");
})
.catch((e) => {});
} catch (e) {
throw new Error(e);
}
} catch (err) {}
};
The black screen when opening:
I solved this by implementing the intent launcher api for android and the sharing api for ios.
To make it work it is important to provide the mimeType of the file (or UTI for IOS). You can extract it manually from the file extension but there's a library for that on RN: react-native-mime-types.
Actually I think any library that doesn't deppend on node core apis shoud work just fine.
Here's the code:
const openFile = async (fileUri: string, type: string) => {
try {
if (Platform.OS === 'android') {
await startActivityAsync('android.intent.action.VIEW', {
data: fileUri,
flags: 1,
type,
});
} else {
await shareAsync(fileUri, {
UTI: type,
mimeType: type,
});
}
} catch (error) {
// do something with error
}
};

TypeError: _this2.setState is not a function, Cant bind state

I am getting this this error. As I am already using ES6 format, That arrow but also getting same error. And somewhat confuse that how to use bind. How can I get out of this error.
code:
async fetchData() {
const { navigate } = this.props.navigation;
var DEMO_TOKEN = await AsyncStorage.getItem(STORAGE_KEY);
NetInfo.isConnected.fetch().then((isConnected) => {
if ( isConnected )
{
return fetch(`${url}`,
{
method: "GET",
headers: {
'Authorization': `JWT ${DEMO_TOKEN}`,
}
})
.then(
function(response) {
console.log(response.headers);
console.log(response.status);
console.log(response.url);
if (response.status !== 200) {
console.log('Status Code: ' + response.status);
return;
}
response.json().then((responseData) => {
console.log(responseData);
this.setState({
ver: responseData.results.appversion, // getting error here
});
});
}
)
.catch(function(err) {
console.log('Fetch Error', err);
});
Just to be clear about which this you're trying to reference, could you do this?
async fetchData() {
const ctx = this
And then reference ctx instead of this:
ctx.setState({
ver: responseData.results.appversion,
});

Firebase Functions response return null

Firebase Function Code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
const db = admin.firestore();
const gameRef = db.collection('Game');
function newRoom(uid) {
gameRef.add({
users: [
uid
],
playing: false,
moves: [],
win: ""
}).then(ref => {
return {
"game": ref.id
}
}).catch(err => {
console.log(err.message)
})
}
function joinRoom(uid, id, data) {
data.users.push(uid);
data.playing = true;
gameRef.doc(id).update(data)
.then(ref => {
return {
"game": id
}
}).catch(err => {
console.log(err.message);
})
;
}
exports.helloWorlds = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
const query = gameRef.where('playing', '==', false).get()
.then(snapshot => {
if (snapshot.docs.length === 0) {
return newRoom(uid)
} else {
return joinRoom(uid, snapshot.docs[0].id, snapshot.docs[0].data())
}
}).catch(err => {
console.log(err.message)
});
});
Android Code
fun requestGame(text:String): Task<HashMap<*, *>> {
// Create the arguments to the callable function.
val data = hashMapOf("text" to text, "push" to true)
return mFunctions
.getHttpsCallable("helloWorlds")
.call(data)
.continueWith {
val result = it.result.data as HashMap<*, *>
result
}
function code works fine. When I make a request on the android device, it returns null. İt write the datas to the database smoothly. Another problem is that sometimes the function does not work when it is not running for a certain period of time. I think the problem is JavaScript, but I did not solve the problem
Right now you're not returning anything from helloWorlds itself, which means that Cloud Functions can't know when it's done. You'll want to return query at the end of helloWorlds:
exports.helloWorlds = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
const query = gameRef.where('playing', '==', false).get()
.then(snapshot => {
if (snapshot.docs.length === 0) {
return newRoom(uid)
} else {
return joinRoom(uid, snapshot.docs[0].id, snapshot.docs[0].data())
}
}).catch(err => {
console.log(err.message)
});
return query;
});
return gameRef.where(...
exports.helloWorlds = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
return gameRef.where('playing', '==', false).get()
.then(snapshot => {
if (snapshot.docs.length === 0) {
return newRoom(uid)
} else {
return joinRoom(uid, snapshot.docs[0].id, snapshot.docs[0].data())
}
}).catch(err => {
console.log(err.message)
});
});

React Native Redux: props not updating after API call

I am quite new to React / React Native / Redux so I feel I am doing something wrong.
The problem
I want to show a spinner while an API is called, and an error message once this API call fails. Props are not updating, and so the components don't show the desired message or spinner
The code (only the relevant chunks)
The component
class Home extends Component {
componentWillMount() {
this.props.tokenGet();
}
renderSpinner() {
if (this.props.loading) {
return (
<Spinner size="large" />
);
}
return null;
}
renderMessage() {
if (this.props.message) {
return (
<Text style={{flex: 1, background: red, color: black}}>
{ this.props.message }
</Text>
)
}
return null;
}
render() {
return (
{ this.renderSpinner() }
{ this.renderMessage() }
)
}
}
const mapStateToProps = (state) => {
const { auth } = state;
const {
loading,
token,
message
} = auth || {
loading: false,
token: null,
message: null
};
return {
loading,
token,
message
}
};
export default connect(mapStateToProps, { tokenGet } )(Home);
The action creator
export const tokenGet = () => {
return (dispatch) => {
dispatch({ type: 'TOKEN_GET_START'});
// Perform the actual API call
let requestToken = fetch(apiBaseUrl + "/tokens", {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(.....)
});
Promise
.race([timeout, requestToken])
.then((response) => response.json())
.then((responseJson) => {
... not relevant ...
})
.catch((error) => {
dispatch({ type: 'TOKEN_GET_FAIL', payload: error});
});
The timeout function, which gets called when the server fails to respond
let timeout = new Promise((resolve, reject) => {
setTimeout(reject, 2000, 'Request timed out. Please check your internet connection.');
});
The reducer
import {
TOKEN_GET_START,
TOKEN_GET_SUCCESS,
TOKEN_GET_FAIL
} from '../actions/types';
const INITIAL_STATE = {
loading: false,
token: null,
message: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case TOKEN_GET_START:
return { ...state, loading: true };
case TOKEN_GET_SUCCESS:
return { ...state, loading: false, token: action.payload };
case TOKEN_GET_FAIL:
return { ...state, loading: false, message: action.payload };
default:
return state;
}
};
The combined reducers
import { combineReducers } from 'redux';
import AuthReducer from './AuthReducer';
export default combineReducers({
auth: AuthReducer
});
The actual behavior is that the props don't change and no message or spinner is visible. With some console logs I know that the API call ends because of the timeout. I am not sure if the state gets updated properly though. I don't know in at which point I can console log this.
It turned out to be because of the quotes in 'TOKEN_GET_FAIL'
That is a string and not the const I need. So I changed to TOKEN_GET_FAIL and it works.

Categories

Resources