GithubHelp home page GithubHelp logo

python-smarttub's Introduction

python-smarttub

This package provides an API for querying and controlling hot tubs using the SmartTub system.

Installation

pip3 install python-smarttub

CLI

python3 -m smarttub --help
python3 -m smarttub -u SMARTTUB_EMAIL -p SMARTTUB_PASSWORD info --status

API

from smarttub import SmartTub

async with aiohttp.ClientSession() as session:
  st = SmartTub(session)
  await st.login(username, password)
  account = await st.get_account()
  spas = await account.get_spas()
  for spa in spas:
    spa.get_status()
    spa.get_pumps()
    spa.get_lights()
    ...
    # See pydoc3 smarttub.api for complete API

See also smarttub/__main__.py for example usage

Troubleshooting

If this module is not working with your device, please run the following command and include the output with your bug report:

python3 -m smarttub -u YOUR_SMARTTUB_EMAIL -p YOUR_SMARTTUB_PASSWORD -vv info -a

Contributing

pip install pre-commit && pre-commit install

python-smarttub's People

Contributors

fowie avatar kaareseras avatar mathyourlife avatar mdz avatar swbradshaw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

python-smarttub's Issues

There is no way to determine which colors or modes are supported by a given light

Different lights have different capabilities, e.g. some are RGB and only support colors, while others are white LEDs and only support the "white" mode. There are also color cycling modes which may or may not be supported. As far as I know, the API does not provide any information about which colors or modes are available, so this information is not provided by the smarttub module at present.

Example issue report: home-assistant/core#62830

Error trying to use CLI

Using Ubuntu 18.04.5 LTS
sudo apt install python3-pip
pip3 install python-smarttub

If I try and use the example CLI
python3 -m smarttub -u [email protected] -p password info --status

I get
Traceback (most recent call last):
File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
"main", mod_spec)
File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/liam/.local/lib/python3.6/site-packages/smarttub/main.py", line 11, in
st.login(*sys.argv[1:])
TypeError: login() takes 3 positional arguments but 7 were given

Also
python3 -m smarttub --help
Traceback (most recent call last):
File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
"main", mod_spec)
File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/liam/.local/lib/python3.6/site-packages/smarttub/main.py", line 11, in
st.login(*sys.argv[1:])
TypeError: login() missing 1 required positional argument: 'password'

Triad 36 by D1: TypeError: object of type 'NoneType' has no len()

Originally reported by @gdeuss via home-assistant/home-assistant.io#20896

*Unexpected error fetching smarttub data: object of type 'NoneType' has no len()
Logger: homeassistant.components.smarttub.controller
Source: components/smarttub/controller.py:98
Integration: SmartTub (documentation, issues)
First occurred: 10:51:14 AM (123 occurrences)
Last logged: 12:56:35 PM

