Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d91f82d
Rough draft of updating login flow
antgiant Nov 23, 2022
fb3df9d
Login 2.0 Beta
antgiant Nov 23, 2022
0b65daf
Tests now pass
antgiant Nov 24, 2022
0fa7f68
Improved error handling
antgiant Nov 24, 2022
edbafa6
Added test coverage for new login flow
antgiant Nov 24, 2022
e047c9e
Refactored URLs and Secrets to simplify any future updates.
antgiant Nov 24, 2022
14a3106
Changed some function names
antgiant Nov 24, 2022
ee4a6fb
Allow download of logged data for new Dream Sock
antgiant Nov 24, 2022
1e7d649
Seems to be fully functional again, bumping version number
antgiant Nov 24, 2022
fc24a0e
Allow all URLs to be updated dynamically
antgiant Nov 24, 2022
a91fb08
Map selected Dream Sock Vitals to Smart Sock 3 format
antgiant Nov 24, 2022
255915f
Translating a few more Dream Sock Data points
antgiant Nov 24, 2022
f31c1f2
Store localID as it is required to access sleep data
antgiant Nov 24, 2022
b443a76
Map Dream Sock Vital Data to Smart Sock 3 Data format.
antgiant Nov 24, 2022
cb40fad
Adding some value explanations
antgiant Nov 26, 2022
3bac0c3
Initial placeholder for DB saving
antgiant Nov 28, 2022
ed56876
Refactor saving device data
antgiant Nov 28, 2022
62578e2
Improving custom property mapping
antgiant Nov 28, 2022
9684677
Improved error handling
antgiant Nov 28, 2022
2853bc6
Expanding list of device properties
antgiant Nov 28, 2022
a3c1471
Adding Device Properties to DB Save
antgiant Nov 28, 2022
c29286b
Bug Fixes:
antgiant Nov 28, 2022
411c544
Fix device_property data storage uniqueness issue
antgiant Nov 30, 2022
a51097a
First pass at adding Property Datapoints (aka 30 day history of values)
antgiant Dec 3, 2022
e2e3fce
Expand new REAL_TIME_VITALS format into older easier to use Smart Soc…
antgiant Dec 4, 2022
561e4d9
save_everything_to_db now includes sleep data
antgiant Dec 19, 2022
4c2a7a6
First pass at adding vital summary data to db dump
antgiant Dec 19, 2022
868e890
Fixed a few typos
antgiant Dec 21, 2022
ebfe9ea
Consolidate DB creation into one place.
antgiant Dec 21, 2022
79339ff
Add default database filename
antgiant Dec 21, 2022
e0a7e64
Add save everything to db as a cli option
antgiant Dec 21, 2022
d9436b6
Add basic status output to save_everything_to_db function.
antgiant Dec 24, 2022
023db62
Further improvements to save status display
antgiant Dec 24, 2022
cdbce06
Add SQL Batching to increase performance of save_everything_to_db fun…
antgiant Dec 24, 2022
e5adaf5
Fix formatting error in status messages
antgiant Dec 24, 2022
064ba97
Bug Fix: Missed Transaction Ending
antgiant Dec 24, 2022
8f58156
Cleaning up status print outs
antgiant Dec 24, 2022
7fe68a1
More status display updates
antgiant Dec 24, 2022
c1aae0a
Adding backoff code to handle too many requests in sleep summary area.
antgiant Mar 19, 2023
ce700b1
Handle slow down errors in more places
antgiant Mar 20, 2023
acb0a65
Try to fix what appear to be login timeout errors
antgiant Mar 21, 2023
3b86872
Updated human friendly error messages
antgiant Mar 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions owlet_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def cli():
parser.add_argument('password', help='Specify Password')
parser.add_argument('actions', help='Specify the actions', nargs='+',
choices=["token", "devices", "attributes",
"stream", 'download'])
"stream", 'dump', 'download'])
parser.add_argument('--dbname', dest='dbname',
help='Specify filename for dump. (dump saves all data to a sqlite database.)')
parser.add_argument('--device', dest='device',
help='Specify DSN for device filter')
parser.add_argument('--stream', dest='attributes', action='append',
Expand All @@ -46,7 +48,7 @@ def cli():
try:
api.login()
except OwletPermanentCommunicationException:
print("Login failed, username or passwort might be wrong")
print("Login failed, username or password might be wrong")
sys.exit(1)
except OwletTemporaryCommunicationException:
print("Login failed, server might be down")
Expand Down Expand Up @@ -74,6 +76,12 @@ def cli():
(myproperty.name, myproperty.display_name,
myproperty.last_update, myproperty.value))

