ArloService WIP (understood the logic but still have to generate the transId.. The only missing info) + commit of arlo.py (great ! but have to look if disconnection is handled) + Arlo\arlo-motiondetect.py integrate connection to mqtt server + update in allInOne (user & pswd hassio)

This commit is contained in:
Thomas Fransolet 2020-02-08 00:00:08 +01:00
parent 98041c07a3
commit 45fbed654a
41 changed files with 6755 additions and 8 deletions

View File

@ -70,6 +70,30 @@ namespace MyCore.Services
public long dateCreated;
}
// Motion Detection Test - {"from":"XXX-XXXXXXX_web","to":"XXXXXXXXXXXXX","action":"set","resource":"cameras/XXXXXXXXXXXXX","transId":"web!XXXXXXXX.XXXXXXXXXXXXXXXXXXXX","publishResponse":true,"properties":{"motionSetupModeEnabled":true,"motionSetupModeSensitivity":80}}
public class MotionDetection
{
public string from;
public string to;
public string action;
public string resource;
public string transId;
public bool publishResponse = true;
public PropertiesRequestMotionSubscription properties;
}
public class PropertiesRequest
{
public bool motionSetupModeEnabled;
public int motionSetupModeSensitivity;
}
public class PropertiesRequestMotionSubscription
{
public string[] devices;
}
public enum RequestType
{
Get,
@ -194,9 +218,56 @@ namespace MyCore.Services
var evt = new EventSourceReader(new Uri($"{_clientSubscribeUrl}?token={resultToken.token}")).Start();
evt.MessageReceived +=
(object sender, EventSourceMessageEventArgs e)
async (object sender, EventSourceMessageEventArgs e)
=>
{
using (var client = new HttpClient())
{
ArloDevice baseStation = allArloDevices.Where(d => d.deviceType == "basestation").FirstOrDefault();
ArloDevice camera = allArloDevices.Where(d => d.deviceType == "camera").FirstOrDefault();
Console.WriteLine($"{e.Event} : {e.Message}");
// ask for motion event test
//Motion Detection Test - { "from":"XXX-XXXXXXX_web","to":"XXXXXXXXXXXXX","action":"set","resource":"cameras/XXXXXXXXXXXXX","transId":"web!XXXXXXXX.XXXXXXXXXXXXXXXXXXXX","publishResponse":true,"properties":{ "motionSetupModeEnabled":true,"motionSetupModeSensitivity":80} }
/*MotionDetection motionDetection = new MotionDetection();
motionDetection.from = $"{baseStation.userId}_web";
motionDetection.to = $"{baseStation.deviceId}";
motionDetection.action = "set";
motionDetection.resource = $"cameras/{camera.deviceId}";
motionDetection.transId = $"web!XXXXXXXX.XXXXXXXXXXXXXXXXXXXX";
motionDetection.publishResponse = true;
PropertiesRequest propertiesRequest = new PropertiesRequest();
propertiesRequest.motionSetupModeEnabled = true;
propertiesRequest.motionSetupModeSensitivity = 80;
motionDetection.properties = propertiesRequest;
var body = JsonConvert.SerializeObject(motionDetection).ToString();*/
// Try to subscribe to motion for camera
/*MotionDetection motionDetection = new MotionDetection();
motionDetection.from = $"{baseStation.userId}_web";
motionDetection.to = $"{baseStation.deviceId}";
motionDetection.action = "set";
motionDetection.resource = $"subscriptions/{baseStation.userId}_web";
motionDetection.transId = $"web!XXXXXXXX.XXXXXXXXXXXXXXXXXXXX";
motionDetection.publishResponse = false;
PropertiesRequestMotionSubscription propertiesRequestMotionSubscription = new PropertiesRequestMotionSubscription();
propertiesRequestMotionSubscription.devices = new string[] { camera.deviceId };
motionDetection.properties = propertiesRequestMotionSubscription;
var body = JsonConvert.SerializeObject(motionDetection).ToString();
HttpContent c = new StringContent(body, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("xcloudId", baseStation.xCloudId);
client.DefaultRequestHeaders.Add("Authorization", resultToken.token);
HttpResponseMessage result = await client.PostAsync($"{ _userDevicesNotifyUrl}/{baseStation.deviceId}", c);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
}*/
}
};
evt.Disconnected += async (object sender, DisconnectEventArgs e) => {
Console.WriteLine($"Retry: {e.ReconnectDelay} - Error: {e.Exception}");
await Task.Delay(e.ReconnectDelay);

View File

@ -0,0 +1,9 @@
# These are supported funding model platforms
github: [jeffreydwalter]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL

View File

@ -0,0 +1,43 @@
Please answer these questions before submitting your issue. Thanks!
### What version of Python are you using (`python -V`)?
### What operating system and processor architecture are you using (`python -c 'import platform; print(platform.uname());'`)?
### Which Python packages do you have installed (run the `pip freeze` or `pip3 freeze` command and paste output)?
```
Paste your ouptut here
```
### Which version of ffmpeg are you using (`ffmpeg -version`)?
```
Paste your output here
```
### Which Arlo hardware do you have (camera types - [Arlo, Pro, Q, etc.], basestation model, etc.)?
### What did you do?
If possible, provide the steps you took to reproduce the issue.
A complete runnable program is good. (don't include your user/password or any sensitive info)
```
Paste your ouptut here
```
### What did you expect to see?
```
Paste your ouptut here
```
### What did you see instead?
```
Paste your ouptut here
```
### Does this issue reproduce with the latest release?

201
RPI Code/Arlo/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

48
RPI Code/Arlo/Makefile Normal file
View File

@ -0,0 +1,48 @@
#red:=$(shell tput setaf 1)
#reset:=$(shell tput sgr0)
init:
pip install -r requirements.txt
clean:
rm -rf *.pyc
rm -rf dist
rm -rf build
rm -rf __pycache__
rm -rf arlo.egg-info
doc:
#pydoc -w ../arlo/arlo.py
#mv arlo.html arlo_api_doc.md
#git add arlo_api_doc.md
pdoc --overwrite --html --html-no-source --html-dir docs arlo.py
sed -i'.bak' 's/#sidebar{width:30%}#content{width:70%;/#sidebar{width:45%}#content{width:55%;/g' docs/arlo.html
rm docs/arlo.html.bak
python dev/html2text.py docs/arlo.html > docs/README.md
git add docs/*
rev:
python dev/rev.py setup.py
git add setup.py
commit:
ifndef message
$(error "Error: commit message required. Usage: make $(MAKECMDGOALS) message='<your commit message here>'")
endif
git add Makefile
git add dev/*
git add arlo.py
git add request.py
git add eventstream.py
git add requirements.txt
git commit -m "$(message)"
git push
release: clean rev doc commit
python3 setup.py sdist
python3 setup.py bdist_wheel --universal
twine upload --skip-existing dist/*

134
RPI Code/Arlo/README.md Normal file
View File

@ -0,0 +1,134 @@
![](logo.png)
# arlo ![](https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-blue.svg)
> Python module for interacting with Netgear's Arlo camera system.
>
>### Now in Golang!
>If you love the Go programming language, check out [arlo-golang](https://github.com/jeffreydwalter/arlo-golang).
>My goal is to bring parity to the Python version asap. If you know what you're doing in Go, I would appreciate any feedback on the >general structure of the library, and contributions, etc.
---
### GETTING STARTED
Check out the [API DOCS](https://github.com/jeffreydwalter/arlo/tree/master/docs)
**IMPORTANT:** There is a regression in `sseclient 0.0.24` that breaks this package. Please ensure you have `seeclient 0.0.22` installed.
**IMPORTANT:** Please ensure you don't have ANY other `sseclient` packages installed in addition to `sseclient 0.0.22`! This may cause this package to fail in unexpected ways. A common one that is known to cause issues is the `sseclient-py 1.7` package. If you have a hard requirement to have more than one, please let me know and we can look into making that work.
**IMPORTANT:** my.arlo.com requires TLS 1.2 for their API. So, if you're getting ssl errors, it's most likely related to your version of openssl. You may need to upgrade your openssl library.
If you're running this library on OSX or macOS, they ship with `openssl v0.9.x` which does not support TLS 1.2. You should follow the instructions found [here](https://comeroutewithme.com/2016/03/13/python-osx-openssl-issue/) to upgrade your openssl library.
---
### Filing an Issue
Please read the [Issue Guidelines and Policies](https://github.com/jeffreydwalter/arlo/wiki/Issue-Guidelines-and-Policies) wiki page **BEFORE** you file an issue. Thanks.
---
## Install
```bash
# Install latest stable package
$ pip install arlo
--or--
# Install from master branch
$ pip install git+https://github.com/jeffreydwalter/arlo
```
---
This just a personal utility that I created out of necessity. It is by no means complete, although it does expose quite a bit of the Arlo interface in an easy to use Python pacakge. As such, this package does not come with unit tests (feel free to add them) or guarantees.
**All [contributions](https://github.com/jeffreydwalter/arlo/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) are welcome and appreciated!**
--
**If you have a specific Arlo device that you want to improve support for, please consider sending me one! Since this project is solely maintained by yours truely and I don't have unlimited funds to support it, I can only really test and debug the code with the first gen Arlo cameras and basestation that I have. I also highly encourage and appreciate Pull Requests!**
**Please, feel free to [contribute](https://github.com/jeffreydwalter/arlo/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) to this repo or buy Jeff a beer!** [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=R77B7UXMLA6ML&lc=US&item_name=Jeff%20Needs%20Beer&item_number=buyjeffabeer&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
---
### Generous Benefactors (Thank you!)
* [apsteinmetz](https://github.com/apsteinmetz) - 🍺
* [mhallikainen](https://github.com/mhallikainen) - 🍺🍺
* [tinsheep](https://github.com/tinsheep) - 🍺🍺
* [cubewot](https://github.com/cubewot) - 🍺🍺
* [imopen](https://github.com/imopen) - 🍺
* [notalifeform](https://github.com/notalifeform) - 🍺🍺
* [anonymous](https://github.com/jeffreydwalter/arlo) - 🍺🍺🍺🍺
* [kewashi](https://github.com/kewashi) - 🍺
---
### Awesomely Smart Contributors (Thank you!)
* [alvin-chang](https://github.com/alvin-chang) - Dec 15, 2019 - Updated some print statements to work with Python 3 in an example script.
* [pabloNZ](https://github.com/pabloNZ) - Jun 4, 2019 - Added the Arlo doorbell, Ultra camera and basestation schemas to the wiki.
* [m3ntalsp00n](https://github.com/m3ntalsp00n) - May 18, 2019 - Expanded ArloQ device support.
* [burken-](https://github.com/burken-) - Apr 17, 2019 - Fixed arming/disarming ArloQ devices.
* [m0urs](https://github.com/m0urs) - Apr 16, 2019 - Updated fqdn to new Arlo domain.
* [kimc78](https://github.com/kimc78) - Aug 16, 2018 - Added method to get CVR recording list.
* [jurgenweber](https://github.com/jurgenweber) - Apr 25, 2018 - Added Arlo Baby APIs!
* [pliablepixels](https://github.com/pliablepixels) - Apr 3, 2018 - Fixed up issues with the README.
* [manluk](https://github.com/manluk) - Mar 2, 2018 - Squashed a couple of bugs.
* [notalifeform](https://github.com/notalifeform) - Feb 10, 2018 - Fixed bug and formatting in example script.
* [erosen](https://github.com/erosen) - Jan 27, 2018 - Added the ArloQ camera schema to the wiki.
* [deanmcguire](https://github.com/deanmcguire) - Dec 7, 2017 - Unravelled the mysteries of RTSP streaming video.
* [andijakl](https://github.com/andijakl) - Jul 24, 2017 - Added Python 3 support and cleaned up examples.
* [cemeyer2](https://github.com/cemeyer2) - Nov 26, 2016 - Fixed setup issues.
* [LenShustek](https://github.com/LenShustek) - Sep 14, 2016, - Added Logout().
If You'd like to make a diffrence in the world and get your name on this most prestegious list, have a look at our [help wanted](https://github.com/jeffreydwalter/arlo/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) section!
After installing all of the required libraries, you can import and use this library like so:
```python
from arlo import Arlo
from datetime import timedelta, date
import datetime
import sys
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
today = (date.today()-timedelta(days=0)).strftime("%Y%m%d")
seven_days_ago = (date.today()-timedelta(days=7)).strftime("%Y%m%d")
# Get all of the recordings for a date range.
library = arlo.GetLibrary(seven_days_ago, today)
# Iterate through the recordings in the library.
for recording in library:
videofilename = datetime.datetime.fromtimestamp(int(recording['name'])//1000).strftime('%Y-%m-%d %H-%M-%S') + ' ' + recording['uniqueId'] + '.mp4'
##
# The videos produced by Arlo are pretty small, even in their longest, best quality settings,
# but you should probably prefer the chunked stream (see below).
###
# # Download the whole video into memory as a single chunk.
# video = arlo.GetRecording(recording['presignedContentUrl'])
# with open('videos/'+videofilename, 'wb') as f:
# f.write(video)
# f.close()
# Or:
#
# Get video as a chunked stream; this function returns a generator.
stream = arlo.StreamRecording(recording['presignedContentUrl'])
with open('videos/'+videofilename, 'wb') as f:
for chunk in stream:
f.write(chunk)
f.close()
print('Downloaded video '+videofilename+' from '+recording['createdDate']+'.')
# Delete all of the videos you just downloaded from the Arlo library.
# Notice that you can pass the "library" object we got back from the GetLibrary() call.
result = arlo.BatchDeleteRecordings(library)
# If we made it here without an exception, then the videos were successfully deleted.
print('Batch deletion of videos completed successfully.')
except Exception as e:
print(e)
```
**For more code examples check out the [wiki](https://github.com/jeffreydwalter/arlo/wiki)**

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
theme: jekyll-theme-architect

View File

@ -0,0 +1,40 @@
import paho.mqtt.client as mqtt
from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
broker="192.168.31.140"
mqttc = mqtt.Client("Hassio_Arlo")
mqttc.username_pw_set("mqtt", "mqtt")
mqttc.connect(broker, 1883)
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
cameras = arlo.GetDevices('camera')
# Define a callback function that will get called once for each motion event.
def callback(arlo, event):
# Here you will have access to self, basestation_id, xcloud_id, and the event schema.
print("motion event detected!")
print(event)
print(arlo)
print("try to send via mqtt")
mqttc.publish("Arlo", "motion event detected!")
#print("try to take snapshot")
#snapshot_url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0])
#arlo.DownloadSnapshot(snapshot_url, 'snapshot.jpg')
#print(basestations)
# Subscribe to motion events. This method blocks until the event stream is closed. (You can close the event stream in the callback if you no longer want to listen for events.)
arlo.SubscribeToMotionEvents(basestations[0], callback)
except Exception as e:
print(e)

View File

@ -0,0 +1,44 @@
from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the cameras.
# This will return an array of cameras, including all of the cameras' associated metadata.
cameras = arlo.GetDevices('camera')
# Trigger the snapshot.
url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0]);
# Download snapshot.
arlo.DownloadSnapshot(url, 'snapshot.jpg')
# If you are already recording, or have a need to snapshot while recording, you can do so like this:
"""
# Starting recording with a camera.
arlo.StartRecording(basestations[0], cameras[0]);
# Wait for 4 seconds while the camera records. (There are probably better ways to do this, but you get the idea.)
time.sleep(4)
# Trigger the snapshot.
url = arlo.TriggerStreamSnapshot(basestations[0], cameras[0]);
# Download snapshot.
arlo.DownloadSnapshot(url, 'snapshot.jpg')
# Stop recording.
arlo.StopRecording(cameras[0]);
"""
except Exception as e:
print(e)

1533
RPI Code/Arlo/arlo.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,914 @@
#!/usr/bin/env python
"""html2text: Turn HTML into equivalent Markdown-structured text."""
__version__ = "3.200.3"
__author__ = "Aaron Swartz (me@aaronsw.com)"
__copyright__ = "(C) 2004-2008 Aaron Swartz. GNU GPL 3."
__contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
# TODO:
# Support decoded entities with unifiable.
try:
True
except NameError:
setattr(__builtins__, 'True', 1)
setattr(__builtins__, 'False', 0)
def has_key(x, y):
if hasattr(x, 'has_key'): return x.has_key(y)
else: return y in x
try:
import htmlentitydefs
import urlparse
import HTMLParser
except ImportError: #Python3
import html.entities as htmlentitydefs
import urllib.parse as urlparse
import html.parser as HTMLParser
try: #Python3
import urllib.request as urllib
except:
import urllib
import optparse, re, sys, codecs, types
try: from textwrap import wrap
except: pass
# Use Unicode characters instead of their ascii psuedo-replacements
UNICODE_SNOB = 0
# Escape all special characters. Output is less readable, but avoids corner case formatting issues.
ESCAPE_SNOB = 0
# Put the links after each paragraph instead of at the end.
LINKS_EACH_PARAGRAPH = 0
# Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
BODY_WIDTH = 78
# Don't show internal links (href="#local-anchor") -- corresponding link targets
# won't be visible in the plain text file anyway.
SKIP_INTERNAL_LINKS = True
# Use inline, rather than reference, formatting for images and links
INLINE_LINKS = True
# Number of pixels Google indents nested lists
GOOGLE_LIST_INDENT = 36
IGNORE_ANCHORS = False
IGNORE_IMAGES = False
IGNORE_EMPHASIS = False
### Entity Nonsense ###
def name2cp(k):
if k == 'apos': return ord("'")
if hasattr(htmlentitydefs, "name2codepoint"): # requires Python 2.3
return htmlentitydefs.name2codepoint[k]
else:
k = htmlentitydefs.entitydefs[k]
if k.startswith("&#") and k.endswith(";"): return int(k[2:-1]) # not in latin-1
return ord(codecs.latin_1_decode(k)[0])
unifiable = {'rsquo':"'", 'lsquo':"'", 'rdquo':'"', 'ldquo':'"',
'copy':'(C)', 'mdash':'--', 'nbsp':' ', 'rarr':'->', 'larr':'<-', 'middot':'*',
'ndash':'-', 'oelig':'oe', 'aelig':'ae',
'agrave':'a', 'aacute':'a', 'acirc':'a', 'atilde':'a', 'auml':'a', 'aring':'a',
'egrave':'e', 'eacute':'e', 'ecirc':'e', 'euml':'e',
'igrave':'i', 'iacute':'i', 'icirc':'i', 'iuml':'i',
'ograve':'o', 'oacute':'o', 'ocirc':'o', 'otilde':'o', 'ouml':'o',
'ugrave':'u', 'uacute':'u', 'ucirc':'u', 'uuml':'u',
'lrm':'', 'rlm':''}
unifiable_n = {}
for k in unifiable.keys():
unifiable_n[name2cp(k)] = unifiable[k]
### End Entity Nonsense ###
def onlywhite(line):
"""Return true if the line does only consist of whitespace characters."""
for c in line:
if c is not ' ' and c is not ' ':
return c is ' '
return line
def hn(tag):
if tag[0] == 'h' and len(tag) == 2:
try:
n = int(tag[1])
if n in range(1, 10): return n
except ValueError: return 0
def dumb_property_dict(style):
"""returns a hash of css attributes"""
return dict([(x.strip(), y.strip()) for x, y in [z.split(':', 1) for z in style.split(';') if ':' in z]]);
def dumb_css_parser(data):
"""returns a hash of css selectors, each of which contains a hash of css attributes"""
# remove @import sentences
data += ';'
importIndex = data.find('@import')
while importIndex != -1:
data = data[0:importIndex] + data[data.find(';', importIndex) + 1:]
importIndex = data.find('@import')
# parse the css. reverted from dictionary compehension in order to support older pythons
elements = [x.split('{') for x in data.split('}') if '{' in x.strip()]
try:
elements = dict([(a.strip(), dumb_property_dict(b)) for a, b in elements])
except ValueError:
elements = {} # not that important
return elements
def element_style(attrs, style_def, parent_style):
"""returns a hash of the 'final' style attributes of the element"""
style = parent_style.copy()
if 'class' in attrs:
for css_class in attrs['class'].split():
css_style = style_def['.' + css_class]
style.update(css_style)
if 'style' in attrs:
immediate_style = dumb_property_dict(attrs['style'])
style.update(immediate_style)
return style
def google_list_style(style):
"""finds out whether this is an ordered or unordered list"""
if 'list-style-type' in style:
list_style = style['list-style-type']
if list_style in ['disc', 'circle', 'square', 'none']:
return 'ul'
return 'ol'
def google_has_height(style):
"""check if the style of the element has the 'height' attribute explicitly defined"""
if 'height' in style:
return True
return False
def google_text_emphasis(style):
"""return a list of all emphasis modifiers of the element"""
emphasis = []
if 'text-decoration' in style:
emphasis.append(style['text-decoration'])
if 'font-style' in style:
emphasis.append(style['font-style'])
if 'font-weight' in style:
emphasis.append(style['font-weight'])
return emphasis
def google_fixed_width_font(style):
"""check if the css of the current element defines a fixed width font"""
font_family = ''
if 'font-family' in style:
font_family = style['font-family']
if 'Courier New' == font_family or 'Consolas' == font_family:
return True
return False
def list_numbering_start(attrs):
"""extract numbering from list element attributes"""
if 'start' in attrs:
return int(attrs['start']) - 1
else:
return 0
class HTML2Text(HTMLParser.HTMLParser):
def __init__(self, out=None, baseurl=''):
HTMLParser.HTMLParser.__init__(self)
# Config options
self.unicode_snob = UNICODE_SNOB
self.escape_snob = ESCAPE_SNOB
self.links_each_paragraph = LINKS_EACH_PARAGRAPH
self.body_width = BODY_WIDTH
self.skip_internal_links = SKIP_INTERNAL_LINKS
self.inline_links = INLINE_LINKS
self.google_list_indent = GOOGLE_LIST_INDENT
self.ignore_links = IGNORE_ANCHORS
self.ignore_images = IGNORE_IMAGES
self.ignore_emphasis = IGNORE_EMPHASIS
self.google_doc = False
self.ul_item_mark = '*'
self.emphasis_mark = '_'
self.strong_mark = '**'
if out is None:
self.out = self.outtextf
else:
self.out = out
self.outtextlist = [] # empty list to store output characters before they are "joined"
try:
self.outtext = unicode()
except NameError: # Python3
self.outtext = str()
self.quiet = 0
self.p_p = 0 # number of newline character to print before next output
self.outcount = 0
self.start = 1
self.space = 0
self.a = []
self.astack = []
self.maybe_automatic_link = None
self.absolute_url_matcher = re.compile(r'^[a-zA-Z+]+://')
self.acount = 0
self.list = []
self.blockquote = 0
self.pre = 0
self.startpre = 0
self.code = False
self.br_toggle = ''
self.lastWasNL = 0
self.lastWasList = False
self.style = 0
self.style_def = {}
self.tag_stack = []
self.emphasis = 0
self.drop_white_space = 0
self.inheader = False
self.abbr_title = None # current abbreviation definition
self.abbr_data = None # last inner HTML (for abbr being defined)
self.abbr_list = {} # stack of abbreviations to write later
self.baseurl = baseurl
try: del unifiable_n[name2cp('nbsp')]
except KeyError: pass
unifiable['nbsp'] = '&nbsp_place_holder;'
def feed(self, data):
data = data.replace("</' + 'script>", "</ignore>")
HTMLParser.HTMLParser.feed(self, data)
def handle(self, data):
self.feed(data)
self.feed("")
return self.optwrap(self.close())
def outtextf(self, s):
self.outtextlist.append(s)
if s: self.lastWasNL = s[-1] == '\n'
def close(self):
HTMLParser.HTMLParser.close(self)
self.pbr()
self.o('', 0, 'end')
self.outtext = self.outtext.join(self.outtextlist)
if self.unicode_snob:
nbsp = unichr(name2cp('nbsp'))
else:
nbsp = u' '
self.outtext = self.outtext.replace(u'&nbsp_place_holder;', nbsp)
return self.outtext
def handle_charref(self, c):
self.o(self.charref(c), 1)
def handle_entityref(self, c):
self.o(self.entityref(c), 1)
def handle_starttag(self, tag, attrs):
self.handle_tag(tag, attrs, 1)
def handle_endtag(self, tag):
self.handle_tag(tag, None, 0)
def previousIndex(self, attrs):
""" returns the index of certain set of attributes (of a link) in the
self.a list
If the set of attributes is not found, returns None
"""
if not has_key(attrs, 'href'): return None
i = -1
for a in self.a:
i += 1
match = 0
if has_key(a, 'href') and a['href'] == attrs['href']:
if has_key(a, 'title') or has_key(attrs, 'title'):
if (has_key(a, 'title') and has_key(attrs, 'title') and
a['title'] == attrs['title']):
match = True
else:
match = True
if match: return i
def drop_last(self, nLetters):
if not self.quiet:
self.outtext = self.outtext[:-nLetters]
def handle_emphasis(self, start, tag_style, parent_style):
"""handles various text emphases"""
tag_emphasis = google_text_emphasis(tag_style)
parent_emphasis = google_text_emphasis(parent_style)
# handle Google's text emphasis
strikethrough = 'line-through' in tag_emphasis and self.hide_strikethrough
bold = 'bold' in tag_emphasis and not 'bold' in parent_emphasis
italic = 'italic' in tag_emphasis and not 'italic' in parent_emphasis
fixed = google_fixed_width_font(tag_style) and not \
google_fixed_width_font(parent_style) and not self.pre
if start:
# crossed-out text must be handled before other attributes
# in order not to output qualifiers unnecessarily
if bold or italic or fixed:
self.emphasis += 1
if strikethrough:
self.quiet += 1
if italic:
self.o(self.emphasis_mark)
self.drop_white_space += 1
if bold:
self.o(self.strong_mark)
self.drop_white_space += 1
if fixed:
self.o('`')
self.drop_white_space += 1
self.code = True
else:
if bold or italic or fixed:
# there must not be whitespace before closing emphasis mark
self.emphasis -= 1
self.space = 0
self.outtext = self.outtext.rstrip()
if fixed:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_last(1)
self.drop_white_space -= 1
else:
self.o('`')
self.code = False
if bold:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_last(2)
self.drop_white_space -= 1
else:
self.o(self.strong_mark)
if italic:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_last(1)
self.drop_white_space -= 1
else:
self.o(self.emphasis_mark)
# space is only allowed after *all* emphasis marks
if (bold or italic) and not self.emphasis:
self.o(" ")
if strikethrough:
self.quiet -= 1
def handle_tag(self, tag, attrs, start):
#attrs = fixattrs(attrs)
if attrs is None:
attrs = {}
else:
attrs = dict(attrs)
if self.google_doc:
# the attrs parameter is empty for a closing tag. in addition, we
# need the attributes of the parent nodes in order to get a
# complete style description for the current element. we assume
# that google docs export well formed html.
parent_style = {}
if start:
if self.tag_stack:
parent_style = self.tag_stack[-1][2]
tag_style = element_style(attrs, self.style_def, parent_style)
self.tag_stack.append((tag, attrs, tag_style))
else:
dummy, attrs, tag_style = self.tag_stack.pop()
if self.tag_stack:
parent_style = self.tag_stack[-1][2]
if hn(tag):
self.p()
if start:
self.inheader = True
self.o(hn(tag)*"#" + ' ')
else:
self.inheader = False
return # prevent redundant emphasis marks on headers
if tag in ['p', 'div']:
if self.google_doc:
if start and google_has_height(tag_style):
self.p()
else:
self.soft_br()
else:
self.p()
if tag == "br" and start: self.o(" \n")
if tag == "hr" and start:
self.p()
self.o("* * *")
self.p()
if tag in ["head", "style", 'script']:
if start: self.quiet += 1
else: self.quiet -= 1
if tag == "style":
if start: self.style += 1
else: self.style -= 1
if tag in ["body"]:
self.quiet = 0 # sites like 9rules.com never close <head>
if tag == "blockquote":
if start:
self.p(); self.o('> ', 0, 1); self.start = 1
self.blockquote += 1
else:
self.blockquote -= 1
self.p()
if tag in ['em', 'i', 'u'] and not self.ignore_emphasis: self.o(self.emphasis_mark)
if tag in ['strong', 'b'] and not self.ignore_emphasis: self.o(self.strong_mark)
if tag in ['del', 'strike', 's']:
if start:
self.o("<"+tag+">")
else:
self.o("</"+tag+">")
if self.google_doc:
if not self.inheader:
# handle some font attributes, but leave headers clean
self.handle_emphasis(start, tag_style, parent_style)
if tag in ["code", "tt"] and not self.pre: self.o('`') #TODO: `` `this` ``
if tag == "abbr":
if start:
self.abbr_title = None
self.abbr_data = ''
if has_key(attrs, 'title'):
self.abbr_title = attrs['title']
else:
if self.abbr_title != None:
self.abbr_list[self.abbr_data] = self.abbr_title
self.abbr_title = None
self.abbr_data = ''
if tag == "a" and not self.ignore_links:
if start:
if has_key(attrs, 'href') and not (self.skip_internal_links and attrs['href'].startswith('#')):
self.astack.append(attrs)
self.maybe_automatic_link = attrs['href']
else:
self.astack.append(None)
else:
if self.astack:
a = self.astack.pop()
if self.maybe_automatic_link:
self.maybe_automatic_link = None
elif a:
if self.inline_links:
self.o("](" + escape_md(a['href']) + ")")
else:
i = self.previousIndex(a)
if i is not None:
a = self.a[i]
else:
self.acount += 1
a['count'] = self.acount
a['outcount'] = self.outcount
self.a.append(a)
self.o("][" + str(a['count']) + "]")
if tag == "img" and start and not self.ignore_images:
if has_key(attrs, 'src'):
attrs['href'] = attrs['src']
alt = attrs.get('alt', '')
self.o("![" + escape_md(alt) + "]")
if self.inline_links:
self.o("(" + escape_md(attrs['href']) + ")")
else:
i = self.previousIndex(attrs)
if i is not None:
attrs = self.a[i]
else:
self.acount += 1
attrs['count'] = self.acount
attrs['outcount'] = self.outcount
self.a.append(attrs)
self.o("[" + str(attrs['count']) + "]")
if tag == 'dl' and start: self.p()
if tag == 'dt' and not start: self.pbr()
if tag == 'dd' and start: self.o(' ')
if tag == 'dd' and not start: self.pbr()
if tag in ["ol", "ul"]:
# Google Docs create sub lists as top level lists
if (not self.list) and (not self.lastWasList):
self.p()
if start:
if self.google_doc:
list_style = google_list_style(tag_style)
else:
list_style = tag
numbering_start = list_numbering_start(attrs)
self.list.append({'name':list_style, 'num':numbering_start})
else:
if self.list: self.list.pop()
self.lastWasList = True
else:
self.lastWasList = False
if tag == 'li':
self.pbr()
if start:
if self.list: li = self.list[-1]
else: li = {'name':'ul', 'num':0}
if self.google_doc:
nest_count = self.google_nest_count(tag_style)
else:
nest_count = len(self.list)
self.o(" " * nest_count) #TODO: line up <ol><li>s > 9 correctly.
if li['name'] == "ul": self.o(self.ul_item_mark + " ")
elif li['name'] == "ol":
li['num'] += 1
self.o(str(li['num'])+". ")
self.start = 1
if tag in ["table", "tr"] and start: self.p()
if tag == 'td': self.pbr()
if tag == "pre":
if start:
self.startpre = 1
self.pre = 1
else:
self.pre = 0
self.p()
def pbr(self):
if self.p_p == 0:
self.p_p = 1
def p(self):
self.p_p = 2
def soft_br(self):
self.pbr()
self.br_toggle = ' '
def o(self, data, puredata=0, force=0):
if self.abbr_data is not None:
self.abbr_data += data
if not self.quiet:
if self.google_doc:
# prevent white space immediately after 'begin emphasis' marks ('**' and '_')
lstripped_data = data.lstrip()
if self.drop_white_space and not (self.pre or self.code):
data = lstripped_data
if lstripped_data != '':
self.drop_white_space = 0
if puredata and not self.pre:
data = re.sub('\s+', ' ', data)
if data and data[0] == ' ':
self.space = 1
data = data[1:]
if not data and not force: return
if self.startpre:
#self.out(" :") #TODO: not output when already one there
if not data.startswith("\n"): # <pre>stuff...
data = "\n" + data
bq = (">" * self.blockquote)
if not (force and data and data[0] == ">") and self.blockquote: bq += " "
if self.pre:
if not self.list:
bq += " "
#else: list content is already partially indented
for i in xrange(len(self.list)):
bq += " "
data = data.replace("\n", "\n"+bq)
if self.startpre:
self.startpre = 0
if self.list:
data = data.lstrip("\n") # use existing initial indentation
if self.start:
self.space = 0
self.p_p = 0
self.start = 0
if force == 'end':
# It's the end.
self.p_p = 0
self.out("\n")
self.space = 0
if self.p_p:
self.out((self.br_toggle+'\n'+bq)*self.p_p)
self.space = 0
self.br_toggle = ''
if self.space:
if not self.lastWasNL: self.out(' ')
self.space = 0
if self.a and ((self.p_p == 2 and self.links_each_paragraph) or force == "end"):
if force == "end": self.out("\n")
newa = []
for link in self.a:
if self.outcount > link['outcount']:
self.out(" ["+ str(link['count']) +"]: " + urlparse.urljoin(self.baseurl, link['href']))
if has_key(link, 'title'): self.out(" ("+link['title']+")")
self.out("\n")
else:
newa.append(link)
if self.a != newa: self.out("\n") # Don't need an extra line when nothing was done.
self.a = newa
if self.abbr_list and force == "end":
for abbr, definition in self.abbr_list.items():
self.out(" *[" + abbr + "]: " + definition + "\n")
self.p_p = 0
self.out(data)
self.outcount += 1
def handle_data(self, data):
if r'\/script>' in data: self.quiet -= 1
if self.style:
self.style_def.update(dumb_css_parser(data))
if not self.maybe_automatic_link is None:
href = self.maybe_automatic_link
if href == data and self.absolute_url_matcher.match(href):
self.o("<" + data + ">")
return
else:
self.o("[")
self.maybe_automatic_link = None
if not self.code and not self.pre:
data = escape_md_section(data, snob=self.escape_snob)
self.o(data, 1)
def unknown_decl(self, data): pass
def charref(self, name):
if name[0] in ['x','X']:
c = int(name[1:], 16)
else:
c = int(name)
if not self.unicode_snob and c in unifiable_n.keys():
return unifiable_n[c]
else:
try:
return unichr(c)
except NameError: #Python3
return chr(c)
def entityref(self, c):
if not self.unicode_snob and c in unifiable.keys():
return unifiable[c]
else:
try: name2cp(c)
except KeyError: return "&" + c + ';'
else:
try:
return unichr(name2cp(c))
except NameError: #Python3
return chr(name2cp(c))
def replaceEntities(self, s):
s = s.group(1)
if s[0] == "#":
return self.charref(s[1:])
else: return self.entityref(s)
r_unescape = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
def unescape(self, s):
return self.r_unescape.sub(self.replaceEntities, s)
def google_nest_count(self, style):
"""calculate the nesting count of google doc lists"""
nest_count = 0
if 'margin-left' in style:
nest_count = int(style['margin-left'][:-2]) / self.google_list_indent
return nest_count
def optwrap(self, text):
"""Wrap all paragraphs in the provided text."""
if not self.body_width:
return text
assert wrap, "Requires Python 2.3."
result = ''
newlines = 0
for para in text.split("\n"):
if len(para) > 0:
if not skipwrap(para):
result += "\n".join(wrap(para, self.body_width))
if para.endswith(' '):
result += " \n"
newlines = 1
else:
result += "\n\n"
newlines = 2
else:
if not onlywhite(para):
result += para + "\n"
newlines = 1
else:
if newlines < 2:
result += "\n"
newlines += 1
return result
ordered_list_matcher = re.compile(r'\d+\.\s')
unordered_list_matcher = re.compile(r'[-\*\+]\s')
md_chars_matcher = re.compile(r"([\\\[\]\(\)])")
md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!])")
md_dot_matcher = re.compile(r"""
^ # start of line
(\s*\d+) # optional whitespace and a number
(\.) # dot
(?=\s) # lookahead assert whitespace
""", re.MULTILINE | re.VERBOSE)
md_plus_matcher = re.compile(r"""
^
(\s*)
(\+)
(?=\s)
""", flags=re.MULTILINE | re.VERBOSE)
md_dash_matcher = re.compile(r"""
^
(\s*)
(-)
(?=\s|\-) # followed by whitespace (bullet list, or spaced out hr)
# or another dash (header or hr)
""", flags=re.MULTILINE | re.VERBOSE)
slash_chars = r'\`*_{}[]()#+-.!'
md_backslash_matcher = re.compile(r'''
(\\) # match one slash
(?=[%s]) # followed by a char that requires escaping
''' % re.escape(slash_chars),
flags=re.VERBOSE)
def skipwrap(para):
# If the text begins with four spaces or one tab, it's a code block; don't wrap
if para[0:4] == ' ' or para[0] == '\t':
return True
# If the text begins with only two "--", possibly preceded by whitespace, that's
# an emdash; so wrap.
stripped = para.lstrip()
if stripped[0:2] == "--" and len(stripped) > 2 and stripped[2] != "-":
return False
# I'm not sure what this is for; I thought it was to detect lists, but there's
# a <br>-inside-<span> case in one of the tests that also depends upon it.
if stripped[0:1] == '-' or stripped[0:1] == '*':
return True
# If the text begins with a single -, *, or +, followed by a space, or an integer,
# followed by a ., followed by a space (in either case optionally preceeded by
# whitespace), it's a list; don't wrap.
if ordered_list_matcher.match(stripped) or unordered_list_matcher.match(stripped):
return True
return False
def wrapwrite(text):
text = text.encode('utf-8')
try: #Python3
sys.stdout.buffer.write(text)
except AttributeError:
sys.stdout.write(text)
def html2text(html, baseurl=''):
h = HTML2Text(baseurl=baseurl)
return h.handle(html)
def unescape(s, unicode_snob=False):
h = HTML2Text()
h.unicode_snob = unicode_snob
return h.unescape(s)
def escape_md(text):
"""Escapes markdown-sensitive characters within other markdown constructs."""
return md_chars_matcher.sub(r"\\\1", text)
def escape_md_section(text, snob=False):
"""Escapes markdown-sensitive characters across whole document sections."""
text = md_backslash_matcher.sub(r"\\\1", text)
if snob:
text = md_chars_matcher_all.sub(r"\\\1", text)
text = md_dot_matcher.sub(r"\1\\\2", text)
text = md_plus_matcher.sub(r"\1\\\2", text)
text = md_dash_matcher.sub(r"\1\\\2", text)
return text
def main():
baseurl = ''
p = optparse.OptionParser('%prog [(filename|url) [encoding]]',
version='%prog ' + __version__)
p.add_option("--ignore-emphasis", dest="ignore_emphasis", action="store_true",
default=IGNORE_EMPHASIS, help="don't include any formatting for emphasis")
p.add_option("--ignore-links", dest="ignore_links", action="store_true",
default=IGNORE_ANCHORS, help="don't include any formatting for links")
p.add_option("--ignore-images", dest="ignore_images", action="store_true",
default=IGNORE_IMAGES, help="don't include any formatting for images")
p.add_option("-g", "--google-doc", action="store_true", dest="google_doc",
default=False, help="convert an html-exported Google Document")
p.add_option("-d", "--dash-unordered-list", action="store_true", dest="ul_style_dash",
default=False, help="use a dash rather than a star for unordered list items")
p.add_option("-e", "--asterisk-emphasis", action="store_true", dest="em_style_asterisk",
default=False, help="use an asterisk rather than an underscore for emphasized text")
p.add_option("-b", "--body-width", dest="body_width", action="store", type="int",
default=BODY_WIDTH, help="number of characters per output line, 0 for no wrap")
p.add_option("-i", "--google-list-indent", dest="list_indent", action="store", type="int",
default=GOOGLE_LIST_INDENT, help="number of pixels Google indents nested lists")
p.add_option("-s", "--hide-strikethrough", action="store_true", dest="hide_strikethrough",
default=False, help="hide strike-through text. only relevant when -g is specified as well")
p.add_option("--escape-all", action="store_true", dest="escape_snob",
default=False, help="Escape all special characters. Output is less readable, but avoids corner case formatting issues.")
(options, args) = p.parse_args()
# process input
encoding = "utf-8"
if len(args) > 0:
file_ = args[0]
if len(args) == 2:
encoding = args[1]
if len(args) > 2:
p.error('Too many arguments')
if file_.startswith('http://') or file_.startswith('https://'):
baseurl = file_
j = urllib.urlopen(baseurl)
data = j.read()
if encoding is None:
try:
from feedparser import _getCharacterEncoding as enc
except ImportError:
enc = lambda x, y: ('utf-8', 1)
encoding = enc(j.headers, data)[0]
if encoding == 'us-ascii':
encoding = 'utf-8'
else:
data = open(file_, 'rb').read()
if encoding is None:
try:
from chardet import detect
except ImportError:
detect = lambda x: {'encoding': 'utf-8'}
encoding = detect(data)['encoding']
else:
data = sys.stdin.read()
data = data.decode(encoding)
h = HTML2Text(baseurl=baseurl)
# handle options
if options.ul_style_dash: h.ul_item_mark = '-'
if options.em_style_asterisk:
h.emphasis_mark = '*'
h.strong_mark = '__'
h.body_width = options.body_width
h.list_indent = options.list_indent
h.ignore_emphasis = options.ignore_emphasis
h.ignore_links = options.ignore_links
h.ignore_images = options.ignore_images
h.google_doc = options.google_doc
h.hide_strikethrough = options.hide_strikethrough
h.escape_snob = options.escape_snob
wrapwrite(h.handle(data))
if __name__ == "__main__":
main()

23
RPI Code/Arlo/dev/rev.py Normal file
View File

@ -0,0 +1,23 @@
import fileinput
import os
import re
import sys
if(len(sys.argv) != 2 or not os.path.isfile(sys.argv[1])):
print("Usage: {0} <path to setup.py>".format(os.path.basename(sys.argv[0])))
sys.exit(1)
pattern = re.compile("\s*version='([0-9.]+)',")
line = ""
maj = ""
min = ""
ver = ""
for line in fileinput.FileInput(sys.argv[1], inplace=1):
m = pattern.match(line)
if m:
version = m.groups()[0]
maj, min, rev = version.split('.')
line = line.replace(version, "{0}.{1}.{2}".format(maj, min, int(rev)+1))
sys.stdout.write(line)

1160
RPI Code/Arlo/docs/README.md Normal file

File diff suppressed because it is too large Load Diff

1690
RPI Code/Arlo/docs/arlo.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
##
# Copyright 2016 Jeffrey D. Walter
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import monotonic
import sseclient
import threading
import sys
if sys.version[0] == '2':
import Queue as queue
else:
import queue as queue
# TODO: There's a lot more refactoring that could/should be done to abstract out the arlo-specific implementation details.
class EventStream(object):
"""This class provides a queue-based EventStream object."""
def __init__(self, event_handler, heartbeat_handler, args):
self.connected = False
self.registered = False
self.queue = queue.Queue()
self.heartbeat_stop_event = threading.Event()
self.event_stream_stop_event = threading.Event()
self.arlo = args[0]
self.heartbeat_handler = heartbeat_handler
try:
event_stream = sseclient.SSEClient('https://my.arlo.com/hmsweb/client/subscribe?token='+self.arlo.request.session.headers.get('Authorization'), session=self.arlo.request.session)
self.event_stream_thread = threading.Thread(name="EventStream", target=event_handler, args=(self.arlo, event_stream, self.event_stream_stop_event, ))
self.event_stream_thread.setDaemon(True)
print(self.arlo.request)
print(self.arlo)
except Exception as e:
raise Exception('Failed to subscribe to eventstream: {0}'.format(e))
def __del__(self):
self.Disconnect()
def Get(self, block=True, timeout=None):
if sys.version[0] == '2' and block:
if timeout:
timeout += monotonic.monotonic()
# If timeout is None, then just pick some arbitrarily large # for the timeout value.
else:
timeout = 1000000 + monotonic.monotonic()
while True:
try:
# Allow check for Ctrl-C every second
item = self.queue.get(timeout=min(1, timeout - monotonic.monotonic()))
self.queue.task_done()
return item
except queue.Empty:
if monotonic.monotonic() > timeout:
return None
else:
pass
else:
try:
item = self.queue.get(block=block, timeout=timeout)
self.queue.task_done()
return item
except queue.Empty as e:
return None
except Exception as e:
return None
def Start(self):
self.event_stream_thread.start()
return self
def Connect(self):
self.connected = True
def Disconnect(self):
self.connected = False
self.Unregister()
def Register(self):
self.heartbeat_thread = threading.Thread(name='HeartbeatThread', target=self.heartbeat_handler, args=(self.arlo, self.heartbeat_stop_event, ))
self.heartbeat_thread.setDaemon(True)
self.heartbeat_thread.start()
self.registered = True
def Unregister(self):
self.registered = False
if self.queue:
self.queue.put(None)
self.event_stream_stop_event.set()
self.heartbeat_stop_event.set()
if self.event_stream_thread != threading.current_thread():
self.event_stream_thread.join()
if self.heartbeat_thread != threading.current_thread():
self.heartbeat_thread.join()

View File

@ -0,0 +1,36 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Set camera brightness to 0%.
#arlo.AdjustBrightness(basestations[0], cameras[0] -2)
# Set camera brightness to 25%.
#arlo.AdjustBrightness(basestations[0], cameras[0], -1)
# Set camera brightness to 50%.
arlo.AdjustBrightness(basestations[0], cameras[0], 0)
# Set camera brightness to 75%.
#arlo.AdjustBrightness(basestations[0], cameras[0], 1)
# Set camera brightness to 100%.
#arlo.AdjustBrightness(basestations[0], cameras[0], 2)
except Exception as e:
print(e)

View File

@ -0,0 +1,88 @@
import sys, os
sys.path.append('..')
import requests
from arlo import Arlo
from datetime import timedelta, date
import datetime
import json
import re
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
videopath = 'videos'
cameraNumber = 2
datetimeFrom = datetime.datetime.strptime('2018-08-11 03:00:00', '%Y-%m-%d %H:%M:%S');
datetimeTo = datetime.datetime.strptime('2018-08-11 04:00:00', '%Y-%m-%d %H:%M:%S');
try:
print("Downloading cvr videos from " + datetimeFrom.strftime("%Y-%m-%d %H:%M:%S") + " to " + datetimeTo.strftime("%Y-%m-%d %H:%M:%S"))
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Get all of the recordings for a date range.
playlist = arlo.GetCvrPlaylist(cameras[cameraNumber], datetimeFrom.strftime("%Y%m%d"), datetimeTo.strftime("%Y%m%d"))
# If no recordings are available exit
if not playlist['playlist']:
print("No playlist found for camera for the period " + datetimeFrom.strftime("%Y-%m-%d %H:%M:%S") + " and " + datetimeTo.strftime("%Y-%m-%d %H:%M:%S"))
arlo.Logout()
print('Logged out')
sys.exit()
# Check if videos folder already exists
if not os.path.exists(videopath):
os.makedirs(videopath)
# debug to show the playlist json
# print(json.dumps(playlist, indent = 4))
# Iterate through each day in the cvr playlist.
for playlistPerDay in playlist['playlist']:
# Iterate through each m3u8 (playlist) file
for recordings in playlist['playlist'][playlistPerDay]:
m3u8 = requests.get(recordings['url']).text.split("\n")
# Iterate the m3u8 file and get all the streams
for m3u8Line in m3u8:
# debug to show the m3u8 file
# print m3u8Line
# Split the url into parts used for filename (camera id and timestamp)
m = re.match("^http.+([A-Z0-9]{13})_[0-9]{13}_([0-9]{13})", m3u8Line)
if m:
cameraId = m.group(1)
videoTime = datetime.datetime.fromtimestamp(int(m.group(2)) // 1000)
# If we are within desired range, then download
if videoTime > datetimeFrom and videoTime < datetimeTo:
# Get video as a chunked stream; this function returns a generator.
stream = arlo.StreamRecording(m3u8Line)
videofilename = cameraId + '-' + videoTime.strftime('%Y%m%d-%H%M%S') + '.mp4'
# Skip files already downloaded
if os.path.isfile(videopath + '/' + videofilename):
print("Video " + videofilename + " already exists")
else:
print('Downloading video ' + videofilename)
with open(videopath + '/' + videofilename, 'wb') as f:
for chunk in stream:
f.write(chunk)
f.close()
arlo.Logout()
print('Logged out')
except Exception as e:
print(e)

View File

@ -0,0 +1,75 @@
from arlo import Arlo
from datetime import timedelta, date
import datetime
import sys, os
sys.path.append('..')
#import json
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
videopath = 'videos'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the cameras.
# This will return an array which includes all of the canera's associated metadata
# and includes Arlo Q devices, re: https://github.com/jeffreydwalter/arlo/wiki/FAQ#frequently-asked-questions
cameras = arlo.GetDevices('camera')
arloq = arlo.GetDevices('arloq')
arloqs = arlo.GetDevices('arloqs')
cameras = cameras + arloq + arloqs
cameras_by_id = {}
# setup a hash where each camera deviceId is assocaited with its name for lookup later
for camera in cameras:
cameras_by_id[camera['deviceId']] = camera['deviceName']
today = (date.today() - timedelta(days=0)).strftime("%Y%m%d")
seven_days_ago = (date.today() - timedelta(days=7)).strftime("%Y%m%d")
# Get all of the recordings for a date range.
library = arlo.GetLibrary(seven_days_ago, today)
# Check if videos folder already exists
if not os.path.exists(videopath):
os.makedirs(videopath)
# Iterate through the recordings in the library.
for recording in library:
# Set the extension based on the content type of the returned media
content_type = recording['contentType']
extension = '.jpg' if content_type == 'image/jpg' else '.mp4'
# Grab the camera name to use for the filename from the cameras_by_id hash above
camera_name = cameras_by_id[recording['deviceId']]
videofilename = camera_name + ' - ' + datetime.datetime.fromtimestamp(int(recording['name']) // 1000).strftime('%Y-%m-%d %H-%M-%S') + extension
# Download the video and write it to the given path.
arlo.DownloadRecording(recording['presignedContentUrl'], videopath + '/' + videofilename)
print('Downloaded video ' + videofilename + ' from ' + recording['createdDate'] + '.')
# Use the following line to print all the data we got for the recording.
#print(json.dumps(recording, indent = 4))
# Delete all of the videos you just downloaded from the Arlo library.
# Notice that you can pass the "library" object we got back from the GetLibrary() call.
#result = arlo.BatchDeleteRecordings(library)
# If we made it here without an exception, then the videos were successfully deleted.
#print ('Batch deletion of videos completed successfully.')
arlo.Logout()
print('Logged out')
except Exception as e:
print(e)

View File

@ -0,0 +1,59 @@
from arlo import Arlo
from datetime import timedelta, date
import datetime
import sys, os
sys.path.append('..')
#import json
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
videopath = 'videos'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
today = (date.today() - timedelta(days=0)).strftime("%Y%m%d")
seven_days_ago = (date.today() - timedelta(days=7)).strftime("%Y%m%d")
# Get all of the recordings for a date range.
library = arlo.GetLibrary(seven_days_ago, today)
# Check if videos folder already exists
if not os.path.exists(videopath):
os.makedirs(videopath)
# Iterate through the recordings in the library.
for recording in library:
# Get video as a chunked stream; this function returns a generator.
stream = arlo.StreamRecording(recording['presignedContentUrl'])
videofilename = datetime.datetime.fromtimestamp(
int(recording['name']) // 1000).strftime(
'%Y-%m-%d %H-%M-%S') + ' ' + recording['uniqueId'] + '.mp4'
with open(videopath + '/' + videofilename, 'wb') as f:
for chunk in stream:
f.write(chunk)
f.close()
print('Downloaded video ' + videofilename + ' from ' +
recording['createdDate'] + '.')
# Use the following line to print all the data we got for the recording.
#print(json.dumps(recording, indent = 4))
# Delete all of the videos you just downloaded from the Arlo library.
# Notice that you can pass the "library" object we got back from the GetLibrary() call.
#result = arlo.BatchDeleteRecordings(library)
# If we made it here without an exception, then the videos were successfully deleted.
#print ('Batch deletion of videos completed successfully.')
arlo.Logout()
print('Logged out')
except Exception as e:
print(e)

View File

@ -0,0 +1,31 @@
from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Tells the Arlo basestation to trigger a snapshot on the given camera.
# This snapshot is not instantaneous, so this method waits for the response and returns the url
# for the snapshot, which is stored on the Amazon AWS servers.
snapshot_url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0])
# This method requests the snapshot for the given url and writes the image data to the location specified.
# In this case, to the current directory as a file named "snapshot.jpg"
# Note: Snapshots are in .jpg format.
arlo.DownloadSnapshot(snapshot_url, 'snapshot.jpg')
except Exception as e:
print(e)

View File

@ -0,0 +1,32 @@
from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
cameras = arlo.GetDevices('camera')
# Define a callback function that will get called once for each motion event.
def callback(arlo, event):
# Here you will have access to self, basestation_id, xcloud_id, and the event schema.
print("motion event detected!")
print(event)
print(arlo)
#print("try to take snapshot")
#snapshot_url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0])
#arlo.DownloadSnapshot(snapshot_url, 'snapshot.jpg')
#print(basestations)
# Subscribe to motion events. This method blocks until the event stream is closed. (You can close the event stream in the callback if you no longer want to listen for events.)
arlo.SubscribeToMotionEvents(basestations[0], callback)
except Exception as e:
print(e)

View File

@ -0,0 +1,31 @@
cameras = arlo.GetDevices('camera')from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Define a callback function that will get called once for each motion event.
def callback(arlo, event):
# Here you will have access to self, basestation_id, xcloud_id, and the event schema.
print("motion event detected!")
print(event)
print(arlo)
snapshot_url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0])
arlo.DownloadSnapshot(snapshot_url, 'snapshot.jpg')
#print(basestations)
# Subscribe to motion events. This method blocks until the event stream is closed. (You can close the event stream in the callback if you no longer want to listen for events.)
arlo.SubscribeToMotionEvents(basestations[0], callback)
except Exception as e:
print(e)

View File

@ -0,0 +1,29 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Arm Arlo.
arlo.Arm(basestations[0])
# Or
# Disarm Arlo.
# arlo.Disarm(basestations[0])
# Or
# Change Mode to some custom mode you created.
# arlo.CustomMode(basestations[0], "mode3") # 'mode3' is the id of a custom mode you created.
# Or
# Change Mode to Schedule.
# arlo.CustomMode(basestations[0], mode=None, schedules=['schedules.1']) # 'schedules.1' is the id of my default schedule."
except Exception as e:
print(e)

View File

@ -0,0 +1,44 @@
from arlo import Arlo
USERNAME = 'fransolet.thomas@gmail.com'
PASSWORD = 'Coconuts09'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the cameras.
# This will return an array of cameras, including all of the cameras' associated metadata.
cameras = arlo.GetDevices('camera')
# Trigger the snapshot.
url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[0]);
# Download snapshot.
arlo.DownloadSnapshot(url, 'snapshot.jpg')
# If you are already recording, or have a need to snapshot while recording, you can do so like this:
"""
# Starting recording with a camera.
arlo.StartRecording(basestations[0], cameras[0]);
# Wait for 4 seconds while the camera records. (There are probably better ways to do this, but you get the idea.)
time.sleep(4)
# Trigger the snapshot.
url = arlo.TriggerStreamSnapshot(basestations[0], cameras[0]);
# Download snapshot.
arlo.DownloadSnapshot(url, 'snapshot.jpg')
# Stop recording.
arlo.StopRecording(cameras[0]);
"""
except Exception as e:
print(e)

View File

@ -0,0 +1,39 @@
from arlo import Arlo
from subprocess import call
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(),
# which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the cameras.
# This will return an array which includes all of the canera's associated metadata.
cameras = arlo.GetDevices('camera')
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Send the command to start the stream and return the stream url.
url = arlo.StartStream(basestations[0], cameras[0])
# Record the stream to a file named 'test.mp4'.
# **Requires ffmpeg 3.4 or greater.**
# For this example, I'm going to open ffmpeg.
# Crucially important is the '-t' flag, which specifies a recording time. (See the ffmpeg documentation.)
# This is just a crude example, but hopefully it will give you some ideas.
# You can use any number of libraries to do the actual streaming. OpenCV or VLC are both good choices.
# NOTE: This will print the output of ffmpeg to STDOUT/STDERR. If you don't want that, you will
# need to pass additional arguments to handle those streams.
call(['ffmpeg', '-re', '-i', url, '-t', '10', '-acodec', 'copy', '-vcodec', 'copy', 'test.mp4'])
except Exception as e:
print(e)

View File

@ -0,0 +1,26 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestations.
# This will return an array of basestations, including all of the basestations' associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the cameras.
# This will return an array of cameras, including all of the cameras' associated metadata.
cameras = arlo.GetDevices('camera')
# Turn camera on.
print(arlo.ToggleCamera(basestations[0], cameras[0], True))
# Turn camera off.
#print(arlo.ToggleCamera(basestations[0], cameras[0], False))
except Exception as e:
print(e)

View File

@ -0,0 +1,39 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Get current state payload
arlo.GetAudioPlayback(cameras[0])
# Start playing
arlo.PlayTrack(cameras[0], track_id, position)
# Pause the track
arlo.PauseTrack(cameras[0])
# Skip the track
arlo.SkipTrack(cameras[0])
# Set the sleep timer
arlo.SetSleepTimerOn(cameras[0])
# Set the playback mode ot continuous
arlo.SetLoopBackModeContinuous(cameras[0])
except Exception as e:
print(e)

View File

@ -0,0 +1,34 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Get current state payload
state=arlo.GetCameraState(cameras[0])
print(state["properties"][0]["nightLight"])
# night light on
arlo.SetNightLightOn(cameras[0])
# night light timer on
arlo.SetNightLightTimerOn(cameras[0], 500)
# night light color mode
arlo.SetNightLightMode(cameras[0], mode={"blue":255,"green":255,"red":255 }) # or mode="rainbow"
except Exception as e:
print(e)

View File

@ -0,0 +1,36 @@
from arlo import Arlo
USERNAME = 'user@example.com'
PASSWORD = 'supersecretpassword'
try:
# Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
# Subsequent successful calls to login will update the oAuth token.
arlo = Arlo(USERNAME, PASSWORD)
# At this point you're logged into Arlo.
# Get the list of devices and filter on device type to only get the basestation.
# This will return an array which includes all of the basestation's associated metadata.
basestations = arlo.GetDevices('basestation')
# Get the list of devices and filter on device type to only get the camera.
# This will return an array which includes all of the camera's associated metadata.
cameras = arlo.GetDevices('camera')
# Turn temperature alerts on
arlo.TempAlertOn(cameras[0])
# Alert min threshold (so if temp falls below this number it alerts)
arlo.SetTempAlertThresholdMin(cameras[0], 170)
# Alert max threshold (so if temp go above this number it alerts)
arlo.SetTempAlertThresholdMax(cameras[0], 270)
# record temperature history
arlo.TempRecordingOn(cameras[0])
# Set the temperature unit to Celcius
arlo.SetTempUnit(cameras[0]["uniqueId"], "C")
except Exception as e:
print(e)

BIN
RPI Code/Arlo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

55
RPI Code/Arlo/request.py Normal file
View File

@ -0,0 +1,55 @@
##
# Copyright 2016 Jeffrey D. Walter
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import requests
from requests.exceptions import HTTPError
class Request(object):
"""HTTP helper class"""
def __init__(self):
self.session = requests.Session()
def _request(self, url, method='GET', params={}, headers={}, stream=False, raw=False):
if method == 'GET':
r = self.session.get(url, params=params, headers=headers, stream=stream)
if stream is True:
return r
elif method == 'PUT':
r = self.session.put(url, json=params, headers=headers)
elif method == 'POST':
r = self.session.post(url, json=params, headers=headers)
r.raise_for_status()
body = r.json()
if raw:
return body
else:
if body['success'] == True:
if 'data' in body:
return body['data']
else:
raise HTTPError('Request ({0} {1}) failed: {2}'.format(method, url, r.json()), response=r)
def get(self, url, params={}, headers={}, stream=False, raw=False):
return self._request(url, 'GET', params, headers, stream, raw)
def put(self, url, params={}, headers={}, raw=False):
return self._request(url, 'PUT', params, headers, raw)
def post(self, url, params={}, headers={}, raw=False):
return self._request(url, 'POST', params, headers, raw)

View File

@ -0,0 +1,15 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile
#
certifi==2019.9.11 # via requests
chardet==3.0.4 # via requests
idna==2.8 # via requests
monotonic==1.5
pysocks==1.7.1
requests==2.22.0
six==1.12.0 # via sseclient
sseclient==0.0.22
urllib3==1.25.6 # via requests

5
RPI Code/Arlo/setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[bdist_wheel]
universal=1
[metadata]
description-file = README.md

44
RPI Code/Arlo/setup.py Normal file
View File

@ -0,0 +1,44 @@
# coding=utf-8
"""Python Arlo setup script."""
from setuptools import setup
def readme():
with open('README.md') as desc:
return desc.read()
setup(
name='arlo',
py_modules=['arlo', 'request', 'eventstream'],
version='1.2.33',
description='Python Arlo is a library written in Python 2.7/3x ' +
'which exposes the Netgear Arlo cameras via the apis that are consumed by their website.',
long_description=readme(),
long_description_content_type='text/markdown',
author='Jeffrey D. Walter',
author_email='jeffreydwalter@gmail.com',
url='https://github.com/jeffreydwalter/arlo',
license='Apache Software License',
include_package_data=True,
install_requires=['monotonic', 'requests', 'sseclient==0.0.22', 'PySocks'],
keywords=[
'arlo',
'camera',
'home automation',
'netgear',
'python',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development :: Libraries :: Python Modules'
],
)

BIN
RPI Code/Arlo/snapshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import Arlo

View File

@ -7,12 +7,12 @@ from ctypes import c_ubyte
import paho.mqtt.client as mqtt
import socket
broker="192.168.31.118"
#username="oilkfgjy"
#password="lEyZb90q49Rf"
broker="192.168.31.140"
username="mqtt"
password="mqtt"
mqttc = mqtt.Client("SmartGarden_PiZero")
#mqttc.username_pw_set(username, password)
mqttc = mqtt.Client("SmartGarden_PiZero1")
mqttc.username_pw_set(username, password)
delayBetweenSending = 300