Giter Site home page Giter Site logo

rust-embedded / gpio-cdev Goto Github PK

View Code? Open in Web Editor NEW
205.0 21.0 34.0 152 KB

Rust interface to the Linux GPIO Character Device API (/dev/gpiochip...)

License: Apache License 2.0

Rust 100.00%
rust linux gpio-character-device gpio-cdev gpio embedded

gpio-cdev's Introduction

gpio-cdev

Build Status Version License

rust-gpio-cdev is a Rust library/crate providing access to GPIO character device ABI. This API, stabilized with Linux v4.4, deprecates the legacy sysfs interface to GPIOs that is planned to be removed from the upstream kernel after year 2020 (which is coming up quickly).

Use of this API is encouraged over the sysfs API used by this crate's predecessor sysfs_gpio if you don't need to target older kernels. For more information on differences see Sysfs GPIO vs GPIO Character Device.

Installation

Add the following to your Cargo.toml

[dependencies]
gpio-cdev = "0.4"

Note that the following features are available:

  • async-tokio: Adds a Stream interface for consuming GPIO events in async code within a tokio runtime.

Examples

There are several additional examples available in the examples directory.

Read State

use gpio_cdev::{Chip, LineRequestFlags};

// Read the state of GPIO4 on a raspberry pi.  /dev/gpiochip0
// maps to the driver for the SoC (builtin) GPIO controller.
let mut chip = Chip::new("/dev/gpiochip0")?;
let handle = chip
    .get_line(4)?
    .request(LineRequestFlags::INPUT, 0, "read-input")?;
for _ in 1..4 {
    println!("Value: {:?}", handle.get_value()?);
}

Mirror State (Read/Write)

use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags, EventType};

// Lines are offset within gpiochip0; see docs for more info on chips/lines
//
// This function will synchronously follow the state of one line
// on gpiochip0 and mirror its state on another line.  With this you
// could, for instance, control the state of an LED with a button
// if hooked up to the right pins on a raspberry pi.
fn mirror_gpio(inputline: u32, outputline: u32) -> Result<(), gpio_cdev::Error> {
    let mut chip = Chip::new("/dev/gpiochip0")?;
    let input = chip.get_line(inputline)?;
    let output = chip.get_line(outputline)?;
    let output_handle = output.request(LineRequestFlags::OUTPUT, 0, "mirror-gpio")?;
    for event in input.events(
        LineRequestFlags::INPUT,
        EventRequestFlags::BOTH_EDGES,
        "mirror-gpio",
    )? {
        let evt = event?;
        println!("{:?}", evt);
        match evt.event_type() {
            EventType::RisingEdge => {
                output_handle.set_value(1)?;
            }
            EventType::FallingEdge => {
                output_handle.set_value(0)?;
            }
        }
    }

    Ok(())
}

Async Usage

Note that this requires the addition of the async-tokio feature.

use futures::stream::StreamExt;
use gpio_cdev::{Chip, AsyncLineEventHandle};

async fn gpiomon(chip: String, line: u32) -> gpio_cdev::Result<()> {
    let mut chip = Chip::new(args.chip)?;
    let line = chip.get_line(args.line)?;
    let mut events = AsyncLineEventHandle::new(line.events(
        LineRequestFlags::INPUT,
        EventRequestFlags::BOTH_EDGES,
        "gpioevents",
    )?)?;

    while let Some(event) = events.next().await {
        let event = event?;
        println!("GPIO Event: {:?}", event);
    }

    Ok(())
}

Sysfs GPIO vs GPIO Character Device

Compared to the sysfs gpio interface (as made available by the sysfs_gpio crate) the character device has several advantages and critical design differences (some of which are driving the deprecation in the kernel).

Since many people are familiar with the sysfs interface (which is easily accessible via basic commands in the shell) and few people are familiar with the GPIO character device, an exploration of the two and key differences here may prove useful.

Getting Access to a GPIO

In the Linux kernel, individual GPIOs are exposed via drivers that on probe register themselves as GPIO chips with the gpio subsystem. Each of these chips provides access to a set of GPIOs. At present, when this chip is registered a global base number is assigned to this driver. The comments from the linux kernel gpio_chip_add_data sum up the situation nicely when assigning the a base number to a GPIO chip on registration.

/*
 * TODO: this allocates a Linux GPIO number base in the global
 * GPIO numberspace for this chip. In the long run we want to
 * get *rid* of this numberspace and use only descriptors, but
 * it may be a pipe dream. It will not happen before we get rid
 * of the sysfs interface anyways.
 */

