GithubHelp home page GithubHelp logo

mtrab / pyworxcloud Goto Github PK

View Code? Open in Web Editor NEW
21.0 5.0 20.0 808 KB

PyPI module for integrating with Worx Cloud devices

License: GNU General Public License v3.0

Python 100.00%
home-assistant integration landroid worx kress landxcape

pyworxcloud's Introduction

Buy Me A Coffee

pyWorxCloud

This is a PyPI module for communicating with Worx Cloud mowers, primarily developed for use with Home Assistant, but I try to keep it as widely usable as possible.

The module are compatible with cloud enabled devices from these vendors

Documentation

The documentation have been moved to the Wiki

pyworxcloud's People

Contributors

cubiemedia avatar dependabot[bot] avatar fblaese avatar github-actions[bot] avatar hanzoh avatar jderusse avatar m0ses avatar maximilian-franz avatar mips2648 avatar mtrab avatar phhere avatar pjfian avatar sithmein avatar thorsten-meinl-knime avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pyworxcloud's Issues

How do I define "CloudType" and the WorxCloud object

When I am doing cloud.connect() with this code:

from pyworxcloud import WorxCloud
import requests

cloud = WorxCloud("[email protected]", "PASS", "worx")
cloud.connect()

I am getting this exception: TypeError: WorxCloud.connect() missing 1 required positional argument: 'dev_id'

I belive that the reason is that I have the work CloudType, but I dont know how to properly declare a "CloudType"

Thanks in advance!
Best regards Max

Question - how can i write current api data to a file

Hi,
I try to use the youre testfile with the setCallback function and it prints the new status very well.
But I'm not able to revere to the variables within the registered function.

Can you give me a tip how i can do that?

Here is the function part of whot I'm trying to do:

def receive_data(
    name: str, device: DeviceHandler  # pylint: disable=unused-argument
) -> None:
    """Callback function when the MQTT broker sends new data."""

    print("Got data on MQTT from " + name)

    #logTimestamp = str(datetime.now())
    #logText = str(vars(cloud))
    landroidLog = open('landroidLog.txt','a')
    landroidLog.write(vars(cloud))

A big thx up front and kind regards,
Mark

Ladroid doesn't appear in Landroid_cloud version: 2.3.0

Environment:

  • Home Assistant version: 2022.7.6
  • Landroid_cloud version: 2.3.0
  • Operation system: Home Assistant Supervised
  • Landroid Worx m500 WR141E

Screenshot

mower2

Describe the bug

Ladroid doesn't appear in Landroid_cloud version: 2.3.0

Debug log

Logger: root
Source: /usr/src/homeassistant/homeassistant/bootstrap.py:330
First occurred: 17:06:14 (1 occurrences)
Last logged: 17:06:14

