Code Monkey home page Code Monkey logo

cumulocity-devicemanagement-agent's Introduction

Deprecation notice

thin-edge.io made huge progress and supports much more functionality as the dm-agent. Please consider using thin-edge.io instead using the dm-agent! It will be no longer maintained.

Cumulocity IoT Device Management Reference Agent

Cumulocity Device Management (DM) Reference Agent written in Python3 to demonstrate most of the Device Management Capabilities of Cumulocity IoT

Quick Start

The agent can be run in a docker container or natively on a device with preferrable with linux OS (e.g raspberry pi) or any other operating system.

The docker version is mainly used to simulate a device including a SSH + VNC server for Remote Access. It can be also used to simulatoe multiple instances of the agent & devices.

The native version is mainly used when connecting physical devices with real capabilities and sensors attached.

Here is a detailed "How-To" guide which provides a step-by-step instruction to get the agent running & to explore the device management features of Cumulocity IoT

Docker Quick Start

To quickly run the agent you can use the prebuild docker image.

docker run switschel/c8ydm

If you want docker management included just mount the docker sock to the container by adding

-v /var/run/docker.sock:/var/run/docker.sock

Per default Bootstrapping is used and no other information is necessary. In this case the docker container Id is the device Id which should be entered when registering a device in cumulocity.

You can find it out with:

docker ps

If you don't want to use the prebuild image for any reason (e.g. you want to make changes to the docker image) just clone the repo and in a linux shell of your choice use:

chmod +x start.sh & ./start.sh

In windows shells like PS or CMD run:

start.bat

The script will build a docker image and starting one instance afterwards.

If you want to run the Agent without using docker you need to build and run the Agent manually.

Docker Quick Start Configuration

To configure the agent you can mount the agent.ini to the docker image by using in the docker run command:

-v {{path-to-your-local-agent.ini}}:/root/.cumulocity/agent.ini

As an alternative you can use environment variables to overwrite the default values in the agent.ini within the image. Here is an example to change the loglevel:

-e C8YDM_AGENT_LOGLEVEL=DEBUG

For the local docker build you can use three options to change the docker image:

  1. Enable/Disable VNC as part of the docker image (1 per default)

    INSTALL_VNC=1 ./start.sh
    
  2. By default, the docker container runs in background. If you want to run it interactively:

    INTERACTIVE=1 ./start.sh
    
  3. Generate self-signed certificates and using certificate based authentication

    USE_CERTS=1 ./start.sh
    

Quick Start with Device Certificates in docker container

The agent contains scripts for generating & uploading self-signed certificates. To use them additional informatiopn as environment variables are needed. You can use the use_certs.env file as a template to provide them or add the environment variables using "-e" command of docker.

Here is an example env file to generate a self signed certificate and upload it to a target tenant:

C8YDM_MQTT_CERT_AUTH=true
C8YDM_MQTT_URL=mqtt.eu-latest.cumulocity.com
CERT_TENANT=<tenantID>
CERT_USER=<username>
CERT_PASSWORD=<password>

The easiest way is to use the --env-file comand of docker to load it. Example:

docker run --env-file use_certs.env switschel/c8ydm

or for the local image run after maintaining the use_certs.env:

USE_CERTS=1 ./start.sh

If you have your own certificates already you have multiple options to use them. In all cases you must make use of the "device.id" property in the agent.ini to set a device ID you used to generate the certificates.

Option 1: Mount the whole config folder to the docker container containing the certs and adapted agent.ini. Make sure that you set the properties accordingly and that the cert paths are pointing to the location /root/.cumulocity/certs:

-v {{path-to-your-local-config-folder}}:/root/.cumulocity/

Option 2: Mount the certificates and adapt the config using environment variables

Option 3: Before building the docker image locally copy the certs to "config/certs" folder, change the agent.ini, build the image and run the image.

Native

To quickly run the agent natively make sure pyhton 3.7+ and pip is installed on your computer. Manually put the config file and the DM_Agent.json into the /.cumulocity folder in your user folder. For example: "/home/user1/.cumulocity" in Linux or "C:\Users\user1\.cumulocity" in Windows.

