├── tests ├── responses │ ├── TS-EC1280U-4.5.2 │ │ ├── volumes.json │ │ ├── systemhealth.json │ │ ├── bandwidth.json │ │ ├── login.xml │ │ ├── systemhealth.xml │ │ ├── volumes.xml │ │ ├── systemstats.json │ │ ├── bandwidth.xml │ │ ├── smartdiskhealth.json │ │ ├── systemstats.xml │ │ └── smartdiskhealth.xml │ ├── TS-1677XU-RP-4.5.2 │ │ ├── volumes.json │ │ ├── systemhealth.json │ │ ├── bandwidth.json │ │ ├── login.xml │ │ ├── systemhealth.xml │ │ ├── systemstats.json │ │ ├── bandwidth.xml │ │ ├── smartdiskhealth.json │ │ ├── volumes.xml │ │ ├── systemstats.xml │ │ └── smartdiskhealth.xml │ ├── TS-451-4.2.2 │ │ ├── systemhealth.json │ │ ├── bandwidth.json │ │ ├── smartdiskhealth.json │ │ ├── volumes.json │ │ ├── login.xml │ │ ├── systemstats.json │ │ ├── systemhealth.xml │ │ ├── bandwidth.xml │ │ ├── volumes.xml │ │ ├── smartdiskhealth.xml │ │ └── systemstats.xml │ ├── TS-420-4.3.3 │ │ ├── firmwareupdate.json │ │ ├── login.xml │ │ └── firmwareupdate.xml │ ├── TS-X53-4.5.4 │ │ ├── bandwidth.xml │ │ ├── bandwidth.json │ │ ├── login.xml │ │ └── bandwidth2.xml │ ├── TS-110-4.2.4 │ │ ├── smartdiskhealth.json │ │ ├── login.xml │ │ └── smartdiskhealth.xml │ ├── TS-659-4.2.6 │ │ ├── login.xml │ │ ├── smartdiskhealth.json │ │ └── smartdiskhealth.xml │ ├── TS-639-4.2.3 │ │ ├── login.xml │ │ ├── systemstats.json │ │ ├── volumes.json │ │ ├── systemstats.xml │ │ └── volumes.xml │ ├── TS-251-4.5.1 │ │ ├── login_with_get.xml │ │ ├── login.xml │ │ ├── systemstats.json │ │ └── systemstats.xml │ └── TS-410-4.2.3 │ │ ├── systemstats.json │ │ ├── login.xml │ │ └── systemstats.xml ├── __init__.py └── test-models.py ├── MANIFEST.in ├── requirements.txt ├── .hound.yml ├── .gitignore ├── qnapstats ├── __init__.py └── qnap_stats.py ├── pyproject.toml ├── requirements.testing.txt ├── pylintrc ├── .github ├── ISSUE_TEMPLATE.md ├── stale.yml └── workflows │ ├── publish.yml │ ├── release.yaml │ └── test.yml ├── .releaserc.json ├── tox.ini ├── CHANGELOG.md ├── LICENSE ├── setup.py ├── debug.py └── README.rst /tests/responses/TS-EC1280U-4.5.2/volumes.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/volumes.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/systemhealth.json: -------------------------------------------------------------------------------- 1 | "good" -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | xmltodict>=0.10.0 3 | 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Functional tests for qnapstats""" 2 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/systemhealth.json: -------------------------------------------------------------------------------- 1 | "good" -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/systemhealth.json: -------------------------------------------------------------------------------- 1 | "good" -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | python: 2 | enabled: true 3 | config_file: tox.ini 4 | -------------------------------------------------------------------------------- /tests/responses/TS-420-4.3.3/firmwareupdate.json: -------------------------------------------------------------------------------- 1 | "4.3.4.0695 Build 20180830" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | qnapstats.egg-info 4 | .tox 5 | /.idea 6 | __pycache__ 7 | -------------------------------------------------------------------------------- /qnapstats/__init__.py: -------------------------------------------------------------------------------- 1 | """Main module for QNAPStats.""" 2 | from .qnap_stats import QNAPStats # noqa: F401 3 | -------------------------------------------------------------------------------- /tests/responses/TS-X53-4.5.4/bandwidth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /requirements.testing.txt: -------------------------------------------------------------------------------- 1 | requests 2 | xmltodict>=0.10.0 3 | 4 | responses>=0.5.1 5 | flake8 6 | isort 7 | pydocstyle 8 | pylint 9 | 10 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | reports=no 3 | disable=I 4 | max-args=6 5 | 6 | [MESSAGES CONTROL] 7 | disable=line-too-long,len-as-condition,broad-except,invalid-name -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/bandwidth.json: -------------------------------------------------------------------------------- 1 | {"eth0": {"is_default": false, "name": "LAN 1", "rx": 10732, "tx": 345770}, "eth1": {"is_default": true, "name": "LAN 2", "rx": 3594, "tx": 5052}} -------------------------------------------------------------------------------- /tests/responses/TS-X53-4.5.4/bandwidth.json: -------------------------------------------------------------------------------- 1 | {"eth0": {"is_default": true, "name": "Adapter 1", "rx": 3157, "tx": 656}, "eth1": {"is_default": false, "name": "Adapter 2", "rx": 0, "tx": 0}} 2 | -------------------------------------------------------------------------------- /tests/responses/TS-110-4.2.4/smartdiskhealth.json: -------------------------------------------------------------------------------- 1 | {"1": {"capacity": "2.73 TB", "drive_number": "1", "health": "OK", "model": "WDC WD30EFRX-68EUZN0", "serial": "WD-WCC4N0795818", "temp_c": 37, "temp_f": 98, "type": "hdd"}} -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/bandwidth.json: -------------------------------------------------------------------------------- 1 | {"eth0": {"is_default": true, "name": "LAN 1", "rx": 6092, "tx": 96}, "eth1": {"is_default": false, "name": "LAN 2", "rx": 6092, "tx": 87}, "eth2": {"is_default": false, "name": "LAN 3", "rx": 7132, "tx": 6111}, "eth3": {"is_default": false, "name": "LAN 4", "rx": 63, "tx": 0}} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please provide the following information along with your issue report (replace anything inside of `<>`): 2 | 3 | **Device Model Number**: 4 | 5 | **QTS Version**: <4.2.3> 6 | 7 | If you're reporting a `KeyError`, please also provide the XML response from your QNAP device. You can easily obtain this by running the `debug.py` script. 8 | 9 | **XML/Debug Output:** 10 | 11 | ~~~ 12 | 13 | ~~~ 14 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/bandwidth.json: -------------------------------------------------------------------------------- 1 | {"bond0": {"is_default": false, "name": "LAN 5+6", "rx": 7035, "tx": 0}, "eth0": {"is_default": false, "name": "LAN 1", "rx": 0, "tx": 0}, "eth1": {"is_default": false, "name": "LAN 2", "rx": 0, "tx": 0}, "eth2": {"is_default": false, "name": "LAN 3", "rx": 0, "tx": 0}, "eth3": {"is_default": false, "name": "LAN 4", "rx": 5041, "tx": 13702}, "eth4": {"is_default": false, "name": "LAN 5", "rx": 3518, "tx": 0}, "eth5": {"is_default": false, "name": "LAN 6", "rx": 3518, "tx": 0}} -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/smartdiskhealth.json: -------------------------------------------------------------------------------- 1 | {"0:1": {"capacity": "2.73 TB", "drive_number": "0:1", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 32, "temp_f": 89, "type": "hdd"}, "0:2": {"capacity": "2.73 TB", "drive_number": "0:2", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 32, "temp_f": 89, "type": "hdd"}, "0:3": {"capacity": "2.73 TB", "drive_number": "0:3", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 31, "temp_f": 87, "type": "hdd"}, "0:4": {"capacity": "2.73 TB", "drive_number": "0:4", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 28, "temp_f": 82, "type": "hdd"}} -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/volumes.json: -------------------------------------------------------------------------------- 1 | {"DataVol1": {"folders": [{"sharename": "Multimedia", "used_size": 602112}, {"sharename": "Download", "used_size": 264058204160}, {"sharename": "Recordings", "used_size": 4096}, {"sharename": "Web", "used_size": 8192}, {"sharename": "Public", "used_size": 4096}, {"sharename": "homes", "used_size": 90112}, {"sharename": "TmpRestore", "used_size": 0}, {"sharename": "Shared", "used_size": 4770844930048}, {"sharename": "Containers", "used_size": 8080171008}, {"sharename": "IncomingBackups", "used_size": 126976}, {"sharename": "System Reserved", "used_size": 25579089920}], "free_size": 865955594240, "id": "1", "label": "DataVol1", "total_size": 5934928510976}} -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-659-4.2.6/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-110-4.2.4/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <![CDATA[]]> 4 | -------------------------------------------------------------------------------- /tests/responses/TS-659-4.2.6/smartdiskhealth.json: -------------------------------------------------------------------------------- 1 | {"1": {"capacity": "1.82 TB", "drive_number": "1", "health": "OK", "model": "WDC WD20EFRX-68EUZN0", "serial": "WD-WCC4M3AHY36Y", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "2": {"capacity": "1.82 TB", "drive_number": "2", "health": "OK", "model": "WDC WD20EFRX-68EUZN0", "serial": "WD-WCC4M5TXJDV9", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "3": {"capacity": "149.05 GB", "drive_number": "3", "health": "OK", "model": "INTEL SSDSA2M160G2GN", "serial": "CVPO13060046160AGN", "temp_c": null, "temp_f": null, "type": "hdd"}, "6": {"capacity": "2.73 TB", "drive_number": "6", "health": "OK", "model": "WDC WD30EFRX-68EUZN0", "serial": "WD-WMC4N2255621", "temp_c": 37, "temp_f": 98, "type": "hdd"}} -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tagFormat": "${version}", 3 | "branches": [ 4 | "master" 5 | ], 6 | "repositoryUrl": "https://github.com/colinodell/python-qnapstats.git", 7 | "plugins": [ 8 | "@semantic-release/commit-analyzer", 9 | "@semantic-release/exec", 10 | "@semantic-release/release-notes-generator", 11 | "@semantic-release/changelog", 12 | [ 13 | "@semantic-release/git", 14 | { 15 | "assets": [ 16 | "CHANGELOG.md" 17 | ], 18 | "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" 19 | } 20 | ], 21 | "@semantic-release/github" 22 | ] 23 | } -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, py37, py38, py39, desc 3 | skip_missing_interpreters = True 4 | 5 | [gh-actions] 6 | python = 7 | 3.5: py35 8 | 3.6: py36 9 | 3.7: py37 10 | 3.8: py38 11 | 3.9: py39, desc 12 | 13 | [flake8] 14 | max-line-length = 120 15 | exclude = .git,__pycache__,.tox 16 | 17 | [testenv] 18 | setenv = 19 | LANG=C.UTF-8 20 | PYTHONPATH = {toxinidir}:{toxinidir}/qnapstats 21 | commands = 22 | flake8 23 | pylint qnapstats 24 | pydocstyle qnapstats 25 | python tests/test-models.py 26 | deps = -r{toxinidir}/requirements.testing.txt 27 | 28 | [testenv:desc] 29 | deps = 30 | docutils 31 | Pygments 32 | commands = 33 | python setup.py check --restructuredtext --strict 34 | -------------------------------------------------------------------------------- /tests/responses/TS-639-4.2.3/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <![CDATA[]]> -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 21 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - on hold 9 | - security 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | recent activity. It will be closed if no further activity occurs. Thank you 16 | for your contributions. 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false 19 | -------------------------------------------------------------------------------- /tests/responses/TS-251-4.5.1/login_with_get.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-639-4.2.3/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": null, "temp_c": 43, "temp_f": 109, "usage_percent": 8.0}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "2017/01/21", "patch": "0", "version": "4.2.3"}, "memory": {"free": 1250.8, "total": 2021.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.5", "link_status": "Up", "mac": "00:08:9b:8c:fb:b0", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 96552797, "tx_packets": 83455310, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9b:8c:fb:b1", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "system": {"model": "TS-639", "name": "QNAP-NAS", "serial_number": "--", "temp_c": 33, "temp_f": 91, "timezone": "(GMT-05:00) Eastern Time(US & Canada)"}, "uptime": {"days": 17, "hours": 12, "minutes": 16, "seconds": 45}} -------------------------------------------------------------------------------- /tests/responses/TS-251-4.5.1/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <![CDATA[]]> 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/responses/TS-251-4.5.1/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": null, "temp_c": null, "temp_f": null, "usage_percent": 48.5}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "21-01-2017", "patch": "0", "version": "4.2.3"}, "memory": {"free": 60.8, "total": 249.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.101", "link_status": "Up", "mac": "00:08:9B:C1:80:6A", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 193439491, "tx_packets": 123234929, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9B:C1:80:6B", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "system": {"model": "TS-410", "name": "hornbill", "serial_number": "MYSERIAL", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 13, "hours": 17, "minutes": 47, "seconds": 48}} -------------------------------------------------------------------------------- /tests/responses/TS-410-4.2.3/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": null, "temp_c": null, "temp_f": null, "usage_percent": 48.5}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "21-01-2017", "patch": "0", "version": "4.2.3"}, "memory": {"free": 60.8, "total": 249.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.101", "link_status": "Up", "mac": "00:08:9B:C1:80:6A", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 193439491, "tx_packets": 123234929, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9B:C1:80:6B", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "system": {"model": "TS-410", "name": "hornbill", "serial_number": "MYSERIAL", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 13, "hours": 17, "minutes": 47, "seconds": 48}} -------------------------------------------------------------------------------- /tests/responses/TS-410-4.2.3/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <![CDATA[]]> 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": "Intel(R) Celeron(R) CPU J1800 @ 2.41GHz", "temp_c": 39, "temp_f": 102, "usage_percent": 2.3}, "dns": ["192.168.1.1", "8.8.8.8"], "firmware": {"build": "20161208", "build_time": "2016/12/08", "patch": "0", "version": "4.2.2"}, "memory": {"free": 5974.8, "total": 7880.3}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.10", "link_status": "Up", "mac": "00:08:9b:f1:d3:34", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 144182777, "tx_packets": 261627662, "usage": "STATIC"}, "eth1": {"err_packets": 0, "ip": "192.168.1.11", "link_status": "Up", "mac": "00:08:9b:f1:d3:35", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 974322250, "tx_packets": 1300296822, "usage": "STATIC"}}, "system": {"model": "TS-451", "name": "Apollo", "serial_number": "Q---", "temp_c": 38, "temp_f": 100, "timezone": "(GMT-05:00) Eastern Time(US & Canada)"}, "uptime": {"days": 63, "hours": 16, "minutes": 20, "seconds": 38}} -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish distributions to PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-and-publish: 9 | name: Build and publish distributions to PyPI 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@master 13 | 14 | - name: Set up Python 3.7 15 | uses: actions/setup-python@v1 16 | with: 17 | python-version: 3.7 18 | 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | 26 | - name: Build a binary wheel and a source tarball 27 | run: >- 28 | python -m 29 | build 30 | --sdist 31 | --wheel 32 | --outdir dist/ 33 | 34 | - name: Publish distribution to PyPI 35 | uses: pypa/gh-action-pypi-publish@master 36 | with: 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /tests/responses/TS-420-4.3.3/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-639-4.2.3/volumes.json: -------------------------------------------------------------------------------- 1 | {"Volume 3": {"folders": [{"sharename": "TMBackup", "used_size": 1702238154752}, {"sharename": "System Reserved", "used_size": 33955840}], "free_size": 264101576704, "id": "3", "label": "Volume 3", "total_size": 1967428317184}, "Volume 4": {"folders": [{"sharename": "Qmultimedia", "used_size": 682364760064}, {"sharename": "Qrecordings", "used_size": 548954112}, {"sharename": "Qdownload", "used_size": 277272903680}, {"sharename": "Qweb", "used_size": 1220763648}, {"sharename": "Qusb", "used_size": 2046537859072}, {"sharename": "Public", "used_size": 330590863360}, {"sharename": "Network Recycle Bin 1", "used_size": 733184}, {"sharename": "QTemp", "used_size": 4096}, {"sharename": "Qhome", "used_size": 35692544}, {"sharename": "My_VM", "used_size": 82662420480}, {"sharename": "homes", "used_size": 37457920}, {"sharename": "WebDav", "used_size": 23797760}, {"sharename": "System Reserved", "used_size": 17268264960}], "free_size": 4432205987840, "id": "4", "label": "Volume 4", "total_size": 7872852889600}} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.6.0](https://github.com/colinodell/python-qnapstats/compare/0.5.0...0.6.0) (2025-06-25) 2 | 3 | 4 | ### Features 5 | 6 | * adding system fan monitoring ([#98](https://github.com/colinodell/python-qnapstats/issues/98)) ([e9254ee](https://github.com/colinodell/python-qnapstats/commit/e9254eec25f72802d17a34f09a47c1f80039f8fd)) 7 | 8 | # [0.5.0](https://github.com/colinodell/python-qnapstats/compare/0.4.0...0.5.0) (2022-06-07) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * add missing token ([2b9eefa](https://github.com/colinodell/python-qnapstats/commit/2b9eefa1f901e96f02ed56b54239bdab4fd88d7c)) 14 | * change main branch in releaserc.json ([c976249](https://github.com/colinodell/python-qnapstats/commit/c9762495395ff8586fac643e0ecc5f8405dad30d)) 15 | 16 | 17 | ### Features 18 | 19 | * add external devices informations ([508f385](https://github.com/colinodell/python-qnapstats/commit/508f3850d9ced07d4a9a33829c2b7f5264929c64)) 20 | * add external devices informations ([1b73056](https://github.com/colinodell/python-qnapstats/commit/1b73056035480f9d26755f6a3a7c0ab551cdbf0d)) 21 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/systemhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # For release build - release that - Github action tag and deploy it on Google Cloud Storage 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v2 17 | with: 18 | # Token is used to push on main branch 19 | submodules: true # Fetch Hugo themes (true OR recursive) 20 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod 21 | 22 | - name: Semantic Release 23 | id: semantic 24 | uses: cycjimmy/semantic-release-action@v2.6.0 25 | with: 26 | semantic_version: 17.3.7 27 | extra_plugins: | 28 | @semantic-release/commit-analyzer@8.0.1 29 | @semantic-release/release-notes-generator@9.0.1 30 | @semantic-release/changelog@5.0.1 31 | @semantic-release/github@7.2.0 32 | @semantic-release/exec@5.0.0 33 | @semantic-release/git@9.0.0 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | # As long as we need Python 3.6 here in the test, we can only use up to Ubuntu 20. 10 | # https://github.com/actions/setup-python/issues/544 11 | runs-on: ubuntu-20.04 12 | strategy: 13 | matrix: 14 | include: 15 | - python-version: '3.6' 16 | toxenv: py36 17 | - python-version: '3.7' 18 | toxenv: py37 19 | - python-version: '3.8' 20 | toxenv: py38 21 | - python-version: '3.9' 22 | toxenv: py39 23 | - python-version: '3.9' 24 | toxenv: desc 25 | 26 | steps: 27 | - uses: actions/checkout@v1 28 | 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install tox tox-gh-actions 38 | 39 | - name: Test with tox 40 | env: 41 | TOXENV: ${{ matrix.toxenv }} 42 | run: tox -v 43 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <![CDATA[]]> -------------------------------------------------------------------------------- /tests/responses/TS-X53-4.5.4/login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <![CDATA[]]> 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Colin O'Dell 4 | 5 | Some code based on `python-synology`, copyright (c) 2017 StaticCube 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | import io 5 | 6 | from setuptools import setup 7 | 8 | setup( 9 | name='qnapstats', 10 | description='Python API for obtaining QNAP NAS system stats', 11 | long_description=io.open('README.rst', encoding='utf-8').read(), 12 | version='0.4.0', 13 | license='MIT', 14 | author='Colin O\'Dell', 15 | author_email='colinodell@gmail.com', 16 | url='https://github.com/colinodell/python-qnapstats', 17 | packages=['qnapstats'], 18 | keywords=['qnap'], 19 | classifiers=[ 20 | 'Intended Audience :: Developers', 21 | 'Intended Audience :: System Administrators', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python :: 3', 25 | 'Programming Language :: Python :: 3.5', 26 | 'Programming Language :: Python :: 3.6', 27 | 'Programming Language :: Python :: 3.7', 28 | 'Programming Language :: Python :: 3.8', 29 | 'Programming Language :: Python :: 3.9', 30 | 'Topic :: Home Automation', 31 | 'Topic :: System :: Monitoring' 32 | ], 33 | install_requires=['requests>=1.0.0', 'xmltodict>=0.10.0'] 34 | ) 35 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/bandwidth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import getpass 3 | import traceback 4 | import qnapstats 5 | 6 | host = input("Host (prefix with 'https://' if needed): ") 7 | port = int(input("Port: ")) 8 | username = input("Username: ") 9 | password = getpass.getpass("Password: ") 10 | 11 | qnap = qnapstats.QNAPStats(host, port, username, password, debugmode=True, verify_ssl=False) 12 | 13 | try: 14 | qnap.get_system_stats() 15 | except Exception as e: 16 | print(e.args) 17 | traceback.print_exc() 18 | 19 | try: 20 | qnap.get_system_health() 21 | except Exception as e: 22 | print(e.args) 23 | traceback.print_exc() 24 | 25 | try: 26 | qnap.get_smart_disk_health() 27 | except Exception as e: 28 | print(e.args) 29 | traceback.print_exc() 30 | 31 | try: 32 | qnap.get_volumes() 33 | except Exception as e: 34 | print(e.args) 35 | traceback.print_exc() 36 | 37 | try: 38 | qnap.get_bandwidth() 39 | except Exception as e: 40 | print(e.args) 41 | traceback.print_exc() 42 | 43 | try: 44 | qnap.get_external_drive() 45 | except Exception as e: 46 | print(e.args) 47 | traceback.print_exc() 48 | 49 | 50 | try: 51 | qnap.get_storage_information_on_external_device() 52 | except Exception as e: 53 | print(e.args) 54 | traceback.print_exc() 55 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/systemhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/systemhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": "AMD Ryzen 7 2700 Eight-Core Processor", "temp_c": 59, "temp_f": 138, "usage_percent": 4.5}, "dns": ["192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206"], "firmware": {"build": "20210302", "build_time": "2021/03/02", "patch": "0", "version": "4.5.2"}, "memory": {"free": 12990.6, "total": 15966.0}, "nics": {"eth0": {"err_packets": 0, "ip": "169.254.5.239", "link_status": "Up", "mac": "24:5e:be:38:87:10", "mask": "255.255.0.0", "max_speed": 1000, "rx_packets": 28503230, "tx_packets": 431819, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "169.254.5.244", "link_status": "Up", "mac": "24:5e:be:38:87:11", "mask": "255.255.0.0", "max_speed": 1000, "rx_packets": 28479859, "tx_packets": 455355, "usage": "DHCP"}, "eth2": {"err_packets": 1, "ip": "192.168.11.7", "link_status": "Up", "mac": "24:5e:be:38:87:13", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 29011169, "tx_packets": 681064, "usage": "STATIC"}, "eth3": {"err_packets": 213, "ip": "192.168.168.51", "link_status": "Up", "mac": "24:5e:be:38:87:12", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 175315116, "tx_packets": 2950389, "usage": "STATIC"}}, "system": {"model": "TS-1677XU-RP", "name": "QNAP06", "serial_number": "Q191I16905", "temp_c": 33, "temp_f": 91, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 7, "hours": 7, "minutes": 39, "seconds": 38}} -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/volumes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/responses/TS-110-4.2.4/smartdiskhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/bandwidth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/systemstats.json: -------------------------------------------------------------------------------- 1 | {"cpu": {"model": "Intel(R) Xeon(R) CPU E3-1246 v3 @ 3.50GHz", "temp_c": 44, "temp_f": 111, "usage_percent": 1.2}, "dns": ["192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206"], "firmware": {"build": "20210302", "build_time": "2021/03/02", "patch": "0", "version": "4.5.2"}, "memory": {"free": 1517.0, "total": 3892.0}, "nics": {"eth0": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2d", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2e", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth2": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2f", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth3": {"err_packets": 0, "ip": "192.168.11.2", "link_status": "Up", "mac": "24:5e:be:04:28:30", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 7526374, "tx_packets": 123045, "usage": "STATIC"}, "eth4": {"err_packets": 0, "ip": "192.168.168.22", "link_status": "Up", "mac": "24:5e:be:03:8f:67", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 7546025, "tx_packets": 460, "usage": "STATIC"}, "eth5": {"err_packets": 0, "ip": "192.168.168.23", "link_status": "Up", "mac": "24:5e:be:03:8f:66", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 70955067, "tx_packets": 38577237, "usage": "STATIC"}}, "system": {"model": "TS-EC1280U", "name": "NAS04282D", "serial_number": "Q166I14410", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 2, "hours": 1, "minutes": 26, "seconds": 49}} -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/bandwidth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/smartdiskhealth.json: -------------------------------------------------------------------------------- 1 | {"0:1": {"capacity": "7.28 TB", "drive_number": "0:1", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWP5", "temp_c": 37, "temp_f": 98, "type": "hdd"}, "0:10": {"capacity": "7.28 TB", "drive_number": "0:10", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13QM5R", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:11": {"capacity": "7.28 TB", "drive_number": "0:11", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13R992", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:12": {"capacity": "7.28 TB", "drive_number": "0:12", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13GF9A", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:2": {"capacity": "7.28 TB", "drive_number": "0:2", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWQE", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:3": {"capacity": "7.28 TB", "drive_number": "0:3", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WD5Y", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:4": {"capacity": "7.28 TB", "drive_number": "0:4", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA10VMTS", "temp_c": 35, "temp_f": 95, "type": "hdd"}, "0:5": {"capacity": "7.28 TB", "drive_number": "0:5", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWQM", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:6": {"capacity": "7.28 TB", "drive_number": "0:6", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA10QTPP", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:7": {"capacity": "7.28 TB", "drive_number": "0:7", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12Z2VN", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:8": {"capacity": "7.28 TB", "drive_number": "0:8", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12Z2VR", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:9": {"capacity": "7.28 TB", "drive_number": "0:9", "health": "OK", "model": "ST8000NE0011-1YG112", "serial": "ZA1483VC", "temp_c": 37, "temp_f": 98, "type": "hdd"}, "23:1": {"capacity": "465.76 GB", "drive_number": "23:1", "health": "OK", "model": "SSD 860 EVO mSATA 500GB", "serial": "S41NNB0K404351Z", "temp_c": 41, "temp_f": 105, "type": "ssd"}, "23:2": {"capacity": "465.76 GB", "drive_number": "23:2", "health": "OK", "model": "SSD 860 EVO mSATA 500GB", "serial": "S41NNB0K404390L", "temp_c": 41, "temp_f": 105, "type": "ssd"}} -------------------------------------------------------------------------------- /tests/responses/TS-420-4.3.3/firmwareupdate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/smartdiskhealth.json: -------------------------------------------------------------------------------- 1 | {"0:1": {"capacity": "894.25 GB", "drive_number": "0:1", "health": "OK", "model": "5200_MTFDDAK960TDD", "serial": "185220CAF3A9", "temp_c": 32, "temp_f": 89, "type": "ssd"}, "0:10": {"capacity": "10.91 TB", "drive_number": "0:10", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKMB1H", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:11": {"capacity": "10.91 TB", "drive_number": "0:11", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHK9B5H", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:12": {"capacity": "10.91 TB", "drive_number": "0:12", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL21UH", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:13": {"capacity": "10.91 TB", "drive_number": "0:13", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKMV3H", "temp_c": 39, "temp_f": 102, "type": "hdd"}, "0:14": {"capacity": "10.91 TB", "drive_number": "0:14", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL2E7H", "temp_c": 40, "temp_f": 104, "type": "hdd"}, "0:15": {"capacity": "10.91 TB", "drive_number": "0:15", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKPUGH", "temp_c": 42, "temp_f": 107, "type": "hdd"}, "0:16": {"capacity": "10.91 TB", "drive_number": "0:16", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKS1TH", "temp_c": 41, "temp_f": 105, "type": "hdd"}, "0:2": {"capacity": "894.25 GB", "drive_number": "0:2", "health": "OK", "model": "5200_MTFDDAK960TDD", "serial": "185220CAF50F", "temp_c": 35, "temp_f": 95, "type": "ssd"}, "0:3": {"capacity": "10.91 TB", "drive_number": "0:3", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKGNRH", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:4": {"capacity": "10.91 TB", "drive_number": "0:4", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKM6RH", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:5": {"capacity": "10.91 TB", "drive_number": "0:5", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKRXVH", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:6": {"capacity": "10.91 TB", "drive_number": "0:6", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKY0MH", "temp_c": 40, "temp_f": 104, "type": "hdd"}, "0:7": {"capacity": "10.91 TB", "drive_number": "0:7", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL2BZH", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:8": {"capacity": "10.91 TB", "drive_number": "0:8", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKRY2H", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:9": {"capacity": "10.91 TB", "drive_number": "0:9", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKS01H", "temp_c": 41, "temp_f": 105, "type": "hdd"}} -------------------------------------------------------------------------------- /tests/responses/TS-251-4.5.1/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 0 3 | 4 | 5 | 6 | 193439491 7 | 123234929 8 | 0 9 | 1 10 | 1000 11 | 192.168.1.101 12 | 255.255.255.0 13 | 00:08:9B:C1:80:6A 14 | DHCP 15 | 0 16 | 0 17 | 0 18 | 0 19 | 1000 20 | 0.0.0.0 21 | 0.0.0.0 22 | 00:08:9B:C1:80:6B 23 | DHCP 24 | 26 | 40 27 | 104 28 | 0 29 | MYSERIAL 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/responses/TS-410-4.2.3/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 0 3 | 4 | 5 | 6 | 193439491 7 | 123234929 8 | 0 9 | 1 10 | 1000 11 | 192.168.1.101 12 | 255.255.255.0 13 | 00:08:9B:C1:80:6A 14 | DHCP 15 | 0 16 | 0 17 | 0 18 | 0 19 | 1000 20 | 0.0.0.0 21 | 0.0.0.0 22 | 00:08:9B:C1:80:6B 23 | DHCP 24 | 26 | 40 27 | 104 28 | 0 29 | MYSERIAL 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/volumes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/responses/TS-639-4.2.3/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | 5 | 6 | 7 | 96552797 8 | 83455310 9 | 0 10 | 1 11 | 1000 12 | 192.168.1.5 13 | 255.255.255.0 14 | 00:08:9b:8c:fb:b0 15 | DHCP 16 | 0 17 | 0 18 | 0 19 | 0 20 | 1000 21 | 0.0.0.0 22 | 0.0.0.0 23 | 00:08:9b:8c:fb:b1 24 | DHCP 25 | 26 | 43109 27 | 33 28 | 91 29 | 779 30 | 0 31 | 728 32 | 0 33 | 2 34 | -- 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/volumes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/smartdiskhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/responses/TS-451-4.2.2/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 0 9 | 10 | 11 | 144182777 12 | 261627662 13 | 0 14 | 1 15 | 1000 16 | 0 17 | 192.168.1.10 18 | 255.255.255.0 19 | 00:08:9b:f1:d3:34 20 | STATIC 21 | 974322250 22 | 1300296822 23 | 0 24 | 1 25 | 1000 26 | 0 27 | 192.168.1.11 28 | 255.255.255.0 29 | 00:08:9b:f1:d3:35 30 | STATIC 31 | 34 | 39102 35 | 38 36 | 100 37 | 1 38 | 527 39 | 0 40 | 0 41 | NAS 42 | Q--- 43 | QW37AR32 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/responses/TS-X53-4.5.4/bandwidth2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 1 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /tests/responses/TS-639-4.2.3/volumes.xml: -------------------------------------------------------------------------------- 1 | 2 | 0 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/responses/TS-659-4.2.6/smartdiskhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/test-models.py: -------------------------------------------------------------------------------- 1 | """Functional tests where the QNAP responses are mocked""" 2 | # -*- coding:utf-8 -*- 3 | import base64 4 | import json 5 | import os 6 | import qnapstats 7 | import responses 8 | 9 | 10 | def get_immediate_subdirectories(a_dir): 11 | return [name for name in os.listdir(a_dir) 12 | if os.path.isdir(os.path.join(a_dir, name))] 13 | 14 | 15 | response_directory = os.path.join(os.path.dirname(__file__), 'responses') 16 | models = get_immediate_subdirectories(response_directory) 17 | 18 | 19 | def add_mock_responses(rsps, directory): 20 | rsps.add(responses.POST, 21 | 'http://localhost:8080/cgi-bin/authLogin.cgi', 22 | body=file_get_contents(directory, 'login.xml'), 23 | status=200, 24 | content_type='text/xml') 25 | if file_get_contents(directory, "login_with_get.xml"): 26 | pwd = base64.b64encode("correcthorsebatterystaple".encode('utf-8')).decode('ascii') 27 | rsps.add(responses.GET, 28 | 'http://localhost:8080/cgi-bin/authLogin.cgi?user=admin&pwd=' + pwd, 29 | body=file_get_contents(directory, 'login_with_get.xml'), 30 | status=200, 31 | content_type='text/xml') 32 | xml = file_get_contents(directory, 'bandwidth.xml') 33 | if xml is not None: 34 | rsps.add(responses.GET, 35 | 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=QSM40bandwidth&sid=12345', 36 | match_querystring=True, 37 | body=xml, 38 | status=200, 39 | content_type='text/xml') 40 | 41 | xml = file_get_contents(directory, 'bandwidth2.xml') 42 | if xml is not None: 43 | rsps.add(responses.GET, 44 | 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=bandwidth&sid=12345', 45 | match_querystring=True, 46 | body=xml, 47 | status=200, 48 | content_type='text/xml') 49 | 50 | xml = file_get_contents(directory, 'systemhealth.xml') 51 | if xml is not None: 52 | rsps.add(responses.GET, 53 | 'http://localhost:8080/cgi-bin/management/manaRequest.cgi?subfunc=sysinfo&sysHealth=1&sid=12345', 54 | match_querystring=True, 55 | body=file_get_contents(directory, 'systemhealth.xml'), 56 | status=200, 57 | content_type='text/xml') 58 | 59 | xml = file_get_contents(directory, 'volumes.xml') 60 | if xml is not None: 61 | rsps.add(responses.GET, 62 | 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=disk_usage&disk_select=all&include=all&sid=12345', # noqa: E501 63 | match_querystring=True, 64 | body=file_get_contents(directory, 'volumes.xml'), 65 | status=200, 66 | content_type='text/xml') 67 | 68 | xml = file_get_contents(directory, 'smartdiskhealth.xml') 69 | if xml is not None: 70 | rsps.add(responses.GET, 71 | 'http://localhost:8080/cgi-bin/disk/qsmart.cgi?func=all_hd_data&sid=12345', 72 | match_querystring=True, 73 | body=file_get_contents(directory, 'smartdiskhealth.xml'), 74 | status=200, 75 | content_type='text/xml') 76 | 77 | xml = file_get_contents(directory, 'systemstats.xml') 78 | if xml is not None: 79 | rsps.add(responses.GET, 80 | 'http://localhost:8080/cgi-bin/management/manaRequest.cgi?subfunc=sysinfo&hd=no&multicpu=1&sid=12345', 81 | match_querystring=True, 82 | body=file_get_contents(directory, 'systemstats.xml'), 83 | status=200, 84 | content_type='text/xml') 85 | 86 | xml = file_get_contents(directory, 'firmwareupdate.xml') 87 | if xml is not None: 88 | rsps.add(responses.GET, 89 | 'http://localhost:8080/cgi-bin/sys/sysRequest.cgi?subfunc=firm_update&sid=12345', 90 | match_querystring=True, 91 | body=file_get_contents(directory, 'firmwareupdate.xml'), 92 | status=200, 93 | content_type='text/xml') 94 | 95 | 96 | def file_get_contents(directory, file): 97 | file = os.path.join(response_directory, directory, file) 98 | if not os.path.exists(file): 99 | return None 100 | 101 | with open(file, 'r') as myfile: 102 | return myfile.read() 103 | 104 | 105 | for model_directory in models: 106 | qnap = qnapstats.QNAPStats("localhost", 8080, "admin", "correcthorsebatterystaple") 107 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: 108 | add_mock_responses(rsps, model_directory) 109 | 110 | bandwidth = file_get_contents(model_directory, 'bandwidth.json') 111 | if bandwidth is not None: 112 | assert json.dumps(qnap.get_bandwidth(), sort_keys=True) == bandwidth.rstrip() 113 | 114 | smartdiskhealth = file_get_contents(model_directory, 'smartdiskhealth.json') 115 | if smartdiskhealth is not None: 116 | assert json.dumps(qnap.get_smart_disk_health(), sort_keys=True) == smartdiskhealth 117 | 118 | systemhealth = file_get_contents(model_directory, 'systemhealth.json') 119 | if systemhealth is not None: 120 | assert json.dumps(qnap.get_system_health(), sort_keys=True) == systemhealth 121 | 122 | systemstats = file_get_contents(model_directory, 'systemstats.json') 123 | if systemstats is not None: 124 | assert json.dumps(qnap.get_system_stats(), sort_keys=True) == systemstats 125 | 126 | volumes = file_get_contents(model_directory, 'volumes.json') 127 | if volumes is not None: 128 | assert json.dumps(qnap.get_volumes(), sort_keys=True) == volumes 129 | 130 | firmwareupdate = file_get_contents(model_directory, 'firmwareupdate.json') 131 | if firmwareupdate is not None: 132 | assert json.dumps(qnap.get_firmware_update(), sort_keys=True) == firmwareupdate 133 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 0 9 | 10 | 11 | 12 | 13 | 3 14 | eth0 15 | Adapter 1 16 | 28503230 17 | 431819 18 | 0 19 | 1 20 | 1000 21 | 0 22 | 169.254.5.239 23 | 255.255.0.0 24 | 24:5e:be:38:87:10 25 | DHCP 26 | 0192.168.15.14 27 | 2 28 | eth1 29 | Adapter 2 30 | 28479859 31 | 455355 32 | 0 33 | 1 34 | 1000 35 | 0 36 | 169.254.5.244 37 | 255.255.0.0 38 | 24:5e:be:38:87:11 39 | DHCP 40 | 0192.168.15.14 41 | 192.168.15.206 42 | 5 43 | eth2 44 | Adapter 3 45 | 29011169 46 | 681064 47 | 1 48 | 1 49 | 10000 50 | 0 51 | 192.168.11.7 52 | 255.255.255.0 53 | 24:5e:be:38:87:13 54 | STATIC 55 | 0192.168.15.14 56 | 192.168.15.206 57 | 4 58 | eth3 59 | Adapter 4 60 | 175315116 61 | 2950389 62 | 213 63 | 1 64 | 10000 65 | 0 66 | 192.168.168.51 67 | 255.255.255.0 68 | 24:5e:be:38:87:12 69 | STATIC 70 | 0192.168.15.14 71 | 192.168.15.206 72 | 73 | 76 | 59138 77 | 33 78 | 91 79 | 4 80 | SYS FAN 81 | 1489 82 | 0 83 | 0 84 | 1476 85 | 0 86 | 0 87 | 1487 88 | 0 89 | 0 90 | 1497 91 | 0 92 | 0 93 | -2 94 | -2 95 | 0 96 | 0 97 | -2 98 | -2 99 | NAS 100 | Q191I16905 101 | QZ49AR55 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/systemstats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 0 9 | 10 | 11 | 12 | 13 | 2 14 | eth0 15 | Adapter 1 16 | 0 17 | 0 18 | 0 19 | 0 20 | 1000 21 | 0 22 | 0.0.0.0 23 | 0.0.0.0 24 | 24:5e:be:04:28:2d 25 | DHCP 26 | 0192.168.15.14 27 | 192.168.15.206 28 | 5 29 | eth1 30 | Adapter 2 31 | 0 32 | 0 33 | 0 34 | 0 35 | 1000 36 | 0 37 | 0.0.0.0 38 | 0.0.0.0 39 | 24:5e:be:04:28:2e 40 | DHCP 41 | 0192.168.15.14 42 | 192.168.15.206 43 | 4 44 | eth2 45 | Adapter 3 46 | 0 47 | 0 48 | 0 49 | 0 50 | 1000 51 | 0 52 | 0.0.0.0 53 | 0.0.0.0 54 | 24:5e:be:04:28:2f 55 | DHCP 56 | 0192.168.15.14 57 | 192.168.15.206 58 | 3 59 | eth3 60 | Adapter 4 61 | 7526374 62 | 123045 63 | 0 64 | 1 65 | 1000 66 | 0 67 | 192.168.11.2 68 | 255.255.255.0 69 | 24:5e:be:04:28:30 70 | STATIC 71 | 0192.168.15.14 72 | 192.168.15.206 73 | 7 74 | eth4 75 | Adapter 5 76 | 7546025 77 | 460 78 | 0 79 | 1 80 | 10000 81 | 0 82 | 192.168.168.22 83 | 255.255.255.0 84 | 24:5e:be:03:8f:67 85 | STATIC 86 | 0192.168.15.14 87 | 192.168.15.206 88 | 6 89 | eth5 90 | Adapter 6 91 | 70955067 92 | 38577237 93 | 0 94 | 1 95 | 10000 96 | 0 97 | 192.168.168.23 98 | 255.255.255.0 99 | 24:5e:be:03:8f:66 100 | STATIC 101 | 0192.168.15.14 102 | 192.168.15.206 103 | 104 | 105 | 44111 106 | 40 107 | 104 108 | 4 109 | SYS FAN 110 | 6108 111 | 0 112 | 0 113 | 6108 114 | 0 115 | 0 116 | 6192 117 | 0 118 | 0 119 | 6081 120 | 0 121 | 0 122 | -2 123 | -2 124 | 0 125 | 0 126 | -2 127 | -2 128 | NAS 129 | Q166I14410 130 | QV99IR34 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | python-qnapstats 3 | ================ 4 | 5 | .. image:: https://img.shields.io/github/actions/workflow/status/colinodell/python-qnapstats/test.yml?branch=master&?style=flat-square 6 | :target: https://github.com/colinodell/python-qnapstats/actions?query=workflow%3ATest+branch%3Amaster 7 | :alt: Build Status 8 | .. image:: https://img.shields.io/pypi/pyversions/qnapstats.svg?style=flat-square 9 | :target: https://pypi.python.org/pypi/qnapstats 10 | :alt: Supported Python Versions 11 | 12 | Library from obtaining system information from QNAP NAS devices running QTS. 13 | 14 | Installation 15 | ============ 16 | 17 | This library requires `xmltodict`, so make sure you have that installed: 18 | 19 | .. code-block:: bash 20 | 21 | pip3 install xmltodict>=0.10.0 22 | 23 | Then install this Python module: 24 | 25 | .. code-block:: bash 26 | 27 | pip3 install qnapstats 28 | 29 | Usage Example 30 | ============= 31 | 32 | .. code-block:: python 33 | 34 | #!/usr/bin/env python3 35 | from qnapstats import QNAPStats 36 | from pprint import pprint 37 | 38 | qnap = QNAPStats('192.168.1.3', 8080, 'admin', 'correcthorsebatterystaple') 39 | 40 | pprint(qnap.get_system_stats()) 41 | pprint(qnap.get_system_health()) 42 | pprint(qnap.get_smart_disk_health()) 43 | pprint(qnap.get_volumes()) 44 | pprint(qnap.get_bandwidth()) 45 | 46 | Account 47 | ======= 48 | The account you connect with must have system monitoring permissions. The simplest 49 | option is to put it in the admin group; it doesn't necessarily 50 | need to be THE "administrator" account, but you can use some account in the 51 | administrators group. 52 | 53 | Alternatively you can configure a normal account and enable Delegated Administration 54 | in control panel, and activate "System Monitoring." 55 | 56 | Once the account is created, and/or you upgrade to a newer of like QTS 5, 57 | also be sure to log into your NAS and complete any agreements, warnings, wizards, etc. 58 | that may prevent this library from using the QNAP API. 59 | 60 | MFA/2FA must also be disabled for that user for this library to work. 61 | 62 | Device Support 63 | ============== 64 | 65 | This library has been tested against the following devices and firmwares: 66 | 67 | +------------------+------------------------+----------------------------------------+ 68 | | Model | QTS* Firmware Versions | Notes | 69 | +==================+========================+========================================+ 70 | | D4 Pro | 4.5.1 | User-reported: no automated tests | 71 | +------------------+------------------------+----------------------------------------+ 72 | | TS-110 | 4.2.4 | | 73 | +------------------+------------------------+----------------------------------------+ 74 | | TS-112P | 4.3.3 | This device does not report CPU temps | 75 | +------------------+------------------------+----------------------------------------+ 76 | | TS-210 | 4.2.6 | This device does not report CPU temps | 77 | +------------------+------------------------+----------------------------------------+ 78 | | TS-219P II | 4.3.3 | User-reported: no automated tests | 79 | +------------------+------------------------+----------------------------------------+ 80 | | TS-251B | 4.4.3 | | 81 | +------------------+------------------------+----------------------------------------+ 82 | | TS-228A | 5.0.1 | This device does not report CPU temps | 83 | +------------------+------------------------+----------------------------------------+ 84 | | TS-233 | 5.1.x | | 85 | +------------------+------------------------+----------------------------------------+ 86 | | TS-251+ | 4.5.1 | No information on dnsInfo | 87 | +------------------+------------------------+----------------------------------------+ 88 | | TS-253 Pro | 4.5.2 | | 89 | +------------------+------------------------+----------------------------------------+ 90 | | TS-253D | 4.5.3 | | 91 | +------------------+------------------------+----------------------------------------+ 92 | | TS-332 | 5.0.0 | | 93 | +------------------+------------------------+----------------------------------------+ 94 | | TS-364 | 5.0.1 | | 95 | +------------------+------------------------+----------------------------------------+ 96 | | TS-269L | 4.3.3 | User-reported: no automated tests | 97 | +------------------+------------------------+----------------------------------------+ 98 | | TS-410 | 4.2.3 | This device does not report CPU temps | 99 | +------------------+------------------------+----------------------------------------+ 100 | | TS-412 | 4.3.3 | This device does not report CPU temps | 101 | +------------------+------------------------+----------------------------------------+ 102 | | TS-431P | 4.3.4 | | 103 | +------------------+------------------------+----------------------------------------+ 104 | | TS-439 Pro II+ | 4.2.6 | | 105 | +------------------+------------------------+----------------------------------------+ 106 | | TS-451 | 4.2.2 - 4.2.4 | | 107 | +------------------+------------------------+----------------------------------------+ 108 | | TS-453A | 4.3.4; 5.0.1 | | 109 | +------------------+------------------------+----------------------------------------+ 110 | | TS-453Be | 4.2.3; 5.0.1 | | 111 | +------------------+------------------------+----------------------------------------+ 112 | | TS-464 | 5.2.7 | User-reported: no automated tests | 113 | +------------------+------------------------+----------------------------------------+ 114 | | TS-639 | 4.2.3 | | 115 | +------------------+------------------------+----------------------------------------+ 116 | | TS-659 | 4.2.6 | May report `None` for some disk temps | 117 | +------------------+------------------------+----------------------------------------+ 118 | | TS-853 Pro | 4.5.4 | | 119 | +------------------+------------------------+----------------------------------------+ 120 | | TS-869 Pro | 4.3.4 | | 121 | +------------------+------------------------+----------------------------------------+ 122 | | TS-873A | 5.0.1.2248 | | 123 | +------------------+------------------------+----------------------------------------+ 124 | | TS-1677XU-RP | 4.5.2 | | 125 | +------------------+------------------------+----------------------------------------+ 126 | | TS-EC1280U | 4.5.2 | | 127 | +------------------+------------------------+----------------------------------------+ 128 | | TS-h886 | QuTS h5.0.1.2376 | | 129 | +------------------+------------------------+----------------------------------------+ 130 | | TS-X53 | 4.5.4 | | 131 | +------------------+------------------------+----------------------------------------+ 132 | | TVS-672N | 5.0.1 | | 133 | +------------------+------------------------+----------------------------------------+ 134 | | TVS-1282 | 5.0.1 | | 135 | +------------------+------------------------+----------------------------------------+ 136 | 137 | ⚠️ *QuTS is not currently supported - see [issue #84](https://github.com/colinodell/python-qnapstats/issues/84)* 138 | 139 | Other QNAP devices using these QTS firmwares should probably work fine, as should the devices listed above on newer firmwares. 140 | If you encounter any compatibility issues, please let us know (or better yet, contribute a patch!) 141 | 142 | 143 | **Upgrading to QTS 5?** Make sure the account you connect with meets the criteria listed earlier in this README. 144 | Also be sure to log into your NAS and complete any agreements, warnings, wizards, etc. that may prevent this 145 | library from using the QNAP API. 146 | -------------------------------------------------------------------------------- /tests/responses/TS-EC1280U-4.5.2/smartdiskhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/responses/TS-1677XU-RP-4.5.2/smartdiskhealth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /qnapstats/qnap_stats.py: -------------------------------------------------------------------------------- 1 | """Module containing multiple classes to obtain QNAP system stats via cgi calls.""" 2 | # -*- coding:utf-8 -*- 3 | import base64 4 | import json 5 | import xmltodict 6 | import requests 7 | 8 | 9 | # pylint: disable=too-many-instance-attributes 10 | class QNAPStats: 11 | """Class containing the main functions.""" 12 | 13 | # pylint: disable=too-many-arguments 14 | def __init__(self, host, port, username, password, debugmode=False, verify_ssl=True, timeout=5): 15 | """Instantiate a new qnap_stats object.""" 16 | self._username = username 17 | self._password = base64.b64encode(password.encode('utf-8')).decode('ascii') 18 | 19 | self._sid = None 20 | self._debugmode = debugmode 21 | 22 | self._session_error = False 23 | self._session = None # type: requests.Session 24 | 25 | if not (host.startswith("http://") or host.startswith("https://")): 26 | host = "http://" + host 27 | 28 | self._verify_ssl = verify_ssl 29 | self._timeout = timeout 30 | 31 | self._base_url = f"{host}:{port}/cgi-bin/" 32 | 33 | def _debuglog(self, message): 34 | """Output message if debug mode is enabled.""" 35 | if self._debugmode: 36 | print("DEBUG: " + message) 37 | 38 | def _init_session(self): 39 | if self._sid is None or self._session is None or self._session_error: 40 | # Clear sid and reset error 41 | self._sid = None 42 | self._session_error = False 43 | 44 | if self._session is not None: 45 | self._session = None 46 | self._debuglog("Creating new session") 47 | self._session = requests.Session() 48 | 49 | # We created a new session so login 50 | if self._login() is False: 51 | self._session_error = True 52 | self._debuglog("Login failed, unable to process request") 53 | return 54 | 55 | def _login(self): 56 | """Log into QNAP and obtain a session id.""" 57 | data = {"user": self._username, "pwd": self._password} 58 | result = self._execute_post_url("authLogin.cgi", data, False) 59 | 60 | if result is None or not result.get("authSid"): 61 | # Another method to login 62 | suffix_url = "authLogin.cgi?user=" + self._username + "&pwd=" + self._password 63 | result = self._execute_get_url(suffix_url, False) 64 | 65 | if result is None: 66 | return False 67 | 68 | self._sid = result["authSid"] 69 | 70 | return True 71 | 72 | def _get_url(self, url, retry_on_error=True, **kwargs): 73 | """High-level function for making GET requests.""" 74 | self._init_session() 75 | 76 | result = self._execute_get_url(url, **kwargs) 77 | if (self._session_error or result is None) and retry_on_error: 78 | self._debuglog("Error occured, retrying...") 79 | self._get_url(url, False, **kwargs) 80 | 81 | return result 82 | 83 | def _execute_get_url(self, url, append_sid=True, **kwargs): 84 | """Low-level function to execute a GET request.""" 85 | url = self._base_url + url 86 | self._debuglog("GET from URL: " + url) 87 | 88 | if append_sid: 89 | self._debuglog("Appending access_token (SID: " + self._sid + ") to url") 90 | url = f"{url}&sid={self._sid}" 91 | 92 | resp = self._session.get(url, timeout=self._timeout, verify=self._verify_ssl) 93 | return self._handle_response(resp, **kwargs) 94 | 95 | def _execute_post_url(self, url, data, append_sid=True, **kwargs): 96 | """Low-level function to execute a POST request.""" 97 | url = self._base_url + url 98 | self._debuglog("POST to URL: " + url) 99 | 100 | if append_sid: 101 | self._debuglog("Appending access_token (SID: " + self._sid + ") to url") 102 | data["sid"] = self._sid 103 | 104 | resp = self._session.post(url, data, timeout=self._timeout, verify=self._verify_ssl) 105 | return self._handle_response(resp, **kwargs) 106 | 107 | def _handle_response(self, resp, force_list=None): 108 | """Ensure response is successful and return body as XML.""" 109 | self._debuglog("Request executed: " + str(resp.status_code)) 110 | if resp.status_code != 200: 111 | return None 112 | 113 | if resp.headers["Content-Type"] != "text/xml": 114 | # JSON requests not currently supported 115 | return None 116 | self._debuglog("Headers: " + json.dumps(dict(resp.headers))) 117 | self._debuglog("Cookies: " + json.dumps(dict(resp.cookies))) 118 | self._debuglog("Response Text: " + resp.text) 119 | data = xmltodict.parse(resp.content, force_list=force_list)['QDocRoot'] 120 | 121 | auth_passed = data.get('authPassed') 122 | if auth_passed is not None and len(auth_passed) == 1 and auth_passed == "0": 123 | self._session_error = True 124 | return None 125 | 126 | return data 127 | 128 | def get_system_health(self): 129 | """Obtain the system's overall health.""" 130 | resp = self._get_url("management/manaRequest.cgi?subfunc=sysinfo&sysHealth=1") 131 | if resp is None: 132 | return None 133 | 134 | status = resp["func"]["ownContent"]["sysHealth"]["status"] 135 | if status is None or len(status) == 0: 136 | return None 137 | 138 | return status 139 | 140 | def get_volumes(self): 141 | """Obtain information about volumes and shared directories.""" 142 | resp = self._get_url( 143 | "management/chartReq.cgi?chart_func=disk_usage&disk_select=all&include=all", 144 | force_list=("volume", "volumeUse", "folder_element") 145 | ) 146 | 147 | if resp is None: 148 | return None 149 | 150 | if resp["volumeList"] is None or resp["volumeUseList"] is None: 151 | return {} 152 | 153 | volumes = {} 154 | id_map = {} 155 | 156 | for vol in resp["volumeList"]["volume"]: 157 | key = vol["volumeValue"] 158 | label = vol["volumeLabel"] if "volumeLabel" in vol else "Volume " + vol["volumeValue"] 159 | 160 | volumes[label] = { 161 | "id": key, 162 | "label": label 163 | } 164 | 165 | id_map[key] = label 166 | 167 | for vol in resp["volumeUseList"]["volumeUse"]: 168 | id_number = vol["volumeValue"] 169 | 170 | # Skip any system reserved volumes 171 | if id_number not in id_map: 172 | continue 173 | 174 | key = id_map[id_number] 175 | 176 | volumes[key]["free_size"] = int(vol["free_size"]) 177 | volumes[key]["total_size"] = int(vol["total_size"]) 178 | 179 | folder_elements = vol["folder_element"] 180 | if len(folder_elements) > 0: 181 | volumes[key]["folders"] = [] 182 | for folder in folder_elements: 183 | try: 184 | sharename = folder["sharename"] 185 | used_size = int(folder["used_size"]) 186 | volumes[key]["folders"].append({"sharename": sharename, "used_size": used_size}) 187 | except Exception as e: 188 | print(e.args) 189 | 190 | return volumes 191 | 192 | def get_smart_disk_health(self): 193 | """Obtain SMART information about each disk.""" 194 | resp = self._get_url("disk/qsmart.cgi?func=all_hd_data", force_list="entry") 195 | 196 | if resp is None: 197 | return None 198 | 199 | disks = {} 200 | for disk in resp["Disk_Info"]["entry"]: 201 | if disk["Model"]: 202 | disks[disk["HDNo"]] = { 203 | "drive_number": disk["HDNo"], 204 | "health": disk["Health"], 205 | "temp_c": int(disk["Temperature"]["oC"]) if disk["Temperature"]["oC"] is not None else None, 206 | "temp_f": int(disk["Temperature"]["oF"]) if disk["Temperature"]["oF"] is not None else None, 207 | "capacity": disk["Capacity"], 208 | "model": disk["Model"], 209 | "serial": disk["Serial"], 210 | "type": "ssd" if ("hd_is_ssd" in disk and int(disk["hd_is_ssd"])) else "hdd", 211 | } 212 | 213 | return disks 214 | 215 | def get_system_stats(self): 216 | """Obtain core system information and resource utilization.""" 217 | resp = self._get_url( 218 | "management/manaRequest.cgi?subfunc=sysinfo&hd=no&multicpu=1", 219 | force_list=("DNS_LIST") 220 | ) 221 | 222 | if resp is None: 223 | return None 224 | 225 | root = resp["func"]["ownContent"]["root"] 226 | 227 | details = { 228 | "system": { 229 | "name": root["server_name"], 230 | "model": resp["model"]["displayModelName"], 231 | "serial_number": root["serial_number"], 232 | "temp_c": int(root["sys_tempc"]), 233 | "temp_f": int(root["sys_tempf"]), 234 | "timezone": root["timezone"], 235 | }, 236 | "firmware": { 237 | "version": resp["firmware"]["version"], 238 | "build": resp["firmware"]["build"], 239 | "patch": resp["firmware"]["patch"], 240 | "build_time": resp["firmware"]["buildTime"], 241 | }, 242 | "uptime": { 243 | "days": int(root["uptime_day"]), 244 | "hours": int(root["uptime_hour"]), 245 | "minutes": int(root["uptime_min"]), 246 | "seconds": int(root["uptime_sec"]), 247 | }, 248 | "cpu": { 249 | "model": root["cpu_model"] if "cpu_model" in root else None, 250 | "usage_percent": float(root["cpu_usage"].replace("%", "")), 251 | "temp_c": int(root["cpu_tempc"]) if "cpu_tempc" in root else None, 252 | "temp_f": int(root["cpu_tempf"]) if "cpu_tempf" in root else None, 253 | }, 254 | "memory": { 255 | "total": float(root["total_memory"]), 256 | "free": float(root["free_memory"]), 257 | }, 258 | "nics": {}, 259 | "dns": [], 260 | "sysfans": {}, 261 | } 262 | 263 | nic_count = int(root["nic_cnt"]) 264 | for nic_index in range(nic_count): 265 | i = str(nic_index + 1) 266 | interface = "eth" + str(nic_index) 267 | status = root["eth_status" + i] 268 | details["nics"][interface] = { 269 | "link_status": "Up" if status == "1" else "Down", 270 | "max_speed": int(root["eth_max_speed" + i]), 271 | "ip": root["eth_ip" + i], 272 | "mask": root["eth_mask" + i], 273 | "mac": root["eth_mac" + i], 274 | "usage": root["eth_usage" + i], 275 | "rx_packets": int(root["rx_packet" + i]), 276 | "tx_packets": int(root["tx_packet" + i]), 277 | "err_packets": int(root["err_packet" + i]) 278 | } 279 | 280 | sysfan_count = int(root["sysfan_count"]) 281 | for sysfan_index in range(sysfan_count): 282 | i = str(sysfan_index + 1) 283 | sysfan = "sysfan" + str(sysfan_index) 284 | details["sysfans"][sysfan] = { 285 | "speed": int(root["sysfan" + i]), 286 | "status": "alert" if int(root["sysfan" + i + "_stat"]) == -1 else "ok" 287 | } 288 | 289 | 290 | dnsInfo = root.get("dnsInfo") 291 | if dnsInfo: 292 | for dns in dnsInfo["DNS_LIST"]: 293 | details["dns"].append(dns) 294 | 295 | return details 296 | 297 | def get_bandwidth(self): 298 | """Obtain the current bandwidth usage speeds.""" 299 | resp = self._get_url( 300 | "management/chartReq.cgi?chart_func=QSM40bandwidth", 301 | force_list="item" 302 | ) 303 | 304 | if resp and "bandwidth_info" not in resp: 305 | # changes in API since QTS 4.5.4, old query returns no values 306 | resp = self._get_url("management/chartReq.cgi?chart_func=bandwidth") 307 | 308 | if resp is None: 309 | return None 310 | 311 | details = {} 312 | interfaces = [] 313 | bandwidth_info = resp["bandwidth_info"] 314 | 315 | default = resp.get("df_gateway") or bandwidth_info.get("df_gateway") 316 | 317 | if "item" in bandwidth_info: 318 | interfaces.extend(bandwidth_info["item"]) 319 | else: 320 | interfaceIds = [] 321 | if bandwidth_info["eth_index_list"]: 322 | for num in bandwidth_info["eth_index_list"].split(','): 323 | interfaceIds.append("eth" + num) 324 | if bandwidth_info["wlan_index_list"]: 325 | for num in bandwidth_info["wlan_index_list"].split(','): 326 | interfaceIds.append("wlan" + num) 327 | for interfaceId in interfaceIds: 328 | interface = bandwidth_info[interfaceId] 329 | interface["id"] = interfaceId 330 | interfaces.extend([interface]) 331 | 332 | for item in interfaces: 333 | details[item["id"]] = { 334 | "name": item["dname"] if "dname" in item else item["name"], 335 | "rx": round(int(item["rx"]) / 5), 336 | "tx": round(int(item["tx"]) / 5), 337 | "is_default": item["id"] == default 338 | } 339 | 340 | return details 341 | 342 | def get_firmware_update(self): 343 | """Get firmware update version if available.""" 344 | resp = self._get_url("sys/sysRequest.cgi?subfunc=firm_update") 345 | if resp is None: 346 | return None 347 | 348 | new_version = resp["func"]["ownContent"]["newVersion"] 349 | if new_version is None or len(new_version) == 0: 350 | return None 351 | 352 | return new_version 353 | 354 | def list_external_drive(self): 355 | """List External drive connected on qnap.""" 356 | data = {"func": "getExternalDev"} 357 | resp = self._execute_post_url("devices/devRequest.cgi", data=data) 358 | if resp is None: 359 | return None 360 | 361 | external_drives = resp.get("func", {}).get("ownContent", {}).get("externalDevice") 362 | if external_drives is None or len(external_drives) == 0: 363 | return None 364 | 365 | return external_drives 366 | 367 | def get_storage_information_on_external_device(self): 368 | """Get informations on volumes in External drive connected on qnap.""" 369 | data = {"func": "external_get_all"} 370 | resp = self._execute_post_url("disk/disk_manage.cgi", data=data) 371 | if resp is None: 372 | return None 373 | 374 | disk_vol = resp.get("Disk_Vol") 375 | if disk_vol is None or len(disk_vol) == 0: 376 | return None 377 | 378 | return disk_vol 379 | --------------------------------------------------------------------------------