Unexpected error fetching smarttub data: object of type 'NoneType' has no len()
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 187, in _async_refresh
self.data = await self._async_update_data()
File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 147, in _async_update_data
return await self.update_method()
File "/usr/src/homeassistant/homeassistant/components/smarttub/controller.py", line 90, in async_update_data
data[spa.id] = await self._get_spa_data(spa)
File "/usr/src/homeassistant/homeassistant/components/smarttub/controller.py", line 98, in _get_spa_data
full_status, reminders, errors = await asyncio.gather(
File "/usr/local/lib/python3.9/site-packages/smarttub/api.py", line 206, in get_reminders
return [
File "/usr/local/lib/python3.9/site-packages/smarttub/api.py", line 207, in
SpaReminder(self, **reminder_info)
File "/usr/local/lib/python3.9/site-packages/smarttub/api.py", line 469, in init
self.last_updated = dateutil.parser.isoparse(properties["lastUpdated"])
File "/usr/local/lib/python3.9/site-packages/dateutil/parser/isoparser.py", line 37, in func
return f(self, str_in, *args, **kwargs)
File "/usr/local/lib/python3.9/site-packages/dateutil/parser/isoparser.py", line 134, in isoparse
components, pos = self._parse_isodate(dt_str)
File "/usr/local/lib/python3.9/site-packages/dateutil/parser/isoparser.py", line 208, in _parse_isodate
return self._parse_isodate_common(dt_str)
File "/usr/local/lib/python3.9/site-packages/dateutil/parser/isoparser.py", line 213, in _parse_isodate_common
len_str = len(dt_str)
TypeError: object of type 'NoneType' has no len()

KeyError: 'LOW_SPEED_WHEEL'

I have a light mode called "LOW_SPEED_WHEEL" which is not supported up to now.
Mode "HIGH_SPEED_WHEEL" works fine.
Please can you add this mode.

KeyError: 'FULL_DYNAMIC_RGB'

Reported by @curt7000:

Unexpected error fetching smarttub data: 'FULL_DYNAMIC_RGB'
Traceback (most recent call last):
File "/srv/homeassistant/lib/python3.8/site-packages/homeassistant/helpers/update_coordinator.py", line 149, in async_refresh
self.data = await self._async_update_data()
File "/srv/homeassistant/lib/python3.8/site-packages/homeassistant/helpers/update_coordinator.py", line 137, in _async_update_data
return await self.update_method()
File "/home/homeassistant/.homeassistant/custom_components/smarttub/controller.py", line 82, in async_update_data
data[spa.id] = await self._get_spa_data(spa)
File "/home/homeassistant/.homeassistant/custom_components/smarttub/controller.py", line 89, in _get_spa_data
status, pumps, lights = await asyncio.gather(
File "/srv/homeassistant/lib/python3.8/site-packages/smarttub/api.py", line 188, in get_lights
return [
File "/srv/homeassistant/lib/python3.8/site-packages/smarttub/api.py", line 189, in
SpaLight(self, **light_info)
File "/srv/homeassistant/lib/python3.8/site-packages/smarttub/api.py", line 403, in init
self.mode = self.LightMode[properties["mode"]]
File "/usr/lib/python3.8/enum.py", line 349, in getitem
return cls.member_map[name]
KeyError: 'FULL_DYNAMIC_RGB'

PrimaryFiltrationMode isn't TrueWater aware

TrueWater (TM :) is a newer filtration mechanism. It uses 'nano bubbles', hence the nano* keys and values.

Probably the only value for node was NORMAL previously , but now we have NANO_MODE.

cheers,
steve

Results of
python3 -m smarttub -u YOUR_SMARTTUB_EMAIL -p YOUR_SMARTTUB_PASSWORD -vv info -a

"
DEBUG:smarttub.api:login successful, username=[email protected]
DEBUG:smarttub.api:GET accounts/3902c368-d524-4a9e-aac6-d429297178bf successful: {'id': '3902c368-d524-4a9e-aac6-d429297178bf', 'email': '[email protected]', 'notificationOptIn': True, 'termsOptIn': True, 'emailVerified': False, 'name': {'first': 'S', 'last': 'M'}, 'phone': None, 'language': 'en_US', 'lastUpdated': '2024-06-18T01:43:44.793151Z', 'voiceEnabledSpaId': None, 'sutroNotifications': True, 'atEaseEnabled': True}
DEBUG:smarttub.api:get_account successful: {'id': '3902c368-d524-4a9e-aac6-d429297178bf', 'email': '[email protected]', 'notificationOptIn': True, 'termsOptIn': True, 'emailVerified': False, 'name': {'first': 'S', 'last': 'M'}, 'phone': None, 'language': 'en_US', 'lastUpdated': '2024-06-18T01:43:44.793151Z', 'voiceEnabledSpaId': None, 'sutroNotifications': True, 'atEaseEnabled': True}
DEBUG:smarttub.api:GET spas?ownerId=3902c368-d524-4a9e-aac6-d429297178bf successful: {'content': [{'id': '100986607', 'dealerId': '0010f00002JiIDrAAN', 'ownerId': '3902c368-d524-4a9e-aac6-d429297178bf', 'name': None, 'dealer': {'id': '0010f00002JiIDrAAN', 'name': 'Aqua Quip', 'phone': '6120339', 'email': '[email protected]', 'address': {'street': '16205 NW BETHANY CT STE 106', 'city': 'BEAVERTON', 'state': 'Oregon', 'zip': '97006', 'country': 'United States'}, 'website': None, 'dealerNumber': '300-2662-1', 'activeDealerAccount': True, 'createdAt': '2020-10-30T23:01:33.769484', 'updatedAt': '2024-04-02T21:06:06.903226'}, 'owner': {'id': '3902c368-d524-4a9e-aac6-d429297178bf', 'email': '[email protected]', 'notificationOptIn': True, 'termsOptIn': True, 'emailVerified': False, 'name': {'first': 'S', 'last': 'M'}, 'phone': None, 'language': 'en_US', 'lastUpdated': '2024-06-18T01:43:44.793151Z', 'voiceEnabledSpaId': None, 'sutroNotifications': True, 'atEaseEnabled': True}, 'deviceId': '0a10aced202194944a04d8e4', 'device': {'id': '0a10aced202194944a04d8e4', 'barcode': 'P054AF40104D8E4', 'mobileSecret': 'UDRWG5B9HEU7F2U'}, 'selfTest': {'status': 'FAIL', 'current': {'heaterAndCirculationPump': 24.0, 'jets': [], 'blower': -99.9, 'circulationPump': -11.1, 'ozone': None, 'ultraviolet': None}, 'frequency': 'DAILY', 'startTime': None, 'lastTestTime': '2024-07-07T12:07:00Z'}, 'brand': 'Jacuzzi', 'series': 'J-400', 'modelNumber': 'Y112SDLDM', 'model': 'J-445', 'stereo': False, 'tubColor': 12, 'cabinetColor': 'D', 'equipmentOption': 'S', 'revision': 'L', 'ecomode': False, 'volume': 380, 'pairedAt': '2024-06-15T12:58:03.887228Z', 'subscriptionExpiredAt': '2024-12-15T12:58:03.887228Z', 'subscriptions': None, 'deviceOptIn': False, 'deviceOnline': False, 'powerResetRequired': False, 'voltage': 240, 'warrantyDate': None, 'createdAt': '2024-05-08T18:39:50.448399', 'createdSource': 'MANUFACTURING', 'adapter': 'OFF', 'productIdOrSlug': 'smarttub-multiwifi-ble-na-24464', 'us3G': False, '_b402Upgrade': False, 'sutro': None, 'location': None, 'signal': None, 'coupon': [], 'heaterWatts': 4945, 'coolingRate': 5.53e-07, 'avgAmbientTemp': None, 'ir': True, 'appSettings': None, 'nano': True, 'smEnable': None, 'current': {'value': 0.6, 'min': 0.6, 'max': 0.7, 'average': 0.6, 'kwh': 0.447}, 'water': {'oxidationReductionPotential': None, 'ph': None, 'temperature': 37.8, 'turbidity': None, 'temperatureLastUpdated': '2024-07-07T19:15:28.040Z'}, 'bp100': False, 'lastUpdated': '2024-07-07T22:51:19.755466Z'}], 'pageable': {'sort': {'sorted': True, 'unsorted': False, 'empty': False}, 'offset': 0, 'pageSize': 10, 'pageNumber': 0, 'paged': True, 'unpaged': False}, 'totalPages': 1, 'totalElements': 1, 'last': True, 'size': 10, 'number': 0, 'sort': {'sorted': True, 'unsorted': False, 'empty': False}, 'first': True, 'numberOfElements': 1, 'empty': False}
DEBUG:smarttub.api:GET spas/100986607 successful: {'id': '100986607', 'dealerId': '0010f00002JiIDrAAN', 'ownerId': '3902c368-d524-4a9e-aac6-d429297178bf', 'name': None, 'dealer': {'id': '0010f00002JiIDrAAN', 'name': 'Aqua Quip', 'phone': '6120339', 'email': '[email protected]', 'address': {'street': '16205 NW BETHANY CT STE 106', 'city': 'BEAVERTON', 'state': 'Oregon', 'zip': '97006', 'country': 'United States'}, 'website': None, 'dealerNumber': '300-2662-1', 'activeDealerAccount': True, 'createdAt': '2020-10-30T23:01:33.769484', 'updatedAt': '2024-04-02T21:06:06.903226'}, 'owner': {'id': '3902c368-d524-4a9e-aac6-d429297178bf', 'email': '[email protected]', 'notificationOptIn': True, 'termsOptIn': True, 'emailVerified': False, 'name': {'first': 'S', 'last': 'M'}, 'phone': None, 'language': 'en_US', 'lastUpdated': '2024-06-18T01:43:44.793151Z', 'voiceEnabledSpaId': None, 'sutroNotifications': True, 'atEaseEnabled': True}, 'deviceId': '0a10aced202194944a04d8e4', 'device': {'id': '0a10aced202194944a04d8e4', 'barcode': 'P054AF40104D8E4', 'mobileSecret': 'UDRWG5B9HEU7F2U'}, 'selfTest': {'status': 'FAIL', 'current': {'heaterAndCirculationPump': 24.0, 'jets': [], 'blower': -99.9, 'circulationPump': -11.1, 'ozone': None, 'ultraviolet': None}, 'frequency': 'DAILY', 'startTime': None, 'lastTestTime': '2024-07-07T12:07:00Z'}, 'brand': 'Jacuzzi', 'series': 'J-400', 'modelNumber': 'Y112SDLDM', 'model': 'J-445', 'stereo': False, 'tubColor': 12, 'cabinetColor': 'D', 'equipmentOption': 'S', 'revision': 'L', 'ecomode': False, 'volume': 380, 'pairedAt': '2024-06-15T12:58:03.887228Z', 'subscriptionExpiredAt': '2024-12-15T12:58:03.887228Z', 'subscriptions': None, 'deviceOptIn': True, 'deviceOnline': True, 'powerResetRequired': False, 'voltage': 240, 'warrantyDate': None, 'createdAt': '2024-05-08T18:39:50.448399', 'createdSource': 'MANUFACTURING', 'adapter': 'OFF', 'productIdOrSlug': 'smarttub-multiwifi-ble-na-24464', 'us3G': False, '_b402Upgrade': False, 'sutro': None, 'location': None, 'signal': None, 'coupon': [], 'heaterWatts': 4945, 'coolingRate': 5.53e-07, 'avgAmbientTemp': None, 'ir': True, 'appSettings': None, 'nano': True, 'smEnable': None, 'current': {'value': 0.6, 'min': 0.6, 'max': 0.7, 'average': 0.6, 'kwh': 0.447}, 'bp100': False, 'lastUpdated': '2024-07-07T22:51:19.755466Z', 'water': {'oxidationReductionPotential': None, 'ph': None, 'temperature': 37.8, 'turbidity': None, 'temperatureLastUpdated': '2024-07-07T19:15:28.040Z'}}
= Spa 'Jacuzzi J-445' =

DEBUG:smarttub.api:GET spas/100986607/status successful: {'ambientTemperature': 65.6, 'blowoutCycle': 'INACTIVE', 'cleanupCycle': 'INACTIVE', 'date': '2024-07-07', 'demoMode': 'DISABLED', 'dipSwitches': 292, 'displayTemperatureFormat': 'FAHRENHEIT', 'errorCode': 0, 'error': {'code': 0, 'title': 'All Clear', 'description': None}, 'flowSwitch': 'OPEN', 'heater': 'OFF', 'heatMode': 'AUTO', 'highTemperatureLimit': 41.1, 'lastUpdated': '2024-07-07T22:51:19.755466Z', 'state': 'NORMAL', 'ozone': 'OFF', 'setTemperature': 38.3, 'time': '15:51:00', 'timeFormat': 'HOURS_24', 'timezone': None, 'uv': 'OFF', 'uvOnDemand': 'OFF', 'current': {'value': 0.6, 'min': 0.6, 'max': 0.7, 'average': 0.6, 'kwh': 0.447}, 'primaryFiltration': {'mode': 'NANO_MODE', 'status': 'INACTIVE', 'startHour': 16, 'duration': 2, 'cycle': 6, 'startMinute': 0, 'durationMinute': 0, 'lastUpdated': '2024-07-07T20:58:32.203Z'}, 'secondaryFiltration': {'mode': 'AWAY', 'status': 'INACTIVE', 'enabled': False, 'startHour': 0, 'startMinute': 0, 'durationHour': 0, 'durationMinute': 0, 'lastUpdated': '2024-07-07T19:15:29.104Z'}, 'water': {'oxidationReductionPotential': None, 'ph': None, 'temperature': 37.8, 'turbidity': None, 'temperatureLastUpdated': '2024-07-07T19:15:28.040Z'}, 'versions': {'balboa': '0.05', 'controller': '2.09', 'jacuzziLink': '90'}, 'locks': {'temperature': 'UNLOCKED', 'spa': 'UNLOCKED', 'access': 'UNLOCKED', 'maintenance': 'UNLOCKED'}, 'location': {'latitude': 47.7010918, 'longitude': -122.3832016, 'accuracy': 1729.0}, 'fieldsLastUpdated': {'heatMode': '2024-07-07T19:15:28.040Z', 'setTemperature': '2024-07-07T19:15:28.040Z', 'uv': '2024-07-07T19:15:28.040Z', 'uvOnDemand': '2024-07-07T19:15:28.040Z', 'online': None, 'errEvent': None, 'locEvent': None, 'rpstEvent': '2024-07-07T22:51:17.933Z', 'spstEvent': '2024-07-07T22:34:53.649Z', 'cfstEvent': '2024-07-07T07:33:23.819Z', 'sp2stEvent': '2024-07-07T07:33:25.740Z', 'wcstEvent': None}, 'watercare': 'AWAY_FROM_HOME', 'online': None, 'pumps': None, 'lights': None, 'sensors': None, 'lowRangeLowTemp': None, 'lowRangeHighTemp': None, 'highRangeLowTemp': None, 'highRangeHighTemp': None, 'tempRange': None, 'heater1Present': None, 'heater2Present': None, 'ozoneSystemPresent': None, 'primingMode': None, 'heater2On': None, 'heatPumpStatus': None, 'heatPumpMode': None, 'heatPumpEfficiency': None, 'electricHeaterMode': None, 'czStatus': None, 'chromazon3Present': None, 'chromazon3Zone1': None, 'chromazon3Zone2': None, 'chromazon3Zone3': None, 'heatCanWait': 4, 'heatVacation': False, 'signal': None, 'errors': None, 'nanoMode': 'MEDIUM', 'nanoStatus': 'OFF', 'nanoSummerMode': 'FILTRATION_INACTIVE', 'nanoSmodeOverride': 'ENABLED', 'nanoPressure': -1.0, 'nanoPressureAlert': 'OK', 'nanoBattery': 'OK', 'nanoAccumulation': 302, 'lastWifi': None, 'timeSet': None}
Traceback (most recent call last):
File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/main.py", line 199, in
asyncio.run(main(sys.argv[1:]))
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
return future.result()
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/main.py", line 196, in main
await args.func(spas, args)
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/main.py", line 17, in info_command
status = await spa.get_status()
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 184, in get_status
return SpaState(self, **await self.request("GET", "status"))
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 311, in init
self._prop(
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 358, in _prop
constructor(self.properties[json_key]),
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 313, in
constructor=lambda p: SpaPrimaryFiltrationCycle(self.spa, **p),
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 397, in init
self._prop("mode", constructor=lambda x: self.PrimaryFiltrationMode[x])
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 358, in _prop
constructor(self.properties[json_key]),
File "/home/willey/.local/lib/python3.10/site-packages/smarttub/api.py", line 397, in
self._prop("mode", constructor=lambda x: self.PrimaryFiltrationMode[x])
File "/usr/lib/python3.10/enum.py", line 440, in getitem
return cls.member_map[name]
KeyError: 'NANO_MODE'
willey@nib:$
willey@nib:
$
"

Add support for pyjwt>=2.0

We, NixOS, as a linux distro, need to update our package set from time to time. This time round we updated pyjwt to 2.0.1. This broke python-smarttub, which in turn broke parts of our home-assistant package.

There seems to be a typing change in 2.0 that causes three tests to fail.

_________________________ ERROR at setup of test_login _________________________

unauthenticated_api = <smarttub.api.SmartTub object at 0x7ffff59cb640>
aresponses = <aresponses.main.ResponsesMockServer object at 0x7ffff59f2970>

    @pytest.fixture(name="api")
    async def api(unauthenticated_api, aresponses):
        api = unauthenticated_api
        aresponses.add(
            response={
>               "access_token": jwt.encode(
                    {api.AUTH_ACCOUNT_ID_KEY: ACCOUNT_ID, "exp": time.time() + 3600},
                    "secret",
                ).decode(),
                "token_type": "Bearer",
                "refresh_token": "refresh1",
            }
        )
E       AttributeError: 'str' object has no attribute 'decode'

tests/test_api.py:25: AttributeError
_____________________ ERROR at setup of test_refresh_token _____________________

unauthenticated_api = <smarttub.api.SmartTub object at 0x7ffff5dc4490>
aresponses = <aresponses.main.ResponsesMockServer object at 0x7ffff59d4040>

    @pytest.fixture(name="api")
    async def api(unauthenticated_api, aresponses):
        api = unauthenticated_api
        aresponses.add(
            response={
>               "access_token": jwt.encode(
                    {api.AUTH_ACCOUNT_ID_KEY: ACCOUNT_ID, "exp": time.time() + 3600},
                    "secret",
                ).decode(),
                "token_type": "Bearer",
                "refresh_token": "refresh1",
            }
        )
E       AttributeError: 'str' object has no attribute 'decode'

tests/test_api.py:25: AttributeError
______________________ ERROR at setup of test_get_account ______________________

unauthenticated_api = <smarttub.api.SmartTub object at 0x7ffff5c4ff70>
aresponses = <aresponses.main.ResponsesMockServer object at 0x7ffff5c4fb80>

    @pytest.fixture(name="api")
    async def api(unauthenticated_api, aresponses):
        api = unauthenticated_api
        aresponses.add(
            response={
>               "access_token": jwt.encode(
                    {api.AUTH_ACCOUNT_ID_KEY: ACCOUNT_ID, "exp": time.time() + 3600},
                    "secret",
                ).decode(),
                "token_type": "Bearer",
                "refresh_token": "refresh1",
            }
        )
E       AttributeError: 'str' object has no attribute 'decode'

tests/test_api.py:25: AttributeError
=========================== short test summary info ============================
ERROR tests/test_api.py::test_login - AttributeError: 'str' object has no att...
ERROR tests/test_api.py::test_refresh_token - AttributeError: 'str' object ha...
ERROR tests/test_api.py::test_get_account - AttributeError: 'str' object has ...
========================= 10 passed, 3 errors in 0.36s =========================

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.