Tracking sleep with email server

Hi everyone,

I’m new to this forum. Want to share a technique I am using to track my sleep hours.

I was looking for a way to track when I fell asleep & woke up. I looked at various apps and wearables but didn’t see anything that suited me well. For example, some wearables consider you to be asleep when you are lying in bed reading a book, and others were not so good privacy-wise.

Theoretically I could just manually record the time when I go to bed and when I wake up, but that is not accurate if I have difficulty falling asleep, or if I wake up in the middle of the night, or simply forget because I am tired, etc…

I realized that checking my email is the first thing I do when I wake up, and the last thing I do before going to sleep. So, I simply made a tiny POP email server that my phone automatically pings whenever I check my email. That server simply records the timestamp each time. Looking at the data, it is pretty obvious when I am awake and when I am sleeping (in context with the rest of the data I am tracking).

I am sharing the Python source code below. Once this script is running (on an EC2 server or anywhere), you just go into your phone’s settings and add a new email account. You can actually enter any bogus username and password. The important thing is to make it only ping when you manually check your email (in iOS, choose “Manual” instead of “Fetch”/“Push”). Then you get automatic logging to a CSV file each time you check your email. Hope this is useful to someone :slight_smile:

'''
Fake email server that writes to a CSV file each time you check your email.
Just run it on any server such as Amazon EC2 (must allow traffic on port 110),
using the filename of the script:

sudo python3 fake_email_server.py

Then add a POP3 email account on your phone using your server's IP address.
You can enter any username/password; it accepts any credentials.

adapted from http://code.activestate.com/recipes/534131-pypopper-python-pop3-server/
'''

import logging
import time
import socket
import sys
import select
import pytz
from datetime import datetime

logging.basicConfig(format="%(name)s %(levelname)s - %(message)s")
log = logging.getLogger("pypopper")
log.setLevel(logging.INFO)

CSV_PATH = 'email_checks.csv'
TIMEZONE = pytz.timezone('US/Eastern')
HOST = '0.0.0.0' # wildcard IP address
PORT = 110 # the default POP3 port

SOCKET_LIFETIME = 15

END = "\r\n"



class ConnectionWrapper:
    '''need wrapper rather than subclass because we don't instantiate it directly'''

    _timeout = 10

    def __init__(self, conn: socket.socket):
        self.conn = conn
        # seconds for one operation until it tries again
        conn.settimeout(self._timeout)

    def sendall(self, data: str):
        if len(data) < 50:
            log.debug("send: %r", data)
        else:
            log.debug("send: %r...", data[:50])
        data += END
        self.conn.sendall(data.encode())

    def recvall(self) -> str:
        data = []
        start = time.time()
        while time.time() - start < self._timeout:
            chunk = self.conn.recv(4096).decode('utf-8')
            if END in chunk:
                data.append(chunk[: chunk.index(END)])
                break
            data.append(chunk)
            if len(data) > 1:
                pair = data[-2] + data[-1]
                if END in pair:
                    data[-2] = pair[: pair.index(END)]
                    data.pop()
                    break
        log.debug("recv: %r", "".join(data))
        return "".join(data)

    def close(self):
        return self.conn.close()

    def get_ip_address(self):
        return self.conn.getsockname()[0]


class ClientClosedSocket(Exception):
    pass


def serve(host, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((host, port))
    try:
        if host:
            hostname = host
        else:
            hostname = "localhost"
        log.info("POP3 serving on %s:%s", hostname, port)
        while True:
            sock.listen(1)
            inner_conn, addr = sock.accept()
            conn = ConnectionWrapper(inner_conn)

            # record the current time stamp and IP address to file
            ip_address = conn.get_ip_address()
            timestamp = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S %Z')
            with open(CSV_PATH, 'a') as f:
                f.write('{},{}\n'.format(timestamp, ip_address))

            log.debug('Connected by %s', addr)
            try:
                conn.sendall("+OK pop3 server ready")
                start = time.time()
                while time.time() - start < SOCKET_LIFETIME:
                    try:
                        _timeout_seconds = 1
                        readable, writable, in_error = select.select(
                            [inner_conn], [inner_conn], [], _timeout_seconds
                        )
                    except OSError:
                        print('OSError...socket closed from client side')
                        raise ClientClosedSocket
                    if not readable:
                        continue
                    # will raise select.error
                    data = conn.recvall()
                    if not data:
                        log.warning('Socket is supposedly readable but got no data')
                        continue
                    command = data.split(maxsplit=1)[0]
                    log.info(f'Received command: {command}')
                    response = dict(
                        USER="+OK user accepted",
                        PASS="+OK pass accepted",
                        STAT="+OK 0 0",
                        LIST="+OK\r\n.\r\n",
                        NOOP="+OK",
                        QUIT="+OK pypopper POP3 server signing off",
                    ).get(command, "-ERR unknown command")
                    conn.sendall(response)
                    if command == 'QUIT':
                        break
                log.info('Closing socket due to timeout')
            except ClientClosedSocket:
                pass
            finally:
                conn.close()
    except (SystemExit, KeyboardInterrupt):
        log.info("pypopper stopped")
    except Exception as ex:
        print('exception is', ex)
        log.critical("fatal error", exc_info=ex)
    finally:
        print('about to do sock.shutdown')
        sock.shutdown(socket.SHUT_RDWR)
        sock.close()


if __name__ == '__main__':        
    serve(HOST, PORT)

2 Likes

Nice!

Tracking my sleep was a big problem for me, when I felt difficulty in sleeping. Thanks, this will help me much.

Love it! :joy: :heart_eyes: Simple yet fitted.:ok_hand:t4:

Clever! On the other hand you’re committing yourself to behavior that isn’t necessarily ideal from a sleep point of view :grimacing: