Skip to content

evanjd/python-logi-circle

 
 

Repository files navigation

Python Logi Circle API

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

PyPI version License Build Status Coverage Status Open Issues

⚠️ This no longer functions due to a breaking change in Logitech's private API (#15). Contributions to fix this issue are welcome. ⚠️

This library exposes the Logi Circle family of cameras as Python objects, wrapping Logi Circle's official API.

Now available as a Home Assistant integration! 🎉

There are two versions of this API:

  • 2.x - which targets the public API (master branch)
  • 1.x - which targets the private API (you are here)

Installation

Installing release version

$ pip install -Iv logi-circle==0.1.9

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)

Usage example

Setup and authenticate:

import asyncio
from logi_circle import Logi

logi_api = Logi('[email protected]', '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
  • 0.1.8
    • Bump user-agent
  • 0.1.9
    • Bump user-agent

Meta

Evan Bruhn – @evanjd[email protected]

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!

About

Python 3.7+ API for Logi Circle cameras

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages