Extend battery life

Hi All,

I have a photon project that is powered by a LiPo via a power shield.
I currently have about a 9 hour battery life from 900mAh, and I’d like to keep the same battery, but implement some code to extend the battery life.
I’m thinking about using sleep or something similar.

The photon connects to my wifi, listens for an interrupt on one of the digital pins, then sends a web hook and some data when the interrupt occurs. I need to have it as close to realtime as possible. I currently have between .5 and 5 seconds between the interrupt and receiving a notification that is triggered by the web hook.

Any suggestions on how to best do this? I’m not sure if there really is a way to do this, as I don’t want a long delay with the unit signing on to the wifi.

Does anyone have a figure on how long it takes to send a web hook starting from a deep sleep, or another sleep mode via the WKP pin?

Thx

1 Like

@jimbol, “as close to realtime as possible” is odd when you also say the it currently takes between .5 and 5 seconds to do the transfer. Do you want the response to the interrupt to be fast (ie fire the webhook) or the response from the target server (full round trip time) to be fast?

There are two sleep modes of interest on the Photon - deep sleep and stop sleep. In deep sleep mode, the Photon is awakened by either a rising edge event on the WKP pin or a timer event (you set the deep sleep time). When the Photon awakens, it will act as if it was reset. Deep sleep uses the least amount of power on the Photon. However, you will need to consider the power consumed by any peripheral devices

With stop sleep, the Photon uses a little more power but can be awakened from any pin interrupt condition (rising, falling, etc) or a timer event. When the Photon awakens, the code will continue from the next line after the sleep call.

In either scenarios, you will need to consider the state of the WiFi and the fact that it will need to reconnect after waking. Then the cloud connection will need to be reestablished and finally, the publish sent to fire the webhook. If your current configuration takes .5 to 5 seconds, expect a sleeping configuration to take longer due to the wifi/cloud reconnect. However, once the webhook is fired, the delays are now outside of its control. Have you measured the latency of the target server’s response and that of the webhook “relay” time? Is this a third part server or your own server?

2 Likes

Hi @peekay123,

Thanks for the reply.

So, I guess what I mean by as close to realtime as possible, is that I want to minimize the time to get connected again to send the web hook. My scenario is this: I have a photon at the stern of a ship at sea, and I’m in the wheelhouse, quite far from the equipment. When the interrupt is triggered the photon sends the web hook via onboard wifi, and then thru a VSAT connection with a fairly high latency -say 3/4 second. The web hook notifies a 3rd party server - Growl, to send a notification to my iPhone, again coming back thru the VSAT. The notification opens an app on the phone that receives data from a variable in the Particle Cloud.

Originally I had a system that was using Xbees to do all the work, which was good, and didn’t have all the latency of this system, but I wanted a more elegant system the would work thru my phone which I already have with me, as opposed to using another gadget in the system for receiving the alert. In a perfect world I would have a system that didn’t go to the cloud at all, and did it all onboard thru the WLAN, but my skills have not proven up to that task! There are lots of ways to do it thru the cloud, but I’ve found it too difficult to do it all locally.
perhaps via bluetooth.

I might be able to live with the delay waking from sleep, but I guess I’ll just have to try it and find out!!

1 Like

@jimbol, wow! If you could stay in range of a bluetooth device like the Redbear Duo, you could create a Photon-to-Duo connection via UDP or TCP and then relay the trigger to your phone via the Duo’s bluetooth. You would need an app on your phone for that however. I suspect the real delay in your transaction comes from the VSAT link and the Growl response time. This is evidenced by the 0.5 to 5s response time you are seeing now.

For the fastest response, you will need to keep all communications onboard meaning a UPD or TCP link between your phone and the Photon. I am not very knowledgable on the phone side so perhaps a search in the forum might help. Or start a topic for “Can iPhone communicate directly to Photon via UPD or TCP?”. This would require a custom app for the phone but might be worth exploring.

@jimbol, don’t forget to post your results! Other members would benefit from your findings :wink:

I think your main problem with sending data to your iPhone (locally) will be Apple’s limitations on what kind of apps can keep running in the background when suspended. When the phone goes to sleep, your app will be suspended, and will not receive data from your device. There are some work-arounds. You can change the auto-lock time to “never” so the phone doesn’t go to sleep ever, but that’s going to use quite a bit of power (it would help if you could turn off the cellular, if that’s acceptable). Also, if you use this method, you wouldn’t be able to use the phone for anything else as long as the app that’s listening to your device is running. I’ve also seen people make their app look like a voip app that is allowed to continue running in the background. I think this mode can be used to receive data in the background that will wake the app up when the data comes in. What kind of frequency of transmissions from your device are you anticipating? How much data will be sent with each transmission? How critical is it that you receive the data with minimal delay?

With the above caveats, it’s certainly possible to create a persistent connection between the phone and the device that will listen for data sent with tcp. I’m using code on my iPhone meant for subscribing to Particle.publish events, that I’ve modified to accept local tcp client transmissions, and that works fine.

1 Like

From my experience you will lose a couple of seconds coming out from deep sleep (approximately 5 seconds or less for restart, connecting to wifi/cloud). But to extend battery life you really want deep sleep.

If your current time is up to 5 seconds from running, that would indicate ~10 seconds from deep sleep. Since it seems like you are not unhappy with the current up to 5 seconds maybe you can explore using deep sleep but then publishing your event data on your local network (starting https://docs.particle.io/reference/firmware/photon/#tcpclient ). Maybe do a POST to a local webserver or similar?

I’m not sure if there is an existing app or another solution for on an iPhone that could monitor for new events from a local webserver. Suggest searching for an existing apps that can read from a localhost supplied address (maybe in the category of apps that can monitor webservers on a local network).

Good luck. Would love to hear of any solutions that work for you.

1 Like

Thanks all!

@Ric, yes, the apple restrictions are a problem for keeping a connection going. I only wish that was the problem I am facing now! That’s further down the track, and i think it’s manageable, as I’m only doing this for my own use, and I can register the app as a VOIP app which will let me keep a socket open. Since it’s my own app for development on my own device I think that’s easy. I have actually considered making a VOIP component to my app if I ever need to put it in the app store, there is a use scenario where that becomes a significant part of the system.

The minimal delay is pretty crucial, as the ship has to be stopped as soon as possible after the notification, and stopping as close to the notification location is the whole point of the excercise.

I have not made much progress making any sort of local wifi communication work, as I’m not experienced with TCP sockets, or UDP packets, either of which seems like they could do what i’m trying to accomplish. @Ric, Am I able to find some example of your code anywhere? It sounds like it may do much of the work I’m trying to accomplish.

@bloukingfisher, I had the system running recently underway, and mostly I was seeing a 10-15 second delay from sleep. Occasionally I had up to a 45 second delay, which appears to be mostly reconnecting to the wifi, I might be able to improve that by putting in another access point right near the Photon’s location. As far as using the web server, one of the things I’m trying to do but didn’t explain, is that I want to minimize the pieces of equipment specific to this task as much as possible, I’d prefer not to have another piece of hardware in the mix. The system needs to be portable, I’ll bring it with me and use it on multiple vessels, and I want to be able to use existing infrastructure onboard, where there is typically a good Wifi system connected to the internet via multiple links (VSAT, Cellular, INMARSAT, etc.)

@peekay123, The custom app side of things doesn’t bother me, I’m doing that already, though I’m not very experienced in app development, I seem to be able to make progress reasonably. I’ll try another topic directed right at that as you suggested.

1 Like

I’ll share some observations but don’t have technical knowledge to confirm or explain. I have noticed longer connection times from my device under a variety of conditions:

  • Using semi-automatic mode with code trying to better manage the wifi connection (and when the connection drops) appear to significantly slow down the connection time.
  • Having connected the device to multiple access points (SSID/password combinations) in my case where I have 3 or 4 wifi connections in range including a range extender. It feels like it is cycling looking for the strongest signal (green slow flashes, rapid flashing, brief red/magenta single flash, returns to green slow flashing, rapid flashing etc…maybe 3 - 5 cycles). These are not extensively tested and verified observations but if connection time is important and over time you have connected it to other access points it may be worth testing forgetting all networks and only setting up 1.
  • If using an external antenna (which you may want to do anyway) it seems to speed up connection time to specify using the external antenna (and not leaving it to automatically choose).

A little while ago I wrote an app to listen to data coming from my Photon via TCP. This probably doesn’t have all the bells and whistles that it should have for safety, but it does work (you might check out some of the posts from @rickkas7 on the subject of TCP) .

Here is the Photon code used to test the connection. It sends the same data every 10 seconds. For ease of determining the end of the transmission, I’ve used a delimiter at the end of the message.

TCPServer server = TCPServer(80);
TCPClient client;
char data[] = {"1 This is some fairly long data, that repeats this sentence multiple times. 2 This is some fairly long data, that repeats this sentence multiple times. 3 This is some fairly long data, that repeats this sentence multiple times. 4 This is some fairly long data, that repeats this sentence multiple times. 5 This is some fairly long data, that repeats this sentence multiple times.6 This is some fairly long data, that repeats this sentence multiple times. 7 This is some fairly long data, that repeats this sentence multiple times. 8 This is some fairly long data, that repeats this sentence multiple times. And now we have the end$#$"};
Timer transmitTimer(10000, transmitTimerHandler);
SYSTEM_MODE(SEMI_AUTOMATIC); // no need to connect to the cloud since this app is for local data transmission

void setup() {
  server.begin();
  WiFi.connect();
  while (!WiFi.ready()) {}
}

void loop() {
  client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
        if (client.available()) {
            char c = client.read();
            // if you've gotten to the end of the line (received a newline
            // character) and the line is blank, the http request has ended,
            // so you can send a reply
            if (c == '\n' && currentLineIsBlank) {
                client.println("HTTP/1.1 200 OK");
                client.println("Connection: keep-alive");  
                client.println("Content-Type: text/event-stream");
                client.println();
                transmitTimer.start();
            }
            
            if (c == '\n') {
                // you're starting a new line
                currentLineIsBlank = true;
            } else if (c != '\r') {
                // you've gotten a character on the current line
                currentLineIsBlank = false;
            }
        }
    }
    delay(100);
  }
  
}


