Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ paho-mqtt = "*"
ring-doorbell = ">=0.6.0"
psutil = "*"
pywin32 = {sys_platform = "== 'win32'"}
pyarlo = "*"

[dev-packages]
"flake8" = "*"
codecov = "*"
coverage = "*"
black = { markers="python_version = '>= 3.6'" }
black = {markers = "python_version = '>= 3.6'"}

[pipenv]
allow_prereleases = true
32 changes: 23 additions & 9 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ GEM
jekyll-seo-tag (~> 2.1)
minitest (5.11.3)
multipart-post (2.0.0)
nokogiri (1.10.4)
nokogiri (1.10.8)
mini_portile2 (~> 2.4.0)
octokit (4.13.0)
sawyer (~> 0.8.0, >= 0.5.3)
Expand Down
16 changes: 16 additions & 0 deletions docs/_data/monitors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,19 @@
- name: percent_free
desc: The minimum percent of available (as per psutils' definition) memory
required: 'yes'
- name: arlo_camera
oneline: Check Arlo camera battery level
params:
- name: username
desc: Arlo username
required: 'yes'
- name: password
desc: Arlo password
required: 'yes'
- name: device_name
desc: Camera device name (e.g. "Front")
required: 'yes'
- name: base_station_id
desc: The number of your base station; only required if you have more than one. It's an array index, but figuring out which one is which is an exercise left the reader
required: 'no'
default: 0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"winmonitor=simplemonitor.winmonitor:main",
]
},
extras_require={"ring": ["ring-doorbell>=0.6.0"]},
extras_require={"ring": ["ring-doorbell>=0.6.0"], "arlo": ["pyarlo"]},
install_requires=[
"boto3",
"colorlog",
Expand Down
95 changes: 95 additions & 0 deletions simplemonitor/Monitors/arlo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
try:
import pyarlo

pyarlo_available = True
except ImportError:
pyarlo_available = False

from typing import Optional, cast

from ..Monitors.monitor import Monitor, register


@register
class MonitorArloCamera(Monitor):
"""Monitor the battery life on an Arlo camera."""

type = "arlo_camera"

def __init__(self, name: str, config_options: dict) -> None:
if "gap" not in config_options:
config_options["gap"] = 21600 # 6 hours
super().__init__(name, config_options)
if not pyarlo_available:
self.monitor_logger.critical("pyarlo library is not installed")
self.monitor_logger.critical("Try: pip install pyarlo")
self.monitor_logger.critical(" or pip install simplemonitor[arlo]")
self.device_name = cast(str, self.get_config_option("device_name"))
self.minimum_battery = cast(
int,
self.get_config_option("minimum_battery", required_type="int", default=25),
)
self.arlo_username = cast(
str, self.get_config_option("username", required=True)
)
self.arlo_password = cast(
str, self.get_config_option("password", required=True)
)
self.base_station_id = cast(
int,
self.get_config_option("base_station_id", required_type="int", default=0),
)
self.arlo = None # type: Optional[pyarlo.PyArlo]
self.arlo_base = None # type: Optional[pyarlo.ArloBaseStation]
self.camera = None # type: Optional[pyarlo.ArloCamera]

def run_test(self) -> bool:
if not pyarlo_available:
return self.record_fail("pyarlo library is not installed")
if self.arlo is None:
self.monitor_logger.info("logging in to Arlo")
try:
self.arlo = pyarlo.PyArlo(
username=self.arlo_username, password=self.arlo_password
)
except Exception:
self.monitor_logger.exception("arlo login failed")
return self.record_fail("could not log in to Arlo")
if self.arlo is None:
return self.record_fail("failed to get Arlo object")
if self.arlo_base is None:
try:
self.arlo_base = self.arlo.base_stations[self.base_station_id]
except Exception:
self.monitor_logger.exception("arlo base station fetch failed")
return self.record_fail("could not fetch base station")
if self.arlo_base is None:
return self.record_fail("failed to get ArloBaseStation object")
if self.camera is None:
for camera in self.arlo.cameras:
if camera.name == self.device_name:
self.camera = camera
if camera is None:
return self.record_fail(
"could not find camera named {}".format(self.device_name)
)
else:
self.monitor_logger.info("Updating Arlo camera")
try:
camera.update()
except Exception:
return self.record_fail("failed to update Arlo camera")
battery = camera.battery_level # type: int
if battery < self.minimum_battery:
return self.record_fail(
"Battery is at {}% (limit: {}%)".format(battery, self.minimum_battery)
)
else:
return self.record_success(
"Battery is at {}% (limit: {}%)".format(battery, self.minimum_battery)
)

def describe(self) -> str:
return "Checking Arlo camera {} has battery level of at least {}%".format(
self.device_name, self.minimum_battery
)
8 changes: 6 additions & 2 deletions simplemonitor/Monitors/ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ def __init__(self, name: str, config_options: dict) -> None:
int,
self.get_config_option("minimum_battery", required_type="int", default=25),
)
self.ring_username = cast(str, self.get_config_option("username"))
self.ring_password = cast(str, self.get_config_option("password"))
self.ring_username = cast(
str, self.get_config_option("username", required=True)
)
self.ring_password = cast(
str, self.get_config_option("password", required=True)
)
self.cache_file = Path(
self.get_config_option("cache_file", default=".ring_token.cache")
)
Expand Down
1 change: 1 addition & 0 deletions simplemonitor/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .Loggers import file as file_logger # noqa: F401
from .Loggers import logger, mqtt # noqa: F401
from .Loggers import network as network_logger # noqa: F401
from .Monitors import arlo # noqa: F401
from .Monitors import compound # noqa: F401
from .Monitors import file as file_monitor # noqa: F401
from .Monitors import hass, host, monitor # noqa: F401
Expand Down