nanocontroller


Namenanocontroller JSON
Version 1.0.3 PyPI version JSON
download
home_pageNone
SummaryNanoLeaf controller
upload_time2024-11-27 23:56:29
maintainerNone
docs_urlNone
authorNone
requires_python>=3.6
licenseMIT
keywords nanoleaf
VCS
bugtrack_url
requirements aiohappyeyeballs aiohttp aiosignal anyio attrs backports.tarfile build cattrs certifi charset-normalizer docutils flatbuffers frozenlist h11 httpcore httpx idna importlib_metadata jaraco.classes jaraco.context jaraco.functools keyring markdown-it-py mashumaro mdurl more-itertools multidict nh3 numpy openmeteo_requests openmeteo_sdk orjson packaging pandas pkginfo platformdirs propcache Pygments pyproject_hooks python-dateutil python-dotenv pytz readme_renderer requests requests-cache requests-toolbelt retry-requests rfc3986 rich six sniffio twine typing_extensions tzdata url-normalize urllib3 yarl zipp
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # NanoController Python Package

## Overview

The nanocontroller package allows you to interface with Nanoleaf devices, providing functionality for controlling individual lights programmatically, setting timers, and displaying weather information. 

---

## Installation
 
```bash
pip install nanocontroller
```

---

## Authorization  

To use the Nanoleaf OpenAPI, physical access to the device is required:

Hold the on-off button for 5-7 seconds until the LED starts flashing in a pattern or open the authorization window via the Nanoleaf mobile app by navigating to device settings and selecting "Connect to API."

Run this function to obtain auth_token during authorization window
```python
from nanocontroller import get_token

get_token(
    ip address,     # Found on back of device, in app, or through network discovery tool 
    port            # Port defaults to "16021", but manual port discovery recommended. 
    )
```
    
---

## Getting Started

### Initializing the NanoController

```python
from nanocontroller import NanoController

nano = NanoController(
    auth_token="sdg7648t24ih"  # Obtained from "get_token"
    ip_address="172.20.7.17",  # Found on the back of the device, in the app, or via a network scan.
    port="16021",              # Default port is 16021. Discovery recommended.
    latitude=28.5383,          # Optional latitude, required for weather functions.
    longitude=-81.3792         # Optional longitude, required for weather functions.
)
```

or create a .env file 
```env
NANO_AUTH_TOKEN=
NANO_IP_ADDRESS=                # Strings
NANO_PORT=

NANO_LATITUDE =                 # Floats
NANO_LONGITUDE = 
```
and just
```python
from nano import NanoController

nano = NanoController()
```
Location can be updated at any point after initialization with:
```python
nano.set_location(lattitude, longitude)
```
---

## Methods

### General Controls

- **Get states:**
  ```python
  await nano.get_brightness()          # Get current brightness level.
  await nano.get_effect()              # Get the currently active effect.
  await nano.get_effects_list()        # List available effects downloaded in the app.
  await nano.get_state()               # Retrieve all states of the panels.
  ```

- **Set states:**
  ```python
  await nano.set_brightness(50)        # Set brightness (0-100).
  await nano.set_effect("Cocoa Beach")      # Set a specific effect by name.
  ```

---

### Custom Effects

Customize the panel colors and effects programmatically:

```python
await nano.custom(color_dict, loop=True)
```

- **Input Format:**
  ```python
  color_dict = {
      0: [(255, 0, 0, 10)],                     # Panel 1: Changes to Red with a 1-second transition.
      3: [(0, 255, 0, 10), (0, 0, 255, 30)],    # Panel 4: Changes to Green with a 1-second transition, then changes to blue with a 3 second transition. Loops by default, if Loop is False then remains static on blue after transitions
      ...
  }
  ```
  You can change all panels at once, or partial sets. The keys(0 to (number of panels - 1)) correspond to the set order of the panels. This defaults to "top_to_bottom" but can be changed with:
  ```python
  nano.panels.bottom_to_top()
  nano.panels.left_to_right()
  nano.panels.right_to_left()
  ```
  You can use multiple ordering methods when only one would leave room for ambiguity:
  ```python
  nano.panels.left_to_right()
  nano.panels.bottom_to_top()
  ```
  You can customize the ordering if the positions do not strictly follow a specific order:
  ```python
  print(nano.panels)                        # Gets IDs and relative postions        
  
  #Output: [Panel(id=30344, x=0, y=580), Panel(id=26383, x=0, y=464), Panel(id=19622, x=0, y=348), Panel(id=29596, x=0, y=232), Panel(id=11739, x=0, y=116), Panel(id=38168, x=0, y=0)]
  
  nano.panels.custom_sort(ordered_ids)      # ordered_ids is an array of the ids in a customized order, [26383, 19622, 30344, etc...]  

  ```
---

### Timer 

![Timer](nanocontroller/examples/IMG_2573.jpeg)

Gradually transition panels from one color to another, one by one, over a defined time. Follows the set ordering of panels, defaulting to "top_to_bottom". Accepts an optional function to execute at the end of the timer alongside the ending animation on the panels:

[Usage demonstration](https://drive.google.com/file/d/1lNFXadyBaEw3cxatC6kpN1qZ_msOE1W1/view?usp=sharing)

```python
await nano.timer(
    duration = 60,                  # Required - Time in seconds (≥ total number of panels).
    start_color = BLUE,             # Default blue.
    end_color = WHITE,              # Default white.
    alarm_length = 10,              # Alarm duration in seconds.
    alarm_brightness = 100,         # Full brightness for the alarm.
    end_animation = None,           # Custom `color_dict` for end effect. Defaults to quckly cycling through random colors.
    end_function = None,            # Optional async function to execute during end_animation
    end_function_kwargs = None      # Arguments for the end function.
)
```

[Application](https://drive.google.com/file/d/1lNFXadyBaEw3cxatC6kpN1qZ_msOE1W1/view?usp=sharing)

---

### Weather Visualization

1. **Set location:**
   ```python
   await nano.set_location(latitude, longitude)
   ```

2. **Display hourly forecast:**
   Each panel represents an hour. For example:
   - Heavy rain in 3 hours: Panel 3 quickly flashes blues and whites.
   - Overcast: Panel slowly transitions between grey tones.
   - weather_codes.py defines weather animations and can be customized. It is not fully optimized yet so edit it how you like.   

   ```python
   await nano.set_hourly_forecast(
       latitude = None, 
       longitude = None,                # If location has not been set at initialization or with .set_location() it must be provided here
       sunrise = 6,                     # Weather effects varry based on if they occur during day or night. Daytime hours fall within (sunrise, sunset) exclusive. 
       sunset = 18,
   )
   ```

3. **Display precipitation levels:**

   ![Precipitation](nanocontroller/examples/IMG_2566.jpeg)
    
   Sets the panels to display precipitaion level over "hour_interval" periods. Defaults to one hour per panel. Precipitation is represented on a gradient from the brightest blue for 100% chance and yellow for 0%. These colors are customizable. The image above represents (starting from the bottom): 0%, 30%, 60%, 100%, 90%, 25%.
   ```python
   await nano.set_precipitation(
            hour_interval = 1, 
            latitude = None,                  # Location necesarry if not already set
            longitude = None,
            max_color = BLUE,                 # Many standard colors are predefined
            min_color = YELLOW
    )
   ```

4. **Display temperature gradients:**

   ![Temperature](nanocontroller/examples/IMG_2550.jpeg)

   Sets the panels to display the temperature per hour intervals. Defaults to one hour per panel.
    Default color gradients defined by the dictionary:
    ```python
    gradient_dict = {
        0: {
            "start": (255, 255, 255),  # Bright white
            "end": (255, 255, 255)     # Bright white
        },
        40: {
            "start": (255, 255, 255),  # Bright white
            "end": (128, 128, 128)     # Light white
        },
        50: {
            "start": (90, 0, 140),    # darker purple
            "end": (150, 50, 255)      # purple
        },
        60: {
            "start": (0, 0, 255),      # Blue
            "end": (40, 90, 255)       # Slightly lighter blue
        },
        70: {
            "start": (0, 255, 0),      # Green
            "end": (90, 255, 60)       # Yellowish green
        },
        80: {
            "start": (255, 255, 0),    # Yellow
            "end": (255, 0, 0)         # Red
        },
        100: {
            "start": (255, 0, 0),      # Red
            "end": (255, 0, 0)         # Red
        }
    }
    ```
    Custom gradient dictionary of the same form can be passed to accomadate different climates. Intervals can be customized. "start" defines the color at the lowest point in the interval and "end" defines the color at the highest point in the interval. 


    ```python
    await nano.set_temperature(
            hour_interval = 1,       
            latitude = None, 
            longitude = None, 
            gradient_dict = None,
    )
    ```

---

## Features

### Built-in Functionalities
- **Weather visualization:** Displays hourly forecasts, precipitation, and temperature using panel colors.
- **Timers:** Create visual countdowns with customizable colors and alarms.

### Extendability
Leverage the `nano.custom()` method to:
- Display dynamic notifications.
- Create personalized status displays.

---
## Contributing

Feel free to submit issues or pull requests on the [GitHub repository](https://github.com/your-repo/nanocontroller). Contributions to add new features or improve existing ones are always welcome.

---

## License

This package is licensed under the MIT License. See the LICENSE file for more information.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "nanocontroller",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": null,
    "keywords": "nanoleaf",
    "author": null,
    "author_email": "Jared Gantt <jaredgantt@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/48/0f/7561dbe3bd01a073ffd5e96fa577ef545da5bcb4f83d46b3435464a3ab56/nanocontroller-1.0.3.tar.gz",
    "platform": null,
    "description": "# NanoController Python Package\n\n## Overview\n\nThe nanocontroller package allows you to interface with Nanoleaf devices, providing functionality for controlling individual lights programmatically, setting timers, and displaying weather information. \n\n---\n\n## Installation\n \n```bash\npip install nanocontroller\n```\n\n---\n\n## Authorization  \n\nTo use the Nanoleaf OpenAPI, physical access to the device is required:\n\nHold the on-off button for 5-7 seconds until the LED starts flashing in a pattern or open the authorization window via the Nanoleaf mobile app by navigating to device settings and selecting \"Connect to API.\"\n\nRun this function to obtain auth_token during authorization window\n```python\nfrom nanocontroller import get_token\n\nget_token(\n    ip address,     # Found on back of device, in app, or through network discovery tool \n    port            # Port defaults to \"16021\", but manual port discovery recommended. \n    )\n```\n    \n---\n\n## Getting Started\n\n### Initializing the NanoController\n\n```python\nfrom nanocontroller import NanoController\n\nnano = NanoController(\n    auth_token=\"sdg7648t24ih\"  # Obtained from \"get_token\"\n    ip_address=\"172.20.7.17\",  # Found on the back of the device, in the app, or via a network scan.\n    port=\"16021\",              # Default port is 16021. Discovery recommended.\n    latitude=28.5383,          # Optional latitude, required for weather functions.\n    longitude=-81.3792         # Optional longitude, required for weather functions.\n)\n```\n\nor create a .env file \n```env\nNANO_AUTH_TOKEN=\nNANO_IP_ADDRESS=                # Strings\nNANO_PORT=\n\nNANO_LATITUDE =                 # Floats\nNANO_LONGITUDE = \n```\nand just\n```python\nfrom nano import NanoController\n\nnano = NanoController()\n```\nLocation can be updated at any point after initialization with:\n```python\nnano.set_location(lattitude, longitude)\n```\n---\n\n## Methods\n\n### General Controls\n\n- **Get states:**\n  ```python\n  await nano.get_brightness()          # Get current brightness level.\n  await nano.get_effect()              # Get the currently active effect.\n  await nano.get_effects_list()        # List available effects downloaded in the app.\n  await nano.get_state()               # Retrieve all states of the panels.\n  ```\n\n- **Set states:**\n  ```python\n  await nano.set_brightness(50)        # Set brightness (0-100).\n  await nano.set_effect(\"Cocoa Beach\")      # Set a specific effect by name.\n  ```\n\n---\n\n### Custom Effects\n\nCustomize the panel colors and effects programmatically:\n\n```python\nawait nano.custom(color_dict, loop=True)\n```\n\n- **Input Format:**\n  ```python\n  color_dict = {\n      0: [(255, 0, 0, 10)],                     # Panel 1: Changes to Red with a 1-second transition.\n      3: [(0, 255, 0, 10), (0, 0, 255, 30)],    # Panel 4: Changes to Green with a 1-second transition, then changes to blue with a 3 second transition. Loops by default, if Loop is False then remains static on blue after transitions\n      ...\n  }\n  ```\n  You can change all panels at once, or partial sets. The keys(0 to (number of panels - 1)) correspond to the set order of the panels. This defaults to \"top_to_bottom\" but can be changed with:\n  ```python\n  nano.panels.bottom_to_top()\n  nano.panels.left_to_right()\n  nano.panels.right_to_left()\n  ```\n  You can use multiple ordering methods when only one would leave room for ambiguity:\n  ```python\n  nano.panels.left_to_right()\n  nano.panels.bottom_to_top()\n  ```\n  You can customize the ordering if the positions do not strictly follow a specific order:\n  ```python\n  print(nano.panels)                        # Gets IDs and relative postions        \n  \n  #Output: [Panel(id=30344, x=0, y=580), Panel(id=26383, x=0, y=464), Panel(id=19622, x=0, y=348), Panel(id=29596, x=0, y=232), Panel(id=11739, x=0, y=116), Panel(id=38168, x=0, y=0)]\n  \n  nano.panels.custom_sort(ordered_ids)      # ordered_ids is an array of the ids in a customized order, [26383, 19622, 30344, etc...]  \n\n  ```\n---\n\n### Timer \n\n![Timer](nanocontroller/examples/IMG_2573.jpeg)\n\nGradually transition panels from one color to another, one by one, over a defined time. Follows the set ordering of panels, defaulting to \"top_to_bottom\". Accepts an optional function to execute at the end of the timer alongside the ending animation on the panels:\n\n[Usage demonstration](https://drive.google.com/file/d/1lNFXadyBaEw3cxatC6kpN1qZ_msOE1W1/view?usp=sharing)\n\n```python\nawait nano.timer(\n    duration = 60,                  # Required - Time in seconds (\u2265 total number of panels).\n    start_color = BLUE,             # Default blue.\n    end_color = WHITE,              # Default white.\n    alarm_length = 10,              # Alarm duration in seconds.\n    alarm_brightness = 100,         # Full brightness for the alarm.\n    end_animation = None,           # Custom `color_dict` for end effect. Defaults to quckly cycling through random colors.\n    end_function = None,            # Optional async function to execute during end_animation\n    end_function_kwargs = None      # Arguments for the end function.\n)\n```\n\n[Application](https://drive.google.com/file/d/1lNFXadyBaEw3cxatC6kpN1qZ_msOE1W1/view?usp=sharing)\n\n---\n\n### Weather Visualization\n\n1. **Set location:**\n   ```python\n   await nano.set_location(latitude, longitude)\n   ```\n\n2. **Display hourly forecast:**\n   Each panel represents an hour. For example:\n   - Heavy rain in 3 hours: Panel 3 quickly flashes blues and whites.\n   - Overcast: Panel slowly transitions between grey tones.\n   - weather_codes.py defines weather animations and can be customized. It is not fully optimized yet so edit it how you like.   \n\n   ```python\n   await nano.set_hourly_forecast(\n       latitude = None, \n       longitude = None,                # If location has not been set at initialization or with .set_location() it must be provided here\n       sunrise = 6,                     # Weather effects varry based on if they occur during day or night. Daytime hours fall within (sunrise, sunset) exclusive. \n       sunset = 18,\n   )\n   ```\n\n3. **Display precipitation levels:**\n\n   ![Precipitation](nanocontroller/examples/IMG_2566.jpeg)\n    \n   Sets the panels to display precipitaion level over \"hour_interval\" periods. Defaults to one hour per panel. Precipitation is represented on a gradient from the brightest blue for 100% chance and yellow for 0%. These colors are customizable. The image above represents (starting from the bottom): 0%, 30%, 60%, 100%, 90%, 25%.\n   ```python\n   await nano.set_precipitation(\n            hour_interval = 1, \n            latitude = None,                  # Location necesarry if not already set\n            longitude = None,\n            max_color = BLUE,                 # Many standard colors are predefined\n            min_color = YELLOW\n    )\n   ```\n\n4. **Display temperature gradients:**\n\n   ![Temperature](nanocontroller/examples/IMG_2550.jpeg)\n\n   Sets the panels to display the temperature per hour intervals. Defaults to one hour per panel.\n    Default color gradients defined by the dictionary:\n    ```python\n    gradient_dict = {\n        0: {\n            \"start\": (255, 255, 255),  # Bright white\n            \"end\": (255, 255, 255)     # Bright white\n        },\n        40: {\n            \"start\": (255, 255, 255),  # Bright white\n            \"end\": (128, 128, 128)     # Light white\n        },\n        50: {\n            \"start\": (90, 0, 140),    # darker purple\n            \"end\": (150, 50, 255)      # purple\n        },\n        60: {\n            \"start\": (0, 0, 255),      # Blue\n            \"end\": (40, 90, 255)       # Slightly lighter blue\n        },\n        70: {\n            \"start\": (0, 255, 0),      # Green\n            \"end\": (90, 255, 60)       # Yellowish green\n        },\n        80: {\n            \"start\": (255, 255, 0),    # Yellow\n            \"end\": (255, 0, 0)         # Red\n        },\n        100: {\n            \"start\": (255, 0, 0),      # Red\n            \"end\": (255, 0, 0)         # Red\n        }\n    }\n    ```\n    Custom gradient dictionary of the same form can be passed to accomadate different climates. Intervals can be customized. \"start\" defines the color at the lowest point in the interval and \"end\" defines the color at the highest point in the interval. \n\n\n    ```python\n    await nano.set_temperature(\n            hour_interval = 1,       \n            latitude = None, \n            longitude = None, \n            gradient_dict = None,\n    )\n    ```\n\n---\n\n## Features\n\n### Built-in Functionalities\n- **Weather visualization:** Displays hourly forecasts, precipitation, and temperature using panel colors.\n- **Timers:** Create visual countdowns with customizable colors and alarms.\n\n### Extendability\nLeverage the `nano.custom()` method to:\n- Display dynamic notifications.\n- Create personalized status displays.\n\n---\n## Contributing\n\nFeel free to submit issues or pull requests on the [GitHub repository](https://github.com/your-repo/nanocontroller). Contributions to add new features or improve existing ones are always welcome.\n\n---\n\n## License\n\nThis package is licensed under the MIT License. See the LICENSE file for more information.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "NanoLeaf controller",
    "version": "1.0.3",
    "project_urls": {
        "Homepage": "https://github.com/JJGantt/nano_control"
    },
    "split_keywords": [
        "nanoleaf"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "165ae37ae8cf6c569eb1e4e970da8eb12077f46d224c6b52fbe035e6323f7363",
                "md5": "8786c1531e292ec2a634144ae0ce91a2",
                "sha256": "3058606bf2cbba706c098e1e057865623a20fafe1e30f4ec04bc3dc677445099"
            },
            "downloads": -1,
            "filename": "nanocontroller-1.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "8786c1531e292ec2a634144ae0ce91a2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 15386,
            "upload_time": "2024-11-27T23:56:27",
            "upload_time_iso_8601": "2024-11-27T23:56:27.465087Z",
            "url": "https://files.pythonhosted.org/packages/16/5a/e37ae8cf6c569eb1e4e970da8eb12077f46d224c6b52fbe035e6323f7363/nanocontroller-1.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "480f7561dbe3bd01a073ffd5e96fa577ef545da5bcb4f83d46b3435464a3ab56",
                "md5": "b3c3ac1cbfc56825cdac9671128fd187",
                "sha256": "72cb391c55c3d17f2aeb0dad4d04c72d1ea760f5820a74469a25f2c1738cc034"
            },
            "downloads": -1,
            "filename": "nanocontroller-1.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "b3c3ac1cbfc56825cdac9671128fd187",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 17428,
            "upload_time": "2024-11-27T23:56:29",
            "upload_time_iso_8601": "2024-11-27T23:56:29.665470Z",
            "url": "https://files.pythonhosted.org/packages/48/0f/7561dbe3bd01a073ffd5e96fa577ef545da5bcb4f83d46b3435464a3ab56/nanocontroller-1.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-27 23:56:29",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "JJGantt",
    "github_project": "nano_control",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "aiohappyeyeballs",
            "specs": [
                [
                    "==",
                    "2.4.3"
                ]
            ]
        },
        {
            "name": "aiohttp",
            "specs": [
                [
                    "==",
                    "3.11.7"
                ]
            ]
        },
        {
            "name": "aiosignal",
            "specs": [
                [
                    "==",
                    "1.3.1"
                ]
            ]
        },
        {
            "name": "anyio",
            "specs": [
                [
                    "==",
                    "4.6.2.post1"
                ]
            ]
        },
        {
            "name": "attrs",
            "specs": [
                [
                    "==",
                    "24.2.0"
                ]
            ]
        },
        {
            "name": "backports.tarfile",
            "specs": [
                [
                    "==",
                    "1.2.0"
                ]
            ]
        },
        {
            "name": "build",
            "specs": [
                [
                    "==",
                    "1.2.2.post1"
                ]
            ]
        },
        {
            "name": "cattrs",
            "specs": [
                [
                    "==",
                    "24.1.2"
                ]
            ]
        },
        {
            "name": "certifi",
            "specs": [
                [
                    "==",
                    "2024.8.30"
                ]
            ]
        },
        {
            "name": "charset-normalizer",
            "specs": [
                [
                    "==",
                    "3.4.0"
                ]
            ]
        },
        {
            "name": "docutils",
            "specs": [
                [
                    "==",
                    "0.21.2"
                ]
            ]
        },
        {
            "name": "flatbuffers",
            "specs": [
                [
                    "==",
                    "24.3.25"
                ]
            ]
        },
        {
            "name": "frozenlist",
            "specs": [
                [
                    "==",
                    "1.5.0"
                ]
            ]
        },
        {
            "name": "h11",
            "specs": [
                [
                    "==",
                    "0.14.0"
                ]
            ]
        },
        {
            "name": "httpcore",
            "specs": [
                [
                    "==",
                    "1.0.7"
                ]
            ]
        },
        {
            "name": "httpx",
            "specs": [
                [
                    "==",
                    "0.27.2"
                ]
            ]
        },
        {
            "name": "idna",
            "specs": [
                [
                    "==",
                    "3.10"
                ]
            ]
        },
        {
            "name": "importlib_metadata",
            "specs": [
                [
                    "==",
                    "8.5.0"
                ]
            ]
        },
        {
            "name": "jaraco.classes",
            "specs": [
                [
                    "==",
                    "3.4.0"
                ]
            ]
        },
        {
            "name": "jaraco.context",
            "specs": [
                [
                    "==",
                    "6.0.1"
                ]
            ]
        },
        {
            "name": "jaraco.functools",
            "specs": [
                [
                    "==",
                    "4.1.0"
                ]
            ]
        },
        {
            "name": "keyring",
            "specs": [
                [
                    "==",
                    "25.5.0"
                ]
            ]
        },
        {
            "name": "markdown-it-py",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "mashumaro",
            "specs": [
                [
                    "==",
                    "3.14"
                ]
            ]
        },
        {
            "name": "mdurl",
            "specs": [
                [
                    "==",
                    "0.1.2"
                ]
            ]
        },
        {
            "name": "more-itertools",
            "specs": [
                [
                    "==",
                    "10.5.0"
                ]
            ]
        },
        {
            "name": "multidict",
            "specs": [
                [
                    "==",
                    "6.1.0"
                ]
            ]
        },
        {
            "name": "nh3",
            "specs": [
                [
                    "==",
                    "0.2.18"
                ]
            ]
        },
        {
            "name": "numpy",
            "specs": [
                [
                    "==",
                    "2.1.3"
                ]
            ]
        },
        {
            "name": "openmeteo_requests",
            "specs": [
                [
                    "==",
                    "1.3.0"
                ]
            ]
        },
        {
            "name": "openmeteo_sdk",
            "specs": [
                [
                    "==",
                    "1.18.0"
                ]
            ]
        },
        {
            "name": "orjson",
            "specs": [
                [
                    "==",
                    "3.10.11"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "24.2"
                ]
            ]
        },
        {
            "name": "pandas",
            "specs": [
                [
                    "==",
                    "2.2.3"
                ]
            ]
        },
        {
            "name": "pkginfo",
            "specs": [
                [
                    "==",
                    "1.10.0"
                ]
            ]
        },
        {
            "name": "platformdirs",
            "specs": [
                [
                    "==",
                    "4.3.6"
                ]
            ]
        },
        {
            "name": "propcache",
            "specs": [
                [
                    "==",
                    "0.2.0"
                ]
            ]
        },
        {
            "name": "Pygments",
            "specs": [
                [
                    "==",
                    "2.18.0"
                ]
            ]
        },
        {
            "name": "pyproject_hooks",
            "specs": [
                [
                    "==",
                    "1.2.0"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.9.0.post0"
                ]
            ]
        },
        {
            "name": "python-dotenv",
            "specs": [
                [
                    "==",
                    "1.0.1"
                ]
            ]
        },
        {
            "name": "pytz",
            "specs": [
                [
                    "==",
                    "2024.2"
                ]
            ]
        },
        {
            "name": "readme_renderer",
            "specs": [
                [
                    "==",
                    "44.0"
                ]
            ]
        },
        {
            "name": "requests",
            "specs": [
                [
                    "==",
                    "2.32.3"
                ]
            ]
        },
        {
            "name": "requests-cache",
            "specs": [
                [
                    "==",
                    "1.2.1"
                ]
            ]
        },
        {
            "name": "requests-toolbelt",
            "specs": [
                [
                    "==",
                    "1.0.0"
                ]
            ]
        },
        {
            "name": "retry-requests",
            "specs": [
                [
                    "==",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "rfc3986",
            "specs": [
                [
                    "==",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "rich",
            "specs": [
                [
                    "==",
                    "13.9.4"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.16.0"
                ]
            ]
        },
        {
            "name": "sniffio",
            "specs": [
                [
                    "==",
                    "1.3.1"
                ]
            ]
        },
        {
            "name": "twine",
            "specs": [
                [
                    "==",
                    "5.1.1"
                ]
            ]
        },
        {
            "name": "typing_extensions",
            "specs": [
                [
                    "==",
                    "4.12.2"
                ]
            ]
        },
        {
            "name": "tzdata",
            "specs": [
                [
                    "==",
                    "2024.2"
                ]
            ]
        },
        {
            "name": "url-normalize",
            "specs": [
                [
                    "==",
                    "1.4.3"
                ]
            ]
        },
        {
            "name": "urllib3",
            "specs": [
                [
                    "==",
                    "2.2.3"
                ]
            ]
        },
        {
            "name": "yarl",
            "specs": [
                [
                    "==",
                    "1.18.0"
                ]
            ]
        },
        {
            "name": "zipp",
            "specs": [
                [
                    "==",
                    "3.21.0"
                ]
            ]
        }
    ],
    "lcname": "nanocontroller"
}
        
Elapsed time: 1.60463s