Installation

pip install c8ydm

To start the agent run

c8ydm.start

and to stop run

c8ydm.stop

Features

Supported Cumulocity DM Features

Feature Supported
Device Certificates Yes
Device Bootstrapping & Registration Yes
Software Updates (apt) Yes
Firmware Updates (simulated only) Yes
Configuration Updates text-based Yes
Configuration file-based Yes
Device Profiles Yes
Network Yes
Device Metrics (CPU, Memory etc.) Yes
Remote Logfile Requests Yes
Location Updates Yes
Remote Shell Yes
Remote Access (SSH, VNC, Passthrough) Yes
Hardware Metering (CPU, Memory, HDD) Yes
Raspberry PI SenseHAT Yes
Docker Management Yes
Connectivity (Mobile) Management No
Adv. Software Management Yes (apt)
Service Management Yes (docker)

Raspberry PI & SenseHAT Support

The DM Agent can run on a Raspberry PI (3+) with a SenseHAT. It supports:

  • Reading out all sensor values of the SenseHAT (Humidity, Temperature, Acceleration, Gyroscope, Compass)
  • Display Messages on the LEDs sent via Cumulocity to the Pi (c8y_Message) via Message Widget.
  • Generates Events when the Joystick is pressed in different direction.

It is suggested to run the Agent as a Service. Use dm-agent.service to install it:

sudo cp ./service/dm-agent.service /etc/systemd/system/
sudo systemctl enable dm-agent.service
sudo systemctl daemon-reload
sudo service dm-agent start 

Configuration

The agent can be configured via the agent.ini which must be placed in

{userFolder}/.cumulocity

When running in docker container the agent.ini can be mounted to the /root/.cumulocity/agent.ini

You can find a reference agent.ini here

Category Property Description
mqtt url The URL of the Cumulocity MQTT endpoint
mqtt port The Port of the Cumulocity MQTT endpoint
mqtt tls True when using port 8883, false when using port 1883
mqtt cert_auth true when you want use Device Certificates for Device Authentication
mqtt client_cert Path to your cert which should be used to for Authentication
mqtt client_key Path to your private key for Authentication
mqtt ping.interval.seconds Interval in seconds for the mqtt client to send pings to MQTT Broker to keep the connection alive.
agent name The prefix name of the Device in Cumulocity. The serial will be attached with a "-" e.g. dm-example-device-1234567.
agent type The Device Type in Cumulocity
agent main.loop.interval.seconds The interval in seconds sensor data will be forwarded to Cumulocity
agent requiredinterval The interval in minutes for Cumulocity to detect that the device is online/offline.
agent loglevel The log level to write and print to file/console.

Environment variables

The environment variables with "C8YDM_" prefix are mapped to configuration files. Mapping rules:

  • Prefix C8YDM_{{CATEGORY}} means what category the option belongs to
  • Upper case letters are mapped to lower case letters
  • Double underscore __ is mapped to .

Examples:

C8YDM_MQTT_CERT_AUTH => mqtt.cert_auth 
C8YDM_AGENT_MAIN__LOOP__INTERVAL__SECONDS => agent.main.loop.interval.seconds

Build

The agent can be build in multiple ways.

Building via pip

To build the agent via pip just run (as a root user, otherwise add "sudo" prior all commands).

pip3 install -r requirements.txt

to install dependencies and afterwards

pip3 install .

to build the agent itself. Please note that in debian/ubuntu you need additionally install

apt install python-apt

via apt.

Continue with chapter Run

Building debian package

In order to build the .deb yourself first install python-stdeb via apt.

apt install python3-stdeb

Afterwards run

python3 setup.py --command-packages=stdeb.command bdist_deb

on the level of the setup.py.

In oder to install the debian package locally run

apt install ./deb_dist/python-c8ydm_0.1-1_all.deb

Continue with chapter Run

Building docker image

To build a docker image you can make use of the provided Dockerfile.

Example:

docker build -t dm-image -f docker/Dockerfile .

Run

Python