if "dump" in args.actions:
if args.dbname is None:
api.save_everything_to_db()
else:
api.save_everything_to_db(args.dbname)

if "download" in args.actions:
for device in api.get_devices():
if args.device is None or args.device == device.dsn:
Expand Down
282 changes: 265 additions & 17 deletions owlet_api/owlet.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python
"""Contains Class Owlet."""

import json
from json.decoder import JSONDecodeError
import requests
from requests.exceptions import RequestException
Expand All @@ -15,18 +16,30 @@ class Owlet():
# pylint: disable=R0902
def __init__(self, api, json):
"""Initialize Owlet with API reference and json object."""
self.product_name = json['product_name']
self.model = json['model']
self.dsn = json['dsn']
self.sw_version = json['sw_version']
self.mac = json['mac']
self.hwsig = json['hwsig']
self.lan_ip = json['lan_ip']
self.connected_at = json['connected_at']
self.connection_status = json['connection_status']
self.lat = float(json['lat'])
self.lon = float(json['lng'])
self.device_type = json['device_type']
self.product_name = json['product_name'] if 'product_name' in json else ''
self.product_class = json['product_class'] if 'product_class' in json else ''
self.model = json['model'] if 'model' in json else ''
self.dsn = json['dsn'] if 'dsn' in json else ''
self.sw_version = json['sw_version'] if 'sw_version' in json else ''
self.mac = json['mac'] if 'mac' in json else ''
self.hwsig = json['hwsig'] if 'hwsig' in json else ''
self.lan_ip = json['lan_ip'] if 'lan_ip' in json else ''
self.connected_at = json['connected_at'] if 'connected_at' in json else ''
self.connection_priority = json['connection_priority'] if 'connection_priority' in json else ''
self.connection_status = json['connection_status'] if 'connection_status' in json else ''
self.dealer = json['dealer'] if 'dealer' in json else ''
self.lat = float(json['lat']) if 'lat' in json else ''
self.lon = float(json['lng']) if 'lng' in json else ''
self.locality = json['locality'] if 'locality' in json else ''
self.device_type = json['device_type'] if 'device_type' in json else ''
self.has_properties = json['has_properties'] if 'has_properties' in json else ''
self.key = json['key'] if 'key' in json else ''
self.lan_enabled = json['lan_enabled'] if 'lan_enabled' in json else ''
self.manuf_model = json['manuf_model'] if 'manuf_model' in json else ''
self.oem_model = json['oem_model'] if 'oem_model' in json else ''
self.model = json['model'] if 'model' in json else ''
self.template_id = json['template_id'] if 'template_id' in json else ''
self.unique_hardware_id = json['unique_hardware_id'] if 'unique_hardware_id' in json else ''
self.properties = {}
self.update_interval = 10
self.owlet_api = api
Expand Down Expand Up @@ -99,18 +112,250 @@ def update(self):
'Server Request failed - status code')

try:
json = result.json()
json_data = result.json()
except JSONDecodeError:
raise OwletTemporaryCommunicationException(
'Update failed - JSON error')

for myproperty in json:
for myproperty in json_data:
property_name = myproperty['property']['name']
temp_key = myproperty['property']['key']

if property_name in self.properties:
self.properties[property_name].update(myproperty['property'])
else:
new_property = OwletProperty(myproperty['property'])
self.properties[new_property.name] = new_property

