Sheetwise

Sheetwise

Background:

Understand that this was a project developed for MIT StupidHack. A hackathon dedicated to developing stupid projects. Also understand that the toilet paper that MIT uses is half a ply, so this is in partly in protest.

Intro:

Let me introduce you to Sheetwise (pronounced shĭtwaɪz). We need to talk about defecation. For too long, our flat mates, colleagues, friends and family have been able to use toilet paper unrestricted, uninterrupted and - importantly - uncharged. This brings about a race to the bottom, a race to the cheapest toilet paper. Today, we democratise access to toilet paper by charging users by the sheet.

Now, for your coddled butts out there – not like the half-ply MIT use - you will be able to purchase the purchase the best toilet paper you want, you deserve it. This is because as you, or your flatmates, through Bluetooth promixity sensors on your phone , approach the Sheetwise device, you will be identified before you evacuate your bowls.

At the wiping phase, we record per sheet extracted from the roll through laser spectroscopy and through associated velocity and frequency measures we can extract the integer number of wipes as well as total toilet paper usage (in metres). The number of sheets is then passed onto the Splitwise API which charges the user accordingly, either in dollar or bitcoin. The use of a sheet will send a push notification to your phone, apple watch and friends - so now you can find out who is using all the good toilet paper, indeed, you might mind less - since they are paying for it!

Finally, faecal regulation is a really important physiological measurement. Specifically, the number of wipes, is a function of the viscosity of the excrement – which is one of the clearest indicators of bodily health. Apple Health is yet to provide an API for wipe rate, but we stand ready to interface with Apple Health.


Photos:







Demo:

We’d now like to demonstrate our solution.

Main (Bluetooth Detection etc)


 import time
from dataclasses import dataclass
import bluetooth
from mouse import RollMonitor

"""
What this should do: 
be waiting for a bluetooth connection
after bluetooth connection starts, initiate measurement module
Occasionally update how much TP has been used for display
After bluetooth connection ends, do end activities

"""

@dataclass
class ToiletRoll:
    name: str
    sheets: int
    roll_price_USD: float

    def get_cost(self, sheets_used) -> float:
        return self.roll_price_USD/self.sheets * sheets_used

class ble_module:
    def __init__(self):
        self.whitelist = ["OnePlus 7Pro", "SPIFF"]
        return 

    def get_nearest(self) -> str: 
        nearby_devices = bluetooth.discover_devices(lookup_names=True)
        print("Found {} devices.".format(len(nearby_devices)))

        devices = [(name, addr) for (addr, name) in nearby_devices if name in self.whitelist]

        return devices[0] if len(devices) else ("", "")
    
    def is_still_nearby(self, addr) -> bool:
        return addr in bluetooth.discover_devices()

class ShitWise:
    def __init__(self, toilet_roll_data:ToiletRoll):
        self.ble = ble_module()
        self.monitor = RollMonitor()
        self.toilet_roll_data = toilet_roll_data

    def run(self):
        # Wait for BLE connection to device
        connected_device_name = ""
        connected_device_addr = ""
        while connected_device_name == "":
            print("Scanning for whitelisted devices")
            connected_device_name, connected_device_addr = self.ble.get_nearest()

        print("Connected to {} {}".format(connected_device_name, connected_device_addr))

        # start rollmonitor
        self.monitor.reset()
        self.monitor.start()

        # Wait for BLE to disconnect
        while self.ble.is_still_nearby(connected_device_addr):
            print("still connected")

        # Disconnected, do final sheet accounting 
        print("{} disconnected! Session finished".format(connected_device_name))
        self.monitor.stop()
        sheets_used = self.monitor.get_sheets()
        print("sheets used: {:.2f} \t price: {:10.2f}".format(sheets_used, self.toilet_roll_data.get_cost(sheets_used)))

        # Splitwise

if __name__ == "__main__":
    toilet_roll_data = ToiletRoll(name = "Charmin ultrasoft", sheets = 240, roll_price_USD = 8.49/6)
    sw = ShitWise(toilet_roll_data)
    sw.run()
     

Splitwise API


 from splitwise import Splitwise 

from splitwise.expense import Expense
from splitwise.user import ExpenseUser
from splitwise.expense import Expense

sObj = Splitwise(XXXX)

# ==== use mary's details 
user = sObj.getCurrentUser()
groups = sObj.getGroups() 


# ==== find members of the group 
flatgroup = groups[60].id 
mems = groups[60].getMembers()
ellen = mems[2].id
tomi = mems[1].id
userid = user.id 