The entire sysfs interface to GPIO is based around offsets from the base number assigned to a GPIO chip. The base number is completely dependent on the order in which the chip was registered with the subsystem and the number of GPIOs that each of the previous chips registered. The only reason this is usable at all is that most GPIOs are accessed via SoC hardware that is registered consistently during boot. It's not great; in fact, it's not even good.

The GPIO character device ABI provides access to GPIOs owned by a GPIO chip via a bus device, /sys/bus/gpiochipN (or /dev/gpiochipN). Within a chip, the programmer will still need to know some details about how to access the GPIO but things are generally sane. Figuring out which bus device is the desired GPIO chip can be done by iterating over all that are present and/or setting up appropriate udev rules. One good example of this is the lsgpio utility in the kernel source.

In sysfs each GPIO within a chip would be exported and used individually. The GPIO character device allows for one or more GPIOs (referenced via offsets) to be read, written, configured, and monitored via a "linehandle" fd that is created dynamically on request.

"Exporting" a GPIO

Using the sysfs API, one would write the global GPIO number to the "export" file to perform further operations using new files on the filesystem. Using the gpiochip character device, a handle for performing operations on one or more GPIO offsets within a chip are available via a "linehandle" fd created using the GPIO_GET_LINEHANDLE_IOCTL. A consequence of this is that a line will remember its state only for as long as the fd is open; the line's state will be reset once the fd is closed.

When a linehandle is requested, additional information is also included about how the individual GPIOs will be used (input, output, as-is, active-low, open drain, open source, etc). Multiple lines can be grouped together in a single request but they must all be configured the same way if being used in that way. See struct gpioevent_request.

Reading/Writing GPIOs

Via sysfs, GPIOs could be read/written using the value file. For GPIO character devices, the GPIOHANDLE_GET_LINE_VALUES_IOCTL and GPIOHANDLE_SET_LINE_VALUES_IOCTL may be used to get/set the state of one or more offsets within the chip.

Input Events

Via sysfs, one could setup things up using the trigger file to notify userspace (by polling on the value file) of a single event based on how things were setup. With GPIO character devices, one can setup a gpio_eventrequest that will create a new anonymous file (fd provided) for event notifications on a lines within a gpiochip. Contrary to sysfs gpio events, the event file will queue multiple events and include with the event (best effort) nanosecond-precision timing and an identifier with event type.

With this information one could more reasonably consider interpreting a basic digital signal from userspace (with rising and falling edges) from userspace using the queueing with timing information captured in the kernel. Previously, one would need to quickly handle the event notification, make another system call to the value file to see the state, etc. which had far too many variables involved to be considered reliable.

Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on stable Rust 1.65.0 and up. It might compile with older versions but that may change in any new patch release.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Code of Conduct

Contribution to this crate is organized under the terms of the Rust Code of Conduct, the maintainer of this crate, the Embedded Linux Team, promises to intervene to uphold that code of conduct.

gpio-cdev's People

Contributors

bors[bot] avatar dfrankland avatar dirreke avatar drinkdhmo avatar eldruin avatar fpagliughi avatar hellow554 avatar jodal avatar kmdouglass avatar lovasoa avatar m-ou-se avatar mgottschlag avatar mpi3d avatar oll3 avatar posborne avatar stefan-muc 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  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

gpio-cdev's Issues

Error: "There is no reactor running, must be called from the context of a Tokio 0.2.x runtime" from tokio::task::spawn

I am fairly new to Rust, and particularly to embedded Rust, so there's a good chance this is just my mistake.
I am running the following:

#[tokio::main]
async fn main() {
    let mut chip = Chip::new("/dev/gpiochip0").unwrap();
    let line = chip.get_line(10).unwrap();
    let _trigger_task = tokio::task::spawn(async move {
        let mut events = AsyncLineEventHandle::new(line.events(LineRequestFlags::INPUT, EventRequestFlags::FALLING_EDGE, "input_trigger").unwrap()).unwrap();
        while let Some(_event) = events.next().await {
            println!("Trigger");
        }
    }
    );
}

And it panics with:
There is no reactor running, must be called from the context of a Tokio 0.2.x runtime.

I replaced the code inside the closure with a simple print statement, and it worked fine suggesting the issue is in the GPIO stuff.
Am I just being stupid? I would really appreciate any help!

No /dev/gpiochip0 on Artik 530s

I want to use this driver to see if it is more performant than sysfs. The one problem I'm running into is there is no gpiochip0 listed under /dev.

How do I add gpiochip0 to /dev so I can start using this library?

Thanks so much for all your hard work and help! Go rust!

How to do monitor for an event on more than one input?

How do you extend the gpioevents.rs example to handle multiple input pins using event handlers?