Before running the agent some manual steps need to be taken

  1. Manually put the config file into {userfolder}/.cumulocity

You can run the agent by executing (as root, otherwise add "sudo")

c8ydm.start

in your console when you used Building via pip to build and install the agent.

apt / debian package

Before running the agent some manual steps need to be taken

  1. Manually put the config file and the DM_Agent.json into ~/.cumulocity folder. ~ stands for the current user folder. The SmartRESTTemplate will be automatically uploaded on first start.

  2. You have to install "c8y-device-proxy" via pip or using the provided deb file of the C8Y Device Proxy

You can run the agent by executing (as root, otherwise add "sudo")

c8ydm.start

in your console when you used Building via deb to build and install the agent.

Docker

You can run the agent by executing

docker run -d -v /var/run/docker.sock:/var/run/docker.sock dm-image

in your console when you used Building Docker Image to build and install the agent.

The config can be mounted the container. Otherwise the default config will be used. See Docker Quick Start Configuration

Mass deployment

You can run multiple instances of an container via:

. mass_start.sh 5

where in this example 5 is the number of agent instances.

Develop

Dev Container

The project comes with VS Code devcontainer support. Make sure you use Visual Studio Code in combination with docker. Just open the project in VS Code and click on "Reopen in Dev Container".

In the background the Agent will be build and started. Also a debug/run configuration is provided so you can easilly start/debug the agent within VS Code.

Using certificate authentication

You can generate certificates necessary to certficate authentication, and you can upload the generated root certificate to your tenant's trusted certificate list by executing the scripts like below:

./scripts/generate_cert.sh \
--serial pyagent0001 \
--root-name iot-ca \
--cert-dir /root/.cumulocity/certs \
--cert-name device-cert

./scripts/upload_cert.sh \
--tenant-domain <tenant domain> \
--tenant-id <tenant ID> \
--username <username for the tenant> \
--password <password for the tenant> \
--cert-path /root/.cumulocity/certs/iot-ca.pem \
--cert-name <(arbitrary) displayed name of the root certificate>

After this, you can connect the agent to your tenant using cert authentication (with the serial pyagent0001 in this case).

Testing

With the agent running in the container, execute

./test.sh \
--url <tenant domain> \
--tenant <tenant ID> \
--username <username for the tenant> \
--password <password for the tenant>

to run pytest.

Extending the agent

The agent knows three types of classes that it will automatically load and include from the "agentmodules" directory.

  1. Sensors

     class Sensor:
       __metaclass__ = ABCMeta
    
       def __init__(self, serial):
         self.serial = serial
    
       '''
       Returns a list of SmartREST messages. Will be called every iteration of the main loop.
       '''
       @abstractmethod
       def getSensorMessages(self): pass
    

    Sensors are periodically polled by the main loop and published.

  2. Listeners

     class Listener:
       __metaclass__ = ABCMeta
    
       def __init__(self, serial, agent):
         self.serial = serial
         self.agent = agent
    
       '''
       Callback that is executed for any operation received
       '''
       @abstractmethod
       def handleOperation(self, message): pass
    
       '''
       Returns a list of supported operations
       '''
       @abstractmethod
       def getSupportedOperations(self): pass
    
       '''
       Returns a list of supported SmartREST templates (X-Ids)
       '''
       @abstractmethod
       def getSupportedTemplates(self): pass
    

    Listeners are called whenever there is a message received on a subscribed topic.

  3. Initializers

     class Initializer:
       __metaclass__ = ABCMeta
    
       def __init__(self, serial):
         self.serial = serial
    
       '''
       Returns a list of SmartREST messages. Will be called at the start of the agent
       '''
       @abstractmethod
       def getMessages(self): pass
    

    Initializers are only called once at the start of the agent.

You can take a look at the two example modules for how it can be used.

Log & Configuration

The logfile and configuration can be found in the following directory.

/root/.cumulocity

These tools are provided as-is and without warranty or support. They do not constitute part of the Software AG product suite. Users are free to use, fork and modify them, subject to the license agreement. While Software AG welcomes contributions, we cannot guarantee to include every contribution in the master project.