void transmitTimerHandler() {
    client.print(data);
}

The iOS code consists of a class ParticleTCPListener with the following code,

import Foundation


protocol ParticleListenerDataDelegate {
    func receivedData(success: String?, error: String?)
}


class ParticleTCPListener: NSObject, URLSessionDataDelegate {
        
    let url: URL
    let delimiter: String
    let retryTime: Double = 3 // in seconds
    var task: URLSessionDataTask?
    let receivedData = NSMutableData()
    var delegate: ParticleListenerDataDelegate?
    
    
    init(url: String, delimiter: String) {
        self.url = URL(string: url)!
        self.delimiter = delimiter
        super.init()
        self.connect()
    }
    
    
    
    func connect() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = TimeInterval(INT_MAX)
        configuration.timeoutIntervalForResource = TimeInterval(INT_MAX)
        configuration.httpAdditionalHeaders = ["Accept": "text/event-stream", "Cache-Control": "no-cache"]
        let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue())
        self.task = urlSession.dataTask(with: self.url)
        self.task!.resume()
    }


    
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        
        self.receivedData.append(data)
        let resultString =  NSString.init(data: self.receivedData as Data, encoding: String.Encoding.utf8.rawValue)!
        if resultString.hasSuffix(self.delimiter) {
            let s = resultString.replacingOccurrences(of: self.delimiter, with: "")
            DispatchQueue.main.async() {
                self.delegate?.receivedData(success: "\(NSDate()): \(s)\n", error: nil)
            }
            self.receivedData.setData(NSData() as Data)
        }
        
    }

    
    
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        print("didReceiveError")
        if error == nil || error!._code != -999 {
            let delayTime = DispatchTime.now() + self.retryTime
            DispatchQueue.main.asyncAfter(deadline: delayTime) {
                self.connect()
            }
        }
        
        DispatchQueue.main.async {
            self.delegate?.receivedData(success: nil, error: (error?.localizedDescription)!)
        }
    }
    
}

…and this minimal code in the view controller,

import UIKit

