GithubHelp home page GithubHelp logo

manzanotti / geniushub-client Goto Github PK

View Code? Open in Web Editor NEW
9.0 5.0 5.0 494 KB

Python library to provide direct connectivity to Genius Hub.

License: MIT License

Python 100.00%
genius geniushub heat heatgenius

geniushub-client's People

Contributors

geoffathome avatar gitter-badger avatar manzanotti avatar rbubley avatar zxdavb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

geniushub-client's Issues

convert_zone fails.

Good to see this being used in HA - but has broken what was previously working for me.

Not sure why some many warning are disabled as these are there for a good reason. To stop crashes like this:

Traceback (most recent call last):
  File "/home/geoff/home-assistant/homeassistant/setup.py", line 153, in _async_setup_component
    hass, processed_config)
  File "/home/geoff/home-assistant/homeassistant/components/geniushub/__init__.py", line 50, in async_setup
    await data._client.hub.update()  # pylint: disable=protected-access
  File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 644, in update
    [_populate_zone(z) for z in await self._get_zones_raw]
  File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 644, in <listcomp>
    [_populate_zone(z) for z in await self._get_zones_raw]
  File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 557, in _populate_zone
    zone_dict = self._convert_zone(zone_raw)
  File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 262, in _convert_zone
    elif setpoint_temp != default_temp:                              # noqa: disable=F821; pylint: disable=used-before-assignment
