I have an ESP32 Battery monitor system (BMS) whose status I want to view on an iPhone, there are about 100 values to be monitored with a maximum update rate of once per second. I also needs to be able to send settings to the BMS occasionally.
Blynk seemed to be the perfect app to do this but the new version 2.0 doesn't support BLE! Does anyone know a similar app that could do this?
Have you tried setting the ESP32 up as a web server and creating an API based on the values? Using this instead of bluetooth allows you to view the values from anywhere with internet connection, and is easier to implement and use across different platforms
You can do this by implementing code into the ESP32, so that when you make http requests, it returns with a value, just like any other API would. Then in Xcode where you are building your IOS app you can make http requests to the web server and retrieve values
for example, in the ESP32 you can do something like this (assuming you are using the Arduino IDE to program it):
server.on("/value", [](AsyncWebServerRequest * request))
{
String value = "value"
request->send(200,"text/plain", value)
}
This YouTube video shows you this in more detail: https://www.youtube.com/watch?v=cWZP7Y8qP6E
Then, in Xcode you can implement something along the lines of:
func loadData() async {
guard let url = URL(string: "http://IP-address-of-esp32/path-of-value", ) else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
// process data here with the 'data' variable
} catch {
print("Invalid Data")
}
}
//Be aware that this is asynchronous when calling it
This person also explains it in more detail in a YouTube video: https://www.youtube.com/watch?v=MBCX1atOvdA
Now, since you have a web server and API set up on your ESP32, and a method of retrieving the values in your IOS app, you can go on and use the values to build the app to your liking.
Good Luck!
Related
I am attempting to use navigator.share in a file running in WKWebView on iOS 16. The file is loaded into the web view using a file: protocol path like so:
uiView.loadFileURL(Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "web")!, allowingReadAccessTo: Bundle.main.bundleURL)
I have this setting on:
configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
So I am able to load file: protocol files into the web view, but I don't seem to be able to use the Web Share API. I get this error:
NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
However, when I point the web view at this HTTPS URL, it works:
https://mdn.github.io/dom-examples/web-share
This leads me to believe the Web Share API is not working because the HTML file loaded over the file: protocol is not viewed as secure and thus the JavaScript runtime don't treat the code on it as if it was running in a secure context.
As a result, I believe the access to the navigator.share API is forbidden.
Is there a way to configure WKWebView to allow access to secure context JS/DOM/web APIs without using a custom scheme (and adding an extra layer atop just loading my file from the bundle)?
The problem was not with the file: protocol or secure context.
I looked through WebKit source code for navigator.share and found that NotAllowedError gets returned whenever there don't seem to be conditions met for transient activation.
https://github.com/WebKit/WebKit/blob/d9bcb08521ddb86af9a713213d15488712919143/Source/WebCore/page/Navigator.cpp#L172
void Navigator::share(Document& document, const ShareData& data, Ref<DeferredPromise>&& promise)
{
if (!document.isFullyActive()) {
promise->reject(InvalidStateError);
return;
}
if (!validateWebSharePolicy(document)) {
promise->reject(NotAllowedError, "Third-party iframes are not allowed to call share() unless explicitly allowed via Feature-Policy (web-share)"_s);
return;
}
if (m_hasPendingShare) {
promise->reject(InvalidStateError, "share() is already in progress"_s);
return;
}
auto* window = this->window();
if (!window || !window->consumeTransientActivation()) {
promise->reject(NotAllowedError);
return;
}
if (!canShare(document, data)) {
promise->reject(TypeError);
return;
}
Transient activation is the thing where some web APIs will only run in response to a user gesture; the user gesture is either a part of the call stack leading up to the API call invocation or a user gesture has happened recently.
More about transient activation here:
https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation
In my case I did have a user gesture there but also a WebRTC peer connection ICE gathering flow which while seemingly quick I guess lasted too long and it reset the transient activation flag preventing me from using the Web Share API at the end of the ICE gathering phase.
The fix is to prepare whatever you need for the Web Share API data payload in advance and in response to the user gesture, such as a button click, call the web API straight away.
I have something strange with my parse server, I start to check Analytics toll at the parse, and I saw during some period I have more than600 request per minutes, to the server, in my opinion, is not possible because only I am, testing app.
At image you can see a trend, I have 10-14 requests, and immediately I get 600?
How I can check how many requests are sent my app?
You can use a proxy to catch your request
-> Charles is a really useful tool for this
If you want count in your app how many are sent, it depends on how you organised your code.
-> you could create a counter for this (in your custom "ReqeustMaker" create a static variable or even in in a shared instance class if you need more fancy stuff)
Using the following code you can trap all the calls, and find them all on Xcode debug console:
class MyURLProtocol: NSURLProtocol {
class func canInit(with request: URLRequest) -> Bool {
print("Requests : \(request.url?.absoluteString ?? "")")
return false
}
}
and call this when app is loading:
URLProtocol.registerClass(MyURLProtocol.self)
I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.
Quick background, I am extremely new in this realm. I am aware that this type of question has been asked before and answered successfully. The issue that I am experiencing is caused by the inability to wrap my head around the process of establishing the connection. I have spent hours (into days) searching for the answer and I am still unsuccessful. This has become my "white whale" so to speak.
I am using Xcode 9 with Swift version 4. Many of the answer I come across use Objective-C and I cannot mix and match. So I would like to UNDERSTAND why I am unable to connect and the correct process to connect so I can write the code with the understanding of what I am doing. Lastly, I have signed up (and completed) a few paid Udemy courses to try and learn the process correctly. I have been able to connect to API sources but OAuth 1 is tripping me up. Any constructive help would be incredibly appreciated.
Background:
I am attempting to connect to the Fat Secret database. I would like to connect a search bar to the food.search functionality and also the food.get for another search bar.
Company- FatSecret
URL for API- platform.fatsecret.com/rest/server.api
URL to FatSecret documentation (I have gone through this MANY times)- http:// {space} platform.fatsecret. {space }com/api/Default. {space} aspx?screen=rapiauth
Parameters- Parameters {
oauth_consumer_key - consumer_key (I have a consumer key)
oauth_signature_method - "HMAC-SHA1"
oauth_timestamp - The date and time, expressed in the number of seconds since January 1, 1970 00:00:00 GMT. The timestamp value must be a positive integer and must be equal or greater than the timestamp used in previous requests
oauth_nonce - A randomly generated string for a request that can be combined with the timestamp to produce a unique value
oauth_version - Must be "1.0"
}
As I previously stated, the answer to my question is displayed above. I understand that part but I do not understand how to incorporate it into my code.
Past code-
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print("success")
} task.resume()
The above code is what I used to establish the connection. I receive "success" in the console so I expanded my parameters.
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print(error)
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
} catch {
} task.resume()
The above code produces nothing in the console. I believe (sorry for my ignorance) that the reason I am not getting a response is because I am not sending any authorization in the request, nor am I am sending in the correct encoding. I imagine that I can create the parameters by var/let statements and then call on those statements but I am not able to see the way to do that. I could likely also store all of my connection information in a different swift file or class and call on that when I need to access data. This base signature is required with every request. I have to imagine that best practice would be setting it up that way but again, I can't visualization the process. It becomes a trial and error process that results in incredible frustration.
Again, any help would be incredibly appreciated. I apologize for the length of this post. Thank you for taking the time to read this post.
This may be late but I have successfully managed to implement the FatSecret REST API and have created a small Xcode project that shows how I handled OAuth. The only calls that can be made are food.search and food.get. https://github.com/NicholasBellucci/FatSecretSwift
I have been working completely fine with google maps sdk and the places api. yesterday my code was running perfectly fine. but woke up this morning, made some modifications then ran into a compiling snowball of issues which seems like an easy fix but have spent a couple hours trying to figure out what is going on. I am trying to develop an iPhone application.
I have gone back, created a "new project" in the developer console
regenerated a new iOS key.
entered the new bundle ID in the info.plist
entered the new api key in the delegate file and within the search parameter of the url.
i am using alamofire and swiftyjson to parse the data
but still get the same issue.
2016-01-28 13:15:55.683 Google Maps SDK for iOS version: 1.11.21919.0
{
"error_message" : "This IP, site or mobile application is not authorized to use this API key. Request received from IP address "myIPAddress", with empty referer",
"results" : [
],
"status" : "REQUEST_DENIED",
"html_attributions" : [
]
}
func downloadRestaurantDetails(completed: DownloadComplete) {
let URL_Search = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
let API_iOSKey = "My iOS API Key"
let urlString = "\(URL_Search)location=\(clLatitude),\(clLongitude)&radius=\(searchRadius)&types=\(searchType)&key=\(API_iOSKey)"
let url = NSURL(string: urlString)!
Alamofire.request(.GET,url).responseJSON { (response) -> Void in
if let value = response.result.value {
let json = JSON(value)
print(json)
if let results = json["results"].array {
for result in results {
if let placeIDs = result["place_id"].string {
self.placeIDArray.append(placeIDs)
}
}
}
}
completed()
}
}
You've misconfigured your API key.
First, your issue is with the Google Places API Web Service, which can only be used with Server keys not iOS keys. You're calling the web service (https://maps.googleapis.com/maps/place/...) so iOS keys and bundleID key restrictions aren't going to work here (and are likely to confuse things).
So you need to follow the instructions at https://developers.google.com/places/web-service/get-api-key, and make a Server Key with "Google Places API Web Service" enabled in the Developers Console (not the same as "Google Places API for iOS"). To be useful on phones you probably don't want to set any IP restrictions (and IP restrictions are the only kind possible for Server keys). Use that key in your HTTP requests.
Anything else will get REQUEST_DENIED errors.
This error sounds like you have generated a browser key rather than an iOS key. Try generating an iOS key in the Cloud Console and make sure that your bundle ID is whitelisted.