if property_name == 'REAL_TIME_VITALS':
# Convert Dream Sock Data to Smart Sock 3 Format
vitals = json.loads(myproperty['property']['value'])

# OXYGEN_LEVEL = ox
new_property = OwletProperty(myproperty['property'])
new_property.name = 'OXYGEN_LEVEL'
new_property.display_name = 'Oxygen Level'
new_property.value = vitals['ox'] if 'ox' in vitals else ''
new_property.key = temp_key + 0.01
new_property.expanded = True
self.properties[new_property.name] = new_property

#HEART_RATE = hr
new_property = OwletProperty(myproperty['property'])
new_property.name = 'HEART_RATE'
new_property.display_name = 'Heart Rate'
new_property.value = vitals['hr'] if 'hr' in vitals else ''
new_property.key = temp_key + 0.02
new_property.expanded = True
self.properties[new_property.name] = new_property

#MOVEMENT = mv
new_property = OwletProperty(myproperty['property'])
new_property.name = 'MOVEMENT'
new_property.display_name = 'Baby Movement'
new_property.value = vitals['mv'] if 'mv' in vitals else ''
new_property.key = temp_key + 0.03
new_property.expanded = True
self.properties[new_property.name] = new_property

# SOCK_CONNECTION = sc
new_property = OwletProperty(myproperty['property'])
new_property.name = 'SOCK_CONNECTION'
new_property.display_name = 'Sock Connection'
new_property.value = vitals['sc'] if 'sc' in vitals else ''
new_property.key = temp_key + 0.04
new_property.expanded = True
self.properties[new_property.name] = new_property

"""
# ??? = st
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['st'] if 'st' in vitals else ''
new_property.key = temp_key + 0.05
new_property.expanded = True
self.properties[new_property.name] = new_property
"""

# BASE_STAT_ON = bso
new_property = OwletProperty(myproperty['property'])
new_property.name = 'BASE_STAT_ON'
new_property.display_name = 'Base Station On'
new_property.value = vitals['bso'] if 'bso' in vitals else ''
new_property.key = temp_key + 0.06
new_property.expanded = True
self.properties[new_property.name] = new_property

#BATT_LEVEL = bat
new_property = OwletProperty(myproperty['property'])
new_property.name = 'BATT_LEVEL'
new_property.display_name = 'Battery Level (%)'
new_property.value = vitals['bat'] if 'bat' in vitals else ''
new_property.key = temp_key + 0.07
new_property.expanded = True
self.properties[new_property.name] = new_property

# BAT_TIME = btt
new_property = OwletProperty(myproperty['property'])
new_property.name = 'BAT_TIME'
new_property.display_name = 'Sock Battery (Minutes)'
new_property.value = vitals['btt'] if 'btt' in vitals else ''
new_property.key = temp_key + 0.08
new_property.expanded = True
self.properties[new_property.name] = new_property

# CHARGE_STATUS = chg
# 1 = Charged
# 2 = Charging
new_property = OwletProperty(myproperty['property'])
new_property.name = 'CHARGE_STATUS'
new_property.display_name = 'Charge Status'
new_property.value = vitals['chg'] if 'chg' in vitals else ''
new_property.key = temp_key + 0.09
new_property.expanded = True
self.properties[new_property.name] = new_property

# ALRTS_DISABLED = aps
new_property = OwletProperty(myproperty['property'])
new_property.name = 'ALRTS_DISABLED'
new_property.display_name = 'Disable Alerts'
new_property.value = vitals['aps'] if 'aps' in vitals else ''
new_property.key = temp_key + 0.10
new_property.expanded = True
self.properties[new_property.name] = new_property

# ALERT = alrt
# 16 = Disconnected
# 32 & 64 = Placement
new_property = OwletProperty(myproperty['property'])
new_property.name = 'ALERT'
new_property.display_name = 'Alert Status'
new_property.value = vitals['alrt'] if 'alrt' in vitals else ''
new_property.key = temp_key + 0.11
new_property.expanded = True
self.properties[new_property.name] = new_property

