needing a guide for how to layout functionality for a React Native app that's pairing with an ESP32 that will eventually feed back weight readings using read Characteristic, and be able to toggle a DI via write to a characteristic.
i can currently scan and connect to the ESP32 and show the values from the ESP32 (random changing values for now) and also toggle the LED via changing a hardcoded value. But i want to be able to do this via a button in the app.
const scanDevices = () => {
//set isLoading to true to show activity Indicator
setIsLoading(true);
//scan for devices, (UUIDs, ScanOptions(error, device))
manager.startDeviceScan(null, null, (error, device) => {
if (error) {
console.log("Error in scanning", error.message)
return;
}
if (device) {
//if a device is scanned, add the name & id details into the scannedDevice object via reducer
dispatch({type: 'DEVICE_ADD', payload: {name: device.name, id: device.id}});
}
});
//end scan after 3 seconds, stop the activity indicator swirly thing
setTimeout(() => {
console.log("Scan timeout after 5 seconds");
manager.stopDeviceScan();
setIsLoading(false);
}, 5000);
};
const deviceConnect = (device) => {
console.log("Connecting to:", device.name, device.id);
setIsConnected(true);
setConnectedDevice(device);
manager.connectToDevice(device.id)
.then((device) => {
console.log("Discovering all services & chars");
return device.discoverAllServicesAndCharacteristics()
}).then((device) => {
// console.log("Write Value inside deviceConnect:", writeValue)
console.log("Device:", device.name, "has been connected.");
return deviceNotifications(device, writeValue);
}).catch((error) => {
console.log("device connect error:", device.name, error)
//JSON.stringify(error)
});
};
const deviceNotifications = async (device, writeValue) => {
const service = "af493e2a-f002-11eb-9a03-0242ac130003";
const characteristicTX = "af49423a-f002-11eb-9a03-0242ac130003";
const characteristicRX = "af49414a-f002-11eb-9a03-0242ac130003";
if (device) {
try {
device.monitorCharacteristicForService(service, characteristicTX, (error, characteristic) => {
if (error) {
console.log(error);
} else {
setCharacteristicValue(() => {
return [{id: uuid.v4(), value: (base64.decode(characteristic.value))}];
})}
});
device.writeCharacteristicWithResponseForService(service, characteristicRX, base64.encode(writeValue));
console.log("Writing to RX:", writeValue);
}
catch (err) {
console.log("deviceNotification catch error:", err);
}
};
}
I'm getting pretty confused trying to sort through the [ble-plx documentation][1] ([github wiki][2])
Currently the only way i can get the LED to turn on/off, is i have the LED toggle section inside the deviceNotifications async function and have to manually change the value that's being encoded and written in the code itself, rather than from the App UI using an useState value.
I tried using the useState toggle off a button (which toggled the value and logged out OK), and then re-calling the deviceConnect function, but the commented out console.log in the .then promise section didn't work past the first one, returning which turned the LED on (writing 'A' to the characteristic).
thanks for any help in advance, i know a lot of these ble-plx questions go unanswered.
//this is at a top level inside the main function
const [writeValue, setWriteValue] = useState('A');
const toggleLED = () => {
if (writeValue == 'B') {
setWriteValue('A');
console.log("Toggling write value:", writeValue);
} else {
setWriteValue('B')
console.log("Toggling write value", writeValue)
};
};
[1]: https://dotintent.github.io/react-native-ble-plx/
[2]: https://github.com/dotintent/react-native-ble-plx/wiki
[3]: https://www.polidea.com/blog/ReactNative_and_Bluetooth_to_An_Other_level/
Related
For a research study, I am trying to record user's interactions with a web interface. I setup a back end with Strapi to store that data. Connection and setting up of a new participant ID is not a problem, but when I try to update the data I get an interesting behavior that is unfamiliar to me.
Basically, it seems that I am setting the participants ID but when accessing it, my logs show that I have a duplicate with no data stored. Below the logs I am getting while debugging:
//Initialising study. connectStrapi.js?t=1668867760343:11
//No participant ID existing. Creating.. connectStrapi.js?t=1668867760343:15
//Confirming, Participant ID: 121 interactionLogger.js?t=1668867760343:199
//On Event: participant id is: 121. interactionLogger.js?t=1668867760343:81
//On Event: participant id is: undefined interactionLogger.js:81
The connectStrapi.js sets up the study and participant data. When no id is present at the start, I am creating new data. The interactionLogger.js is responsible to log the data and update Strapi when something happens.
I am not completely new to JavaScript, but I go to admit, that I have no idea what the different identifiers mean. I am only updating once, but I the same line twice - once for interactionLogger.js?t=1668867760343:81 and a second time for interactionLogger.js:81. I am not exactly sure, what the .js?t=1668867760343 means and how I can ensure passing the right value at the right time.
Currently, I when I am updating the data, the undefined value is being passed resulting in no data being updated. I am thinking, I am missing something basic here but can't figure it out.. In case it matters, I have implemented the JavaScript files as type=modules and import functions accordingly.
InitialiseStudy function from connectStrapi.js:
import * as interactionLogger from './interactionLogger.js/'
//...
export const initialiseStudy = async (participantData, id) => {
console.log("Initialising study.")
if(id === undefined) {
await axios.post(apiUrl, participantData)
.then( response =>{
console.log('No participant ID existing. Creating..')
//setupParticipant(response.data.data);
interactionLogger.setParticipantID(response.data.data.id)
})
.catch( error => {
if (error.response){
console.log('error:', error.response)
}
else if (error.request){
console.log('error:', error.request)
}
else if (error.message){
console.log('error:', error.message)
}
else {
console.log('error:', error)
}
})
} else {
axios.put(apiUrl+ id, participantData)
.then(response => {
console.log('response.data:', response.data)
})
.catch( error => {
console.log('error:', error)
})
}
}
My updateData function from connectStrapi.js:
export const updateData = (data, id) => {
if (id === undefined) {
alert("No Participant ID")
} else {
axios.put(apiUrl + '/' + id, data)
.then( response => {
console.log('response.data:', response.data)
} )
.catch( error => {
console.err('error:', error)
})
}
}
The setter from interactionLogger.js is a normal setter:
let participantID
//...
export const setParticipantID = (id) => {
participantID = id
}
The logData function uses updateData, which is imported before it based on events in interactionLogger.js:
import * as connectStrapi from './connectStrapi.js'
//...
const logData = (data) => {
connectStrapi.updateData(data, participantID) //<- This is a local variable initialized as undefined but updated using the setParticipantID in connectStrapi.js
}
Thanks in advance!
I'm adding the claim to a user's profile that he or she paid for something, though, after the payment this attribute isn't visible. I'm running the functions on an emulator on a local host.
This is the code I'm using:
If the paypal function has been handled succesfully through paypalHandleOrder, then the function addPaidClaim is invoked.
onApprove: (data, actions) => {
paypalHandleOrder({ orderId: data.orderID }).then(
addPaidClaim(currentUser).then(
alert("THANKS FOR ORDERING!"),
// currentUser.getIdTokenResult().then(idTokenResult => {
// console.log(idTokenResult.claims)
// })
)
.catch((err) => {
return err;
})
);}
addPaidClaim is a firebase cloud function, which goes as follows:
exports.addPaidClaim = functions.https.onCall((data, context) => {
// get user and add custom claim (paid)
return admin.auth().setCustomUserClaims(data.uid, {
paid: true,
}).then(() => {
return {
message: `Success! ${data.email} has paid the course`,
};
}).catch((err) => {
return err;
});
});
I've refreshed the page and checked the user attributes afterwards through console.log on the user to see if the attribute had been added, but this is not the case. I can't find attribute paid inside the idTokenResult object. What should I do? I also find it hard to make sense of what's happening inside the function addPaidClaim. It's not returning an error when I look at the logs on my firebase console, and not much information is given, besides that the function has been invoked.
Okay, I know this question is pretty old. But I found a way just yesterday after 3 days searching over the solution. After we set up a new claim for a new user using, we need to refresh the client's getIdTokenResult(true) in the app. These are the ways I did it in Flutter Dart until a new user with updated claim managed to use it:
FirebaseAuth auth = FirebaseAuth.instance;
Future<Map<String, dynamic>> signInWithGoogle() async {
Map<String, dynamic> output = {};
final googleUser = await googleSignIn.signIn();
if (googleUser == null) {
log("Firebase => Gmail account doesn't exist");
} else {
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken,
);
await auth.signInWithCredential(credential).then((values) async {
await userAuth(credential).then((value) =>
value.addAll(output));
});
}
return output;
}
Future<Map<String, dynamic> userAuth (OAuthCredential credential) async {
Map<String, dynamic> output = {};
await auth.currentUser!.reauthenticateWithCredential(credential);
await auth.currentUser!.reload();
await auth.currentUser!.getIdTokenResult().then((result) => {
if(result.claims!.isNotEmpty){
//check your claim here
} else {
//assign log here
}
});
return output;
}
I'm working with a MySql database and a web application; I use Firebase Functions (Google Cloud Functions) to retrieve data from tables and send it to the web app. When the record's create_dt and update_dt fields make it to the web application, they're an object that doesn't have any accessible properties.
I'm trying to display the create and update date values in my application, but whenever I try to display one of the values, the web application displays [object Object]. Looking at the object in the console, it looks like an empty object with nothing but prototype properties
I've looked around here and other places on the Internet and found a bunch of articles that show how to manipulate a MySql Timestamp (as a time string) in JavaScript, but none that shows how to actually access the timestamp value.
My goal right now is just to display the time/date value in my app, but ultimately I want to get it as a JavaScript Date object so I can format the output the way I want in my app. Can someone please show me how to do this? I don't get why the timestamp shows up in the browser as an object with no accessible properties.
My function looks like this:
export const get = functions
.runWith({
vpcConnector: 'myapp-connector',
vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY'
})
.https.onCall((data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(AUTHCODE, AUTHMSG);
}
const idx = data.idx;
if (idx) {
let cmd = `SELECT * FROM companies WHERE id=${idx}`;
return sqlStuff.executeQuery(cmd)
.then(result => {
functions.logger.log('Query result', result);
return { result: result };
}).catch(err => {
functions.logger.log('ERROR', err);
return { err: err };
});
} else {
functions.logger.log('Missing index');
return { result: {} };
}
});
The query code looks like this:
export async function executeQuery(cmd: string) {
const mySQLConfig = {
connectionLimit: 10,
host: functions.config().sql.prodhost,
user: functions.config().sql.produser,
password: functions.config().sql.prodpswd,
database: functions.config().sql.proddatabase,
}
var pool: any;
if (!pool) {
pool = mysql.createPool(mySQLConfig);
}
return new Promise(function (resolve, reject) {
pool.query(cmd, function (error, results) {
if (error) {
return reject(error);
}
resolve(results);
});
});
}
On the client, I'm using AngularFireFunctions since this is an Angular (Ionic) app.
getCompany(idx: number) {
const companyGet = this.fireFunc.httpsCallable('companyGet');
companyGet({ idx }).subscribe(
data => {
if (data.result && data.result.length > 0) {
this.company = Object.assign({}, data.result[0]);
} else {
this.alertController.create({
header: 'Company Lookup',
message: `The specified company record (${idx}) does not exist`,
buttons: ['OK']
}).then(alert => {
alert.present();
this.router.navigate(['/companies']);
});
}
},
err => {
this.alertController.create({
header: 'Company Refresh',
message: `The process reported the following error: ${err.message}`,
buttons: ['OK']
}).then(alert => alert.present());
},
() => {
console.log('CompanyPage: Company request completed');
}
);
});
}
I am trying to load a notification token (notificationToken) that I've stored within Firebase to a React Native component.
Once the notificationToken is loaded to my redux state, I want to check for my device permissions to see if the notificationToken has expired within the function getExistingPermission() that I run in the componentDidMount().
If the token has expired, then I'll replace the token within Firebase with the new token. If it's the same, then nothing happens (which is intended functionality).
When I'm running my function getExistingPermission() to check if the token is up-to-date the Firebase listener that pulls the notificationToken does not load in time, and so it's always doing a write to the Firebase database with a 'new' token.
I'm pretty sure using async/await would solve for this, but have not been able to get it to work. Any idea how I can ensure that the notificationToken loads from firebase to my redux state first before I run any functions within my componentDidMount() function? Code below - thank you!
src/screens/Dashboard.js
Should I use a .then() or async/await operator to ensure the notificationToken loads prior to running it through the getExistingPermission() function?
import {
getExistingPermission
} from '../components/Notifications/NotificationFunctions';
componentDidMount = async () => {
// Listener that loads the user, reminders, contacts, and notification data
this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
try {
this.props.watchUserData();
} catch (e) {
this.setState({ error: e, });
}
});
if (
!getExistingPermission(
this.props.notificationToken, //this doesn't load in time
this.props.user.uid)
) {
this.setState({ showNotificationsModal: true });
}
};
src/components/Notifications/NotificationFunctions.js
The problem is probably not here
export const getExistingPermission = async (
notificationToken,
uid,
) => {
const { status: existingStatus } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
if (existingStatus !== 'granted') {
console.log('status not granted');
return false;
} else {
let token = await Notifications.getExpoPushTokenAsync();
/* compare to the firebase token; if it's the same, do nothing,
if it's different, replace */
if (token === notificationToken) {
console.log('existing token loaded');
return true;
} else {
console.log('token: ' + token);
console.log('notificationToken: ' + notificationToken);
console.log('token is not loading, re-writing token to firebase');
writeNotificationToken(uid, token);
return false;
}
}
};
src/actions/actions.js
// Permissions stuff
watchPermissions = (uid) => (
(dispatch) => {
getPermissions(uid + '/notificationToken', (snapshot) => {
try {
dispatch(loadNotificationToken(Object.values([snapshot.val()])[0]));
}
catch (error) {
dispatch(loadNotificationToken(''));
// I could call a modal here so this can be raised at any point of the flow
}
});
}
);
// User Stuff
export const watchUserData = () => (
(dispatch) => {
currentUserListener((user) => {
if (user !== null) {
console.log('from action creator: ' + user.displayName);
dispatch(loadUser(user));
dispatch(watchReminderData(user.uid)); //listener to pull reminder data
dispatch(watchContactData(user.uid)); //listener to pull contact data
dispatch(watchPermissions(user.uid)); //listener to pull notificationToken
} else {
console.log('from action creator: ' + user);
dispatch(removeUser(user));
dispatch(logOutUser(false));
dispatch(NavigationActions.navigate({ routeName: 'Login' }));
}
});
}
);
export const loadNotificationToken = (notificationToken) => (
{
type: 'LOAD_NOTIFICATION_TOKEN',
notificationToken,
}
);
Tony gave me the answer. Needed to move the permissions check to componentDidUpdate(). For those having a similar issue, the component looks like this:
src/screens/Dashboard.js
componentDidUpdate = (prevProps) => {
if (!prevProps.notificationToken && this.props.notificationToken) {
if (!getExistingPermission(
this.props.notificationToken,
this.props.user.uid
)) {
this.setState({ showNotificationsModal: true });
}
}
};
Take a look at redux subscribers for this: https://redux.js.org/api-reference/store#subscribe . I implement a subscriber to manage a small state machine like STATE1_DO_THIS, STATE2_THEN_DO_THAT and store that state in redux and use it to render your component. Only the subscriber should change those states. That gives you a nice way to handle tricky flows where you want to wait on action1 finishing before doing action2. Does this help?
In order to realize a project of connected objects. I need to implement a Bluetooth connection between the various devices.
Here, the goal is to create an application in React Native and then send data from this application to my Raspberry. This Raspberry has a connected HC-08 module that takes care of Bluetooth communication.
Now, I would like to use react-native-ble-plx library to send data through Bluetooth. I'm able to connect my Android to the module. But I don't understand how to send data ...
Here's my code :
constructor() {
super()
this.manager = new BleManager()
}
componentWillMount() {
console.log("mounted")
const subscription = this.manager.onStateChange((state) => {
if (state === 'PoweredOn') {
this.scanAndConnect();
subscription.remove();
}
}, true);
}
scanAndConnect() {
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
// Handle error (scanning will be stopped automatically)
return
}
console.log(device.name)
// Check if it is a device you are looking for based on advertisement data
// or other criteria.
if (device.name === 'SH-HC-08') {
// Stop scanning as it's not necessary if you are scanning for one device.
this.manager.stopDeviceScan();
console.log(`Found ${device.name}`)
this.setState({
device: device
})
// Proceed with connection.
device.connect()
.then((device) => {
console.log(device)
return device.discoverAllServicesAndCharacteristics()
})
.then((device) => {
console.log(device)
})
.then((result) => {
// Do work on device with services and characteristics
//console.log(this.manager.characteristicsForService("00001800-0000-1000-8000-00805f9b34fb"))
console.log(result)
console.log("connected")
})
.catch((error) => {
// Handle errors
console.log(error)
});
}
});
}
send() {
this.manager.writeCharacteristicWithResponseForDevice("58:7A:62:4F:EF:6D",
this.device.serviceUUIDs[0],
this.manager.characteristicsForDevice(this.device.id),
"ok")
.catch((error) => {
console.log('error in writing data');
console.log(error);
})
}
I would like to have a send method that will send data whenever I want to. But I don't really understand how it works :/
Could someone help me or even give me an example ? I would be really appreciated.
Best regards.
I had success implementing the following:
scanAndConnect() {
this.manager.startDeviceScan(null, null, (error, device) => {
this.info("Scanning...");
console.log(device);
if (error) {
this.error(error.message);
return
}
if (device.name ==='MyDevice') {
this.info("Connecting to Tappy");
this.manager.stopDeviceScan();
device.connect()
.then((device) => {
this.info("Discovering services and characteristics");
return device.discoverAllServicesAndCharacteristics()
})
.then((device) => {
this.info(device.id);
device.writeCharacteristicWithResponseForService('12ab', '34cd', 'aGVsbG8gbWlzcyB0YXBweQ==')
.then((characteristic) => {
this.info(characteristic.value);
return
})
})
.catch((error) => {
this.error(error.message)
})
}
});
Where I use 12ab, insert the UUID of your BLE service. Similarly, where I use 34cd, insert the UUID of your BLE characteristic. Lastly, include a base64 encoding of whatever message you're trying to send where I have aGVsbG8gbWlzcyB0YXBweQ==.
Hope this helps.