UnboundLocalError: local variable 'setpoint_temp' referenced before assignment
2019-05-19 15:24:29 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=persistent_notification, service=create, service_data=title=Invalid config, message=The following components and platforms could not be set up:

 - introduction
 - [geniushub](https://home-assistant.io/components/geniushub/)

Please check your config., notification_id=invalid_config>
2019-05-19 15:24:29 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=persistent_notification.invalid_config, old_state=<state persistent_notification.invalid_config=notifying; title=Invalid config, message=The following components and platforms could not be set up:

Still no time to help on the project but hopefully in July.

Can't set override mode (temp, duration) for zones

This is the initial state of the On/Off zone, Override/On:

(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": true, "override": {"duration": 549, "setpoint": true}, "_state": {"bIsActive": true, "bOutRequestHeat": true}}

... then we instruct the zone to Override/Off (0 is off) for 10 minutes:

(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -t0 -s600

However, it didn't take effect (setpoint is still true):

(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": true, "override": {"duration": 529, "setpoint": true}, "_state": {"bIsActive": true, "bOutRequestHeat": true}}

... let's try it again, but via curl:

(venv) dbonnes@vm-builder:~$ curl --user ${USERNAME}:${HASH} -X PATCH  http://${HUB_ADDRESS}:1223/v3/zone/17 -d '{"iMode": 16, "fBoostSP": 0, "iBoostTimeRemaining": 600}' > /dev/null

That did work (setpoint is true, and duration has increased):

(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": false, "override": {"duration": 589, "setpoint": false}, "_state": {"bIsActive": true, "bOutRequestHeat": false}}

I don't know if other zone types/mode are affected.

Switches

I am trying to write an automation in HA for a switch.

The switch exists in its own zone in Genius Hub.

In HA the switch appears the following two entities:

  • binary_sensor.smart_plug_4
  • switch.zone_name

binary_sensor.smart_plug_4 service data
assigned_zone: zone_name socket
last_comms: '2021-01-21T15:30:59+00:00'
state: {}
friendly_name: Smart Plug 4

switch.zone_name service data
entity_id: switch.zone_name

I have tried turning the switch off and on calling the service switch.turn_off and switch.turn_on
I have tried using the services geniushub.set_zone_mode and geniushub.set_zone_override

The HA documentation for the GH states:
Individual smart plugs are not yet exposed as switches - you can create one zone per smart plug as a work-around.

Am I missing something or a setup/user error on my part, but how do I toggle the switch on and off from HA?

Happy to extend the code to support this if needed but just wanted to check here first incase it is user error on my part or not.

If the code needs to be extended I would expose smart sockets as a switch. This would toggle on and off. On would be the default timer period. Also create a service geniushub.set_switch_override to allow the override duration to be changed.

Let me know your views.

Home Assistant Integration failing suddenly

The integration between Home Assistant and Genius Hub suddenly stopped working last week. I have done nothing to the integration in Home Assistant and I have done nothing to my Genius Hub system. I have rebooted my GH system three times and restarted HA countless times. Genius Hub is showing no issues or errors.

I have removed the configuration from configuration.yaml and rebooted, replaced it and rebooted again and still I see the following error:

Logger: homeassistant.setup
Source: components/geniushub/__init__.py:124
First occurred: 10:26:05 (1 occurrences)
Last logged: 10:26:05

Error during setup of component geniushub
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/setup.py", line 253, in _async_setup_component
    result = await task
  File "/usr/src/homeassistant/homeassistant/components/geniushub/__init__.py", line 124, in async_setup
    await client.update()
  File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 336, in update
    await self._update()  # now convert all the raw JSON
  File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 303, in _update
    self.issues = [convert_issue(raw_json) for raw_json in self._issues]
  File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 303, in <listcomp>
    self.issues = [convert_issue(raw_json) for raw_json in self._issues]
  File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 262, in convert_issue
    device_type = self.device_by_id[raw_json["data"]["nodeID"]].data["type"]
KeyError: '29'

I have tried:

  • removed the integration (then reboot)
  • deleted all genius hub entities (reboot)
  • searched files in .storage to find any mentions of 29 - there aren’t any
  • searched in customisations, sensors, automations, known devices files for ‘29’ - again nothing found
  • (reboot)
  • added the integration (reboot)

Error is still seen. I repeated the above twice.

What version of Home Assistant Core has the issue?
2023.1.6

What type of installation are you running?
Home Assistant OS

Integration causing the issue
Genius Hub

Link to integration documentation on our website
https://www.home-assistant.io/integrations/geniushub/

Also raised here, with no responses to date
home-assistant/core#86277

A smart plug state, as a switch, does not reflect correct state on start-up

The state of plug (on or off) is not updated until the switch is used.

In switch.py we have:

    @property
    def is_on(self) -> bool:
        """Return the current state of the on/off zone.

        The zone is considered 'on' if & only if it is override/on (e.g. timer/on is 'off').
        """
        return self._zone.data["mode"] == "override" and self._zone.data["setpoint"]

The switch can on if the mode is "override" or "timer". I am not sure if or why this test is required.

The return statement could be:

      return and self._zone.data["setpoint"]

or if we what the check on the mode include "timer"

    return self._zone.data["mode"] in ["override", "timer"] and self._zone.data["setpoint"]

Both appear to resolve the issue and reflect the same state as in the Genius App.

Updates for installation

While attempting to build on a new system (Windows) I am capturing here some issues I have run into.

python .\setup.py installTraceback (most recent call last):
File "setup.py", line 8, in <module>
from setuptools import find_packages, setup
ModuleNotFoundError: No module named 'setuptools'

Fix: pip install setuptools

python .\setup.py install
Traceback (most recent call last):
setup.py", line 15, in <module>
 VERSION = os.environ["GITHUB_REF_NAME"]
   ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "<frozen os>", line 685, in __getitem__
KeyError: 'GITHUB_REF_NAME'

Could not solve this using Windows POWERSHELL. Ended up using pip install aiohttp. This allowed me to run the client locally.

Battery warning (not battery low) doesn't state which zone

The battery warning e.g. 'The battery for the Genius Valve is low' doesn't provide zone information, and therefore isn't localisable to an individual device. I think this this because it is missing {zone_name} from the error message.

Smart plugs are duplicated as Switches and Binary Sensors

If we are saying Smart Plugs should be switches and not Binary Sensors, then Smart Plugs should be filtered out of Binary Sensors. To do this make the following two changes to binary_sensor.py:
Add

GH_TYPE = "Receiver"

to

GH_STATE_ATTR = "outputOnOff"
GH_TYPE = "Receiver"

And change:

async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the Genius Hub sensor entities."""
    if discovery_info is None:
        return

    broker = hass.data[DOMAIN]["broker"]

    switches = [
        GeniusBinarySensor(broker, d, GH_STATE_ATTR)
        for d in broker.client.device_objs
        if GH_STATE_ATTR in d.data["state"]
    ]

    async_add_entities(switches, update_before_add=True)

to

async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the Genius Hub sensor entities."""
    if discovery_info is None:
        return

    broker = hass.data[DOMAIN]["broker"]

    switches = [
        GeniusBinarySensor(broker, d, GH_STATE_ATTR)
        for d in broker.client.device_objs
        if GH_TYPE in d.data["type"]
    ]

    async_add_entities(switches, update_before_add=True)```

Move zone properties to a class

Whilst I am happy to maintain the client's current v1-compatible JSON output, I would like to expose the v3 API too.

To make the distinction more obvious to existing users, I propose adding classes to contain the properties and statuses of a zone, with obviously named properties on the classes (e.g. _has_room_sensor rather than has_Pir).

The current data property that returns the v1-compatible JSON can then be generated from the properties on these classes.

Errors logged in HA.

Not really sure why the schedule is required to be processed by HA. HA just needs to reflect the state of each device.

2019-11-13 19:08:06 ERROR (MainThread) [geniushubclient] Failed to convert Zone 23, message: list index out of range.
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/geniushubclient/__init__.py", line 546, in _convert
    result["schedule"]["timer"] = _timer_schedule(raw_json)
  File "/usr/local/lib/python3.7/site-packages/geniushubclient/__init__.py", line 463, in _timer_schedule
    tm_last = setpoints[idx + 1]["iTm"]
IndexError: list index out of range

It would be far easier, for HA, if during initialisation this code was not executed unless I am missing the point as to why it is needed. In HA schedules cannot, currently, be viewed or edited.

I have been away sometime. Finally back home, moved, and get HA up and running in our new house.

iMode 0 not mapped - causing errors

Originally posted by @sftgunner in home-assistant/core#78799 (comment)

    Hi @manzanotti, just to add to your todo list (sorry!)...

I've done the same as @rbubley and have replaced the client library with the current master branch which solves some of the error messages, but I'm still getting the error
Failed to convert Zone 0. Traceback (most recent call last): File "/config/custom_components/geniushub/geniushubclient/zone.py", line 150, in data result["mode"] = IMODE_TO_MODE[raw_json["iMode"]] KeyError: 0.
From inspection of the raw v3 /zones API endpoint, it looks like I have two zones - one for the whole house and a zone which appears in the GeniusHub web GUI as my test room. I have a powered room thermostat in the test room, which has iMode 2. However, the "whole house" zone has iMode 0 (which helpfully isn't documented), which doesn't exist in the const IMODE_TO_MODE and thus causes the whole thing to fail.
Any ideas?

Sensor should reflect zone name in addition to just an id

Currently sensors such as battery levels just reflect the id of the device. If this included the zone name it would make it more useful.

In sensor.py

I suggest the code is changed from:

class GeniusBattery(GeniusDevice, SensorEntity):
    """Representation of a Genius Hub sensor."""

    def __init__(self, broker, device, state_attr) -> None:
        """Initialize the sensor."""
        super().__init__(broker, device)

        self._state_attr = state_attr

        self._attr_name = f"{device.type} {device.id}"

to:

class GeniusBattery(GeniusDevice, SensorEntity):
    """Representation of a Genius Hub sensor."""

    def __init__(self, broker, device, state_attr) -> None:
        """Initialize the sensor."""
        super().__init__(broker, device)

        self._state_attr = state_attr

        self._attr_name = f"{device.type} ({device.id}) in {device.assigned_zone.name}"

from

Smart Plug Override with Time

Hi - first of all many thanks for all the work here, I came by way of Home Assistant where I wanted to override a Smart Plug at Sunset for a period of time, I can't get it to work there so wanted to check the underlying solution.

Here's what I have found:

  1. I can connect to and override the zone to "off" ( python3 ghclient.py -u [blahblah] -p [blahblah] [HUB IP] -z 15 -m off)
  2. I cannot override the zone to "on" with a time ( python3 ghclient.py -u [blahblah] -p [blah] [HUB IP] -z 15 -m override -s 600)

There is no error and the zone does come on but the override time is whatever it was before (when trying this in Home Assistant I get an error (see image)

image.

There are a couple of things in ghclient.py that confuse me and this may be the root of my issue:

Examples:
ghclient.py HUB_ID [Makes sense]
Display information about the Hub.

ghclient.py HUB_ID zones -v [Makes sense]
Display detailed information about all Zones.

ghclient.py HUB_ID -z 3 off [Assume this needs to be "-m off"]
Turn Zone 3 off.

ghclient.py HUB_ID -z 12 -d 3600 -t 19.5 [Assume this needs to be -s 3600 and it would override for 3600 seconds]
Set the override temperature for Zone 12 to 19.5C for 1 hour.

I can't get the override to work with Power Zone or Heating Zone - the mode changes OK to Override but the time and the temperature are not applied.

So in summary this is either user error or a bug, override works but the time and temperature settings don't seem to (for me).

I'm very happy to provide more information or try things out if it helps.

Regards,
Gav

PS The Temperature Override in HA appears OK, but the duration does not seem to work.

Extra route for v3 API

For what it's worth, I have observed that the iOS client can request a Z-Wave ping by a making a POST to this route: /v3/hg-zwave/ping/{deviceId:int}. This route isn't in the published API description.

(Incidentally GETs to /v3/hg-zwave give a variety of information, including all the information on the 'Device List' page of the app and quite a lot more - it might be interesting in due course to be able to access this info from geniushub-client).

Making your client more resilient to changes by Genius Hub

Hi there!

I have been getting a very similar failure to the one found here: #74 only my issue complains because it can't find 'type' in the dict its found. The stack trace fails in exactly the same place however.

With a bit of investigation, I established your library gets 'friendly device names' and so forth from data that is hardcoded in const.py - this of course means that if Genius Hub release new devices, or even update the firmware in such a way that device ids change, things break.

With a bit more investigation, I've established that you are getting your mapping data structures from the Genius Hub frontend client. A data object that appears to be completely identical in shape to the one that is causing the problem for me can be found in https://www.geniushub.co.uk/app/js/bower.js.

To make your library more resilient, I think you need to find some way of automatically fetching that data, otherwise as the maintainer you are faced with the task of continually updating the package to match any updates the GH team do. To do it at runtime would be slow, but it might not be a bad idea to do this either at install or publish time. With that in mind, I've written a python script this evening that essentially does just this: downloads the bower.js file, parses it, locates the dict containing the AST for the 'devicesModel', generates a python AST from it, then writes that to disk as a python source file.

It's not perfect (and parsing that JS file is particularly slow), but I'd like to work this into something that you can use in your package to make sure that its always up to date. If this works, would you accept it as a PR? Script is as follows:

import aiohttp
import json
import asyncio
import ast
import astor
from pyjsparser import parse


def find_symbol_with_name(name, my_dict, parent):
    """
    Recursively iterate through the generated JS AST
    until you find an object with a 'name' that matches
    the passed in name argument. Once found, return its parent
    """
    if (isinstance(my_dict, dict) and "name" in my_dict and my_dict['name'] == name):
        return parent

    if (isinstance(my_dict, dict)):
        for next_key in my_dict:
            if (type(my_dict[next_key]) == list):
                for item in my_dict[next_key]:
                    result = find_symbol_with_name(name, item, my_dict)
                    if (result != None):
                        return result

            if (isinstance(my_dict[next_key], dict)):
                result = find_symbol_with_name(name, my_dict[next_key], my_dict)
                if (result != None):
                    return result

    return None


def transform_value(value):
    if value['type'] == 'Literal':
        return ast.Constant(value['value'])

    if value['type'] == 'ArrayExpression':
        values = []
        for element in value['elements']:
            values.append(transform_value(element))

        return ast.List(elts=values)


def process_devices_model(data):
    """
    Generate a python AST from the found JS AST
    """
    elements = data['value']['elements']

    keys = []
    values = []

    for element in elements:
        for property in element['properties']:
            keys.append(ast.Constant(value=property['key']['value']))
            values.append(transform_value(property['value']))

    ast_elements = ast.Dict(
        keys=keys,
        values=values
    )

    return ast.Module(body=[
        ast.Assign(targets=[ast.Name(id="DEVICES_MODEL")],
                   value=ast.List(
            elts=[ast_elements]
        )
        )
    ])


async def main():
    async with aiohttp.ClientSession() as session:
        print('Downloading bower.js')
        async with session.get('https://www.geniushub.co.uk/app/js/bower.js') as response:
            javascript = await response.text()

            print('Parsing JavaScript')
            ast = parse(javascript)

            print('Extracting devicesModel')
            devices = find_symbol_with_name('devicesModel', ast, None)

            print('Generating source')
            devices_ast = process_devices_model(devices)
            source = astor.to_source(devices_ast)

            print('Writing to disk')
            with open('output.py', 'w') as file:
                file.write(source)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

nodeID missing for raw_json["Data"]

I had an issue that has now gone away so this is more for information.

As part of trying to address the problem with switches not being in a zone I tried to create that situation on my HG hub. I did this by deleting a zone, before removing the device (smart plug) from the zone. The smart plug got orphaned. I could see it in the list of devices but this led to two errors.
The first:
image
Without this information geniushub-client fails to log in and reports a 503 error as it cannot retrieve the version information.

The second, once the first was fixed by power cycling the hub leads to this device_id = raw_json["data"]["nodeID"] line throwing a key error "nodeID" missing.

I have strengthen the code for testing like this.

           if "{device_type}" in description:
                # don't use nodeHash, it won't pick up (e.g. DCR - Channel 1)
                # vice_type = DEVICE_HASH_TO_TYPE[raw_json["data"]["nodeHash"]]
                if "nodeID" in raw_json["data"]:
                    device_id = raw_json["data"]["nodeID"]
                    if device_id in self._device_by_id.keys():
                        device = self._device_by_id[device_id]
                        device_type = device.data["type"]
                    else:
                        device_type = "Unknown device"
                else:
                    device_type = "Unknown device"

My hub is now in a good state and I cannot reproduce this problem or capture the JSON returned from the hub.

I think the support team at HG did something as the orphaned smart plug is now back where it should have been.

Feel free to close this issue as if it can't be reproduced it can't be fixed. You may want make the code more robust first.

Unable to login with V3

All was working fine with HA until the latest update. I can no longer connect to my Genius Hub.

I am testing standalone with ghclient.py with correct local IP address, username and password. The follow error is produced:

Exception has occurred: ClientConnectorError
Cannot connect to host http:80 ssl:default [getaddrinfo failed]
  File "D:\demos\geniushub-client\geniushubclient\session.py", line 45, in request
    async with http_method(
  File "D:\demos\geniushub-client\geniushubclient\__init__.py", line 248, in update
    zones, data_manager, auth = await asyncio.gather(
  File "D:\demos\geniushub-client\ghclient.py", line 191, in main
    await hub.update()  # initialise: enumerate all zones, devices & issues
  File "D:\demos\geniushub-client\ghclient.py", line 262, in <module>
    LOOP.run_until_complete(main(LOOP))

Doing the same (local IP address, username and password) from https://pypi.org/project/geniushub/ => https://github.com/GeoffAtHome/geniushub connects fine.

I am wondering if anyone else is seeing this issue or is it my system and setup?

Will investigate more later... just reaching out incase someone else has see the same issue.

Errors parsing some zone timer schedules

I get this error for 2 zones in my list whenever I run ghclient:

ERROR: Failed to convert Zone 3, message: list index out of range.
Traceback (most recent call last):
  File "./geniushubclient/__init__.py", line 246, in populate_objects
    entity = obj_by_id[raw_json[key]]
KeyError: 3

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./geniushubclient/__init__.py", line 556, in _convert
    result["schedule"]["timer"] = _timer_schedule(raw_json)
  File "./geniushubclient/__init__.py", line 473, in _timer_schedule
    tm_last = setpoints[idx + 1]["iTm"]
IndexError: list index out of range

Which is actually taking place in _timer_schedule(raw_json) -> Dict, when the last item in objTimer has the same temp value as the "defaultSetpoint"

I stopped it complaining like this (the error was in the setpoints[idx + 1]["iTm"]:

-                elif sp_next != node["defaultSetpoint"]:
+                elif sp_next != node["defaultSetpoint"] and idx != len(setpoints)-1:

Though I'm not quite sure what the comparison to defaultSetPoint is for, only collect times when the temp is different to the default?

The data:

      {
        "fSP": 21,
        "iDay": 6,
        "iTm": -1
      },
      {
        "fSP": 15,
        "iDay": 6,
        "iTm": 0
      },
      {
        "fSP": 21,
        "iDay": 6,
        "iTm": 300
      }

Zone with missing type key

I have a Genius Hub with an added OpenTherm Board (a beta trial they ran). That has added an extra zone for the boiler but without a type key returned from the api. I have emailed Genius Hub asking them to add one but no response as yet.
Would you accept a PR to check that the type key exists? I am currently getting the following error:

Logger: geniushubclient.zone
Source: /usr/local/lib/python3.10/site-packages/geniushubclient/zone.py:184 
First occurred: 12:18:41 (4 occurrences) 
Last logged: 12:21:41

Failed to convert Zone 1.
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/geniushubclient/zone.py", line 145, in data
    result["type"] = ITYPE_TO_TYPE[raw_json["iType"]]
KeyError: 7

The relevant part returned from /zones of the api:

{
    "id": 1,
    "name": "Ideal Vogue Max",
    "output": 0,
    "mode": "linked",
    "schedule": {
        "timer": {},
        "footprint": {}
    }
},

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.