For more information you can Ask a Question in the Tech Community Forums.

cumulocity-devicemanagement-agent's People

Contributors

6293 avatar dependabot[bot] avatar elielias avatar mbay-odw avatar pandora2000 avatar switschel avatar techcommunity 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cumulocity-devicemanagement-agent's Issues

agent software update module needs to handle files in the Cumulocity software repo

Some customers want to be able to host library and deb package upgrades in Cumulocity directly, including new versions of the agent, and install from there. Currently it doesn't provide any mechanism to do so, and specifying the file or path in Cumulocity is ignored by the agent in lieu of the devices own source list. This is a critical need for at least 2 customers at the moment.

Docker build not working with Apple M1

Building with ARM based Machine such as Apple´s M1 does exit with an error due to an repo error in Line 43.

Changing add-apt-repo to arch=arm64 solves the problem.

External Id configuration missing

In the docs:

"The script will build a docker image and starting one instance afterwards. Per default Bootstrapping is used and no other information is necessary. In this case the docker container Id is the device Id which should be entered when registering a device in cumulocity."

But how can the device id (external_id) be configured when running outside of docker?

Add apt-utils to the list of packages

Hi,

could you please add package apt-utils to the initially installed packages?
This is required if you want to install nano in a demo.

Regards Christof

RUN apt-get update \
    && apt-get -y full-upgrade \
    && apt-get -y install \
    python3 \
    python3-apt \
    python3-pip \
    openssh-server \
    curl \
    gpg \
    lsb-release \
    apt-utils

Debug configuration error

If I try to debug the agent I get the following warnings and errors:

cd /home/mstoffel/cumulocity-devicemanagement-agent ; /usr/bin/env /bin/python3 /home/mstoffel/.vscode-server/extensions/ms-python.python-2021.12.1559732655/pythonFiles/lib/python/debugpy/launcher 41065 -- -m c8ydm.main
/usr/lib/python3.8/runpy.py:127: RuntimeWarning: 'c8ydm.main' found in sys.modules after import of package 'c8ydm', but prior to execution of 'c8ydm.main'; this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
2021-12-15 08:09:05,785 MainThread INFO root Output of SystemUtils Serial: 91764171647
Starting...
2021-12-15 08:09:05,789 MainThread INFO root Found pid file with pid 14816
2021-12-15 08:09:05,791 MainThread INFO root Checking if pid 14816 is existing...
2021-12-15 08:09:05,793 MainThread INFO root Pid 14816 does not exist
2021-12-15 08:09:05,794 MainThread INFO root Removing PID File /home/mstoffel/.cumulocity/agent.pid
2021-12-15 08:09:05,796 MainThread INFO root Serial: 91764171647
2021-12-15 08:09:05,799 MainThread INFO root No credentials found. Starting bootstrap mode.
2021-12-15 08:09:05,800 MainThread ERROR root No bootstrap credentials found. Stopping agent.

Add hint to set appropriate End of Line Sequence

When I try to run the agent on a Windows machine, I recognized VSCODE did not use the right EOL Sequence for running the *.sh scripts. So maybe put one hint in the documentation to set "LF" or "\n" for End of Line Sequence in Windows environments. "Auto" detection did not work for me (at least not in VSCODE).

Create DM Storyline

Brainstorm about the Device Management with Cumulocity Storyline can look like.
Use existing presentation to get an orientation and build a structure

Required Interval is not updated on Cloud Device

I've tried to change the required interval via Cumulocity Text-baed Configuration, which did succeed only partly. Reproduction steps:

  1. Start and register agent in Cumulocity with default configuration
  2. Go to the Device in Devicemanagement, note that the required Interval in Info Tab is 10
  3. Go to Configuration > Text-Based Configuration and change field agent.requiredinterval to e.g. 13. Send the configuration to the device
  4. Agent picks it up, operation succeeds
  5. With "getting snapshot from device" you can see the new configuration got transferred to Cumulocity
  6. However: The required interval in Info Tab is still at 10 Minutes, also the ManagedObject field is at 10 Minutes => seems the update of the MO has an issue.

The logs dont tell anything specific, this is the received operation:

2022-07-21 18:50:21,977 ListenerThread-ConfigurationManager INFO c8ydm.core.configuration Configuration Operation received: ['3bc64215ffe1', '"mqtt.url=mqtt.eu-latest.cumulocity.com\nmqtt.port=8883\nmqtt.tls=true\nmqtt.cert_auth=false\nmqtt.client_cert=/root/.cumulocity/certs/chain-cert.pem\nmqtt.client_key=/root/.cumulocity/certs/device-cert-private-key.pem\nmqtt.cacert=/etc/ssl/certs/ca-certificates.crt\nmqtt.ping.interval.seconds=60\nagent.name=dm-example-device\nagent.type=c8y_dm_example_device\nagent.main.loop.interval.seconds=10\nagent.requiredinterval=13\nagent.loglevel=INFO"']

SensorThread-DockerSensor ERROR is spamming Log Files

Using DM agent, we can register devices, however we noticed the following error in the logs, it is spamming log files.

2022-07-08 04:44:10,477 InitializerThread-DockerSensor WARNING c8ydm.client.rest_client Managed object not updated in C8Y
2022-07-08 04:44:10,793 SensorThread-DockerSensor ERROR c8ydm.core.docker_watcher The following error occured: [Errno 2] No such file or directory: 'docker'
2022-07-08 04:44:11,877 SensorThread-DockerSensor WARNING c8ydm.client.rest_client Managed object not updated in C8Y
2022-07-08 04:44:20,814 SensorThread-DockerSensor ERROR c8ydm.core.docker_watcher The following error occured: [Errno 2] No such file or directory: 'docker'
2022-07-08 04:44:21,957 SensorThread-DockerSensor WARNING c8ydm.client.rest_client Managed object not updated in C8Y
2022-07-08 04:44:30,830 SensorThread-DockerSensor ERROR c8ydm.core.docker_watcher The following error occured: [Errno 2] No such file or directory: 'docker'

Move debian from 10 -> 11

With Debian 11 Python 3.9.2 is the desired Python version.
There might be some issues with this version as for example platform.linux_distribution was removed and leads to errors in network module and other modules using pyspectator.

Build and run instructions issues

I would like to run the agent in wsl2 Ubuntu.
When I follow the description in the build step:

  • Build
    pip install -r requirements.txt
    pip install .

apt install python-apt -> this cannot be run without sudo

  • Run
    sudo c8ydm.start -> will not be found since the agent was installed without sudo

When I do all the steps using sudo or not and execute c8ydm.start I always get:

from c8ydm.core.apt_package_manager import AptPackageManager
File "/home/mstoffel/.local/lib/python3.8/site-packages/c8ydm/core/apt_package_manager.py", line 23, in
if 'Linux' == platform.system() and ('debian' == platform.linux_distribution()[0] or 'ubuntu' == platform.linux_distribution()[0]):
AttributeError: module 'platform' has no attribute 'linux_distribution'

Agent fails with 'NoneType' object is not subscriptable on RPI

When running the dm agent outside of docker

c8ydm.start

on RPi IV using the settings:

tls = true
cert_auth = true

I get the following exception:

Starting...
2022-07-17 19:10:34,411 MainThread INFO root Serial: 3332221
2022-07-17 19:10:34,412 MainThread INFO c8ydm.client.mqtt_agent Starting agent
2022-07-17 19:10:34,706 Thread-1 INFO c8ydm.client.mqtt_agent Agent connected with result code: 0
2022-07-17 19:10:35,662 MainThread INFO c8ydm.client.mqtt_agent Starting refresh token thread 
2022-07-17 19:10:35,664 TokenThread-1 INFO c8ydm.client.mqtt_agent Refreshing Token
SenseHat not available
2022-07-17 19:10:35,991 InitializerThread-AgentInitializer INFO c8ydm.agentmodules.agent_initializer Agent Initializer called...
2022-07-17 19:10:35,994 InitializerThread-DockerSensor INFO c8ydm.agentmodules.docker_watcher Docker Initializer called...
2022-07-17 19:10:36,001 InitializerThread-EventInitializer INFO c8ydm.agentmodules.event_initializer Event Initializer called...
2022-07-17 19:10:36,008 InitializerThread-Network INFO c8ydm.agentmodules.network Network Initializer called...
2022-07-17 19:10:36,032 InitializerThread-SmartRestInitializer INFO c8ydm.agentmodules.smartRest_inizializer SmartRest Template Initializer called...
2022-07-17 19:10:36,035 InitializerThread-SmartRestInitializer ERROR c8ydm.client.rest_client The following error occured while trying to check for existing SmartRest templates: 'NoneType' object is not subscriptable
2022-07-17 19:10:36,036 InitializerThread-SmartRestInitializer INFO c8ydm.agentmodules.smartRest_inizializer SmartRest Template does not exist, creating....
2022-07-17 19:10:36,036 InitializerThread-SmartRestInitializer ERROR c8ydm.client.rest_client The following error occured while trying to create SmartRest template: 'NoneType' object is not subscriptable
2022-07-17 19:10:36,071 MainThread INFO c8ydm.client.mqtt_agent Supported operations:
2022-07-17 19:10:36,240 MainThread INFO c8ydm.client.mqtt_agent {'c8y_DeviceProfile', 'c8y_MeasurementRequestOperation', 'c8y_RemoteAccessConnect', 'c8y_DownloadConfigFile', 'c8y_SoftwareList', 'c8y_Restart', 'c8y_UploadConfigFile', 'c8y_Firmware', 'c8y_Message', 'c8y_Command', 'c8y_SoftwareUpdate', 'c8y_Configuration', 'c8y_LogfileRequest', 'c8y_Docker'}
2022-07-17 19:10:36,807 MainThread INFO c8ydm.client.mqtt_agent Required interval: 10
2022-07-17 19:10:36,813 MainThread INFO c8ydm.client.mqtt_agent Model:
2022-07-17 19:10:36,814 MainThread INFO c8ydm.client.mqtt_agent ['3332221', 'raspberry', '1.0']
2022-07-17 19:10:36,950 MainThread INFO c8ydm.client.mqtt_agent Subscribing to XID: c8y-dm-agent-v1.0
2022-07-17 19:10:37,480 InitializerThread-DockerSensor ERROR c8ydm.client.rest_client The following error occured: 'NoneType' object is not subscriptable
2022-07-17 19:10:37,481 MainThread ERROR c8ydm.client.rest_client The following error occured: 'NoneType' object is not subscriptable
2022-07-17 19:10:37,548 InitializerThread-DockerSensor ERROR c8ydm.client.rest_client The following error occured: 'NoneType' object is not subscriptable
2022-07-17 19:10:37,582 MainThread ERROR c8ydm.client.rest_client The following error occured: 'NoneType' object is not subscriptable
--- Logging error ---
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.7/site-packages/c8ydm/client/mqtt_agent.py", line 100, in run
    self.__init_agent()
  File "/home/pi/.local/lib/python3.7/site-packages/c8ydm/client/mqtt_agent.py", line 283, in __init_agent
    self.rest_client.set_operations_to_failed(ops)
  File "/home/pi/.local/lib/python3.7/site-packages/c8ydm/client/rest_client.py", line 320, in set_operations_to_failed
    if len(operations) > 0:
TypeError: object of type 'NoneType' has no len()

The registration in the cloud works, but it seems that no measurements are send to the could tenant.
What could be the issue?

dangling `EXECUTING` operation

When an agent has accidentally shut down after it sent 501 (EXECUTING) and before 502 / 503 (failure / success), it creates a "dangling" operation in the Cumulocity server. After the device restart, this operation is not visible from the agent since the cloud will not resend EXECUTING operations (they only resend PENDING operations).

Besides, if you create a new operation which has the same fragment as the dangling operation, the dangling operation would be set to SUCCESS and the new one is set to EXECUTING. This is because the agent would successfully send 501 then 503 to the cloud, but 503 will only update the old operation while 501 is applied to the new one.

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.