# OTA_STATUS = ota
# 0 = None
# 1 = Firmware being sent
# 2 = Waiting for sock to be plugged in
# 3 = Installing
# 4 = Installing Critical
# 5 = Unknown
new_property = OwletProperty(myproperty['property'])
new_property.name = 'OTA_STATUS'
new_property.display_name = 'OTA Status'
new_property.value = vitals['ota'] if 'ota' in vitals else ''
new_property.key = temp_key + 0.12
new_property.expanded = True
self.properties[new_property.name] = new_property

# SOCK_STATUS = srf
# 1 = Checking On
# 2 (When sc also = 2) = Kicking
# 3 = Recently Placed
new_property = OwletProperty(myproperty['property'])
new_property.name = 'SOCK_STATUS'
new_property.display_name = 'Sock Status'
new_property.value = vitals['srf'] if 'srf' in vitals else ''
new_property.key = temp_key + 0.13
new_property.expanded = True
self.properties[new_property.name] = new_property

#BLE_RSSI = rsi
new_property = OwletProperty(myproperty['property'])
new_property.name = 'BLE_RSSI'
new_property.display_name = 'BLE RSSI'
new_property.value = vitals['rsi'] if 'rsi' in vitals else ''
new_property.key = temp_key + 0.14
new_property.expanded = True
self.properties[new_property.name] = new_property

"""
# ??? = sb
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['sb'] if 'sb' in vitals else ''
new_property.key = temp_key + 0.15
new_property.expanded = True
self.properties[new_property.name] = new_property
"""

"""
# ??? = ss
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['ss'] if 'ss' in vitals else ''
new_property.key = temp_key + 0.16
new_property.expanded = True
self.properties[new_property.name] = new_property
"""
"""

# ??? = mvb
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['mvb'] if 'mvb' in vitals else ''
new_property.key = temp_key + 0.17
new_property.expanded = True
self.properties[new_property.name] = new_property
"""
"""

# ??? = mst
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['mst'] if 'mst' in vitals else ''
new_property.key = temp_key + 0.18
new_property.expanded = True
self.properties[new_property.name] = new_property
"""
# OXYGEN_TEN_MIN = oxta
new_property = OwletProperty(myproperty['property'])
new_property.name = 'OXYGEN_TEN_MIN'
new_property.display_name = 'Oxygen Ten Minute Average'
new_property.value = vitals['oxta'] if 'oxta' in vitals else ''
new_property.key = temp_key + 0.19
new_property.expanded = True
self.properties[new_property.name] = new_property
"""

# ??? = onm
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['onm'] if 'onm' in vitals else ''
new_property.key = temp_key + 0.20
new_property.expanded = True
self.properties[new_property.name] = new_property
"""
"""

# ??? = bsb
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['bsb'] if 'bsb' in vitals else ''
new_property.key = temp_key + 0.21
new_property.expanded = True
self.properties[new_property.name] = new_property
"""
"""

# ??? = hw
new_property = OwletProperty(myproperty['property'])
new_property.name = '???'
new_property.display_name = '???'
new_property.value = vitals['hw'] if 'hw' in vitals else ''
new_property.key = temp_key + 0.22
new_property.expanded = True
self.properties[new_property.name] = new_property
"""

for name, myproperty in self.properties.items():
if name == "APP_ACTIVE":
Expand All @@ -132,11 +377,14 @@ def download_logged_data(self):
raise OwletNotInitializedException(
'Initialize first - no properties')

if 'LOGGED_DATA_CACHE' not in self.properties:
if 'LOGGED_DATA_CACHE' not in self.properties and \
'VITALS_LOG_FILE' not in self.properties:
raise OwletNotInitializedException(
'Initialize first - missing property')

download_url = self.properties['LOGGED_DATA_CACHE'].value
if 'LOGGED_DATA_CACHE' in self.properties:
download_url = self.properties['LOGGED_DATA_CACHE'].value
if 'VITALS_LOG_FILE' in self.properties:
download_url = self.properties['VITALS_LOG_FILE'].value
download_header = self.owlet_api.get_request_headers()

try:
Expand Down
Loading