{"id":19245359,"url":"https://github.com/abovecolin/petsseries","last_synced_at":"2025-10-05T04:32:10.281Z","repository":{"id":257806243,"uuid":"865983348","full_name":"AboveColin/petsseries","owner":"AboveColin","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-18T10:57:36.000Z","size":172,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-01T12:49:26.485Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AboveColin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-01T12:59:09.000Z","updated_at":"2024-12-18T10:55:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"36a8b0e1-4db3-4396-8362-91268c9024d6","html_url":"https://github.com/AboveColin/petsseries","commit_stats":null,"previous_names":["abovecolin/petsseries"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AboveColin%2Fpetsseries","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AboveColin%2Fpetsseries/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AboveColin%2Fpetsseries/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AboveColin%2Fpetsseries/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AboveColin","download_url":"https://codeload.github.com/AboveColin/petsseries/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249211952,"owners_count":21231010,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T17:27:33.911Z","updated_at":"2025-10-05T04:32:05.218Z","avatar_url":"https://github.com/AboveColin.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![PyPI - Downloads](https://img.shields.io/pypi/dm/petsseries)\n![PyPI - Downloads](https://img.shields.io/pypi/dd/petsseries)\n\n# Unofficial PetsSeries API Client\n\n_Disclaimer: This is an unofficial Python client for the PetsSeries API. It is not affiliated with, endorsed by, or in any way connected to the official PetsSeries, Versuni or Philips companies._\n\n## Introduction\nThe Unofficial PetsSeries API Client is a Python library designed to interact with the PetsSeries backend services. It provides asynchronous methods to manage user information, homes, devices, meals, events, and more. This client handles authentication, token refreshing, and provides a convenient interface for integrating PetsSeries functionalities into your Python applications.\n\n## Features\n- **Authentication Management:** Handles access and refresh tokens, including automatic refreshing when expired.\n- **Comprehensive API Coverage:** Methods to interact with user info, homes, devices, meals, events, and device settings.\n- **Schedule Management:** Methods to manage schedules\n- **Food Dispenser Control:** Methods to control food dispensers (Requires Tuya)\n- **Event Parsing:** Automatically parses different event types into structured Python objects.\n\n## Features to be Added\n- **Camera Feed Access:** Methods to access the camera feed\n\n(feel free to PR if you manage to implement any of these features)\n\n## Installation\nEnsure you have Python 3.10 or higher installed. You can install the package using pip:\n\n```bash\npip install -r requirements.txt\n```\n\n## Authentication\nThis client uses OAuth2 tokens (access_token and refresh_token) to authenticate with the PetsSeries API. Follow the steps below to obtain and set up your tokens.\n\n### Obtaining Tokens\n1. Login via Web Interface:\n\n    - Navigate to [PetsSeries Appliance Login](https://www.home.id/find-appliance).\n    - Select a PetsSeries product (Starts with PAW) and log in with your credentials.\n\n2. Retrieve Tokens:\n\n    - After logging in, you will be redirected to a \"Thanks for your purchase\" screen.\n    - Open your browser's developer tools (usually by pressing F12 or Ctrl+Shift+I).\n    - Go to the Application tab and inspect the cookies.\n    - Locate and copy the cc-access-token and cc-refresh-token from the cookies.\n\n3. Provide Tokens to the Client:\n\n    - You can provide the access_token and refresh_token when initializing the client. These tokens will be saved to tokens.json for future use.\n\n### Example Initialization with Tokens\n```python\nimport asyncio\nfrom petsseries import PetsSeriesClient\n\nasync def main():\n    client = PetsSeriesClient(\n        access_token=\"your_access_token_here\",\n        refresh_token=\"your_refresh_token_here\"\n    )\n    await client.initialize()\n    # Your code here\n\nasyncio.run(main())\n```\nAfter the first run, the tokens will be saved automatically, and you won't need to provide them again unless they are invalidated.\n\n## Tuya Integration (Optional)\n\nThe client also supports integration with Tuya devices, which is required for controlling features such as food dispensers. To enable this, you will need to provide the following:\n\n- client_id: This can be found in the PetsSeries app's device screen.\n\n- ip: The ip of the device.\n\n- local_key: You can extract this from the device using a rooted phone and running frida-trace as shown below:\n    \n```bash \nfrida-trace -H 127.0.0.1:27042 --decorate -j '*!*encodeString*'  -f com.versuni.petsseries. -o \u003ca folder location to save frida_trace outputs to a local file\u003e\n```\nThen, search for the localKey in the logs.\n\n\n### Example Initialization with Tokens and Tuya Credentials\n\n```python\nimport asyncio\nfrom petsseries import PetsSeriesClient\n\nasync def main():\n    client = PetsSeriesClient(\n        access_token=\"your_access_token_here\",\n        refresh_token=\"your_refresh\n        tuya_credentials={\"client_id\": \"CLIENT_ID\", \"ip\": \"IP_ADDRESS\", \"local_key\": \"LOCAL_KEY\"}\n    )\n    await client.initialize()\n    # Your code here\n\nasyncio.run(main())\n```\n\n## Usage\n### Initialization\nInitialize the PetsSeriesClient with optional access_token and refresh_token. If tokens are not provided, ensure that tokens.json exists with valid tokens.\n\n```python\nimport asyncio\nfrom petsseries import PetsSeriesClient\n\nasync def initialize_client():\n    async with PetsSeriesClient() as client:\n        await client.initialize()\n        # Use the client for API calls\n\nasyncio.run(initialize_client())\n```\n### Fetching Data\nThe client provides various methods to fetch data from the PetsSeries API.\n\n#### Get User Info\n```python\nuser = await client.get_user_info()\nprint(user.name, user.email)\n```\n#### Get Homes\n```python\nhomes = await client.get_homes()\nfor home in homes:\n    print(home.name)\n```\n\n#### Get Devices\n```python\nfor home in homes:\n    devices = await client.get_devices(home)\n    for device in devices:\n        print(device.name, device.id)\n```\n### Get Events\n```python\nfrom datetime import datetime\nfrom pytz import timezone\n\nfrom_date = datetime(2024, 9, 27, tzinfo=timezone(\"Europe/Amsterdam\"))\nto_date = datetime(2024, 9, 28, tzinfo=timezone(\"Europe/Amsterdam\"))\n\nevents = await client.get_events(home, from_date, to_date)\nfor event in events:\n    print(event)\n```\n\n### Get Meals\n```python\nfor home in homes:\n    meals = await client.get_meals(home)\n    for meal in meals:\n        print(meal)\n```\n\n#### Create Meal\n```python\n# Define the meal details\nmeal_name = \"Dinner\"\nportion_amount = 10  # 1 - 20 portions\nfeed_time = datetime.combine(datetime.today(), time(hour=18, minute=30))  # 6:30 PM\nrepeat_days = [1, 2, 3, 4, 5, 6, 7]  # Every day of the week\n\n# Create a Meal instance\nmeal = Meal(\n    id=\"\",  # ID will be assigned by the server\n    name=meal_name,\n    portion_amount=portion_amount,\n    feed_time=feed_time,\n    repeat_days=repeat_days,\n    device_id=device.id,\n    enabled=True,\n    url=\"\"  # URL will be assigned by the server\n)\n\ntry:\n    created_meal = await client.create_meal(home, meal)\n    print(f\"Meal '{created_meal.name}' created successfully with ID: {created_meal.id}\")\n    print(f\"Feed Time: {created_meal.feed_time}\")\n    print(f\"Portion Amount: {created_meal.portion_amount}\")\n    print(f\"Repeat Days: {created_meal.repeat_days}\")\n    print(f\"Device ID: {created_meal.device_id}\")\n    print(f\"Enabled: {created_meal.enabled}\")\n    print(f\"Meal URL: {created_meal.url}\")\nexcept Exception as e:\n    print(f\"An error occurred while creating the meal: {e}\")\n```\n\n#### Update Meal\n```python\n# Fetch homes\nhomes = await client.get_homes()\nhome = homes[0]  # Select the first home for example\n\n# Fetch meals\nmeals = await client.get_meals(home)\nmeal_to_update = meals[0]  # Select the first meal for example\n\n# Modify the meal details\nmeal_to_update.name = \"Updated Meal Name\"\nmeal_to_update.portion_amount = 3\nmeal_to_update.feed_time = time(13, 30)  # Update to 1:30 PM\n\n# Update the meal\nupdated_meal = await client.update_meal(home, meal_to_update)\nprint(f\"Meal updated: {updated_meal}\")\n```\n\n### Managing Devices\nYou can manage device settings such as powering devices on/off and toggling motion notifications.\n\n#### Device Power\n```python\nresult = await client.power_on_device(home, device_id)\nif result:\n    print(\"Device powered on successfully.\")\n\nresult = await client.power_off_device(home, device_id)\nif result:\n    print(\"Device powered off successfully.\")\n\nresult = await client.toggle_device_power(home, device_id)\nif result:\n    print(\"Device power toggled successfully.\")\n```\n#### Motion Notifications\n```python\nresult = await client.enable_motion_notifications(home, device_id)\nif result:\n    print(\"Motion notifications enabled successfully.\")\n\nresult = await client.disable_motion_notifications(home, device_id)\nif result:\n    print(\"Motion notifications disabled successfully.\")\n\nresult = await client.toggle_motion_notifications(home, device_id)\nif result:\n    print(\"Motion notifications toggled successfully.\")\n```\n\n### Example\nHere's a complete example demonstrating how to initialize the client, fetch user info, homes, devices, and manage device settings.\n\n```python\nimport asyncio\nfrom petsseries import PetsSeriesClient\n\nasync def main():\n    async with PetsSeriesClient(\n        access_token=\"your_access_token_here\",\n        refresh_token=\"your_refresh_token_here\"\n    ) as client:\n        await client.initialize()\n        \n        # Fetch user info\n        user = await client.get_user_info()\n        print(f\"User: {user.name} ({user.email})\")\n        \n        # Fetch homes\n        homes = await client.get_homes()\n        for home in homes:\n            print(f\"Home: {home.name}\")\n            \n            # Fetch devices in home\n            devices = await client.get_devices(home)\n            for device in devices:\n                print(f\"Device: {device.name} (ID: {device.id})\")\n                \n                # Power off device\n                success = await client.power_off_device(home, device.id)\n                if success:\n                    print(f\"{device.name} powered off.\")\n                \n                # Toggle motion notifications\n                success = await client.toggle_motion_notifications(home, device.id)\n                if success:\n                    print(f\"Motion notifications toggled for {device.name}.\")\n                \n                from_date = dt.datetime(2021, 9, 27, tzinfo=dt.timezone(dt.timedelta(hours=2)))\n                to_date = dt.datetime(2100, 9, 28, tzinfo=dt.timezone(dt.timedelta(hours=2)))\n                print(from_date, to_date)\n\n                events = await client.get_events(home, from_date, to_date)\n                # Possible eventTypes are: [\"motion_detected\", \"meal_dispensed\", \"meal_upcoming\", \"food_level_low\"]\n                for event in events:\n                    if event.type == \"meal_dispensed\":\n                        logger.info(f\"Event: {event}\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n### Usage with Tuya Integration\n#### Set Motion Sensitivity\nSets the motion sensitivity level on the Tuya device.\n\n```python\nsuccess = await client.set_motion_sensitivity(\"1\")\nif success:\n    print(\"Motion sensitivity set successfully.\")\n```\nParameters:\n\nvalue (str): Sensitivity level (\"0\", \"1\", or \"2\").\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n#### Get Motion Sensitivity\nRetrieves the current motion sensitivity setting of the Tuya device.\n\n```python\nsensitivity = await client.get_motion_sensitivity()\nif sensitivity:\n    print(f\"Current motion sensitivity: {sensitivity['motion_sensitivity']}\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current motion sensitivity if successful, else None.\n\n#### Get Night Vision Level\nSets the night vision level on the Tuya device.\n\n```python\nsuccess = await client.set_nightvision_level(\"2\")\nif success:\n    print(\"Night vision level set successfully.\")\n```\nParameters:\n\nvalue (str): Night vision level (\"0\", \"1\", or \"2\").\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Get Night Vision Level\nRetrieves the current night vision setting of the Tuya device.\n\n```python\nnightvision = await client.get_nightvision_level()\nif nightvision:\n    print(f\"Current night vision level: {nightvision['nightvision']}\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current night vision level if successful, else None.\n\n#### Toggle Motion Switch\nFlips the motion switch on the Tuya device.\n\n```python\nsuccess = await client.set_motion_switch()\nif success:\n    print(\"Motion switch toggled successfully.\")\n```\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Get Motion Switch Status\nRetrieves the current status of the motion switch on the Tuya device.\n\n```python\nmotion_switch = await client.get_motion_switch()\nif motion_switch:\n    print(f\"Motion switch is {'on' if motion_switch['motion_switch'] else 'off'}.\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current motion switch status if successful, else None.\n\n#### Set Anti-Flicker Level\nSets the anti-flicker level on the Tuya device.\n\n```python\nsuccess = await client.set_anti_flicker_level(\"1\")\nif success:\n    print(\"Anti-flicker level set successfully.\")\n```\nParameters:\n\nvalue (str): Anti-flicker level (\"0\", \"1\", or \"2\").\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Get Anti-Flicker Level\nRetrieves the current anti-flicker setting of the Tuya device.\n\n```python\nanti_flicker = await client.get_anti_flicker_level()\nif anti_flicker:\n    print(f\"Current anti-flicker level: {anti_flicker['anti_flicker']}\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current anti-flicker level if successful, else None.\n\n#### Set Feed Number\nSets the number of feeds on the Tuya device.\n\n```python\nsuccess = await client.feed_num(5)\nif success:\n    print(\"Feed number set successfully.\")\n```\nParameters:\n\nvalue (int): Number of feeds (0 to 20).\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Set Device Volume\nSets the device volume on the Tuya device.\n\n```python\nsuccess = await client.set_device_volume(75)\nif success:\n    print(\"Device volume set successfully.\")\n```\nParameters:\n\nvalue (int): Volume level (1 to 100).\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Get Device Volume\nRetrieves the current volume setting of the Tuya device.\n\n```python\ndevice_volume = await client.get_device_volume()\nif device_volume:\n    print(f\"Current device volume: {device_volume['device_volume']}\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current device volume if successful, else None.\n\n#### Set Food Weight\nSets the food weight in grams on the Tuya device.\n\n```python\nsuccess = await client.set_food_weight(50)\nif success:\n    print(\"Food weight set successfully.\")\n```\nParameters:\n\nvalue (int): Food weight (0 to 100).\nReturns:\n\nbool: True if the operation was successful, False otherwise.\n\n#### Get Food Weight\nRetrieves the current food weight setting of the Tuya device.\n\n```python\nfood_weight = await client.get_food_weight()\nif food_weight:\n    print(f\"Current food weight: {food_weight['food_weight']} grams\")\n```\nReturns:\n\nOptional[Dict[str, Any]]: The current food weight if successful, else None.\n\nThese Tuya methods enhance the functionality of the PetsSeries API Client by allowing granular control over Tuya-integrated devices. Ensure that you have properly initialized the TuyaClient with the necessary credentials before attempting to use these methods.\n\n#### Example Initialization with Tuya Features:\n\n```python\n    import asyncio\n    from petsseries import PetsSeriesClient\n\n    async def main():\n        client = PetsSeriesClient(\n            access_token=\"your_access_token_here\",\n            refresh_token=\"your_refresh_token_here\",\n            tuya_credentials={\n                \"client_id\": \"YOUR_CLIENT_ID\",\n                \"ip\": \"DEVICE_IP_ADDRESS\",\n                \"local_key\": \"YOUR_LOCAL_KEY\"\n            }\n        )\n        await client.initialize()\n        \n        # Example: Set and get motion sensitivity\n        await client.set_motion_sensitivity(\"1\")\n        sensitivity = await client.get_motion_sensitivity()\n        print(f\"Motion Sensitivity: {sensitivity}\")\n\n    asyncio.run(main())\n```\n\n## Contributing\nContributions are more than welcome!\n\n\n__Disclaimer:__ This project is not affiliated with PetsSeries or Versuni. It is developed independently and is intended for personal use. Use it responsibly and respect the terms of service of the official PetsSeries API.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabovecolin%2Fpetsseries","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabovecolin%2Fpetsseries","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabovecolin%2Fpetsseries/lists"}