Uncaught thread exception
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 3591, in _thread_main
    self.loop_forever(retry_first_connection=True)
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 1756, in loop_forever
    rc = self._loop(timeout)
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 1164, in _loop
    rc = self.loop_read()
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 1556, in loop_read
    rc = self._packet_read()
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 2439, in _packet_read
    rc = self._packet_handle()
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 3033, in _packet_handle
    return self._handle_publish()
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 3327, in _handle_publish
    self._handle_on_message(message)
  File "/usr/local/lib/python3.10/site-packages/paho/mqtt/client.py", line 3570, in _handle_on_message
    on_message(self, self._userdata, message)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 363, in _forward_on_message
    self._decode_data(device)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 556, in _decode_data
    device.schedules.update_progress_and_next(
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/schedules.py", line 196, in update_progress_and_next
    self["daily_progress"] = info.calculate_progress()
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/schedules.py", line 114, in calculate_progress
    if self.__now >= start and self.__now < end:
TypeError: can't compare offset-naive and offset-aware datetimes

Attributes

battery_level: null
battery_icon: mdi:battery-unknown
accessories: null
battery: 
cycles:
  total: 832
  current: 0
  reset_at: null
  reset_time: null
temperature: 23.5
voltage: 19.26
percent: 100
charging: false

blades: 
total_on: 74489
reset_at: 61657
reset_time: '2022-04-19T07:04:53+02:00'
current_on: 12832

error: 
id: 5
description: rain delay

firmware: 
auto_upgrade: false
version: 3.26

locked: false
mac_address: xxxxxxxxxxx
model: Landroid M500
online: true
orientation: 
pitch: 0
roll: 0
yaw: 0

rain_sensor: 
delay: 60
triggered: true
remaining: 60

schedule: 
time_extension: 0
active: true
auto_schedule:
  settings: {}
  enabled: null
primary:
  monday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: false
  tuesday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: false
  wednesday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: true
  thursday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: false
  friday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: false
  saturday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: false
  sunday:
    start: '11:00'
    end: '12:30'
    duration: 90
    boundary: true
secondary:
  monday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  tuesday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  wednesday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  thursday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  friday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  saturday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false
  sunday:
    start: '18:00'
    end: '19:30'
    duration: 90
    boundary: false

serial_number: xxxxxxxxxxxxx
status_info: 
id: 1
description: home

time_zone: Europe/Berlin
zone: 
current: 0
next: 0
index: 7
indicies:
  - 0
  - 0
  - 0
  - 0
  - 0
  - 0
  - 0
  - 0
  - 0
  - 0
starting_point:
  - 0
  - 0
  - 0
  - 0

capabilities: null
mqtt_connected: true
supported_landroid_features: 0
daily_progress: null
next_scheduled_start: null
device_class: landroid_cloud__state
friendly_name: Mower
supported_features: 12500

str, not None

Error in version following upgrade from 2.1.0 to 2.1.1 below is environment and current error message

Environment:

  • Home Assistant version: 2022.6.7
  • Landroid_cloud version: 2.1.1
  • Operation system: Unraid Docker - HA Core
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 339, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/config/custom_components/landroid_cloud/__init__.py", line 61, in async_setup_entry
    result = await _setup(hass, entry)
  File "/config/custom_components/landroid_cloud/__init__.py", line 203, in _setup
    await hass.async_add_executor_job(cloud.connect, False, False)
  File "/usr/local/lib/python3.9/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/__init__.py", line 224, in connect
    self._fetch()
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/__init__.py", line 609, in _fetch
    device = DeviceHandler(self._api, product)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/devices.py", line 36, in __init__
    self.__mapinfo(api, product)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/devices.py", line 84, in __mapinfo
    self.warranty = Warranty(data)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/warranty.py", line 20, in __init__
    self["expires_at"] = string_to_time(
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/helpers/time_format.py", line 38, in string_to_time
    datetime.strptime(dt_string, format)
TypeError: strptime() argument 1 must be str, not None

asyncio really needed?

Hello,

thanks for sharing pyworxcloud!
I intend to use it for smarthomeng.de.
Unfortunately, I fear that combining the two is difficult, as smarthomeng is threaded, whereas pyworxcloud uses asyncio.

I wonder, if it is really required to use asyncio. I see that it is used only:

  • worxlandroidapi.py - imported but not used (?)
  • init.py - async def initialize(self, username, password, type="worx" ) for self._authenticate

So, I wonder if it is really required to use asyncio in the library?

Best regards,
Hendrik

BUG: unhandled exception if no gps key stops MQTT data receive

/Dear @MTrab ,
I think I've found a situation in which the JSON sent from the Positech cloud is missing the key : DATA.MODULES.4G.GPS .

if this happens an exception is thrown :

Exception in thread Thread-1 (_thread_main):
Traceback (most recent call last):
File "/usr/local/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
     self.run()
   File "/usr/local/lib/python3.11/threading.py", line 982, in run
     self._target(*self._args, **self._kwargs)
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 3591, in _thread_main
     self.loop_forever(retry_first_connection=True)
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 1756, in loop_forever
     rc = self._loop(timeout)
          ^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 1164, in _loop
     rc = self.loop_read()
          ^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 1556, in loop_read
     rc = self._packet_read()
          ^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 2439, in _packet_read
     rc = self._packet_handle()
          ^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 3033, in _packet_handle
     return self._handle_publish()
            ^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 3330, in _handle_publish
     self._handle_on_message(message)
   File "/usr/local/lib/python3.11/site-packages/paho/mqtt/client.py", line 3570, in _handle_on_message
    on_message(self, self._userdata, message)
   File "/code/pyworxcloud/utils/mqtt.py", line 149, in _forward_on_message
     self._on_update(msg)
   File "/code/pyworxcloud/__init__.py", line 395, in _on_update
     self._decode_data(device)
   File "/code/pyworxcloud/__init__.py", line 475, in _decode_data
     data["dat"]["modules"]["4G"]["gps"]["coo"][0],
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^

this error seems to stop the connection to the MQTT server , thus interrupting data receive.

as you can see the library correctly issue a force update, but no data is received:

2024-05-29T05:48:22.652964462Z 2024-05-29 07:48:22,652 [mqtt.py] [ping] [DEBUG] [239] Sending '{"id": xxxxxx, "uuid": "xxxxxxxxxxxxx", "tm": "2024-05-29T07:48:22Z", "cmd": 0}' on topic 'KR/MW/KR173E/xxxxxxxxxxxx/v1/commandIn'
2024-05-29T05:48:22.652257769Z 2024-05-29 07:48:22,652 [__init__.py] [_force_refresh] [DEBUG] [341] Forcing refresh for 'RTK KR173E'
2024-05-29T05:48:22.652825783Z 2024-05-29 07:48:22,652 [__init__.py] [update] [DEBUG] [730] Trying to refresh 'xxxxxxxxxxxxx'
2024-05-29T05:48:22.652881468Z 2024-05-29 07:48:22,652 [mqtt.py] [format_message] [DEBUG] [287] Formatting message '{'cmd': 0}' to '{'id': 21893, 'uuid': 'xxxxxxxxxxxxxx', 'tm': '2024-05-29T07:48:22Z', 'cmd': 0}'

I suggest to add Key verification to the code at line 471:

# Check for extra module availability
                if "modules" in data["dat"]:
                    if "4G" in data["dat"]["modules"]:
                        if "gps" in data["dat"]["modules"]["4G"]:
                            device.gps = Location(
                                data["dat"]["modules"]["4G"]["gps"]["coo"][0],
                                data["dat"]["modules"]["4G"]["gps"]["coo"][1],
                            )

Not aligned requirements file

The requirements.txt file is not aligned with the dependencies in pyproject.toml.

I'm not sure if this is because the requirements.txt file is outdated, but I think that it is better to either align it or to remove it from the repo (of course with a very low priority; after all, very few people will use it).

In addition, I noticed that when I created manually a venv to run the examples I also had to pull the tzdata package; maybe it is an implicit dependency for poetry, but in requirements.txt I had to add it otherwise the python script complained

Thank you

Loading fails when missing last_status

Zone mapping renders an error when key "last_status" is missing in dat dictionary.

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 387, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/config/custom_components/landroid_cloud/__init__.py", line 63, in async_setup_entry
    result = await _async_setup(hass, entry)
  File "/config/custom_components/landroid_cloud/__init__.py", line 206, in _async_setup
    await hass.async_add_executor_job(cloud.connect)
  File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 235, in connect
    self._fetch()
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 516, in _fetch
    device = DeviceHandler(self._api, mower)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/devices.py", line 46, in __init__
    self.__mapinfo(api, mower)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/devices.py", line 100, in __mapinfo
    self.zone = Zone(data)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/zone.py", line 22, in __init__
    self["index"] = data["last_status"]["payload"]["dat"]["lz"]
KeyError: 'lz'

Reported in MTrab/landroid_cloud#372

Issue installing this in home assistant

I tried to install v3 in home assistant, via the landroid_cloud integration - but it failed to install.
It seems the issue is due to awscrt, and by association awsiot which provides the mqtt capability.

Is awscrt (via awsiotsdk) required? I am able to manually install awsiot, but when I try install awsiotsdk, it pulls in awscrt and I get the following:

Collecting awscrt
Using cached awscrt-0.16.10.tar.gz (21.9 MB)
Preparing metadata (setup.py): started
Preparing metadata (setup.py): finished with status 'done'
Building wheels for collected packages: awscrt
Building wheel for awscrt (setup.py): started
Building wheel for awscrt (setup.py): finished with status 'error'
error: subprocess-exited-with-error

× python setup.py bdist_wheel did not run successfully.
│ exit code: 1
╰─> [67 lines of output]
running bdist_wheel
running build
running build_py
creating build
creating build/lib.linux-x86_64-cpython-310
creating build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/websocket.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/s3.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/mqtt5.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/mqtt.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/io.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/http.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/exceptions.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/crypto.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/common.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/checksums.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/auth.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/_test.py -> build/lib.linux-x86_64-cpython-310/awscrt
copying awscrt/init.py -> build/lib.linux-x86_64-cpython-310/awscrt
creating build/lib.linux-x86_64-cpython-310/awscrt/eventstream
copying awscrt/eventstream/rpc.py -> build/lib.linux-x86_64-cpython-310/awscrt/eventstream
copying awscrt/eventstream/init.py -> build/lib.linux-x86_64-cpython-310/awscrt/eventstream
running build_ext
Traceback (most recent call last):
File "", line 2, in
File "", line 34, in
File "/tmp/pip-install-i36yyhey/awscrt_dc0d744314454142b16be4fa750afda1/setup.py", line 348, in
setuptools.setup(
File "/usr/local/lib/python3.10/site-packages/setuptools/init.py", line 87, in setup
return distutils.core.setup(**attrs)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 177, in setup
return run_commands(dist)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 193, in run_commands
dist.run_commands()
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 968, in run_commands
self.run_command(cmd)
File "/usr/local/lib/python3.10/site-packages/setuptools/dist.py", line 1229, in run_command
super().run_command(command)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
cmd_obj.run()
File "/usr/local/lib/python3.10/site-packages/wheel/bdist_wheel.py", line 299, in run
self.run_command('build')
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/cmd.py", line 317, in run_command
self.distribution.run_command(command)
File "/usr/local/lib/python3.10/site-packages/setuptools/dist.py", line 1229, in run_command
super().run_command(command)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
cmd_obj.run()
File "/usr/local/lib/python3.10/site-packages/setuptools/command/build.py", line 24, in run
super().run()
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/command/build.py", line 131, in run
self.run_command(cmd_name)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/cmd.py", line 317, in run_command
self.distribution.run_command(command)
File "/usr/local/lib/python3.10/site-packages/setuptools/dist.py", line 1229, in run_command
super().run_command(command)
File "/usr/local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
cmd_obj.run()
File "/tmp/pip-install-i36yyhey/awscrt_dc0d744314454142b16be4fa750afda1/setup.py", line 255, in run
self._build_dependencies(dep_build_dir, dep_install_path)
File "/tmp/pip-install-i36yyhey/awscrt_dc0d744314454142b16be4fa750afda1/setup.py", line 247, in _build_dependencies
self._build_dependencies_impl(build_dir, install_path)
File "/tmp/pip-install-i36yyhey/awscrt_dc0d744314454142b16be4fa750afda1/setup.py", line 158, in _build_dependencies_impl
cmake = get_cmake_path()
File "/tmp/pip-install-i36yyhey/awscrt_dc0d744314454142b16be4fa750afda1/setup.py", line 121, in get_cmake_path
raise Exception("CMake must be installed to build from source.")
Exception: CMake must be installed to build from source.
[end of output]

note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for awscrt

so it appears to need cmake, which is not part of HA

Can the dependency on awscrt be removed, if it is not strictly required for awsiot to function ?

How to get the current status of the Landroid

Hey!

Awesome thing youve created! Works fantastic! I am curious if theres an easy way to get the current status of the mower, like "mowing" or "paused". Can you do it with this repo?

Thanks in advance!
Best regards Max

Can I use this as a bridge

First of all great project - wor(x)ks right away!
Not really an issue just a question:
The season started and to my surprise I see that certificates for mqtt don't work anymore with Worx.
You adapted the code to handle the new process. But all my stuff is based on MQTT means somehow I need to bridge or forward the messages to my mqtt server.
Can you give me a hint where in your code I could add this logic or are there any plans in that direction already?
I see self._forward_on_message is triggered and calls on_update, maybe there?
but don't know where to put in the additional connection part.

Integration startup fails for landxcape because of missing timezone information in warranty.py

Environment:

  • Home Assistant version: 2022.7.5
  • Landroid_cloud version: 2.2.2
  • Operation system: docker

Configuration

Configured for Landxcape through GUI

Describe the bug

During initialization an unhandled exception occurs in pyworxcloud.utils.warranty because the data input argument has None for the timezone.

Debug log

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 353, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/config/custom_components/landroid_cloud/__init__.py", line 66, in async_setup_entry
    result = await _async_setup(hass, entry)
  File "/config/custom_components/landroid_cloud/__init__.py", line 208, in _async_setup
    await hass.async_add_executor_job(cloud.connect, False, False)
  File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 226, in connect
    self._fetch()
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/__init__.py", line 638, in _fetch
    device = DeviceHandler(self._api, product)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/devices.py", line 37, in __init__
    self.__mapinfo(api, product)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/devices.py", line 90, in __mapinfo
    self.warranty = Warranty(data)
  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/warranty.py", line 27, in __init__
    < datetime.now().astimezone(pytz.timezone(data["time_zone"]))
  File "/usr/local/lib/python3.10/site-packages/pytz/__init__.py", line 168, in timezone
    raise UnknownTimeZoneError(None)
pytz.exceptions.UnknownTimeZoneError: None

Landxcape doesn't work anymore

Trying to login to a Landxcape account results in an 404 error.
Looks like they have changed something in the URLs for the Landxcape devices.

404 Client Error: Not Found for url: https://api.landxcape-services.com/api/v2/oauth/token

Uncaught thread exception

I noticed the extension was not giving updates, and going in the logs I found this:

uncaught_exception.txt

Can this be linked to #58 or, in your opinion, it is different? The event happened roughly 12 hours after I restarted HA.

Best regards

Issue with name resolver

Hello

Due to issues with the ISP, sometimes the name of servers cannot be resolved.

This however permanently blocks the extension, since from that moment the extension (I assume) doesn't try again to contact the server.

Here is the log entry with that problem.
pyworxcloud - log.txt

The issue happened on 1st July, and until now (3 days) it has not recovered. No additional log entries are present in the log list in Home Assistant, so I assume the script crashed.

I expect the extension to continue retrying and, when the DNS come back online, resume proper operations

setzone stopped working after upgrade

After upgrading to v2.1.2 the setzone call fails.

This is how i call the service:

service: landroid_cloud.setzone
data:
  zone: '2'
target:
  entity_id: vacuum.rasimir

This is what appears in HA logfile:

2022-06-30 11:29:02 ERROR (MainThread) [homeassistant.helpers.script.websocket_api_script] websocket_api script: Error executing script. Unexpected error for call_service at pos 1: 'DeviceHandler' object has no attribute 'zone_probability'
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 680, in _async_call_service_step
    await service_task
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1704, in async_call
    task.result()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1741, in _execute_service
    await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/services.py", line 155, in async_call_landroid_service
    await api.services[service][ATTR_SERVICE](service_data)
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/device_base.py", line 677, in async_set_zone
    await self.hass.async_add_executor_job(partial(device.setzone, str(zone)))
  File "/usr/lib/python3.9/concurrent/futures/thread.py", line 52, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/srv/homeassistant/lib/python3.9/site-packages/pyworxcloud/utils/actions.py", line 186, in setzone
    current = self.zone_probability
AttributeError: 'DeviceHandler' object has no attribute 'zone_probability'
2022-06-30 11:29:02 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [1550688512] Error handling message: Unknown error (unknown_error)
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/components/websocket_api/decorators.py", line 27, in _handle_async_response
    await func(hass, connection, msg)
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/components/websocket_api/commands.py", line 636, in handle_execute_script
    await script_obj.async_run(msg.get("variables"), context=context)
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 1513, in async_run
    await asyncio.shield(run.async_run())
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 405, in async_run
    await self._async_step(log_exceptions=False)
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 449, in _async_step
    self._handle_exception(
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 472, in _handle_exception
    raise exception
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 680, in _async_call_service_step
    await service_task
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1704, in async_call
    task.result()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1741, in _execute_service
    await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/services.py", line 155, in async_call_landroid_service
    await api.services[service][ATTR_SERVICE](service_data)
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/device_base.py", line 677, in async_set_zone
    await self.hass.async_add_executor_job(partial(device.setzone, str(zone)))
  File "/usr/lib/python3.9/concurrent/futures/thread.py", line 52, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/srv/homeassistant/lib/python3.9/site-packages/pyworxcloud/utils/actions.py", line 186, in setzone
    current = self.zone_probability
AttributeError: 'DeviceHandler' object has no attribute 'zone_probability'

Python version requirement

Hello Marlene,

thanks for your work on updating to v3!
I have one issue though:
I (and the target audience) do not have python 3.10. I have "patched" pyworxcloud to accept 3.9 and it seems to work. At least:

cloud.connect()

print(vars(cloud))

cloud.disconnect()

Can you tell me whether 3.10 is really needed?

Best regards,
Hendrik

Configuring time_extension doesn't work for negative values

Environment:

  • Home Assistant 2022.7.3
  • Supervisor 2022.07.0
  • Operating System 8.2
  • Landroid_cloud version: 2.2.0

Configuration

service: landroid_cloud.config
data:
  timeextension: -10
target:
  device_id: [XXX]
  entity_id: vacuum.[xxx]

Describe the bug

Using landroid_cloud.config to set timeextension doesn't work for negative values. Positive values, including zero, works fine. No error, the value just isn't applied on the robot.

Debug log

no error

How to send mover to specific zone

Hi,
not sure if this is the right way to ask this question, but i don't find a better place,....

First of all THX a lot for you work!!!!!

I can already start and stop my mover (worx) but i'm not able to send him to a specific Zone. is there any excample or someone who can help me on that?
I'm using just the phyton script without IO Broaker.

Thx a lot up front!
Mark

Start method does not work

I created a python script to read some data from the Worx cloud. This works fine.
Now I also wanted to start my Worx Landroid from this script. But my Landroid is not responding. I also don't get an error.

My script:

from pyworxcloud import WorxCloud
cloud = WorxCloud("XXX", "XXX")

# Initialize connection
auth = cloud.initialize()

if not auth:
    # If invalid credentials are used, or something happend during
    # authorize, then exit
    exit(0)

# Connect to device with index 0 (devices are enumerated 0, 1, 2 ...) and do
# not verify SSL (False)
cloud.connect(0, False)

cloud.update()

print("online: ----")
print(cloud.online)

print("mqtt: ------")
print(cloud._mqtt)

cloud.start()
print("----")

# Read latest states received from the device
# cloud.update()
# print(cloud._raw)
# print(cloud.status)
# print(cloud.battery_percent)
# print(cloud.battery_charging)

Outputs:

online: ----
True
mqtt: ------
<paho.mqtt.client.Client object at 0x10269bcd0>
----

No output for the start method, nor any reaction from the Landroid.

FYI: print(cloud.battery_charging) is in comment but this outputs the correct data.

error after update to new version

The system cannot restart because the configuration is not valid: Component error: landroid_cloud - Exception importing custom_components.landroid_cloud

now i cannot restart anymore

example.py still uses update()

Hello Marlene,

with 1.4.19 update() is deprecated. But example.py still uses it.
Could you please update example.py?

Best regards,
Hendrik

[FR] Support for "One time schedule"

I am a user of MTrab/landroid_cloud and it looks like that this upstream project does not yet support the "One time schedule" feature. It's the ability to start the mower for a defined number of hours, up to 12 h on my WR165E. It would be great to have it in MTrab/landroid_cloud and then hopefully in https://github.com/Barma-lej/halandroid. Thank you for this project. 👍

Additional note:
I think the duration of the "One time schedule" is a setting and not a parameter of the specific command. I can update the number of hours without starting the "One time schedule" sequence. In this case, it would mean an additional setting for cfg + an additional cmdcode to find and implement.

Official documentation:
https://wiki.worx.com/en/landroid-schedule-1

image
Screenshot taken from MTrab/landroid_cloud#59 (comment)

Connection broken

Greetings!

Upon testing version 3.04 I got the following error message:

Traceback (most recent call last):
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\pyworxcloud\utils\requests.py", line 73, in GET
req.raise_for_status()
File "C:\Users\MrBG\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\models.py", line 1021, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://api.worxlandroid.com/api/v2/product-items?status=1

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\test.py", line 19, in
cloud.connect()
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\pyworxcloud_init_.py", line 234, in connect
self.fetch()
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\pyworxcloud_init
.py", line 496, in _fetch
self._mowers = self._api.get_mowers()
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\pyworxcloud\api.py", line 102, in get_mowers
mowers = GET(
^^^^
File "C:\Users\MrBG\Downloads\pyworxcloud-3.0.4\pyworxcloud-3.0.4\pyworxcloud\utils\requests.py", line 79, in GET
raise AuthorizationError()
pyworxcloud.exceptions.AuthorizationError

Username / Password and manual login verified.

Thank you in advance,

Frank

Firmware version is no longer a floating point number and parsing fails as a result

In the Worx app, the firmware is "3.32.0+1". pyworxcloud seems to parse this as "{:.2f}".format, which fails.
Pyworxcloud version 3.1.14 (+ landroid_cloud version 3.0.5)

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 388, in async_setup
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/landroid_cloud/__init__.py", line 63, in async_setup_entry
    result = await _async_setup(hass, entry)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/landroid_cloud/__init__.py", line 206, in _async_setup
    await hass.async_add_executor_job(cloud.connect)
  File "/usr/local/lib/python3.11/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pyworxcloud/__init__.py", line 259, in connect
    self._fetch()
  File "/usr/local/lib/python3.11/site-packages/pyworxcloud/__init__.py", line 611, in _fetch
    self._mowers = self._api.get_mowers()
                   ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pyworxcloud/api.py", line 119, in get_mowers
    mower["firmware_version"] = "{:.2f}".format(mower["firmware_version"])
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: Unknown format code 'f' for object of type 'str'

edgecut stopped working after upgrade

After upgrading to v2.1.2 the edgecut service does not do anything at all.
there is no error, no log, no reaction of the mower.

This is how i call the service:

service: landroid_cloud.edgecut
target:
entity_id: vacuum.rasimir

Request: More stable API

Hello,

we are using your lovely package for smarthome-ng.
Unfortunately, it is a bit difficult to keep track of the changes that you do. Could you please consider the following:

  • less breaking changes
  • less bleeding edge (python 3.9; is that really needed?)
  • depreciation warnings before changing the API
  • add upgrade-guidelines when introducing breaking changes to the API

Thanks and best regards,
Hendrik

Update vs. getStatus

Hi,
First of all - thank you for your great work here!
I've got a question - what is the difference between method update and getStatus. Only getStatus is "bannable"? Can I call update how often I want but there will be new data only if server sends any since the previous call?

Connection seems to work, but mqtt exception

Hello,

I am trying the simple example from the wiki.

2022-10-24 11:59:33,957 [__init__.py] [connect] [DEBUG] [222] Fetching basic API data
2022-10-24 11:59:34,458 [__init__.py] [connect] [DEBUG] [224] Done fetching basic API data
2022-10-24 11:59:34,458 [__init__.py] [connect] [DEBUG] [226] Setting up MQTT handler
2022-10-24 11:59:34,459 [__init__.py] [connect] [DEBUG] [241] Done setting up MQTT handler, setting MQTT config.
2022-10-24 11:59:34,459 [__init__.py] [connect] [DEBUG] [267] Done setting MQTT config, fetching certificate.
2022-10-24 11:59:34,831 [__init__.py] [connect] [DEBUG] [271] Done fetching certificate, setting TLS.
2022-10-24 11:59:34,831 [__init__.py] [connect] [DEBUG] [276] Done setting TLS, beginning MQTT connect.
2022-10-24 11:59:34,965 [__init__.py] [connect] [DEBUG] [279] MQTT connect done, starting loop
2022-10-24 11:59:34,966 [__init__.py] [connect] [DEBUG] [282] MQTT loop started
2022-10-24 11:59:34,966 [__init__.py] [connect] [DEBUG] [285] Converting date and time string
2022-10-24 11:59:34,967 [__init__.py] [connect] [DEBUG] [290] Connection tasks all done
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/threading.py", line 980, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.9/threading.py", line 917, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 3591, in _thread_main
    self.loop_forever(retry_first_connection=True)
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 1756, in loop_forever
    rc = self._loop(timeout)
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 1164, in _loop
    rc = self.loop_read()
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 1558, in loop_read
    return self._loop_rc_handle(rc)
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 2350, in _loop_rc_handle
    self._do_on_disconnect(rc, properties)
  File "/usr/local/lib/python3.9/site-packages/paho/mqtt/client.py", line 3475, in _do_on_disconnect
    on_disconnect(self, self._userdata, rc)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/__init__.py", line 627, in _on_disconnect
    raise MQTTException(
pyworxcloud.exceptions.MQTTException: Unexpected MQTT disconnect - were you perhaps banned?
2022-10-24 11:59:35,194 [__init__.py] [_decode_data] [DEBUG] [371] Data decoding for M started
2022-10-24 11:59:35,194 [__init__.py] [_decode_data] [DEBUG] [374] Found JSON decoded data: {'id': ...

The JSON data seems correct.

What could be the issue?

Best regards,
Hendrik

Exception: 'bool' object is not subscriptable

Hi Morten,

some of us are actually planning to use pyworxcloud for a plugin in a Smarthome application. While using pyworxcloud Version: 1.4.11, there is a exception coming up every time the data is polled:

`2022-01-24 11:22:48 ERROR plugins.landroid.poll_device Method plugins.landroid.poll_device exception: 'bool' object is not subscriptable

Traceback (most recent call last):
File "/usr/local/smarthome/lib/scheduler.py", line 670, in _task
obj()
File "/usr/local/smarthome/plugins/landroid/init.py", line 345, in poll_device
self.worx.update()
File "/home/smarthome/.local/lib/python3.9/site-packages/pyworxcloud/init.py", line 299, in update
self._fetch()
File "/home/smarthome/.local/lib/python3.9/site-packages/pyworxcloud/init.py", line 293, in _fetch
for attr, val in products[self._dev_id].items():
TypeError: 'bool' object is not subscriptable`

Appreciate if you could have a look on it.

Regards
Marcus

UnknownTimeZoneError

I updated your landroid-integration to the latest version (skipped a few...) and am getting the following error on startup. I deleted the mower, removed the integration from hacs and re-installed everything. Stil getting the same error. any idea what goes wrong? I am using the current version of HA (2022.6.7). Mower is a landxcape LX 790i.

error setting up entry Landxcape [email protected] for landroid_cloud
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 339, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/config/custom_components/landroid_cloud/__init__.py", line 64, in async_setup_entry
    result = await _async_setup(hass, entry)
  File "/config/custom_components/landroid_cloud/__init__.py", line 205, in _async_setup
    await hass.async_add_executor_job(cloud.connect, False, False)
  File "/usr/local/lib/python3.9/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/__init__.py", line 225, in connect
    self._fetch()
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/__init__.py", line 619, in _fetch
    device = DeviceHandler(self._api, product)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/devices.py", line 37, in __init__
    self.__mapinfo(api, product)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/devices.py", line 90, in __mapinfo
    self.warranty = Warranty(data)
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/utils/warranty.py", line 20, in __init__
    self["expires_at"] = string_to_time(
  File "/usr/local/lib/python3.9/site-packages/pyworxcloud/helpers/time_format.py", line 34, in string_to_time
    timezone = pytz.timezone(tz)
  File "/usr/local/lib/python3.9/site-packages/pytz/__init__.py", line 168, in timezone
    raise UnknownTimeZoneError(None)
pytz.exceptions.UnknownTimeZoneError: None

last_status is None

Yesterdays commit (e290a63) breaks the library for me:

My last_status is None:


  File "/usr/local/lib/python3.10/site-packages/pyworxcloud/utils/zone.py", line 22, in __init__
    self["index"] = data["last_status"]["payload"]["dat"]["lz"]
TypeError: 'NoneType' object is not subscriptable

Capability data: { .......... 'last_status': None, 'model': {'code': 'WR105SI.1', .............. }

set_mowingzone fails again

Sadly, set_mowingzone fails again after fixing edgecut:

might be related to #49

2022-07-04 09:48:06 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/components/script/__init__.py", line 428, in _async_run
    return await self.script.async_run(script_vars, context)
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 1513, in async_run
    await asyncio.shield(run.async_run())
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 405, in async_run
    await self._async_step(log_exceptions=False)
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 449, in _async_step
    self._handle_exception(
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 472, in _handle_exception
    raise exception
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/script.py", line 680, in _async_call_service_step
    await service_task
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1704, in async_call
    task.result()
  File "/srv/homeassistant/lib/python3.9/site-packages/homeassistant/core.py", line 1741, in _execute_service
    await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/services.py", line 160, in async_call_landroid_service
    await api.services[service][ATTR_SERVICE](service_data)
  File "/home/homeassistant/.homeassistant/custom_components/landroid_cloud/device_base.py", line 708, in async_set_zone
    await self.hass.async_add_executor_job(partial(device.setzone, str(zone)))
  File "/usr/lib/python3.9/concurrent/futures/thread.py", line 52, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/srv/homeassistant/lib/python3.9/site-packages/pyworxcloud/utils/actions.py", line 188, in setzone
    while not new_zones[self.mowing_zone] == zone:
AttributeError: 'DeviceHandler' object has no attribute 'mowing_zone'

enhancement: handle Cloudflare Turnstile interception

Worx hosts their cloud API server with Cloudflare, and appears to have enabled Cloudflare Turnstile to intercept connection attempts from flagged IP addresses. How those IP addresses are flagged is unclear. The effect is that, for some requestors, instead of directly connecting to the API, HTTP status 403 is returned, with an error page that's full of Javascript, with the intention that a normal browser would successfully execute a challenge and be forwarded to the actual API endpoint. Unfortunately, this interception will of course result in an exception here:

req = requests.post(URL, REQUEST_BODY, headers=HEADER)
req.raise_for_status()
return req.json()
except requests.exceptions.HTTPError as err:
code = err.response.status_code
if code == 400:
raise RequestError()
elif code == 401:
raise AuthorizationError()
elif code == 403:
raise ForbiddenError()
elif code == 404:
raise NotFoundError()

Solving it programmatically in this library might be possible, but would be pretty clunky, for example by running a JS engine to process the challenge. Instead, I'm hoping that with your open dialog with Worx, you might be able to find a way to work through it -- whether using specific headers to identify this library and bypass Turnstile, or some other approach mutually agreeable with them.

For reference, I'm attaching logs showing the results of curl -v https://id.worx.com/login from a client that's being intercepted by Turnstile, and one that is allowed to connect unimpeded.
worx-clear.log
worx-cloudfare-turnstile.log

Needs to import mqtt from awsiotsdk, not awsiot

mqtt_connection_builder comes from awsiotsdk, not awsiot

from awsiot import mqtt, mqtt_connection_builder

awsiot==0.1.3

awsiot = "0.1.3"

name = "awsiot"

{file = "awsiot-0.1.3-py3-none-any.whl", hash = "sha256:ffa52ffce81c481bb94edcbe93fe675cf8dc72120d84b0922412da6d457771ec"},

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.