Skip to main content

A Python library to communicate with Logi Circle cameras

Project description

Python Logi Circle API

Python 3.6+ API for interacting with Logi Circle cameras, written with asyncio and aiohttp.

PyPI version License Build Status Coverage Status Open Issues

This library exposes the Logi Circle family of cameras as Python objects. The goal is to expose most of the functionality from Logi's 1st party applications, allowing integration of those features into other projects.

Note that the API this project is based on is not open, and therefore could change/break at any time.

Installation

Installing release version

$ pip install logi-circle

Installing development master

$ pip install \
    git+https://github.com/evanjd/python-logi-circle

Features available

  • Download real-time live stream data to disk or serve to your application as a raw bytes object
  • Query/filter the activity history by start time and/or activity properties (duration, relevance)
  • Download any activity video to disk or serve to your application as a raw bytes object
  • Download still images from camera to disk or serve to your application as a raw bytes object
  • Set streaming mode, privacy mode, LED status, speaker volume, microphone gain and other properties of camera
  • On-demand polling from server to update camera properties
  • Camera properties exposed:
    • ID
    • Node ID
    • Name
    • Live image (as JPEG)
    • Last activity
    • Timezone
    • Connected status (is it powered and in range)
    • Streaming status (is currently streaming and capable of recording activities)
    • Privacy mode (is it recording activities)
    • Firmware version
    • Battery %
    • Charging status
    • Model
    • Model generation (eg. 1st gen, 2nd gen)
    • Mount (eg. Wired, Wireless)
    • Connected Wifi SSID
    • Signal strength %
    • IP address
    • MAC address
    • Microphone status and gain
    • Speaker status and volume
    • LED enabled
    • Plan name
    • Temperature (if supported by your device)
    • Relative humidity % (if supported by your device)
  • Activity properties exposed:
    • Start time (local or UTC)
    • End time (local or UTC)
    • Duration
    • Relevance level (indicating whether people/objects were detected)

Features planned

  • Motion alerts (eventually)
  • Logi Circle CLI (eventually)
  • Speaker support (maybe)

Usage example

Setup and authenticate:

import asyncio
from logi_circle import Logi

logi_api = Logi('my@email.com', 'my-password')

Grab latest still image for each camera:

async def get_snapshot_images():
    for camera in await logi_api.cameras:
        await camera.get_snapshot_image('%s.jpg' % (camera.name))
    await logi_api.logout()

loop = asyncio.get_event_loop()
loop.run_until_complete(get_snapshot_images())
loop.close()

Download latest activity for all cameras:

async def get_latest_activity():
    for camera in await logi_api.cameras:
        last_activity = await camera.last_activity
        await last_activity.download('%s-last-activity.mp4' % (camera.name))
    await logi_api.logout()

loop = asyncio.get_event_loop()
loop.run_until_complete(get_latest_activity())
loop.close()

Stream live stream data to disk:

async def get_livestream():
    camera = (await logi_api.cameras)[0]
    filename = '%s-livestream.mp4' % (camera.name)

    # Grab 1 minute of footage from live stream
    await camera.record_livestream(filename=filename, duration=timedelta(minutes=1))

    await logi_api.logout()

loop = asyncio.get_event_loop()
loop.run_until_complete(get_livestream())
loop.close()

Download last 24 hours activity for the 1st camera (limited to 100, 5 at a time):

from datetime import datetime, timedelta

# Don't go nuts with parallelising downloads, you'll probably hit rate limits.
semaphore = asyncio.Semaphore(5)

async def download(camera, activity):
    async with semaphore:
        file_name = '%s - %s.mp4' % (camera.name,
                                     activity.start_time.isoformat())
        await activity.download(file_name)

async def run():
    my_camera = (await logi_api.cameras)[0]
    activities = await my_camera.query_activity_history(date_filter=datetime.now() - timedelta(hours=24), date_operator='>', limit=100)
    tasks = []

    for activity in activities:
        task = asyncio.ensure_future(download(my_camera, activity))
        tasks.append(task)

    await asyncio.gather(*tasks)
    logi_api.logout()

loop = asyncio.get_event_loop()
future = asyncio.ensure_future(run())
loop.run_until_complete(future)

Turn off streaming for all cameras

async def disable_streaming_all():
    for camera in await logi_api.cameras:
        if camera.streaming_mode is True:
            await camera.set_streaming_mode(False)
            print('%s is now off.' % (camera.name))
        else:
            print('%s is already off.' % (camera.name))
    await logi_api.logout()

loop = asyncio.get_event_loop()
loop.run_until_complete(disable_streaming_all())
loop.close()

Play with camera properties

