opencyphal / pycyphal Goto Github PK
View Code? Open in Web Editor NEWPython implementation of the Cyphal protocol stack.
Home Page: https://pycyphal.readthedocs.io/
License: MIT License
Python implementation of the Cyphal protocol stack.
Home Page: https://pycyphal.readthedocs.io/
License: MIT License
Currently, the serial transport does not respect the frame priority when scheduling transmission. Frame ordering is guaranteed within a transfer; transfers themselves, however, are unordered. That is, if there are multiple transfers awaiting transmission, their transmission slots will be allocated at random. The optimal behavior is to serve high-priority transfers first. This is the culprit:
The synchronization primitive needs to be replaced with a priority-aware alternative.
Running pyuavcan on a raspberry pi to log stuff into influx db, I encounter high CPU load on ~20 packets/s so I get packet loss. Profiling revealed a lot of time (>75%) spent in the dsdl signature calculation.
In parser.py
, there is a cache that is local to each instance of a type (_data_type_signature
), but that seems to not be in effect here (I did not dig deep to understand the lifecycle of the type objects).
Adding a simple cache to compute_signature
(signature.py
) helped solving my CPU load problem. From 100% with packet loss to ~50 % without packet loss. Profiling shows still some time calculating signatures, but magnitudes less impact than before.
See NerdyProjects@e358444 for a simple solution. As that is only python3, I did not create a pull request but leave this issue to further discussion.
In my opinion, the parser should get a type-global cache that would be even more effective, but my solution works easily, reliable and good enough.
Context: OpenCyphal-Garage/gui_tool#26
The following features are implemented in Nunavut but are not yet used in PyUAVCAN:
Max line length should be 120 chars.
The test suite consists mostly of complex functional tests that require a particular configuration of the environment. The configuration is enforced by the test script test.sh
, and also its much-simplified counterpart with reduced functionality for Windows test.ps1
.
Do we want to convert it into Tox?
CAN frames:
RX 14:34:43.053642 1004277D 85 AD 00 00 00 00 00 92 ........ 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.053643 1004277D 00 00 F3 F8 DD E9 27 32 ......'2 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.053644 1004277D 4B 05 40 00 1B F8 67 12 [email protected]. 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.053644 1004277D CD E0 01 D6 A3 6A 58 32 .....jX2 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.053644 1004277D 72 61 C0 C0 52 A0 18 12 ra..R... 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.054628 1004277D BA B5 DD 28 D1 B6 1F 32 ...(...2 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.054629 1004277D 00 06 7A 4E 7A 4E C9 12 ..zNzN.. 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.055632 1004277D 4E B3 39 B3 39 B3 39 32 N.9.9.92 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.055633 1004277D 4D 41 CC CC CC 3E EB 12 MA...>.. 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.055633 1004277D 51 B8 3E 99 99 19 3E 32 Q.>...>2 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.055634 1004277D A8 88 47 AA 08 A4 DA 12 ..G..... 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.055634 1004277D E8 30 0E 57 52 38 10 32 .0.WR8.2 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.056645 1004277D 06 A0 52 A0 52 A0 52 12 ..R.R.R. 125 uavcan.equipment.gnss.Fix2
RX 14:34:43.056646 1004277D A5 39 A5 39 A5 39 72 .9.9.9r 125 uavcan.equipment.gnss.Fix2
Properly decoded message (using libuavcan):
timestamp:
usec: 0
gnss_timestamp:
usec: 1490009682999539
gnss_time_standard: 2
num_leap_seconds: 27
longitude_deg_1e8: 3771557880
latitude_deg_1e8: 5560456250
height_ellipsoid_mm: 231369
height_msl_mm: 218122
ned_velocity: [-0.35791, 0.0379944, -0.426025]
sats_used: 7
status: 3
mode: 0
sub_mode: 0
covariance: [25.9062, 25.9062, 27.1406, 0.712402, 0.712402, 0.712402]
pdop: 2.65039
ecef_position_velocity:
-
velocity_xyz: [0.4, 0.36, 0.15]
position_xyz_mm: [2856814760, 2209238410, 5239887630]
covariance: [53, 53, 53, 0.705566, 0.705566, 0.705566]
PyUAVCAN output:
timestamp:
usec: 0 # UNKNOWN
gnss_timestamp:
usec: 1490009682999539
gnss_time_standard: 2 # UTC
num_leap_seconds: 27
longitude_deg_1e8: 3771557880
latitude_deg_1e8: 5560456250
height_ellipsoid_mm: 231369
height_msl_mm: 218122
ned_velocity: [-0.3579, 0.0380, -0.4260]
sats_used: 7
status: 3 # 3D_FIX
mode: 0 # SINGLE
sub_mode: 0
covariance: [25.9062, 25.9062, 27.1406, 0.7124, 0.7124, 0.7124]
pdop: 2.6504
ecef_position_velocity:
-
velocity_xyz: [0.400000, 0.360000, 0.150000]
position_xyz_mm: [2856814760, 2209238410, 5239887630]
covariance: [-0.0000, -0.0000, -0.0000, -0.0010, -0.0012, -0.0012]
Notice that the last covariance
field contains incorrect data.
Data type definition: https://github.com/UAVCAN/dsdl/tree/a7d19e2a07a0d3e2651fb80e60945d6b2bb7b296/uavcan/equipment/gnss
Basically, we need this reimplemented for PyUAVCAN v1.0: #53
@windelbouwman @adolfogc could use your help here!
Exceptions like below occur randomly, typically when the bus load exceeds 10% (at 1 Mbps):
Traceback (most recent call last):
File "./drwatson_zubax_gnss.py", line 144, in safe_spin
n.spin(timeout)
File "pyuavcan/uavcan/node.py", line 328, in spin
execute_once()
File "pyuavcan/uavcan/node.py", line 324, in execute_once
self._recv_frame(frame)
File "pyuavcan/uavcan/node.py", line 247, in _recv_frame
transfer.from_frames(transfer_frames)
File "pyuavcan/uavcan/transport.py", line 587, in from_frames
raise TransferError("Start of transmission set unexpectedly on frame {0}".format(idx))
uavcan.transport.TransferError: Start of transmission set unexpectedly on frame 4
This probably indicates that the library is losing frames. Libuavcan-based applications that simultaneously use the same bus do not experience any issues.
The abstract transport interface should expose a secondary diagnostic API for use with bus monitoring tools. The objective is to provide the foundation for building transport-agnostic utilities that can observe all traffic exchanged over the network and present it to the user in a human-friendly way at the transport frame level. Ideally, it should also be possible to reconstruct any subset of received frames into higher-level protocol constructs up to the application level: frames --> transfers --> messages/requests/responses.
There is no common entity modeling a transport frame in general, and I am not yet sure whether it's possible to define one because the variance there is huge.
Example from v0:
Relevant discussion in this thread starting with the specified post: https://forum.uavcan.org/t/yukon-design-megathread/390/106?u=pavel.kirienko
@bendyer The following works on Python 2.7, but doesn't on Python 3.4:
$ python3
Python 3.4.3 (default, Jul 28 2015, 18:20:59)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import uavcan
>>> uavcan.load_dsdl()
/usr/lib/python3/dist-packages/pkg_resources.py:1031: UserWarning: /home/pavel/.python-eggs is writable by group/others and vulnerable to attack when used with get_resource_filename. Consider a more secure location (set with .set_extraction_path or the PYTHON_EGG_CACHE environment variable).
warnings.warn(msg, UserWarning)
>>> import logging
>>> logging.root.setLevel(logging.DEBUG)
>>> import uavcan.node
>>> node = uavcan.node.Node([])
>>> node.listen('can0')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/node.py", line 122, in listen
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 168, in _recv
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 140, in _read
BlockingIOError: [Errno 11] Resource temporarily unavailable
>>> node.listen('slcan0')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/node.py", line 122, in listen
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 168, in _recv
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 140, in _read
BlockingIOError: [Errno 11] Resource temporarily unavailable
>>>
SLCAN backend also doesn't work on Python 3, but the symptoms are different:
>>> node.listen('/dev/ttyACM1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/node.py", line 101, in listen
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 279, in open
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/driver.py", line 295, in close
File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 475, in write
n = os.write(self.fd, d)
TypeError: 'str' does not support the buffer interface
>>> node.listen(b'/dev/ttyACM1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.4/dist-packages/uavcan-1.0.0dev3-py3.4.egg/uavcan/node.py", line 96, in listen
TypeError: startswith first arg must be bytes or a tuple of bytes, not str
>>>
I suggest to focus the development on Python 3 (while maintaining backward compatibility with 2.7), as it is already used as default in some Linux distributions, and it is going to be even more widespread once Ubuntu 16.04 is out.
Custom definitions should be passed to children processes.
Loading them from disk is a bad idea because definitions may diverge if the source files are changed while the application is running.
Implementation-specific fields of CompoundValue
such as mode
, is_union
, etc. may conflict with DSDL-defined fields.
A possible solution is to prefix these fields with underscore (a regular DSDL field always starts with a letter character so this rules out a conflict), and implement global accessors available via package namespace uavcan.
, e.g.:
def get_data_type_id(obj):
return obj._data_type_id
def set_data_type_id(obj, data_type_id):
obj._data_type_id = data_type_id
def is_union(obj):
return obj._is_union
def get_union_field(obj):
return obj._union_field
def set_union_field(obj, name):
obj._union_field = name
As for CompoundType.mode
, it does not seem to be used anywhere outside constructor, and if so it should be removed.
@bendyer do you think this is a sensible idea?
THIS ISSUE IS RELATED ONLY TO THE UAVCAN v0 (legacy) VERSION OF THE LIBRARY
Reported by @emrainey via Gitter. His pics:
C++ generated response:
Python-generated response:
My analysis:
Pavel Kirienko @pavel-kirienko 10:22
looks like the Python implementation is shifted one bit left
the C++ one actually looks correct
Pavel Kirienko @pavel-kirienko 10:32
The first two bytes are CRC16, ignore them.
The following 5 bits are padding, and then there are three bits of the tag, which are set to 4 in both cases, which indicatesstring_value
. So far so good.
The entire next byte is the length prefix of the string; its correct value is 6 (becauserecord
). We can see that the length prefix in C++ is set correctly, but in Python it equals 0xC (12), which is (6 << 1). There are two possible reasons:
- The union tag length is computed incorrectly. Although this function looks correct: https://github.com/UAVCAN/pyuavcan/blob/0fd4122fe6e128aabd00d5ca88aba5cc82518c9c/uavcan/transport.py#L111-L112
- The array length prefix is computed incorrectly. Although this function also looks correct: https://github.com/UAVCAN/pyuavcan/blob/0fd4122fe6e128aabd00d5ca88aba5cc82518c9c/uavcan/transport.py#L114
I suggest you to put a breakpoint here and evaluate what's happening during the packing https://github.com/UAVCAN/pyuavcan/blob/0fd4122fe6e128aabd00d5ca88aba5cc82518c9c/uavcan/transport.py#L606
I am developing a small application based on libcanard and am using the GuiTool for configuration.
As I wanted to implement param.GetSet for string parameters, I noticed that Value
does not get decoded properly for string_value
although I implemented it to my understanding.
Debugging showed that in transport.py, around line 410, only 7 bits were used for the array size.
I fixed locally by calculating count_width as:
count_width = int(math.ceil(math.log(self._type.max_size + 1, 2))) or 1
To my understanding, even the DSDL for value
describes this prefix length field as 8 bits.
As I am getting GetSet requests with garbage data when trying to set string parameters using GuiTool, I am now questioning the stability of this library/toolkit and thought about opening this issue before submitting a pull request for this bug. Did I just trigger a codepath that has never been used before !? Nobody using string parameters? :D
Hi,
I am trying to use pyuavcan with python 2.7 but I am getting an error upon node initialization. I installed pyuavcan using pip install uavcan
and pip install pyserial
for SLCAN.
test.py
:
#!/usr/bin/python
import uavcan
node_id = 123
node_info = uavcan.protocol.GetNodeInfo.Response()
node_info.name = 'test'
node_info.software_version.major = 1
node_info.hardware_version.unique_id = map(ord,'12345')
node = uavcan.make_node('/dev/ttyACM0', node_id=node_id, node_info=node_info)
Output:
No handlers could be found for logger "uavcan.driver.slcan"
Traceback (most recent call last):
File "./test.py", line 10, in <module>
node = uavcan.make_node('/dev/ttyACM0', node_id=node_id, node_info=node_info)
File "/usr/local/lib/python2.7/dist-packages/uavcan/node.py", line 501, in make_node
can = driver.make_driver(can_device_name, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/uavcan/driver/__init__.py", line 33, in make_driver
return SLCAN(device_name, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/uavcan/driver/slcan.py", line 655, in __init__
raise sig
TypeError: monotonic() takes no arguments (1 given)
In the README it says
If Python 2.7 is used, additional dependencies are needed - refer to setup.py for more info.
but I can't find any additional information.
I have tried using python 3 and everything works fine.
This issue is a placeholder; will elaborate later. Those interested can track the progress in the presentation
branch.
Offending line: https://github.com/UAVCAN/pyuavcan/blob/master/uavcan/node.py#L96
device.startswith("/dev")
Tag v1.0.0.dev4 has been pushed a while ago, but PyPi still serves v1.0.0.dev3.
@bendyer am I using Travis wrong?
The allocator (and its client) should be implemented inside pyuavcan/application/pnp.py
.
This is idea for an improvement, please feel free to close this ticket if required.
Currently there are several interlinked git repositories which depend upon eachother by the use of git submodules. It often makes sense to split a codebase into several git repositories, but it also has its downsides. Some difficulties that might occur:
In order to reduce the amount of repositories, I would suggest to regroup several repositories into a single repository like this:
uavcan
: a merger of pyuavcan
, specification
, libuavcan
, 'dsdl,
libuavcan,
libcanard`. My suggestion is to bundle specification, compiler, standard definition and reference implementation, since these are tightly coupled and must be in sync at all times.gui_tool
and uavcan.rs
: these are more building upon the uavcan
repository. Optionally those could be subfolders of the bigger repository.Let me know what you think!
Context: OpenCyphal/nunavut#23
Currently, mangling is only done for attributes. If one attempted to create a namespace named like if
, things would break. We can't resolve this problem within PyUAVCAN, it has to be supported by Nunavut.
the defer function can't pass parameters as input to the callback function, can you do some change on this function?
Sometimes I hit a bug or an exception in a callback. However this never hits my debugger because UAVCAN catches them and ignores them.
Example: https://github.com/UAVCAN/pyuavcan/blob/master/uavcan/node.py#L217-L220
I would suggest passing a flag to the node or the spin method, something like node.spin(catch_callback_exceptions=False)
to let advanced users use exceptions like they want.
What do you think? Maybe I am missing something on this design choice?
obj = uavcan.protocol.debug.KeyValue()
obj.value = 1.23 # Fine
obj.key = 'Key' # AttributeError('key cannot be set directly')
Expected result:
[uavcan.equipment.hardpoint.Status]
# Received struct ts_m=126159.735617 ts_utc=1448947534.085266 snid=125
hardpoint_id: 7
payload_weight: nan
payload_weight_variance: inf
status: 0
Actual result:
uavcan.equipment.hardpoint.Status(hardpoint_id=7, payload_weight=131008.0, payload_weight_variance=65536.0, status=0)
Run the RX logic in a separate thread. Raise an error during instantiation if PySerial is not available. Support the SLCAN command line extensions as documented in the Babel datasheet.
This is crucial for supporting non-Linux-based OS (on Linux there's SocketCAN which covers SLCAN as well). Many low-cost USB-CAN adapters out there support SLCAN.
IMO this should be released, because fixing it makes pyuavcan much more usable, especially on slower hardware.
Hello.
This is probably a minor issue. In Windows, uavcan being imported from within drive, other than the one, containing python lib, throws exception:
c:\users\captain\appdata\local\programs\python\python36-32\lib\site-packages\uavcan\dsdl\common.py in pretty_filename(filename)
39 '''Returns a nice human readable path to 'filename'.'''
40 a = os.path.abspath(filename)
---> 41 r = os.path.relpath(filename)
42 return a if '..' in r else r
43
c:\users\captain\appdata\local\programs\python\python36-32\lib\ntpath.py in relpath(path, start)
584 if normcase(start_drive) != normcase(path_drive):
585 raise ValueError("path is on mount %r, start on mount %r" % (
--> 586 path_drive, start_drive))
587
588 start_list = [x for x in start_rest.split(sep) if x]
ValueError: path is on mount 'c:', start on mount 'D:'
Quite eloquent, I guess. The fix might be to catch ValueError and return filename as is.
Hi Pavel,
I have a Raspberry Pi plugged into a Babel which is then connected into a CAN network that has a GNSS 2 and a Pixhawk. On the Raspberry Pi I am running a pyuavcan application and I am having a lot of performance issues if there is any non-trivial amount of traffic on the CAN network. Running htop
shows that pyuavcan pegs one cpu core at 100%. With #33 now I am also getting a significant delay between the message being sent on the CAN network and it appearing in my pyuavcan application.
Can I suggest the addition of a CAN acceptance filter (or a blacklist) in pyuavcan that stops pyuavcan from processing certain messages and services? That way I can then filter out any high speed messages and save a lot of processing time.
Thanks.
The current library API could greatly benefit from built-in async support in newer versions of Python. Compatibility with Python 2.x can be dropped easily at this point.
Release on PyPI automatically when pushed to master.
Discovered by Kent Martin on the mailing list:
broadcast(uavcan.thirdparty.rfd.equipment.eng_mon.EngineStatus.Request(clearServiceData=0))
2018-05-21 11:08:39,887 ERROR uavcan_gui_tool.active_data_type_detector: Could not detect data type name from transfer Transfer(id=1, source_node_id=20, dest_node_id=None, transfer_priority=30, payload=bytearray(b'\x00'))
Traceback (most recent call last):
File "C:\Program Files (x86)\UAVCAN\UAVCAN GUI Tool\uavcan_gui_tool\active_data_type_detector.py", line 37, in _on_transfer
dtname = uavcan.get_uavcan_data_type(tr.payload).full_name
File "C:\Program Files (x86)\UAVCAN\UAVCAN GUI Tool\uavcan\transport.py", line 33, in get_uavcan_data_type
return obj._type
AttributeError: 'bytearray' object has no attribute '_type'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Program Files (x86)\UAVCAN\UAVCAN GUI Tool\uavcan_gui_tool\active_data_type_detector.py", line 42, in _on_transfer
dtname = uavcan.DATATYPES[(tr.data_type_id, kind)].full_name
KeyError: (210, 1)
@bendyer I suggest to move your developments into this repository, as was discussed about two months ago.
Context: https://forum.uavcan.org/t/si-namespace-design/207/5?u=pavel.kirienko
This feature will be needed for #56 (specifically for the subscriber tool)
@bendyer I suppose we'll need to tackle this together when the core functionality is more or less stable. Suggested location is http://uavcan.org/Implementations/Pyuavcan/Tutorials.
Required for #37
A suite of very basic low-level command-line tools for automation of production and testing workflows:
ps
, and then exits. Calls uavcan.node.GetInfo
on each node if a local node ID is specified.rostopic echo
. Prints YAML into stdout. This tool should support subscription to multiple subjects concurrently with time synchronization -- this is a commonly occurring usage scenario.The need for the above has been indicated by our actual experience of deploying UAVCAN applications in the field. The following could theoretically be useful but we have no empirical evidence of that so far:
These are very low-level basic building blocks that are not to be confused with Yukon. They are to be CLI-only so that one could easily automate a basic task in bash or whatever.
>>> import uavcan
>>> uavcan.Timestamp
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Module' object has no attribute 'Timestamp'
>>> uavcan.TYPENAMES['uavcan.Timestamp']
uavcan.Timestamp
Reproducible in Python 3.5 and Python 2.7.
There are two transport configurations supported by PyUAVCAN.
Non-redundant | Redundant |
---|---|
Redundant configurations are supported through a pseudo-transport that implements the transport interface itself.
It is expected that a high-throughput PyUAVCAN application might want to offload some of the work performed by the protocol stack into a dedicated worker process. The offloading can be implemented cleanly by injecting a thin proxy object into the stack either directly above or directly below the Presentation layer.
Considering that the Transport layer is equipped with a very generic interface, it might be preferable to place the proxy under the presentation layer where it would implement the transport interface itself quite like the redundant transport implementation. Additionally, this approach would also allow the user to offload each of the redundant transports individually which may be useful depending on the load profile. If this approach is chosen, we will need a transport implementation that upon instantiation would spawn a new worker process and initialize a specified transport instance inside the worker, providing the calling process with a transparent interface behaving like the remote instance.
The downside of the transport-level offloading is that serialization/deserialization will be performed in the master process, which may theoretically amount to significant work. An alternative would be to add the proxy above the presentation layer, but the problem here is that it would be much harder to do so cleanly because the presentation layer lacks a generic abstract interface like the transport layer does.
Couldn't figure this out yet, some help would be welcome. See the context on StackOverflow: https://stackoverflow.com/questions/56565861/referring-to-class-attributes-of-a-generic-type
I understood that the TAO (tail array optimization) will be removed from the protocol. Is this already the case for pyuavcan? If I check the sourcecode, I think TAO is still in use.
Add a class containing a real-time (non-bidirectional) implementation of the Olson algorithm into pyuavcan.util._olson_latency_estimator.py
.
Paper: https://files.zubax.com/products/com.zubax.babel/olson_sensor_synchronization.pdf
One time synchronization, search for "Olson": https://forum.uavcan.org/t/alternative-transport-protocols/324
The old implementation can be found here (the source time resolver class is irrelevant and actually harmful because it breaks encapsulation):
The new implementation need not be API compatible with the old one, obviously; it's just an example.
Needs further coverage, in particular:
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.