Git Product home page Git Product logo

telefonicaid / iotagent-node-lib Goto Github PK

View Code? Open in Web Editor NEW
58.0 25.0 83.0 7.81 MB

Module to enable IoT Agent developers to build custom agents for their devices that can easily connect to NGSI Context Brokers

Home Page: https://iotagent-node-lib.rtfd.io/

License: GNU Affero General Public License v3.0

JavaScript 97.95% HTML 0.15% CSS 0.42% Shell 0.07% Dockerfile 0.06% Python 1.36%
fiware iot iot-agent

iotagent-node-lib's Introduction

FIWARE IoT Agent Node.js Library

FIWARE IoT Agents License: APGL Support badge
Documentation badge CI Coverage Status Status CII Best Practices

This project aims to provide a Node.js module to enable IoT Agent developers to build custom agents for their devices that can easily connect to NGSI Context Brokers (such as Orion).

An IoT Agent is a component that lets groups of devices send their data to and be managed from a FIWARE NGSI Context Broker using their own native protocols. IoT Agents should also be able to deal with security aspects of the FIWARE platform (authentication and authorization of the channel) and provide other common services to the device programmer.

This project is part of FIWARE. For more information check the FIWARE Catalogue entry for the IoT Agents.

📚 Documentation 🎓 Academy 🎯 Roadmap

Index

Background

The main concept of the IoT Agent node library is to provide a common framework for provisioning IoT devices, allowing each individual IoT Agent to access standardized mapping data for devices and to offer a series common utility functions.

  • For southbound communications, the library listens to changes in context entities and raises callbacks for the IoT Agent to process.
  • For northbound communications, the library offers an interface which accepts structured input data so that all NGSI communications are left to the library.
  • Standardized OAuth2-based security is available to enable each IoT Agent to connect to several common Identity Managers (e.g. Keystone and Keyrock) so that communications can be restricted to trusted components.

Each individual IoT Agent offers is driven by a config.js configuration file contains explicit custom settings based on the protocol and payload the IoT Agent is translating. It will also contain some common flags for common functionality provided by the IoT Agent node lin (e.g. for contecting to a conext broker or for authentication). The IoT Agent node library offers a standard API for provisioning devices and ensures that each IoT Agent can configure its device communications using a common vocabulary regardless of the payload, syntax or transport protocol used by the device itself.

Install

The IoT Agent node library is not a standalone product and should be added as a dependency to package.json of the IoT Agent

...
"dependencies": {
    "iotagent-node-lib": "*",
}

In order to use the library within your own IoT Agent, you must first you require it before use:

const iotagentLib = require('iotagent-node-lib');

Information about how to configure the Library can be found at the corresponding section of the Installation & Administration Guide.

Usage

This library has no packaging or build processes. The Getting Started is a good place to start. You can also review the API documentation for a full list of the available functions.

If you plan to use the library in your own IoT Agent, you should read the Developer Guide. You can also review the Architecture documentation and Northbound API.

The following features are listed as deprecated.

API

The IoT Agent node library offers a simple REST API which provides common functionality to access, provision and decommission devices. API.

Testing

Contributions to development can be found here - additional contributions are welcome.

If you are whishing to test the library, or include new tests (either as part of a contribution or as a new feature or as a bug report), you can use the functional tests suite included in the project. The tests are described using a JSON file. You can find more information about the test suite in the Functional Tests Guide.

Agent Console

A command-line client to experiment with the library is packed with it. The command-line client can be started using the following command:

bin/agentConsole.js

The client offers an API similar to the one offered by the library: it can start and stop an IoT agent, register and unregister devices, send measures mimicking the device and receive updates of the device data. Take into account that, by default, the console uses the same config.js file than the IoT Agent.

The command-line client creates a console that offers the following options:

stressInit

	Start recording a stress batch.

stressCommit <delay> <times> <threads> <initTime>

	Executes the recorded batch as many times as requested, with delay (ms) between commands.
	The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
	parameter indicates the mean of the random initial waiting times for each agent.

exit

	Exit from the command-line.

start

	Start the IoT Agent

stop

	Stop the IoT Agent

register <id> <type>

	Register a new device in the IoT Agent. The attributes to register will be extracted from the
	type configuration

unregister <id> <type>

	Unregister the selected device

showConfig

	Show the current configuration file

config <newConfig>

	Change the configuration file to a new one

updatevalue <deviceId> <deviceType> <attributes>

	Update a device value in the Context Broker. The attributes should be triads with the following
	format: "name/type/value" sepparated by commas.

listdevices

	List all the devices that have been registered in this IoT Agent session

Agent tester

Command-line testing

The library also offers a Context Broker and IoT Agent client that can be used to:

  • Simulate operations to the Context Broker used by the IoT Agent, triggering Context Provider forwardings for lazy attributes and checking the appropriate values for active ones.
  • Simulate operations to the Device Provisioning API and Configuration API of the IoT Agent.

The tester can be started with the following command, from the root folder of the project:

bin/iotAgentTester.js

From the command-line, the help command can be used to show a description of the currently supported features. These are the following:

stressInit

	Start recording a stress batch.

stressCommit <delay> <times> <threads> <initTime>

	Executes the recorded batch as many times as requested, with delay (ms) between commands.
	The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
	parameter indicates the mean of the random initial waiting times for each agent.

exit

	Exit from the command-line.

update <entity> <type> <attributes>

	Update the values of the defined set of attributes, using the following format: name#type=value(|name#type=value)*

append <entity> <type> <attributes>

	Append a new Entity with the defined set of attributes, using the following format: name:type=value(,name:type=value)*

query <entity> <type>

	Get all the information on the selected object.

queryAttr <entity> <type> <attributes>

	Get information on the selected object for the selected attributes.

discover <entity> <type>

	Get all the context providers for a entity and type.

configCb <host> <port> <service> <subservice>

	Config a new host and port for the remote Context Broker.

showConfigCb

	Show the current configuration of the client for the Context Broker.

configIot <host> <port> <service> <subservice>

	Config a new host and port for the remote IoT Agent.

showConfigIot

	Show the current configuration of the client for the IoT Agent.

provision <filename>

	Provision a new device using the Device Provisioning API. The device configuration is
	read from the file specified in the "filename" parameter.

provisionGroup <template> <data> <type>

	Provision a group of devices with the selected template, taking the information needed to
	fill the template from a CSV with two columns, DEVICE_ID and DEVICE_NAME. The third parameter, type
	will be used to replace the DEVICE_TYPE field in the template. All the devices will be provisioned
	to the same IoT Agent, once the templates have been fulfilled.

listProvisioned

	List all the provisioned devices in an IoT Agent.

removeProvisioned <deviceId>

	Remove the selected provisioned device from the IoT Agent, specified by its Device ID.

addGroup <filename>

	Add a new device group to the specified IoT Agent through the Configuration API. The
	body is taken from the file specified in the "filename" parameter.

listGroups

	List all the device groups created in the selected IoT Agent for the configured service

removeGroup <apiKey> <resource>

	Remove the device group corresponding to the current configured subservice.

authenticate <host> <port> <user> <password> <service>

	Authenticates to the given authentication server, and use the token in subsequent requests.

setProtocol <protocol>

	Sets the protocol to use in the requests (http or https). Defaults to http.

configMigration <host> <port> <originDb>

	Sets the configuration for a migration between a C++ IoTA and a Node.js one.

showConfigMigration

	Shows the current migration configuration.

addProtocols <protocols>

	Add a protocol translation table, in the following format:
		protocolOrigin1=protocolTarget1;protocolOrigin2=protocolTarget2...


migrate <targetDb> <service> <subservice>

	Migrate all the devices and services for the selected service and subservice into the
	specified Mongo database. To perform the migration for all the services or all the
	subservices, use the "*" value.

The agent session stores transient configuration data about the target Context Broker and the target IoT Agent. This configuration is independent, and can be checked with the showConfigCb and showConfigIot commands, respectively. Their values can be changed with the configCb and configIot commands respectively. The new configurations will be deleted upon startup.


Licence

The IoT Agent Node Library is licensed under Affero General Public License (GPL) version 3.

© 2022 Telefonica Investigación y Desarrollo, S.A.U

Are there any legal issues with AGPL 3.0? Is it safe for me to use?

There is absolutely no problem in using a product licensed under AGPL 3.0. Issues with GPL (or AGPL) licenses are mostly related with the fact that different people assign different interpretations on the meaning of the term “derivate work” used in these licenses. Due to this, some people believe that there is a risk in just using software under GPL or AGPL licenses (even without modifying it).

For the avoidance of doubt, the owners of this software licensed under an AGPL-3.0 license wish to make a clarifying public statement as follows:

Please note that software derived as a result of modifying the source code of this software in order to fix a bug or incorporate enhancements is considered a derivative work of the product. Software that merely uses or aggregates (i.e. links to) an otherwise unmodified version of existing software is not considered a derivative work, and therefore it does not need to be released as under the same license, or even released as open source.

iotagent-node-lib's People

Contributors