# ======= expense creation - working but shared

# expense = Expense()
# expense.setGroupId(flatgroup)
# expense.setSplitEqually(True)
# expense.setCost('0.2')
# expense.setDescription("Testing")
# created_expense, errors = sObj.createExpense(expense)
# test = created_expense.getId()
# print(expense)


# ======= expense creation - also working 


def sheetpushmary():
  expense = Expense()
  expense.setGroupId(flatgroup)
  expense.setCost("0.02")
  expense.setDescription("1 Sheet")
  user1 = ExpenseUser()
  user1.setId(userid)
  user1.setPaidShare('0')
  user1.setOwedShare('0.02')
  user2 = ExpenseUser()
  user2.setId(ellen)
  user2.setPaidShare('0.01')
  user2.setOwedShare('0.00')
  user3 = ExpenseUser()
  user3.setId(tomi)
  user3.setPaidShare('0.01')
  user3.setOwedShare('0.00')
  expense.addUser(user1)
  expense.addUser(user2)
  expense.addUser(user3)
  nExpense, errors = sObj.createExpense(expense)
  print(nExpense)

def sheetpushellen():
  expense = Expense()
  expense.setGroupId(flatgroup)
  expense.setCost("0.02")
  expense.setDescription("1 Sheet")
  user1 = ExpenseUser()
  user1.setId(ellen)
  user1.setPaidShare('0')
  user1.setOwedShare('0.02')
  user2 = ExpenseUser()
  user2.setId(userid)
  user2.setPaidShare('0.01')
  user2.setOwedShare('0.00')
  user3 = ExpenseUser()
  user3.setId(tomi)
  user3.setPaidShare('0.01')
  user3.setOwedShare('0.00')
  expense.addUser(user1)
  expense.addUser(user2)
  expense.addUser(user3)
  nExpense, errors = sObj.createExpense(expense)
  print(nExpense)

def sheetpushtomi():
  expense = Expense()
  expense.setGroupId(flatgroup)
  expense.setCost("0.02")
  expense.setDescription("1 Sheet")
  user1 = ExpenseUser()
  user1.setId(tomi)
  user1.setPaidShare('0')
  user1.setOwedShare('0.02')
  user2 = ExpenseUser()
  user2.setId(userid)
  user2.setPaidShare('0.01')
  user2.setOwedShare('0.00')
  user3 = ExpenseUser()
  user3.setId(ellen)
  user3.setPaidShare('0.01')
  user3.setOwedShare('0.00')
  expense.addUser(user1)
  expense.addUser(user2)
  expense.addUser(user3)
  nExpense, errors = sObj.createExpense(expense)
  print(nExpense)

sheetpushmary()
sheetpushellen()
sheetpushtomi()

 

     

Laser Spectroscopy


from datetime import datetime, timedelta
import threading
import time
import win32api

RESOLUTION = (1920, 1080)
PIXELS_PER_SHEET = 5400


class RollMonitor:

    def __init__(self):
        self._resolution = RESOLUTION
        self._center = (RESOLUTION[0] // 2, RESOLUTION[1] // 2)
        self._x_accum = 0
        self._y_accum = 0
        self._running = False
        self._thread = None
        self._changed = False

    def _reset_cursor(self):
        win32api.SetCursorPos(self._center)

    def _update(self):
        self._reset_cursor()
        while self._running:
            x, y = win32api.GetCursorPos()
            self._reset_cursor()
            delta_x = abs(self._center[0] - x)
            delta_y = abs(self._center[1] - y)
            if delta_x or delta_y:
                self._changed = True
                self._x_accum += delta_x
                self._y_accum += delta_y
            time.sleep(0.1)

    def start(self):
        self.reset()
        self._running = True
        self._thread = threading.Thread(target=self._update)
        self._thread.start()

    def stop(self):
        self._running = False
        self._thread.join()

    def reset(self):
        self._x_accum = 0
        self._y_accum = 0

    def changed(self):
        return self._changed

    def getpos(self):
        self._changed = False
        return self._x_accum, self._y_accum

    def get_sheets(self):
        return self._x_accum / PIXELS_PER_SHEET


def main():
    mon = RollMonitor()
    mon.start()
    start_time = datetime.now()
    print("starting")
    while (datetime.now() - start_time) < timedelta(seconds=10):
        if mon.changed():
            print(mon.getpos(), round(mon.get_sheets(), 2))
    print("stopping")
    mon.stop()

def calibrate():
    mon = RollMonitor()
    print("")

if __name__ == '__main__':
    main()