pyduro


Namepyduro JSON
Version 3.2.1 PyPI version JSON
download
home_pagehttps://github.com/clementprevot/pyduro
SummaryA Pypi library to communicate with Aduro (H1) wood/pellet burner via NBE communication
upload_time2023-01-11 09:38:33
maintainer
docs_urlNone
authorClément PRÉVOT
requires_python>=3.6
licenseMIT
keywords aduro h1 wood pellet burner nbe
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![GitHub Latest Release][releases_shield]][latest_release]
[![GitHub All Releases][downloads_total_shield]][releases]
[![Buy me a coffee][buy_me_a_coffee_shield]][buy_me_a_coffee]
[![PayPal.Me][paypal_me_shield]][paypal_me]

[latest_release]: https://github.com/clementprevot/pyduro/releases/latest
[releases_shield]: https://img.shields.io/github/release/clementprevot/pyduro.svg?style=for-the-badge
[releases]: https://github.com/clementprevot/pyduro/releases
[downloads_total_shield]: https://img.shields.io/github/downloads/clementprevot/pyduro/total?style=for-the-badge
[buy_me_a_coffee_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20pizza&color=ff6937&style=for-the-badge&logo=buy%20me%20a%20coffee&logoColor=white
[buy_me_a_coffee]: https://www.buymeacoffee.com/clementprevot
[paypal_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&style=for-the-badge&logo=paypal
[paypal_me]: https://paypal.me/clementprevot

# PyDuro

A Pypi library to communicate with Aduro (H1) wood/pellet burner via NBE
communication.

## About

This library is intented to communicate with NBE capable burners (typically
Adura hybrid stoves).

### Request

NBE is an UDP protocol that uses ASCII frames to communicate. Here is an example
frame (this one is used to discover a burner):

```
abcdefghijkl041837 \x02000012345678904361465775pad 013NBE Discovery\x04
```

And here the different part of the frame:

* **abcdefghijkl** - `appId`, 12 alphanumerical (upper and lowercase) characters -
  Uniquely identify the application that is talking with the oven _(you can
  put here whatever you want)_
* **123456** - `controllerId`, 6 digits - This is the "Serial number" of your
  oven _(the same as the one you entered in the official Aduro application, you
  can find it on a sticker on the oven, often inside of the door)_
* **space** or **\*** or **-** - Encryption level of the frame _(' ' = not
  encrypted, '*' = RSA encrypted, '-' = XTEA encrypted)_
* **\x02** - `startChar`, the ASCII character `0x02` _(so 1 char, not "\x02")_ -
  This is the separator to identify the beginning of the request
* **00** - `function`, 2 digits - Identify the type of request _(see below)_
* **00** - `sequenceNumber`, 2 digits - To identify the request when run in
  sequence _(optional, you can leave this at 00 if you don't care about the
  order of your requests)_
* **1234567890** - `pinCode`, 10 digit - The password to connect to your oven
  _(the same as the one you entered in the official Aduro application, you can
  find it on a sticker on the oven, often inside of the door)_
* **4361465775** - `time`, 10 digits - The timestamp at which the request as
  been issued _(`'{:0>10.10}'.format(str(time()))` in Python do the trick)_
* **pad** _(literally the string "pad ") - Extra space reserved for future use
* **013** - `payloadSize`, 3 digits - The size of the actual payload of the
  request _(that comes right after this)_
* **NBE Discovery** - `payload`, max 495 bytes - The actual payload of the
  request _(here in the example it's a discovery request)_.
* **\x04** - `endChar`, the ASCII character `0x04` _(so 1 char, not "\x04")_ -
  This is the separator to identify the end of the payload

### Response

A response will always be formed in pretty much the same way:

```
abcdefghijkl123456\x0200000065Serial=123456;IP=192.168.1.250;Type=v13std;Ver=705;Build=38;Lang=0\x04
```

* **abcdefghijkl** - `appId`, 12 alphanumerical (upper and lowercase) characters -
  Uniquely identify the application that the oven  is talking to
* **123456** - `controllerId`, 6 digits - This is the "Serial number" of the
  oven responding
* **\x02** - `startChar`, the ASCII character `0x02` _(so 1 char, not "\x02")_ -
  This is the separator to identify the beginning of the response
* **00** - `function`, 2 digits - Identify the type of response _(see below)_
* **00** - `sequenceNumber`, 2 digits - To identify the request this response is
  for when using a sequence
* **0** - `status`, 1 digit - The status of the response _(0 = success, >0 =
  error)_
* **013** - `payloadSize`, 3 digits - The size of the actual payload of the
  response _(that comes right after this)_
* **Serial=12345;IP=192.168.1.250;Type=v13std;Ver=705;Build=38;Lang=0** -
  `payload`, max 1007 bytes - The actual payload of the response _(here in the
  example it's a discovery response in the form of a semicolon separated list of
  key/values pair)_.
* **\x04** - `endChar`, the ASCII character `0x04` _(so 1 char, not "\x04")_ -
  This is the separator to identify the end of the payload

### Functions

There is a limited set of functions you can use in the NBE protocol:

* **0**: Discovery
* **1**: Read settings value
* **2**: Set settings value
* **3**: Read setup range
* **4**: Read operating data
* **5**: Read advanced data
* **6**: Read consumption data
* **7**: Read chart data
* **8**: Read event log
* **9**: Read info
* **10**: Read available programs

> Note that your burner might not support all of them and might also support
> others (for example, `11` is a supported type on Aduro H1 burners).

### More info

You can learn more about NBE with
[this repository](https://github.com/motoz/nbetest)
_(I highly recommend you to play with the CLI to discover more about the protocol)_
and in particular
[this documentation](https://github.com/motoz/nbetest/blob/master/protocol.md)
as well as the various implementations.

You can also read [this issue](https://github.com/clementprevot/pyduro/issues/1)
and the implementation of the protocol in the PyDuro library.

## Installation

```bash
pip install pyduro
```

## Lib usage

Simply import the actions and use them:

```python
from pyduro.actions import discover, get, set, raw

discover.run()
get.run(
  burner_address="<burner IP address>",
  serial="<burner serial number>",
  pin_code="<burner pin code>",
  function_name="<settings|range|operating|advanced|consumption|chart|logs|info|versions>",
  path="<path>"
)
set.run(
  burner_address="<burner IP address>",
  serial="<burner serial number>",
  pin_code="<burner pin code>",
  path="<path>"
  value="<value>"
)
raw.run(
  burner_address="<burner IP address>",
  serial="<burner serial number>",
  pin_code="<burner pin code>",
  function_id="<function ID>",
  payload="<payload>"
)
```

### Response

Every response from a burner will be composed with the same fields:

* `frame`: the whole raw NBE frame received
* `burner_address`: the burner IP address from which the response originated
* `burner_port`: the burner UDP port from which the response originated
* `app_id`: the application ID the response is intended to
  _(when using PyDuro will always be `___pyduro___`)_
* `serial`: the burner serial number from which the response originated
* `function`: the function identifier the response is intended to
* `sequence_number`: the identifier of the request the response is intended to
* `status`: the status of the response _(0 = success, >0 = error)_
* `payload_size`: the size of the payload of the response
* `payload`: the actual response payload

You can also use the `parse_payload` method that will return:

* a string if the payload is a string
* a dict if the payload is a semicolon separated list of fields _(`name=value`)_
* a list of the payload is a semicolon separated list of values

## CLI usage

### Integrated help

```bash
python -m pyduro --help
```

### Discover a burner

```bash
python -m pyduro [discover]
```

The CLI will exit with 0 if a burner is found, 1 otherwise.

### Get the status of a burner

```bash
python -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> status
```

The result will be output as a JSON object that you can then manipulate with
`jq` for example.

The CLI will exit with the return code return by the burner (0 = success, >0 =
error).

**Examples**

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 status

> {
>   "boiler_temp": "14.9",
>   "boiler_ref": "20.0",
>   "content": "-2038",
>   "dhw_temp": "13.6",
>   "dhw_ref": "0.0",
>   [...]
> }
```

### Get information from a burner

```bash
python -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> get <settings|range|operating|advanced|consumption|chart|logs|info|versions> "<path>"
```

The result will be output as a JSON object that you can then manipulate with
`jq` for example.

The CLI will exit with the return code return by the burner (0 = success, >0 =
error).

> For `logs` action, you can pass "now" as path (which is also the default
> value) to get the latest logs from your burner.

> For `settings` action, you have to pass one of the valid following path:
>
> * "boiler",
> * "hot_water"
> * "regulation"
> * "weather"
> * "weather2"
> * "oxygen"
> * "cleaning"
> * "hopper"
> * "fan"
> * "auger"
> * "ignition"
> * "pump"
> * "sun"
> * "vacuum"
> * "misc"
> * "alarm"
> * "manual"
>
> To see all sub element of a path, add `.*` at the end of the path.  
> To see a specific element of a path, add `.<element name>` at the end of the
> path.

> For `consumption` action, you can pass one of the following path:
>
> * "total_hours",
> * "total_days"
> * "total_months"
> * "total_years"
> * "dhw_hours"
> * "dhw_days"
> * "dhw_months"
> * "dhw_years"
> * "counter"

**Examples**

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get operating

> {
>   "NA": "38",
>   "air_flow": "0",
>   "air_quality": "0",
>   "ashbox_contact": "0.0",
>   "ashbox_minutes": "0.0",
>   "back_pressure": "0",
>   "boiler_pump_state": "0",
>   "boiler_ref": "19.0",
>   [...]
> }
```

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get operating "boiler_ref"

> "boiler_ref=19.0"
```

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get settings "misc.*"

> {
>   [...]
>   "start": "0",
>   "stop": "0",
>   [...]
> }
```

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get settings "misc.start"

> "start=0"
```

> Note that you can pass "*" as a path to get the full response from your
> burner.  
> If you don't give a pass (or give an empty one) then "*" will be used
> as default.

### Update a burner's setting

```bash
python -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> set "<path>" "<value>"
```

The CLI will exit with the return code return by the burner (0 = success, >0 =
error).

**Examples**

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 set "misc.start" "1"
```

```bash
python -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 set "misc.stop" "1"
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/clementprevot/pyduro",
    "name": "pyduro",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "",
    "keywords": "aduro h1 wood pellet burner nbe",
    "author": "Cl\u00e9ment PR\u00c9VOT",
    "author_email": "clementprevot+pypi@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/60/96/db48a38e3feb6f1ef3e9f5f0167ccfed804f8cc8cc24a76c1309b43851f2/pyduro-3.2.1.tar.gz",
    "platform": null,
    "description": "[![GitHub Latest Release][releases_shield]][latest_release]\n[![GitHub All Releases][downloads_total_shield]][releases]\n[![Buy me a coffee][buy_me_a_coffee_shield]][buy_me_a_coffee]\n[![PayPal.Me][paypal_me_shield]][paypal_me]\n\n[latest_release]: https://github.com/clementprevot/pyduro/releases/latest\n[releases_shield]: https://img.shields.io/github/release/clementprevot/pyduro.svg?style=for-the-badge\n[releases]: https://github.com/clementprevot/pyduro/releases\n[downloads_total_shield]: https://img.shields.io/github/downloads/clementprevot/pyduro/total?style=for-the-badge\n[buy_me_a_coffee_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20pizza&color=ff6937&style=for-the-badge&logo=buy%20me%20a%20coffee&logoColor=white\n[buy_me_a_coffee]: https://www.buymeacoffee.com/clementprevot\n[paypal_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&style=for-the-badge&logo=paypal\n[paypal_me]: https://paypal.me/clementprevot\n\n# PyDuro\n\nA Pypi library to communicate with Aduro (H1) wood/pellet burner via NBE\ncommunication.\n\n## About\n\nThis library is intented to communicate with NBE capable burners (typically\nAdura hybrid stoves).\n\n### Request\n\nNBE is an UDP protocol that uses ASCII frames to communicate. Here is an example\nframe (this one is used to discover a burner):\n\n```\nabcdefghijkl041837 \\x02000012345678904361465775pad 013NBE Discovery\\x04\n```\n\nAnd here the different part of the frame:\n\n* **abcdefghijkl** - `appId`, 12 alphanumerical (upper and lowercase) characters -\n  Uniquely identify the application that is talking with the oven _(you can\n  put here whatever you want)_\n* **123456** - `controllerId`, 6 digits - This is the \"Serial number\" of your\n  oven _(the same as the one you entered in the official Aduro application, you\n  can find it on a sticker on the oven, often inside of the door)_\n* **space** or **\\*** or **-** - Encryption level of the frame _(' ' = not\n  encrypted, '*' = RSA encrypted, '-' = XTEA encrypted)_\n* **\\x02** - `startChar`, the ASCII character `0x02` _(so 1 char, not \"\\x02\")_ -\n  This is the separator to identify the beginning of the request\n* **00** - `function`, 2 digits - Identify the type of request _(see below)_\n* **00** - `sequenceNumber`, 2 digits - To identify the request when run in\n  sequence _(optional, you can leave this at 00 if you don't care about the\n  order of your requests)_\n* **1234567890** - `pinCode`, 10 digit - The password to connect to your oven\n  _(the same as the one you entered in the official Aduro application, you can\n  find it on a sticker on the oven, often inside of the door)_\n* **4361465775** - `time`, 10 digits - The timestamp at which the request as\n  been issued _(`'{:0>10.10}'.format(str(time()))` in Python do the trick)_\n* **pad** _(literally the string \"pad \") - Extra space reserved for future use\n* **013** - `payloadSize`, 3 digits - The size of the actual payload of the\n  request _(that comes right after this)_\n* **NBE Discovery** - `payload`, max 495 bytes - The actual payload of the\n  request _(here in the example it's a discovery request)_.\n* **\\x04** - `endChar`, the ASCII character `0x04` _(so 1 char, not \"\\x04\")_ -\n  This is the separator to identify the end of the payload\n\n### Response\n\nA response will always be formed in pretty much the same way:\n\n```\nabcdefghijkl123456\\x0200000065Serial=123456;IP=192.168.1.250;Type=v13std;Ver=705;Build=38;Lang=0\\x04\n```\n\n* **abcdefghijkl** - `appId`, 12 alphanumerical (upper and lowercase) characters -\n  Uniquely identify the application that the oven  is talking to\n* **123456** - `controllerId`, 6 digits - This is the \"Serial number\" of the\n  oven responding\n* **\\x02** - `startChar`, the ASCII character `0x02` _(so 1 char, not \"\\x02\")_ -\n  This is the separator to identify the beginning of the response\n* **00** - `function`, 2 digits - Identify the type of response _(see below)_\n* **00** - `sequenceNumber`, 2 digits - To identify the request this response is\n  for when using a sequence\n* **0** - `status`, 1 digit - The status of the response _(0 = success, >0 =\n  error)_\n* **013** - `payloadSize`, 3 digits - The size of the actual payload of the\n  response _(that comes right after this)_\n* **Serial=12345;IP=192.168.1.250;Type=v13std;Ver=705;Build=38;Lang=0** -\n  `payload`, max 1007 bytes - The actual payload of the response _(here in the\n  example it's a discovery response in the form of a semicolon separated list of\n  key/values pair)_.\n* **\\x04** - `endChar`, the ASCII character `0x04` _(so 1 char, not \"\\x04\")_ -\n  This is the separator to identify the end of the payload\n\n### Functions\n\nThere is a limited set of functions you can use in the NBE protocol:\n\n* **0**: Discovery\n* **1**: Read settings value\n* **2**: Set settings value\n* **3**: Read setup range\n* **4**: Read operating data\n* **5**: Read advanced data\n* **6**: Read consumption data\n* **7**: Read chart data\n* **8**: Read event log\n* **9**: Read info\n* **10**: Read available programs\n\n> Note that your burner might not support all of them and might also support\n> others (for example, `11` is a supported type on Aduro H1 burners).\n\n### More info\n\nYou can learn more about NBE with\n[this repository](https://github.com/motoz/nbetest)\n_(I highly recommend you to play with the CLI to discover more about the protocol)_\nand in particular\n[this documentation](https://github.com/motoz/nbetest/blob/master/protocol.md)\nas well as the various implementations.\n\nYou can also read [this issue](https://github.com/clementprevot/pyduro/issues/1)\nand the implementation of the protocol in the PyDuro library.\n\n## Installation\n\n```bash\npip install pyduro\n```\n\n## Lib usage\n\nSimply import the actions and use them:\n\n```python\nfrom pyduro.actions import discover, get, set, raw\n\ndiscover.run()\nget.run(\n  burner_address=\"<burner IP address>\",\n  serial=\"<burner serial number>\",\n  pin_code=\"<burner pin code>\",\n  function_name=\"<settings|range|operating|advanced|consumption|chart|logs|info|versions>\",\n  path=\"<path>\"\n)\nset.run(\n  burner_address=\"<burner IP address>\",\n  serial=\"<burner serial number>\",\n  pin_code=\"<burner pin code>\",\n  path=\"<path>\"\n  value=\"<value>\"\n)\nraw.run(\n  burner_address=\"<burner IP address>\",\n  serial=\"<burner serial number>\",\n  pin_code=\"<burner pin code>\",\n  function_id=\"<function ID>\",\n  payload=\"<payload>\"\n)\n```\n\n### Response\n\nEvery response from a burner will be composed with the same fields:\n\n* `frame`: the whole raw NBE frame received\n* `burner_address`: the burner IP address from which the response originated\n* `burner_port`: the burner UDP port from which the response originated\n* `app_id`: the application ID the response is intended to\n  _(when using PyDuro will always be `___pyduro___`)_\n* `serial`: the burner serial number from which the response originated\n* `function`: the function identifier the response is intended to\n* `sequence_number`: the identifier of the request the response is intended to\n* `status`: the status of the response _(0 = success, >0 = error)_\n* `payload_size`: the size of the payload of the response\n* `payload`: the actual response payload\n\nYou can also use the `parse_payload` method that will return:\n\n* a string if the payload is a string\n* a dict if the payload is a semicolon separated list of fields _(`name=value`)_\n* a list of the payload is a semicolon separated list of values\n\n## CLI usage\n\n### Integrated help\n\n```bash\npython -m pyduro --help\n```\n\n### Discover a burner\n\n```bash\npython -m pyduro [discover]\n```\n\nThe CLI will exit with 0 if a burner is found, 1 otherwise.\n\n### Get the status of a burner\n\n```bash\npython -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> status\n```\n\nThe result will be output as a JSON object that you can then manipulate with\n`jq` for example.\n\nThe CLI will exit with the return code return by the burner (0 = success, >0 =\nerror).\n\n**Examples**\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 status\n\n> {\n>   \"boiler_temp\": \"14.9\",\n>   \"boiler_ref\": \"20.0\",\n>   \"content\": \"-2038\",\n>   \"dhw_temp\": \"13.6\",\n>   \"dhw_ref\": \"0.0\",\n>   [...]\n> }\n```\n\n### Get information from a burner\n\n```bash\npython -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> get <settings|range|operating|advanced|consumption|chart|logs|info|versions> \"<path>\"\n```\n\nThe result will be output as a JSON object that you can then manipulate with\n`jq` for example.\n\nThe CLI will exit with the return code return by the burner (0 = success, >0 =\nerror).\n\n> For `logs` action, you can pass \"now\" as path (which is also the default\n> value) to get the latest logs from your burner.\n\n> For `settings` action, you have to pass one of the valid following path:\n>\n> * \"boiler\",\n> * \"hot_water\"\n> * \"regulation\"\n> * \"weather\"\n> * \"weather2\"\n> * \"oxygen\"\n> * \"cleaning\"\n> * \"hopper\"\n> * \"fan\"\n> * \"auger\"\n> * \"ignition\"\n> * \"pump\"\n> * \"sun\"\n> * \"vacuum\"\n> * \"misc\"\n> * \"alarm\"\n> * \"manual\"\n>\n> To see all sub element of a path, add `.*` at the end of the path.  \n> To see a specific element of a path, add `.<element name>` at the end of the\n> path.\n\n> For `consumption` action, you can pass one of the following path:\n>\n> * \"total_hours\",\n> * \"total_days\"\n> * \"total_months\"\n> * \"total_years\"\n> * \"dhw_hours\"\n> * \"dhw_days\"\n> * \"dhw_months\"\n> * \"dhw_years\"\n> * \"counter\"\n\n**Examples**\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get operating\n\n> {\n>   \"NA\": \"38\",\n>   \"air_flow\": \"0\",\n>   \"air_quality\": \"0\",\n>   \"ashbox_contact\": \"0.0\",\n>   \"ashbox_minutes\": \"0.0\",\n>   \"back_pressure\": \"0\",\n>   \"boiler_pump_state\": \"0\",\n>   \"boiler_ref\": \"19.0\",\n>   [...]\n> }\n```\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get operating \"boiler_ref\"\n\n> \"boiler_ref=19.0\"\n```\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get settings \"misc.*\"\n\n> {\n>   [...]\n>   \"start\": \"0\",\n>   \"stop\": \"0\",\n>   [...]\n> }\n```\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 get settings \"misc.start\"\n\n> \"start=0\"\n```\n\n> Note that you can pass \"*\" as a path to get the full response from your\n> burner.  \n> If you don't give a pass (or give an empty one) then \"*\" will be used\n> as default.\n\n### Update a burner's setting\n\n```bash\npython -m pyduro -b <burner IP address> -s <burner serial number> -p <burner pin code> set \"<path>\" \"<value>\"\n```\n\nThe CLI will exit with the return code return by the burner (0 = success, >0 =\nerror).\n\n**Examples**\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 set \"misc.start\" \"1\"\n```\n\n```bash\npython -m pyduro -b 192.168.1.250 -s 1234 -p 12345678 set \"misc.stop\" \"1\"\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Pypi library to communicate with Aduro (H1) wood/pellet burner via NBE communication",
    "version": "3.2.1",
    "split_keywords": [
        "aduro",
        "h1",
        "wood",
        "pellet",
        "burner",
        "nbe"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6096db48a38e3feb6f1ef3e9f5f0167ccfed804f8cc8cc24a76c1309b43851f2",
                "md5": "53fbc6f2464b1d60242572e328aa8829",
                "sha256": "2073a6b2f8a4052e39de22890b8b0326c97f52b3c6b0fce73719ae9923680ae0"
            },
            "downloads": -1,
            "filename": "pyduro-3.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "53fbc6f2464b1d60242572e328aa8829",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 15116,
            "upload_time": "2023-01-11T09:38:33",
            "upload_time_iso_8601": "2023-01-11T09:38:33.075456Z",
            "url": "https://files.pythonhosted.org/packages/60/96/db48a38e3feb6f1ef3e9f5f0167ccfed804f8cc8cc24a76c1309b43851f2/pyduro-3.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-01-11 09:38:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "clementprevot",
    "github_project": "pyduro",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "pyduro"
}
        
Elapsed time: 0.02724s