aarranz avatar actions-user avatar alvarovega avatar anabelengp avatar bobeal avatar chicco785 avatar danielvillalbamota avatar dcalvoalonso avatar deepshikhanec avatar dependabot[bot] avatar dmoranj avatar fgalan avatar flopezag avatar frbattid avatar gtorodelvalle avatar jagatjot avatar jason-fox avatar kandarp-n avatar keshavsoni2511 avatar kevinf745 avatar madhu-nec avatar manucarrace avatar mapedraza avatar mayanksingh-nec avatar mgaggero avatar michelsc avatar mrutid avatar nmatsui avatar samueltjackson avatar xavierval avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iotagent-node-lib's Issues

Different device groups with the same type name can't be created

Currently, the entity type name is an internal key so if a new device group arrives with the same device type it will overwrite de older one. This behavior is wrong, but there is no clear idea about what to use as the universal ID of a device group (service/subservice tuple may be a candidate, but multiple types should appear for a single tuple).

Issue using agentConsole.js: listdevices not working

An error is raised running "listdevices" option from agent console:

IoTAgent> listdevices
/programs/telefonicaid/iotagent-node-lib/lib/services/deviceRegistryMemory.js:91
callback(null, result);
^
TypeError: undefined is not a function
at Object.listDevices as list
at Object.listDevices (/programs/telefonicaid/iotagent-node-lib/lib/services/ngsiService.js:421:18)
at Object.listDevices as handler
at executeCommander (/programs/telefonicaid/iotagent-node-lib/bin/agentConsole.js:175:30)
at Interface. (/programs/telefonicaid/iotagent-node-lib/bin/agentConsole.js:216:9)
at Interface.emit (events.js:107:17)
at Interface._onLine (readline.js:214:10)
at Interface._line (readline.js:553:8)
at Interface._ttyWrite (readline.js:830:14)
at ReadStream.onkeypress (readline.js:109:10)

Modify the callback signature to allow unstructured format

There is currently a slight difference in the NGSI Handlers between the way the information is received (split in different parameters) and the way it should be returned (grouped in an object). Callbacks should offer the option of returning the split information instead of the object. This shouldn't change the signature, as two of the attributes (id and type) are required, so the different uses can be easily distinguished (by number of parameters or type).

Device provisioning handler

Currently, the library handles the Device Provisioning in a fully automatic way, without giving the user code the opportunity to react to the provisioning. In the case of Configuration provisionings, this opportunity is given using an optional user handler for Configuration updates. A similar mechanism should be provided for Device provisioning.

GET /services

there are differences between Uri calls on both implementations of iota (C++ vs Node)

It happen in the retrieve of services info:

In iota c++ is used to be like:

/iot/services 

but in the Node iota is like:

/iot/agents/default/services 

Static attributes

Add static attributes to all the entities of the device (always the same information for the device).

Implementation of the Command features

Currently, commands implementation is not working. Commands should behave like Lazy attributes with state writing in the Context Broker, as defined in the documentation.

Different NGSI9 and NGSI10 endpoints

In order to be more flexible and enable some IoT scenarios (e.g. IoTBroker dealing with NGSI10 and IoTDiscovery dealing with NGSI9 as different components), it would be useful to have the possibility of specifiying different endpoints for NGSI9 and NGSI10 operations (by default, both endpoints will be the same).

Check duplicities in attribute names

Currently, the IoT Agent doesn't make any duplicity checks on the names that are provisioned via the Device Provisioning API or the Configuration API. The following checks should be done in each incoming creation or update:

  • In a Device provisioning, each attribute name in the active, lazy, static or command category arrays must be unique among all the categories.
  • In a Configuration provisioning, each attribute name in the active, lazy, static or command category arrays must be unique among all the categories.
  • If a Device belonging to a Configuration is provisioned, and an attribute in any of the attribute categories (e.g: lazy) is defined in that provision, its name should not be present in any other category (e.g: active or command). I.e: a lazy attribute in the device should be able to overwrite a lazy attribute in the Configuration, but should not conflict with attributes in the active, command or static categories.

Issue using agentConsole.js: "updatevalue" not working.

An error is raised when running "updatevalue" option from agent console:

IoTAgent> updatevalue l2 Light status/Boolean/false
time=2015-04-20T15:23:59.586Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.NGSIService | msg=Updating device value in the Context Broker at [http://cbroot:1026/v1/updateContext]
time=2015-04-20T15:23:59.586Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.NGSIService | msg=Using the following request:

{
"url": "http://cbroot:1026/v1/updateContext",
"method": "POST",
"json": {
"contextElements": [
{
"type": "Light",
"isPattern": "false",
"id": "l2"
}
],
"updateAction": "APPEND"
},
"headers": {
"fiware-service": "wiseacess",
"fiware-servicepath": "/garden",
"X-Auth-Token": null
}
}

time=2015-04-20T15:24:00.034Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.NGSIService | msg=Value updated successfully
/programs/telefonicaid/iotagent-node-lib/lib/services/ngsiService.js:285
callback(null, body);
^
TypeError: undefined is not a function
at Request._callback (/programs/telefonicaid/iotagent-node-lib/lib/services/ngsiService.js:285:17)
at Request.self.callback (/programs/telefonicaid/iotagent-node-lib/node_modules/request/request.js:123:22)
at Request.emit (events.js:110:17)
at Request. (/programs/telefonicaid/iotagent-node-lib/node_modules/request/request.js:1047:14)
at Request.emit (events.js:129:20)
at IncomingMessage. (/programs/telefonicaid/iotagent-node-lib/node_modules/request/request.js:998:12)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickCallback (node.js:355:11)

Deal with multiple ContextResponses

When the contextBroker returns a response that is being served by different Context Providers, there may be multiple Context Responses with different error codes. The IoT Agent should cope with the different codes.

Add pagination options for the Provisioning API

The GET operations in the Provisioning API currently return all the information in the device registry without any limits or pagination. It should at least implement the limit and offset parameters.

Should provide internal attributes for device groups

There is currently a mechanism to provide internal attributes (for its use in particular details of each protocol) for individual device provisionings, but the Configuration API lacks of the same support. It should provide the same mechanisms.

Error 500 returned trying to delete a device

Given a device already provisioned, like button_dev_async_c86f5c7b which is in mongoDb entity at devices collection:

{
    "_id" : ObjectId("560a93828d7bcd44799d899c"),
    "protocol" : "TT_BLACKBUTTON",
    "internalAttributes" : [],
    "internalId" : null,
    "registrationId" : "560a9381feb1449905efdfcf",
    "subservice" : "/telepizza_c86f5c7b",
    "service" : "blackbutton",
    "name" : "button_dev_sync_c86f5c7b",
    "type" : "BlackButton",
    "id" : "button_dev_sync_c86f5c7b",
    "creationDate" : ISODate("2015-09-29T13:34:58.050Z"),
    "staticAttributes" : [ 
        {
            "value" : "BBB",
            "name" : "ccid",
            "type" : "string"
        }, 
        {
            "value" : "2345678902",
            "name" : "imei",
            "type" : "string"
        }, 
        {
            "value" : "2987654322",
            "name" : "imsi",
            "type" : "string"
        }, 
        {
            "value" : "synchronous",
            "name" : "interaction_type",
            "type" : "string"
        }, 
        {
            "value" : "blackbutton",
            "name" : "service_id",
            "type" : "string"
        }, 
        {
            "value" : "0",
            "name" : "geolocation",
            "type" : "string"
        }
    ],
    "commands" : [],
    "active" : [ 
        {
            "name" : "internal_id",
            "type" : "string"
        }, 
        {
            "name" : "last_operation",
            "type" : "string"
        }, 
        {
            "name" : "op_status",
            "type" : "string"
        }, 
        {
            "name" : "op_result",
            "type" : "string"
        }, 
        {
            "name" : "op_action",
            "type" : "string"
        }, 
        {
            "name" : "op_extra",
            "type" : "string"
        }, 
        {
            "name" : "sleepcondition",
            "type" : "string"
        }, 
        {
            "name" : "sleeptime",
            "type" : "string"
        }
    ],
    "lazy" : [ 
        {
            "name" : "lazy_op_result",
            "type" : "string"
        }
    ],
    "__v" : 0
}

Trying to remove a device by /iot/devices DELETE method

time=2015-09-29T13:36:03.583Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.MongoDBDeviceRegister | msg=Looking for entity with id [button_dev_async_c86f5c7b].
time=2015-09-29T13:36:03.611Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.MongoDBDeviceRegister | msg=Removing device with id [button_dev_async_c86f5c7b]
time=2015-09-29T13:36:03.614Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.MongoDBDeviceRegister | msg=Entity [button_dev_async_c86f5c7b] not found for removal.
time=2015-09-29T13:36:03.614Z | lvl=DEBUG | corr=n/a | trans=n/a | op=IoTAgentNGSI.NorthboundServer | msg=Error [ENTITY_NOT_FOUND] handing request: The entity with the requested id [button_dev_async_c86f5c7b] was not found.

The entity device exists, bug if no entity device was found a 400 error would be enough.

Use logops instead of tdaf-node-logger

We are currently using a temporary version of logops that was released temporary, as a link to the Github repository. We should use the standard versions of logops instead.

Document library usage

Some important pieces of documentation are missing:

  • Library configuration.
  • How to use the trust-based security.
  • How to use de Provisioning API.

It should also be updated to the last version of the library.

Add Travis CI

Add the files needed to integrate Travis CI into the development process.

Refactor code to improve `ngsiService.js` mantainability

The file ngsiService.js is currently too big to be mantainable. It could be easily splitted in at least two files:

  • One regarding registrations and device management.
  • Another one regarding context operations.

Other tasks that could be performed to improve mantainability:

  • Create a RegistryNotAvailable wrapper for the device management functions.
  • Create a common function with the sendUpdateValue and sendQueryValue functionalities.
  • Extract sendRegistrations request handler to an external function.

Modify the Configuration API to accept multiple groups per subservice

Currently, just one Configuration is allowed per subservice (as the service/subservice pair is key in the API). The API should accept multiple configuration groups in the POST and more identification information in the PUT operations, using two parameters (apikey and resource) to complete the available interactions.

Some tests fail ocassionally

The folowing test fails ocasionally:
´´´
NOTE: ACTUAL 0, EXPECTED -1

  1. Device provisioning API: List provisioned devices When a request for listing all the devices with a offset of 3 arrives should skip the first 3 devices:
  actual expected

  -10

  at Assertion.prop.(anonymous function) (/Users/mru/github/iotagent-node-lib/node_modules/should/lib/should.js:61:14)
  at Request._callback (/Users/mru/github/iotagent-node-lib/test/unit/listProvisionedDevices-test.js:272:91)
  at Request.self.callback (/Users/mru/github/iotagent-node-lib/node_modules/request/request.js:123:22)

´´´

Publicly exposed IoT Agent configuration

The general IoT Agent configuration is currently read from a text file (the exception is the per device configuration of the device provisioning API). It should be configurable using the exposed API.

Change the Identification of the Devices.

Early in the development of the IoT Agent, the library API was thought to use the combination of deviceId and deviceType as a key to distinguish different devices. This assumption doesn't hold anymore, so it's time to change it.

Support MongoDB 3.0

The test suite seems to fail when using MongoDB 3 instead of using 2.x versions. MongoDB 3 support should be granted to the library (but we should move along with Orion's Mongo version in this case).

Synchronization with CPP Framework: Device provisioning

Some changes have been accomplished in the device provisioning API:

  • The root document must include a single "devices" attribute with an array of devices, instead of a single device per document.
  • The "protocol" attribute must be included with each device provisioning in order to identify the kind of protocol in use for the device.
  • The "name" attribute must be changed to "deviceId".

String quotes not supported in tester

IoTAgentTester doesn't currently support string quotes in the command line, so information about an entity or attribute with an space in the name cannot be retrieved.

Protect Device Group operations from the modification from accesses from other services

With the change from the (service, subservice) key to the (resource, apikey) key, the opportunity arrives for an operation targeting a group in a specific service to be executed by an actor identified in another service (as the only validated piece of data are the fiware-service and fiware-servicepath headers). An error should be raised for those cases when there is a mismatch between the stored service and the one declared in the headers.

Problem with running tests related to project

I'm having issues with running tests for the IoT agent library. Here's error description from mocha:

1) MongoDB Group Registry test When a new device group creation request arrives should store the service information from the headers into the DB:
 Uncaught TypeError: Cannot read property 'length' of undefined
  at processResults (/home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1581:31)
  at /home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1619:20
  at /home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1157:7
  at b (domain.js:183:18)
  at /home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1890:9
  at b (domain.js:183:18)
  at Server.Base._callHandler (/home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:448:41)
  at /home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:481:18
  at MongoReply.parseBody (/home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:68:5)
  at null.<anonymous> (/home/marcin/iotagent-node-lib/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:439:20)

My environment is:

  • clean install of Ubuntu Server 14.04
  • fresh clone of repository
  • Mongo 3.0.4 installed as specified here
  • node 0.10.25 and npm 1.3.10
  • npm install and npm install -g grunt
  • grunt test

The problem is also occuring on Windows.

What might be the problem?

Context Provider Server doesn't answer correctly to global queries

When a query arrives to the Context Provider Server of the IOT Agent, if the query does not specify any attributes, the Agent answers with a response without attribtes. This behavior doesn't comply with the standard: empty queries should return all the attributes in the entity.

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.