EDIT: To clarify, I don't really want to have to follow the poll methodology in monitor.rs as polling IO pins is inefficient and prone to missing changes in IO state.

-Andy.

Replace error-chain with std::error::Error

Impossible to use anyhow with gpio-cdev:

error[E0277]: `(dyn std::error::Error + std::marker::Send + 'static)` cannot be shared between threads safely
 --> src/main.rs:9:52
  |
9 |     let mut chip = gpio_cdev::Chip::new(RELAY_CHIP)?;
  |                                                    ^ `(dyn std::error::Error + std::marker::Send + 'static)` cannot be shared between threads safely
  |
  = help: the trait `std::marker::Sync` is not implemented for `(dyn std::error::Error + std::marker::Send + 'static)`
  = note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<(dyn std::error::Error + std::marker::Send + 'static)>`
  = note: required because it appears within the type `std::boxed::Box<(dyn std::error::Error + std::marker::Send + 'static)>`
  = note: required because it appears within the type `std::option::Option<std::boxed::Box<(dyn std::error::Error + std::marker::Send + 'static)>>`
  = note: required because it appears within the type `error_chain::State`
  = note: required because it appears within the type `gpio_cdev::errors::Error`
  = note: required because of the requirements on the impl of `std::convert::From<gpio_cdev::errors::Error>` for `anyhow::Error`
  = note: required by `std::convert::From::from`

Control the mode or peripheral function aka alternate mode of GPIO PINs.

Is it possible to use gpio-cdev to change the peripheral function of a GPIO PIN?

For context, I'm using gpio-cdev on a Raspberry PI to turn GPIO PIN 2 into output mode and send a wake pulse to a chip connected via I2C. Unfortunately, calling chip.get_line(2).request(LineRequestFlags::OUTPUT, 1, "test") changes the mode of the GPIO PIN to output and I have not yet found a way of "resetting" the PIN to its original state after completing the wake pulse.

I found, however, I can reset it manually from the command line using sudo raspi-gpio set 2 a0 but I was wondering if the same could be accomplished from within gpio-cdev or if it could be extended to allow for that.

Version 0.3.0

Is there anything that needs doing before bumping the version?

Lines.request fail at runtime

I've tried to run readall.rs example on orange pi zero, but app panicked with the message "panicked at 'index out of bounds: the len is 64 but the index is 64', .\gpio-cdev-0.2.0\src\lib.rs:766:13"
The problem is that Lines struct in my case contains more than 64(GPIOHANDLES_MAX) lines.

possible to target wasm32-wasi?

I think it would be amazing to be able to compile to WASM + WASI, so that we can compile code, that has only access to given devices. In my understanding, it should be possible, since /dev/gpiochipX are handled as files** and could be preopened via fs over WASI. I'm a total noob though, so I don't know if that's even possible.

Following, some interesing infos I've found:

  • I've seen examples from wasm3 runtime that use WASI with Arduinos by passing the needed functions into WASM.
  • WASI has no official support for hardware peripheral abstractions as derived from this WASI issue.
  • In this tutorial by wasmtime, they demonstrate how files or dirs can be passed into the WASM sandbox env

Currently building to target wasm32-wasi results in several errors.

** on Linux

Servo example?

Want to say thanks to the team behind this library, but as a newbie to embedded programming how do i use this to control a servo?

New release?

It has been 2 years since it released last time. Could we make a new release?

gpio-cdev = { version = "0.3", features = ["async-tokio"] } fails

Create a new project foo with gpio-cdev as a dependency with the async-tokio feature.

[dependencies]
gpio-cdev = { version = "0.3", features = ["async-tokio"] }

This gives us an error

... the package `foo` depends on `gpio-cdev`, with features: `async-tokio` but `gpio-cdev` does not have these features.

Example for async multiread

Could an example be shown for doing an async multiread? I don't see a lot of things i can do with get_lines such as events() and events_async()

Error using blocking iterator

Hi, I've successfully used the streaming interface using the request method:

fn do_main(args: Cli) -> errors::Result<()> {
    let mut chip = Chip::new("/dev/gpiochip7")?;
    let lines = vec![0, 1, 2, 3, 4, 5];
    let initial_values = vec![0; lines.len()];
    loop {
        let handle = chip.get_lines(&lines)?.request(
            LineRequestFlags::INPUT,
            &initial_values,
            "multiread",
        )?;
        println!("Values: {:?}", handle.get_values()?);
        thread::sleep(time::Duration::from_millis(10));
    }
}

When I try to use a blocking iterator however (see code below), I get the following error:
Error: Error(Msg("lineevent ioctl failed"), State { next_error: Some(Sys(ENODEV)), backtrace: InternalBacktrace { backtrace: None } }).

ENODEV would appear to be a device driver issue ("the corresponding device driver does not support the ioctl() function.") but I'm not clear on that.

Has anyone bumped into this issue before?

Failing code:

fn do_main(args: Cli) -> errors::Result<()> {
    let mut chip = Chip::new("/dev/gpiochip7")?;
    let input = chip.get_line(0)?;

    // Show all state changes for this line forever
    for event in input.events(
        LineRequestFlags::INPUT,
        EventRequestFlags::BOTH_EDGES,
        "rust-gpio",
    )? {
        println!("{:?}", event?);
    }

    Ok(())
}

Ambiguity of Line.request()'s default arg polarity in light of ACTIVE_LOW outputs

Background

Line.request() accepts LineRequestFlags including ACTIVE_LOW. This inverts the polarity of the value in the LineHandle::get_value() and LineHandle::set_value() APIs to mean:

  • 0 -> inactive -> electrically high
  • 1 -> active -> electrically low

Problem

Line.request() also accepts a default argument which is currently documented as:

For an output, the default parameter specifies the value the line should have when it is configured as an output.

The polarity of this argument is not clear.

If I do this:

line.request(LineRequestFlags.ACTIVE_LOW, 0, "me");

does 0 mean:

  • inactive -> electrically high
  • electrically low

Somewhat related: #49

Multi-bit access

Hi! Do you have a plan, in the near future, to implement the access of multiple bits simultaneously? If not, do you want me to take a crack at it? I'm going to need this within the next few weeks.

Fix CI

CI is currently broken, also we need to migrate to GitHub MQ.
See #72

Version 0.2 is not on crates.io

Installation instructions in the readme uses version 0.2, but it looks like 0.2 is not published on crates.io

    Updating crates.io index
error: failed to select a version for the requirement `gpio-cdev = "^0.2"`
  candidate versions found which didn't match: 0.1.0
  location searched: crates.io index

Seeming deadlock on select!

Hello,

When using the following snippet in my code (as part of a Wiegand reader program) on a Raspberry Pi 0:

let monitor_0: Next<AsyncLineEventHandle> = events0.next();
let monitor_1: Next<AsyncLineEventHandle> = events1.next();

pin!(monitor_0, monitor_1);

select! {
    res = monitor_0 => { data = data << 1; },
    res = monitor_1 => { data = (data << 1) | 1 as u64; },
}

it seems like the code ends up in a Deadlock in the select! macro after running this snippet for 128 times, after which the program uses 100% CPU but neither of the Nexts ever completes.

events0 and events1 are AsyncLineEventHandles that come from Lines that come from the same Chip.

Introducing a millisecond delay at the very top increases the amount of bytes that can be read to 256.

Introducing a larger delay seems to remove the deadlock all together, but the ordering of the events is lost causing the data to become garbage as the order of the events determines the final data output.

I'm not certain if this is a gpio-cdev problem, a kernel problem, or if I'm simply doing this incorrectly.

Any feedback is highly appreciated.

Please include Async documentation on docs.rs

There is no mention of AsyncLineEventHandle anywhere on docs.rs. The only way to see this documentation is to build it locally with cargo doc, which is a bit of a pain, especially since many of the people using gpio-cdev are working on a raspberry pi, and if they are running the lite version of Raspbian, without a GUI, as I am, there is no browser, so cargo doc --open won't work. We need to manually go in and copy the docs directory to a different computer to read it.

When I did cargo doc on my project, it generated 16k files. Using scp to copy them over to my other computer was going to take like half an hour. I had to interrupt the scp copy operation, tar the 16k of doc html files, transfer it as one file, and unarchive it before opening it. The whole process took like 20 minutes. It would have been nice if I could have just found the documentation on docs.rs in seconds, as I do with most other documentation.

Update to Tokio 1

Tokio 1 with long stability guarantee is out. It would be nice if at some point gpio-cdev updates to this version.

Improve documentation about keeping LineHandles in scope

Hello!

First, thanks a lot for creating this crate and the work you put into it.

I have a small suggestion that could improve the documentation. Consider the following program which is a simplified version of the driveoutput.rs example:

use gpio_cdev::{Chip, LineRequestFlags};

fn main() {
    let mut chip = Chip::new("/dev/gpiochip0").unwrap();

    chip
        .get_line(4).unwrap()
        .request(LineRequestFlags::OUTPUT, 1, "set-pin").unwrap();
    
    println!("Press Enter to exit");
    let mut buf = String::new();
    ::std::io::stdin().read_line(&mut buf).unwrap();
}

This program compiles and does not panic, but seemingly does nothing as the pin remains in an unset state while the program waits on the user to press Enter. However, if we assign the LineHandle instance to a variable like this:

let _handle = chip
        .get_line(4).unwrap()
        .request(LineRequestFlags::OUTPUT, 1, "set-pin").unwrap();

Then the pin state is correctly set, presumably because the file descriptor contained inside the LineHandle doesn't immediately get dropped after request is called.

I think that it is important to note in the comments that the LineHandle needs to remain in scope or assigned to a persistent variable to prevent these sorts of "silent failures." It may be obvious in the above example, but in my case I do some initialization of the chip and store it inside a struct that is passed around; it took me about a day of debugging to realize that I needed initialize and store the LineHandle instance in addition to the Chip.

If you agree, then I will open a small PR to update the examples and docstrings to make note of this.

LineEventHandle drops events

LineEventHandle seems to be dropping events, as evident in the repro below, but also in a more optimized repro just filtering for FALLING_EDGES and knowing the min/max rate that these events are emitted, versus the actual interval between events.

Repro

The following snippet run on a Raspberry Pi Zero (ARM32).

for e in Chip::new("/dev/gpiochip0")?
            .get_line(pin)?
            .events(
                LineRequestFlags::INPUT,
                EventRequestFlags::BOTH_EDGES,
                "read",
            )? {
    println!("{}", e.event_type());
}

Observed

LineEventHandle yields multiple events of the same type in sequence.

Expected

LineEventHandle strictly alternates between each type.

Represent line levels as a bool

I know the char driver(s) expose bits as u8 values, but it might be easier to manipulate the values as booleans. Should we consider:

impl LineHandle {
    pub fn get_value(&self) -> Result<bool> { ... }
    pub fn set_value(&self, value: bool) -> Result<()> { ... }
    ...

Non-blocking polling of events

Hello,

Thank you for providing us with this wonderful library!
A missing feature of this library to me is the ability to do a non-blocking poll on the LineEventHandle struct.
Currently every method (.get_event(), .next() and .read_event()) is blocking.
Ideally we would have a sort of .has_event() -> bool function available to check whether there is an event available to process. This way we would take maximum advantage of the queue-like architecture of the new GPIO character device ABI.

Please let me know what you think of this suggestion.

Cheers,
Sven.

Please remove `backtrace` and `libc` dependency

Great library! It got me up and running on the new chardev gpio devices which was fantastic. I've been experimenting with using Rust on Linux SBCs. However the library isn't suitable for cross compiling currently; would it be possible to remove the extra C library dependencies?

My embedded device build system uses cross compiling for everything and the backtrace dependency (via both the error-chain and clap dependencies) makes cross compiling a pain. To get it to function correctly you have to use an obscure environment variable CARGO_TARGET_$(TRIPLET)_LINKER= to setup the appropriate cross linker.

Effectively the addition of a C library in the Rust toolchain eliminates almost all of the advantages of using Rust. It add unnecessary complications in a cross compiling scenario as now I have 2 more compilers settings to get correct (the Rust one, and the c compiler). Is it possible to remove the backtrace or use a Rust native one?

no method named `get_lines`

I'm trying to follow the mutliread.rs example but when I try to compile it then I get the following error:

error[E0599]: no method named get_lines found for type std::result::Result<gpio_cdev::Chip, gpio_cdev::errors::Error> in the current scope

I'm new to Rust so don't know exactly what I am doing and why this is going wrong!

-Andy.

Remove error-chain and manually implement an error type.

As discussed in #24

This would remove a dependency and give end users of the library more flexibility to handle errors how they wish.

error-chain is going to be deprecated in any case.

The new error type should endeavor to play nicely with failure without having a direct dependency on it.

Is kernel event queueing per-process?

Just looking for a bit of clarification here.

From what I understand, this is how it works:

  • When a GPIO pin's state changes (a button is pressed, or whatever), the CPU gets an interrupt, and the kernel is notified
  • The kernel checks if anyone is registered to be alerted of that pin's state change
  • If so, they have a dedicated queue that the event will be added to

Importantly, this queue is per-process.
As in, when our Rust application starts up, and uses gpio_cdev to register to be notified about a pin state change with:

let handle = chip.get_line(N)?.request(LineRequestFlags::INPUT, 0, "read-input")?;

The kernel sets up a queue specifically for this process, for this one line.

The alternative, which I'm really hoping isn't the case, is that there's one queue per line that the kernel maintains, and when your Rust application starts, and registers to be notified, the queue may already have events in it that may potentially need to be discarded.

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.