bxparks / epoxyduino Goto Github PK
View Code? Open in Web Editor NEWCompile and run Arduino programs natively on Linux, MacOS and FreeBSD.
License: MIT License
Compile and run Arduino programs natively on Linux, MacOS and FreeBSD.
License: MIT License
Hi there,
any many greetings from the Open Source Ventilator project!
I have tried to integrate available code with UnixHostDuino but get this error:
make: *** No rule to make target 'OpenSoftwareVentilator.ino', needed by 'OpenSoftwareVentilator.o'. Stop.
Code:
https://github.com/covid-supplies/Open-Source-Ventilator/blob/master/Dockerfile
Hello Brian, could you please add this patch to IPAddress
I need this in order to enhance my tests around EspMock / TinyMqtt
Best regards
diff --git a/cores/epoxy/IPAddress.cpp b/cores/epoxy/IPAddress.cpp
index a7df382..3e835d6 100644
--- a/cores/epoxy/IPAddress.cpp
+++ b/cores/epoxy/IPAddress.cpp
@@ -114,3 +114,17 @@ size_t IPAddress::printTo(Print& p) const
n += p.print(_address.bytes[3], DEC);
return n;
}
+
+String IPAddress::toString() const
+{
+ String s;
+
+ size_t n = 0;
+ for (int i =0; i < 3; i++)
+ {
+ s += String(_address.bytes[i]);
+ s += '.';
+ }
+ s += String(_address.bytes[3]);
+ return s;
+}
diff --git a/cores/epoxy/IPAddress.h b/cores/epoxy/IPAddress.h
index 2c0c1b6..5e1e504 100644
--- a/cores/epoxy/IPAddress.h
+++ b/cores/epoxy/IPAddress.h
@@ -67,6 +67,7 @@ public:
IPAddress& operator=(uint32_t address);
virtual size_t printTo(Print& p) const;
+ String toString() const;
};
const IPAddress INADDR_NONE(0,0,0,0);
I cant compile EEPROM.h on my laptop because the are used avr functions:
I am using Platformio, not makefile, but I think it is not correct.
Thanks for an awesome library!
I'm noticing that the end-of-line convention in the Print class has been changed recently. The documentation in the release notes is clear and I realize that this is an expected change. However, it does break unit tests. I think the change could also break programs that parse the serial output of an Arduino program.
Isn't it desirable for the EpoxyDuino Print class be as similar to the "real" Print class as possible? Can you provide a bit of background on what the issue was that prompted change?
I'm trying to figure out how best to clean up breaking unit tests and knowing the full context of the change would be most helpful.
Thanks in advance!
Seeing this when running make
. Any thoughts?
The Arduino build system is very modular you can add your own libraries, boards and build tools.
Would it be possible to make UnixHostDuino a build tool which can be used from ArduinoIDE and ArduinoCLI. For example UnixHostDuino could provide an own platform with the mocks and build tools. In the ArduinoIDE we the select "UnixHostDuino" instead of "Arduino Uno" as board and then can "verify" the sketch, which compiles the sketch to a native executable and place it in the sketch folder.
Hi I have a feature request. I don't know how hard it would be but is it possible to add ISR from <avr/interrupts.h> and <avr/io.h>? Would be nice to test in this environment.
As noted in the README.md, if the executable (e.g. SampleTest.out
) is piped to a screen pager like less(1)
or more(1)
, sometimes (not all the time) the executable hangs and displays nothing on the pager program. The program seems to be blocking on the standard input. The problem is probably caused by an interaction between how the screen pager manipulates the stdin
and how I place the stdin into "raw" mode in Arduino.cpp
. I would love to fix this, but I don't know enough about Unix tty device configuration to figure this out.
In the meantime, the workaround is to explicitly redirect the stdin
using /dev/null
, like this:
$ ./SampleTest.out | less # hangs
$ ./SampleTest.out < /dev/null | less # works
My sample failing test (that works on an Arduino AVR Nano) is
test(T1272_GoldStandard_Test_snprintf_9) {
char buff128[128] = "?";
auto str1 = "Resolved";
auto str2 = F("Hello9");
auto str3 = F("not this");
str3 = F("World");
snprintf(buff128, ARRAY_SIZE(buff128), "<%s><%S><%S>", str1, str2, str3);
assertEqual("<Resolved><Hello9><World>", buff128);
}
Note that traditionally (whatever that means), '%S' is used for UNICODE(wchar_t) strings, but AVR uses it for ASCII strings in Flash(Programme) memory..
When compiling my test, it gives the warning:
TestGetFlashString.ino: In member function ‘virtual void test_T1272_GoldStandard_Test_snprintf_9::once()’:
TestGetFlashString.ino:34:42: warning: format ‘%S’ expects argument of type ‘wchar_t*’, but argument 5 has type ‘const __FlashStringHelper*’ [-Wformat=]
34 | snprintf(buff128, ARRAY_SIZE(buff128), "<%s><%S><%S>", str1, str2, str3);
| ^~~~~~~~~~~~~~ ~~~~
| |
| const __FlashStringHelper*
For an application that normally runs on an ESP32, I use EpoxyDuino to run unit tests and for other maintenance tasks on the build host, using platformio's "native" build environment. It works well when "native" refers to a Linux host, but there are a few issues when "native" is a Windows host compiled via MINGW. I have a patch to fix these problems, which I will submit as a PR. I am creating this issue as documentation and explanation of the problems and their solutions.
With these changes, EpoxyDuino works on Windows under MINGW.
While trying to root-cause #2 I ran into a different issue.
I am running with a modified PipeFail.ino. With 2000 I can't reproduce this issue nor #2.
diff --git a/examples/PipeFail/PipeFail.ino b/examples/PipeFail/PipeFail.ino
index c46087d..d2e7ee6 100644
--- a/examples/PipeFail/PipeFail.ino
+++ b/examples/PipeFail/PipeFail.ino
@@ -18,7 +18,7 @@
// * Ubuntu 20.04: 300-1000
// * MacOS 10.13: ~1000
// * FreeBSD 12: 1000-2000
-const int NUM_LINES = 2000;
+const int NUM_LINES = 20000;
const char LINE[] = "Reproduce https://github.com/bxparks/EpoxyDuino/issues/2";
void setup() {
Now when I run PipeFail.out it looks like this:
mmrazik@t7610:~/github/EpoxyDuino/examples$ PipeFail/PipeFail.out
0 (0): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
1 (67): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
2 (134): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
3 (201): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
...
14784 (990528): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
14785 (990595): Reproduce https://github.com/bxparks/EpoxyDuino/issues/2
mmrazik@t7610:~/github/EpoxyDuino/examples$ /bxparks/EpoxyDuino/issues/2
It fails at different places but usually after 10k iterations.
It turns out the putchar
in StdioSerial::write
returns EOF
. When I played with it turned out errno is set to EAGAIN (Resource temporarily unavailable).
The following patch fixes it for me:
index b1b985c..9004e22 100644
--- a/StdioSerial.cpp
+++ b/StdioSerial.cpp
@@ -4,10 +4,18 @@
*/
#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
#include "StdioSerial.h"
size_t StdioSerial::write(uint8_t c) {
int result = putchar(c);
+ int max_retries = 10;
+ while (result == EOF && errno == EAGAIN && max_retries > 0) {
+ max_retries--;
+ usleep(1000);
+ result = putchar(c);
+ }
return (result == EOF) ? 0 : 1;
}
When trying to use the library Adafruit_Motor_Shield_V2_Library with EpoxyDuino, it fails to compile because the Adafruit library includes some files in a subdirectory "utilitiy". If I hack the library source in my Arduino libraries directory to put all the files in the top-level library directory, it works as expected.
The older Arduino library folder format allows for source in a "utility" directory; see the Arduino library specification. EpoxyDuino should handle those extra files.
Hello bxparks
My project (TinyMqtt) unit tests needs to simulate the time.
This helps to
The last point is important, because AUnit fails if unit tests take too long !
I've modified Arduino.* in order to achieve this.
All is working very well for TinyMqtt / EspMock with these modifications.
By the way, thank you for mentioning EspMock in your README, this mock now support network simulation !
Here are the modifications:
diff --git a/cores/epoxy/Arduino.cpp b/cores/epoxy/Arduino.cpp
index 1f196da..387dbda 100644
--- a/cores/epoxy/Arduino.cpp
+++ b/cores/epoxy/Arduino.cpp
@@ -17,6 +17,9 @@
#include <time.h> // clock_gettime()
#include "Arduino.h"
+unsigned long epoxy_micros;
+bool epoxy_real_time = true;
+
// -----------------------------------------------------------------------
// Arduino methods emulated in Unix
// -----------------------------------------------------------------------
@@ -36,25 +39,94 @@ int analogRead(uint8_t /*pin*/) { return 0; }
void analogWrite(uint8_t /*pin*/, int /*val*/) {}
unsigned long millis() {
- struct timespec spec;
- clock_gettime(CLOCK_MONOTONIC, &spec);
- unsigned long ms = spec.tv_sec * 1000U + spec.tv_nsec / 1000000UL;
- return ms;
+ if (epoxy_real_time)
+ {
+ struct timespec spec;
+ clock_gettime(CLOCK_MONOTONIC, &spec);
+ unsigned long ms = spec.tv_sec * 1000U + spec.tv_nsec / 1000000UL;
+ return ms;
+ }
+ else
+ {
+ return epoxy_micros / 1000;
+ }
+}
+
+void set_real_time()
+{
+ epoxy_real_time = true;
+}
+
+void set_seconds(unsigned long s)
+{
+ epoxy_micros = s * 1000 * 1000;
+ epoxy_real_time = false;
+}
+
+void add_seconds(unsigned long s)
+{
+ epoxy_micros += s * 1000 * 1000;
+ epoxy_real_time = false;
+}
+
+void set_millis(unsigned long ms)
+{
+ epoxy_micros = 1000 * ms;
+ epoxy_real_time = false;
+}
+
+void add_millis(unsigned long delta_ms)
+{
+ epoxy_micros += 1000 * delta_ms;
+ epoxy_real_time = false;
+}
+
+void set_micros(unsigned long us)
+{
+ epoxy_micros = us;
+ epoxy_real_time = false;
+}
+
+void add_micros(unsigned long delta_us)
+{
+ epoxy_micros += delta_us;
+ epoxy_real_time = false;
}
unsigned long micros() {
- struct timespec spec;
- clock_gettime(CLOCK_MONOTONIC, &spec);
- unsigned long us = spec.tv_sec * 1000000UL + spec.tv_nsec / 1000U;
- return us;
+ if (epoxy_real_time)
+ {
+ struct timespec spec;
+ clock_gettime(CLOCK_MONOTONIC, &spec);
+ unsigned long us = spec.tv_sec * 1000000UL + spec.tv_nsec / 1000U;
+ return us;
+ }
+ else
+ {
+ return 1000*epoxy_micros;
+ }
}
void delay(unsigned long ms) {
- usleep(ms * 1000);
+ if (epoxy_real_time)
+ {
+ usleep(ms * 1000);
+ }
+ else
+ {
+ epoxy_micros += 1000 * ms;
+ }
}
void delayMicroseconds(unsigned int us) {
- usleep(us);
+ if (epoxy_real_time)
+ {
+ usleep(us);
+ }
+ else
+ {
+ delay(1000*us);
+ }
}
unsigned long pulseIn(
diff --git a/cores/epoxy/Arduino.h b/cores/epoxy/Arduino.h
index 6efd1e7..ce2b402 100644
--- a/cores/epoxy/Arduino.h
+++ b/cores/epoxy/Arduino.h
@@ -223,8 +223,18 @@ void analogWrite(uint8_t pin, int val);
void analogWriteRange(uint32_t range);
#endif
+// Simulated time
+void set_millis(unsigned long);
+void add_millis(unsigned long);
+void set_micros(unsigned long);
+void add_micros(unsigned long);
+void add_seconds(unsigned long);
+void set_seconds(unsigned long);
+void set_real_time();
+
unsigned long millis();
unsigned long micros();
+
void delay(unsigned long ms);
void delayMicroseconds(unsigned int us);
Hi folks, I'm having a problem when I try to use the U8glib library. In my Makefile I have ARDUINO_LIBS := U8glib
, and my .ino
file looks like this:
#include <Arduino.h>
#include <U8glib.h>
U8GLIB_SH1106_128X64 My_u8g_Panel(U8G_I2C_OPT_NONE);
When I run make, I get this error:
Undefined symbols for architecture x86_64:
"U8GLIB::initI2C(_u8g_dev_t*, unsigned char)", referenced from:
U8GLIB::U8GLIB(_u8g_dev_t*, unsigned char) in game.o
"_u8g_DrawGlyph", referenced from:
U8GLIB::write(unsigned char) in game.o
"_u8g_dev_sh1106_128x64_i2c", referenced from:
U8GLIB_SH1106_128X64::U8GLIB_SH1106_128X64(unsigned char) in game.o
I'm very new to Ardunio and EpoxyDuino, so I apologize if I haven't included enough info.
hi there,
Is your feature request related to a problem? Please describe.
according to the EpoxyDuino
documentation:
The location of the EpoxyDuino directory can be arbitrary
this is my current project structure:
.
├── EpoxyDuino
├── lab1_code
└── tests
this is my Makefile
:
APP_NAME := lab1_code_test
ARDUINO_LIBS := AUnit
include ../EpoxyDuino/EpoxyDuino.mk
the Makefile
is located in the tests
directory.
but i get linker errors when EpoxyDuino
is placed in the same directory as my code:
/usr/bin/ld: lab1_code_test.o: in function `test_robocar_valid::once()':
lab1_code_test.ino:(.text+0x72): undefined reference to `aunit::internal::compareNotEqual(void const*, void const*)'
/usr/bin/ld: lab1_code_test.ino:(.text+0x81): undefined reference to `aunit::Assertion::assertion(char const*, unsigned short, void const*, char const*, bool (*)(void const*, void const*), void const*)'
/usr/bin/ld: lab1_code_test.o: in function `test_robocar_turned_left::once()':
lab1_code_test.ino:(.text+0x121): undefined reference to `aunit::Assertion::assertionBool(char const*, unsigned short, bool, bool)'
/usr/bin/ld: lab1_code_test.o: in function `aunit::TestOnce::TestOnce()':
lab1_code_test.ino:(.text._ZN5aunit8TestOnceC2Ev[_ZN5aunit8TestOnceC2Ev]+0x20): undefined reference to `vtable for aunit::TestOnce'
/usr/bin/ld: lab1_code_test.o: in function `aunit::Test::init(__FlashStringHelper const*)':
lab1_code_test.ino:(.text._ZN5aunit4Test4initEPK19__FlashStringHelper[_ZN5aunit4Test4initEPK19__FlashStringHelper]+0x44): undefined reference to `aunit::Test::insert()'
/usr/bin/ld: lab1_code_test.o: in function `aunit::TestRunner::run()':
lab1_code_test.ino:(.text._ZN5aunit10TestRunner3runEv[_ZN5aunit10TestRunner3runEv]+0x5): undefined reference to `aunit::TestRunner::getRunner()'
/usr/bin/ld: lab1_code_test.o: in function `aunit::Assertion::Assertion()':
lab1_code_test.ino:(.text._ZN5aunit9AssertionC2Ev[_ZN5aunit9AssertionC2Ev]+0x15): undefined reference to `aunit::Test::Test()'
/usr/bin/ld: lab1_code_test.o: in function `aunit::TestRunner::runTest()':
lab1_code_test.ino:(.text._ZN5aunit10TestRunner7runTestEv[_ZN5aunit10TestRunner7runTestEv]+0x2c): undefined reference to `aunit::TestRunner::printStartRunner() const'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner7runTestEv[_ZN5aunit10TestRunner7runTestEv]+0x39): undefined reference to `aunit::Test::getRoot()'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner7runTestEv[_ZN5aunit10TestRunner7runTestEv]+0x63): undefined reference to `aunit::TestRunner::resolveRun() const'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner7runTestEv[_ZN5aunit10TestRunner7runTestEv]+0xba): undefined reference to `aunit::Test::getRoot()'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner7runTestEv[_ZN5aunit10TestRunner7runTestEv]+0x30e): undefined reference to `aunit::Test::resolve()'
/usr/bin/ld: lab1_code_test.o: in function `aunit::TestRunner::setupRunner()':
lab1_code_test.ino:(.text._ZN5aunit10TestRunner11setupRunnerEv[_ZN5aunit10TestRunner11setupRunnerEv]+0x43): undefined reference to `aunit::TestRunner::processCommandLine()'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner11setupRunnerEv[_ZN5aunit10TestRunner11setupRunnerEv]+0x50): undefined reference to `aunit::TestRunner::countTests()'
/usr/bin/ld: lab1_code_test.ino:(.text._ZN5aunit10TestRunner11setupRunnerEv[_ZN5aunit10TestRunner11setupRunnerEv]+0x60): undefined reference to `aunit::Test::getRoot()'
/usr/bin/ld: lab1_code_test.o: in function `aunit::Printer::getPrinter()':
lab1_code_test.ino:(.text._ZN5aunit7Printer10getPrinterEv[_ZN5aunit7Printer10getPrinterEv]+0x7): undefined reference to `aunit::Printer::sPrinter'
/usr/bin/ld: lab1_code_test.o: in function `aunit::Printer::setPrinter(Print*)':
lab1_code_test.ino:(.text._ZN5aunit7Printer10setPrinterEP5Print[_ZN5aunit7Printer10setPrinterEP5Print]+0xf): undefined reference to `aunit::Printer::sPrinter'
/usr/bin/ld: lab1_code_test.o:(.data.rel.ro+0x20): undefined reference to `aunit::TestOnce::loop()'
/usr/bin/ld: lab1_code_test.o:(.data.rel.ro+0x50): undefined reference to `aunit::TestOnce::loop()'
/usr/bin/ld: lab1_code_test.o:(.data.rel.ro+0x70): undefined reference to `typeinfo for aunit::TestOnce'
/usr/bin/ld: lab1_code_test.o:(.data.rel.ro+0x88): undefined reference to `typeinfo for aunit::TestOnce'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [../EpoxyDuino/EpoxyDuino.mk:210: lab1_code_test.out] Error 1
Describe the solution you'd like
i would like to be able to choose between using an absolute
path or relative
path to EpoxyDuino
Describe alternatives you've considered
the linker errors disappear when i use an environment variable, which i place in a .bashrc
file, i.e.:
export EPOXY_HOME=path/to/EpoxyDuino
and i update my Makefile
as follows:
APP_NAME := lab1_code_test
ARDUINO_LIBS := AUnit
include ${EPOXY_HOME}/EpoxyDuino.mk
Additional context
environment: Linux
compiler: /usr/bin/g++
sample test:
testing(must_be_true) {
bool isTrue = true;
assertTrue(isTrue);
}
commands:
make clean
make
make run
please advise.
thanks in advance,
rey malahay
snprintf_P is not supported (but vsnprintf_P is).
I fixed this locally by adding a line
diff --git a/cores/epoxy/pgmspace.h b/cores/epoxy/pgmspace.h
index 7dc881d..c7635ba 100644
--- a/cores/epoxy/pgmspace.h
+++ b/cores/epoxy/pgmspace.h
@@ -37,6 +37,7 @@
#define strchr_P strchr
#define strrchr_P strrchr
#define memcpy_P memcpy
+#define snprintf_P snprintf
#define vsnprintf_P vsnprintf
#endif
I wanted to use: serialEvent Arduino
Taking the '''StdioSerialEcho.ino''' example as base.
Changed the following lines:
void serialEvent(){
loopImplicitly();
}
void loop() {
// loopExplicitly();
}
I would expect this to read the keyboard input, but it doesn't.
I'm going to change the name of this project to EpoxyDuino to preemptively avoid any future trademark issues with the word "Unix". I think the 3 big user-visible changes are:
EPOXY_DUINO
will replace UNIX_HOST_DUINO
macro, but the latter will be kept for some time for backwards compatibility.EpoxyDuino.mk
file will replace UnixHostDuino.mk
, but I will point the old file to the new file for backwards compatibility.Initially, I didn't think that this project would become as useful as it has. I like self-descriptive names for projects, and at the time, UnixHostDuino seemed the most self-descriptive. But I have since found that I want to build other things on top of this project, so it seems best to avoid any potential legal problems.
"Epoxy" is a generic word, there is no trademark. It kinda sounds like "Posix-y" or "Unix-y". And it describes 2 materials that combine to make something stronger. This seems like an appropriate metaphor for this project that implements the Arduino API on Posix-y or Unix-like desktop class machines.
When I include the LoRa library in the make file it produces the following errors:
/Users/xyz/Documents/Arduino/libraries/LoRa/src/LoRa.cpp:283:42: error: use of undeclared identifier 'B1000'
if (readRegister(REG_FREQ_ERROR_MSB) & B1000) { // Sign bit is on
^
/Users/xyz/Documents/Arduino/libraries/LoRa/src/LoRa.cpp:369:24: error: use of undeclared identifier 'digitalPinToInterrupt'
SPI.usingInterrupt(digitalPinToInterrupt(_dio0));
Anyone know how to resolve this?
Hi,
I think EpoxyDuino is a terrific thing. Thanks! I recently wrote a few small test sketches to test my debugger and it was great that one could compile them on my Mac as well. However, terminal I/O did not work as expected. Looking into the code, I noticed that the core reads characters only when loop is called. However, there are a lot of programs that have internal loops (such as my small tictactoe program), and for this reason never see any input from the terminal.
So, I rewrote the read/peek/available to directly call Unix read, which is the most straightforward way, I guess. Well, one needs a one character buffer. I also added a flush to every output character to have the same functionality as in my Arduino sketches. I'll send you a PR. Perhaps, it is worthwhile to include these changes.
Best,
Bernhard
Hello, EpoxyDuino does not support std::make_unique.
I've used this implementation that work like a charm. https://github.com/bstreiff/cppbits/blob/master/make_unique.h
Is it possible to add it to EpoxyDuino please ?
Best regards
Hi,
I followed the setup instructions and the EpoxyDuino library causes a small warning when loading Arduino IDE:
Invalid library found in xxx/Arduino/libraries/EpoxyDuino: no headers files (.h) found in xxx/Arduino/libraries/EpoxyDuino
I know it is due to the fact this library isn't really an actual Arduino library but it's a bit anoying to see this warning every time you startup the IDE :-)
After some testing it seems that adding an empty EpoxyDuino.h
fixes the problem.
An alternative would be not to put this project inside the library folder to prevent the IDE to try to scan it.
Hi. Nice work!
I started something similar myself yesterday but after a while I was smart enough to do a quick search and found your project. After I ported my project to use your setup I noticed a 90% performance degradation in my HTTP server throughput when I was load testing it with ApacheBench. Profiler revealed that most of the time is actually spent in usleep function. After removing the usleep the performance was back where it should be.
Is the usleep really needed in the yield? Maybe it could be configurable.
Hello, I was doing some programming and I found that EpoxyDuino returns 4 on sizeof(int). while in arduino it is said that an int is 2 bytes.
I used the followingin the makefile.
EPOXY_CORE := EPOXY_CORE_AVR
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.