class ViewController: UIViewController, ParticleListenerDataDelegate {
    
    var eventSource: ParticleTCPListener!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        eventSource = ParticleTCPListener(url: "http://10.0.1.13", delimiter: "$#$")
        eventSource.delegate = self
    }
    

    func receivedData(success: String?, error: String?) {
        guard success != nil else {
            print(error!)
            return
        }
        print(success!)
        print("\n\n")
    }
}

Thanks @Ric,
Looking forward to having the time to work with this, hopefully next week!

Thanks for the info @Ric, it’s taken a while for me to get back to this topic.

I’ve been trying to get this to work for the past week with no luck, my problem is on the IOS side - I’m not up to speed enough in Xcode to make it work.

I make a new single view app, and I add a new Cocoa Touch class called ParticleTCPListener, but I can’t get the code to work in the view controller or the ParticleTCPListener. It doesn’t like the class or the protocol statements.

I’m not sure what I’m doing wrong, if you see my error, please let me know, otherwise, I guess I’ll have to start hanging out in an Xcode IOS forum!

Thanks!

ViewController.m:

//  ViewController.m
//  TCPTest

#import "ViewController.h"

@interface ViewController ()

class ViewController: UIViewController, ParticleListenerDataDelegate {

    var eventSource: ParticleTCPListener!

    override func viewDidLoad() {
        super.viewDidLoad()
        eventSource = ParticleTCPListener(url: "http://10.0.1.13", delimiter: "$#$")
        eventSource.delegate = self
    }


    func receivedData(success: String?, error: String?) {
        guard success != nil else {
            print(error!)
            return
        }
        print(success!)
        print("\n\n")
    }
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

ParticleTCPListener.h:

//
//  ParticleTCPListener.h
//  TCPTest


#import <Foundation/Foundation.h>


@interface ParticleTCPListener : NSObject

> @end

ParticleTCPListener.m:

//
//  ParticleTCPListener.m
//  TCPTest


#import "ParticleTCPListener.h"

@implementation ParticleTCPListener



protocol ParticleListenerDataDelegate {
    func receivedData(success: String?, error: String?)
}


class ParticleTCPListener: NSObject, URLSessionDataDelegate {

    let url: URL
    let delimiter: String
    let retryTime: Double = 3 // in seconds
    var task: URLSessionDataTask?
    let receivedData = NSMutableData()
    var delegate: ParticleListenerDataDelegate?


    init(url: String, delimiter: String) {
        self.url = URL(string: url)!
        self.delimiter = delimiter
        super.init()
        self.connect()
    }



    func connect() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = TimeInterval(INT_MAX)
        configuration.timeoutIntervalForResource = TimeInterval(INT_MAX)
        configuration.httpAdditionalHeaders = ["Accept": "text/event-stream", "Cache-Control": "no-cache"]
        let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue())
        self.task = urlSession.dataTask(with: self.url)
        self.task!.resume()
    }




    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {

        self.receivedData.append(data)
        let resultString =  NSString.init(data: self.receivedData as Data, encoding: String.Encoding.utf8.rawValue)!
        if resultString.hasSuffix(self.delimiter) {
            let s = resultString.replacingOccurrences(of: self.delimiter, with: "")
            DispatchQueue.main.async() {
                self.delegate?.receivedData(success: "\(NSDate()): \(s)\n", error: nil)
            }
            self.receivedData.setData(NSData() as Data)
        }

    }




    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        print("didReceiveError")
        if error == nil || error!._code != -999 {
            let delayTime = DispatchTime.now() + self.retryTime
            DispatchQueue.main.asyncAfter(deadline: delayTime) {
                self.connect()
            }
        }

        DispatchQueue.main.async {
            self.delegate?.receivedData(success: nil, error: (error?.localizedDescription)!)
        }
    }

}

@end

I see one obvious problem; my code is written in Swift, but your project is in Objective-C. You need to start a new project, and make sure you choose Swift as the language. When you add a new file, choose Swift File, not Cocoa Touch Class.

Thanks Ric,
I’ll try that and see if I can make any progress!

Ric,
Got it to work, thanks!
I’ll see what I can do with it from here