async def play_with_props():
    for camera in await logi_api.cameras:
        last_activity = await camera.last_activity
        print('%s: %s' % (camera.name,
                          ('is charging' if camera.is_charging else 'is not charging')))
        print('%s: %s%% battery remaining' %
              (camera.name, camera.battery_level))
        print('%s: Battery saving mode is %s' %
              (camera.name, 'on' if camera.battery_saving else 'off'))
        print('%s: Model number is %s' % (camera.name, camera.model))
        print('%s: Model generation is %s' % (camera.name, camera.model_generation))
        print('%s: Mount is %s' % (camera.name, camera.mount))
        print('%s: Signal strength is %s%% (%s)' % (
            camera.name, camera.signal_strength_percentage, camera.signal_strength_category))
        print('%s: last activity was at %s and lasted for %s seconds.' % (
            camera.name, last_activity.start_time.isoformat(), last_activity.duration.total_seconds()))
        print('%s: Firmware version %s' % (camera.name, camera.firmware))
        print('%s: IP address is %s' % (camera.name, camera.ip_address))
        print('%s: MAC address is %s' % (camera.name, camera.mac_address))
        print('%s: Microphone is %s and gain is set to %s (out of 100)' % (
            camera.name, 'on' if camera.microphone_on else 'off', camera.microphone_gain))
        print('%s: Speaker is %s and volume is set to %s (out of 100)' % (
            camera.name, 'on' if camera.speaker_on else 'off', camera.speaker_volume))
        print('%s: LED is %s' % (
            camera.name, 'on' if camera.led_on else 'off'))
        print('%s: Privacy mode is %s' % (
            camera.name, 'on' if camera.privacy_mode else 'off'))
        print('%s: Subscribed to plan %s' % (
            camera.name, camera.plan_name))
    await logi_api.logout()

loop = asyncio.get_event_loop()
loop.run_until_complete(play_with_props())
loop.close()

Release History

  • 0.0.1
    • Initial commit
  • 0.0.2
    • Added support for querying activity history
  • 0.0.3
    • Added support for retrieving the latest still image for a given camera
  • 0.0.4
    • Replaced requests with aiohttp
    • Added support for turning camera on & off
    • Added update() method to Camera object to refresh data from server
  • 0.1.0
    • Added preliminary support for live streams (to be improved)
  • 0.1.1
    • Fixed timing bug causing live streams to download at half real-time speeds
    • Live streams will now automatically append to an existing file (instead of overwriting)
    • Added a bunch of new camera properties
    • Added support for setting privacy mode, LED status, speaker status, speaker volume, microphone status and microphone gain
  • 0.1.2
    • Removed is_streaming property as I've discovered this is not a binary sensor for 2nd gen cameras. Replaced with streaming_mode.
    • set_streaming_mode now accepts a string instead a boolean.
    • Added model_name property.
  • 0.1.3
    • Renamed model_name to model_type to better reflect what the property reports.
    • Added rudimentary feature detection, exposed via supported_features and supports_feature methods and derived from model type.
  • 0.1.4
    • Fixed missing last_activity_time sensor on 2nd gen wired cameras.
  • 0.1.5
    • Added get_livestream_image and record_livestream methods to camera object, allowing snapshots (images) and videos of a specified length to be recorded from the camera's livestream (both requiring ffmpeg)
  • 0.1.6
    • set_streaming_mode now accepts a boolean instead of string.
    • Removed model_type property, replaced with mount and model_generation properties.
    • Added battery_saving property and set_battery_saving_mode method
  • 0.1.7
    • Implemented rudimentary throttling on update() requests

Meta

Evan Bruhn – @evanjdevan.bruhn@gmail.com

Distributed under the MIT license. See LICENSE for more information.

Thanks

  • This API borrows a lot of design and some utility functions from tchellomello's Python Ring Doorbell project. Our projects are doing similar things with similar devices and I really appreciated how simple and readable python-ring-doorbell is.
  • Thanks sergeymaysak for suggesting a switch to aiohttp and for a tip to make downloading snapshot images more reliable.

Contributing

They're very welcome, every little bit helps! I'm especially keen for help supporting devices that I do not own and cannot test with (eg. Circle 2 wired and wireless cameras).

  1. Raise an issue with your feature request or bug before starting work.
  2. Fork it (https://github.com/evanjd/python-logi-circle/fork).
  3. Create your feature branch (git checkout -b feature/fooBar).
  4. Commit your changes (git commit -am 'Add some fooBar').
  5. Add/update tests if needed, then run tox to confirm no test failures.
  6. Push to the branch (git push origin feature/fooBar).
  7. Create a new pull request!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Filename, size & hash SHA256 hash help File type Python version Upload date
logi_circle-0.1.7-py3-none-any.whl (19.4 kB) Copy SHA256 hash SHA256 Wheel py3 Sep 10, 2018
logi_circle-0.1.7.tar.gz (20.6 kB) Copy SHA256 hash SHA256 Source None Sep 10, 2018

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page